diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..04ba039aee --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 2 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..71cd7eda9d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: mailcow +custom: ["https://www.servercow.de/mailcow?lang=en#sal"] diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 24874a2540..0000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,29 +0,0 @@ -**Notice (you can delete this paragraph)** -Please understand that we use GitHub as **bug tracker and for feature requests only**. -For general (community) support and other discussion, you are welcome to visit us @ Freenode, #mailcow -Answering may take from a few seconds to hours, please be patient. :-) -Immediate commercial support is available via mailcow@incidents.servercow.de - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**System** - - OS: [e.g. iOS] - - Browser: [e.g. chrome, safari] - - Other clients involved: - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/Bug_report.yml b/.github/ISSUE_TEMPLATE/Bug_report.yml new file mode 100644 index 0000000000..f6fb7d2801 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_report.yml @@ -0,0 +1,172 @@ +name: ๐Ÿž Bug Report +description: Report a reproducible bug for mailcow. (NOT to be used for support questions.) +labels: ["bug"] +body: + - type: checkboxes + attributes: + label: Contribution guidelines + description: Please read the contribution guidelines before proceeding. + options: + - label: I've read the [contribution guidelines](https://github.com/mailcow/mailcow-dockerized/blob/master/CONTRIBUTING.md) and wholeheartedly agree + required: true + - type: checkboxes + attributes: + label: Checklist prior issue creation + description: Prior to creating the issue... + options: + - label: I understand that failure to follow below instructions may cause this issue to be closed. + required: true + - label: I understand that vague, incomplete or inaccurate information may cause this issue to be closed. + required: true + - label: I understand that this form is intended solely for reporting software bugs and not for support-related inquiries. + required: true + - label: I understand that all responses are voluntary and community-driven, and do not constitute commercial support. + required: true + - label: I confirm that I have reviewed previous [issues](https://github.com/mailcow/mailcow-dockerized/issues) to ensure this matter has not already been addressed. + required: true + - label: I confirm that my environment meets all [prerequisite requirements](https://docs.mailcow.email/getstarted/prerequisite-system/) as specified in the official documentation. + required: true + - type: textarea + attributes: + label: Description + description: Please provide a brief description of the bug. If applicable, add screenshots to help explain your problem. (Very useful for bugs in mailcow UI.) + validations: + required: true + - type: textarea + attributes: + label: "Steps to reproduce:" + description: "Please describe the steps to reproduce the bug. Screenshots can be added, if helpful." + placeholder: |- + 1. ... + 2. ... + 3. ... + validations: + required: true + - type: textarea + attributes: + label: "Logs:" + description: "Please take a look at the [official documentation](https://docs.mailcow.email/troubleshooting/debug-logs/) and post the last few lines of logs, when the error occurs. For example, docker container logs of affected containers. This will be automatically formatted into code, so no need for backticks." + render: plain text + validations: + required: true + - type: markdown + attributes: + value: | + ## System information + In this stage we would kindly ask you to attach general system information about your setup. + - type: dropdown + attributes: + label: "Which branch are you using?" + description: "#### Run: `git rev-parse --abbrev-ref HEAD`" + multiple: false + options: + - master (stable) + - staging + - nightly + validations: + required: true + - type: dropdown + attributes: + label: "Which architecture are you using?" + description: "#### Run: `uname -m`" + multiple: false + options: + - x86_64 + - ARM64 (aarch64) + validations: + required: true + - type: input + attributes: + label: "Operating System:" + description: "#### Run: `lsb_release -ds`" + placeholder: "e.g. Ubuntu 22.04 LTS" + validations: + required: true + - type: input + attributes: + label: "Server/VM specifications:" + placeholder: "Memory, CPU Cores" + validations: + required: true + - type: input + attributes: + label: "Is Apparmor, SELinux or similar active?" + placeholder: "yes/no" + validations: + required: true + - type: input + attributes: + label: "Virtualization technology:" + description: "LXC and OpenVZ are not supported!" + placeholder: "KVM, VMware ESXi, Xen, etc" + validations: + required: true + - type: input + attributes: + label: "Docker version:" + description: "#### Run: `docker version`" + placeholder: "20.10.21" + validations: + required: true + - type: input + attributes: + label: "docker-compose version or docker compose version:" + description: "#### Run: `docker-compose version` or `docker compose version`" + placeholder: "v2.12.2" + validations: + required: true + - type: input + attributes: + label: "mailcow version:" + description: "#### Run: ```git describe --tags `git rev-list --tags --max-count=1` ```" + placeholder: "2022-08x" + validations: + required: true + - type: input + attributes: + label: "Reverse proxy:" + placeholder: "e.g. nginx/Traefik, or none" + validations: + required: true + - type: textarea + attributes: + label: "Logs of git diff:" + description: "#### Output of `git diff origin/master`, any other changes to the code? Sanitize if needed. If so, **please post them**:" + render: plain text + validations: + required: false + - type: textarea + attributes: + label: "Logs of iptables -L -vn:" + description: "#### Output of `iptables -L -vn`" + render: plain text + validations: + required: true + - type: textarea + attributes: + label: "Logs of ip6tables -L -vn:" + description: "#### Output of `ip6tables -L -vn`" + render: plain text + validations: + required: true + - type: textarea + attributes: + label: "Logs of iptables -L -vn -t nat:" + description: "#### Output of `iptables -L -vn -t nat`" + render: plain text + validations: + required: true + - type: textarea + attributes: + label: "Logs of ip6tables -L -vn -t nat:" + description: "#### Output of `ip6tables -L -vn -t nat`" + render: plain text + validations: + required: true + - type: textarea + attributes: + label: "DNS check:" + description: "#### Output of `docker exec -it $(docker ps -qf name=acme-mailcow) dig +short stackoverflow.com @172.22.1.254` (set the IP accordingly, if you changed the internal mailcow network)" + render: plain text + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/Feature_request.yml b/.github/ISSUE_TEMPLATE/Feature_request.yml new file mode 100644 index 0000000000..39414f1be8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_request.yml @@ -0,0 +1,20 @@ +name: ๐Ÿ’ก Feature Request +description: Suggest an idea for mailcow. +labels: ["enhancement"] +body: + - type: textarea + attributes: + label: Summary + description: Please describe your idea in a reasonable amount of detail. + validations: + required: true + - type: textarea + attributes: + label: Motivation + description: Please describe how your idea would benefit you and other users. + validations: + required: true + - type: textarea + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..9d1990ffee --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: โ“ Community-driven support (Free) + url: https://docs.mailcow.email/#community-support-and-chat + about: Please use the community forum for questions or assistance + - name: ๐Ÿ”ฅ Premium Support (Paid) + url: https://www.servercow.de/mailcow?lang=en#support + about: Buy a support subscription for any critical issues and get assisted by the mailcow Team. See conditions! + - name: ๐Ÿšจ Report a security vulnerability + url: "mailto:info@servercow.de?subject=mailcow: dockerized Security Vulnerability" + about: Please give us appropriate time to verify, respond and fix before disclosure. diff --git a/.github/ISSUE_TEMPLATE/pr_to_nighty_template.yml b/.github/ISSUE_TEMPLATE/pr_to_nighty_template.yml new file mode 100644 index 0000000000..d9f8785847 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/pr_to_nighty_template.yml @@ -0,0 +1,3 @@ +## :file_folder: Modified files + + \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..68ead39fa6 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,38 @@ + + +## Contribution Guidelines + +* [ ] I've read the [contribution guidelines](https://github.com/mailcow/mailcow-dockerized/blob/master/CONTRIBUTING.md) and wholeheartedly agree them + + + +## What does this PR include? + +### Short Description + + + +### Affected Containers + + + + + +## Did you run tests? + +### What did you tested? + + + +### What were the final results? (Awaited, got) + + \ No newline at end of file diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000000..08f87ea375 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,25 @@ +{ + "enabled": true, + "timezone": "Europe/Berlin", + "dependencyDashboard": true, + "dependencyDashboardTitle": "Renovate Dashboard", + "commitBody": "Signed-off-by: milkmaker ", + "rebaseWhen": "auto", + "labels": ["renovate"], + "assignees": [ + "@magiccc" + ], + "baseBranches": ["staging"], + "enabledManagers": ["github-actions", "regex", "docker-compose"], + "ignorePaths": [ + "data\/web\/inc\/lib\/vendor\/**" + ], + "regexManagers": [ + { + "fileMatch": ["(^|/)Dockerfile[^/]*$"], + "matchStrings": [ + "#\\srenovate:\\sdatasource=(?.*?) depName=(?.*?)( versioning=(?.*?))?( extractVersion=(?.*?))?\\s(ENV|ARG) .*?_VERSION=(?.*)\\s" + ] + } + ] +} diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 24db8c039a..0000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,18 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 60 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - pinned - - security - - enhancement -# Label to use when marking an issue as stale -staleLabel: dunno -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false diff --git a/.github/workflows/assets/check_prs_if_on_staging.png b/.github/workflows/assets/check_prs_if_on_staging.png new file mode 100644 index 0000000000..2e0fc7ff87 Binary files /dev/null and b/.github/workflows/assets/check_prs_if_on_staging.png differ diff --git a/.github/workflows/check_if_support_labeled.yml b/.github/workflows/check_if_support_labeled.yml new file mode 100644 index 0000000000..87d93ecb65 --- /dev/null +++ b/.github/workflows/check_if_support_labeled.yml @@ -0,0 +1,37 @@ +name: Check if labeled support, if so send message and close issue +on: + issues: + types: + - labeled +jobs: + add-comment: + if: github.event.label.name == 'support' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Add comment + run: gh issue comment "$NUMBER" --body "$BODY" + env: + GH_TOKEN: ${{ secrets.SUPPORTISSUES_ACTION_PAT }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} + BODY: | + **THIS IS A AUTOMATED MESSAGE!** + + It seems your issue is not a bug. + Therefore we highly advise you to get support! + + You can get support either by: + - ordering a paid [support contract at Servercow](https://www.servercow.de/mailcow?lang=en#support/) (Directly from the developers) or + - using the [community forum](https://community.mailcow.email) (**Based on volunteers! NO guaranteed answer**) or + - using the [Telegram support channel](https://t.me/mailcow) (**Based on volunteers! NO guaranteed answer**) + + This issue will be closed. If you think your reported issue is not a support case feel free to comment above and if so the issue will reopened. + + - name: Close issue + env: + GH_TOKEN: ${{ secrets.SUPPORTISSUES_ACTION_PAT }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} + run: gh issue close "$NUMBER" -r "not planned" \ No newline at end of file diff --git a/.github/workflows/check_prs_if_on_staging.yml b/.github/workflows/check_prs_if_on_staging.yml new file mode 100644 index 0000000000..d951e28199 --- /dev/null +++ b/.github/workflows/check_prs_if_on_staging.yml @@ -0,0 +1,33 @@ +name: Check PRs if on staging +on: + pull_request_target: + types: [opened, edited] +permissions: {} + +jobs: + is_not_staging: + runs-on: ubuntu-latest + if: github.event.pull_request.base.ref != 'staging' #check if the target branch is not staging + steps: + - name: Send message + uses: thollander/actions-comment-pull-request@v3.0.1 + with: + github-token: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }} + message: | + Thanks for contributing! + + I noticed that you didn't select `staging` as your base branch. Please change the base branch to `staging`. + See the attached picture on how to change the base branch to `staging`: + + ![check_prs_if_on_staging.png](https://raw.githubusercontent.com/mailcow/mailcow-dockerized/master/.github/workflows/assets/check_prs_if_on_staging.png) + + - name: Fail #we want to see failed checks in the PR + if: ${{ success() }} #set exit code to 1 even if commenting somehow failed + run: exit 1 + + is_staging: + runs-on: ubuntu-latest + if: github.event.pull_request.base.ref == 'staging' #check if the target branch is staging + steps: + - name: Success + run: exit 0 diff --git a/.github/workflows/close_old_issues_and_prs.yml b/.github/workflows/close_old_issues_and_prs.yml new file mode 100644 index 0000000000..13dbac2794 --- /dev/null +++ b/.github/workflows/close_old_issues_and_prs.yml @@ -0,0 +1,37 @@ +name: 'Close stale issues and PRs' +on: + schedule: + # Once every day at midnight UTC + - cron: "0 0 * * *" + workflow_dispatch: # Allow to run workflow manually + issue_comment: # Run workflow on comments + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - name: Mark/Close Stale Issues and Pull Requests ๐Ÿ—‘๏ธ + uses: actions/stale@v11.0.0 + with: + repo-token: ${{ secrets.STALE_ACTION_PAT }} + days-before-stale: 60 + days-before-close: 7 + stale-issue-message: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. + stale-pr-message: > + This pull request has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. + exempt-issue-labels: "pinned,security,enhancement,investigating,neverstale" + exempt-pr-labels: "pinned,security,enhancement,investigating,neverstale" + stale-issue-label: "stale" + stale-pr-label: "stale" + exempt-draft-pr: "true" + close-issue-reason: "not_planned" + operations-per-run: "250" + ascending: "true" + #DRY-RUN + debug-only: "false" diff --git a/.github/workflows/image_builds.yml b/.github/workflows/image_builds.yml new file mode 100644 index 0000000000..aa56b03818 --- /dev/null +++ b/.github/workflows/image_builds.yml @@ -0,0 +1,42 @@ +name: Build mailcow Docker Images + +on: + push: + branches: [ "master", "staging" ] + workflow_dispatch: + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + docker_image_builds: + strategy: + matrix: + images: + - "acme-mailcow" + - "clamd-mailcow" + - "dockerapi-mailcow" + - "dovecot-mailcow" + - "netfilter-mailcow" + - "olefy-mailcow" + - "php-fpm-mailcow" + - "postfix-mailcow" + - "rspamd-mailcow" + - "sogo-mailcow" + - "unbound-mailcow" + - "watchdog-mailcow" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Setup Docker + run: | + curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh + sudo service docker start + - name: Prepair Image Builds + run: | + cp helper-scripts/docker-compose.override.yml.d/BUILD_FLAGS/docker-compose.override.yml docker-compose.override.yml + - name: Build Docker Images + run: | + docker compose build ${image} + env: + image: ${{ matrix.images }} diff --git a/.github/workflows/pr_to_nightly.yml b/.github/workflows/pr_to_nightly.yml new file mode 100644 index 0000000000..8d6b2524d0 --- /dev/null +++ b/.github/workflows/pr_to_nightly.yml @@ -0,0 +1,25 @@ +name: Create PR to merge to nightly from staging +on: + push: + branches: + - staging +jobs: + action-pull-request: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: Run the Action + uses: devops-infra/action-pull-request@v1.4.0 + with: + github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }} + title: Automatic PR to nightly from ${{ github.event.repository.updated_at}} + assignee: DerLinkman + source_branch: staging + target_branch: nightly + reviewer: DerLinkman + label: upstream + template: .github/ISSUE_TEMPLATE/pr_to_nighty_template.yml + get_diff: true diff --git a/.github/workflows/rebuild_backup_image.yml b/.github/workflows/rebuild_backup_image.yml new file mode 100644 index 0000000000..4490afee1d --- /dev/null +++ b/.github/workflows/rebuild_backup_image.yml @@ -0,0 +1,39 @@ +name: Build mailcow backup image + +on: + schedule: + # At 00:00 on Sunday + - cron: "0 0 * * 0" + workflow_dispatch: # Allow to run workflow manually + +jobs: + docker_image_build: + runs-on: ubuntu-latest + permissions: + packages: write + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Login to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v7 + with: + context: . + platforms: linux/amd64,linux/arm64 + file: data/Dockerfiles/backup/Dockerfile + push: true + tags: ghcr.io/mailcow/backup:latest \ No newline at end of file diff --git a/.github/workflows/update_postscreen_access_list.yml b/.github/workflows/update_postscreen_access_list.yml new file mode 100644 index 0000000000..ed63281c7e --- /dev/null +++ b/.github/workflows/update_postscreen_access_list.yml @@ -0,0 +1,39 @@ +name: Update postscreen_access.cidr + +on: + schedule: + # Monthly + - cron: "0 0 1 * *" + workflow_dispatch: # Allow to run workflow manually + +permissions: + contents: read # to fetch code (actions/checkout) + + +jobs: + Update-postscreen_access_cidr: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Generate postscreen_access.cidr + run: | + bash helper-scripts/update_postscreen_whitelist.sh + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v8 + with: + token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }} + commit-message: update postscreen_access.cidr + committer: milkmaker + author: milkmaker + signoff: false + branch: update/postscreen_access.cidr + base: staging + delete-branch: true + add-paths: | + data/conf/postfix/postscreen_access.cidr + title: '[Postfix] update postscreen_access.cidr' + body: | + This PR updates the postscreen_access.cidr using GitHub Actions and [helper-scripts/update_postscreen_whitelist.sh](https://github.com/mailcow/mailcow-dockerized/blob/master/helper-scripts/update_postscreen_whitelist.sh) \ No newline at end of file diff --git a/.gitignore b/.gitignore index 18df41c28d..416af9f7de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,79 @@ -rebuild-images.sh -data/conf/sogo/sieve.creds +!data/conf/nginx/dynmaps.conf +!data/conf/nginx/meta_exporter.conf +!data/conf/nginx/site.conf +!/**/.gitkeep +*.iml +.idea +.vscode/* +data/assets/ssl-example/* +data/assets/ssl/* +data/conf/borgmatic/ +data/conf/clamav/whitelist.ign2 +data/conf/dovecot/acl_anyone data/conf/dovecot/dovecot-master.passwd -mailcow.conf -mailcow.conf_backup -data/conf/nginx/*.active -data/conf/postfix/sql +data/conf/dovecot/dovecot-master.userdb +data/conf/dovecot/extra.conf +data/conf/dovecot/mail_replica.conf +data/conf/dovecot/global_sieve_* +data/conf/dovecot/last_login +data/conf/dovecot/lua +data/conf/dovecot/mail_plugins* +data/conf/dovecot/shared_namespace.conf +data/conf/dovecot/sni.conf +data/conf/dovecot/sogo-sso.conf +data/conf/dovecot/sogo_trusted_ip.conf data/conf/dovecot/sql +data/conf/dovecot/conf.d/fts.conf data/conf/nextcloud-*.bak -data/web/inc/vars.local.inc.php -data/assets/ssl/* -.vscode/* -data/web/.well-known/acme-challenge -data/web/nextcloud/ -data/conf/rspamd/local.d/* -data/conf/rspamd/override.d/* -!data/conf/nginx/dynmaps.conf -!data/conf/nginx/site.conf -!data/conf/nginx/meta_exporter.conf +data/conf/nginx/*.active +data/conf/nginx/*.bak data/conf/nginx/*.conf data/conf/nginx/*.custom -data/conf/nginx/*.bak -data/conf/dovecot/extra.conf -data/conf/rspamd/custom/* +data/conf/phpfpm/sogo-sso/sogo-sso.pass data/conf/portainer/ +data/conf/postfix/allow_mailcow_local.regexp +data/conf/postfix/custom_postscreen_whitelist.cidr +data/conf/postfix/custom_transport.pcre +data/conf/postfix/extra.cf +data/conf/postfix/sni.map +data/conf/postfix/sni.map.db +data/conf/postfix/sql +data/conf/postfix/dns_blocklists.cf +data/conf/postfix/dnsbl_reply.map +data/conf/rspamd/custom/* +data/conf/rspamd/local.d/* +data/conf/rspamd/override.d/* +data/conf/sogo/custom-theme.js +data/conf/sogo/plist_ldap +data/conf/sogo/plist_ldap.sh +data/conf/sogo/sieve.creds +data/conf/sogo/cron.creds +data/conf/sogo/custom-fulllogo.svg +data/conf/sogo/custom-shortlogo.svg +data/conf/sogo/custom-fulllogo.png +data/conf/acme/dns-01.conf data/gitea/ data/gogs/ -.github/ +data/hooks/dovecot/* +data/hooks/phpfpm/* +data/hooks/postfix/* +data/hooks/rspamd/* +data/hooks/sogo/* +data/hooks/unbound/* +data/web/templates/cache/* +!data/web/templates/cache/.gitkeep +data/web/.well-known/acme-challenge +data/web/css/build/0081-custom-mailcow.css +data/web/inc/vars.local.inc.php +data/web/inc/app_info.inc.php +data/web/nextcloud*/ +data/web/rc*/ docker-compose.override.yml +mailcow.conf +mailcow.conf_backup +rebuild-images.sh +refresh_images.sh +update_diffs/ +create_cold_standby.sh +!data/conf/nginx/mailcow_auth.conf +data/conf/postfix/postfix-tlspol \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8435709078..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -sudo: required -services: -- docker -script: -- echo 'Europe/Berlin' | MAILCOW_HOSTNAME=build.mailcow ./generate_config.sh -- docker-compose pull --ignore-pull-failures --parallel -- docker-compose build -- docker login --username=$DOCKER_HUB_USERNAME --password=$DOCKER_HUB_PASSWORD -- docker-compose push -branches: - only: - - master_disabled -env: - global: - - secure: MpxpTwD7f0CNEVLitSpVmocK7O9r+BwFE1deEHK4AlQo/oc9cOlhGe1EL3mx9zbglPmjlDg/8kMUGv6vSirIabfBo9Szjps76bHckFr9lr2Ykkg0e29oC8pgPpSXD1eY/1ZIN/FvIkxpUFLETo1okS/j9q/A0DCGFmti0n3EoMORsgRz9CpNAiEh0zpSd6+euPAGHuczuCrDuO84my9bIOCjA/+aPunHNeXiuM8yIM2SxCSyGtIKT0+jvquIvLF58VxivysXBlRfhDn8fhB09nXA2Ru/derYQACfcmNSn9Pd4bDpebPJW5B9H/XA8xjb58uKinUlncbAMB/QnxoT75j9YRWJZRSQ+34XNYP6ZgK9soZ2TC6djQyEKTUu45Kp/1s+poSn42m9jytJJTmmK0KxsZTRcC8JD5nrjIMZWPUNNTwC5L4+I7ZRWg2WooK3LNyq1Ng8Hn6W77wSgsvAJw2HD3Lx58AprGUhHuBeaIZRuSN9aKwZrl9vKQJLqPnOp/nF2EC6kot5HYYtcotGtETXPUDih21gWD5ZM2BqVqYfQQnJnNMgeYmMdj6QQuTFqhuNJf7hXRIRkTnD3j1gDOLKQZazW0+N2JE8XWDFwi6fKScDsxT85lJti9HmzHa7+k4RVHmUYuDgRoPuzUgjWHvPsiz3/Z8WQ9JYpH84S8w= - - secure: fWzZisT6nGDNL4lf6tXB07eFG2drgBakHxzdF/NFVvzuP861RFR6omuL+ED0PgXrEHDJBxaBLv52je8irmUXrAH1CNr7T8DWiZo/h5h609Uzr+38T1NnIu4krL0Wo6/CDwlLKnzqTq9yBIZLQSHVJmo8AOpo1JPIi2ajodqj9ZfmAxDQTQl+G6zvQjtqIkYHsHY7A44Rto0f14ykn7w2S82Jn6Ry89VNI5V1WEO3sMpM/XekNP/HokNcRIuntL/0+kuLvTJ5akGoTjBQxSnSW95opzPeGky74HRU2obExJYqKvF0VfVJRNAqejwjIiFIbbjqV0Sk5391kFuhuBErQQDM1bOHGdxZ41HsJH29qNWIl7C33Yl10qERoqecgsJ1N/bS2ZEmWqm/zQh5GClCXPvYmzEqMYsMGM3vjbKdjDlc1Wh2w/eFclsXN9LSXh1mc35rtj46frcT6e5Kof87AIfC9hTgDvk9kAsyjaHMkSHSZthbZXCIcsD8qriNm5UqfFBYD79mPIP1S2YMQ2jscCsjHOZgYVrcm0kzDF21J1w6H0Lo7d1jw37LYlegBdtLQ9gYgqY2D5m+nxWuVoD5FZmpR+5JGtK+ootyLFF8aiFoHXd4op1JCxRLjgkmnZKXzw3kTQSpE7oa7CgzchtQmK2nqcqla1b5Qk7ilVcjooo= diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e1bc1cdb70..5e194b20d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,9 +1,62 @@ -When a problem occurs, then always for a reason! What you want to do in such a case is: +# Contribution Guidelines +**_Last modified on 12th November 2025_** +First of all, thank you for wanting to provide a bugfix or a new feature for the mailcow community, it's because of your help that the project can continue to grow! + +As we want to keep mailcow's development structured we setup these Guidelines which helps you to create your issue/pull request accordingly. + +**PLEASE NOTE, THAT WE WILL CLOSE ISSUES/PULL REQUESTS IF THEY DON'T FULFILL OUR WRITTEN GUIDELINES WRITTEN INSIDE THIS DOCUMENT**. So please check this guidelines before you propose a Issue/Pull Request. + +## Topics + +- [Pull Requests](#pull-requests) +- [Issue Reporting](#issue-reporting) + - [Guidelines](#issue-reporting-guidelines) + - [Issue Report Guide](#issue-report-guide) + +## Pull Requests +**_Last modified on 15th August 2024_** + +However, please note the following regarding pull requests: + +1. **ALWAYS** create your PR using the staging branch of your locally cloned mailcow instance, as the pull request will end up in said staging branch of mailcow once approved. Ideally, you should simply create a new branch for your pull request that is named after the type of your PR (e.g. `feat/` for function updates or `fix/` for bug fixes) and the actual content (e.g. `sogo-6.0.0` for an update from SOGo to version 6 or `html-escape` for a fix that includes escaping HTML in mailcow). +2. **ALWAYS** report/request issues/features in the english language, even though mailcow is a german based company. This is done to allow other GitHub users to reply to your issues/requests too which did not speak german or other languages besides english. +3. Please **keep** this pull request branch **clean** and free of commits that have nothing to do with the changes you have made (e.g. commits from other users from other branches). *If you make changes to the `update.sh` script or other scripts that trigger a commit, there is usually a developer mode for clean working in this case.* +4. **Test your changes before you commit them as a pull request.** If possible, write a small **test log** or demonstrate the functionality with a **screenshot or GIF**. *We will of course also test your pull request ourselves, but proof from you will save us the question of whether you have tested your own changes yourself.* +5. **Please use** the pull request template we provide once creating a pull request. *HINT: During editing you encounter comments which looks like: ``. These can be removed or kept, as they will not rendered later on GitHub! Please only create actual content without the said comments.* +6. Please **ALWAYS** create the actual pull request against the staging branch and **NEVER** directly against the master branch. *If you forget to do this, our moobot will remind you to switch the branch to staging.* +7. Wait for a merge commit: It may happen that we do not accept your pull request immediately or sometimes not at all for various reasons. Please do not be disappointed if this is the case. We always endeavor to incorporate any meaningful changes from the community into the mailcow project. +8. If you are planning larger and therefore more complex pull requests, it would be advisable to first announce this in a separate issue and then start implementing it after the idea has been accepted in order to avoid unnecessary frustration and effort! +9. If your PR requires a Docker image rebuild (changes to Dockerfiles or files in data/Dockerfiles/), update the image tag in docker-compose.yml. Use the base-image versioning (e.g. ghcr.io/mailcow/sogo:5.12.4 โ†’ :5.12.5 for version bumps; append a letter for patch fixes, e.g. :5.12.4a). Follow this scheme. + +--- + +## Issue Reporting +**_Last modified on 12th November 2025_** + +If you plan to report a issue within mailcow please read and understand the following rules: + +### Security disclosures / Security-related fixes +- Security vulnerabilities and security fixes must always be reported confidentially first to the contact address specified in SECURITY.md before they are integrated, published, or publicly disclosed in issues/PRs. Please wait for a response from the specified contact to ensure coordinated and responsible disclosure. + +### Issue Reporting Guidelines + +1. **ONLY** use the issue tracker for bug reports or improvement requests and NOT for support questions. For support questions you can either contact the [mailcow community on Telegram](https://docs.mailcow.email/#community-support-and-chat) or the mailcow team directly in exchange for a [support fee](https://docs.mailcow.email/#commercial-support). +2. **ONLY** report an error if you have the **necessary know-how (at least the basics)** for the administration of an e-mail server and the usage of Docker. mailcow is a complex and fully-fledged e-mail server including groupware components on a Docker basement and it requires a bit of technical know-how for debugging and operating. +3. **ALWAYS** report/request issues/features in the english language, even though mailcow is a german based company. This is done to allow other GitHub users to reply to your issues/requests too which did not speak german or other languages besides english. +4. **ONLY** report bugs that are contained in the latest mailcow release series. *The definition of the latest release series includes the last major patch (e.g. 2023-12) and all minor patches (revisions) below it (e.g. 2023-12a, b, c etc.).* New issue reports published starting from January 1, 2024 must meet this criterion, as versions below the latest releases are no longer supported by us. +5. When reporting a problem, please be as detailed as possible and include even the smallest changes to your mailcow installation. Simply fill out the corresponding bug report form in detail and accurately to minimize possible questions. +6. **Before you open an issue/feature request**, please first check whether a similar request already exists in the mailcow tracker on GitHub. If so, please include yourself in this request. +7. When you create a issue/feature request: Please note that the creation does **not guarantee an instant implementation or fix by the mailcow team or the community**. +8. Please **ALWAYS** anonymize any sensitive information in your bug report or feature request before submitting it. + +### Issue Report Guide 1. Read your logs; follow them to see what the reason for your problem is. 2. Follow the leads given to you in your logfiles and start investigating. 3. Restarting the troubled service or the whole stack to see if the problem persists. -4. Read the [documentation](https://mailcow.github.io/mailcow-dockerized-docs/) of the troubled service and search it's bugtracker for your problem. +4. Read the [documentation](https://docs.mailcow.email/) of the troubled service and search its bugtracker for your problem. 5. Search our [issues](https://github.com/mailcow/mailcow-dockerized/issues) for your problem. -6. [Create an issue](https://github.com/mailcow/mailcow-dockerized/issues) over at our GitHub repository if you think your problem might be a bug or a missing feature you badly need. But please make sure, that you include **all the logs** and a full description to your problem. -8. Connect to IRC ([chat.freenode.net](https://webchat.freenode.net/)) and join our IRC channel #mailcow. +6. [Create an issue](https://github.com/mailcow/mailcow-dockerized/issues/new/choose) over at our GitHub repository if you think your problem might be a bug or a missing feature you badly need. But please make sure, that you include **all the logs** and a full description to your problem. +7. Ask your questions in our community-driven [support channels](https://docs.mailcow.email/#community-support-and-chat). + +## When creating an issue/feature request or a pull request, you will be asked to confirm these guidelines. diff --git a/README.md b/README.md index ec500d9c0d..63d0b64800 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,66 @@ # mailcow: dockerized - ๐Ÿฎ + ๐Ÿ‹ = ๐Ÿ’• +[![Translation status](https://translate.mailcow.email/widgets/mailcow-dockerized/-/translation/svg-badge.svg)](https://translate.mailcow.email/engage/mailcow-dockerized/) +[![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/mailcow_email.svg?style=social&label=Follow%20%40mailcow_email)](https://twitter.com/mailcow_email) +![Mastodon Follow](https://img.shields.io/mastodon/follow/109388212176073348?domain=https%3A%2F%2Fmailcow.social&label=Follow%20%40doncow%40mailcow.social&link=https%3A%2F%2Fmailcow.social%2F%40doncow) + + ## Want to support mailcow? -Donate via **PayPal** [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=JWBSYHF4SMC68) or via **Liberapay** [![Liberapay.com](https://mailcow.email/img/lp.png)](https://liberapay.com/mailcow) +Please [consider a support contract with Servercow](https://www.servercow.de/mailcow?lang=en#support) to support further development. _We_ support _you_ while _you_ support _us_. :) + +You can also [get a SAL](https://www.servercow.de/mailcow?lang=en#sal) which is a one-time payment with no liabilities or returning fees. Or just spread the word: moo. -## Info and documentation +## Many thanks to our GitHub Sponsors โค๏ธ +A big thank you to everyone supporting us on GitHub Sponsorsโ€”your contributions mean the world to us! Special thanks to the following amazing supporters: + +### 100$/Month Sponsors + + + + +### 50$/Month Sponsors + + +## Info, documentation and support + +Please see [the official documentation](https://docs.mailcow.email/) for installation and support instructions. ๐Ÿ„ + +๐Ÿ› **If you found a critical security issue, please mail us to [info at servercow.de](mailto:info@servercow.de).** + +## Cowmunity -Please see [the official documentation](https://mailcow.github.io/mailcow-dockerized-docs/) for instructions. +[mailcow community](https://community.mailcow.email) + +[Telegram mailcow channel](https://telegram.me/mailcow) + +[Telegram mailcow Off-Topic channel](https://t.me/mailcowOfftopic) + +[Official ๐• (Twitter) Account](https://twitter.com/mailcow_email) + +[Official Mastodon Account](https://mailcow.social/@doncow) + +Telegram desktop clients are available for [multiple platforms](https://desktop.telegram.org). You can search the groups history for keywords. + +## Misc **Important**: mailcow makes use of various open-source software. Please assure you agree with their license before using mailcow. Any part of mailcow itself is released under **GNU General Public License, Version 3**. + +mailcow is a registered word mark of The Infrastructure Company GmbH, Parkstr. 42, 47877 Willich, Germany. + +The project is managed and maintained by The Infrastructure Company GmbH. + +Originated from @andryyy (Andrรฉ) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..de63ca3eb8 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,42 @@ +# Security Policies and Procedures + +This document outlines security procedures and general policies for the _mailcow: dockerized_ project as found on [mailcow-dockerized](https://github.com/mailcow/mailcow-dockerized). + + * [Reporting a Vulnerability](#reporting-a-vulnerability) + * [Disclosure Policy](#disclosure-policy) + * [Comments on this Policy](#comments-on-this-policy) + +## Reporting a Vulnerability + +The mailcow team and community take all security vulnerabilities +seriously. Thank you for improving the security of our open source +software. We appreciate your efforts and responsible disclosure and will +make every effort to acknowledge your contributions. + +Report security vulnerabilities by emailing the mailcow team at: + + info at servercow.de + +mailcow team will acknowledge your email as soon as possible, and will +send a more detailed response afterwards indicating the next steps in +handling your report. After the initial reply to your report, the mailcow +team will endeavor to keep you informed of the progress towards a fix and +full announcement, and may ask for additional information or guidance. + +Report security vulnerabilities in third-party modules to the person or +team maintaining the module. + +## Disclosure Policy + +When the mailcow team receives a security bug report, they will assign it +to a primary handler. This person will coordinate the fix and release +process, involving the following steps: + + * Confirm the problem and determine the affected versions. + * Audit code to find any potential similar problems. + * Prepare fixes for all releases still under maintenance. + +## Comments on this Policy + +If you have suggestions on how this process could be improved please submit a +pull request. diff --git a/_modules/scripts/core.sh b/_modules/scripts/core.sh new file mode 100644 index 0000000000..576967a48f --- /dev/null +++ b/_modules/scripts/core.sh @@ -0,0 +1,230 @@ +#!/usr/bin/env bash +# _modules/scripts/core.sh +# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY! +# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!! + +# ANSI color for red errors +RED='\e[31m' +GREEN='\e[32m' +YELLOW='\e[33m' +BLUE='\e[34m' +MAGENTA='\e[35m' +LIGHT_RED='\e[91m' +LIGHT_GREEN='\e[92m' +NC='\e[0m' + +caller="${BASH_SOURCE[1]##*/}" + +get_installed_tools(){ + for bin in openssl curl docker git awk sha1sum grep cut jq; do + if [[ -z $(command -v ${bin}) ]]; then + echo "Error: Cannot find command '${bin}'. Cannot proceed." + echo "Solution: Please review system requirements and install requirements. Then, re-run the script." + echo "See System Requirements: https://docs.mailcow.email/getstarted/install/" + echo "Exiting..." + exit 1 + fi + done + + if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox grep detected, please install gnu grep, \"apk add --no-cache --upgrade grep\"${NC}"; exit 1; fi + # This will also cover sort + if cp --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox cp detected, please install coreutils, \"apk add --no-cache --upgrade coreutils\"${NC}"; exit 1; fi + if sed --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox sed detected, please install gnu sed, \"apk add --no-cache --upgrade sed\"${NC}"; exit 1; fi +} + +get_docker_version(){ + # Check Docker Version (need at least 24.X) + docker_version=$(docker version --format '{{.Server.Version}}' | cut -d '.' -f 1) +} + +get_compose_type(){ + if docker compose > /dev/null 2>&1; then + if docker compose version --short | grep -e "^[2-9]\." -e "^v[2-9]\." -e "^[1-9][0-9]\." -e "^v[1-9][0-9]\." > /dev/null 2>&1; then + COMPOSE_VERSION=native + COMPOSE_COMMAND="docker compose" + if [[ "$caller" == "update.sh" ]]; then + sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' "$SCRIPT_DIR/mailcow.conf" + fi + echo -e "\e[33mFound Docker Compose Plugin (native).\e[0m" + echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m" + sleep 2 + echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m" + else + echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" + echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m" + exit 1 + fi + elif docker-compose > /dev/null 2>&1; then + if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then + if docker-compose version --short | grep -e "^[2-9]\." -e "^[1-9][0-9]\." > /dev/null 2>&1; then + COMPOSE_VERSION=standalone + COMPOSE_COMMAND="docker-compose" + if [[ "$caller" == "update.sh" ]]; then + sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=standalone/' "$SCRIPT_DIR/mailcow.conf" + fi + echo -e "\e[33mFound Docker Compose Standalone.\e[0m" + echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m" + sleep 2 + echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m" + else + echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" + echo -e "\e[31mPlease update/install manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m" + exit 1 + fi + fi + else + echo -e "\e[31mCannot find Docker Compose.\e[0m" + echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m" + exit 1 + fi +} + +detect_bad_asn() { + echo -e "\e[33mDetecting if your IP is listed on Spamhaus Bad ASN List...\e[0m" + response=$(curl --connect-timeout 15 --max-time 30 -s -o /dev/null -w "%{http_code}" "https://asn-check.mailcow.email") + if [ "$response" -eq 503 ]; then + if [ -z "$SPAMHAUS_DQS_KEY" ]; then + echo -e "\e[33mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS public blocklists for Postfix.\e[0m" + echo -e "\e[33mmailcow did not detected a value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf!\e[0m" + sleep 2 + echo "" + echo -e "\e[33mTo use the Spamhaus DNS Blocklists again, you will need to create a FREE account for their Data Query Service (DQS) at: https://www.spamhaus.com/free-trial/sign-up-for-a-free-data-query-service-account\e[0m" + echo -e "\e[33mOnce done, enter your DQS API key in mailcow.conf and mailcow will do the rest for you!\e[0m" + echo "" + sleep 2 + else + echo -e "\e[33mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS public blocklists for Postfix.\e[0m" + echo -e "\e[32mmailcow detected a Value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf. Postfix will use DQS with the given API key...\e[0m" + fi + elif [ "$response" -eq 200 ]; then + echo -e "\e[33mCheck completed! Your IP is \e[32mclean\e[0m" + elif [ "$response" -eq 429 ]; then + echo -e "\e[33mCheck completed! \e[31mYour IP seems to be rate limited on the ASN Check service... please try again later!\e[0m" + else + echo -e "\e[31mCheck failed! \e[0mMaybe a DNS or Network problem?\e[0m" + fi +} + +check_online_status() { + CHECK_ONLINE_DOMAINS=('https://github.com' 'https://hub.docker.com') + for domain in "${CHECK_ONLINE_DOMAINS[@]}"; do + if timeout 6 curl --head --silent --output /dev/null ${domain}; then + return 0 + fi + done + return 1 +} + +prefetch_images() { + [[ -z ${BRANCH} ]] && { echo -e "\e[33m\nUnknown branch...\e[0m"; exit 1; } + git fetch origin #${BRANCH} + while read image; do + RET_C=0 + until docker pull "${image}"; do + RET_C=$((RET_C + 1)) + echo -e "\e[33m\nError pulling $image, retrying...\e[0m" + [ ${RET_C} -gt 3 ] && { echo -e "\e[31m\nToo many failed retries, exiting\e[0m"; exit 1; } + sleep 1 + done + done < <(git show "origin/${BRANCH}:docker-compose.yml" | grep "image:" | awk '{ gsub("image:","", $3); print $2 }') +} + +docker_garbage() { + SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )" + IMGS_TO_DELETE=() + + declare -A IMAGES_INFO + COMPOSE_IMAGES=($(grep -oP "image: \K(ghcr\.io/)?mailcow.+" "${SCRIPT_DIR}/docker-compose.yml")) + + for existing_image in $(docker images --format "{{.ID}}:{{.Repository}}:{{.Tag}}" | grep -E '(mailcow/|ghcr\.io/mailcow/)'); do + ID=$(echo "$existing_image" | cut -d ':' -f 1) + REPOSITORY=$(echo "$existing_image" | cut -d ':' -f 2) + TAG=$(echo "$existing_image" | cut -d ':' -f 3) + + if [[ "$REPOSITORY" == "mailcow/backup" || "$REPOSITORY" == "ghcr.io/mailcow/backup" ]]; then + if [[ "$TAG" != "" ]]; then + continue + fi + fi + + if [[ " ${COMPOSE_IMAGES[@]} " =~ " ${REPOSITORY}:${TAG} " ]]; then + continue + else + IMGS_TO_DELETE+=("$ID") + IMAGES_INFO["$ID"]="$REPOSITORY:$TAG" + fi + done + + if [[ ! -z ${IMGS_TO_DELETE[*]} ]]; then + echo "The following unused mailcow images were found:" + for id in "${IMGS_TO_DELETE[@]}"; do + echo " ${IMAGES_INFO[$id]} ($id)" + done + + if [ -z "$FORCE" ]; then + read -r -p "Do you want to delete them to free up some space? [y/N] " response + if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + docker rmi ${IMGS_TO_DELETE[*]} + else + echo "OK, skipped." + fi + else + echo "Running in forced mode! Force removing old mailcow images..." + docker rmi ${IMGS_TO_DELETE[*]} + fi + echo -e "\e[32mFurther cleanup...\e[0m" + echo "If you want to cleanup further garbage collected by Docker, please make sure all containers are up and running before cleaning your system by executing \"docker system prune\"" + fi +} + +in_array() { + local e match="$1" + shift + for e; do [[ "$e" == "$match" ]] && return 0; done + return 1 +} + +detect_major_update() { + if [ ${BRANCH} == "master" ]; then + # Array with major versions + # Add major versions here + MAJOR_VERSIONS=( + "2025-02" + "2025-03" + "2025-09" + ) + + current_version="" + if [[ -f "${SCRIPT_DIR}/data/web/inc/app_info.inc.php" ]]; then + current_version=$(grep 'MAILCOW_GIT_VERSION' ${SCRIPT_DIR}/data/web/inc/app_info.inc.php | sed -E 's/.*MAILCOW_GIT_VERSION="([^"]+)".*/\1/') + fi + if [[ -z "$current_version" ]]; then + return 1 + fi + release_url="https://github.com/mailcow/mailcow-dockerized/releases/tag" + + updates_to_apply=() + + for version in "${MAJOR_VERSIONS[@]}"; do + if [[ "$current_version" < "$version" ]]; then + updates_to_apply+=("$version") + fi + done + + if [[ ${#updates_to_apply[@]} -gt 0 ]]; then + echo -e "\e[33m\nMAJOR UPDATES to be applied:\e[0m" + for update in "${updates_to_apply[@]}"; do + echo "$update - $release_url/$update" + done + + echo -e "\nPlease read the release notes before proceeding." + read -p "Do you want to proceed with the update? [y/n] " response + if [[ "${response}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + echo "Proceeding with the update..." + else + echo "Update canceled. Exiting." + exit 1 + fi + fi + fi +} diff --git a/_modules/scripts/ipv6_controller.sh b/_modules/scripts/ipv6_controller.sh new file mode 100644 index 0000000000..a025ee1783 --- /dev/null +++ b/_modules/scripts/ipv6_controller.sh @@ -0,0 +1,239 @@ +#!/usr/bin/env bash +# _modules/scripts/ipv6_controller.sh +# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY! +# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!! + +# 1) Check if the host supports IPv6 +get_ipv6_support() { + # ---- helper: probe external IPv6 connectivity without DNS ---- + _probe_ipv6_connectivity() { + # Use literal, always-on IPv6 echo responders (no DNS required) + local PROBE_IPS=("2001:4860:4860::8888" "2606:4700:4700::1111") + local ip rc=1 + + for ip in "${PROBE_IPS[@]}"; do + if command -v ping6 &>/dev/null; then + ping6 -c1 -W2 "$ip" &>/dev/null || ping6 -c1 -w2 "$ip" &>/dev/null + rc=$? + elif command -v ping &>/dev/null; then + ping -6 -c1 -W2 "$ip" &>/dev/null || ping -6 -c1 -w2 "$ip" &>/dev/null + rc=$? + else + rc=1 + fi + [[ $rc -eq 0 ]] && return 0 + done + return 1 + } + + if [[ ! -f /proc/net/if_inet6 ]] || grep -qs '^1' /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null; then + DETECTED_IPV6=false + echo -e "${YELLOW}IPv6 not detected on host โ€“ ${LIGHT_RED}IPv6 is administratively disabled${YELLOW}.${NC}" + return + fi + + if ip -6 route show default 2>/dev/null | grep -qE '^default'; then + echo -e "${YELLOW}Default IPv6 route found โ€“ testing external IPv6 connectivity...${NC}" + if _probe_ipv6_connectivity; then + DETECTED_IPV6=true + echo -e "IPv6 detected on host โ€“ ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}" + else + DETECTED_IPV6=false + echo -e "${YELLOW}Default IPv6 route present but external IPv6 connectivity failed โ€“ ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}" + fi + return + fi + + if ip -6 addr show scope global 2>/dev/null | grep -q 'inet6'; then + DETECTED_IPV6=false + echo -e "${YELLOW}Global IPv6 address present but no default route โ€“ ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}" + return + fi + + if ip -6 addr show scope link 2>/dev/null | grep -q 'inet6'; then + echo -e "${YELLOW}Only link-local IPv6 addresses found โ€“ testing external IPv6 connectivity...${NC}" + if _probe_ipv6_connectivity; then + DETECTED_IPV6=true + echo -e "External IPv6 connectivity available โ€“ ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}" + else + DETECTED_IPV6=false + echo -e "${YELLOW}Only link-local IPv6 present and no external connectivity โ€“ ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}" + fi + return + fi + + DETECTED_IPV6=false + echo -e "${YELLOW}IPv6 not detected on host โ€“ ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}" +} + +# 2) Ensure Docker daemon.json has (or create) the required IPv6 settings +docker_daemon_edit(){ + DOCKER_DAEMON_CONFIG="/etc/docker/daemon.json" + DOCKER_MAJOR=$(docker version --format '{{.Server.Version}}' 2>/dev/null | cut -d. -f1) + MISSING=() + + _has_kv() { grep -Eq "\"$1\"[[:space:]]*:[[:space:]]*$2" "$DOCKER_DAEMON_CONFIG" 2>/dev/null; } + + if [[ -f "$DOCKER_DAEMON_CONFIG" ]]; then + + # reject empty or whitespace-only file immediately + if [[ ! -s "$DOCKER_DAEMON_CONFIG" ]] || ! grep -Eq '[{}]' "$DOCKER_DAEMON_CONFIG"; then + echo -e "${RED}ERROR: $DOCKER_DAEMON_CONFIG exists but is empty or contains no JSON braces โ€“ please initialize it with valid JSON (e.g. {}).${NC}" + exit 1 + fi + + # Validate JSON if jq is present + if command -v jq &>/dev/null && ! jq empty "$DOCKER_DAEMON_CONFIG" &>/dev/null; then + echo -e "${RED}ERROR: Invalid JSON in $DOCKER_DAEMON_CONFIG โ€“ please correct manually.${NC}" + exit 1 + fi + + # Gather missing keys + ! _has_kv ipv6 true && MISSING+=("ipv6: true") + + # For Docker < 28, keep requiring fixed-cidr-v6 (default bridge needs it on old engines) + if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then + ! grep -Eq '"fixed-cidr-v6"[[:space:]]*:[[:space:]]*".+"' "$DOCKER_DAEMON_CONFIG" \ + && MISSING+=('fixed-cidr-v6: "fd00:dead:beef:c0::/80"') + fi + + # For Docker < 27, ip6tables needed and was tied to experimental in older releases + if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then + _has_kv ipv6 true && ! _has_kv ip6tables true && MISSING+=("ip6tables: true") + ! _has_kv experimental true && MISSING+=("experimental: true") + fi + + # Fix if needed + if ((${#MISSING[@]}>0)); then + echo -e "${MAGENTA}Your daemon.json is missing: ${YELLOW}${MISSING[*]}${NC}" + if [[ -n "$FORCE" ]]; then + ans=Y + else + read -p "Would you like to update $DOCKER_DAEMON_CONFIG now? [Y/n] " ans + ans=${ans:-Y} + fi + + if [[ $ans =~ ^[Yy]$ ]]; then + cp "$DOCKER_DAEMON_CONFIG" "${DOCKER_DAEMON_CONFIG}.bak" + if command -v jq &>/dev/null; then + TMP=$(mktemp) + # Base filter: ensure ipv6 = true + JQ_FILTER='.ipv6 = true' + + # Add fixed-cidr-v6 only for Docker < 28 + if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then + JQ_FILTER+=' | .["fixed-cidr-v6"] = (.["fixed-cidr-v6"] // "fd00:dead:beef:c0::/80")' + fi + + # Add ip6tables/experimental only for Docker < 27 + if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then + JQ_FILTER+=' | .ip6tables = true | .experimental = true' + fi + + jq "$JQ_FILTER" "$DOCKER_DAEMON_CONFIG" >"$TMP" && mv "$TMP" "$DOCKER_DAEMON_CONFIG" + echo -e "${LIGHT_GREEN}daemon.json updated. Restarting Docker...${NC}" + (command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart + echo -e "${YELLOW}Docker restarted.${NC}" + else + echo -e "${RED}Please install jq or manually update daemon.json and restart Docker.${NC}" + exit 1 + fi + else + echo -e "${YELLOW}User declined Docker update โ€“ please insert these changes manually:${NC}" + echo "${MISSING[*]}" + exit 1 + fi + fi + + else + # Create new daemon.json if missing + if [[ -n "$FORCE" ]]; then + ans=Y + else + read -p "$DOCKER_DAEMON_CONFIG not found. Create it with IPv6 settings? [Y/n] " ans + ans=${ans:-Y} + fi + + if [[ $ans =~ ^[Yy]$ ]]; then + mkdir -p "$(dirname "$DOCKER_DAEMON_CONFIG")" + if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then + cat > "$DOCKER_DAEMON_CONFIG" < "$DOCKER_DAEMON_CONFIG" < "$DOCKER_DAEMON_CONFIG" </dev/null && systemctl restart docker) || service docker restart + echo "Docker restarted." + else + echo "User declined to create daemon.json โ€“ please manually merge the docker daemon with these configs:" + echo "${MISSING[*]}" + exit 1 + fi + fi +} + +# 3) Main wrapper for generate_config.sh and update.sh +configure_ipv6() { + # detect manual override if mailcow.conf is present + if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then + MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2) + elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then + MANUAL_SETTING="$ENABLE_IPV6" + else + MANUAL_SETTING="" + fi + + get_ipv6_support + + # if user manually set it, check for mismatch + if [[ "$DETECTED_IPV6" != "true" ]]; then + if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then + if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then + sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=false/' "$MAILCOW_CONF" + else + echo "ENABLE_IPV6=false" >> "$MAILCOW_CONF" + fi + else + export IPV6_BOOL=false + fi + echo "Skipping Docker IPv6 configuration because host does not support IPv6." + echo "Make sure to check if your docker daemon.json does not include \"enable_ipv6\": true if you do not want IPv6." + echo "IPv6 configuration complete: ENABLE_IPV6=false" + sleep 2 + return + fi + + docker_daemon_edit + + if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then + if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then + sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=true/' "$MAILCOW_CONF" + else + echo "ENABLE_IPV6=true" >> "$MAILCOW_CONF" + fi + else + export IPV6_BOOL=true + fi + + echo "IPv6 configuration complete: ENABLE_IPV6=true" +} \ No newline at end of file diff --git a/_modules/scripts/migrate_options.sh b/_modules/scripts/migrate_options.sh new file mode 100644 index 0000000000..6a584b9f76 --- /dev/null +++ b/_modules/scripts/migrate_options.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# _modules/scripts/migrate_options.sh +# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY! +# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!! + +migrate_config_options() { + + sed -i --follow-symlinks '$a\' mailcow.conf + + KEYS=( + SOLR_HEAP + SKIP_SOLR + SOLR_PORT + FLATCURVE_EXPERIMENTAL + DISABLE_IPv6 + ACME_CONTACT + ) + + for key in "${KEYS[@]}"; do + if grep -q "${key}" mailcow.conf; then + case "${key}" in + SOLR_HEAP) + echo "Removing ${key} in mailcow.conf" + sed -i '/# Solr heap size in MB\b/d' mailcow.conf + sed -i '/# Solr is a prone to run\b/d' mailcow.conf + sed -i '/SOLR_HEAP\b/d' mailcow.conf + ;; + SKIP_SOLR) + echo "Removing ${key} in mailcow.conf" + sed -i '/\bSkip Solr on low-memory\b/d' mailcow.conf + sed -i '/\bSolr is disabled by default\b/d' mailcow.conf + sed -i '/\bDisable Solr or\b/d' mailcow.conf + sed -i '/\bSKIP_SOLR\b/d' mailcow.conf + ;; + SOLR_PORT) + echo "Removing ${key} in mailcow.conf" + sed -i '/\bSOLR_PORT\b/d' mailcow.conf + ;; + FLATCURVE_EXPERIMENTAL) + echo "Removing ${key} in mailcow.conf" + sed -i '/\bFLATCURVE_EXPERIMENTAL\b/d' mailcow.conf + ;; + DISABLE_IPv6) + echo "Migrating ${key} to ENABLE_IPv6 in mailcow.conf" + local old=$(grep '^DISABLE_IPv6=' "mailcow.conf" | cut -d'=' -f2) + local new + if [[ "$old" == "y" ]]; then + new="false" + else + new="true" + fi + sed -i '/^DISABLE_IPv6=/d' "mailcow.conf" + echo "ENABLE_IPV6=$new" >> "mailcow.conf" + ;; + ACME_CONTACT) + echo "Deleting obsoleted ${key} in mailcow.conf" + sed -i '/^# Lets Encrypt registration contact information/d' mailcow.conf + sed -i '/^# Optional: Leave empty for none/d' mailcow.conf + sed -i '/^# This value is only used on first order!/d' mailcow.conf + sed -i '/^# Setting it at a later point will require the following steps:/d' mailcow.conf + sed -i '/^# https:\/\/docs.mailcow.email\/troubleshooting\/debug-reset_tls\//d' mailcow.conf + sed -i '/^ACME_CONTACT=.*/d' mailcow.conf + sed -i '/^#ACME_CONTACT=.*/d' mailcow.conf + ;; + esac + fi + done + + solr_volume=$(docker volume ls -qf name=^${COMPOSE_PROJECT_NAME}_solr-vol-1) + if [[ -n $solr_volume ]]; then + echo -e "\e[34mSolr has been replaced within mailcow since 2025-01.\nThe volume $solr_volume is unused.\e[0m" + sleep 1 + if [ ! "$FORCE" ]; then + read -r -p "Remove $solr_volume? [y/N] " response + if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + echo -e "\e[33mRemoving $solr_volume...\e[0m" + docker volume rm $solr_volume || echo -e "\e[31mFailed to remove. Remove it manually!\e[0m" + echo -e "\e[32mSuccessfully removed $solr_volume!\e[0m" + else + echo -e "Not removing $solr_volume. Run \`docker volume rm $solr_volume\` manually if needed." + fi + else + echo -e "\e[33mForce removing $solr_volume...\e[0m" + docker volume rm $solr_volume || echo -e "\e[31mFailed to remove. Remove it manually!\e[0m" + echo -e "\e[32mSuccessfully removed $solr_volume!\e[0m" + fi + fi + + # Delete old fts.conf before forced switch to flatcurve to ensure update is working properly + FTS_CONF_PATH="${SCRIPT_DIR}/data/conf/dovecot/conf.d/fts.conf" + if [[ -f "$FTS_CONF_PATH" ]]; then + if grep -q "Autogenerated by mailcow" "$FTS_CONF_PATH"; then + rm -rf $FTS_CONF_PATH + fi + fi +} \ No newline at end of file diff --git a/_modules/scripts/new_options.sh b/_modules/scripts/new_options.sh new file mode 100644 index 0000000000..2c39c8a7c2 --- /dev/null +++ b/_modules/scripts/new_options.sh @@ -0,0 +1,317 @@ +#!/usr/bin/env bash +# _modules/scripts/new_options.sh +# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY! +# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!! + +adapt_new_options() { + + CONFIG_ARRAY=( + "AUTODISCOVER_SAN" + "SKIP_LETS_ENCRYPT" + "SKIP_SOGO" + "USE_WATCHDOG" + "WATCHDOG_NOTIFY_EMAIL" + "WATCHDOG_NOTIFY_WEBHOOK" + "WATCHDOG_NOTIFY_WEBHOOK_BODY" + "WATCHDOG_NOTIFY_BAN" + "WATCHDOG_NOTIFY_START" + "WATCHDOG_EXTERNAL_CHECKS" + "WATCHDOG_SUBJECT" + "SKIP_CLAMD" + "SKIP_OLEFY" + "SKIP_IP_CHECK" + "ADDITIONAL_SAN" + "DOVEADM_PORT" + "IPV4_NETWORK" + "IPV6_NETWORK" + "LOG_LINES" + "SNAT_TO_SOURCE" + "SNAT6_TO_SOURCE" + "COMPOSE_PROJECT_NAME" + "DOCKER_COMPOSE_VERSION" + "SQL_PORT" + "API_KEY" + "API_KEY_READ_ONLY" + "API_ALLOW_FROM" + "MAILDIR_GC_TIME" + "MAILDIR_SUB" + "ACL_ANYONE" + "FTS_HEAP" + "FTS_PROCS" + "SKIP_FTS" + "ENABLE_SSL_SNI" + "ALLOW_ADMIN_EMAIL_LOGIN" + "SKIP_HTTP_VERIFICATION" + "SOGO_EXPIRE_SESSION" + "SOGO_URL_ENCRYPTION_KEY" + "REDIS_PORT" + "REDISPASS" + "DOVECOT_MASTER_USER" + "DOVECOT_MASTER_PASS" + "MAILCOW_PASS_SCHEME" + "ADDITIONAL_SERVER_NAMES" + "WATCHDOG_VERBOSE" + "WEBAUTHN_ONLY_TRUSTED_VENDORS" + "SPAMHAUS_DQS_KEY" + "SKIP_UNBOUND_HEALTHCHECK" + "DISABLE_NETFILTER_ISOLATION_RULE" + "HTTP_REDIRECT" + "ENABLE_IPV6" + "ACME_DNS_CHALLENGE" + "ACME_DNS_PROVIDER" + "ACME_ACCOUNT_EMAIL" + ) + + sed -i --follow-symlinks '$a\' mailcow.conf + for option in ${CONFIG_ARRAY[@]}; do + if grep -q "^#\?${option}=" mailcow.conf; then + continue + fi + + echo "Adding new option \"${option}\" to mailcow.conf" + + case "${option}" in + AUTODISCOVER_SAN) + echo '# Obtain certificates for autodiscover.* and autoconfig.* domains.' >> mailcow.conf + echo '# This can be useful to switch off in case you are in a scenario where a reverse proxy already handles those.' >> mailcow.conf + echo '# There are mixed scenarios where ports 80,443 are occupied and you do not want to share certs' >> mailcow.conf + echo '# between services. So acme-mailcow obtains for maildomains and all web-things get handled' >> mailcow.conf + echo '# in the reverse proxy.' >> mailcow.conf + echo 'AUTODISCOVER_SAN=y' >> mailcow.conf + ;; + + DOCKER_COMPOSE_VERSION) + echo "# Used Docker Compose version" >> mailcow.conf + echo "# Switch here between native (compose plugin) and standalone" >> mailcow.conf + echo "# For more informations take a look at the mailcow docs regarding the configuration options." >> mailcow.conf + echo "# Normally this should be untouched but if you decided to use either of those you can switch it manually here." >> mailcow.conf + echo "# Please be aware that at least one of those variants should be installed on your machine or mailcow will fail." >> mailcow.conf + echo "" >> mailcow.conf + echo "DOCKER_COMPOSE_VERSION=${DOCKER_COMPOSE_VERSION}" >> mailcow.conf + ;; + + DOVEADM_PORT) + echo "DOVEADM_PORT=127.0.0.1:19991" >> mailcow.conf + ;; + + LOG_LINES) + echo '# Max log lines per service to keep in Redis logs' >> mailcow.conf + echo "LOG_LINES=9999" >> mailcow.conf + ;; + IPV4_NETWORK) + echo '# Internal IPv4 /24 subnet, format n.n.n. (expands to n.n.n.0/24)' >> mailcow.conf + echo "IPV4_NETWORK=172.22.1" >> mailcow.conf + ;; + IPV6_NETWORK) + echo '# Internal IPv6 subnet in fc00::/7' >> mailcow.conf + echo "IPV6_NETWORK=fd4d:6169:6c63:6f77::/64" >> mailcow.conf + ;; + SQL_PORT) + echo '# Bind SQL to 127.0.0.1 on port 13306' >> mailcow.conf + echo "SQL_PORT=127.0.0.1:13306" >> mailcow.conf + ;; + API_KEY) + echo '# Create or override API key for web UI' >> mailcow.conf + echo "#API_KEY=" >> mailcow.conf + ;; + API_KEY_READ_ONLY) + echo '# Create or override read-only API key for web UI' >> mailcow.conf + echo "#API_KEY_READ_ONLY=" >> mailcow.conf + ;; + API_ALLOW_FROM) + echo '# Must be set for API_KEY to be active' >> mailcow.conf + echo '# IPs only, no networks (networks can be set via UI)' >> mailcow.conf + echo "#API_ALLOW_FROM=" >> mailcow.conf + ;; + SNAT_TO_SOURCE) + echo '# Use this IPv4 for outgoing connections (SNAT)' >> mailcow.conf + echo "#SNAT_TO_SOURCE=" >> mailcow.conf + ;; + SNAT6_TO_SOURCE) + echo '# Use this IPv6 for outgoing connections (SNAT)' >> mailcow.conf + echo "#SNAT6_TO_SOURCE=" >> mailcow.conf + ;; + MAILDIR_GC_TIME) + echo '# Garbage collector cleanup' >> mailcow.conf + echo '# Deleted domains and mailboxes are moved to /var/vmail/_garbage/timestamp_sanitizedstring' >> mailcow.conf + echo '# How long should objects remain in the garbage until they are being deleted? (value in minutes)' >> mailcow.conf + echo '# Check interval is hourly' >> mailcow.conf + echo 'MAILDIR_GC_TIME=1440' >> mailcow.conf + ;; + ACL_ANYONE) + echo '# Set this to "allow" to enable the anyone pseudo user. Disabled by default.' >> mailcow.conf + echo '# When enabled, ACL can be created, that apply to "All authenticated users"' >> mailcow.conf + echo '# This should probably only be activated on mail hosts, that are used exclusively by one organisation.' >> mailcow.conf + echo '# Otherwise a user might share data with too many other users.' >> mailcow.conf + echo 'ACL_ANYONE=disallow' >> mailcow.conf + ;; + FTS_HEAP) + echo '# Dovecot Indexing (FTS) Process maximum heap size in MB, there is no recommendation, please see Dovecot docs.' >> mailcow.conf + echo '# Flatcurve is used as FTS Engine. It is supposed to be pretty efficient in CPU and RAM consumption.' >> mailcow.conf + echo '# Please always monitor your Resource consumption!' >> mailcow.conf + echo "FTS_HEAP=128" >> mailcow.conf + ;; + SKIP_FTS) + echo '# Skip FTS (Fulltext Search) for Dovecot on low-memory, low-threaded systems or if you simply want to disable it.' >> mailcow.conf + echo "# Dovecot inside mailcow use Flatcurve as FTS Backend." >> mailcow.conf + echo "SKIP_FTS=y" >> mailcow.conf + ;; + FTS_PROCS) + echo '# Controls how many processes the Dovecot indexing process can spawn at max.' >> mailcow.conf + echo '# Too many indexing processes can use a lot of CPU and Disk I/O' >> mailcow.conf + echo '# Please visit: https://doc.dovecot.org/configuration_manual/service_configuration/#indexer-worker for more informations' >> mailcow.conf + echo "FTS_PROCS=1" >> mailcow.conf + ;; + ENABLE_SSL_SNI) + echo '# Create seperate certificates for all domains - y/n' >> mailcow.conf + echo '# this will allow adding more than 100 domains, but some email clients will not be able to connect with alternative hostnames' >> mailcow.conf + echo '# see https://wiki.dovecot.org/SSL/SNIClientSupport' >> mailcow.conf + echo "ENABLE_SSL_SNI=n" >> mailcow.conf + ;; + SKIP_SOGO) + echo '# Skip SOGo: Will disable SOGo integration and therefore webmail, DAV protocols and ActiveSync support (experimental, unsupported, not fully implemented) - y/n' >> mailcow.conf + echo "SKIP_SOGO=n" >> mailcow.conf + ;; + MAILDIR_SUB) + echo '# MAILDIR_SUB defines a path in a users virtual home to keep the maildir in. Leave empty for updated setups.' >> mailcow.conf + echo "#MAILDIR_SUB=Maildir" >> mailcow.conf + echo "MAILDIR_SUB=" >> mailcow.conf + ;; + WATCHDOG_NOTIFY_WEBHOOK) + echo '# Send notifications to a webhook URL that receives a POST request with the content type "application/json".' >> mailcow.conf + echo '# You can use this to send notifications to services like Discord, Slack and others.' >> mailcow.conf + echo '#WATCHDOG_NOTIFY_WEBHOOK=https://discord.com/api/webhooks/XXXXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' >> mailcow.conf + ;; + WATCHDOG_NOTIFY_WEBHOOK_BODY) + echo '# JSON body included in the webhook POST request. Needs to be in single quotes.' >> mailcow.conf + echo '# Following variables are available: SUBJECT, BODY' >> mailcow.conf + WEBHOOK_BODY='{"username": "mailcow Watchdog", "content": "**${SUBJECT}**\n${BODY}"}' + echo "#WATCHDOG_NOTIFY_WEBHOOK_BODY='${WEBHOOK_BODY}'" >> mailcow.conf + ;; + WATCHDOG_NOTIFY_BAN) + echo '# Notify about banned IP. Includes whois lookup.' >> mailcow.conf + echo "WATCHDOG_NOTIFY_BAN=y" >> mailcow.conf + ;; + WATCHDOG_NOTIFY_START) + echo '# Send a notification when the watchdog is started.' >> mailcow.conf + echo "WATCHDOG_NOTIFY_START=y" >> mailcow.conf + ;; + WATCHDOG_SUBJECT) + echo '# Subject for watchdog mails. Defaults to "Watchdog ALERT" followed by the error message.' >> mailcow.conf + echo "#WATCHDOG_SUBJECT=" >> mailcow.conf + ;; + WATCHDOG_EXTERNAL_CHECKS) + echo '# Checks if mailcow is an open relay. Requires a SAL. More checks will follow.' >> mailcow.conf + echo '# No data is collected. Opt-in and anonymous.' >> mailcow.conf + echo '# Will only work with unmodified mailcow setups.' >> mailcow.conf + echo "WATCHDOG_EXTERNAL_CHECKS=n" >> mailcow.conf + ;; + SOGO_EXPIRE_SESSION) + echo '# SOGo session timeout in minutes' >> mailcow.conf + echo "SOGO_EXPIRE_SESSION=480" >> mailcow.conf + ;; + REDIS_PORT) + echo "REDIS_PORT=127.0.0.1:7654" >> mailcow.conf + ;; + DOVECOT_MASTER_USER) + echo '# DOVECOT_MASTER_USER and _PASS must _both_ be provided. No special chars.' >> mailcow.conf + echo '# Empty by default to auto-generate master user and password on start.' >> mailcow.conf + echo '# User expands to DOVECOT_MASTER_USER@mailcow.local' >> mailcow.conf + echo '# LEAVE EMPTY IF UNSURE' >> mailcow.conf + echo "DOVECOT_MASTER_USER=" >> mailcow.conf + ;; + DOVECOT_MASTER_PASS) + echo '# LEAVE EMPTY IF UNSURE' >> mailcow.conf + echo "DOVECOT_MASTER_PASS=" >> mailcow.conf + ;; + MAILCOW_PASS_SCHEME) + echo '# Password hash algorithm' >> mailcow.conf + echo '# Only certain password hash algorithm are supported. For a fully list of supported schemes,' >> mailcow.conf + echo '# see https://docs.mailcow.email/models/model-passwd/' >> mailcow.conf + echo "MAILCOW_PASS_SCHEME=BLF-CRYPT" >> mailcow.conf + ;; + ADDITIONAL_SERVER_NAMES) + echo '# Additional server names for mailcow UI' >> mailcow.conf + echo '#' >> mailcow.conf + echo '# Specify alternative addresses for the mailcow UI to respond to' >> mailcow.conf + echo '# This is useful when you set mail.* as ADDITIONAL_SAN and want to make sure mail.maildomain.com will always point to the mailcow UI.' >> mailcow.conf + echo '# If the server name does not match a known site, Nginx decides by best-guess and may redirect users to the wrong web root.' >> mailcow.conf + echo '# You can understand this as server_name directive in Nginx.' >> mailcow.conf + echo '# Comma separated list without spaces! Example: ADDITIONAL_SERVER_NAMES=a.b.c,d.e.f' >> mailcow.conf + echo 'ADDITIONAL_SERVER_NAMES=' >> mailcow.conf + ;; + WEBAUTHN_ONLY_TRUSTED_VENDORS) + echo "# WebAuthn device manufacturer verification" >> mailcow.conf + echo '# After setting WEBAUTHN_ONLY_TRUSTED_VENDORS=y only devices from trusted manufacturers are allowed' >> mailcow.conf + echo '# root certificates can be placed for validation under mailcow-dockerized/data/web/inc/lib/WebAuthn/rootCertificates' >> mailcow.conf + echo 'WEBAUTHN_ONLY_TRUSTED_VENDORS=n' >> mailcow.conf + ;; + SPAMHAUS_DQS_KEY) + echo "# Spamhaus Data Query Service Key" >> mailcow.conf + echo '# Optional: Leave empty for none' >> mailcow.conf + echo '# Enter your key here if you are using a blocked ASN (OVH, AWS, Cloudflare e.g) for the unregistered Spamhaus Blocklist.' >> mailcow.conf + echo '# If empty, it will completely disable Spamhaus blocklists if it detects that you are running on a server using a blocked AS.' >> mailcow.conf + echo '# Otherwise it will work as usual.' >> mailcow.conf + echo 'SPAMHAUS_DQS_KEY=' >> mailcow.conf + ;; + WATCHDOG_VERBOSE) + echo '# Enable watchdog verbose logging' >> mailcow.conf + echo 'WATCHDOG_VERBOSE=n' >> mailcow.conf + ;; + SKIP_UNBOUND_HEALTHCHECK) + echo '# Skip Unbound (DNS Resolver) Healthchecks (NOT Recommended!) - y/n' >> mailcow.conf + echo 'SKIP_UNBOUND_HEALTHCHECK=n' >> mailcow.conf + ;; + DISABLE_NETFILTER_ISOLATION_RULE) + echo '# Prevent netfilter from setting an iptables/nftables rule to isolate the mailcow docker network - y/n' >> mailcow.conf + echo '# CAUTION: Disabling this may expose container ports to other neighbors on the same subnet, even if the ports are bound to localhost' >> mailcow.conf + echo 'DISABLE_NETFILTER_ISOLATION_RULE=n' >> mailcow.conf + ;; + HTTP_REDIRECT) + echo '# Redirect HTTP connections to HTTPS - y/n' >> mailcow.conf + echo 'HTTP_REDIRECT=n' >> mailcow.conf + ;; + ENABLE_IPV6) + echo '# IPv6 Controller Section' >> mailcow.conf + echo '# This variable controls the usage of IPv6 within mailcow.' >> mailcow.conf + echo '# Can either be true or false | Defaults to true' >> mailcow.conf + echo '# WARNING: MAKE SURE TO PROPERLY CONFIGURE IPv6 ON YOUR HOST FIRST BEFORE ENABLING THIS AS FAULTY CONFIGURATIONS CAN LEAD TO OPEN RELAYS!' >> mailcow.conf + echo '# A COMPLETE DOCKER STACK REBUILD (compose down && compose up -d) IS NEEDED TO APPLY THIS.' >> mailcow.conf + echo ENABLE_IPV6=${IPV6_BOOL} >> mailcow.conf + ;; + SKIP_CLAMD) + echo '# Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n' >> mailcow.conf + echo 'SKIP_CLAMD=n' >> mailcow.conf + ;; + SKIP_OLEFY) + echo '# Skip Olefy (olefy-mailcow) anti-virus for Office documents (Rspamd will auto-detect a missing Olefy container) - y/n' >> mailcow.conf + echo 'SKIP_OLEFY=n' >> mailcow.conf + ;; + REDISPASS) + echo "REDISPASS=$(LC_ALL=C /dev/null | head -c 28)" >> mailcow.conf + ;; + SOGO_URL_ENCRYPTION_KEY) + echo '# SOGo URL encryption key (exactly 16 characters, limited to Aโ€“Z, aโ€“z, 0โ€“9)' >> mailcow.conf + echo '# This key is used to encrypt email addresses within SOGo URLs' >> mailcow.conf + echo "SOGO_URL_ENCRYPTION_KEY=$(LC_ALL=C /dev/null | head -c 16)" >> mailcow.conf + ;; + ACME_DNS_CHALLENGE) + echo '# Enable DNS-01 challenge for ACME (acme-mailcow) - y/n' >> mailcow.conf + echo '# This requires you to set ACME_DNS_PROVIDER and ACME_ACCOUNT_EMAIL below' >> mailcow.conf + echo 'ACME_DNS_CHALLENGE=n' >> mailcow.conf + ;; + ACME_DNS_PROVIDER) + echo '# DNS provider for DNS-01 challenge (e.g. dns_cf, dns_azure, dns_gd, etc.)' >> mailcow.conf + echo '# See the dns-01 provider documentation for more information.' >> mailcow.conf + echo 'ACME_DNS_PROVIDER=dns_xxx' >> mailcow.conf + ;; + ACME_ACCOUNT_EMAIL) + echo '# Account email for ACME DNS-01 challenge registration' >> mailcow.conf + echo 'ACME_ACCOUNT_EMAIL=me@example.com' >> mailcow.conf + ;; + *) + echo "${option}=" >> mailcow.conf + ;; + esac + done +} \ No newline at end of file diff --git a/create_cold_standby.sh b/create_cold_standby.sh new file mode 100755 index 0000000000..924339af18 --- /dev/null +++ b/create_cold_standby.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +export REMOTE_SSH_KEY=/root/.ssh/id_rsa +export REMOTE_SSH_PORT=22 +export REMOTE_SSH_HOST=my.remote.host + +/opt/mailcow-dockerized/helper-scripts/_cold-standby.sh diff --git a/data/Dockerfiles/acme/Dockerfile b/data/Dockerfiles/acme/Dockerfile index ce3fc171b1..193ca7e00b 100644 --- a/data/Dockerfiles/acme/Dockerfile +++ b/data/Dockerfiles/acme/Dockerfile @@ -1,10 +1,10 @@ -FROM alpine:3.6 +FROM alpine:3.23 -LABEL maintainer "Andre Peters " +LABEL maintainer = "The Infrastructure Company GmbH " -RUN apk add --update --no-cache \ +RUN apk upgrade --no-cache \ + && apk add --update --no-cache \ bash \ - acme-client \ curl \ openssl \ bind-tools \ @@ -12,9 +12,27 @@ RUN apk add --update --no-cache \ mariadb-client \ redis \ tini \ - tzdata + tzdata \ + python3 \ + acme-tiny \ + git \ + socat \ + && git clone --depth 1 https://github.com/acmesh-official/acme.sh.git /opt/acme.sh \ + && chmod +x /opt/acme.sh/acme.sh \ + && mkdir -p /var/lib/acme/acme-sh -COPY docker-entrypoint.sh /srv/docker-entrypoint.sh +ENV ACME_SH_BIN=/opt/acme.sh/acme.sh \ + ACME_SH_HOME=/opt/acme.sh \ + ACME_SH_CONFIG_HOME=/var/lib/acme/acme-sh + +COPY acme.sh /srv/acme.sh +COPY functions.sh /srv/functions.sh +COPY obtain-certificate.sh /srv/obtain-certificate.sh +COPY obtain-certificate-dns.sh /srv/obtain-certificate-dns.sh +COPY load-dns-config.sh /srv/load-dns-config.sh +COPY reload-configurations.sh /srv/reload-configurations.sh COPY expand6.sh /srv/expand6.sh -CMD ["/sbin/tini", "-g", "--", "/srv/docker-entrypoint.sh"] +RUN chmod +x /srv/*.sh + +CMD ["/sbin/tini", "-g", "--", "/srv/acme.sh"] diff --git a/data/Dockerfiles/acme/acme.sh b/data/Dockerfiles/acme/acme.sh new file mode 100755 index 0000000000..6b7b743001 --- /dev/null +++ b/data/Dockerfiles/acme/acme.sh @@ -0,0 +1,505 @@ +#!/bin/bash +set -o pipefail +exec 5>&1 + +# Do not attempt to write to slave +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + export REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning" +else + export REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning" +fi + +until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do + echo "Waiting for Redis..." + sleep 2 +done + +# Create DNS-01 configuration template if it doesn't exist +if [[ ! -f /etc/acme/dns-01.conf ]]; then + mkdir -p /etc/acme + cat > /etc/acme/dns-01.conf <<'EOF' +# Add here your DNS-01 challenge configuration +# For more information, visit the acme.sh documentation: +# https://github.com/acmesh-official/acme.sh/wiki/dnsapi +EOF + echo "Created DNS-01 configuration template at /etc/acme/dns-01.conf" +fi + +source /srv/functions.sh +# Thanks to https://github.com/cvmiller -> https://github.com/cvmiller/expand6 +source /srv/expand6.sh + +# Skipping IP check when we like to live dangerously +if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + SKIP_IP_CHECK=y +fi + +# Skipping HTTP check when we like to live dangerously +if [[ "${SKIP_HTTP_VERIFICATION}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + SKIP_HTTP_VERIFICATION=y +fi + +# Request certificate for MAILCOW_HOSTNAME only +if [[ "${ONLY_MAILCOW_HOSTNAME}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + ONLY_MAILCOW_HOSTNAME=y +fi + +if [[ "${AUTODISCOVER_SAN}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + AUTODISCOVER_SAN=y +fi + +# Request individual certificate for every domain +if [[ "${ENABLE_SSL_SNI}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + ENABLE_SSL_SNI=y +fi + +if [[ "${ACME_DNS_CHALLENGE}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + ACME_DNS_CHALLENGE=y +fi + +if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + log_f "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..." + sleep 365d + exec $(readlink -f "$0") +fi + +log_f "Waiting for Docker API..." +until ping dockerapi -c1 > /dev/null; do + sleep 1 +done +log_f "Docker API OK" + +log_f "Waiting for Postfix..." +until ping postfix -c1 > /dev/null; do + sleep 1 +done +log_f "Postfix OK" + +log_f "Waiting for Dovecot..." +until ping dovecot -c1 > /dev/null; do + sleep 1 +done +log_f "Dovecot OK" + +ACME_BASE=/var/lib/acme +SSL_EXAMPLE=/var/lib/ssl-example + +mkdir -p ${ACME_BASE}/acme + +# Migrate +[[ -f ${ACME_BASE}/acme/private/privkey.pem ]] && mv ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/acme/key.pem +[[ -f ${ACME_BASE}/acme/private/account.key ]] && mv ${ACME_BASE}/acme/private/account.key ${ACME_BASE}/acme/account.pem +if [[ -f ${ACME_BASE}/acme/key.pem && -f ${ACME_BASE}/acme/cert.pem ]]; then + if verify_hash_match ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/acme/key.pem; then + log_f "Migrating to SNI folder structure..." + CERT_DOMAIN=($(openssl x509 -noout -text -in ${ACME_BASE}/acme/cert.pem | grep "Subject:" | sed -e 's/\(Subject:\)\|\(CN = \)\|\(CN=\)//g' | sed -e 's/^[[:space:]]*//')) + CERT_DOMAINS=(${CERT_DOMAIN} $(openssl x509 -noout -text -in ${ACME_BASE}/acme/cert.pem | grep "DNS:" | sed -e 's/\(DNS:\)\|,//g' | sed "s/${CERT_DOMAIN}//" | sed -e 's/^[[:space:]]*//')) + mkdir -p ${ACME_BASE}/${CERT_DOMAIN} + mv ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/${CERT_DOMAIN}/cert.pem + # key is only copied, not moved, because it is used by all other requests too + cp ${ACME_BASE}/acme/key.pem ${ACME_BASE}/${CERT_DOMAIN}/key.pem + chmod 600 ${ACME_BASE}/${CERT_DOMAIN}/key.pem + echo -n ${CERT_DOMAINS[*]} > ${ACME_BASE}/${CERT_DOMAIN}/domains + mv ${ACME_BASE}/acme/acme.csr ${ACME_BASE}/${CERT_DOMAIN}/acme.csr + log_f "OK" no_date + fi +fi + +[[ ! -f ${ACME_BASE}/dhparams.pem ]] && cp ${SSL_EXAMPLE}/dhparams.pem ${ACME_BASE}/dhparams.pem + +if [[ -f ${ACME_BASE}/cert.pem ]] && [[ -f ${ACME_BASE}/key.pem ]] && [[ $(stat -c%s ${ACME_BASE}/cert.pem) != 0 ]]; then + ISSUER=$(openssl x509 -in ${ACME_BASE}/cert.pem -noout -issuer) + if [[ ${ISSUER} != *"Let's Encrypt"* && ${ISSUER} != *"mailcow"* && ${ISSUER} != *"Fake LE Intermediate"* ]]; then + log_f "Found certificate with issuer other than mailcow snake-oil CA and Let's Encrypt, skipping ACME client..." + sleep 3650d + exec $(readlink -f "$0") + fi +else + if [[ -f ${ACME_BASE}/${MAILCOW_HOSTNAME}/cert.pem ]] && [[ -f ${ACME_BASE}/${MAILCOW_HOSTNAME}/key.pem ]] && verify_hash_match ${ACME_BASE}/${MAILCOW_HOSTNAME}/cert.pem ${ACME_BASE}/${MAILCOW_HOSTNAME}/key.pem; then + log_f "Restoring previous acme certificate and restarting script..." + cp ${ACME_BASE}/${MAILCOW_HOSTNAME}/cert.pem ${ACME_BASE}/cert.pem + cp ${ACME_BASE}/${MAILCOW_HOSTNAME}/key.pem ${ACME_BASE}/key.pem + # Restarting with env var set to trigger a restart, + exec env TRIGGER_RESTART=1 $(readlink -f "$0") + else + log_f "Restoring mailcow snake-oil certificates and restarting script..." + cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem + cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem + exec env TRIGGER_RESTART=1 $(readlink -f "$0") + fi +fi + +chmod 600 ${ACME_BASE}/key.pem + +log_f "Waiting for database..." +while ! /usr/bin/mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent > /dev/null; do + sleep 2 +done +log_f "Database OK" + +log_f "Waiting for Nginx..." +until $(curl --output /dev/null --silent --head --fail http://nginx.${COMPOSE_PROJECT_NAME}_mailcow-network:8081); do + sleep 2 +done +log_f "Nginx OK" + +log_f "Waiting for resolver..." +until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do + sleep 2 +done +log_f "Resolver OK" + +# Waiting for domain table +log_f "Waiting for domain table..." +while [[ -z ${DOMAIN_TABLE} ]]; do + curl --silent http://nginx.${COMPOSE_PROJECT_NAME}_mailcow-network/ >/dev/null 2>&1 + DOMAIN_TABLE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs) + [[ -z ${DOMAIN_TABLE} ]] && sleep 10 +done +log_f "OK" no_date + +log_f "Initializing, please wait..." + +while true; do + POSTFIX_CERT_SERIAL="$(echo | openssl s_client -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + DOVECOT_CERT_SERIAL="$(echo | openssl s_client -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + POSTFIX_CERT_SERIAL_NEW="$(echo | openssl s_client -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + DOVECOT_CERT_SERIAL_NEW="$(echo | openssl s_client -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + # Re-using previous acme-mailcow account and domain keys + if [[ ! -f ${ACME_BASE}/acme/key.pem ]]; then + log_f "Generating missing domain private rsa key..." + openssl genrsa 4096 > ${ACME_BASE}/acme/key.pem + else + log_f "Using existing domain rsa key ${ACME_BASE}/acme/key.pem" + fi + if [[ ! -f ${ACME_BASE}/acme/account.pem ]]; then + log_f "Generating missing Lets Encrypt account key..." + openssl genrsa 4096 > ${ACME_BASE}/acme/account.pem + else + log_f "Using existing Lets Encrypt account key ${ACME_BASE}/acme/account.pem" + fi + + chmod 600 ${ACME_BASE}/acme/key.pem + chmod 600 ${ACME_BASE}/acme/account.pem + + unset EXISTING_CERTS + declare -a EXISTING_CERTS + for cert_dir in ${ACME_BASE}/*/ ; do + if [[ ! -f ${cert_dir}domains ]] || [[ ! -f ${cert_dir}cert.pem ]] || [[ ! -f ${cert_dir}key.pem ]]; then + continue + fi + EXISTING_CERTS+=("$(basename ${cert_dir})") + done + + # Cleaning up and init validation arrays + unset SQL_DOMAIN_ARR + unset VALIDATED_CONFIG_DOMAINS + unset ADDITIONAL_VALIDATED_SAN + unset ADDITIONAL_WC_ARR + unset ADDITIONAL_SAN_ARR + unset CERT_ERRORS + unset CERT_CHANGED + unset CERT_AMOUNT_CHANGED + unset VALIDATED_CERTIFICATES + CERT_ERRORS=0 + CERT_CHANGED=0 + CERT_AMOUNT_CHANGED=0 + declare -a SQL_DOMAIN_ARR + declare -a VALIDATED_CONFIG_DOMAINS + declare -a ADDITIONAL_VALIDATED_SAN + declare -a ADDITIONAL_WC_ARR + declare -a ADDITIONAL_SAN_ARR + declare -a VALIDATED_CERTIFICATES + IFS=',' read -r -a TMP_ARR <<< "${ADDITIONAL_SAN}" + for i in "${TMP_ARR[@]}" ; do + if [[ "$i" =~ \.\*$ ]]; then + ADDITIONAL_WC_ARR+=(${i::-2}) + else + ADDITIONAL_SAN_ARR+=($i) + fi + done + + if [[ ${AUTODISCOVER_SAN} == "y" ]]; then + # Fetch certs for autoconfig and autodiscover subdomains + ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig' 'mta-sts') + fi + + if [[ ${SKIP_IP_CHECK} != "y" ]]; then + # Start IP detection + log_f "Detecting IP addresses..." + IPV4=$(get_ipv4) + IPV6=$(get_ipv6) + log_f "OK: ${IPV4}, ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}" + fi + + ######################################### + # IP and webroot challenge verification # + SQL_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 and active=1" -Bs) + if [[ ! $? -eq 0 ]]; then + log_f "Failed to read SQL domains, retrying in 1 minute..." + sleep 1m + exec $(readlink -f "$0") + fi + while read domains; do + if [[ -z "${domains}" ]]; then + # ignore empty lines + continue + fi + SQL_DOMAIN_ARR+=("${domains}") + done <<< "${SQL_DOMAINS}" + + if [[ ${ONLY_MAILCOW_HOSTNAME} != "y" ]]; then + # Fetch all domains with an active MTA-STS policy once. + unset MTA_STS_ACTIVE_DOMAINS + declare -A MTA_STS_ACTIVE_DOMAINS + if [[ ${AUTODISCOVER_SAN} == "y" ]]; then + SQL_MTA_STS_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM mta_sts WHERE active = 1" -Bs) + if [[ $? -eq 0 ]]; then + while read mta_sts_domain; do + if [[ -z "${mta_sts_domain}" ]]; then + # ignore empty lines + continue + fi + MTA_STS_ACTIVE_DOMAINS["${mta_sts_domain}"]=1 + done <<< "${SQL_MTA_STS_DOMAINS}" + fi + fi + for SQL_DOMAIN in "${SQL_DOMAIN_ARR[@]}"; do + unset VALIDATED_CONFIG_DOMAINS_SUBDOMAINS + declare -a VALIDATED_CONFIG_DOMAINS_SUBDOMAINS + for SUBDOMAIN in "${ADDITIONAL_WC_ARR[@]}"; do + FULL_SUBDOMAIN="${SUBDOMAIN}.${SQL_DOMAIN}" + + # Skip mta-sts subdomain unless MTA-STS is enabled (active) for this domain + if [[ "${SUBDOMAIN}" == "mta-sts" && -z "${MTA_STS_ACTIVE_DOMAINS[${SQL_DOMAIN}]}" ]]; then + log_f "MTA-STS is not enabled for ${SQL_DOMAIN} - skipping mta-sts subdomain certificate" + continue + fi + + # Skip if subdomain matches MAILCOW_HOSTNAME + if [[ "${FULL_SUBDOMAIN}" == "${MAILCOW_HOSTNAME}" ]]; then + continue + fi + # Skip if subdomain is covered by a wildcard in ADDITIONAL_SAN + if is_covered_by_wildcard "${FULL_SUBDOMAIN}"; then + log_f "Subdomain '${FULL_SUBDOMAIN}' is covered by wildcard - skipping explicit subdomain" + continue + fi + # Validate and add subdomain + if check_domain "${FULL_SUBDOMAIN}"; then + VALIDATED_CONFIG_DOMAINS_SUBDOMAINS+=("${FULL_SUBDOMAIN}") + fi + done + VALIDATED_CONFIG_DOMAINS+=("${VALIDATED_CONFIG_DOMAINS_SUBDOMAINS[*]}") + done + + # Fetch alias domains where target domain has MTA-STS enabled + if [[ ${AUTODISCOVER_SAN} == "y" ]]; then + SQL_ALIAS_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT ad.alias_domain FROM alias_domain ad INNER JOIN mta_sts m ON ad.target_domain = m.domain WHERE ad.active = 1 AND m.active = 1" -Bs) + if [[ $? -eq 0 ]]; then + while read alias_domain; do + if [[ -z "${alias_domain}" ]]; then + # ignore empty lines + continue + fi + # Only add mta-sts subdomain for alias domains + if [[ "mta-sts.${alias_domain}" != "${MAILCOW_HOSTNAME}" ]]; then + # Skip if mta-sts subdomain is covered by a wildcard + if is_covered_by_wildcard "mta-sts.${alias_domain}"; then + log_f "Alias domain mta-sts subdomain 'mta-sts.${alias_domain}' is covered by wildcard - skipping" + elif check_domain "mta-sts.${alias_domain}"; then + VALIDATED_CONFIG_DOMAINS+=("mta-sts.${alias_domain}") + fi + fi + done <<< "${SQL_ALIAS_DOMAINS}" + fi + fi + fi + + if check_domain ${MAILCOW_HOSTNAME}; then + VALIDATED_MAILCOW_HOSTNAME="${MAILCOW_HOSTNAME}" + fi + + if [[ ${ONLY_MAILCOW_HOSTNAME} != "y" ]]; then + for SAN in "${ADDITIONAL_SAN_ARR[@]}"; do + # Skip on CAA errors for SAN + SAN_PARENT_DOMAIN=$(echo ${SAN} | cut -d. -f2-) + SAN_CAAS=( $(dig CAA ${SAN_PARENT_DOMAIN} +short | sed -n 's/\d issue "\(.*\)"/\1/p') ) + if [[ ! -z ${SAN_CAAS} ]]; then + if [[ ${SAN_CAAS[@]} =~ "letsencrypt.org" ]]; then + log_f "Validated CAA for parent domain ${SAN_PARENT_DOMAIN} of ${SAN}" + else + log_f "Skipping ACME validation for ${SAN}: Lets Encrypt disallowed for ${SAN} by CAA record" + continue + fi + fi + if [[ ${SAN} == ${MAILCOW_HOSTNAME} ]]; then + continue + fi + if check_domain ${SAN}; then + ADDITIONAL_VALIDATED_SAN+=("${SAN}") + fi + done + fi + + # Check if MAILCOW_HOSTNAME is covered by a wildcard in ADDITIONAL_SAN + MAILCOW_HOSTNAME_COVERED=0 + if [[ ! -z ${VALIDATED_MAILCOW_HOSTNAME} ]]; then + if is_covered_by_wildcard "${VALIDATED_MAILCOW_HOSTNAME}"; then + MAILCOW_PARENT_DOMAIN=$(echo ${VALIDATED_MAILCOW_HOSTNAME} | cut -d. -f2-) + log_f "MAILCOW_HOSTNAME '${VALIDATED_MAILCOW_HOSTNAME}' is covered by wildcard '*.${MAILCOW_PARENT_DOMAIN}' - skipping explicit hostname" + MAILCOW_HOSTNAME_COVERED=1 + fi + fi + + # Unique domains for server certificate + if [[ ${ENABLE_SSL_SNI} == "y" ]]; then + # create certificate for server name and fqdn SANs only + if [[ ${MAILCOW_HOSTNAME_COVERED} == "1" ]]; then + SERVER_SAN_VALIDATED=($(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs)) + else + SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs)) + fi + else + # create certificate for all domains, including all subdomains from other domains [*] + if [[ ${MAILCOW_HOSTNAME_COVERED} == "1" ]]; then + SERVER_SAN_VALIDATED=($(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs)) + else + SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs)) + fi + fi + if [[ ! -z ${SERVER_SAN_VALIDATED[*]} ]]; then + CERT_NAME=${SERVER_SAN_VALIDATED[0]} + VALIDATED_CERTIFICATES+=("${CERT_NAME}") + + # obtain server certificate if required + DOMAINS=${SERVER_SAN_VALIDATED[@]} /srv/obtain-certificate.sh rsa + RETURN="$?" + if [[ "$RETURN" == "0" ]]; then # 0 = cert created successfully + CERT_AMOUNT_CHANGED=1 + CERT_CHANGED=1 + elif [[ "$RETURN" == "1" ]]; then # 1 = cert renewed successfully + CERT_CHANGED=1 + elif [[ "$RETURN" == "2" ]]; then # 2 = cert not due for renewal + : + else + CERT_ERRORS=1 + fi + # copy hostname certificate to default/server certificate + # do not a key when cert is missing, this can lead to a mismatch of cert/key + if [[ -f ${ACME_BASE}/${CERT_NAME}/cert.pem ]]; then + cp ${ACME_BASE}/${CERT_NAME}/cert.pem ${ACME_BASE}/cert.pem + cp ${ACME_BASE}/${CERT_NAME}/key.pem ${ACME_BASE}/key.pem + fi + fi + + # individual certificates for SNI [@] + if [[ ${ENABLE_SSL_SNI} == "y" ]]; then + for VALIDATED_DOMAINS in "${VALIDATED_CONFIG_DOMAINS[@]}"; do + VALIDATED_DOMAINS_ARR=(${VALIDATED_DOMAINS}) + + unset VALIDATED_DOMAINS_SORTED + declare -a VALIDATED_DOMAINS_SORTED + VALIDATED_DOMAINS_SORTED=(${VALIDATED_DOMAINS_ARR[0]} $(echo ${VALIDATED_DOMAINS_ARR[@]:1} | xargs -n1 | sort -u | xargs)) + + # remove all domain names that are already inside the server certificate (SERVER_SAN_VALIDATED) + for domain in "${SERVER_SAN_VALIDATED[@]}"; do + for i in "${!VALIDATED_DOMAINS_SORTED[@]}"; do + if [[ ${VALIDATED_DOMAINS_SORTED[i]} = $domain ]]; then + unset 'VALIDATED_DOMAINS_SORTED[i]' + fi + done + done + + if [[ ! -z ${VALIDATED_DOMAINS_SORTED[*]} ]]; then + CERT_NAME=${VALIDATED_DOMAINS_SORTED[0]} + VALIDATED_CERTIFICATES+=("${CERT_NAME}") + # obtain certificate if required + DOMAINS=${VALIDATED_DOMAINS_SORTED[@]} /srv/obtain-certificate.sh rsa + RETURN="$?" + if [[ "$RETURN" == "0" ]]; then # 0 = cert created successfully + CERT_AMOUNT_CHANGED=1 + CERT_CHANGED=1 + elif [[ "$RETURN" == "1" ]]; then # 1 = cert renewed successfully + CERT_CHANGED=1 + elif [[ "$RETURN" == "2" ]]; then # 2 = cert not due for renewal + : + else + CERT_ERRORS=1 + fi + fi + done + fi + + if [[ -z ${VALIDATED_CERTIFICATES[*]} ]]; then + log_f "Cannot validate any hostnames, skipping Let's Encrypt for 1 hour." + log_f "Use SKIP_LETS_ENCRYPT=y in mailcow.conf to skip it permanently." + ${REDIS_CMDLINE} SET ACME_FAIL_TIME "$(date +%s)" + sleep 1h + exec $(readlink -f "$0") + fi + + # find orphaned certificates if no errors occurred + if [[ "${CERT_ERRORS}" == "0" ]]; then + for EXISTING_CERT in "${EXISTING_CERTS[@]}"; do + if [[ ! "`printf '_%s_\n' "${VALIDATED_CERTIFICATES[@]}"`" == *"_${EXISTING_CERT}_"* ]]; then + DATE=$(date +%Y-%m-%d_%H_%M_%S) + log_f "Found orphaned certificate: ${EXISTING_CERT} - archiving it at ${ACME_BASE}/backups/${EXISTING_CERT}/" + BACKUP_DIR=${ACME_BASE}/backups/${EXISTING_CERT}/${DATE} + # archive rsa cert and any other files + mkdir -p ${ACME_BASE}/backups/${EXISTING_CERT} + mv ${ACME_BASE}/${EXISTING_CERT} ${BACKUP_DIR} + CERT_CHANGED=1 + CERT_AMOUNT_CHANGED=1 + fi + done + fi + + # reload on new or changed certificates + if [[ "${CERT_CHANGED}" == "1" ]]; then + rm -f "${ACME_BASE}/force_renew" 2> /dev/null + RELOAD_LOOP_C=1 + while [[ "${POSTFIX_CERT_SERIAL}" == "${POSTFIX_CERT_SERIAL_NEW}" ]] || [[ "${DOVECOT_CERT_SERIAL}" == "${DOVECOT_CERT_SERIAL_NEW}" ]] || [[ ${#POSTFIX_CERT_SERIAL_NEW} -ne 36 ]] || [[ ${#DOVECOT_CERT_SERIAL_NEW} -ne 36 ]]; do + log_f "Reloading or restarting services... (${RELOAD_LOOP_C})" + RELOAD_LOOP_C=$((RELOAD_LOOP_C + 1)) + CERT_AMOUNT_CHANGED=${CERT_AMOUNT_CHANGED} /srv/reload-configurations.sh + log_f "Waiting for containers to settle..." + sleep 10 + until nc -z dovecot 143; do + sleep 1 + done + until nc -z postfix 25; do + sleep 1 + done + POSTFIX_CERT_SERIAL_NEW="$(echo | openssl s_client -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + DOVECOT_CERT_SERIAL_NEW="$(echo | openssl s_client -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + if [[ ${RELOAD_LOOP_C} -gt 3 ]]; then + log_f "Some services do return old end dates, something went wrong!" + ${REDIS_CMDLINE} SET ACME_FAIL_TIME "$(date +%s)" + break; + fi + done + fi + + case "$CERT_ERRORS" in + 0) # all successful + if [[ "${CERT_CHANGED}" == "1" ]]; then + if [[ "${CERT_AMOUNT_CHANGED}" == "1" ]]; then + log_f "Certificates successfully requested and renewed where required, sleeping one day" + else + log_f "Certificates were successfully renewed where required, sleeping for another day." + fi + else + log_f "Certificates were successfully validated, no changes or renewals required, sleeping for another day." + fi + sleep 1d + ;; + *) # non-zero + log_f "Some errors occurred, retrying in 30 minutes..." + ${REDIS_CMDLINE} SET ACME_FAIL_TIME "$(date +%s)" + sleep 30m + exec $(readlink -f "$0") + ;; + esac + +done diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh deleted file mode 100755 index 0c2099077c..0000000000 --- a/data/Dockerfiles/acme/docker-entrypoint.sh +++ /dev/null @@ -1,425 +0,0 @@ -#!/bin/bash -set -o pipefail -exec 5>&1 - -# Thanks to https://github.com/cvmiller -> https://github.com/cvmiller/expand6 -source /srv/expand6.sh - -log_f() { - if [[ ${2} == "no_nl" ]]; then - echo -n "$(date) - ${1}" - elif [[ ${2} == "no_date" ]]; then - echo "${1}" - elif [[ ${2} != "redis_only" ]]; then - echo "$(date) - ${1}" - fi - if [[ ${3} == "b64" ]]; then - redis-cli -h redis LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"base64,$(printf '%s' "${1}")\"}" > /dev/null - else - redis-cli -h redis LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \ - tr '%&;$"_[]{}-\r\n' ' ')\"}" > /dev/null - fi -} - -if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then - log_f "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..." - sleep 365d - exec $(readlink -f "$0") -fi - -log_f "Waiting for Docker API..." no_nl -until ping dockerapi -c1 > /dev/null; do - sleep 1 -done -log_f "OK" no_date - -ACME_BASE=/var/lib/acme -SSL_EXAMPLE=/var/lib/ssl-example - -mkdir -p ${ACME_BASE}/acme/private - -restart_containers(){ - for container in $*; do - log_f "Restarting ${container}..." no_nl - C_REST_OUT=$(curl -X POST --insecure https://dockerapi/containers/${container}/restart | jq -r '.msg') - log_f "${C_REST_OUT}" no_date - done -} - -array_diff() { - # https://stackoverflow.com/questions/2312762, Alex Offshore - eval local ARR1=\(\"\${$2[@]}\"\) - eval local ARR2=\(\"\${$3[@]}\"\) - local IFS=$'\n' - mapfile -t $1 < <(comm -23 <(echo "${ARR1[*]}" | sort) <(echo "${ARR2[*]}" | sort)) -} - -verify_hash_match(){ - CERT_HASH=$(openssl x509 -noout -modulus -in "${1}" | openssl md5) - KEY_HASH=$(openssl rsa -noout -modulus -in "${2}" | openssl md5) - if [[ ${CERT_HASH} != ${KEY_HASH} ]]; then - log_f "Certificate and key hashes do not match!" - return 1 - else - log_f "Verified hashes." - return 0 - fi -} - -get_ipv4(){ - local IPV4= - local IPV4_SRCS= - local TRY= - IPV4_SRCS[0]="ip4.mailcow.email" - IPV4_SRCS[1]="ip4.korves.net" - until [[ ! -z ${IPV4} ]] || [[ ${TRY} -ge 10 ]]; do - IPV4=$(curl --connect-timeout 3 -m 10 -L4s ${IPV4_SRCS[$RANDOM % ${#IPV4_SRCS[@]} ]} | grep -E "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$") - [[ ! -z ${TRY} ]] && sleep 1 - TRY=$((TRY+1)) - done - echo ${IPV4} -} - -get_ipv6(){ - local IPV6= - local IPV6_SRCS= - local TRY= - IPV6_SRCS[0]="ip6.korves.net" - IPV6_SRCS[1]="ip6.mailcow.email" - until [[ ! -z ${IPV6} ]] || [[ ${TRY} -ge 10 ]]; do - IPV6=$(curl --connect-timeout 3 -m 10 -L6s ${IPV6_SRCS[$RANDOM % ${#IPV6_SRCS[@]} ]} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") - [[ ! -z ${TRY} ]] && sleep 1 - TRY=$((TRY+1)) - done - echo ${IPV6} -} - -[[ ! -f ${ACME_BASE}/dhparams.pem ]] && cp ${SSL_EXAMPLE}/dhparams.pem ${ACME_BASE}/dhparams.pem - -if [[ -f ${ACME_BASE}/cert.pem ]] && [[ -f ${ACME_BASE}/key.pem ]]; then - ISSUER=$(openssl x509 -in ${ACME_BASE}/cert.pem -noout -issuer) - if [[ ${ISSUER} != *"Let's Encrypt"* && ${ISSUER} != *"mailcow"* ]]; then - log_f "Found certificate with issuer other than mailcow snake-oil CA and Let's Encrypt, skipping ACME client..." - sleep 3650d - exec $(readlink -f "$0") - else - declare -a SAN_ARRAY_NOW - SAN_NAMES=$(openssl x509 -noout -text -in ${ACME_BASE}/cert.pem | awk '/X509v3 Subject Alternative Name/ {getline;gsub(/ /, "", $0); print}' | tr -d "DNS:") - if [[ ! -z ${SAN_NAMES} ]]; then - IFS=',' read -a SAN_ARRAY_NOW <<< ${SAN_NAMES} - log_f "Found Let's Encrypt or mailcow snake-oil CA issued certificate with SANs: ${SAN_ARRAY_NOW[*]}" - fi - fi -else - if [[ -f ${ACME_BASE}/acme/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/privkey.pem ]]; then - if verify_hash_match ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/acme/private/privkey.pem; then - log_f "Restoring previous acme certificate and restarting script..." - cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem - # Restarting with env var set to trigger a restart, - exec env TRIGGER_RESTART=1 $(readlink -f "$0") - fi - ISSUER="mailcow" - else - log_f "Restoring mailcow snake-oil certificates and restarting script..." - cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem - cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem - exec env TRIGGER_RESTART=1 $(readlink -f "$0") - fi -fi - -log_f "Waiting for database... " -while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do - sleep 2 -done -log_f "Initializing, please wait... " - - -while true; do - if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then - SKIP_IP_CHECK=y - fi - unset SQL_DOMAIN_ARR - unset VALIDATED_CONFIG_DOMAINS - unset ADDITIONAL_VALIDATED_SAN - declare -a SQL_DOMAIN_ARR - declare -a VALIDATED_CONFIG_DOMAINS - declare -a ADDITIONAL_VALIDATED_SAN - IFS=',' read -r -a ADDITIONAL_SAN_ARR <<< "${ADDITIONAL_SAN}" - log_f "Detecting IP addresses... " no_nl - IPV4=$(get_ipv4) - IPV6=$(get_ipv6) - log_f "OK" no_date - - # Hard-fail on CAA errors for MAILCOW_HOSTNAME - MH_PARENT_DOMAIN=$(echo ${MAILCOW_HOSTNAME} | cut -d. -f2-) - MH_CAAS=( $(dig CAA ${MH_PARENT_DOMAIN} +short | sed -n 's/\d issue "\(.*\)"/\1/p') ) - if [[ ! -z ${MH_CAAS} ]]; then - if [[ ${MH_CAAS[@]} =~ "letsencrypt.org" ]]; then - echo "Validated CAA for parent domain ${MH_PARENT_DOMAIN}" - else - echo "Skipping ACME validation: Lets Encrypt disallowed for ${MAILCOW_HOSTNAME} by CAA record, retrying in 1h..." - sleep 1h - exec $(readlink -f "$0") - fi - fi - - # Container ids may have changed - CONTAINERS_RESTART=($(curl --silent --insecure https://dockerapi/containers/json | jq -r '.[] | {name: .Config.Labels["com.docker.compose.service"], id: .Id}' | jq -rc 'select( .name | tostring | contains("nginx-mailcow") or contains("postfix-mailcow") or contains("dovecot-mailcow")) | .id' | tr "\n" " ")) - - log_f "Waiting for domain table... " no_nl - while [[ -z ${DOMAIN_TABLE} ]]; do - curl --silent http://nginx/ >/dev/null 2>&1 - DOMAIN_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs) - [[ -z ${DOMAIN_TABLE} ]] && sleep 10 - done - log_f "OK" no_date - - while read domains; do - SQL_DOMAIN_ARR+=("${domains}") - done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 UNION SELECT alias_domain FROM alias_domain" -Bs) - - for SQL_DOMAIN in "${SQL_DOMAIN_ARR[@]}"; do - A_CONFIG=$(dig A autoconfig.${SQL_DOMAIN} +short | tail -n 1) - AAAA_CONFIG=$(dig AAAA autoconfig.${SQL_DOMAIN} +short | tail -n 1) - # Check if CNAME without v6 enabled target - if [[ ! -z ${AAAA_CONFIG} ]] && [[ -z $(echo ${AAAA_CONFIG} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") ]]; then - AAAA_CONFIG= - fi - if [[ ! -z ${AAAA_CONFIG} ]]; then - log_f "Found AAAA record for autoconfig.${SQL_DOMAIN}: ${AAAA_CONFIG} - skipping A record check" - if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_CONFIG}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed AAAA record autoconfig.${SQL_DOMAIN}" - VALIDATED_CONFIG_DOMAINS+=("autoconfig.${SQL_DOMAIN}") - else - log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname autoconfig.${SQL_DOMAIN} ($(expand ${AAAA_CONFIG}))" - fi - elif [[ ! -z ${A_CONFIG} ]]; then - log_f "Found A record for autoconfig.${SQL_DOMAIN}: ${A_CONFIG}" - if [[ ${IPV4:-ERR} == ${A_CONFIG} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed A record autoconfig.${SQL_DOMAIN}" - VALIDATED_CONFIG_DOMAINS+=("autoconfig.${SQL_DOMAIN}") - else - log_f "Cannot match your IP ${IPV4} against hostname autoconfig.${SQL_DOMAIN} (${A_CONFIG})" - fi - else - log_f "No A or AAAA record found for hostname autoconfig.${SQL_DOMAIN}" - fi - - A_DISCOVER=$(dig A autodiscover.${SQL_DOMAIN} +short | tail -n 1) - AAAA_DISCOVER=$(dig AAAA autodiscover.${SQL_DOMAIN} +short | tail -n 1) - # Check if CNAME without v6 enabled target - if [[ ! -z ${AAAA_DISCOVER} ]] && [[ -z $(echo ${AAAA_DISCOVER} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") ]]; then - AAAA_DISCOVER= - fi - if [[ ! -z ${AAAA_DISCOVER} ]]; then - log_f "Found AAAA record for autodiscover.${SQL_DOMAIN}: ${AAAA_DISCOVER} - skipping A record check" - if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_DISCOVER}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed AAAA record autodiscover.${SQL_DOMAIN}" - VALIDATED_CONFIG_DOMAINS+=("autodiscover.${SQL_DOMAIN}") - else - log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname autodiscover.${SQL_DOMAIN} ($(expand ${AAAA_DISCOVER}))" - fi - elif [[ ! -z ${A_DISCOVER} ]]; then - log_f "Found A record for autodiscover.${SQL_DOMAIN}: ${A_DISCOVER}" - if [[ ${IPV4:-ERR} == ${A_DISCOVER} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed A record autodiscover.${SQL_DOMAIN}" - VALIDATED_CONFIG_DOMAINS+=("autodiscover.${SQL_DOMAIN}") - else - log_f "Cannot match your IP ${IPV4} against hostname autodiscover.${SQL_DOMAIN} (${A_DISCOVER})" - fi - else - log_f "No A or AAAA record found for hostname autodiscover.${SQL_DOMAIN}" - fi - done - - A_MAILCOW_HOSTNAME=$(dig A ${MAILCOW_HOSTNAME} +short | tail -n 1) - AAAA_MAILCOW_HOSTNAME=$(dig AAAA ${MAILCOW_HOSTNAME} +short | tail -n 1) - # Check if CNAME without v6 enabled target - if [[ ! -z ${AAAA_MAILCOW_HOSTNAME} ]] && [[ -z $(echo ${AAAA_MAILCOW_HOSTNAME} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") ]]; then - AAAA_MAILCOW_HOSTNAME= - fi - if [[ ! -z ${AAAA_MAILCOW_HOSTNAME} ]]; then - log_f "Found AAAA record for ${MAILCOW_HOSTNAME}: ${AAAA_MAILCOW_HOSTNAME} - skipping A record check" - if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_MAILCOW_HOSTNAME}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed AAAA record ${MAILCOW_HOSTNAME}" - VALIDATED_MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - else - log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname ${MAILCOW_HOSTNAME} ($(expand ${AAAA_MAILCOW_HOSTNAME}))" - fi - elif [[ ! -z ${A_MAILCOW_HOSTNAME} ]]; then - log_f "Found A record for ${MAILCOW_HOSTNAME}: ${A_MAILCOW_HOSTNAME}" - if [[ ${IPV4:-ERR} == ${A_MAILCOW_HOSTNAME} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed A record ${A_MAILCOW_HOSTNAME}" - VALIDATED_MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - else - log_f "Cannot match your IP ${IPV4} against hostname ${MAILCOW_HOSTNAME} (${A_MAILCOW_HOSTNAME})" - fi - else - log_f "No A or AAAA record found for hostname ${MAILCOW_HOSTNAME}" - fi - - for SAN in "${ADDITIONAL_SAN_ARR[@]}"; do - # Skip on CAA errors for SAN - SAN_PARENT_DOMAIN=$(echo ${SAN} | cut -d. -f2-) - SAN_CAAS=( $(dig CAA ${SAN_PARENT_DOMAIN} +short | sed -n 's/\d issue "\(.*\)"/\1/p') ) - if [[ ! -z ${SAN_CAAS} ]]; then - if [[ ${SAN_CAAS[@]} =~ "letsencrypt.org" ]]; then - echo "Validated CAA for parent domain ${SAN_PARENT_DOMAIN} of ${SAN}" - else - echo "Skipping ACME validation for ${SAN}: Lets Encrypt disallowed for ${SAN} by CAA record" - continue - fi - fi - if [[ ${SAN} == ${MAILCOW_HOSTNAME} ]]; then - continue - fi - A_SAN=$(dig A ${SAN} +short | tail -n 1) - AAAA_SAN=$(dig AAAA ${SAN} +short | tail -n 1) - # Check if CNAME without v6 enabled target - if [[ ! -z ${AAAA_SAN} ]] && [[ -z $(echo ${AAAA_SAN} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") ]]; then - AAAA_SAN= - fi - if [[ ! -z ${AAAA_SAN} ]]; then - log_f "Found AAAA record for ${SAN}: ${AAAA_SAN} - skipping A record check" - if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_SAN}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed AAAA record ${SAN}" - ADDITIONAL_VALIDATED_SAN+=("${SAN}") - else - log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname ${SAN} ($(expand ${AAAA_SAN}))" - fi - elif [[ ! -z ${A_SAN} ]]; then - log_f "Found A record for ${SAN}: ${A_SAN}" - if [[ ${IPV4:-ERR} == ${A_SAN} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then - log_f "Confirmed A record ${A_SAN}" - ADDITIONAL_VALIDATED_SAN+=("${SAN}") - else - log_f "Cannot match your IP ${IPV4} against hostname ${SAN} (${A_SAN})" - fi - else - log_f "No A or AAAA record found for hostname ${SAN}" - fi - done - - # Unique elements - ALL_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs)) - if [[ -z ${ALL_VALIDATED[*]} ]]; then - log_f "Cannot validate hostnames, skipping Let's Encrypt for 1 hour." - log_f "Use SKIP_LETS_ENCRYPT=y in mailcow.conf to skip it permanently." - sleep 1h - exec $(readlink -f "$0") - fi - - array_diff ORPHANED_SAN SAN_ARRAY_NOW ALL_VALIDATED - if [[ ! -z ${ORPHANED_SAN[*]} ]] && [[ ${ISSUER} != *"mailcow"* ]]; then - DATE=$(date +%Y-%m-%d_%H_%M_%S) - log_f "Found orphaned SAN ${ORPHANED_SAN[*]} in certificate, moving old files to ${ACME_BASE}/acme/private/${DATE}.bak/, keeping key file..." - mkdir -p ${ACME_BASE}/acme/private/${DATE}.bak/ - [[ -f ${ACME_BASE}/acme/private/account.key ]] && mv ${ACME_BASE}/acme/private/account.key ${ACME_BASE}/acme/private/${DATE}.bak/ - [[ -f ${ACME_BASE}/acme/fullchain.pem ]] && mv ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/acme/private/${DATE}.bak/ - [[ -f ${ACME_BASE}/acme/cert.pem ]] && mv ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/acme/private/${DATE}.bak/ - cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/acme/private/${DATE}.bak/ # Keep key for TLSA 3 1 1 records - fi - - ACME_RESPONSE=$(acme-client \ - -v -e -b -N -n \ - -a 'https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf' \ - -f ${ACME_BASE}/acme/private/account.key \ - -k ${ACME_BASE}/acme/private/privkey.pem \ - -c ${ACME_BASE}/acme \ - ${ALL_VALIDATED[*]} 2>&1 | tee /dev/fd/5) - case "$?" in - 0) # new certs - ACME_RESPONSE_B64=$(echo ${ACME_RESPONSE} | openssl enc -e -A -base64) - log_f "${ACME_RESPONSE_B64}" redis_only b64 - # cp the new certificates and keys - cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem - - # restart docker containers - if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then - log_f "Certificate was successfully requested, but key and certificate have non-matching hashes, restoring mailcow snake-oil and restarting containers..." - cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem - cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem - fi - restart_containers ${CONTAINERS_RESTART[*]} - ;; - 1) # failure - ACME_RESPONSE_B64=$(echo ${ACME_RESPONSE} | openssl enc -e -A -base64) - log_f "${ACME_RESPONSE_B64}" redis_only b64 - if [[ $ACME_RESPONSE =~ "No registration exists" ]]; then - log_f "Registration keys are invalid, deleting old keys and restarting..." - rm ${ACME_BASE}/acme/private/account.key - exec $(readlink -f "$0") - fi - if [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ]]; then - log_f "Error requesting certificate, restoring previous certificate from backup and restarting containers...." - cp ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - elif [[ -f ${ACME_BASE}/acme/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/privkey.pem ]]; then - log_f "Error requesting certificate, restoring from previous acme request and restarting containers..." - cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - fi - if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then - log_f "Error verifying certificates, restoring mailcow snake-oil and restarting containers..." - cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem - cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - fi - [[ ${TRIGGER_RESTART} == 1 ]] && restart_containers ${CONTAINERS_RESTART[*]} - log_f "Retrying in 30 minutes..." - sleep 30m - exec $(readlink -f "$0") - ;; - 2) # no change - ACME_RESPONSE_B64=$(echo ${ACME_RESPONSE} | openssl enc -e -A -base64) - log_f "${ACME_RESPONSE_B64}" redis_only b64 - if ! diff ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem; then - log_f "Certificate was not changed, but active certificate does not match the verified certificate, fixing and restarting containers..." - cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - fi - if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then - log_f "Certificate was not changed, but hashes do not match, restoring from previous acme request and restarting containers..." - cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - fi - log_f "Certificate was not changed" - [[ ${TRIGGER_RESTART} == 1 ]] && restart_containers ${CONTAINERS_RESTART[*]} - ;; - *) # unspecified - ACME_RESPONSE_B64=$(echo ${ACME_RESPONSE} | openssl enc -e -A -base64) - log_f "${ACME_RESPONSE_B64}" redis_only b64 - if [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ]]; then - log_f "Error requesting certificate, restoring previous certificate from backup and restarting containers...." - cp ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - elif [[ -f ${ACME_BASE}/acme/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/privkey.pem ]]; then - log_f "Error requesting certificate, restoring from previous acme request and restarting containers..." - cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem - cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - fi - if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then - log_f "Error verifying certificates, restoring mailcow snake-oil..." - cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem - cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem - TRIGGER_RESTART=1 - fi - [[ ${TRIGGER_RESTART} == 1 ]] && restart_containers ${CONTAINERS_RESTART[*]} - log_f "Retrying in 30 minutes..." - sleep 30m - exec $(readlink -f "$0") - ;; - esac - - log_f "ACME certificate validation done. Sleeping for another day." - sleep 1d - -done diff --git a/data/Dockerfiles/acme/functions.sh b/data/Dockerfiles/acme/functions.sh new file mode 100644 index 0000000000..707f46950c --- /dev/null +++ b/data/Dockerfiles/acme/functions.sh @@ -0,0 +1,166 @@ +#!/bin/bash + +log_f() { + if [[ ${2} == "no_nl" ]]; then + echo -n "$(date) - ${1}" + elif [[ ${2} == "no_date" ]]; then + echo "${1}" + elif [[ ${2} != "redis_only" ]]; then + echo "$(date) - ${1}" + fi + if [[ ${3} == "b64" ]]; then + ${REDIS_CMDLINE} LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"base64,$(printf '%s' "${MAILCOW_HOSTNAME} - ${1}")\"}" > /dev/null + else + ${REDIS_CMDLINE} LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${MAILCOW_HOSTNAME} - ${1}" | \ + tr '%&;$"[]{}-\r\n' ' ')\"}" > /dev/null + fi +} + +verify_email(){ + regex="^(([A-Za-z0-9]+((\.|\-|\_|\+)?[A-Za-z0-9]?)*[A-Za-z0-9]+)|[A-Za-z0-9]+)@(([A-Za-z0-9]+)+((\.|\-|\_)?([A-Za-z0-9]+)+)*)+\.([A-Za-z]{2,})+$" + if [[ $1 =~ ${regex} ]]; then + return 0 + else + return 1 + fi +} + +verify_hash_match(){ + CERT_HASH=$(openssl x509 -in "${1}" -noout -pubkey | openssl md5) + KEY_HASH=$(openssl pkey -in "${2}" -pubout | openssl md5) + if [[ ${CERT_HASH} != ${KEY_HASH} ]]; then + log_f "Certificate and key hashes do not match!" + return 1 + else + log_f "Verified hashes." + return 0 + fi +} + +get_ipv4(){ + local IPV4= + local IPV4_SRCS= + local TRY= + IPV4_SRCS[0]="ip4.mailcow.email" + IPV4_SRCS[1]="ip4.nevondo.com" + until [[ ! -z ${IPV4} ]] || [[ ${TRY} -ge 10 ]]; do + IPV4=$(curl --connect-timeout 3 -m 10 -L4s ${IPV4_SRCS[$RANDOM % ${#IPV4_SRCS[@]} ]} | grep -E "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$") + [[ ! -z ${TRY} ]] && sleep 1 + TRY=$((TRY+1)) + done + echo ${IPV4} +} + +get_ipv6(){ + local IPV6= + local IPV6_SRCS= + local TRY= + IPV6_SRCS[0]="ip6.mailcow.email" + IPV6_SRCS[1]="ip6.nevondo.com" + until [[ ! -z ${IPV6} ]] || [[ ${TRY} -ge 10 ]]; do + IPV6=$(curl --connect-timeout 3 -m 10 -L6s ${IPV6_SRCS[$RANDOM % ${#IPV6_SRCS[@]} ]} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") + [[ ! -z ${TRY} ]] && sleep 1 + TRY=$((TRY+1)) + done + echo ${IPV6} +} + +check_domain(){ + DOMAIN=$1 + A_DOMAIN=$(dig A ${DOMAIN} +short | tail -n 1) + AAAA_DOMAIN=$(dig AAAA ${DOMAIN} +short | tail -n 1) + # Hard-fail on CAA errors for MAILCOW_HOSTNAME + PARENT_DOMAIN=$(echo ${DOMAIN} | cut -d. -f2-) + CAAS=( $(dig CAA ${PARENT_DOMAIN} +short | sed -n 's/\d issue "\(.*\)"/\1/p') ) + if [[ ! -z ${CAAS} ]]; then + if [[ ${CAAS[@]} =~ "letsencrypt.org" ]]; then + log_f "Validated CAA for parent domain ${PARENT_DOMAIN}" + else + log_f "Lets Encrypt disallowed for ${PARENT_DOMAIN} by CAA record" + return 1 + fi + fi + + if [[ ${ACME_DNS_CHALLENGE} == "y" ]]; then + log_f "ACME_DNS_CHALLENGE=y - skipping IP and HTTP validation for ${DOMAIN}" + return 0 + fi + # Check if CNAME without v6 enabled target + if [[ ! -z ${AAAA_DOMAIN} ]] && [[ -z $(echo ${AAAA_DOMAIN} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") ]]; then + AAAA_DOMAIN= + fi + if [[ ! -z ${AAAA_DOMAIN} ]]; then + log_f "Found AAAA record for ${DOMAIN}: ${AAAA_DOMAIN} - skipping A record check" + if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_DOMAIN}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]] || [[ ${SNAT6_TO_SOURCE} != "n" ]]; then + if verify_challenge_path "${DOMAIN}" 6; then + log_f "Confirmed AAAA record with IP $(expand ${AAAA_DOMAIN})" + return 0 + else + log_f "Confirmed AAAA record with IP $(expand ${AAAA_DOMAIN}), but HTTP validation failed" + fi + else + log_f "Cannot match your IP $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) against hostname ${DOMAIN} (DNS returned $(expand ${AAAA_DOMAIN}))" + fi + elif [[ ! -z ${A_DOMAIN} ]]; then + log_f "Found A record for ${DOMAIN}: ${A_DOMAIN}" + if [[ ${IPV4:-ERR} == ${A_DOMAIN} ]] || [[ ${SKIP_IP_CHECK} == "y" ]] || [[ ${SNAT_TO_SOURCE} != "n" ]]; then + if verify_challenge_path "${DOMAIN}" 4; then + log_f "Confirmed A record ${A_DOMAIN}" + return 0 + else + log_f "Confirmed A record with IP ${A_DOMAIN}, but HTTP validation failed" + fi + else + log_f "Cannot match your IP ${IPV4} against hostname ${DOMAIN} (DNS returned ${A_DOMAIN})" + fi + else + log_f "No A or AAAA record found for hostname ${DOMAIN}" + fi + return 1 +} + +verify_challenge_path(){ + if [[ ${SKIP_HTTP_VERIFICATION} == "y" ]]; then + echo '(skipping check, returning 0)' + return 0 + fi + # verify_challenge_path URL 4|6 + RANDOM_N=${RANDOM}${RANDOM}${RANDOM} + echo ${RANDOM_N} > /var/www/acme/${RANDOM_N} + if [[ "$(curl --insecure -${2} -L http://${1}/.well-known/acme-challenge/${RANDOM_N} --silent)" == "${RANDOM_N}" ]]; then + rm /var/www/acme/${RANDOM_N} + return 0 + else + rm /var/www/acme/${RANDOM_N} + return 1 + fi +} + +# Check if a domain is covered by a wildcard (*.example.com) in ADDITIONAL_SAN +# Usage: is_covered_by_wildcard "subdomain.example.com" +# Returns: 0 if covered, 1 if not covered +# Note: Only returns 0 (covered) when DNS-01 challenge is enabled, +# as wildcards cannot be validated with HTTP-01 challenge +is_covered_by_wildcard() { + local DOMAIN=$1 + + # Only skip if DNS challenge is enabled (wildcards require DNS-01) + if [[ ${ACME_DNS_CHALLENGE} != "y" ]]; then + return 1 + fi + + # Return early if no ADDITIONAL_SAN is set + if [[ -z ${ADDITIONAL_SAN} ]]; then + return 1 + fi + + # Extract parent domain (e.g., mail.example.com -> example.com) + local PARENT_DOMAIN=$(echo ${DOMAIN} | cut -d. -f2-) + + # Check if ADDITIONAL_SAN contains a wildcard for this parent domain + if [[ "${ADDITIONAL_SAN}" == *"*.${PARENT_DOMAIN}"* ]]; then + return 0 # Covered by wildcard + fi + + return 1 # Not covered +} diff --git a/data/Dockerfiles/acme/load-dns-config.sh b/data/Dockerfiles/acme/load-dns-config.sh new file mode 100755 index 0000000000..af9caeaf60 --- /dev/null +++ b/data/Dockerfiles/acme/load-dns-config.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +SCRIPT_SOURCE="${BASH_SOURCE[0]:-${0}}" +if [[ "${SCRIPT_SOURCE}" == "${0}" ]]; then + __dns_loader_standalone=1 +else + __dns_loader_standalone=0 +fi + +CONFIG_PATH="${ACME_DNS_CONFIG_FILE:-/etc/acme/dns-01.conf}" + +if [[ ! -f "${CONFIG_PATH}" ]]; then + if [[ $__dns_loader_standalone -eq 1 ]]; then + exit 0 + else + return 0 + fi +fi + +source /srv/functions.sh + +log_f "Loading DNS-01 configuration from ${CONFIG_PATH}" + +LINE_NO=0 +while IFS= read -r line || [[ -n "${line}" ]]; do + LINE_NO=$((LINE_NO+1)) + line="${line%$'\r'}" + line_trimmed="$(printf '%s' "${line}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + [[ -z "${line_trimmed}" ]] && continue + [[ "${line_trimmed:0:1}" == "#" ]] && continue + if [[ "${line_trimmed}" != *=* ]]; then + log_f "Skipping invalid DNS config line ${LINE_NO} (missing key=value)" + continue + fi + KEY="${line_trimmed%%=*}" + VALUE="${line_trimmed#*=}" + KEY="$(printf '%s' "${KEY}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + VALUE="$(printf '%s' "${VALUE}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + if [[ -z "${KEY}" ]]; then + log_f "Skipping invalid DNS config line ${LINE_NO} (empty key)" + continue + fi + if [[ "${VALUE}" =~ ^\".*\"$ ]]; then + VALUE="${VALUE:1:-1}" + elif [[ "${VALUE}" =~ ^\'.*\'$ ]]; then + VALUE="${VALUE:1:-1}" + fi + export "${KEY}"="${VALUE}" + log_f "Exported DNS config key ${KEY}" + +done < "${CONFIG_PATH}" + +if [[ $__dns_loader_standalone -eq 1 ]]; then + exit 0 +else + return 0 +fi diff --git a/data/Dockerfiles/acme/obtain-certificate-dns.sh b/data/Dockerfiles/acme/obtain-certificate-dns.sh new file mode 100644 index 0000000000..f88089f016 --- /dev/null +++ b/data/Dockerfiles/acme/obtain-certificate-dns.sh @@ -0,0 +1,177 @@ +#!/bin/bash + +# Return values / exit codes +# 0 = cert created successfully +# 1 = cert renewed successfully +# 2 = cert not due for renewal +# * = errors + +source /srv/functions.sh + +CERT_DOMAINS=(${DOMAINS[@]}) +CERT_DOMAIN=${CERT_DOMAINS[0]} +ACME_BASE=/var/lib/acme + +# Load optional DNS provider secrets from /etc/acme/dns-01.conf +if [[ -f /srv/load-dns-config.sh ]]; then + source /srv/load-dns-config.sh + if declare -F log_f >/dev/null; then + log_f "ACME_DNS_CHALLENGE is enabled, DNS provider secrets loaded" + fi +fi + +TYPE=${1} +PREFIX="" +# only support rsa certificates for now +if [[ "${TYPE}" != "rsa" ]]; then + log_f "Unknown certificate type '${TYPE}' requested" + exit 5 +fi + +if [[ -z "${ACME_DNS_PROVIDER}" ]]; then + log_f "ACME_DNS_PROVIDER is required when ACME_DNS_CHALLENGE is enabled" + exit 6 +fi + +DOMAINS_FILE=${ACME_BASE}/${CERT_DOMAIN}/domains +CERT=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}cert.pem +SHARED_KEY=${ACME_BASE}/acme/${PREFIX}key.pem # must already exist +KEY=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}key.pem +CSR=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}acme.csr + +if [[ -z ${CERT_DOMAINS[*]} ]]; then + log_f "Missing CERT_DOMAINS to obtain a certificate" + exit 3 +fi + +if [[ "${LE_STAGING}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + if [[ ! -z "${DIRECTORY_URL}" ]]; then + log_f "Cannot use DIRECTORY_URL with LE_STAGING=y - ignoring DIRECTORY_URL" + fi + log_f "Using Let's Encrypt staging servers" + ACME_SH_SERVER_ARGS=("--staging") +elif [[ ! -z "${DIRECTORY_URL}" ]]; then + log_f "Using custom directory URL ${DIRECTORY_URL}" + ACME_SH_SERVER_ARGS=("--server" "${DIRECTORY_URL}") +else + log_f "Using Let's Encrypt production servers" + ACME_SH_SERVER_ARGS=("--server" "letsencrypt") +fi + +if [[ -f ${DOMAINS_FILE} && "$(cat ${DOMAINS_FILE})" == "${CERT_DOMAINS[*]}" ]]; then + if [[ ! -f ${CERT} || ! -f "${KEY}" || -f "${ACME_BASE}/force_renew" ]]; then + log_f "Certificate ${CERT} doesn't exist yet or forced renewal - start obtaining" + elif ! openssl x509 -checkend 2592000 -noout -in ${CERT} > /dev/null; then + log_f "Certificate ${CERT} is due for renewal (< 30 days) - start renewing" + else + log_f "Certificate ${CERT} validation done, neither changed nor due for renewal." + exit 2 + fi +else + log_f "Certificate ${CERT} missing or changed domains '${CERT_DOMAINS[*]}' - start obtaining" +fi + +# Make backup +if [[ -f ${CERT} ]]; then + DATE=$(date +%Y-%m-%d_%H_%M_%S) + BACKUP_DIR=${ACME_BASE}/backups/${CERT_DOMAIN}/${PREFIX}${DATE} + log_f "Creating backups in ${BACKUP_DIR} ..." + mkdir -p ${BACKUP_DIR}/ + [[ -f ${DOMAINS_FILE} ]] && cp ${DOMAINS_FILE} ${BACKUP_DIR}/ + [[ -f ${CERT} ]] && cp ${CERT} ${BACKUP_DIR}/ + [[ -f ${KEY} ]] && cp ${KEY} ${BACKUP_DIR}/ + [[ -f ${CSR} ]] && cp ${CSR} ${BACKUP_DIR}/ +fi + +mkdir -p ${ACME_BASE}/${CERT_DOMAIN} +if [[ ! -f ${KEY} ]]; then + log_f "Copying shared private key for this certificate..." + cp ${SHARED_KEY} ${KEY} + chmod 600 ${KEY} +fi + +# Generating CSR to keep layout parity with HTTP challenge flow +printf "[SAN]\nsubjectAltName=" > /tmp/_SAN +printf "DNS:%s," "${CERT_DOMAINS[@]}" >> /tmp/_SAN +sed -i '$s/,$//' /tmp/_SAN +openssl req -new -sha256 -key ${KEY} -subj "/" -reqexts SAN -config <(cat "$(openssl version -d | sed 's/.*\"\(.*\)\"/\1/g')/openssl.cnf" /tmp/_SAN) > ${CSR} + +log_f "Checking resolver..." +until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do + sleep 2 +done +log_f "Resolver OK" + +ACME_SH_BIN_PATH=${ACME_SH_BIN:-/opt/acme.sh/acme.sh} +ACME_SH_WORK_HOME=${ACME_SH_CONFIG_HOME:-/var/lib/acme/acme-sh} +mkdir -p ${ACME_SH_WORK_HOME} + +if [[ ! -x ${ACME_SH_BIN_PATH} ]]; then + log_f "acme.sh binary not found at ${ACME_SH_BIN_PATH}" + exit 7 +fi + +if [[ ! -f ${ACME_SH_WORK_HOME}/account.conf ]]; then + if [[ -z "${ACME_ACCOUNT_EMAIL}" ]]; then + log_f "ACME_ACCOUNT_EMAIL is required to register a new acme.sh account" + exit 8 + fi + log_f "Registering acme.sh account for ${ACME_ACCOUNT_EMAIL}" + REGISTER_CMD=("${ACME_SH_BIN_PATH}" "--home" "${ACME_SH_WORK_HOME}" "--config-home" "${ACME_SH_WORK_HOME}" "--cert-home" "${ACME_SH_WORK_HOME}" "--register-account" "-m" "${ACME_ACCOUNT_EMAIL}") + REGISTER_CMD+=("${ACME_SH_SERVER_ARGS[@]}") + REGISTER_RESPONSE=$("${REGISTER_CMD[@]}" 2>&1) + if [[ $? -ne 0 ]]; then + log_f "Failed to register acme.sh account: ${REGISTER_RESPONSE}" + exit 9 + fi +fi + +TMP_CERT=$(mktemp /tmp/acme-cert.XXXXXX) +TMP_FULLCHAIN=$(mktemp /tmp/acme-fullchain.XXXXXX) + +ACME_CMD=("${ACME_SH_BIN_PATH}" "--home" "${ACME_SH_WORK_HOME}" "--config-home" "${ACME_SH_WORK_HOME}" "--cert-home" "${ACME_SH_WORK_HOME}") +ACME_CMD+=("${ACME_SH_SERVER_ARGS[@]}") +ACME_CMD+=("--issue" "--dns" "${ACME_DNS_PROVIDER}" "--key-file" "${KEY}" "--cert-file" "${TMP_CERT}" "--fullchain-file" "${TMP_FULLCHAIN}" "--force") +for domain in "${CERT_DOMAINS[@]}"; do + ACME_CMD+=("-d" "${domain}") +done + +log_f "Using command ${ACME_CMD[*]}" +if [[ -n "${ACME_DNS_PROVIDER}" ]]; then + log_f "DNS provider: ${ACME_DNS_PROVIDER}" +fi +if compgen -A variable | grep -Eq "^DNS_|^ACME_"; then + LOG_KEYS=$(env | grep -E "^(DNS_|ACME_)" | cut -d= -f1 | tr '\n' ' ') + log_f "Available DNS/ACME env keys: ${LOG_KEYS}" redis_only +fi +ACME_RESPONSE=$("${ACME_CMD[@]}" 2>&1 | tee /dev/fd/5; exit ${PIPESTATUS[0]}) +SUCCESS="$?" +ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64) +log_f "${ACME_RESPONSE_B64}" redis_only b64 + +case "$SUCCESS" in + 0) + log_f "Deploying certificate ${CERT}..." + if verify_hash_match ${TMP_FULLCHAIN} ${KEY}; then + RETURN=0 + if [[ -f ${CERT} ]]; then + RETURN=1 + fi + mv -f ${TMP_FULLCHAIN} ${CERT} + rm -f ${TMP_CERT} + echo -n ${CERT_DOMAINS[*]} > ${DOMAINS_FILE} + log_f "Certificate successfully obtained via DNS challenge" + exit ${RETURN} + else + log_f "Certificate was requested, but key and certificate hashes do not match" + rm -f ${TMP_CERT} ${TMP_FULLCHAIN} + exit 4 + fi + ;; + *) + log_f "Failed to obtain certificate ${CERT} for domains '${CERT_DOMAINS[*]}' via DNS challenge" + redis-cli -h redis -a ${REDISPASS} --no-auth-warning SET ACME_FAIL_TIME "$(date +%s)" + rm -f ${TMP_CERT} ${TMP_FULLCHAIN} + exit 100${SUCCESS} + ;; +esac diff --git a/data/Dockerfiles/acme/obtain-certificate.sh b/data/Dockerfiles/acme/obtain-certificate.sh new file mode 100644 index 0000000000..9d727a2027 --- /dev/null +++ b/data/Dockerfiles/acme/obtain-certificate.sh @@ -0,0 +1,134 @@ +#!/bin/bash + +# Return values / exit codes +# 0 = cert created successfully +# 1 = cert renewed successfully +# 2 = cert not due for renewal +# * = errors + + +source /srv/functions.sh + +CERT_DOMAINS=(${DOMAINS[@]}) +CERT_DOMAIN=${CERT_DOMAINS[0]} +ACME_BASE=/var/lib/acme + +TYPE=${1} +PREFIX="" +# only support rsa certificates for now +if [[ "${TYPE}" != "rsa" ]]; then + log_f "Unknown certificate type '${TYPE}' requested" + exit 5 +fi + +if [[ "${ACME_DNS_CHALLENGE}" == "y" ]]; then + exec /srv/obtain-certificate-dns.sh "$@" +fi +DOMAINS_FILE=${ACME_BASE}/${CERT_DOMAIN}/domains +CERT=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}cert.pem +SHARED_KEY=${ACME_BASE}/acme/${PREFIX}key.pem # must already exist +KEY=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}key.pem +CSR=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}acme.csr + +if [[ -z ${CERT_DOMAINS[*]} ]]; then + log_f "Missing CERT_DOMAINS to obtain a certificate" + exit 3 +fi + +if [[ "${LE_STAGING}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + if [[ ! -z "${DIRECTORY_URL}" ]]; then + log_f "Cannot use DIRECTORY_URL with LE_STAGING=y - ignoring DIRECTORY_URL" + fi + log_f "Using Let's Encrypt staging servers" + DIRECTORY_URL='--directory-url https://acme-staging-v02.api.letsencrypt.org/directory' +elif [[ ! -z "${DIRECTORY_URL}" ]]; then + log_f "Using custom directory URL ${DIRECTORY_URL}" + DIRECTORY_URL="--directory-url ${DIRECTORY_URL}" +fi + +if [[ -f ${DOMAINS_FILE} && "$(cat ${DOMAINS_FILE})" == "${CERT_DOMAINS[*]}" ]]; then + if [[ ! -f ${CERT} || ! -f "${KEY}" || -f "${ACME_BASE}/force_renew" ]]; then + log_f "Certificate ${CERT} doesn't exist yet or forced renewal - start obtaining" + # Certificate exists and did not change but could be due for renewal (30 days) + elif ! openssl x509 -checkend 2592000 -noout -in ${CERT} > /dev/null; then + log_f "Certificate ${CERT} is due for renewal (< 30 days) - start renewing" + else + log_f "Certificate ${CERT} validation done, neither changed nor due for renewal." + exit 2 + fi +else + log_f "Certificate ${CERT} missing or changed domains '${CERT_DOMAINS[*]}' - start obtaining" +fi + + +# Make backup +if [[ -f ${CERT} ]]; then + DATE=$(date +%Y-%m-%d_%H_%M_%S) + BACKUP_DIR=${ACME_BASE}/backups/${CERT_DOMAIN}/${PREFIX}${DATE} + log_f "Creating backups in ${BACKUP_DIR} ..." + mkdir -p ${BACKUP_DIR}/ + [[ -f ${DOMAINS_FILE} ]] && cp ${DOMAINS_FILE} ${BACKUP_DIR}/ + [[ -f ${CERT} ]] && cp ${CERT} ${BACKUP_DIR}/ + [[ -f ${KEY} ]] && cp ${KEY} ${BACKUP_DIR}/ + [[ -f ${CSR} ]] && cp ${CSR} ${BACKUP_DIR}/ +fi + +mkdir -p ${ACME_BASE}/${CERT_DOMAIN} +if [[ ! -f ${KEY} ]]; then + log_f "Copying shared private key for this certificate..." + cp ${SHARED_KEY} ${KEY} + chmod 600 ${KEY} +fi + +# Generating CSR +printf "[SAN]\nsubjectAltName=" > /tmp/_SAN +printf "DNS:%s," "${CERT_DOMAINS[@]}" >> /tmp/_SAN +sed -i '$s/,$//' /tmp/_SAN +openssl req -new -sha256 -key ${KEY} -subj "/" -reqexts SAN -config <(cat "$(openssl version -d | sed 's/.*"\(.*\)"/\1/g')/openssl.cnf" /tmp/_SAN) > ${CSR} + +# acme-tiny writes info to stderr and ceritifcate to stdout +# The redirects will do the following: +# - redirect stdout to temp certificate file +# - redirect acme-tiny stderr to stdout (logs to variable ACME_RESPONSE) +# - tee stderr to get live output and log to dockerd + +log_f "Checking resolver..." +until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do + sleep 2 +done +log_f "Resolver OK" +log_f "Using command acme-tiny ${DIRECTORY_URL} --account-key ${ACME_BASE}/acme/account.pem --disable-check --csr ${CSR} --acme-dir /var/www/acme/" +ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} \ + --account-key ${ACME_BASE}/acme/account.pem \ + --disable-check \ + --csr ${CSR} \ + --acme-dir /var/www/acme/ 2>&1 > /tmp/_cert.pem | tee /dev/fd/5; exit ${PIPESTATUS[0]}) +SUCCESS="$?" +ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64) +log_f "${ACME_RESPONSE_B64}" redis_only b64 +case "$SUCCESS" in + 0) # cert requested + log_f "Deploying certificate ${CERT}..." + # Deploy the new certificate and key + # Moving temp cert to {domain} folder + if verify_hash_match /tmp/_cert.pem ${KEY}; then + RETURN=0 # certificate created + if [[ -f ${CERT} ]]; then + RETURN=1 # certificate renewed + fi + mv -f /tmp/_cert.pem ${CERT} + echo -n ${CERT_DOMAINS[*]} > ${DOMAINS_FILE} + rm /var/www/acme/* 2> /dev/null + log_f "Certificate successfully obtained" + exit ${RETURN} + else + log_f "Certificate was successfully requested, but key and certificate have non-matching hashes, ignoring certificate" + exit 4 + fi + ;; + *) # non-zero is non-fun + log_f "Failed to obtain certificate ${CERT} for domains '${CERT_DOMAINS[*]}'" + redis-cli -h redis -a ${REDISPASS} --no-auth-warning SET ACME_FAIL_TIME "$(date +%s)" + exit 100${SUCCESS} + ;; +esac diff --git a/data/Dockerfiles/acme/reload-configurations.sh b/data/Dockerfiles/acme/reload-configurations.sh new file mode 100644 index 0000000000..8d194b68ba --- /dev/null +++ b/data/Dockerfiles/acme/reload-configurations.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Reading container IDs +# Wrapping as array to ensure trimmed content when calling $NGINX etc. +NGINX=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"nginx-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " ")) +DOVECOT=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"dovecot-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " ")) +POSTFIX=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"postfix-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " ")) + +reload_nginx(){ + echo "Reloading Nginx..." + NGINX_RELOAD_RET=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${NGINX}/exec -d '{"cmd":"reload", "task":"nginx"}' --silent -H 'Content-type: application/json' | jq -r .type) + [[ ${NGINX_RELOAD_RET} != 'success' ]] && { echo "Could not reload Nginx, restarting container..."; restart_container ${NGINX} ; } +} + +reload_dovecot(){ + echo "Reloading Dovecot..." + DOVECOT_RELOAD_RET=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${DOVECOT}/exec -d '{"cmd":"reload", "task":"dovecot"}' --silent -H 'Content-type: application/json' | jq -r .type) + [[ ${DOVECOT_RELOAD_RET} != 'success' ]] && { echo "Could not reload Dovecot, restarting container..."; restart_container ${DOVECOT} ; } +} + +reload_postfix(){ + echo "Reloading Postfix..." + POSTFIX_RELOAD_RET=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${POSTFIX}/exec -d '{"cmd":"reload", "task":"postfix"}' --silent -H 'Content-type: application/json' | jq -r .type) + [[ ${POSTFIX_RELOAD_RET} != 'success' ]] && { echo "Could not reload Postfix, restarting container..."; restart_container ${POSTFIX} ; } +} + +restart_container(){ + for container in $*; do + echo "Restarting ${container}..." + C_REST_OUT=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${container}/restart --silent | jq -r '.msg') + echo "${C_REST_OUT}" + done +} + +if [[ "${CERT_AMOUNT_CHANGED}" == "1" ]]; then + restart_container ${NGINX} + restart_container ${DOVECOT} + restart_container ${POSTFIX} +else + reload_nginx + #reload_dovecot + restart_container ${DOVECOT} + #reload_postfix + restart_container ${POSTFIX} +fi diff --git a/data/Dockerfiles/backup/Dockerfile b/data/Dockerfiles/backup/Dockerfile new file mode 100644 index 0000000000..9ff6bbb13f --- /dev/null +++ b/data/Dockerfiles/backup/Dockerfile @@ -0,0 +1,3 @@ +FROM debian:trixie-slim + +RUN apt update && apt install pigz zstd -y --no-install-recommends \ No newline at end of file diff --git a/data/Dockerfiles/clamd/Dockerfile b/data/Dockerfiles/clamd/Dockerfile index fc6f0a1f67..5f444a8f44 100644 --- a/data/Dockerfiles/clamd/Dockerfile +++ b/data/Dockerfiles/clamd/Dockerfile @@ -1,47 +1,110 @@ -FROM alpine:3.8 - -LABEL maintainer "Andrรฉ Peters " - -# Add scripts -COPY dl_files.sh bootstrap.sh ./ - -# Installation -ENV CLAMAV 0.100.1 - -RUN apk add --no-cache --virtual build-dependencies alpine-sdk ncurses-dev zlib-dev bzip2-dev pcre-dev linux-headers fts-dev libxml2-dev libressl-dev \ - && apk add --no-cache curl bash tini libxml2 libbz2 pcre fts libressl tzdata \ - && wget -O - https://www.clamav.net/downloads/production/clamav-${CLAMAV}.tar.gz | tar xfvz - \ - && cd clamav-${CLAMAV} \ - && LIBS=-lfts ./configure \ - --prefix=/usr \ - --libdir=/usr/lib \ - --sysconfdir=/etc/clamav \ - --mandir=/usr/share/man \ - --infodir=/usr/share/info \ - --without-iconv \ - --disable-llvm \ - --with-user=clamav \ - --with-group=clamav \ - --with-dbdir=/var/lib/clamav \ - --enable-clamdtop \ - --enable-bigstack \ - --with-pcre \ - && make -j4 \ - && make install \ - && make clean \ - && cd .. && rm -rf clamav-${CLAMAV} \ - && apk del build-dependencies \ - && addgroup -S clamav \ - && adduser -S -D -h /var/lib/clamav -s /sbin/nologin -G clamav -g clamav clamav \ - && adduser clamav tty \ - && mkdir -p /run/clamav \ - && chown clamav:clamav /run/clamav \ - && chmod +x /dl_files.sh \ - && set -ex; /bin/bash /dl_files.sh \ - && chmod 750 /run/clamav - -# Port provision -EXPOSE 3310 - -# AV daemon bootstrapping -CMD ["/sbin/tini", "-g", "--", "/bootstrap.sh"] +FROM alpine:3.24 AS builder + +WORKDIR /src +ENV CLAMD_VERSION=1.4.6 + +RUN apk upgrade --no-cache \ + && apk add --update --no-cache \ + g++ \ + gcc \ + gdb \ + make \ + cmake \ + py3-pytest \ + python3 \ + valgrind \ + bzip2-dev \ + check-dev \ + curl-dev \ + json-c-dev \ + libmilter-dev \ + libxml2-dev \ + linux-headers \ + ncurses-dev \ + openssl-dev \ + pcre2-dev \ + zlib-dev \ + cargo \ + rust + +RUN wget -P /src https://www.clamav.net/downloads/production/clamav-${CLAMD_VERSION}.tar.gz \ + && tar xzfv /src/clamav-${CLAMD_VERSION}.tar.gz \ + && cd /src/clamav-${CLAMD_VERSION} \ + && cmake . \ + -D CMAKE_BUILD_TYPE="Release" \ + -D CMAKE_INSTALL_PREFIX="/usr" \ + -D CMAKE_INSTALL_LIBDIR="/usr/lib" \ + -D APP_CONFIG_DIRECTORY="/etc/clamav" \ + -D DATABASE_DIRECTORY="/var/lib/clamav" \ + -D ENABLE_CLAMONACC=OFF \ + -D ENABLE_EXAMPLES=OFF \ + -D ENABLE_MILTER=ON \ + -D ENABLE_MAN_PAGES=OFF \ + -D ENABLE_STATIC_LIB=OFF \ + -D ENABLE_JSON_SHARED=ON \ + && cmake --build . \ + && make DESTDIR="/clamav" -j$(($(nproc) - 1)) install \ + && rm -r "/clamav/usr/lib/pkgconfig/" \ + && sed -e "s|^\(Example\)|\# \1|" \ + -e "s|.*\(LocalSocket\) .*|\1 /tmp/clamd.sock|" \ + -e "s|.*\(TCPSocket\) .*|\1 3310|" \ + -e "s|.*\(TCPAddr\) .*|#\1 0.0.0.0|" \ + -e "s|.*\(User\) .*|\1 clamav|" \ + -e "s|^\#\(LogFile\) .*|\1 /var/log/clamav/clamd.log|" \ + -e "s|^\#\(LogTime\).*|\1 yes|" \ + "/clamav/etc/clamav/clamd.conf.sample" > "/clamav/etc/clamav/clamd.conf" \ + && sed -e "s|^\(Example\)|\# \1|" \ + -e "s|.*\(DatabaseOwner\) .*|\1 clamav|" \ + -e "s|^\#\(UpdateLogFile\) .*|\1 /var/log/clamav/freshclam.log|" \ + -e "s|^\#\(NotifyClamd\).*|\1 /etc/clamav/clamd.conf|" \ + -e "s|^\#\(ScriptedUpdates\).*|\1 yes|" \ + "/clamav/etc/clamav/freshclam.conf.sample" > "/clamav/etc/clamav/freshclam.conf" \ + && sed -e "s|^\(Example\)|\# \1|" \ + -e "s|.*\(MilterSocket\) .*|\1 inet:7357|" \ + -e "s|.*\(User\) .*|\1 clamav|" \ + -e "s|^\#\(LogFile\) .*|\1 /var/log/clamav/milter.log|" \ + -e "s|^\#\(LogTime\).*|\1 yes|" \ + -e "s|.*\(\ClamdSocket\) .*|\1 unix:/tmp/clamd.sock|" \ + "/clamav/etc/clamav/clamav-milter.conf.sample" > "/clamav/etc/clamav/clamav-milter.conf" || exit 1 + + +FROM alpine:3.24 + +LABEL maintainer = "The Infrastructure Company GmbH " + +RUN apk upgrade --no-cache \ + && apk add --update --no-cache \ + tzdata \ + rsync \ + bind-tools \ + bash \ + tini \ + json-c \ + libbz2 \ + libcurl \ + libmilter \ + libxml2 \ + ncurses-libs \ + pcre2 \ + zlib \ + libgcc \ + && addgroup -S "clamav" && \ + adduser -D -G "clamav" -h "/var/lib/clamav" -s "/bin/false" -S "clamav" && \ + install -d -m 755 -g "clamav" -o "clamav" "/var/log/clamav" && \ + chown -R clamav:clamav /var/lib/clamav + +COPY --from=builder "/clamav" "/" + +# init +COPY clamd.sh /clamd.sh +RUN chmod +x /sbin/tini + +# healthcheck +COPY healthcheck.sh /healthcheck.sh +COPY clamdcheck.sh /usr/local/bin +RUN chmod +x /healthcheck.sh +RUN chmod +x /usr/local/bin/clamdcheck.sh +HEALTHCHECK --start-period=6m CMD "/healthcheck.sh" + +ENTRYPOINT [] +CMD ["/sbin/tini", "-g", "--", "/clamd.sh"] \ No newline at end of file diff --git a/data/Dockerfiles/clamd/bootstrap.sh b/data/Dockerfiles/clamd/bootstrap.sh deleted file mode 100755 index ba1cee85a1..0000000000 --- a/data/Dockerfiles/clamd/bootstrap.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -if [[ "${SKIP_CLAMD}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then - echo "SKIP_CLAMD=y, skipping ClamAV..." - sleep 365d - exit 0 -fi - -# Create log pipes -mkdir -p /var/log/clamav -touch /var/log/clamav/clamd.log /var/log/clamav/freshclam.log -chown -R clamav:clamav /var/log/clamav/ -chown root:tty /dev/console -chmod g+rw /dev/console - -# Prepare -[[ ! -f /var/lib/clamav/whitelist.ign2 ]] && touch /var/lib/clamav/whitelist.ign2 -dos2unix /var/lib/clamav/whitelist.ign2 -sed -i '/^\s*$/d' /var/lib/clamav/whitelist.ign2 - -BACKGROUND_TASKS=() - -( -while true; do - sleep 1m - freshclam - sleep 1h -done -) & -BACKGROUND_TASKS+=($!) - -clamd & -BACKGROUND_TASKS+=($!) - -while true; do - for bg_task in ${BACKGROUND_TASKS[*]}; do - if ! kill -0 ${bg_task} 1>&2; then - echo "Worker ${bg_task} died, stopping container waiting for respawn..." - kill -TERM 1 - fi - sleep 10 - done -done diff --git a/data/Dockerfiles/clamd/clamd.sh b/data/Dockerfiles/clamd/clamd.sh new file mode 100755 index 0000000000..c656be065d --- /dev/null +++ b/data/Dockerfiles/clamd/clamd.sh @@ -0,0 +1,106 @@ +#!/bin/bash + +if [[ "${SKIP_CLAMD}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + echo "SKIP_CLAMD=y, skipping ClamAV..." + sleep 365d + exit 0 +fi + +# Cleaning up garbage +echo "Cleaning up tmp files..." +rm -rf /var/lib/clamav/tmp.* + +# Prepare whitelist + +mkdir -p /run/clamav /var/lib/clamav + +if [[ -s /etc/clamav/whitelist.ign2 ]]; then + echo "Copying non-empty whitelist.ign2 to /var/lib/clamav/whitelist.ign2" + cp /etc/clamav/whitelist.ign2 /var/lib/clamav/whitelist.ign2 +fi + +if [[ ! -f /var/lib/clamav/whitelist.ign2 ]]; then + echo "Creating /var/lib/clamav/whitelist.ign2" + cat < /var/lib/clamav/whitelist.ign2 +# Please restart ClamAV after changing signatures +Example-Signature.Ignore-1 +PUA.Win.Trojan.EmbeddedPDF-1 +PUA.Pdf.Trojan.EmbeddedJavaScript-1 +PUA.Pdf.Trojan.OpenActionObjectwithJavascript-1 +EOF +fi + +chown clamav:clamav -R /var/lib/clamav /run/clamav + +chmod 755 /var/lib/clamav +chmod 644 -R /var/lib/clamav/* +chmod 750 /run/clamav + +stat /var/lib/clamav/whitelist.ign2 +dos2unix /var/lib/clamav/whitelist.ign2 +sed -i '/^\s*$/d' /var/lib/clamav/whitelist.ign2 +# Copying to /etc/clamav to expose file as-is to administrator +cp -p /var/lib/clamav/whitelist.ign2 /etc/clamav/whitelist.ign2 + + +BACKGROUND_TASKS=() + +echo "Running freshclam..." +freshclam + +( +while true; do + sleep 12600 + freshclam +done +) & +BACKGROUND_TASKS+=($!) + +( +while true; do + sleep 10m + SANE_MIRRORS="$(dig +ignore +short rsync.sanesecurity.net)" + for sane_mirror in ${SANE_MIRRORS}; do + CE= + rsync -avp --chown=clamav:clamav --chmod=Du=rwx,Dgo=rx,Fu=rw,Fog=r --timeout=5 rsync://${sane_mirror}/sanesecurity/ \ + --include 'blurl.ndb' \ + --include 'junk.ndb' \ + --include 'jurlbl.ndb' \ + --include 'jurbla.ndb' \ + --include 'phishtank.ndb' \ + --include 'phish.ndb' \ + --include 'spamimg.hdb' \ + --include 'scam.ndb' \ + --include 'rogue.hdb' \ + --include 'sanesecurity.ftm' \ + --include 'sigwhitelist.ign2' \ + --exclude='*' /var/lib/clamav/ + CE=$? + chmod 755 /var/lib/clamav/ + if [ ${CE} -eq 0 ]; then + while [ ! -z "$(pidof freshclam)" ]; do + echo "Freshclam is active, waiting..." + sleep 5 + done + echo RELOAD | nc clamd-mailcow 3310 + break + fi + done + sleep 12h +done +) & +BACKGROUND_TASKS+=($!) + +echo "$(clamd -V) is starting... please wait a moment." +nice -n10 clamd & +BACKGROUND_TASKS+=($!) + +while true; do + for bg_task in ${BACKGROUND_TASKS[*]}; do + if ! kill -0 ${bg_task} 1>&2; then + echo "Worker ${bg_task} died, stopping container waiting for respawn..." + kill -TERM 1 + fi + sleep 10 + done +done diff --git a/data/Dockerfiles/clamd/clamdcheck.sh b/data/Dockerfiles/clamd/clamdcheck.sh new file mode 100644 index 0000000000..e7e53a65f5 --- /dev/null +++ b/data/Dockerfiles/clamd/clamdcheck.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -eu + +if [ "${CLAMAV_NO_CLAMD:-}" != "false" ]; then + if [ "$(echo "PING" | nc localhost 3310)" != "PONG" ]; then + echo "ERROR: Unable to contact server" + exit 1 + fi + + echo "Clamd is up" +fi + +exit 0 diff --git a/data/Dockerfiles/clamd/dl_files.sh b/data/Dockerfiles/clamd/dl_files.sh deleted file mode 100755 index 09d612410f..0000000000 --- a/data/Dockerfiles/clamd/dl_files.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -declare -a DB_MIRRORS=( - "switch.clamav.net" - "clamavdb.heanet.ie" - "clamav.iol.cz" - "clamav.univ-nantes.fr" - "clamav.easynet.fr" - "clamav.begi.net" -) -declare -a DB_MIRRORS=( $(shuf -e "${DB_MIRRORS[@]}") ) - -DB_FILES=( - "bytecode.cvd" - "daily.cvd" - "main.cvd" -) - -for i in "${DB_MIRRORS[@]}"; do - for j in "${DB_FILES[@]}"; do - [[ -f "/var/lib/clamav/${j}" && -s "/var/lib/clamav/${j}" ]] && continue; - if [[ $(curl -o /dev/null --connect-timeout 1 \ - --max-time 1 \ - --silent \ - --head \ - --write-out "%{http_code}\n" "${i}/${j}") == 200 ]]; then - curl "${i}/${j}" -o "/var/lib/clamav/${j}" -# - fi - done -done - -chown clamav:clamav /var/lib/clamav/*.cvd diff --git a/data/Dockerfiles/clamd/healthcheck.sh b/data/Dockerfiles/clamd/healthcheck.sh new file mode 100755 index 0000000000..6c18ac06cf --- /dev/null +++ b/data/Dockerfiles/clamd/healthcheck.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +if [[ "${SKIP_CLAMD}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + echo "SKIP_CLAMD=y, skipping ClamAV..." + exit 0 +fi + +# run clamd healthcheck +/usr/local/bin/clamdcheck.sh diff --git a/data/Dockerfiles/dockerapi/Dockerfile b/data/Dockerfiles/dockerapi/Dockerfile index 9d0bdaec7e..c27f6154b0 100644 --- a/data/Dockerfiles/dockerapi/Dockerfile +++ b/data/Dockerfiles/dockerapi/Dockerfile @@ -1,10 +1,27 @@ -FROM alpine:3.8 -LABEL maintainer "Andre Peters " +FROM alpine:3.23 -RUN apk add -U --no-cache python2 python-dev py-pip gcc musl-dev tzdata openssl-dev libffi-dev \ - && pip2 install --upgrade docker==3.0.1 flask flask-restful pyOpenSSL \ - && apk del python-dev py2-pip gcc +LABEL maintainer = "The Infrastructure Company GmbH " -COPY server.py / +ARG PIP_BREAK_SYSTEM_PACKAGES=1 +WORKDIR /app -CMD ["python2", "-u", "/server.py"] +RUN apk add --update --no-cache python3 \ + py3-pip \ + openssl \ + tzdata \ + py3-psutil \ + py3-redis \ + py3-async-timeout \ +&& pip3 install --upgrade pip \ + fastapi \ + uvicorn \ + aiodocker \ + docker +RUN mkdir /app/modules + +COPY docker-entrypoint.sh /app/ +COPY main.py /app/main.py +COPY modules/ /app/modules/ + +ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"] +CMD ["python", "main.py"] \ No newline at end of file diff --git a/data/Dockerfiles/dockerapi/docker-entrypoint.sh b/data/Dockerfiles/dockerapi/docker-entrypoint.sh new file mode 100755 index 0000000000..64f4b8295e --- /dev/null +++ b/data/Dockerfiles/dockerapi/docker-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +`openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \ + -keyout /app/dockerapi_key.pem \ + -out /app/dockerapi_cert.pem \ + -subj /CN=dockerapi/O=mailcow \ + -addext subjectAltName=DNS:dockerapi` + +exec "$@" diff --git a/data/Dockerfiles/dockerapi/main.py b/data/Dockerfiles/dockerapi/main.py new file mode 100644 index 0000000000..bf197bd61f --- /dev/null +++ b/data/Dockerfiles/dockerapi/main.py @@ -0,0 +1,261 @@ +import os +import sys +import uvicorn +import json +import uuid +import async_timeout +import asyncio +import aiodocker +import docker +import logging +from logging.config import dictConfig +from fastapi import FastAPI, Response, Request +from modules.DockerApi import DockerApi +from redis import asyncio as aioredis +from contextlib import asynccontextmanager + +dockerapi = None + +@asynccontextmanager +async def lifespan(app: FastAPI): + global dockerapi + + # Initialize a custom logger + logger = logging.getLogger("dockerapi") + logger.setLevel(logging.INFO) + # Configure the logger to output logs to the terminal + handler = logging.StreamHandler() + handler.setLevel(logging.INFO) + formatter = logging.Formatter("%(levelname)s: %(message)s") + handler.setFormatter(formatter) + logger.addHandler(handler) + + logger.info("Init APP") + + # Init redis client + if os.environ['REDIS_SLAVEOF_IP'] != "": + redis_client = redis = await aioredis.from_url(f"redis://{os.environ['REDIS_SLAVEOF_IP']}:{os.environ['REDIS_SLAVEOF_PORT']}/0", password=os.environ['REDISPASS']) + else: + redis_client = redis = await aioredis.from_url("redis://redis-mailcow:6379/0", password=os.environ['REDISPASS']) + + # Init docker clients + sync_docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto') + async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock') + + dockerapi = DockerApi(redis_client, sync_docker_client, async_docker_client, logger) + + logger.info("Subscribe to redis channel") + # Subscribe to redis channel + dockerapi.pubsub = redis.pubsub() + await dockerapi.pubsub.subscribe("MC_CHANNEL") + asyncio.create_task(handle_pubsub_messages(dockerapi.pubsub)) + + + yield + + # Close docker connections + dockerapi.sync_docker_client.close() + await dockerapi.async_docker_client.close() + + # Close redis + await dockerapi.pubsub.unsubscribe("MC_CHANNEL") + await dockerapi.redis_client.close() + +app = FastAPI(lifespan=lifespan) + +# Define Routes +@app.get("/host/stats") +async def get_host_update_stats(): + global dockerapi + + if dockerapi.host_stats_isUpdating == False: + asyncio.create_task(dockerapi.get_host_stats()) + dockerapi.host_stats_isUpdating = True + + while True: + if await dockerapi.redis_client.exists('host_stats'): + break + await asyncio.sleep(1.5) + + stats = json.loads(await dockerapi.redis_client.get('host_stats')) + return Response(content=json.dumps(stats, indent=4), media_type="application/json") + +@app.get("/containers/{container_id}/json") +async def get_container(container_id : str): + global dockerapi + + if container_id and container_id.isalnum(): + try: + for container in (await dockerapi.async_docker_client.containers.list()): + if container._id == container_id: + container_info = await container.show() + return Response(content=json.dumps(container_info, indent=4), media_type="application/json") + + res = { + "type": "danger", + "msg": "no container found" + } + return Response(content=json.dumps(res, indent=4), media_type="application/json") + except Exception as e: + res = { + "type": "danger", + "msg": str(e) + } + return Response(content=json.dumps(res, indent=4), media_type="application/json") + else: + res = { + "type": "danger", + "msg": "no or invalid id defined" + } + return Response(content=json.dumps(res, indent=4), media_type="application/json") + +@app.get("/containers/json") +async def get_containers(all: bool = False): + global dockerapi + + containers = {} + try: + for container in (await dockerapi.async_docker_client.containers.list(all=all)): + container_info = await container.show() + containers.update({container_info['Id']: container_info}) + return Response(content=json.dumps(containers, indent=4), media_type="application/json") + except Exception as e: + res = { + "type": "danger", + "msg": str(e) + } + return Response(content=json.dumps(res, indent=4), media_type="application/json") + +@app.post("/containers/{container_id}/{post_action}") +async def post_containers(container_id : str, post_action : str, request: Request): + global dockerapi + + try: + request_json = await request.json() + except Exception as err: + request_json = {} + + if container_id and container_id.isalnum() and post_action: + try: + """Dispatch container_post api call""" + if post_action == 'exec': + if not request_json or not 'cmd' in request_json: + res = { + "type": "danger", + "msg": "cmd is missing" + } + return Response(content=json.dumps(res, indent=4), media_type="application/json") + if not request_json or not 'task' in request_json: + res = { + "type": "danger", + "msg": "task is missing" + } + return Response(content=json.dumps(res, indent=4), media_type="application/json") + + api_call_method_name = '__'.join(['container_post', str(post_action), str(request_json['cmd']), str(request_json['task']) ]) + else: + api_call_method_name = '__'.join(['container_post', str(post_action) ]) + + api_call_method = getattr(dockerapi, api_call_method_name, lambda container_id: Response(content=json.dumps({'type': 'danger', 'msg':'container_post - unknown api call' }, indent=4), media_type="application/json")) + + dockerapi.logger.info("api call: %s, container_id: %s" % (api_call_method_name, container_id)) + return api_call_method(request_json, container_id=container_id) + except Exception as e: + dockerapi.logger.error("error - container_post: %s" % str(e)) + res = { + "type": "danger", + "msg": str(e) + } + return Response(content=json.dumps(res, indent=4), media_type="application/json") + + else: + res = { + "type": "danger", + "msg": "invalid container id or missing action" + } + return Response(content=json.dumps(res, indent=4), media_type="application/json") + +@app.post("/container/{container_id}/stats/update") +async def post_container_update_stats(container_id : str): + global dockerapi + + # start update task for container if no task is running + if container_id not in dockerapi.containerIds_to_update: + asyncio.create_task(dockerapi.get_container_stats(container_id)) + dockerapi.containerIds_to_update.append(container_id) + + while True: + if await dockerapi.redis_client.exists(container_id + '_stats'): + break + await asyncio.sleep(1.5) + + stats = json.loads(await dockerapi.redis_client.get(container_id + '_stats')) + return Response(content=json.dumps(stats, indent=4), media_type="application/json") + + +# PubSub Handler +async def handle_pubsub_messages(channel: aioredis.client.PubSub): + global dockerapi + + while True: + try: + async with async_timeout.timeout(60): + message = await channel.get_message(ignore_subscribe_messages=True, timeout=30) + if message is not None: + # Parse message + data_json = json.loads(message['data'].decode('utf-8')) + dockerapi.logger.info(f"PubSub Received - {json.dumps(data_json)}") + + # Handle api_call + if 'api_call' in data_json: + # api_call: container_post + if data_json['api_call'] == "container_post": + if 'post_action' in data_json and 'container_name' in data_json: + try: + """Dispatch container_post api call""" + request_json = {} + if data_json['post_action'] == 'exec': + if 'request' in data_json: + request_json = data_json['request'] + if 'cmd' in request_json: + if 'task' in request_json: + api_call_method_name = '__'.join(['container_post', str(data_json['post_action']), str(request_json['cmd']), str(request_json['task']) ]) + else: + dockerapi.logger.error("api call: task missing") + else: + dockerapi.logger.error("api call: cmd missing") + else: + dockerapi.logger.error("api call: request missing") + else: + api_call_method_name = '__'.join(['container_post', str(data_json['post_action'])]) + + if api_call_method_name: + api_call_method = getattr(dockerapi, api_call_method_name) + if api_call_method: + dockerapi.logger.info("api call: %s, container_name: %s" % (api_call_method_name, data_json['container_name'])) + api_call_method(request_json, container_name=data_json['container_name']) + else: + dockerapi.logger.error("api call not found: %s, container_name: %s" % (api_call_method_name, data_json['container_name'])) + except Exception as e: + dockerapi.logger.error("container_post: %s" % str(e)) + else: + dockerapi.logger.error("api call: missing container_name, post_action or request") + else: + dockerapi.logger.error("Unknown PubSub received - %s" % json.dumps(data_json)) + else: + dockerapi.logger.error("Unknown PubSub received - %s" % json.dumps(data_json)) + + await asyncio.sleep(0.0) + except asyncio.TimeoutError: + pass + +if __name__ == '__main__': + uvicorn.run( + app, + host="0.0.0.0", + port=443, + ssl_certfile="/app/dockerapi_cert.pem", + ssl_keyfile="/app/dockerapi_key.pem", + log_level="info", + loop="none" + ) diff --git a/data/Dockerfiles/dockerapi/modules/DockerApi.py b/data/Dockerfiles/dockerapi/modules/DockerApi.py new file mode 100644 index 0000000000..4701cbf515 --- /dev/null +++ b/data/Dockerfiles/dockerapi/modules/DockerApi.py @@ -0,0 +1,626 @@ +import psutil +import sys +import os +import re +import time +import json +import asyncio +import platform +from datetime import datetime +from fastapi import FastAPI, Response, Request + +class DockerApi: + def __init__(self, redis_client, sync_docker_client, async_docker_client, logger): + self.redis_client = redis_client + self.sync_docker_client = sync_docker_client + self.async_docker_client = async_docker_client + self.logger = logger + + self.host_stats_isUpdating = False + self.containerIds_to_update = [] + + # api call: container_post - post_action: stop + def container_post__stop(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(all=True, filters=filters): + container.stop() + + res = { 'type': 'success', 'msg': 'command completed successfully'} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: start + def container_post__start(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(all=True, filters=filters): + container.start() + + res = { 'type': 'success', 'msg': 'command completed successfully'} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: restart + def container_post__restart(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(all=True, filters=filters): + container.restart() + + res = { 'type': 'success', 'msg': 'command completed successfully'} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: top + def container_post__top(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(all=True, filters=filters): + res = { 'type': 'success', 'msg': container.top()} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: stats + def container_post__stats(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(all=True, filters=filters): + for stat in container.stats(decode=True, stream=True): + res = { 'type': 'success', 'msg': stat} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: exec - cmd: mailq - task: delete + def container_post__exec__mailq__delete(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'items' in request_json: + r = re.compile("^[0-9a-fA-F]+$") + filtered_qids = filter(r.match, request_json['items']) + if filtered_qids: + flagged_qids = ['-d %s' % i for i in filtered_qids] + sanitized_string = str(' '.join(flagged_qids)) + for container in self.sync_docker_client.containers.list(filters=filters): + postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) + return self.exec_run_handler('generic', postsuper_r) + # api call: container_post - post_action: exec - cmd: mailq - task: hold + def container_post__exec__mailq__hold(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'items' in request_json: + r = re.compile("^[0-9a-fA-F]+$") + filtered_qids = filter(r.match, request_json['items']) + if filtered_qids: + flagged_qids = ['-h %s' % i for i in filtered_qids] + sanitized_string = str(' '.join(flagged_qids)) + for container in self.sync_docker_client.containers.list(filters=filters): + postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) + return self.exec_run_handler('generic', postsuper_r) + # api call: container_post - post_action: exec - cmd: mailq - task: cat + def container_post__exec__mailq__cat(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'items' in request_json: + r = re.compile("^[0-9a-fA-F]+$") + filtered_qids = filter(r.match, request_json['items']) + if filtered_qids: + sanitized_string = str(' '.join(filtered_qids)) + + for container in self.sync_docker_client.containers.list(filters=filters): + postcat_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix') + if not postcat_return: + postcat_return = 'err: invalid' + return self.exec_run_handler('utf8_text_only', postcat_return) + # api call: container_post - post_action: exec - cmd: mailq - task: unhold + def container_post__exec__mailq__unhold(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'items' in request_json: + r = re.compile("^[0-9a-fA-F]+$") + filtered_qids = filter(r.match, request_json['items']) + if filtered_qids: + flagged_qids = ['-H %s' % i for i in filtered_qids] + sanitized_string = str(' '.join(flagged_qids)) + for container in self.sync_docker_client.containers.list(filters=filters): + postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) + return self.exec_run_handler('generic', postsuper_r) + # api call: container_post - post_action: exec - cmd: mailq - task: deliver + def container_post__exec__mailq__deliver(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'items' in request_json: + r = re.compile("^[0-9a-fA-F]+$") + filtered_qids = filter(r.match, request_json['items']) + if filtered_qids: + flagged_qids = ['-i %s' % i for i in filtered_qids] + for container in self.sync_docker_client.containers.list(filters=filters): + for i in flagged_qids: + postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix') + # todo: check each exit code + res = { 'type': 'success', 'msg': 'Scheduled immediate delivery'} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: exec - cmd: mailq - task: list + def container_post__exec__mailq__list(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(filters=filters): + mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix') + return self.exec_run_handler('utf8_text_only', mailq_return) + # api call: container_post - post_action: exec - cmd: mailq - task: flush + def container_post__exec__mailq__flush(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(filters=filters): + postqueue_r = container.exec_run(["/usr/sbin/postqueue", "-f"], user='postfix') + return self.exec_run_handler('generic', postqueue_r) + # api call: container_post - post_action: exec - cmd: mailq - task: super_delete + def container_post__exec__mailq__super_delete(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(filters=filters): + postsuper_r = container.exec_run(["/usr/sbin/postsuper", "-d", "ALL"]) + return self.exec_run_handler('generic', postsuper_r) + # api call: container_post - post_action: exec - cmd: system - task: fts_rescan + def container_post__exec__system__fts_rescan(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'username' in request_json: + for container in self.sync_docker_client.containers.list(filters=filters): + rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail') + if rescan_return.exit_code == 0: + res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + else: + res = { 'type': 'warning', 'msg': 'fts_rescan error'} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + if 'all' in request_json: + for container in self.sync_docker_client.containers.list(filters=filters): + rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail') + if rescan_return.exit_code == 0: + res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + else: + res = { 'type': 'warning', 'msg': 'fts_rescan error'} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: exec - cmd: system - task: df + def container_post__exec__system__df(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'dir' in request_json: + for container in self.sync_docker_client.containers.list(filters=filters): + df_return = container.exec_run(["/bin/bash", "-c", "/bin/df -H '" + request_json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody') + if df_return.exit_code == 0: + return df_return.output.decode('utf-8').rstrip() + else: + return "0,0,0,0,0,0" + # api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade + def container_post__exec__system__mysql_upgrade(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(filters=filters): + sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql') + if sql_return.exit_code == 0: + matched = False + for line in sql_return.output.decode('utf-8').split("\n"): + if 'is already upgraded to' in line: + matched = True + if matched: + res = { 'type': 'success', 'msg':'mysql_upgrade: already upgraded', 'text': sql_return.output.decode('utf-8')} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + else: + container.restart() + res = { 'type': 'warning', 'msg':'mysql_upgrade: upgrade was applied', 'text': sql_return.output.decode('utf-8')} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + else: + res = { 'type': 'error', 'msg': 'mysql_upgrade: error running command', 'text': sql_return.output.decode('utf-8')} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: exec - cmd: system - task: mysql_tzinfo_to_sql + def container_post__exec__system__mysql_tzinfo_to_sql(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(filters=filters): + sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_tzinfo_to_sql /usr/share/zoneinfo | /bin/sed 's/Local time zone must be set--see zic manual page/FCTY/' | /usr/bin/mysql -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "' mysql \n"], user='mysql') + if sql_return.exit_code == 0: + res = { 'type': 'info', 'msg': 'mysql_tzinfo_to_sql: command completed successfully', 'text': sql_return.output.decode('utf-8')} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + else: + res = { 'type': 'error', 'msg': 'mysql_tzinfo_to_sql: error running command', 'text': sql_return.output.decode('utf-8')} + return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: exec - cmd: reload - task: dovecot + def container_post__exec__reload__dovecot(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(filters=filters): + reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/dovecot reload"]) + return self.exec_run_handler('generic', reload_return) + # api call: container_post - post_action: exec - cmd: reload - task: postfix + def container_post__exec__reload__postfix(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(filters=filters): + reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postfix reload"]) + return self.exec_run_handler('generic', reload_return) + # api call: container_post - post_action: exec - cmd: reload - task: nginx + def container_post__exec__reload__nginx(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(filters=filters): + reload_return = container.exec_run(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"]) + return self.exec_run_handler('generic', reload_return) + # api call: container_post - post_action: exec - cmd: sieve - task: list + def container_post__exec__sieve__list(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'username' in request_json: + for container in self.sync_docker_client.containers.list(filters=filters): + sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request_json['username'].replace("'", "'\\''") + "'"]) + return self.exec_run_handler('utf8_text_only', sieve_return) + # api call: container_post - post_action: exec - cmd: sieve - task: print + def container_post__exec__sieve__print(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'username' in request_json and 'script_name' in request_json: + for container in self.sync_docker_client.containers.list(filters=filters): + cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"] + sieve_return = container.exec_run(cmd) + return self.exec_run_handler('utf8_text_only', sieve_return) + # api call: container_post - post_action: exec - cmd: maildir - task: cleanup + def container_post__exec__maildir__cleanup(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'maildir' in request_json: + for container in self.sync_docker_client.containers.list(filters=filters): + sane_name = re.sub(r'\W+', '', request_json['maildir']) + vmail_name = request_json['maildir'].replace("'", "'\\''") + cmd_vmail = "if [[ -d '/var/vmail/" + vmail_name + "' ]]; then /bin/mv '/var/vmail/" + vmail_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi" + index_name = request_json['maildir'].split("/") + if len(index_name) > 1: + index_name = index_name[1].replace("'", "'\\''") + "@" + index_name[0].replace("'", "'\\''") + cmd_vmail_index = "if [[ -d '/var/vmail_index/" + index_name + "' ]]; then /bin/mv '/var/vmail_index/" + index_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "_index'; fi" + cmd = ["/bin/bash", "-c", cmd_vmail + " && " + cmd_vmail_index] + else: + cmd = ["/bin/bash", "-c", cmd_vmail] + maildir_cleanup = container.exec_run(cmd, user='vmail') + return self.exec_run_handler('generic', maildir_cleanup) + # api call: container_post - post_action: exec - cmd: maildir - task: move + def container_post__exec__maildir__move(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'old_maildir' in request_json and 'new_maildir' in request_json: + for container in self.sync_docker_client.containers.list(filters=filters): + vmail_name = request_json['old_maildir'].replace("'", "'\\''") + new_vmail_name = request_json['new_maildir'].replace("'", "'\\''") + cmd_vmail = f"if [[ -d '/var/vmail/{vmail_name}' ]]; then /bin/mv '/var/vmail/{vmail_name}' '/var/vmail/{new_vmail_name}'; fi" + + index_name = request_json['old_maildir'].split("/") + new_index_name = request_json['new_maildir'].split("/") + if len(index_name) > 1 and len(new_index_name) > 1: + index_name = index_name[1].replace("'", "'\\''") + "@" + index_name[0].replace("'", "'\\''") + new_index_name = new_index_name[1].replace("'", "'\\''") + "@" + new_index_name[0].replace("'", "'\\''") + cmd_vmail_index = f"if [[ -d '/var/vmail_index/{index_name}' ]]; then /bin/mv '/var/vmail_index/{index_name}' '/var/vmail_index/{new_index_name}_index'; fi" + cmd = ["/bin/bash", "-c", cmd_vmail + " && " + cmd_vmail_index] + else: + cmd = ["/bin/bash", "-c", cmd_vmail] + maildir_move = container.exec_run(cmd, user='vmail') + return self.exec_run_handler('generic', maildir_move) + # api call: container_post - post_action: exec - cmd: rspamd - task: worker_password + def container_post__exec__rspamd__worker_password(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'raw' in request_json: + for container in self.sync_docker_client.containers.list(filters=filters): + cmd = "/usr/bin/rspamadm pw -e -p '" + request_json['raw'].replace("'", "'\\''") + "' 2> /dev/null" + cmd_response = self.exec_cmd_container(container, cmd, user="_rspamd") + + matched = False + for line in cmd_response.split("\n"): + if '$2$' in line: + hash = line.strip() + hash_out = re.search(r'\$2\$.+$', hash).group(0) + rspamd_passphrase_hash = re.sub(r'[^0-9a-zA-Z\$]+', '', hash_out.rstrip()) + rspamd_password_filename = "/etc/rspamd/override.d/worker-controller-password.inc" + cmd = '''/bin/echo 'enable_password = "%s";' > %s && cat %s''' % (rspamd_passphrase_hash, rspamd_password_filename, rspamd_password_filename) + cmd_response = self.exec_cmd_container(container, cmd, user="_rspamd") + if rspamd_passphrase_hash.startswith("$2$") and rspamd_passphrase_hash in cmd_response: + container.restart() + matched = True + if matched: + res = { 'type': 'success', 'msg': 'command completed successfully' } + self.logger.info('success changing Rspamd password') + return Response(content=json.dumps(res, indent=4), media_type="application/json") + else: + self.logger.error('failed changing Rspamd password') + res = { 'type': 'danger', 'msg': 'command did not complete' } + return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: exec - cmd: sogo - task: rename + def container_post__exec__sogo__rename_user(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'old_username' in request_json and 'new_username' in request_json: + for container in self.sync_docker_client.containers.list(filters=filters): + old_username = request_json['old_username'].replace("'", "'\\''") + new_username = request_json['new_username'].replace("'", "'\\''") + + sogo_return = container.exec_run(["/bin/bash", "-c", f"sogo-tool rename-user '{old_username}' '{new_username}'"], user='sogo') + return self.exec_run_handler('generic', sogo_return) + # api call: container_post - post_action: exec - cmd: doveadm - task: get_acl + def container_post__exec__doveadm__get_acl(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(filters=filters): + id = request_json['id'].replace("'", "'\\''") + + shared_folders = container.exec_run(["/bin/bash", "-c", f"doveadm mailbox list -u '{id}'"]) + shared_folders = shared_folders.output.decode('utf-8') + shared_folders = shared_folders.splitlines() + + formatted_acls = [] + mailbox_seen = [] + for shared_folder in shared_folders: + if "Shared" not in shared_folder: + mailbox = shared_folder.replace("'", "'\\''") + if mailbox in mailbox_seen: + continue + + acls = container.exec_run(["/bin/bash", "-c", f"doveadm acl get -u '{id}' '{mailbox}'"]) + acls = acls.output.decode('utf-8').strip().splitlines() + if len(acls) >= 2: + for acl in acls[1:]: + user_id, rights = acl.split(maxsplit=1) + user_id = user_id.split('=')[1] + mailbox_seen.append(mailbox) + formatted_acls.append({ 'user': id, 'id': user_id, 'mailbox': mailbox, 'rights': rights.split() }) + elif "Shared" in shared_folder and "/" in shared_folder: + shared_folder = shared_folder.split("/") + if len(shared_folder) < 3: + continue + + user = shared_folder[1].replace("'", "'\\''") + mailbox = '/'.join(shared_folder[2:]).replace("'", "'\\''") + if mailbox in mailbox_seen: + continue + + acls = container.exec_run(["/bin/bash", "-c", f"doveadm acl get -u '{user}' '{mailbox}'"]) + acls = acls.output.decode('utf-8').strip().splitlines() + if len(acls) >= 2: + for acl in acls[1:]: + user_id, rights = acl.split(maxsplit=1) + user_id = user_id.split('=')[1].replace("'", "'\\''") + if user_id == id and mailbox not in mailbox_seen: + mailbox_seen.append(mailbox) + formatted_acls.append({ 'user': user, 'id': id, 'mailbox': mailbox, 'rights': rights.split() }) + + return Response(content=json.dumps(formatted_acls, indent=4), media_type="application/json") + # api call: container_post - post_action: exec - cmd: doveadm - task: delete_acl + def container_post__exec__doveadm__delete_acl(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(filters=filters): + user = request_json['user'].replace("'", "'\\''") + mailbox = request_json['mailbox'].replace("'", "'\\''") + id = request_json['id'].replace("'", "'\\''") + + if user and mailbox and id: + acl_delete_return = container.exec_run(["/bin/bash", "-c", f"doveadm acl delete -u '{user}' '{mailbox}' 'user={id}'"]) + return self.exec_run_handler('generic', acl_delete_return) + # api call: container_post - post_action: exec - cmd: doveadm - task: set_acl + def container_post__exec__doveadm__set_acl(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(filters=filters): + user = request_json['user'].replace("'", "'\\''") + mailbox = request_json['mailbox'].replace("'", "'\\''") + id = request_json['id'].replace("'", "'\\''") + rights = "" + + available_rights = [ + "admin", + "create", + "delete", + "expunge", + "insert", + "lookup", + "post", + "read", + "write", + "write-deleted", + "write-seen" + ] + for right in request_json['rights']: + right = right.replace("'", "'\\''").lower() + if right in available_rights: + rights += right + " " + + if user and mailbox and id and rights: + acl_set_return = container.exec_run(["/bin/bash", "-c", f"doveadm acl set -u '{user}' '{mailbox}' 'user={id}' {rights}"]) + return self.exec_run_handler('generic', acl_set_return) + + + # Collect host stats + async def get_host_stats(self, wait=5): + try: + system_time = datetime.now() + host_stats = { + "cpu": { + "cores": psutil.cpu_count(), + "usage": psutil.cpu_percent() + }, + "memory": { + "total": psutil.virtual_memory().total, + "usage": psutil.virtual_memory().percent, + "swap": psutil.swap_memory() + }, + "uptime": time.time() - psutil.boot_time(), + "system_time": system_time.strftime("%d.%m.%Y %H:%M:%S"), + "architecture": platform.machine() + } + + await self.redis_client.set('host_stats', json.dumps(host_stats), ex=10) + except Exception as e: + res = { + "type": "danger", + "msg": str(e) + } + + await asyncio.sleep(wait) + self.host_stats_isUpdating = False + # Collect container stats + async def get_container_stats(self, container_id, wait=5, stop=False): + if container_id and container_id.isalnum(): + try: + for container in (await self.async_docker_client.containers.list()): + if container._id == container_id: + res = await container.stats(stream=False) + + if await self.redis_client.exists(container_id + '_stats'): + stats = json.loads(await self.redis_client.get(container_id + '_stats')) + else: + stats = [] + stats.append(res[0]) + if len(stats) > 3: + del stats[0] + await self.redis_client.set(container_id + '_stats', json.dumps(stats), ex=60) + except Exception as e: + res = { + "type": "danger", + "msg": str(e) + } + else: + res = { + "type": "danger", + "msg": "no or invalid id defined" + } + + await asyncio.sleep(wait) + if stop == True: + # update task was called second time, stop + self.containerIds_to_update.remove(container_id) + else: + # call update task a second time + await self.get_container_stats(container_id, wait=0, stop=True) + + def exec_cmd_container(self, container, cmd, user, timeout=2, shell_cmd="/bin/bash"): + def recv_socket_data(c_socket, timeout): + c_socket.setblocking(0) + total_data=[] + data='' + begin=time.time() + while True: + if total_data and time.time()-begin > timeout: + break + elif time.time()-begin > timeout*2: + break + try: + data = c_socket.recv(8192) + if data: + total_data.append(data.decode('utf-8')) + #change the beginning time for measurement + begin=time.time() + else: + #sleep for sometime to indicate a gap + time.sleep(0.1) + break + except: + pass + return ''.join(total_data) + + try : + socket = container.exec_run([shell_cmd], stdin=True, socket=True, user=user).output._sock + if not cmd.endswith("\n"): + cmd = cmd + "\n" + socket.send(cmd.encode('utf-8')) + data = recv_socket_data(socket, timeout) + socket.close() + return data + except Exception as e: + self.logger.error("error - exec_cmd_container: %s" % str(e)) + traceback.print_exc(file=sys.stdout) + + def exec_run_handler(self, type, output): + if type == 'generic': + if output.exit_code == 0: + res = { 'type': 'success', 'msg': 'command completed successfully' } + return Response(content=json.dumps(res, indent=4), media_type="application/json") + else: + res = { 'type': 'danger', 'msg': 'command failed: ' + output.output.decode('utf-8') } + return Response(content=json.dumps(res, indent=4), media_type="application/json") + if type == 'utf8_text_only': + return Response(content=output.output.decode('utf-8'), media_type="text/plain") diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/phpunit.xml.tmppica b/data/Dockerfiles/dockerapi/modules/__init__.py similarity index 100% rename from data/web/inc/lib/vendor/robthree/twofactorauth/phpunit.xml.tmppica rename to data/Dockerfiles/dockerapi/modules/__init__.py diff --git a/data/Dockerfiles/dockerapi/server.py b/data/Dockerfiles/dockerapi/server.py deleted file mode 100644 index a161c8a693..0000000000 --- a/data/Dockerfiles/dockerapi/server.py +++ /dev/null @@ -1,267 +0,0 @@ -from flask import Flask -from flask_restful import Resource, Api -from flask import jsonify -from flask import request -from threading import Thread -from OpenSSL import crypto -import docker -import uuid -import signal -import time -import os -import re -import sys -import ssl -import socket - -docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto') -app = Flask(__name__) -api = Api(app) - -class containers_get(Resource): - def get(self): - containers = {} - try: - for container in docker_client.containers.list(all=True): - containers.update({container.attrs['Id']: container.attrs}) - return containers - except Exception as e: - return jsonify(type='danger', msg=str(e)) - -class container_get(Resource): - def get(self, container_id): - if container_id and container_id.isalnum(): - try: - for container in docker_client.containers.list(all=True, filters={"id": container_id}): - return container.attrs - except Exception as e: - return jsonify(type='danger', msg=str(e)) - else: - return jsonify(type='danger', msg='no or invalid id defined') - -class container_post(Resource): - def post(self, container_id, post_action): - if container_id and container_id.isalnum() and post_action: - if post_action == 'stop': - try: - for container in docker_client.containers.list(all=True, filters={"id": container_id}): - container.stop() - return jsonify(type='success', msg='command completed successfully') - except Exception as e: - return jsonify(type='danger', msg=str(e)) - - elif post_action == 'start': - try: - for container in docker_client.containers.list(all=True, filters={"id": container_id}): - container.start() - return jsonify(type='success', msg='command completed successfully') - except Exception as e: - return jsonify(type='danger', msg=str(e)) - - elif post_action == 'restart': - try: - for container in docker_client.containers.list(all=True, filters={"id": container_id}): - container.restart() - return jsonify(type='success', msg='command completed successfully') - except Exception as e: - return jsonify(type='danger', msg=str(e)) - - elif post_action == 'exec': - - if not request.json or not 'cmd' in request.json: - return jsonify(type='danger', msg='cmd is missing') - - if request.json['cmd'] == 'df' and request.json['dir']: - try: - for container in docker_client.containers.list(filters={"id": container_id}): - # Should be changed to be able to validate a path - directory = re.sub('[^0-9a-zA-Z/]+', '', request.json['dir']) - df_return = container.exec_run(["/bin/bash", "-c", "/bin/df -H " + directory + " | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody') - if df_return.exit_code == 0: - return df_return.output.rstrip() - else: - return "0,0,0,0,0,0" - except Exception as e: - return jsonify(type='danger', msg=str(e)) - elif request.json['cmd'] == 'sieve_list' and request.json['username']: - try: - for container in docker_client.containers.list(filters={"id": container_id}): - sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"]) - return sieve_return.output - except Exception as e: - return jsonify(type='danger', msg=str(e)) - elif request.json['cmd'] == 'sieve_print' and request.json['script_name'] and request.json['username']: - try: - for container in docker_client.containers.list(filters={"id": container_id}): - sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve get -u '" + request.json['username'].replace("'", "'\\''") + "' '" + request.json['script_name'].replace("'", "'\\''") + "'"]) - return sieve_return.output - except Exception as e: - return jsonify(type='danger', msg=str(e)) - # not in use... - elif request.json['cmd'] == 'mail_crypt_generate' and request.json['username'] and request.json['old_password'] and request.json['new_password']: - try: - for container in docker_client.containers.list(filters={"id": container_id}): - # create if missing - crypto_generate = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm mailbox cryptokey generate -u '" + request.json['username'].replace("'", "'\\''") + "' -URf"], user='vmail') - if crypto_generate.exit_code == 0: - # open a shell, bind stdin and return socket - cryptokey_shell = container.exec_run(["/bin/bash"], stdin=True, socket=True, user='vmail') - # command to be piped to shell - cryptokey_cmd = "/usr/local/bin/doveadm mailbox cryptokey password -u '" + request.json['username'].replace("'", "'\\''") + "' -n '" + request.json['new_password'].replace("'", "'\\''") + "' -o '" + request.json['old_password'].replace("'", "'\\''") + "'\n" - # socket is .output - cryptokey_socket = cryptokey_shell.output; - try : - # send command utf-8 encoded - cryptokey_socket.sendall(cryptokey_cmd.encode('utf-8')) - # we won't send more data than this - cryptokey_socket.shutdown(socket.SHUT_WR) - except socket.error: - # exit on socket error - return jsonify(type='danger', msg=str('socket error')) - # read response - cryptokey_response = recv_socket_data(cryptokey_socket) - crypto_error = re.search('dcrypt_key_load_private.+failed.+error', cryptokey_response) - if crypto_error is not None: - return jsonify(type='danger', msg=str("dcrypt_key_load_private error")) - return jsonify(type='success', msg=str("key pair generated")) - else: - return jsonify(type='danger', msg=str(crypto_generate.output)) - except Exception as e: - return jsonify(type='danger', msg=str(e)) - elif request.json['cmd'] == 'maildir_cleanup' and request.json['maildir']: - try: - for container in docker_client.containers.list(filters={"id": container_id}): - sane_name = re.sub(r'\W+', '', request.json['maildir']) - maildir_cleanup = container.exec_run(["/bin/bash", "-c", "if [[ -d '/var/vmail/" + request.json['maildir'].replace("'", "'\\''") + "' ]]; then /bin/mv '/var/vmail/" + request.json['maildir'].replace("'", "'\\''") + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"], user='vmail') - if maildir_cleanup.exit_code == 0: - return jsonify(type='success', msg=str("moved to garbage")) - else: - return jsonify(type='danger', msg=str(maildir_cleanup.output)) - except Exception as e: - return jsonify(type='danger', msg=str(e)) - elif request.json['cmd'] == 'worker_password' and request.json['raw']: - try: - for container in docker_client.containers.list(filters={"id": container_id}): - worker_shell = container.exec_run(["/bin/bash"], stdin=True, socket=True, user='_rspamd') - worker_cmd = "/usr/bin/rspamadm pw -e -p '" + request.json['raw'].replace("'", "'\\''") + "' 2> /dev/null\n" - worker_socket = worker_shell.output; - try : - worker_socket.sendall(worker_cmd.encode('utf-8')) - worker_socket.shutdown(socket.SHUT_WR) - except socket.error: - return jsonify(type='danger', msg=str('socket error')) - worker_response = recv_socket_data(worker_socket) - matched = False - for line in worker_response.split("\n"): - if '$2$' in line: - matched = True - hash = line.strip() - hash_out = re.search('\$2\$.+$', hash).group(0) - f = open("/access.inc", "w") - f.write('enable_password = "' + re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip()) + '";\n') - f.close() - container.restart() - if matched: - return jsonify(type='success', msg='command completed successfully') - else: - return jsonify(type='danger', msg='command did not complete') - except Exception as e: - return jsonify(type='danger', msg=str(e)) - elif request.json['cmd'] == 'mailman_password' and request.json['email'] and request.json['passwd']: - try: - for container in docker_client.containers.list(filters={"id": container_id}): - add_su = container.exec_run(["/bin/bash", "-c", "/opt/mm_web/add_su.py '" + request.json['passwd'].replace("'", "'\\''") + "' '" + request.json['email'].replace("'", "'\\''") + "'"], user='mailman') - if add_su.exit_code == 0: - return jsonify(type='success', msg='command completed successfully') - else: - return jsonify(type='danger', msg='command did not complete, exit code was ' + int(add_su.exit_code)) - except Exception as e: - return jsonify(type='danger', msg=str(e)) - - else: - return jsonify(type='danger', msg='Unknown command') - - else: - return jsonify(type='danger', msg='invalid action') - - else: - return jsonify(type='danger', msg='invalid container id or missing action') - -class GracefulKiller: - kill_now = False - def __init__(self): - signal.signal(signal.SIGINT, self.exit_gracefully) - signal.signal(signal.SIGTERM, self.exit_gracefully) - - def exit_gracefully(self, signum, frame): - self.kill_now = True - -def startFlaskAPI(): - create_self_signed_cert() - try: - ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - ctx.check_hostname = False - ctx.load_cert_chain(certfile='/cert.pem', keyfile='/key.pem') - except: - print "Cannot initialize TLS, retrying in 5s..." - time.sleep(5) - app.run(debug=False, host='0.0.0.0', port=443, threaded=True, ssl_context=ctx) - -def recv_socket_data(c_socket, timeout=10): - c_socket.setblocking(0) - total_data=[]; - data=''; - begin=time.time() - while True: - if total_data and time.time()-begin > timeout: - break - elif time.time()-begin > timeout*2: - break - try: - data = c_socket.recv(8192) - if data: - total_data.append(data) - #change the beginning time for measurement - begin=time.time() - else: - #sleep for sometime to indicate a gap - time.sleep(0.1) - break - except: - pass - return ''.join(total_data) - -def create_self_signed_cert(): - pkey = crypto.PKey() - pkey.generate_key(crypto.TYPE_RSA, 2048) - cert = crypto.X509() - cert.get_subject().O = "mailcow" - cert.get_subject().CN = "dockerapi" - cert.set_serial_number(int(uuid.uuid4())) - cert.gmtime_adj_notBefore(0) - cert.gmtime_adj_notAfter(10*365*24*60*60) - cert.set_issuer(cert.get_subject()) - cert.set_pubkey(pkey) - cert.sign(pkey, 'sha512') - cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert) - pkey = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey) - with os.fdopen(os.open('/cert.pem', os.O_WRONLY | os.O_CREAT, 0o644), 'w') as handle: - handle.write(cert) - with os.fdopen(os.open('/key.pem', os.O_WRONLY | os.O_CREAT, 0o600), 'w') as handle: - handle.write(pkey) - -api.add_resource(containers_get, '/containers/json') -api.add_resource(container_get, '/containers//json') -api.add_resource(container_post, '/containers//') - -if __name__ == '__main__': - api_thread = Thread(target=startFlaskAPI) - api_thread.daemon = True - api_thread.start() - killer = GracefulKiller() - while True: - time.sleep(1) - if killer.kill_now: - break - print "Stopping dockerapi-mailcow" - diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index fdfa84f55b..f1152c8a15 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -1,137 +1,139 @@ -FROM debian:stretch-slim -LABEL maintainer "Andre Peters " +FROM alpine:3.21 -ARG DEBIAN_FRONTEND=noninteractive -ENV LC_ALL C -ENV DOVECOT_VERSION 2.3.2.1 -ENV PIGEONHOLE_VERSION 0.5.2 +LABEL maintainer="The Infrastructure Company GmbH " -RUN apt-get update && apt-get -y --no-install-recommends install \ - automake \ - autotools-dev \ - build-essential \ +# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?.*)$ +ARG GOSU_VERSION=1.19 + +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 + +# Add groups and users before installing Dovecot to not break compatibility +RUN addgroup -g 5000 vmail \ + && addgroup -g 401 dovecot \ + && addgroup -g 402 dovenull \ + && sed -i "s/999/99/" /etc/group \ + && addgroup -g 999 sogo \ + && addgroup nobody sogo \ + && adduser -D -u 5000 -G vmail -h /var/vmail vmail \ + && adduser -D -G dovecot -u 401 -h /dev/null -s /sbin/nologin dovecot \ + && adduser -D -G dovenull -u 402 -h /dev/null -s /sbin/nologin dovenull \ + && apk add --no-cache --update \ + bash \ + bind-tools \ + findutils \ + envsubst \ ca-certificates \ - cpanminus \ curl \ - default-libmysqlclient-dev \ - dnsutils \ - libjson-webtoken-perl \ - libcgi-pm-perl \ - libcrypt-openssl-rsa-perl \ - libdata-uniqid-perl \ - libhtml-parser-perl \ - libmail-imapclient-perl \ - libparse-recdescent-perl \ - libsys-meminfo-perl \ - libtest-mockobject-perl \ - libwww-perl \ - libauthen-ntlm-perl \ - libbz2-dev \ - libcrypt-ssleay-perl \ - libcurl4-openssl-dev \ - libdbd-mysql-perl \ - libdbi-perl \ - libdigest-hmac-perl \ - libexpat1-dev \ - libfile-copy-recursive-perl \ - libio-compress-perl \ - libio-socket-inet6-perl \ - libio-socket-ssl-perl \ - libio-tee-perl \ - libipc-run-perl \ - liblockfile-simple-perl \ - liblz-dev \ - liblz4-dev \ - liblzma-dev \ - libmodule-scandeps-perl \ - libnet-ssleay-perl \ - libpam-dev \ - libpar-packer-perl \ - libreadonly-perl \ - libssl-dev \ - libterm-readkey-perl \ - libtest-pod-perl \ - libtest-simple-perl \ - libtry-tiny-perl \ - libunicode-string-perl \ - libproc-processtable-perl \ - libtest-nowarnings-perl \ - libtest-deep-perl \ - libtest-warn-perl \ - libregexp-common-perl \ - liburi-perl \ - lzma-dev \ - make \ - mysql-client \ + coreutils \ + jq \ + lua \ + lua-cjson \ + lua-socket \ + lua-sql-mysql \ + lua5.3-sql-mysql \ + icu-data-full \ + mariadb-connector-c \ + lua-sec \ + mariadb-dev \ + glib-dev \ + gcompat \ + mariadb-client \ + perl \ + perl-dev \ + perl-ntlm \ + perl-cgi \ + perl-crypt-openssl-rsa \ + perl-utils \ + perl-crypt-ssleay \ + perl-data-uniqid \ + perl-dbd-mysql \ + perl-dbi \ + perl-digest-hmac \ + perl-dist-checkconflicts \ + perl-encode-imaputf7 \ + perl-file-copy-recursive \ + perl-file-tail \ + perl-io-socket-inet6 \ + perl-io-gzip \ + perl-io-socket-ssl \ + perl-io-tee \ + perl-ipc-run \ + perl-json-webtoken \ + perl-mail-imapclient \ + perl-module-implementation \ + perl-module-scandeps \ + perl-net-ssleay \ + perl-package-stash \ + perl-package-stash-xs \ + perl-par-packer \ + perl-parse-recdescent \ + perl-lockfile-simple \ + libproc2 \ + perl-readonly \ + perl-regexp-common \ + perl-sys-meminfo \ + perl-term-readkey \ + perl-test-deep \ + perl-test-fatal \ + perl-test-mockobject \ + perl-test-mock-guard \ + perl-test-pod \ + perl-test-requires \ + perl-test-simple \ + perl-test-warn \ + perl-try-tiny \ + perl-unicode-string \ + perl-proc-processtable \ + perl-app-cpanminus \ procps \ - supervisor \ - cron \ - redis-server \ + python3 \ + py3-mysqlclient \ + py3-html2text \ + py3-jinja2 \ + py3-redis \ + redis \ syslog-ng \ - syslog-ng-core \ - syslog-ng-mod-redis \ - && rm -rf /var/lib/apt/lists/* - -RUN curl https://www.dovecot.org/releases/2.3/dovecot-$DOVECOT_VERSION.tar.gz | tar xvz \ - && cd dovecot-$DOVECOT_VERSION \ - && ./configure --with-solr --with-mysql --with-lzma --with-lz4 --with-ssl=openssl --with-notify=inotify --with-storages=mdbox,sdbox,maildir,mbox,imapc,pop3c --with-bzlib --with-zlib \ - && make -j3 \ - && make install \ - && make clean \ - && cd .. && rm -rf dovecot-$DOVECOT_VERSION \ - && curl https://pigeonhole.dovecot.org/releases/2.3/dovecot-2.3-pigeonhole-$PIGEONHOLE_VERSION.tar.gz | tar xvz \ - && cd dovecot-2.3-pigeonhole-$PIGEONHOLE_VERSION \ - && ./configure \ - && make -j3 \ - && make install \ - && make clean \ - && cd .. \ - && rm -rf dovecot-2.3-pigeonhole-$PIGEONHOLE_VERSION + syslog-ng-redis \ + syslog-ng-json \ + supervisor \ + tzdata \ + wget \ + dovecot \ + dovecot-dev \ + dovecot-lmtpd \ + dovecot-lua \ + dovecot-ldap \ + dovecot-mysql \ + dovecot-sql \ + dovecot-submissiond \ + dovecot-pigeonhole-plugin \ + dovecot-pop3d \ + dovecot-fts-flatcurve \ + && arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \ + && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$arch" \ + && chmod +x /usr/local/bin/gosu \ + && gosu nobody true -RUN cpanm Data::Uniqid Mail::IMAPClient String::Util -RUN echo '* * * * * root /usr/local/bin/imapsync_cron.pl' > /etc/cron.d/imapsync -RUN echo '30 3 * * * vmail /usr/local/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync -RUN echo '* * * * * vmail /usr/local/bin/trim_logs.sh >> /dev/stdout 2>&1' > /etc/cron.d/trim_logs -RUN echo '25 * * * * vmail /usr/local/bin/maildir_gc.sh >> /dev/stdout 2>&1' > /etc/cron.d/maildir_gc -RUN echo '30 1 * * * root /usr/local/bin/sa-rules.sh >> /dev/stdout 2>&1' > /etc/cron.d/sa-rules COPY trim_logs.sh /usr/local/bin/trim_logs.sh +COPY clean_q_aged.sh /usr/local/bin/clean_q_aged.sh COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf +COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf COPY imapsync /usr/local/bin/imapsync -COPY postlogin.sh /usr/local/bin/postlogin.sh -COPY imapsync_cron.pl /usr/local/bin/imapsync_cron.pl -COPY report-spam.sieve /usr/local/lib/dovecot/sieve/report-spam.sieve -COPY report-ham.sieve /usr/local/lib/dovecot/sieve/report-ham.sieve -COPY rspamd-pipe-ham /usr/local/lib/dovecot/sieve/rspamd-pipe-ham -COPY rspamd-pipe-spam /usr/local/lib/dovecot/sieve/rspamd-pipe-spam +COPY imapsync_runner.pl /usr/local/bin/imapsync_runner.pl +COPY report-spam.sieve /usr/lib/dovecot/sieve/report-spam.sieve +COPY report-ham.sieve /usr/lib/dovecot/sieve/report-ham.sieve +COPY rspamd-pipe-ham /usr/lib/dovecot/sieve/rspamd-pipe-ham +COPY rspamd-pipe-spam /usr/lib/dovecot/sieve/rspamd-pipe-spam COPY sa-rules.sh /usr/local/bin/sa-rules.sh COPY maildir_gc.sh /usr/local/bin/maildir_gc.sh COPY docker-entrypoint.sh / COPY supervisord.conf /etc/supervisor/supervisord.conf - -RUN chmod +x /usr/local/lib/dovecot/sieve/rspamd-pipe-ham \ - /usr/local/lib/dovecot/sieve/rspamd-pipe-spam \ - /usr/local/bin/imapsync_cron.pl \ - /usr/local/bin/postlogin.sh \ - /usr/local/bin/imapsync \ - /usr/local/bin/trim_logs.sh \ - /usr/local/bin/sa-rules.sh \ - /usr/local/bin/maildir_gc.sh - -RUN groupadd -g 5000 vmail \ - && groupadd -g 401 dovecot \ - && groupadd -g 402 dovenull \ - && useradd -g vmail -u 5000 vmail -d /var/vmail \ - && useradd -c "Dovecot unprivileged user" -d /dev/null -u 401 -g dovecot -s /bin/false dovecot \ - && useradd -c "Dovecot login user" -d /dev/null -u 402 -g dovenull -s /bin/false dovenull - -RUN touch /etc/default/locale -RUN apt-get purge -y build-essential automake autotools-dev default-libmysqlclient-dev libbz2-dev libcurl4-openssl-dev libexpat1-dev liblz-dev liblz4-dev liblzma-dev libpam-dev libssl-dev lzma-dev \ - && apt-get autoremove --purge -y +COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh +COPY quarantine_notify.py /usr/local/bin/quarantine_notify.py +COPY quota_notify.py /usr/local/bin/quota_notify.py +COPY repl_health.sh /usr/local/bin/repl_health.sh +COPY optimize-fts.sh /usr/local/bin/optimize-fts.sh ENTRYPOINT ["/docker-entrypoint.sh"] -CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf - -RUN rm -rf \ - /tmp/* \ - /var/tmp/* - +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] diff --git a/data/Dockerfiles/dovecot/clean_q_aged.sh b/data/Dockerfiles/dovecot/clean_q_aged.sh new file mode 100755 index 0000000000..ebedc89991 --- /dev/null +++ b/data/Dockerfiles/dovecot/clean_q_aged.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +source /source_env.sh + +MAX_AGE=$(redis-cli --raw -h redis-mailcow -a ${REDISPASS} --no-auth-warning GET Q_MAX_AGE) + +if [[ -z ${MAX_AGE} ]]; then + echo "Max age for quarantine items not defined" + exit 1 +fi + +NUM_REGEXP='^[0-9]+$' +if ! [[ ${MAX_AGE} =~ ${NUM_REGEXP} ]] ; then + echo "Max age for quarantine items invalid" + exit 1 +fi + +TO_DELETE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT COUNT(id) FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY" -BN) +mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY" +echo "Deleted ${TO_DELETE} items from quarantine table (max age is ${MAX_AGE//[!0-9]/} days)" diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index d45322b71d..e58abbb6a2 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -2,45 +2,68 @@ set -e # Wait for MySQL to warm-up -while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do +while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do echo "Waiting for database to come up..." sleep 2 done -# Hard-code env vars to scripts due to cron not passing them to the perl script -sed -i "/^\$DBUSER/c\\\$DBUSER='${DBUSER}';" /usr/local/bin/imapsync_cron.pl -sed -i "/^\$DBPASS/c\\\$DBPASS='${DBPASS}';" /usr/local/bin/imapsync_cron.pl -sed -i "/^\$DBNAME/c\\\$DBNAME='${DBNAME}';" /usr/local/bin/imapsync_cron.pl -sed -i "s/LOG_LINES/${LOG_LINES}/g" /usr/local/bin/trim_logs.sh +until dig +short mailcow.email > /dev/null; do + echo "Waiting for DNS..." + sleep 1 +done + +# Do not attempt to write to slave +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning" +else + REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning" +fi + +until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do + echo "Waiting for Redis..." + sleep 2 +done + +${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null # Create missing directories -[[ ! -d /usr/local/etc/dovecot/sql/ ]] && mkdir -p /usr/local/etc/dovecot/sql/ +[[ ! -d /etc/dovecot/sql/ ]] && mkdir -p /etc/dovecot/sql/ +[[ ! -d /etc/dovecot/auth/ ]] && mkdir -p /etc/dovecot/auth/ +[[ ! -d /etc/dovecot/conf.d/ ]] && mkdir -p /etc/dovecot/conf.d/ [[ ! -d /var/vmail/_garbage ]] && mkdir -p /var/vmail/_garbage [[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve [[ ! -d /etc/sogo ]] && mkdir -p /etc/sogo +[[ ! -d /var/volatile ]] && mkdir -p /var/volatile # Set Dovecot sql config parameters, escape " in db password DBPASS=$(echo ${DBPASS} | sed 's/"/\\"/g') # Create quota dict for Dovecot -cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-quota.conf +if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + QUOTA_TABLE=quota2 +else + QUOTA_TABLE=quota2replica +fi +cat < /etc/dovecot/sql/dovecot-dict-sql-quota.conf +# Autogenerated by mailcow connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" map { pattern = priv/quota/storage - table = quota2 + table = ${QUOTA_TABLE} username_field = username value_field = bytes } map { pattern = priv/quota/messages - table = quota2 + table = ${QUOTA_TABLE} username_field = username value_field = messages } EOF # Create dict used for sieve pre and postfilters -cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf +cat < /etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf +# Autogenerated by mailcow connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" map { pattern = priv/sieve/name/\$script_name @@ -62,7 +85,8 @@ map { } EOF -cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf +cat < /etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf +# Autogenerated by mailcow connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" map { pattern = priv/sieve/name/\$script_name @@ -84,38 +108,139 @@ map { } EOF +echo -n ${ACL_ANYONE} > /etc/dovecot/acl_anyone -# Create userdb dict for Dovecot -cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-userdb.conf +if [[ "${SKIP_FTS}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then +echo -e "\e[33mDetecting SKIP_FTS=y... not enabling Flatcurve (FTS) then...\e[0m" +echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify listescape replication lazy_expunge' > /etc/dovecot/mail_plugins +echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify listescape replication mail_log' > /etc/dovecot/mail_plugins_imap +echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl notify listescape replication' > /etc/dovecot/mail_plugins_lmtp +else +echo -e "\e[32mDetecting SKIP_FTS=n... enabling Flatcurve (FTS)\e[0m" +echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify fts fts_flatcurve listescape replication lazy_expunge' > /etc/dovecot/mail_plugins +echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify mail_log fts fts_flatcurve listescape replication' > /etc/dovecot/mail_plugins_imap +echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl fts fts_flatcurve notify listescape replication' > /etc/dovecot/mail_plugins_lmtp +fi +chmod 644 /etc/dovecot/mail_plugins /etc/dovecot/mail_plugins_imap /etc/dovecot/mail_plugins_lmtp /templates/quarantine.tpl + +cat < /etc/dovecot/sql/dovecot-dict-sql-userdb.conf +# Autogenerated by mailcow driver = mysql connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" -user_query = SELECT CONCAT('maildir:/var/vmail/',maildir) AS mail, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND active = '1' -iterate_query = SELECT username FROM mailbox WHERE active='1'; +user_query = SELECT CONCAT(JSON_UNQUOTE(JSON_VALUE(attributes, '$.mailbox_format')), mailbox_path_prefix, '%d/%n/${MAILDIR_SUB}:VOLATILEDIR=/var/volatile/%u:INDEX=/var/vmail_index/%u') AS mail, '%s' AS protocol, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND (active = '1' OR active = '2') +iterate_query = SELECT username FROM mailbox WHERE active = '1' OR active = '2'; EOF -# Create pass dict for Dovecot -cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-passdb.conf -driver = mysql -connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS} ssl_verify_server_cert=no ssl_ca=/etc/ssl/certs/ca-certificates.crt" -default_pass_scheme = SSHA256 -password_query = SELECT password FROM mailbox WHERE username = '%u' AND domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1') AND JSON_EXTRACT(attributes, '$.force_pw_update') NOT LIKE '%%1%%' -EOF -# Create global sieve_after script -cat /usr/local/etc/dovecot/sieve_after > /var/vmail/sieve/global.sieve +# Migrate old sieve_after file +[[ -f /etc/dovecot/sieve_after ]] && mv /etc/dovecot/sieve_after /etc/dovecot/global_sieve_after +# Create global sieve scripts +cat /etc/dovecot/global_sieve_after > /var/vmail/sieve/global_sieve_after.sieve +cat /etc/dovecot/global_sieve_before > /var/vmail/sieve/global_sieve_before.sieve -# Check permissions of vmail directory. +# Check permissions of vmail/index/garbage directories. # Do not do this every start-up, it may take a very long time. So we use a stat check here. if [[ $(stat -c %U /var/vmail/) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail ; fi if [[ $(stat -c %U /var/vmail/_garbage) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail/_garbage ; fi +if [[ $(stat -c %U /var/vmail_index) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail_index ; fi + +# Cleanup random user maildirs +rm -rf /var/vmail/mailcow.local/* +# Cleanup PIDs +[[ -f /tmp/quarantine_notify.pid ]] && rm /tmp/quarantine_notify.pid + +# create sni configuration +echo "" > /etc/dovecot/sni.conf +for cert_dir in /etc/ssl/mail/*/ ; do + if [[ ! -f ${cert_dir}domains ]] || [[ ! -f ${cert_dir}cert.pem ]] || [[ ! -f ${cert_dir}key.pem ]]; then + continue + fi + domains=($(cat ${cert_dir}domains)) + for domain in ${domains[@]}; do + echo 'local_name '${domain}' {' >> /etc/dovecot/sni.conf; + echo ' ssl_cert = <'${cert_dir}'cert.pem' >> /etc/dovecot/sni.conf; + echo ' ssl_key = <'${cert_dir}'key.pem' >> /etc/dovecot/sni.conf; + echo '}' >> /etc/dovecot/sni.conf; + done +done # Create random master for SOGo sieve features RAND_USER=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1) RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 24 | head -n 1) -echo ${RAND_USER}@mailcow.local:$(doveadm pw -s SHA1 -p ${RAND_PASS}) > /usr/local/etc/dovecot/dovecot-master.passwd +if [[ ! -z ${DOVECOT_MASTER_USER} ]] && [[ ! -z ${DOVECOT_MASTER_PASS} ]]; then + RAND_USER=${DOVECOT_MASTER_USER} + RAND_PASS=${DOVECOT_MASTER_PASS} +fi +echo ${RAND_USER}@mailcow.local:{SHA1}$(echo -n ${RAND_PASS} | sha1sum | awk '{print $1}'):::::: > /etc/dovecot/dovecot-master.passwd +echo ${RAND_USER}@mailcow.local::5000:5000:::: > /etc/dovecot/dovecot-master.userdb echo ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/sieve.creds +if [[ -z ${MAILDIR_SUB} ]]; then + MAILDIR_SUB_SHARED= +else + MAILDIR_SUB_SHARED=/${MAILDIR_SUB} +fi +cat < /etc/dovecot/shared_namespace.conf +# Autogenerated by mailcow +namespace { + type = shared + separator = / + prefix = Shared/%%u/ + location = maildir:%%h${MAILDIR_SUB_SHARED}:INDEX=~${MAILDIR_SUB_SHARED}/Shared/%%u + subscriptions = no + list = children +} +EOF + + +cat < /etc/dovecot/sogo_trusted_ip.conf +# Autogenerated by mailcow +remote ${IPV4_NETWORK}.248 { + disable_plaintext_auth = no +} +EOF + +# Create random master Password for SOGo SSO +RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1) +echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass +cat < /etc/dovecot/sogo-sso.conf +# Autogenerated by mailcow +passdb { + driver = static + args = allow_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS} +} +EOF + +# Creating additional creds file for SOGo notify crons (calendars, etc) (dummy user, sso password) +echo -n ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/cron.creds + +if [[ "${MASTER}" =~ ^([nN][oO]|[nN])+$ ]]; then + # Toggling MASTER will result in a rebuild of containers, so the quota script will be recreated + cat <<'EOF' > /usr/local/bin/quota_notify.py +#!/usr/bin/python3 +import sys +sys.exit() +EOF +fi + +# Set mail_replica for HA setups +if [[ -n ${MAILCOW_REPLICA_IP} && -n ${DOVEADM_REPLICA_PORT} ]]; then + cat < /etc/dovecot/mail_replica.conf +# Autogenerated by mailcow +mail_replica = tcp:${MAILCOW_REPLICA_IP}:${DOVEADM_REPLICA_PORT} +EOF +fi + +# Setting variables for indexer-worker inside fts.conf automatically according to mailcow.conf settings +if [[ "${SKIP_FTS}" =~ ^([nN][oO]|[nN])+$ ]]; then + echo -e "\e[94mConfiguring FTS Settings...\e[0m" + echo -e "\e[94mSetting FTS Memory Limit (per process) to ${FTS_HEAP} MB\e[0m" + sed -i "s/vsz_limit\s*=\s*[0-9]*\s*MB*/vsz_limit=${FTS_HEAP} MB/" /etc/dovecot/conf.d/fts.conf + echo -e "\e[94mSetting FTS Process Limit to ${FTS_PROCS}\e[0m" + sed -i "s/process_limit\s*=\s*[0-9]*/process_limit=${FTS_PROCS}/" /etc/dovecot/conf.d/fts.conf +fi + # 401 is user dovecot if [[ ! -s /mail_crypt/ecprivkey.pem || ! -s /mail_crypt/ecpubkey.pem ]]; then openssl ecparam -name prime256v1 -genkey | openssl pkey -out /mail_crypt/ecprivkey.pem @@ -125,29 +250,100 @@ else chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem fi +# Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20) +if grep -qE 'ssl_min_protocol\s*=\s*(TLSv1|TLSv1\.1)\s*$' /etc/dovecot/dovecot.conf /etc/dovecot/extra.conf; then + sed -i '/\[openssl_init\]/a ssl_conf = ssl_configuration' /etc/ssl/openssl.cnf + + echo "[ssl_configuration]" >> /etc/ssl/openssl.cnf + echo "system_default = tls_system_default" >> /etc/ssl/openssl.cnf + echo "[tls_system_default]" >> /etc/ssl/openssl.cnf + echo "MinProtocol = TLSv1" >> /etc/ssl/openssl.cnf + echo "CipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf +fi + # Compile sieve scripts -sievec /var/vmail/sieve/global.sieve -sievec /usr/local/lib/dovecot/sieve/report-spam.sieve -sievec /usr/local/lib/dovecot/sieve/report-ham.sieve +sievec /var/vmail/sieve/global_sieve_before.sieve +sievec /var/vmail/sieve/global_sieve_after.sieve +sievec /usr/lib/dovecot/sieve/report-spam.sieve +sievec /usr/lib/dovecot/sieve/report-ham.sieve # Fix permissions -chown root:root /usr/local/etc/dovecot/sql/*.conf -chown root:dovecot /usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve* /usr/local/etc/dovecot/sql/dovecot-dict-sql-quota* -chmod 640 /usr/local/etc/dovecot/sql/*.conf +chown root:root /etc/dovecot/sql/*.conf +chown root:dovecot /etc/dovecot/sql/dovecot-dict-sql-sieve* /etc/dovecot/sql/dovecot-dict-sql-quota* /etc/dovecot/auth/passwd-verify.lua +chmod 640 /etc/dovecot/sql/*.conf /etc/dovecot/auth/passwd-verify.lua chown -R vmail:vmail /var/vmail/sieve +chown -R vmail:vmail /var/volatile +chown -R vmail:vmail /var/vmail_index +adduser vmail tty +chmod g+rw /dev/console +chown root:tty /dev/console +chmod +x /usr/lib/dovecot/sieve/rspamd-pipe-ham \ + /usr/lib/dovecot/sieve/rspamd-pipe-spam \ + /usr/local/bin/imapsync_runner.pl \ + /usr/local/bin/imapsync \ + /usr/local/bin/trim_logs.sh \ + /usr/local/bin/sa-rules.sh \ + /usr/local/bin/clean_q_aged.sh \ + /usr/local/bin/maildir_gc.sh \ + /usr/local/sbin/stop-supervisor.sh \ + /usr/local/bin/quota_notify.py \ + /usr/local/bin/repl_health.sh \ + /usr/local/bin/optimize-fts.sh -# Fix more than 1 hardlink issue -touch /etc/crontab /etc/cron.*/* +# Prepare environment file for cronjobs +printenv | sed 's/^\(.*\)$/export \1/g' > /source_env.sh # Clean old PID if any -[[ -f /usr/local/var/run/dovecot/master.pid ]] && rm /usr/local/var/run/dovecot/master.pid +[[ -f /var/run/dovecot/master.pid ]] && rm /var/run/dovecot/master.pid # Clean stopped imapsync jobs rm -f /tmp/imapsync_busy.lock -IMAPSYNC_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs) -[[ ! -z ${IMAPSYNC_TABLE} ]] && mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'" +IMAPSYNC_TABLE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs) +[[ ! -z ${IMAPSYNC_TABLE} ]] && mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'" + +# Envsubst maildir_gc +echo "$(envsubst < /usr/local/bin/maildir_gc.sh)" > /usr/local/bin/maildir_gc.sh + +# GUID generation +while [[ ${VERSIONS_OK} != 'OK' ]]; do + if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = \"${DBNAME}\" AND TABLE_NAME = 'versions'") ]]; then + VERSIONS_OK=OK + else + echo "Waiting for versions table to be created..." + sleep 3 + fi +done +PUBKEY_MCRYPT=$(doveconf -P 2> /dev/null | grep -i mail_crypt_global_public_key | cut -d '<' -f2) +if [ -f ${PUBKEY_MCRYPT} ]; then + GUID=$(cat <(echo ${MAILCOW_HOSTNAME}) /mail_crypt/ecpubkey.pem | sha256sum | cut -d ' ' -f1 | tr -cd "[a-fA-F0-9.:/] ") + if [ ${#GUID} -eq 64 ]; then + mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF +REPLACE INTO versions (application, version) VALUES ("GUID", "${GUID}"); +EOF + else + mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF +REPLACE INTO versions (application, version) VALUES ("GUID", "INVALID"); +EOF + fi +fi # Collect SA rules once now /usr/local/bin/sa-rules.sh +# Run hooks +for file in /hooks/*; do + if [ -x "${file}" ]; then + echo "Running hook ${file}" + "${file}" + fi +done + +# For some strange, unknown and stupid reason, Dovecot may run into a race condition, when this file is not touched before it is read by dovecot/auth +# May be related to something inside Docker, I seriously don't know +touch /etc/dovecot/auth/passwd-verify.lua + +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf +fi + exec "$@" diff --git a/data/Dockerfiles/dovecot/imapsync b/data/Dockerfiles/dovecot/imapsync index cfbd1ee822..de63d658ce 100755 --- a/data/Dockerfiles/dovecot/imapsync +++ b/data/Dockerfiles/dovecot/imapsync @@ -1,6 +1,6 @@ -#!/usr/bin/perl +#!/usr/bin/env perl -# $Id: imapsync,v 1.882 2018/05/05 21:10:43 gilles Exp gilles $ +# $Id: imapsync,v 2.178 2022/01/12 21:28:37 gilles Exp gilles $ # structure # pod documentation # use pragmas @@ -10,7 +10,7 @@ # default values # folder loop # subroutines -# sub usage { +# sub usage # pod documentation @@ -19,13 +19,13 @@ =head1 NAME -imapsync - Email IMAP tool for syncing, copying and migrating -email mailboxes between two imap servers, one way, +imapsync - Email IMAP tool for syncing, copying, migrating +and archiving email mailboxes between two imap servers, one way, and without duplicates. =head1 VERSION -This documentation refers to Imapsync $Revision: 1.882 $ +This documentation refers to Imapsync $Revision: 2.178 $ =head1 USAGE @@ -46,46 +46,91 @@ one another. Imapsync command is a tool allowing incremental and recursive imap transfers from one mailbox to another. +If you don't understand the previous sentence, it's normal, +it's pedantic computer-oriented jargon. -By default all folders are transferred, recursively, meaning -the whole folder hierarchy is taken, all messages in them, -and all messages flags (\Seen \Answered \Flagged etc.) +All folders are transferred, recursively, meaning +the whole folder hierarchy is taken, all messages in them, +and all message flags (\Seen \Answered \Flagged etc.) are synced too. -Imapsync reduces the amount of data transferred by not transferring -a given message if it resides already on both sides. +Imapsync reduces the amount of data transferred by not transferring +a given message if it already resides on the destination side. +Messages that are on the destination side but not on the +source side stay as they are. See the --delete2 +option to have strict sync and delete them. -Same specific headers and the transfer is done only once. -By default, the identification headers are -"Message-Id:" and "Received:" lines -but this choice can be changed with the --useheader option. +How imapsync know a message is already on both sides? +Same specific headers and the transfer is done only once. +By default, the identification headers are +"Message-Id:" and "Received:" lines +but this choice can be changed with the --useheader option, +most often a duplicate problem is solved by using +--useheader "Message-Id" -All flags are preserved, unread messages will stay unread, + +All flags are preserved, unread messages will stay unread, read ones will stay read, deleted will stay deleted. +In the IMAP protocol, a deleted message is not really deleted, +it is marked \Deleted and can be undelete. Real destruction +comes with the EXPUNGE or UIDEXPUNGE IMAP commands. -You can stop the transfer at any time and restart it later, +You can abort the transfer at any time and restart it later, imapsync works well with bad connections and interruptions, -by design. +by design. On a terminal hit Ctr-c twice within two seconds +to abort the program. Hit Ctr-c just once makes +imapsync reconnect to both imap servers. -You can decide to delete the messages from the source mailbox -after a successful transfer, it can be a good feature when migrating -live mailboxes since messages will be only on one side. +How do you know the sync is finished and well done? +When imapsync ends by itself it mentions it with lines like those: + + Exiting with return value 0 (EX_OK: successful termination) 0/50 nb_errors/max_errors PID 301 + Removing pidfile /tmp/imapsync.pid + Log file is LOG_imapsync/2020_11_17_15_59_22_761_test1_test2.txt ( to change it, use --logfile filepath ; or use --nolog to turn off logging ) + +If you don't have those lines it means that either the sync process is still +running (or eventually hanging indefinitely) or that it ended without +a whisper, a strong kill -9 on Linux for example. -In that case, use the --delete1 option. Option --delete1 implies -also option --expunge1 so all messages marked deleted on host1 -will be really deleted. +If you have those final lines then it means the sync process is properly +finished. It may have encountered problems though. -You can also decide to remove empty folders once all of their -messages have been transferred. Add --delete1emptyfolders to -obtain this behavior. +A good synchronization is mentioned by some lines above the last ones, especially +those three lines: -A different scenario is synchronizing a mailbox B from another mailbox A -in case you just want to keep a "live" copy of A in B. + The sync looks good, all 1745 identified messages in host1 are on host2. + There is no unidentified message on host1. + Detected 0 errors -For this, option --delete2 has to be used, it deletes messages in host2 -folder B that are not in host1 folder A. If you also need to destroy + +A classical scenario is synchronizing a mailbox B from another mailbox A +where you just want to keep a strict copy of A in B. Strict meaning +all messages in A will be in B but no more. + +For this, option --delete2 can be used, it deletes messages in the host2 +folder B that are not in the host1 folder A. If you also need to destroy host2 folders that are not in host1 then use --delete2folders. See also ---delete2foldersonly and --delete2foldersbutnot. +--delete2foldersonly and --delete2foldersbutnot to set up exceptions +on folders to destroy. INBOX will never be destroyed, it's a mandatory +folder in IMAP so imapsync doesn't even try to remove it. + +A different scenario is to delete the messages from the source mailbox +after a successful transfer, it can be a good feature when migrating +mailboxes since messages will be only on one side. The source account +will only have messages that are not on the destination yet, ie, +messages that arrived after a sync or that failed to be transferred. + +In that case, use the --delete1 option. Option --delete1 implies also +the option --expunge1 so all messages marked deleted on host1 will be +deleted. In IMAP protocol deleting a message does not delete it, +it marks it with the flag \Deleted, allowing an undelete. Expunging +a folder removes, definitively, all the messages marked as \Deleted +in this folder. + +You can also decide to remove empty folders once all of their messages +have been transferred. Add --delete1emptyfolders to obtain this +behavior. + Imapsync is not adequate for maintaining two active imap accounts in synchronization when the user plays independently on both sides. @@ -97,43 +142,52 @@ Michael R. Elkins) for a 2 ways synchronization. usage: imapsync [options] -Mandatory options are the six values, three on each sides, -needed to log in into the IMAP servers, ie, -a host, a username, and a password, two times. +The standard options are the six values forming the credentials. +Three values on each side are needed in order to login into the IMAP +servers. These six values are a hostname, a username, and a password, two times. -Conventions used: +Conventions used in the following descriptions of the options: str means string - int means integer + int means integer number + flo means float number reg means regular expression cmd means command - --dry : Makes imapsync doing nothing for real, just print what - would be done without --dry. + --dry : Makes imapsync doing nothing for real; it just print what + would be done without --dry. =head2 OPTIONS/credentials - --host1 str : Source or "from" imap server. Mandatory. - --port1 int : Port to connect on host1. - Optional since default port is 143 or 993 if --ssl1 - --user1 str : User to login on host1. Mandatory. - --password1 str : Password for the user1. + --host1 str : Source or "from" imap server. + --port1 int : Port to connect on host1. + Optional since default ports are the + well known ports imap/143 or imaps/993. + --user1 str : User to login on host1. + --password1 str : Password of user1. - --host2 str : "destination" imap server. Mandatory. - --port2 int : Port to connect on host2. - Optional since default port is 143 or 993 if --ssl2 - --user2 str : User to login on host2. Mandatory. - --password2 str : Password for the user2. + --host2 str : "destination" imap server. + --port2 int : Port to connect on host2. Optional + --user2 str : User to login on host2. + --password2 str : Password of user2. --showpasswords : Shows passwords on output instead of "MASKED". - Useful to restart a complete run by just reading the log, - or to debug passwords. It's not a secure practice. + Useful to restart a complete run by just reading + the command line used in the log, + or to debug passwords. + It's not a secure practice at all! --passfile1 str : Password file for the user1. It must contain the - password on the first line. This option avoids to show + password on the first line. This option avoids showing the password on the command line like --password1 does. - --passfile2 str : Password file for the user2. Contains the password. + --passfile2 str : Password file for the user2. + +You can also pass the passwords in the environment variables +IMAPSYNC_PASSWORD1 and IMAPSYNC_PASSWORD2. If you don't pass +the user1 password via --password1 nor --passfile1 nor $IMAPSYNC_PASSWORD1 +then imapsync will prompt to enter the password on the terminal. +Same thing for user2 password. =head2 OPTIONS/encryption @@ -158,11 +212,6 @@ Conventions used: --sslargs2 str : Pass any ssl parameter for host2 ssl or tls connection. See --sslargs1 - --timeout1 int : Connection timeout in seconds for host1. - Default is 120 and 0 means no timeout at all. - --timeout2 int : Connection timeout in seconds for host2. - Default is 120 and 0 means no timeout at all. - =head2 OPTIONS/authentication @@ -183,6 +232,28 @@ Conventions used: --domain1 str : Domain on host1 (NTLM authentication). --domain2 str : Domain on host2 (NTLM authentication). + --oauthaccesstoken1 str : The access token to authenticate with OAUTH2. + It will be combined with the --user1 value to form the + string to pass with XOAUTH2 authentication. + The password given by --password1 or --passfile1 + is ignored. + Instead of the access token itself, the value can be a + file containing the access token on the first line. + If the value is a file, imapsync reads its first line + and take this line as the access token. The advantage + of the file is that if the access token changes then + imapsync can read it again when it needs to reconnect + during a run. + + + --oauthaccesstoken2 str : same thing as --oauthaccesstoken1 + + --oauthdirect1 str : The direct string to pass with XOAUTH2 authentication. + The password given by --password1 or --passfile1 and + the user given by --user1 are ignored. + + --oauthdirect2 str : same thing as oauthdirect1 + =head2 OPTIONS/folders @@ -192,15 +263,16 @@ Conventions used: --folderrec str : Sync this folder recursively. --folderrec str : and this one, etc. - --folderfirst str : Sync this folder first. --folderfirst "Work" + --folderfirst str : Sync this folder first. Ex. --folderfirst "INBOX" --folderfirst str : then this one, etc. --folderlast str : Sync this folder last. --folderlast "[Gmail]/All Mail" --folderlast str : then this one, etc. --nomixfolders : Do not merge folders when host1 is case-sensitive while host2 is not (like Exchange). Only the first - similar folder is synced (ex: with Sent SENT sent - on host1 only Sent will be synced to host2). + similar folder is synced (example: with folders + "Sent", "SENT" and "sent" + on host1 only "Sent" will be synced to host2). --skipemptyfolders : Empty host1 folders are not created on host2. @@ -218,11 +290,26 @@ Conventions used: --f1f2 str1=str2 : Force folder str1 to be synced to str2, --f1f2 overrides --automap and --regextrans2. + Use several --f1f2 options to map several folders. + Option --f1f2 is a one to one only folder mapping, + str1 and str2 have to be full path folder names. + + --subfolder2 str : Syncs the whole host1 folders hierarchy under the + host2 folder named str. + It does it internally by adding three + --regextrans2 options before all others. + Add --debug to see what's really going on. + + --subfolder1 str : Syncs the host1 folders hierarchy which is under folder + str to the root hierarchy of host2. + It's the couterpart of a sync done by --subfolder2 + when doing it in the reverse order. + Backup/Restore scenario: + Use --subfolder2 str for a backup to the folder str + on host2. Then use --subfolder1 str for restoring + from the folder str, after inverting + host1/host2 user1/user2 values. - --subfolder2 str : Move whole host1 folders hierarchy under this - host2 folder str . - It does it by adding two --regextrans2 options before - all others. Add --debug to see what's really going on. --subscribed : Transfers subscribed folders. --subscribe : Subscribe to the folders transferred on the @@ -231,28 +318,37 @@ Conventions used: host2 even if they are not subscribed on host1. --prefix1 str : Remove prefix str to all destination folders, - usually INBOX. or INBOX/ or an empty string "". + usually "INBOX." or "INBOX/" or an empty string "". imapsync guesses the prefix if host1 imap server - does not have NAMESPACE capability. This option - should not be used, most of the time. + does not have NAMESPACE capability. So this option + should not be used most of the time. --prefix2 str : Add prefix to all host2 folders. See --prefix1 - --sep1 str : Host1 separator in case NAMESPACE is not supported. - --sep2 str : Host2 separator in case NAMESPACE is not supported. + + --sep1 str : Host1 separator. This option should not be used + most of the time. + Imapsync gets the separator from the server itself, + by using NAMESPACE, or it tries to guess it + from the folders listing (it counts + characters / . \\ \ in folder names and choose the + more frequent, or finally / if nothing is found. + --sep2 str : Host2 separator. See --sep1 --regextrans2 reg : Apply the whole regex to each destination folders. --regextrans2 reg : and this one. etc. When you play with the --regextrans2 option, first add also the safe options --dry --justfolders - Then, when happy, remove --dry, remove --justfolders. - Have in mind that --regextrans2 is applied after prefix - and separator inversion. For examples see + Then, when happy, remove --dry for a run, then + remove --justfolders for the next ones. + Have in mind that --regextrans2 is applied after + the automatic prefix and separator inversion. + For examples see: https://imapsync.lamiral.info/FAQ.d/FAQ.Folders_Mapping.txt =head2 OPTIONS/folders sizes --nofoldersizes : Do not calculate the size of each folder at the beginning of the sync. Default is to calculate them. - --nofoldersizesatend: Do not calculate the size of each folder at the + --nofoldersizesatend: Do not calculate the size of each folder at the end of the sync. Default is to calculate them. --justfoldersizes : Exit after having printed the initial folder sizes. @@ -265,9 +361,11 @@ Conventions used: Default is system specific, Unix is /tmp but /tmp is often too small and deleted at reboot. --tmpdir /var/tmp should be better. + --pidfile str : The file where imapsync pid is written, - it can be dirname/filename. - Default name is imapsync.pid in tmpdir. + it can be dirname/filename complete path. + The default name is imapsync.pid in tmpdir. + --pidfilelocking : Abort if pidfile already exists. Useful to avoid concurrent transfers on the same mailbox. @@ -278,27 +376,81 @@ Conventions used: --logfile str : Change the default log filename (can be dirname/filename). --logdir str : Change the default log directory. Default is LOG_imapsync/ +The default logfile name is for example + + LOG_imapsync/2019_12_22_23_57_59_532_user1_user2.txt + +where: + + 2019_12_22_23_57_59_532 is nearly the date of the start + YYYY_MM_DD_HH_MM_SS_mmm + year_month_day_hour_minute_seconde_millisecond + +and user1 user2 are the --user1 --user2 values. =head2 OPTIONS/messages --skipmess reg : Skips messages matching the regex. - Example: 'm/[\x80-ff]/' # to avoid 8bits messages. + Example: 'm/[\x80-\xff]/' # to avoid 8bits messages. --skipmess is applied before --regexmess --skipmess reg : or this one, etc. + --skipcrossduplicates : Avoid copying messages that are already copied + in another folder, good from Gmail to XYZ when + XYZ is not also Gmail. + Activated with --gmail1 unless --noskipcrossduplicates + + --debugcrossduplicates : Prints which messages (UIDs) are skipped with + --skipcrossduplicates and in what other folders + they are. + --pipemess cmd : Apply this cmd command to each message content before the copy. --pipemess cmd : and this one, etc. + With several --pipemess, the output of each cmd + command (STDOUT) is given to the input (STDIN) + of the next command. + For example, + --pipemess cmd1 --pipemess cmd2 --pipemess cmd3 + is like a Unix pipe: + "cat message | cmd1 | cmd2 | cmd3" --disarmreadreceipts : Disarms read receipts (host2 Exchange issue) --regexmess reg : Apply the whole regex to each message before transfer. - Example: 's/\000/ /g' # to replace null by space. + Example: 's/\000/ /g' # to replace null characters + by spaces. --regexmess reg : and this one, etc. + --truncmess int : truncates messages when their size exceed the int + value, specified in bytes. Good to sync too big + messages or to "suppress" attachments. + Have in mind that this way, messages become + uncoherent somehow. + +=head2 OPTIONS/labels + +Gmail present labels as folders in imap. Imapsync can accelerate the sync +by syncing X-GM-LABELS, it will avoid to transfer messages when they are +already on host2 in another folder. + + + --synclabels : Syncs also Gmail labels when a message is copied to host2. + Activated by default with --gmail1 --gmail2 unless + --nosynclabels is added. + + --resynclabels : Resyncs Gmail labels when a message is already on host2. + Activated by default with --gmail1 --gmail2 unless + --noresynclabels is added. + +For Gmail syncs, see also: +https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt =head2 OPTIONS/flags + If you encounter flag problems see also: + https://imapsync.lamiral.info/FAQ.d/FAQ.Flags.txt + --regexflag reg : Apply the whole regex to each flags list. Example: 's/"Junk"//g' # to remove "Junk" flag. --regexflag reg : then this one, etc. @@ -306,9 +458,12 @@ Conventions used: --resyncflags : Resync flags for already transferred messages. On by default. --noresyncflags : Do not resync flags for already transferred messages. - May be useful when a user has already started to play + May be useful when a user has already started to play with its host2 account. + --filterbuggyflags : Filter flags known to be buggy and generators of errors + "BAD Invalid system flag" or "NO APPEND Invalid flag list". + =head2 OPTIONS/deletions --delete1 : Deletes messages on host1 server after a successful @@ -317,57 +472,74 @@ Conventions used: \Deleted, then messages are really deleted with an EXPUNGE IMAP command. If expunging after each message slows down too much the sync then use - --noexpungeaftereach to speed up. + --noexpungeaftereach to speed up, expunging will then be + done only twice per folder, one at the beginning and + one at the end of a folder sync. + --expunge1 : Expunge messages on host1 just before syncing a folder. Expunge is done per folder. Expunge aims is to really delete messages marked deleted. An expunge is also done after each message copied - if option --delete1 is set. + if option --delete1 is set (unless --noexpungeaftereach). + --noexpunge1 : Do not expunge messages on host1. + --delete1emptyfolders : Deletes empty folders on host1, INBOX excepted. Useful with --delete1 since what remains on host1 is only what failed to be synced. - --delete2 : Delete messages in host2 that are not in - host1 server. Useful for backup or pre-sync. - --delete2duplicates : Delete messages in host2 that are duplicates. + --delete2 : Delete messages in the host2 account that are not in + the host1 account. Useful for backup or pre-sync. + --delete2 implies --uidexpunge2 + + --delete2duplicates : Deletes messages in host2 that are duplicates in host2. Works only without --useuid since duplicates are detected with an header part of each message. + NB: --delete2duplicates is far less violent than --delete2 + since it removes only duplicates. - --delete2folders : Delete folders in host2 that are not in host1 server. - For safety, first try it like this (it is safe): + --delete2folders : Delete folders in host2 that are not in host1. + For safety, first try it like this, it is safe: --delete2folders --dry --justfolders --nofoldersizes - --delete2foldersonly reg : Deleted only folders matching regex. + and see what folders will be deleted. + + --delete2foldersonly reg : Delete only folders matching the regex reg. Example: --delete2foldersonly "/^Junk$|^INBOX.Junk$/" - --delete2foldersbutnot reg : Do not delete folders matching regex. + This option activates --delete2folders + + --delete2foldersbutnot reg : Do not delete folders matching the regex rex. Example: --delete2foldersbutnot "/Tasks$|Contacts$|Foo$/" + This option activates --delete2folders - --expunge2 : Expunge messages on host2 after messages transfer. - --uidexpunge2 : uidexpunge messages on the host2 account - that are not on the host1 account, requires --delete2 + --noexpunge2 : Do not expunge messages on host2. + --nouidexpunge2 : Do not uidexpunge messages on the host2 account + that are not on the host1 account. =head2 OPTIONS/dates - --syncinternaldates : Sets the internal dates on host2 same as host1. + If you encounter problems with dates, see also: + https://imapsync.lamiral.info/FAQ.d/FAQ.Dates.txt + + --syncinternaldates : Sets the internal dates on host2 as the same as host1. Turned on by default. Internal date is the date - a message arrived on a host (mtime). - --idatefromheader : Sets the internal dates on host2 same as the - "Date:" headers. - If you encounter problems with dates see also - https://imapsync.lamiral.info/FAQ.d/FAQ.Dates.txt + a message arrived on a host (Unix mtime usually). + --idatefromheader : Sets the internal dates on host2 as same as the + ones in "Date:" headers. + =head2 OPTIONS/message selection --maxsize int : Skip messages larger (or equal) than int bytes --minsize int : Skip messages smaller (or equal) than int bytes - --maxage int : Skip messages older than int days. + + --maxage int : Skip messages older than int days. final stats (skipped) don't count older messages see also --minage --minage int : Skip messages newer than int days. final stats (skipped) don't count newer messages - You can do (+ are the messages selected): + You can do (+ zone are the messages selected): past|----maxage+++++++++++++++>now past|+++++++++++++++minage---->now past|----maxage+++++minage---->now (intersection) @@ -375,25 +547,38 @@ Conventions used: --search str : Selects only messages returned by this IMAP SEARCH command. Applied on both sides. - For a complete of what can be search see + For a complete set of what can be search see https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Selection.txt - + --search1 str : Same as --search but for selecting host1 messages only. --search2 str : Same as --search but for selecting host2 messages only. - --search CRIT equals --search1 CRIT --search2 CRIT + So --search CRIT equals --search1 CRIT --search2 CRIT + + --noabletosearch : Makes --minage and --maxage options use the internal + dates given by a FETCH imap command instead of the + "Date:" header. Internal date is the arrival date + in the mailbox. + --noabletosearch equals --noabletosearch1 --noabletosearch2 + + --noabletosearch1 : Like --noabletosearch but for host1 only. + --noabletosearch2 : Like --noabletosearch but for host2 only. --maxlinelength int : skip messages with a line length longer than int bytes. - RFC 2822 says it must be no more than 1000 bytes. + RFC 2822 says it must be no more than 1000 bytes but + real life servers and email clients do more. --useheader str : Use this header to compare messages on both sides. - Ex: Message-ID or Subject or Date. + Example: "Message-Id" or "Received" or "Date". --useheader str and this one, etc. - --usecache : Use cache to speed up the sync. + --syncduplicates : Sync also duplicates. Off by default. + + --usecache : Use cache to speed up next syncs. Off by default. --nousecache : Do not use cache. Caveat: --useuid --nousecache creates duplicates on multiple runs. - --useuid : Use UIDs instead of headers as a criterium to recognize + + --useuid : Use UIDs instead of headers as a criterion to recognize messages. Option --usecache is then implied unless --nousecache is used. @@ -401,9 +586,16 @@ Conventions used: =head2 OPTIONS/miscellaneous --syncacls : Synchronizes acls (Access Control Lists). + Acls in IMAP are not standardized, be careful + since one acl code on one side may signify something + else on the other one. --nosyncacls : Does not synchronize acls. This is the default. - Acls in IMAP are not standardized, be careful. + --addheader : When a message has no headers to be identified, + --addheader adds a "Message-Id" header, + like "Message-Id: 12345@imapsync", where 12345 + is the imap UID of the message on the host1 folder. + Useful to sync folders "Sent" or "Draft". =head2 OPTIONS/debugging @@ -422,51 +614,79 @@ Conventions used: --tests : Run local non-regression tests. Exit code 0 means all ok. --testslive : Run a live test with test1.lamiral.info imap server. Useful to check the basics. Needs internet connection. - --testslive6 : Run a live test with ks2ipv6.lamiral.info imap server. + --testslive6 : Run a live test with ks6ipv6.lamiral.info imap server. Useful to check the ipv6 connectivity. Needs internet. -=head2 OPTIONS/specific +=head2 OPTIONS/specific + + --gmail1 : sets --host1 to Gmail and other options. See FAQ.Gmail.txt + --gmail2 : sets --host2 to Gmail and other options. See FAQ.Gmail.txt + + --office1 : sets --host1 to Office365 and other options. See FAQ.Office365.txt + --office2 : sets --host2 to Office365 and other options. See FAQ.Office365.txt + + --exchange1 : sets options for Exchange. See FAQ.Exchange.txt + --exchange2 : sets options for Exchange. See FAQ.Exchange.txt + + --domino1 : sets options for Domino. See FAQ.Domino.txt + --domino2 : sets options for Domino. See FAQ.Domino.txt + - --gmail1 : sets --host1 to Gmail and options from FAQ.Gmail.txt - --gmail2 : sets --host2 to Gmail and options from FAQ.Gmail.txt - - --office1 : sets --host1 to Office365 options from FAQ.Exchange.txt - --office2 : sets --host2 to Office365 options from FAQ.Exchange.txt +=head2 OPTIONS/behavior - --exchange1 : sets options from FAQ.Exchange.txt, account1 part - --exchange2 : sets options from FAQ.Exchange.txt, account2 part - - --domino1 : sets options from FAQ.Domino.txt, account1 part - --domino2 : sets options from FAQ.Domino.txt, account2 part - - - + --timeout1 flo : Connection timeout in seconds for host1. + Default is 120 and 0 means no timeout at all. + --timeout2 flo : Connection timeout in seconds for host2. + Default is 120 and 0 means no timeout at all. + + Caveat, under CGI context, you may encounter a timeout + from the webserver, killing imapsync and the imap connexions. + See the document INSTALL.OnlineUI.txt and search + for "Timeout" for how to deal with this issue. + + --keepalive1 : https://metacpan.org/pod/Mail::IMAPClient#Keepalive + Some firewalls and network gears like to timeout connections + prematurely if the connection sits idle. + This option enables SO_KEEPALIVE on the host1 socket. + --keepalive1 is on by default since imapsync release 2.169 + Use --nokeepalive1 to disable it. -=head2 OPTIONS/behavior + --keepalive2 : Same as --keepalive2 but for host2. + Use --nokeepalive2 to disable it. + + --maxmessagespersecond flo : limits the average number of messages + transferred per second. - --maxmessagespersecond int : limits the number of messages transferred per second. - --maxbytespersecond int : limits the average transfer rate per second. - --maxbytesafter int : starts --maxbytespersecond limitation only after + --maxbytesafter int : starts --maxbytespersecond limitation only after --maxbytesafter amount of data transferred. - - --maxsleep int : do not sleep more than int seconds. + + --maxsleep flo : do not sleep more than int seconds. On by default, 2 seconds max, like --maxsleep 2 - --abort : terminates a previous call still running. + --abort : terminates a previous call still running. It uses the pidfile to know what process to abort. - --exitwhenover int : Stop syncing when total bytes transferred reached. + --exitwhenover int : Stop syncing and exits when int total bytes + transferred is reached. + + --version : Print only the software version. + --noreleasecheck : Do not check for any new imapsync release. + --releasecheck : Check for new imapsync release. + it's an http request to + http://imapsync.lamiral.info/prj/imapsync/VERSION + + --noid : Do not send/receive IMAP "ID" command to imap servers. - --version : Print only software version. - --noreleasecheck : Do not check for new imapsync release (a http request). - --releasecheck : Check for new imapsync release (a http request). - --noid : Do not send/receive ID command to imap servers. --justconnect : Just connect to both servers and print useful information. Need only --host1 and --host2 options. + Obsolete since "imapsync --host1 imaphost" alone + implies --justconnect + --justlogin : Just login to both host1 and host2 with users credentials, then exit. + --justfolders : Do only things about folders (ignore messages). --help : print this help. @@ -490,25 +710,27 @@ Conventions used: =head1 SECURITY -You can use --passfile1 instead of --password1 to give the -password since it is safer. With --password1 option, any user -on your host can see the password by using the 'ps auxwwww' -command. Using a variable (like $PASSWORD1) is also +You can use --passfile1 instead of --password1 to mention the +password since it is safer. With --password1 option, on Linux, +any user on your host can see the password by using the 'ps auxwwww' +command. Using a variable (like IMAPSYNC_PASSWORD1) is also dangerous because of the 'ps auxwwwwe' command. So, saving the password in a well protected file (600 or rw-------) is the best solution. -Imapsync activates ssl or tls encryption by default, if possible. -What detailed behavior is under this "if possible"? -Imapsync activates ssl if the well known port imaps port (993) is open -on the imap servers. If the imaps port is closed then it open a -normal (clear) connection on port 143 but it looks for TLS support -in the CAPABILITY list of the servers. If TLS is supported -then imapsync goes to encryption. +Imapsync activates ssl or tls encryption by default, if possible. + +What detailed behavior is under this "if possible"? + +Imapsync activates ssl if the well known port imaps port (993) is open +on the imap servers. If the imaps port is closed then it open a +normal (clear) connection on port 143 but it looks for TLS support +in the CAPABILITY list of the servers. If TLS is supported +then imapsync goes to encryption with STARTTLS. -If the automatic ssl/tls detection fails then imapsync will -not protect against sniffing activities on the -network, especially for passwords. +If the automatic ssl and the tls detections fail then imapsync will +not protect against sniffing activities on the network, especially +for passwords. If you want to force ssl or tls just use --ssl1 --ssl2 or --tls1 --tls2 @@ -518,14 +740,49 @@ or at https://imapsync.lamiral.info/FAQ.d/FAQ.Security.txt =head1 EXIT STATUS Imapsync will exit with a 0 status (return code) if everything went good. -Otherwise, it exits with a non-zero status. - +Otherwise, it exits with a non-zero status. That's classical Unix behavior. +Here is the list of the exit code values (an integer between 0 and 255). +In Bourne Shells, this exit code value can be retrieved within the variable +value "$?" if you read it just after the imapsync call. + +The names reflect their meaning: + +=for comment +egrep '^Readonly my.*\$EX' imapsync | egrep -o 'EX.*' | sed 's_^_ _' + + EX_OK => 0 ; #/* successful termination */ + EX_USAGE => 64 ; #/* command line usage error */ + EX_NOINPUT => 66 ; #/* cannot open input */ + EX_UNAVAILABLE => 69 ; #/* service unavailable */ + EX_SOFTWARE => 70 ; #/* internal software error */ + EXIT_CATCH_ALL => 1 ; # Any other error + EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num + EXIT_BY_FILE => 7 ; + EXIT_PID_FILE_ERROR => 8 ; + EXIT_CONNECTION_FAILURE => 10 ; + EXIT_TLS_FAILURE => 12 ; + EXIT_AUTHENTICATION_FAILURE => 16 ; + EXIT_SUBFOLDER1_NO_EXISTS => 21 ; + EXIT_WITH_ERRORS => 111 ; + EXIT_WITH_ERRORS_MAX => 112 ; + EXIT_OVERQUOTA => 113 ; + EXIT_ERR_APPEND => 114 ; + EXIT_ERR_FETCH => 115 ; + EXIT_ERR_CREATE => 116 ; + EXIT_ERR_SELECT => 117 ; + EXIT_TRANSFER_EXCEEDED => 118 ; + EXIT_ERR_APPEND_VIRUS => 119 ; + EXIT_TESTS_FAILED => 254 ; # Like Test::More API + EXIT_CONNECTION_FAILURE_HOST1 => 101 ; + EXIT_CONNECTION_FAILURE_HOST2 => 102 ; + EXIT_AUTHENTICATION_FAILURE_USER1 => 161 ; + EXIT_AUTHENTICATION_FAILURE_USER2 => 162 ; =head1 LICENSE AND COPYRIGHT Imapsync is free, open, public but not always gratis software -cover by the NOLIMIT Public License. +cover by the NOLIMIT Public License, now called NLPL. See the LICENSE file included in the distribution or just read this simple sentence as it IS the licence text: @@ -535,20 +792,21 @@ In case it is not long enough, I repeat: "No limits to do anything with this work and this license." -https://imapsync.lamiral.info/LICENSE +Look at https://imapsync.lamiral.info/LICENSE =head1 AUTHOR Gilles LAMIRAL -Feedback good or bad is very often welcome. +Good feedback is always welcome. +Bad feedback is very often welcome. Gilles LAMIRAL earns his living by writing, installing, -configuring and teaching free, open and often gratis +configuring and sometimes teaching free, open and often gratis software. Imapsync used to be "always gratis" but now it is -only "often gratis" because imapsync is sold by its author, -a good way to maintain and support free open public -software over decades. +only "often gratis" because imapsync is sold by its author, +your servitor, a good way to maintain and support free open public +software tools over decades. =head1 BUGS AND LIMITATIONS @@ -560,19 +818,6 @@ See https://imapsync.lamiral.info/S/imapservers.shtml =head1 HUGE MIGRATION -Pay special attention to options ---subscribed ---subscribe ---delete1 ---delete1emptyfolders ---delete2 ---delete2folders ---maxage ---minage ---maxsize ---useuid ---usecache - If you have many mailboxes to migrate think about a little shell program. Write a file called file.txt (for example) containing users and passwords. @@ -608,14 +853,14 @@ https://imapsync.lamiral.info/examples/ =head1 INSTALL - Imapsync works under any Unix with perl. - + Imapsync works under any Unix with Perl. + Imapsync works under most Windows (2000, XP, Vista, Seven, Eight, Ten - and all Server releases 2000, 2003, 2008 and R2, 2012 and R2) + and all Server releases 2000, 2003, 2008 and R2, 2012 and R2, 2016) as a standalone binary software called imapsync.exe, - usually launched from a batch file in order to avoid always typing - the options. - + usually launched from a batch file in order to avoid always typing + the options. There is also a 32bit binary called imapsync_32bit.exe + Imapsync works under OS X as a standalone binary software called imapsync_bin_Darwin @@ -652,52 +897,77 @@ Feel free to hack imapsync as the NOLIMIT license permits it. See also https://imapsync.lamiral.info/S/external.shtml for a better up to date list. - imap_tools : https://github.com/andrewnimmo/rick-sanders-imap-tools - offlineimap : https://github.com/nicolas33/offlineimap - Doveadm-Sync : http://wiki2.dovecot.org/Tools/Doveadm/Sync - ( Dovecot sync tool ) - mbsync : http://isync.sourceforge.net/ - mailsync : http://mailsync.sourceforge.net/ - mailutil : http://www.washington.edu/imap/ - part of the UW IMAP tookit. - imaprepl : http://www.bl0rg.net/software/ - http://freecode.com/projects/imap-repl/ - imapcopy : http://www.ardiehl.de/imapcopy/ - migrationtool : http://sourceforge.net/projects/migrationtool/ - imapmigrate : http://sourceforge.net/projects/cyrus-utils/ - wonko_imapsync: http://wonko.com/article/554 - see also file W/tools/wonko_ruby_imapsync - exchange-away : http://exchange-away.sourceforge.net/ - pop2imap : http://www.linux-france.org/prj/pop2imap/ - - -Feedback (good or bad) will often be welcome. +List verified on Friday July 1, 2021. + + imapsync: https://github.com/imapsync/imapsync (this is an imapsync copy, sometimes delayed, with --noreleasecheck by default since release 1.592, 2014/05/22) + imap_tools: https://web.archive.org/web/20161228145952/http://www.athensfbc.com/imap_tools/. The imap_tools code is now at https://github.com/andrewnimmo/rick-sanders-imap-tools + imaputils: https://github.com/mtsatsenko/imaputils (very old imap_tools fork) + Doveadm-Sync: https://wiki2.dovecot.org/Tools/Doveadm/Sync ( Dovecot sync tool ) + davmail: http://davmail.sourceforge.net/ + offlineimap: http://offlineimap.org/ + fdm: https://github.com/nicm/fdm + mbsync: http://isync.sourceforge.net/ + mailsync: http://mailsync.sourceforge.net/ + mailutil: https://www.washington.edu/imap/ part of the UW IMAP toolkit. (well, seems abandoned now) + imaprepl: https://bl0rg.net/software/ http://freecode.com/projects/imap-repl/ + imapcopy (Pascal): http://www.ardiehl.de/imapcopy/ + imapcopy (Java): https://code.google.com/archive/p/imapcopy/ + imapsize: http://www.broobles.com/imapsize/ + migrationtool: http://sourceforge.net/projects/migrationtool/ + imapmigrate: http://sourceforge.net/projects/cyrus-utils/ + larch: https://github.com/rgrove/larch (derived from wonko_imapsync, good at Gmail) + wonko_imapsync: http://wonko.com/article/554 (superseded by larch) + pop2imap: http://www.linux-france.org/prj/pop2imap/ (I wrote that too) + exchange-away: http://exchange-away.sourceforge.net/ + SyncBackPro: http://www.2brightsparks.com/syncback/sbpro.html + ImapSyncClient: https://github.com/ridaamirini/ImapSyncClient + MailStore: https://www.mailstore.com/en/products/mailstore-home/ + mnIMAPSync: https://github.com/manusa/mnIMAPSync + imap-upload: http://imap-upload.sourceforge.net/ (A tool for uploading a local mbox file to IMAP4 server) + imapbackup: https://github.com/rcarmo/imapbackup (A Python script for incremental backups of IMAP mailboxes) + BitRecover email-backup 99 USD, 299 USD https://www.bitrecover.com/email-backup/. + ImportExportTools: https://addons.thunderbird.net/en-us/thunderbird/addon/importexporttools/ ImportExportTools for Mozilla Thunderbird by Paolo Kaosmos. ImportExportTools does not do IMAP. + rximapmail: https://sourceforge.net/projects/rximapmail/ + CodeTwo: https://www.codetwo.com/ but CodeTwo does imap source to Office365 only. =head1 HISTORY -I wrote imapsync because an enterprise (basystemes) paid me to install -a new imap server without losing huge old mailboxes located in a far -away remote imap server, accessible by a low-bandwidth often broken link. -The tool imapcp (written in python) could not help me because I had to verify -every mailbox was well transferred, and then delete it after a good -transfer. Imapsync started its life as a patch of the copy_folder.pl +I initially wrote imapsync in July 2001 because an enterprise, +called BaSystemes, paid me to install a new imap server +without losing huge old mailboxes located in a far +away remote imap server, accessible by an +often broken low-bandwidth ISDN link. + +I had to verify every mailbox was well transferred, all folders, all messages, +without wasting bandwidth or creating duplicates upon resyncs. The imapsync +design was made with the beautiful rsync command in mind. + +Imapsync started its life as a patch of the copy_folder.pl script. The script copy_folder.pl comes from the Mail-IMAPClient-2.1.3 perl -module tarball source (more precisely in the examples/ directory of the -Mail-IMAPClient tarball). So many happened since then that I wonder -if it remains any lines of the original copy_folder.pl in imapsync source code. +module tarball source (more precisely in the examples/ directory of the +Mail-IMAPClient tarball). + +So many changes happened since then that I wonder +if it remains any lines of the original +copy_folder.pl in imapsync source code. =cut # use pragmas +# use strict ; use warnings ; use Carp ; +use Cwd ; +use Compress::Zlib ; use Data::Dumper ; use Digest::HMAC_SHA1 qw( hmac_sha1 hmac_sha1_hex ) ; use Digest::MD5 qw( md5 md5_hex md5_base64 ) ; +use Encode ; +use Encode::IMAPUTF7 ; use English qw( -no_match_vars ) ; use Errno qw(EAGAIN EPIPE ECONNRESET) ; use Fcntl ; @@ -714,20 +984,24 @@ use IO::Socket::INET6 ; use IO::Socket::SSL ; use IO::Tee ; use IPC::Open3 'open3' ; +#use locale ; use Mail::IMAPClient 3.30 ; use MIME::Base64 ; use Pod::Usage qw(pod2usage) ; -use POSIX qw(uname SIGALRM) ; +use POSIX qw( uname SIGALRM :sys_wait_h ) ; use Sys::Hostname ; use Term::ReadKey ; use Test::More ; use Time::HiRes qw( time sleep ) ; use Time::Local ; use Unicode::String ; -use Cwd ; use Readonly ; use Sys::MemInfo ; use Regexp::Common ; +use Text::ParseWords ; # for quotewords() +use File::Tail ; + + local $OUTPUT_AUTOFLUSH = 1 ; @@ -735,6 +1009,10 @@ local $OUTPUT_AUTOFLUSH = 1 ; # Let us do like sysexits.h # /usr/include/sysexits.h +# and https://www.tldp.org/LDP/abs/html/exitcodes.html + +# Should avoid 2 126 127 128..128+64=192 255 +# Should use 0 1 3..125 193..254 Readonly my $EX_OK => 0 ; #/* successful termination */ Readonly my $EX_USAGE => 64 ; #/* command line usage error */ @@ -754,21 +1032,191 @@ Readonly my $EX_SOFTWARE => 70 ; #/* internal software error */ #Readonly my $EX_CONFIG => 78 ; #/* configuration error */ # Mine -Readonly my $EXIT_BY_SIGNAL => 6 ; +Readonly my $EXIT_CATCH_ALL => 1 ; # Any other error +Readonly my $EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num +Readonly my $EXIT_BY_FILE => 7 ; Readonly my $EXIT_PID_FILE_ERROR => 8 ; - +Readonly my $EXIT_CONNECTION_FAILURE => 10 ; +Readonly my $EXIT_TLS_FAILURE => 12 ; +Readonly my $EXIT_AUTHENTICATION_FAILURE => 16 ; +Readonly my $EXIT_SUBFOLDER1_NO_EXISTS => 21 ; Readonly my $EXIT_WITH_ERRORS => 111 ; Readonly my $EXIT_WITH_ERRORS_MAX => 112 ; -Readonly my $EXIT_UNKNOWN => 126 ; +Readonly my $EXIT_OVERQUOTA => 113 ; +Readonly my $EXIT_ERR_APPEND => 114 ; +Readonly my $EXIT_ERR_FETCH => 115 ; +Readonly my $EXIT_ERR_CREATE => 116 ; +Readonly my $EXIT_ERR_SELECT => 117 ; +Readonly my $EXIT_TRANSFER_EXCEEDED => 118 ; + +Readonly my $EXIT_ERR_APPEND_VIRUS => 119 ; +Readonly my $EXIT_ERR_FLAGS => 120 ; Readonly my $EXIT_TESTS_FAILED => 254 ; # Like Test::More API +Readonly my $EXIT_CONNECTION_FAILURE_HOST1 => 101 ; +Readonly my $EXIT_CONNECTION_FAILURE_HOST2 => 102 ; +Readonly my $EXIT_AUTHENTICATION_FAILURE_USER1 => 161 ; +Readonly my $EXIT_AUTHENTICATION_FAILURE_USER2 => 162 ; + + +Readonly my %EXIT_TXT => ( + $EX_OK => 'EX_OK: successful termination', + $EX_USAGE => 'EX_USAGE: command line usage error', + $EX_NOINPUT => 'EX_NOINPUT: cannot open input', + $EX_UNAVAILABLE => 'EX_UNAVAILABLE: service unavailable', + $EX_SOFTWARE => 'EX_SOFTWARE: internal software error', + + $EXIT_CATCH_ALL => 'EXIT_CATCH_ALL', + $EXIT_BY_SIGNAL => 'EXIT_BY_SIGNAL', + $EXIT_BY_FILE => 'EXIT_BY_FILE', + $EXIT_PID_FILE_ERROR => 'EXIT_PID_FILE_ERROR' , + $EXIT_CONNECTION_FAILURE => 'EXIT_CONNECTION_FAILURE', + $EXIT_TLS_FAILURE => 'EXIT_TLS_FAILURE', + $EXIT_AUTHENTICATION_FAILURE => 'EXIT_AUTHENTICATION_FAILURE', + $EXIT_SUBFOLDER1_NO_EXISTS => 'EXIT_SUBFOLDER1_NO_EXISTS', + $EXIT_WITH_ERRORS => 'EXIT_WITH_ERRORS', + $EXIT_WITH_ERRORS_MAX => 'EXIT_WITH_ERRORS_MAX', + $EXIT_OVERQUOTA => 'EXIT_OVERQUOTA', + $EXIT_ERR_APPEND => 'EXIT_ERR_APPEND', + $EXIT_ERR_APPEND_VIRUS => 'EXIT_ERR_APPEND_VIRUS', + $EXIT_ERR_FETCH => 'EXIT_ERR_FETCH', + $EXIT_ERR_FLAGS => 'EXIT_ERR_FLAGS', + $EXIT_ERR_CREATE => 'EXIT_ERR_CREATE', + $EXIT_ERR_SELECT => 'EXIT_ERR_SELECT', + $EXIT_TESTS_FAILED => 'EXIT_TESTS_FAILED', + $EXIT_TRANSFER_EXCEEDED => 'EXIT_TRANSFER_EXCEEDED', + $EXIT_CONNECTION_FAILURE_HOST1 => 'EXIT_CONNECTION_FAILURE_HOST1', + $EXIT_CONNECTION_FAILURE_HOST2 => 'EXIT_CONNECTION_FAILURE_HOST2', + $EXIT_AUTHENTICATION_FAILURE_USER1 => 'EXIT_AUTHENTICATION_FAILURE_USER1', + $EXIT_AUTHENTICATION_FAILURE_USER2 => 'EXIT_AUTHENTICATION_FAILURE_USER2', +) ; + + +Readonly my %EXIT_VALUE_OF_ERR_TYPE => ( + ERR_APPEND_SIZE => $EXIT_ERR_APPEND, + ERR_OVERQUOTA => $EXIT_OVERQUOTA, + ERR_APPEND => $EXIT_ERR_APPEND, + ERR_APPEND_VIRUS => $EXIT_ERR_APPEND_VIRUS, + ERR_CREATE => $EXIT_ERR_CREATE, + ERR_SELECT => $EXIT_ERR_SELECT, + ERR_Host1_FETCH => $EXIT_ERR_FETCH, + ERR_FLAGS => $EXIT_ERR_FLAGS, + ERR_UNCLASSIFIED => $EXIT_WITH_ERRORS, + ERR_NOTHING_REPORTED => $EXIT_WITH_ERRORS, + ERR_TRANSFER_EXCEEDED => $EXIT_TRANSFER_EXCEEDED, + ERR_CONNECTION_FAILURE_HOST1 => $EXIT_CONNECTION_FAILURE_HOST1, + ERR_CONNECTION_FAILURE_HOST2 => $EXIT_CONNECTION_FAILURE_HOST2, + ERR_AUTHENTICATION_FAILURE_USER1 => $EXIT_AUTHENTICATION_FAILURE_USER1, + ERR_AUTHENTICATION_FAILURE_USER2 => $EXIT_AUTHENTICATION_FAILURE_USER2, + ERR_EXIT_TLS_FAILURE => $EXIT_TLS_FAILURE, +) ; + + + +Readonly my %COMMENT_OF_ERR_TYPE => ( + ERR_APPEND_SIZE => \&comment_err_append_size, + ERR_OVERQUOTA => \&comment_err_overquota, + ERR_APPEND => \&comment_err_blank, + ERR_APPEND_VIRUS => \&comment_err_blank, + ERR_CREATE => \&comment_err_blank, + ERR_SELECT => \&comment_err_blank, + ERR_Host1_FETCH => \&comment_err_blank, + ERR_FLAGS => \&comment_err_flags, + ERR_UNCLASSIFIED => \&comment_err_blank, + ERR_NOTHING_REPORTED => \&comment_err_blank, + ERR_TRANSFER_EXCEEDED => \&comment_err_transfer_exceeded, + ERR_CONNECTION_FAILURE_HOST1 => \&comment_err_connection_failure_host1, + ERR_CONNECTION_FAILURE_HOST2 => \&comment_err_connection_failure_host2, + ERR_AUTHENTICATION_FAILURE_USER1 => \&comment_err_authentication_failure_host1, + ERR_AUTHENTICATION_FAILURE_USER2 => \&comment_err_authentication_failure_host2, + ERR_EXIT_TLS_FAILURE => \&comment_err_blank, +) ; + + +sub comment_err_blank +{ + return '' ; +} + + +sub comment_err_append_size +{ + my $mysync = shift @ARG ; + + my $comment = "The destination server refuses too big messages. Use --truncmess option. Read https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Too_Big.txt" ; + return $comment ; +} + + +sub comment_err_authentication_failure_host1 +{ + my $mysync = shift @ARG ; + + my $comment = "Check the credentials for $mysync->{ user1 }." ; + return $comment ; +} + +sub comment_err_authentication_failure_host2 +{ + my $mysync = shift @ARG ; + + my $comment = "Check the credentials for $mysync->{ user2 }." ; + return $comment ; +} + + +sub comment_err_connection_failure_host1 +{ + my $mysync = shift @ARG ; + + my $comment = "Check that host1 $mysync->{ host1 } on port $mysync->{ port1 } is the right IMAP server to be contacted for your mailbox." ; + return $comment ; +} + +sub comment_err_connection_failure_host2 +{ + my $mysync = shift @ARG ; + + my $comment = "Check that host1 $mysync->{ host2 } on port $mysync->{ port2 } is the right IMAP server to be contacted for your mailbox." ; + return $comment ; +} + +sub comment_err_overquota +{ + my $mysync = shift @ARG ; + + my $comment = 'The destination mailbox is 100% full, get free space on it and then resume the sync.' ; + return $comment ; +} + + +sub comment_err_transfer_exceeded +{ + my $mysync = shift @ARG ; + + my $size_limit_human = bytes_display_string_dec( $mysync->{ exitwhenover } ) ; + my $comment = "The maximum transfer size for a single sync is reached ( over $size_limit_human ). Relaunch the sync to sync more." ; + return $comment ; +} + +sub comment_err_flags +{ + my $mysync = shift @ARG ; + + my $comment = 'Many STORE errors with FLAGS. Retry with the option --noresyncflags' ; + return $comment ; +} + + + Readonly my $DEFAULT_LOGDIR => 'LOG_imapsync' ; Readonly my $ERRORS_MAX => 50 ; # exit after 50 errors. Readonly my $ERRORS_MAX_CGI => 20 ; # exit after 20 errors in CGI context. + Readonly my $INTERVAL_TO_EXIT => 2 ; # interval max to exit instead of reconnect Readonly my $SPLIT => 100 ; # By default, 100 at a time, not more. @@ -788,7 +1236,7 @@ Readonly my $RELEASE_NUMBER_EXAMPLE_2 => 42.4242 ; Readonly my $TCP_PING_TIMEOUT => 5 ; Readonly my $DEFAULT_TIMEOUT => 120 ; Readonly my $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND => 3 ; -Readonly my $DEFAULT_UIDNEXT => 999_999 ; + Readonly my $DEFAULT_BUFFER_SIZE => 4096 ; Readonly my $MAX_SLEEP => 2 ; # 2 seconds max for limiting too long sleeps from --maxbytespersecond and --maxmessagespersecond @@ -804,6 +1252,8 @@ Readonly my $NUMBER_42 => 42 ; Readonly my $NUMBER_100 => 100 ; Readonly my $NUMBER_200 => 200 ; Readonly my $NUMBER_300 => 300 ; +Readonly my $NUMBER_123456 => 123_456 ; +Readonly my $NUMBER_654321 => 654_321 ; Readonly my $NUMBER_20_000 => 20_000 ; @@ -828,138 +1278,152 @@ Readonly my $UMASK_PARANO => '0077' ; Readonly my $STR_use_releasecheck => q{Check if a new imapsync release is available by adding --releasecheck} ; +Readonly my $GMAIL_MAXSIZE => 35_651_584 ; + +Readonly my $FORCE => 1 ; + +# if ( 'MSWin32' eq $OSNAME ) +# if ( 'darwin' eq $OSNAME ) +# if ( 'linux' eq $OSNAME ) + + # global variables -# Currently working to finish with only $sync +# Currently working to finish with only $sync, $acc1, $acc2 # Not finished yet... my( - $sync, - $debug, $debugimap, $debugimap1, $debugimap2, $debugcontent, $debugflags, + $sync, $acc1, $acc2, + $debugflags, $debuglist, $debugdev, $debugmaxlinelength, $debugcgi, - $domain1, $domain2, - $passfile1, $passfile2, @include, @exclude, @folderrec, @folderfirst, @folderlast, + @h1_folders_all, %h1_folders_all, + @h2_folders_all, %h2_folders_all, + @h2_folders_from_1_wanted, %h2_folders_from_1_all, + %requested_folder, + $h1_folders_wanted_nb, $h1_folders_wanted_ct, + @h2_folders_not_in_1, + %h1_subscribed_folder, %h2_subscribed_folder, + %h2_folders_from_1_wanted, + %h2_folders_from_1_several, $prefix1, $prefix2, - $subfolder2, - @regextrans2, @regexmess, @regexflag, @skipmess, @pipemess, $pipemesscheck, - $flagscase, $filterflags, $syncflagsaftercopy, - $sep1, $sep2, + @regexmess, @skipmess, @pipemess, $pipemesscheck, + $syncflagsaftercopy, $syncinternaldates, $idatefromheader, - $syncacls, - $fastio1, $fastio2, - $maxsize, $minsize, $maxage, $minage, - $exitwhenover, - $search, $search1, $search2, - $skipheader, @useheader, - $skipsize, $allowsizemismatch, $foldersizes, $foldersizesatend, $buffersize, - $delete1, $delete2, $delete2duplicates, - $expunge1, $expunge2, $uidexpunge2, - $justfoldersizes, + $minsize, $maxage, $minage, + $search, + @useheader, %useheader, + $skipsize, $allowsizemismatch, $buffersize, $authmd5, $authmd51, $authmd52, $subscribed, $subscribe, $subscribeall, $help, - $justfolders, $justbanner, - $fast, - $total_bytes_skipped, - $total_bytes_error, - $nb_msg_skipped, $nb_msg_skipped_dry_mode, - $h1_nb_msg_duplicate, - $h2_nb_msg_duplicate, - $h1_nb_msg_noheader, $h2_nb_msg_noheader, - $h1_total_bytes_duplicate, - $h2_total_bytes_duplicate, - $h1_nb_msg_deleted, - $h2_nb_msg_deleted, - $h1_bytes_processed, - $h1_nb_msg_processed, - $h1_nb_msg_start, $h1_bytes_start, - $h2_nb_msg_start, $h2_bytes_start, $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end, - - $timeout, $timestart_int, - $timebefore, $uid1, $uid2, - $authuser1, $authuser2, - $proxyauth1, $proxyauth2, - $authmech1, $authmech2, $split1, $split2, - $reconnectretry1, $reconnectretry2, - $max_msg_size_in_bytes, $modulesversion, $delete2folders, $delete2foldersonly, $delete2foldersbutnot, $usecache, $debugcache, $cacheaftercopy, $wholeheaderifneeded, %h1_msgs_copy_by_uid, $useuid, $h2_uidguess, - %h1, %h2, $checkmessageexists, - $expungeaftereach, - $fixslash2, $messageidnodomain, $fixInboxINBOX, $maxlinelength, $maxlinelengthcmd, $minmaxlinelength, - $uidnext_default, $fixcolonbug, $create_folder_old, $skipcrossduplicates, $debugcrossduplicates, $disarmreadreceipts, - $mixfolders, $skipemptyfolders, + $mixfolders, $fetch_hash_set, -); + $cgidir, + %month_abrev, + $SSL_VERIFY_POLICY, +) ; + +single_sync( $sync, $acc1, $acc2 ); + +sub single_sync +{ + # main program # global variables initialization -# Currently removing all global variables except $sync -# passing each of them under $sync->{variable_name} +# I'm currently removing all global variables except $sync $acc1 $acc2 +# passing each of them under +# $sync->{variable_name} +# or $acc1->{variable_name} +# or $acc1->{variable_name} + +# +$acc1 = {} ; +$acc2 = {} ; +$sync->{ acc1 } = $acc1 ; +$sync->{ acc2 } = $acc2 ; + +$acc1->{ Side } = 'Host1' ; +$acc2->{ Side } = 'Host2' ; +$acc1->{ N } = '1' ; +$acc2->{ N } = '2' ; $sync->{timestart} = time ; # Is a float because of use Time::HiRres -$sync->{rcs} = q{$Id: imapsync,v 1.882 2018/05/05 21:10:43 gilles Exp gilles $} ; +$sync->{rcs} = q{$Id: imapsync,v 2.178 2022/01/12 21:28:37 gilles Exp gilles $} ; $sync->{ memory_consumption_at_start } = memory_consumption( ) || 0 ; + my @loadavg = loadavg( ) ; -$sync->{cpu_number} = cpu_number( ) ; -$sync->{loaddelay} = load_and_delay( $sync->{cpu_number}, @loadavg ) ; -$sync->{loadavg} = join( q{ }, $loadavg[ 0 ] ) +$sync->{ cpu_number } = cpu_number( ) ; +$sync->{ loaddelay } = load_and_delay( $sync->{ cpu_number }, @loadavg ) ; +$sync->{ loaddelay } = 0 ; + +$sync->{ loadavg } = join( q{ }, $loadavg[ 0 ] ) . " on $sync->{cpu_number} cores and " . ram_memory_info( ) ; -$sync->{total_bytes_transferred} = 0 ; -$total_bytes_skipped = 0; -$total_bytes_error = 0; -$sync->{nb_msg_transferred} = 0; -$nb_msg_skipped = $nb_msg_skipped_dry_mode = 0; -$h1_nb_msg_deleted = $h2_nb_msg_deleted = 0; -$h1_nb_msg_duplicate = $h2_nb_msg_duplicate = 0; -$h1_nb_msg_noheader = $h2_nb_msg_noheader = 0; -$h1_total_bytes_duplicate = $h2_total_bytes_duplicate = 0; +$sync->{ total_bytes_transferred } = 0 ; +$sync->{ total_bytes_skipped } = 0 ; +$sync->{ nb_msg_transferred } = 0 ; +$sync->{ nb_msg_skipped } = $nb_msg_skipped_dry_mode = 0 ; + +$sync->{ acc1 }->{ nb_msg_deleted } = 0 ; +$sync->{ acc2 }->{ nb_msg_deleted } = 0 ; +$sync->{ acc1 }->{ nb_msg_duplicate } = 0 ; +$sync->{ acc2 }->{ nb_msg_duplicate } = 0 ; -$h1_nb_msg_start = $h1_bytes_start = 0 ; -$h2_nb_msg_start = $h2_bytes_start = 0 ; -$h1_nb_msg_processed = $h1_bytes_processed = 0 ; +$sync->{ h1_nb_msg_noheader } = 0 ; +$h2_nb_msg_noheader = 0 ; + + +$sync->{ h1_nb_msg_start } = 0 ; +$sync->{ h1_bytes_start } = 0 ; +$sync->{ h2_nb_msg_start } = 0 ; +$sync->{ h2_bytes_start } = 0 ; +$sync->{ h1_nb_msg_processed } = $h1_bytes_processed = 0 ; + +$sync->{ h2_nb_msg_crossdup } = 0 ; #$h1_nb_msg_end = $h1_bytes_end = 0 ; #$h2_nb_msg_end = $h2_bytes_end = 0 ; -$sync->{nb_errors} = 0; -$max_msg_size_in_bytes = 0; +$sync->{ nb_errors } = 0; +$sync->{ biggest_message_transferred } = 0; -my %month_abrev = ( +%month_abrev = ( Jan => '00', Feb => '01', Mar => '02', @@ -975,23 +1439,21 @@ my %month_abrev = ( ); -my $cgidir ; -# Just create a CGI object if under cgi context only. +# Just create a CGI object if under cgi context only. # Needed for the get_options() call cgibegin( $sync ) ; # In cgi context, printing must start by the header so we delay other prints by using output() storage my $options_good = get_options( $sync, @ARGV ) ; -# Is it The first myprint? -docker_context( $sync ) ; +# Is it the first myprint? cgibuildheader( $sync ) ; +docker_context( $sync ) ; -myprint( output( $sync ) ) ; -output_reset_with( $sync ) ; +print_output_if_needed( $sync ) ; -# Can break here if load is too heavy -cgiload( $sync ) ; + +output_reset_with( $sync ) ; # don't go on if options are not all known. if ( ! defined $options_good ) { exit $EX_USAGE ; } @@ -1001,73 +1463,97 @@ if ( ! defined $options_good ) { exit $EX_USAGE ; } # the second line (ending with "1 ;") can then stay active or be commented, # the result will be the same: no releasecheck by default (because 0 is then the defined value). -$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 0 ; -#$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 1 ; +#$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 0 ; +$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 1 ; # just the version if ( $sync->{ version } ) { myprint( imapsync_version( $sync ), "\n" ) ; - exit 0 ; + return 0 ; } +#$sync->{debugenv} = 1 ; $sync->{debugenv} and printenv( $sync ) ; # if option --debugenv load_modules( ) ; # after_get_options call usage and exit if --help or options were not well got after_get_options( $sync, $options_good ) ; +#local $ENV{TZ} = 'GMT' if ( under_cgi_context( $sync ) and 'MSWin32' ne $OSNAME ) ; +#output( $sync, localtime(time) . " " . gmtime(time) . "\n" ) ; # Under CGI environment, fix caveat emptor potential issues cgisetcontext( $sync ) ; +get_options_extra( $sync ) ; + +# --gmail --gmail --exchange --office etc. easyany( $sync ) ; +$sync->{ sanitize } = defined $sync->{ sanitize } ? $sync->{ sanitize } : 1 ; +sanitize( $sync ) ; + $sync->{ tmpdir } ||= File::Spec->tmpdir( ) ; # Unit tests -testsexit( $sync ) ; +my $unittestssuite = unittestssuite( $sync ) ; + + +if ( condition_to_leave_after_tests( $sync ) ) +{ + return $unittestssuite ; +} # init live varaiables -testslive( $sync ) if ( $sync->{testslive} ) ; -testslive6( $sync ) if ( $sync->{testslive6} ) ; -# -$sync->{pidfile} = defined $sync->{pidfile} ? $sync->{pidfile} : $sync->{ tmpdir } . '/imapsync.pid' ; -$sync->{pidfilelocking} = defined $sync->{pidfilelocking} ? $sync->{pidfilelocking} : 0 ; +if ( $sync->{ testslive } ) +{ + testslive_init( $sync ) ; +} -# old abort place +if ( $sync->{ testslive6 } ) +{ + testslive6_init( $sync ) ; +} -@{ $sync->{ sigexit } } = ( defined( $sync->{ sigexit } ) ) ? @{ $sync->{ sigexit } } : ( 'QUIT', 'TERM' ) ; -@{ $sync->{ sigreconnect } } = ( defined( $sync->{ sigreconnect } ) ) ? @{ $sync->{ sigreconnect } } : ( 'INT' ) ; +define_pidfile( $sync ) ; +if ( $sync->{ abortbyfile } ) { $sync->{ abort } = 1 ; } -sig_install( $sync, \&catch_exit, @{ $sync->{ sigexit } } ) ; -sig_install( $sync, \&catch_reconnect, @{ $sync->{ sigreconnect } } ) ; -# --sigignore can override sigexit and sigreconnect (for the same signals only) -sig_install( $sync, \&catch_ignore, @{ $sync->{ sigignore } } ) ; +install_signals( $sync ) ; -sig_install( $sync, \&toggle_sleep, 'USR1' ) ; +$sync->{ log } = defined $sync->{ log } ? $sync->{ log } : 1 ; +$sync->{ errorsdump } = defined $sync->{ errorsdump } ? $sync->{ errorsdump } : 1 ; +$sync->{ errorsmax } = defined $sync->{ errorsmax } ? $sync->{ errorsmax } : $ERRORS_MAX ; -$sync->{log} = defined $sync->{log} ? $sync->{log} : 1 ; -$sync->{errorsdump} = defined $sync->{errorsdump} ? $sync->{errorsdump} : 1 ; -$sync->{errorsmax} = defined $sync->{errorsmax} ? $sync->{errorsmax} : $ERRORS_MAX ; +# log and output +binmode STDOUT, ":encoding(UTF-8)" ; -if ( $sync->{log} ) { +if ( $sync->{ log } ) { setlogfile( $sync ) ; teelaunch( $sync ) ; # now $sync->{tee} is a filehandle to STDOUT and the logfile } + +#binmode STDERR, ":encoding(UTF-8)" ; # STDERR goes to the same place: LOG and STDOUT (if logging is on) -$sync->{tee} and local *STDERR = *${$sync->{tee}}{IO} ; - +# Useful only for --debugssl +$sync->{tee} and local *STDERR = *${$sync->{tee}}{IO} ; + + + $timestart_int = int( $sync->{timestart} ) ; -$timebefore = $sync->{timestart} ; +$sync->{timebefore} = $sync->{timestart} ; + + +$sync->{ timestart_str } = localtimez( $sync->{timestart} ) ; + +# The prints in the log starts here -my $timestart_str = localtime( $sync->{timestart} ) ; myprint( localhost_info( $sync ), "\n" ) ; -myprint( "Transfer started at $timestart_str\n" ) ; -myprint( "PID is $PROCESS_ID\n" ) ; -myprint( "Log file is $sync->{logfile} ( to change it, use --logfile path ; or use --nolog to turn off logging )\n" ) if ( $sync->{log} ) ; +myprint( "Transfer started at $sync->{ timestart_str }\n" ) ; +myprint( "PID is $PROCESS_ID my PPID is ", mygetppid( ), "\n" ) ; +announcelogfile( $sync ) ; myprint( "Load is " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $sync->{cpu_number} cores\n" ) ; #myprintf( "Memory consumption so far: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ; myprint( 'Current directory is ' . getcwd( ) . "\n" ) ; @@ -1076,20 +1562,21 @@ myprint( 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (eui $modulesversion = defined $modulesversion ? $modulesversion : 1 ; - -my $warn_release = ( $sync->{releasecheck} ) ? check_last_release( ) : $STR_use_releasecheck ; +$sync->{ warn_release } = ( $sync->{ releasecheck } ) ? check_last_release( ) : $STR_use_releasecheck ; $wholeheaderifneeded = defined $wholeheaderifneeded ? $wholeheaderifneeded : 1; -# Activate --usecache if --useuid is set and no --nousecache +# Activate --usecache if --useuid is set and there is no --nousecache $usecache = 1 if ( $useuid and ( ! defined $usecache ) ) ; $cacheaftercopy = 1 if ( $usecache and ( ! defined $cacheaftercopy ) ) ; -$sync->{ checkselectable } = defined $sync->{ checkselectable } ? $sync->{ checkselectable } : 1 ; + + + $sync->{ checkfoldersexist } = defined $sync->{ checkfoldersexist } ? $sync->{ checkfoldersexist } : 1 ; $checkmessageexists = defined $checkmessageexists ? $checkmessageexists : 0 ; -$expungeaftereach = defined $expungeaftereach ? $expungeaftereach : 1 ; +$sync->{ expungeaftereach } = defined $sync->{ expungeaftereach } ? $sync->{ expungeaftereach } : 1 ; # abletosearch is on by default $sync->{abletosearch} = defined $sync->{abletosearch} ? $sync->{abletosearch} : 1 ; @@ -1098,60 +1585,96 @@ $sync->{abletosearch2} = defined $sync->{abletosearch2} ? $sync->{abletosear $checkmessageexists = 0 if ( not $sync->{abletosearch1} ) ; +$sync->{ trylogin } = defined $sync->{ trylogin } ? $sync->{ trylogin } : 1 ; $sync->{showpasswords} = defined $sync->{showpasswords} ? $sync->{showpasswords} : 0 ; -$fixslash2 = defined $fixslash2 ? $fixslash2 : 1 ; +$sync->{ fixslash2 } = defined $sync->{ fixslash2 } ? $sync->{ fixslash2 } : 1 ; $fixInboxINBOX = defined $fixInboxINBOX ? $fixInboxINBOX : 1 ; $create_folder_old = defined $create_folder_old ? $create_folder_old : 0 ; $mixfolders = defined $mixfolders ? $mixfolders : 1 ; $sync->{automap} = defined $sync->{automap} ? $sync->{automap} : 0 ; -$delete2duplicates = 1 if ( $delete2 and ( ! defined $delete2duplicates ) ) ; +$sync->{ delete2duplicates } = 1 if ( $sync->{ delete2 } and ( ! defined $sync->{ delete2duplicates } ) ) ; $sync->{maxmessagespersecond} = defined $sync->{maxmessagespersecond} ? $sync->{maxmessagespersecond} : 0 ; $sync->{maxbytespersecond} = defined $sync->{maxbytespersecond} ? $sync->{maxbytespersecond} : 0 ; $sync->{sslcheck} = defined $sync->{sslcheck} ? $sync->{sslcheck} : 1 ; -myprint( banner_imapsync( @ARGV ) ) ; +myprint( banner_imapsync( $sync, @ARGV ) ) ; myprint( "Temp directory is $sync->{ tmpdir } ( to change it use --tmpdir dirpath )\n" ) ; + myprint( output( $sync ) ) ; +output_reset_with( $sync ) ; do_valid_directory( $sync->{ tmpdir } ) || croak "Error creating tmpdir $sync->{ tmpdir } : $OS_ERROR" ; -remove_pidfile_not_running( $sync->{pidfile} ) ; +remove_pidfile_not_running( $sync->{ pidfile } ) ; +# if another imapsync is running then tail -f its logfile and exit +# useful in cgi context +if ( $sync->{ tail } and tail( $sync ) ) +{ + exit_clean( $sync, $EX_OK, "Tail -f finished. Now finishing myself processus $PROCESS_ID\n" ) ; + exit $EX_OK ; +} if ( ! write_pidfile( $sync ) ) { + myprint( "Exiting with return value $EXIT_PID_FILE_ERROR ($EXIT_TXT{$EXIT_PID_FILE_ERROR}) $sync->{nb_errors}/$sync->{errorsmax} nb_errors/max_errors PID $PROCESS_ID\n" ) ; exit $EXIT_PID_FILE_ERROR ; } -# simulong is just a loop printing some lines for xx seconds with option "--simulong xx". -if ( $sync->{simulong} ) { simulong( $sync->{simulong} ) ; } -# New place to abort -if ( $sync->{abort} ) { - abort( $sync ) ; +# New place for abort +# abort before simulong in order to be able to abort a simulong sync +if ( $sync->{ abort } ) +{ + abort( $sync ) ; + # well, the abort job is done, because even when not succeeded + # in aborting another run, this run has to end without doing any + # thing else + + exit $EX_OK ; } +# simulong is just a loop printing some lines for xx seconds with option "--simulong xx". +simulong( $sync ) ; + + + +# New place for cgiload 2019_03_03 +# because I want to log it +# Can break here if load is too heavy +# Have in mind the CGI header has already a 503 Service Unavailable +cgiload( $sync ) ; + + $fixcolonbug = defined $fixcolonbug ? $fixcolonbug : 1 ; if ( $usecache and $fixcolonbug ) { tmpdir_fix_colon_bug( $sync ) } ; -$modulesversion and myprint( "Modules version list:\n", modulesversion(), "( use --no-modulesversion to turn off printing this Perl modules list )\n" ) ; +$modulesversion and myprint( "Modules version list ( use --no-modulesversion to turn off printing this Perl modules list ):\n", modulesversion(), "\n" ) ; -check_lib_version( ) or +check_lib_version( $sync ) or croak "imapsync needs perl lib Mail::IMAPClient release 3.30 or superior.\n"; -exit_clean( $sync, $EX_OK ) if ( $justbanner ) ; + +if ( $sync->{ justbanner } ) +{ + myprint( "Exiting because of --justbanner\n" ) ; + exit_clean( $sync, $EX_OK ) ; +} # turn on RFC standard flags correction like \SEEN -> \Seen -$flagscase = defined $flagscase ? $flagscase : 1 ; +$sync->{ flagscase } = defined $sync->{ flagscase } ? $sync->{ flagscase } : 1 ; # Use PERMANENTFLAGS if available -$filterflags = defined $filterflags ? $filterflags : 1 ; +$sync->{ filterflags } = defined $sync->{ filterflags } ? $sync->{ filterflags } : 1 ; + +filterbuggyflags( $sync ) ; + # sync flags just after an APPEND, some servers ignore the flags given in the APPEND # like MailEnable IMAP server. @@ -1167,21 +1690,25 @@ if ( $sync->{resyncflags} ) { } - sslcheck( $sync ) ; - +#print Data::Dumper->Dump( [ \$sync ] ) ; $split1 ||= $SPLIT ; $split2 ||= $SPLIT ; -$sync->{host1} || missing_option( '--host1' ) ; +#$sync->{host1} || missing_option( $sync, '--host1' ) ; +$sync->{host1} = sanitize_host( $sync->{host1} ) ; $sync->{port1} ||= ( $sync->{ssl1} ) ? $IMAP_SSL_PORT : $IMAP_PORT ; -$sync->{host2} || missing_option( '--host2' ) ; +#$sync->{host2} || missing_option( $sync, '--host2' ) ; +$sync->{host2} = sanitize_host( $sync->{host2} ) ; $sync->{port2} ||= ( $sync->{ssl2} ) ? $IMAP_SSL_PORT : $IMAP_PORT ; -$debugimap1 = $debugimap2 = 1 if ( $debugimap ) ; -$debug = 1 if ( $debugimap1 or $debugimap2 ) ; + +$acc1->{ debugimap } = $acc2->{ debugimap } = 1 if ( $sync->{ debugimap } ) ; +# Set on debug mode if one of the imap dialogs are in debug. +# imap dialog without the debug mode is scary and useless. +$sync->{ debug } = 1 if ( $acc1->{ debugimap } or $acc2->{ debugimap } ) ; # By default, don't take size to compare $skipsize = (defined $skipsize) ? $skipsize : 1; @@ -1200,9 +1727,6 @@ if ( defined $delete2foldersbutnot or defined $delete2foldersonly ) { } - - -my $SSL_VERIFY_POLICY ; my %SSL_VERIFY_STR ; Readonly $SSL_VERIFY_POLICY => IO::Socket::SSL::SSL_VERIFY_NONE( ) ; @@ -1210,8 +1734,8 @@ Readonly %SSL_VERIFY_STR => ( IO::Socket::SSL::SSL_VERIFY_NONE( ) => 'SSL_VERIFY_NONE, ie, do not check the certificate server.' , IO::Socket::SSL::SSL_VERIFY_PEER( ) => 'SSL_VERIFY_PEER, ie, check the certificate server' , ) ; -$IO::Socket::SSL::DEBUG = $sync->{debugssl} || 1 ; +$IO::Socket::SSL::DEBUG = defined( $sync->{debugssl} ) ? $sync->{debugssl} : 1 ; if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} or $sync->{tls2}) { @@ -1221,6 +1745,7 @@ if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} or $sync->{tls2}) { if ( $sync->{ssl1} ) { myprint( qq{Host1: SSL default mode is like --sslargs1 "SSL_verify_mode=$SSL_VERIFY_POLICY", meaning for host1 $SSL_VERIFY_STR{$SSL_VERIFY_POLICY}\n} ) ; myprint( 'Host1: Use --sslargs1 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER( ) . " to have $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER( )} of host1\n" ) ; + # $sync->{ acc1 }->{sslargs}->{SSL_verify_mode} } if ( $sync->{ssl2} ) { @@ -1228,16 +1753,27 @@ if ( $sync->{ssl2} ) { myprint( 'Host2: Use --sslargs2 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER( ) . " to have $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER( )} of host2\n" ) ; } +# ID on by default since 1.832 +$sync->{id} = defined $sync->{id} ? $sync->{id} : 1 ; +if ( $sync->{justconnect} + or not $sync->{user1} + or not $sync->{user2} + or not $sync->{host1} + or not $sync->{host2} + ) +{ + my $justconnect = justconnect( $sync ) ; -if ( $sync->{justconnect} ) { - justconnect( ) ; myprint( debugmemory( $sync, " after justconnect() call" ) ) ; - exit_clean( $sync, $EX_OK ) ; + exit_clean( $sync, $EX_OK, + "Exiting after a justconnect on host(s): $justconnect\n" + ) ; } -$sync->{user1} || missing_option( '--user1' ) ; -$sync->{user2} || missing_option( '--user2' ) ; + +#$sync->{user1} || missing_option( $sync, '--user1' ) ; +#$sync->{user2} || missing_option( $sync, '--user2' ) ; $syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1; @@ -1246,31 +1782,36 @@ $syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1; # Done because --delete1 --noexpunge1 is very dangerous on the second run: # the Deleted flag is then synced to all previously transferred messages. # So --delete1 implies --expunge1 is a better usability default behavior. -if ( $delete1 ) { - if ( ! defined $expunge1 ) { +if ( $sync->{ delete1 } ) { + if ( ! defined $sync->{ expunge1 } ) { myprint( "Info: turning on --expunge1 because --delete1 --noexpunge1 is very dangerous on the second run.\n" ) ; - $expunge1 = 1 ; + $sync->{ expunge1 } = 1 ; } myprint( "Info: if expunging after each message slows down too much the sync then use --noexpungeaftereach to speed up\n" ) ; } -if ( $uidexpunge2 and not Mail::IMAPClient->can( 'uidexpunge' ) ) { - myprint( "Failure: uidexpunge not supported (IMAPClient release < 3.17), use --expunge2 instead\n" ) ; +if ( $sync->{ uidexpunge2 } and not Mail::IMAPClient->can( 'uidexpunge' ) ) { + myprint( "Failure: uidexpunge not supported (IMAPClient release < 3.17), use nothing or --expunge2 instead\n" ) ; + $sync->{nb_errors}++ ; exit_clean( $sync, $EX_SOFTWARE ) ; } -if ( ( $delete2 or $delete2duplicates ) and not defined $uidexpunge2 ) { +if ( ( $sync->{ delete2 } or $sync->{ delete2duplicates } ) and not defined $sync->{ uidexpunge2 } ) { if ( Mail::IMAPClient->can( 'uidexpunge' ) ) { myprint( "Info: will act as --uidexpunge2\n" ) ; - $uidexpunge2 = 1 ; - }elsif ( not defined $expunge2 ) { + $sync->{ uidexpunge2 } = 1 ; + }elsif ( not defined $sync->{ expunge2 } ) { myprint( "Info: will act as --expunge2 (no uidexpunge support)\n" ) ; - $expunge2 = 1 ; + $sync->{ expunge2 } = 1 ; } } -if ( $delete1 and $delete2 ) { - myprint( "Warning: using --delete1 and --delete2 together is almost always a bad idea, exiting imapsync\n" ) ; +if ( $sync->{ delete1 } and $sync->{ delete2 } ) { + myprint( "Warning: using --delete1 and --delete2 together is almost always a bad idea. " + . "You should probably launch two runs, the first with --delete2 for a strict sync, " + . "then the second with --delete1 to remove messages from the source account. " + . "Exiting imapsync.\n" ) ; + $sync->{ nb_errors }++ ; exit_clean( $sync, $EX_USAGE ) ; } @@ -1293,67 +1834,88 @@ if ( defined $authmd5 and $authmd5 ) { } if ( defined $authmd51 and $authmd51 ) { - $authmech1 ||= 'CRAM-MD5'; + $acc1->{ authmech } ||= 'CRAM-MD5' ; } else{ - $authmech1 ||= $authuser1 ? 'PLAIN' : 'LOGIN'; + $acc1->{ authmech } ||= $acc1->{ authuser } ? 'PLAIN' : 'LOGIN' ; } if ( defined $authmd52 and $authmd52 ) { - $authmech2 ||= 'CRAM-MD5'; + $acc2->{ authmech } ||= 'CRAM-MD5'; } else{ - $authmech2 ||= $authuser2 ? 'PLAIN' : 'LOGIN'; + $acc2->{ authmech } ||= $acc2->{ authuser } ? 'PLAIN' : 'LOGIN'; } -$authmech1 = uc $authmech1; -$authmech2 = uc $authmech2; +$acc1->{ authmech } = uc $acc1->{ authmech } ; +$acc2->{ authmech } = uc $acc2->{ authmech } ; -if (defined $proxyauth1 && !$authuser1) { - missing_option( 'With --proxyauth1, --authuser1' ) ; +if ( defined $acc1->{ proxyauth } && !$acc1->{ authuser } ) +{ + missing_option( $sync, 'With --proxyauth1, --authuser1' ) ; } -if (defined $proxyauth2 && !$authuser2) { - missing_option( 'With --proxyauth2, --authuser2' ) ; +if ( defined $acc2->{ proxyauth } && !$acc2->{ authuser } ) +{ + missing_option( $sync, 'With --proxyauth2, --authuser2' ) ; } -$authuser1 ||= $sync->{user1}; -$authuser2 ||= $sync->{user2}; +myprint( "Host1: will try to use $acc1->{ authmech } authentication on host1\n") ; +myprint( "Host2: will try to use $acc2->{ authmech } authentication on host2\n") ; -myprint( "Host1: will try to use $authmech1 authentication on host1\n") ; -myprint( "Host2: will try to use $authmech2 authentication on host2\n") ; +$sync->{ timeout } = defined $sync->{ timeout } ?$sync->{ timeout } : $DEFAULT_TIMEOUT ; -$timeout = defined $timeout ? $timeout : $DEFAULT_TIMEOUT ; +$sync->{ acc1 }->{timeout} = defined $sync->{ acc1 }->{timeout} ? $sync->{ acc1 }->{timeout} : $sync->{ timeout } ; +myprint( "Host1: imap connection timeout is $sync->{ acc1 }->{timeout} seconds\n") ; +$sync->{ acc2 }->{timeout} = defined $sync->{ acc2 }->{timeout} ? $sync->{ acc2 }->{timeout} : $sync->{ timeout } ; +myprint( "Host2: imap connection timeout is $sync->{ acc2 }->{timeout} seconds\n" ) ; -$sync->{h1}->{timeout} = defined $sync->{h1}->{timeout} ? $sync->{h1}->{timeout} : $timeout ; -myprint( "Host1: imap connection timeout is $sync->{h1}->{timeout} seconds\n") ; -$sync->{h2}->{timeout} = defined $sync->{h2}->{timeout} ? $sync->{h2}->{timeout} : $timeout ; -myprint( "Host2: imap connection timeout is $sync->{h2}->{timeout} seconds\n" ) ; -$syncacls = defined $syncacls ? $syncacls : 0 ; +keepalive1( $sync ) ; +keepalive2( $sync ) ; -# No folders sizes if --justfolders, unless really wanted. -if ( $justfolders and not defined $foldersizes ) { $foldersizes = 0 ; } -$foldersizes = ( defined $foldersizes ) ? $foldersizes : 1 ; -$foldersizesatend = ( defined $foldersizesatend ) ? $foldersizesatend : $foldersizes ; +if ( under_cgi_context( $sync ) ) +{ + myprint( "Under CGI context, a timeout can occur from the webserver, see https://imapsync.lamiral.info/INSTALL.d/INSTALL.OnlineUI.txt\n" ) ; +} + +$sync->{ syncacls } = defined $sync->{ syncacls } ? $sync->{ syncacls } : 0 ; -$fastio1 = defined $fastio1 ? $fastio1 : 0 ; -$fastio2 = defined $fastio2 ? $fastio2 : 0 ; +# No folders sizes at the beginning if --justfolders, unless really wanted. +if ( + $sync->{ justfolders } + and not defined $sync->{ foldersizes } + and not $sync->{ justfoldersizes } ) +{ + $sync->{ foldersizes } = 0 ; + $sync->{ foldersizesatend } = 1 ; +} -$reconnectretry1 = defined $reconnectretry1 ? $reconnectretry1 : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ; -$reconnectretry2 = defined $reconnectretry2 ? $reconnectretry2 : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ; +$sync->{ foldersizes } = ( defined $sync->{ foldersizes } ) ? $sync->{ foldersizes } : 1 ; +$sync->{ foldersizesatend } = ( defined $sync->{ foldersizesatend } ) ? $sync->{ foldersizesatend } : $sync->{ foldersizes } ; -# Since select_msgs() returns no messages when uidnext does not return something -# then $uidnext_default is never used. So I have to remove it. -$uidnext_default = $DEFAULT_UIDNEXT ; +#$sync->{ checknoabletosearch } = ( defined $sync->{ checknoabletosearch } ) ? $sync->{ checknoabletosearch } : 1 ; +set_checknoabletosearch( $sync ) ; -if ( ! @useheader ) { @useheader = qw( Message-Id Received ) ; } -my %useheader ; +$acc1->{ fastio } = defined $acc1->{ fastio } ? $acc1->{ fastio } : 0 ; +$acc2->{ fastio } = defined $acc2->{ fastio } ? $acc2->{ fastio } : 0 ; + + +$acc1->{ reconnectretry } = defined $acc1->{ reconnectretry } ? $acc1->{ reconnectretry } : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ; +$acc2->{ reconnectretry } = defined $acc2->{ reconnectretry } ? $acc2->{ reconnectretry } : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ; + +# IMAP compression on by default +#$acc1->{ compress } = defined $acc1->{ compress } ? $acc1->{ compress } : 0 ; +#$acc2->{ compress } = defined $acc2->{ compress } ? $acc2->{ compress } : 0 ; + + + +if ( ! @useheader ) { @useheader = qw( Message-Id Received ) ; } # Make a hash %useheader of each --useheader 'key' in uppercase -for ( @useheader ) { $useheader{ uc $_ } = undef } ; +for ( @useheader ) { $sync->{useheader}->{ uc $_ } = undef } ; #myprint( Data::Dumper->Dump( [ \%useheader ] ) ) ; #exit ; @@ -1364,20 +1926,21 @@ myprint( "Host2: IMAP server [$sync->{host2}] port [$sync->{port2}] user [$sync- get_password1( $sync ) ; get_password2( $sync ) ; - +# --dry1 make imapsync not fetching messages from host1, it is on when --dry is on. +# Use --dry --nodry1 to make imapsync fetching messages from host1, +# It is useful when debugging transformation options like --pipemess or --regexmess +$sync->{dry1} = defined $sync->{dry1} ? $sync->{dry1} : $sync->{dry} ; $sync->{dry_message} = q{} ; if( $sync->{dry} ) { $sync->{dry_message} = "\t(not really since --dry mode)" ; } +$sync->{ search1 } ||= $search if ( $search ) ; +$sync->{ search2 } ||= $search if ( $search ) ; -$search1 ||= $search if ( $search ) ; -$search2 ||= $search if ( $search ) ; - - - -if ( $disarmreadreceipts ) { +if ( $disarmreadreceipts ) +{ push @regexmess, q{s{\A((?:[^\n]+\r\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims} ; } @@ -1390,17 +1953,25 @@ if ( @pipemess and $pipemesscheck ) { my $string = pipemess( q{ }, @pipemess ) ; # string undef means something was bad. if ( not ( defined $string ) ) { - die_clean( "Error: one of --pipemess command is bad, check it\n" ) ; + $sync->{nb_errors}++ ; + exit_clean( $sync, $EX_USAGE, + "Error: one of --pipemess command is bad, check it\n" + ) ; } myprint( "Ok with each --pipemess @pipemess\n" ) ; } if ( $maxlinelengthcmd ) { - myprint( "Checking --maxlinelengthcmd command, $maxlinelengthcmd, with an space string.\n" ) ; + myprint( "Checking --maxlinelengthcmd command, + $maxlinelengthcmd, with an space string.\n" + ) ; my $string = pipemess( q{ }, $maxlinelengthcmd ) ; # string undef means something was bad. if ( not ( defined $string ) ) { - die_clean( "Error: --maxlinelengthcmd command is bad, check it\n" ) ; + $sync->{nb_errors}++ ; + exit_clean( $sync, $EX_USAGE, + "Error: --maxlinelengthcmd command is bad, check it\n" + ) ; } myprint( "Ok with --maxlinelengthcmd $maxlinelengthcmd\n" ) ; } @@ -1410,7 +1981,10 @@ if ( @regexmess ) { myprint( "Checking each --regexmess command with an space string.\n" ) ; # string undef means one of the eval regex was bad. if ( not ( defined $string ) ) { - die_clean( 'Error: one of --regexmess option is bad, check it' ) ; + #errors_incr( $sync, 'Warning: one of --regexmess option may be bad, check them' ) ; + exit_clean( $sync, $EX_USAGE, + "Error: one of --regexmess option is bad, check it\n" + ) ; } myprint( "Ok with each --regexmess\n" ) ; } @@ -1420,55 +1994,70 @@ if ( @skipmess ) { my $match = skipmess( q{ } ) ; # match undef means one of the eval regex was bad. if ( not ( defined $match ) ) { - die_clean( 'Error: one of --skipmess option is bad, check it' ) ; + $sync->{nb_errors}++ ; + exit_clean( $sync, $EX_USAGE, + "Error: one of --skipmess option is bad, check it\n" + ) ; } myprint( "Ok with each --skipmess\n" ) ; } -if ( @regexflag ) { +if ( $sync->{ regexflag } ) { myprint( "Checking each --regexflag command with an space string.\n" ) ; - my $string = flags_regex( q{ } ) ; + my $string = regexflags( $sync, q{ } ) ; # string undef means one of the eval regex was bad. if ( not ( defined $string ) ) { - die_clean( 'Error: one of --regexflag option is bad, check it' ) ; + $sync->{nb_errors}++ ; + exit_clean( $sync, $EX_USAGE, + "Error: one of --regexflag option is bad, check it\n" + ) ; } myprint( "Ok with each --regexflag\n" ) ; } -$sync->{imap1} = my $imap1 = login_imap( $sync->{host1}, $sync->{port1}, $sync->{user1}, $domain1, $sync->{password1}, - $debugimap1, $sync->{h1}->{timeout}, $fastio1, $sync->{ssl1}, $sync->{tls1}, - $authmech1, $authuser1, $reconnectretry1, - $proxyauth1, $uid1, $split1, 'Host1', $sync->{h1}, $sync ) ; +$sync->{imap1} = login_imap( $sync->{host1}, $sync->{port1}, $sync->{user1}, $sync->{password1}, + $sync->{ssl1}, $sync->{tls1}, + $uid1, $split1, $sync->{ acc1 }, $sync ) ; + +$sync->{imap2} = login_imap( $sync->{host2}, $sync->{port2}, $sync->{user2}, $sync->{password2}, + $sync->{ssl2}, $sync->{tls2}, + $uid2, $split2, $sync->{ acc2 }, $sync ) ; -$sync->{imap2} = my $imap2 = login_imap( $sync->{host2}, $sync->{port2}, $sync->{user2}, $domain2, $sync->{password2}, - $debugimap2, $sync->{h2}->{timeout}, $fastio2, $sync->{ssl2}, $sync->{tls2}, - $authmech2, $authuser2, $reconnectretry2, - $proxyauth2, $uid2, $split2, 'Host2', $sync->{h2}, $sync ) ; +$sync->{ debug } and $sync->{imap1} and myprint( 'Host1 Buffer I/O: ', $sync->{imap1}->Buffer(), "\n" ) ; +$sync->{ debug } and $sync->{imap2} and myprint( 'Host2 Buffer I/O: ', $sync->{imap2}->Buffer(), "\n" ) ; -$debug and myprint( 'Host1 Buffer I/O: ', $imap1->Buffer(), "\n" ) ; -$debug and myprint( 'Host2 Buffer I/O: ', $imap2->Buffer(), "\n" ) ; + +if ( ! $sync->{imap1} || ! $sync->{imap2} ) +{ + exit_most_errors( $sync ) ; +} -if ( ! $imap1->IsAuthenticated( ) ) { die_clean( 'Not authenticated on host1' ) ; } myprint( "Host1: state Authenticated\n" ) ; -if ( ! $imap2->IsAuthenticated( ) ) { die_clean( 'Not authenticated on host2' ) ; } myprint( "Host2: state Authenticated\n" ) ; -myprint( 'Host1 capability once authenticated: ', join(q{ }, @{ $imap1->capability() || [] }), "\n" ) ; -myprint( 'Host2 capability once authenticated: ', join(q{ }, @{ $imap2->capability() || [] }), "\n" ) ; +myprint( 'Host1 capability once authenticated: ', join(q{ }, @{ $sync->{imap1}->capability() || [] }), "\n" ) ; + +#myprint( Data::Dumper->Dump( [ $sync->{imap1} ] ) ) ; +#myprint( "imap4rev1: " . $sync->{imap1}->imap4rev1() . "\n" ) ; + +myprint( 'Host2 capability once authenticated: ', join(q{ }, @{ $sync->{imap2}->capability() || [] }), "\n" ) ; -# ID on by default since 1.832 -$sync->{id} = defined $sync->{id} ? $sync->{id} : 1 ; imap_id_stuff( $sync ) ; -#quota( $imap1, 'h1', $sync ) ; # quota on host1 is useless and pollute host2 output. -quota( $imap2, 'h2', $sync ) ; +#quota( $sync, $sync->{imap1}, 'h1' ) ; # quota on host1 is useless and pollute host2 output. +quota( $sync, $sync->{imap2}, 'h2' ) ; -if ( $sync->{justlogin} ) { - $imap1->logout( ) ; - $imap2->logout( ) ; - exit_clean( $sync, $EX_OK ) ; +maxsize_setting( $sync ) ; + +acc_compress_imap( $acc1 ) ; +acc_compress_imap( $acc2 ) ; + +if ( $sync->{ justlogin } ) { + $sync->{imap1}->logout( ) ; + $sync->{imap2}->logout( ) ; + exit_clean( $sync, $EX_OK, "Exiting because of --justlogin\n" ) ; } @@ -1476,62 +2065,65 @@ if ( $sync->{justlogin} ) { # Folder stuff # -my ( - @h1_folders_all , %h1_folders_all , @h1_folders_wanted , %requested_folder , - %h1_subscribed_folder , %h2_subscribed_folder , - @h2_folders_all , %h2_folders_all , %h2_folders_all_UPPER , - @h2_folders_from_1_wanted , %h2_folders_from_1_wanted , - %h2_folders_from_1_several , - %h2_folders_from_1_all , -) ; - -my $h1_folders_wanted_nb = 0 ; -my $h1_folders_wanted_ct = 0 ; # counter of folders done. +$h1_folders_wanted_nb = 0 ; # counter of folders to be done. +$h1_folders_wanted_ct = 0 ; # counter of folders done. # All folders on host1 and host2 -@h1_folders_all = sort $imap1->folders( ) ; -@h2_folders_all = sort $imap2->folders( ) ; +@h1_folders_all = sort $sync->{imap1}->folders( ) ; +@h2_folders_all = sort $sync->{imap2}->folders( ) ; myprint( 'Host1: found ', scalar @h1_folders_all , " folders.\n" ) ; myprint( 'Host2: found ', scalar @h2_folders_all , " folders.\n" ) ; -foreach my $f ( @h1_folders_all ) { - $h1_folders_all{ $f } = 1 +foreach my $f ( @h1_folders_all ) +{ + $h1_folders_all{ $f } = 1 } -foreach my $f ( @h2_folders_all ) { + +foreach my $f ( @h2_folders_all ) +{ $h2_folders_all{ $f } = 1 ; - $h2_folders_all_UPPER{ uc $f } = 1 ; + $sync->{h2_folders_all_UPPER}{ uc $f } = 1 ; } $sync->{h1_folders_all} = \%h1_folders_all ; $sync->{h2_folders_all} = \%h2_folders_all ; -$sync->{h2_folders_all_UPPER} = \%h2_folders_all_UPPER ; -# Make a hash of subscribed folders in both servers. -for ( $imap1->subscribed( ) ) { $h1_subscribed_folder{ $_ } = 1 } ; -for ( $imap2->subscribed( ) ) { $h2_subscribed_folder{ $_ } = 1 } ; +private_folders_separators_and_prefixes( ) ; + +# Make a hash of subscribed folders in both servers. -if ( defined $subfolder2 ) { - unshift @regextrans2, - q(s,^$sync->{h2_prefix}(.*),$sync->{h2_prefix}${subfolder2}${h2_sep}$1,), - q(s,^INBOX$,$sync->{h2_prefix}${subfolder2}${h2_sep}INBOX,) ; +for ( $sync->{imap1}->subscribed( ) ) { $h1_subscribed_folder{ $_ } = 1 } ; +for ( $sync->{imap2}->subscribed( ) ) { $h2_subscribed_folder{ $_ } = 1 } ; -} -if ( $fixInboxINBOX and ( my $reg = fix_Inbox_INBOX_mapping( \%h1_folders_all, \%h2_folders_all ) ) ) { - push @regextrans2, $reg ; +if ( defined $sync->{ subfolder1 } ) { + subfolder1( $sync ) ; } -if ( ( $sync->{folder} and scalar @{ $sync->{folder} } ) - or $subscribed - or scalar @folderrec ) { - # folders given by option --folder - if ( $sync->{folder} and scalar @{ $sync->{folder} } ) { - add_to_requested_folders( @{ $sync->{folder} } ); + + +if ( defined $sync->{ subfolder2 } ) { + subfolder2( $sync ) ; +} + +if ( $fixInboxINBOX and ( my $reg = fix_Inbox_INBOX_mapping( \%h1_folders_all, \%h2_folders_all ) ) ) { + push @{ $sync->{ regextrans2 } }, $reg ; +} + + + +if ( ( $sync->{ folder } and scalar @{ $sync->{ folder } } ) + or $subscribed + or scalar @folderrec ) +{ + # folders given by option --folder + if ( $sync->{ folder } and scalar @{ $sync->{ folder } } ) { + add_to_requested_folders( @{ $sync->{ folder } } ) ; } # option --subscribed @@ -1540,16 +2132,18 @@ if ( ( $sync->{folder} and scalar @{ $sync->{folder} } ) } # option --folderrec - if (scalar @folderrec) { - foreach my $folderrec (@folderrec) { - add_to_requested_folders($imap1->folders($folderrec)); + if ( scalar @folderrec ) { + foreach my $folderrec ( @folderrec ) { + add_to_requested_folders( $sync->{imap1}->folders( $folderrec ) ) ; } } -} else { +} +else +{ # no include, no folder/subscribed/folderrec options => all folders if ( not scalar @include ) { myprint( "Including all folders found by default. Use --subscribed or --folder or --folderrec or --include to select specific folders. Use --exclude to unselect specific folders.\n" ) ; - add_to_requested_folders(@h1_folders_all); + add_to_requested_folders( @h1_folders_all ) ; } } @@ -1557,6 +2151,8 @@ if ( ( $sync->{folder} and scalar @{ $sync->{folder} } ) # consider (optional) includes and excludes if ( scalar @include ) { foreach my $include ( @include ) { + # No, do not add /x after the regex, never. + # Users would kill you! my @included_folders = grep { /$include/ } @h1_folders_all ; add_to_requested_folders( @included_folders ) ; myprint( "Including folders matching pattern $include\n" . jux_utf8_list( @included_folders ) . "\n" ) ; @@ -1566,6 +2162,8 @@ if ( scalar @include ) { if ( scalar @exclude ) { foreach my $exclude ( @exclude ) { my @requested_folder = sort keys %requested_folder ; + # No, do not add /x after the regex, never. + # Users would kill you! my @excluded_folders = grep { /$exclude/ } @requested_folder ; remove_from_requested_folders( @excluded_folders ) ; myprint( "Excluding folders matching pattern $exclude\n" . jux_utf8_list( @excluded_folders ) . "\n" ) ; @@ -1575,7 +2173,8 @@ if ( scalar @exclude ) { # sort before is not very powerful # it adds --folderfirst and --folderlast even if they don't exist on host1 -@h1_folders_wanted = sort_requested_folders( ) ; +#@h1_folders_wanted = sort_requested_folders( ) ; +$sync->{h1_folders_wanted} = [ sort_requested_folders( ) ] ; # Remove no selectable folders @@ -1583,8 +2182,8 @@ if ( scalar @exclude ) { if ( $sync->{ checkfoldersexist } ) { my @h1_folders_wanted_exist ; myprint( "Host1: Checking wanted folders exist. Use --nocheckfoldersexist to avoid this check (shared of public namespace targeted).\n" ) ; - foreach my $folder ( @h1_folders_wanted ) { - ( $debug or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n" ) ; + foreach my $folder ( @{ $sync->{h1_folders_wanted} } ) { + ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n" ) ; if ( ! exists $h1_folders_all{ $folder } ) { myprint( "Host1: warning! ignoring folder $folder because it is not in host1 whole folders list.\n" ) ; next ; @@ -1592,48 +2191,24 @@ if ( $sync->{ checkfoldersexist } ) { push @h1_folders_wanted_exist, $folder ; } } - @h1_folders_wanted = @h1_folders_wanted_exist ; + @{ $sync->{h1_folders_wanted} } = @h1_folders_wanted_exist ; }else{ myprint( "Host1: Not checking that wanted folders exist. Remove --nocheckfoldersexist to get this check.\n" ) ; } +setcheckselectable( $sync ) ; -if ( $sync->{ checkselectable } ) { - my @h1_folders_wanted_selectable ; - myprint( "Host1: Checking wanted folders are selectable. Use --nocheckselectable to avoid this check.\n" ) ; - foreach my $folder ( @h1_folders_wanted ) { - ( $debug or $sync->{debugfolders} ) and myprint( "Checking $folder is selectable on host1\n" ) ; - # It does an imap command LIST "" $folder and then search for no \Noselect - if ( ! $imap1->selectable( $folder ) ) { - myprint( "Host1: warning! ignoring folder $folder because it is not selectable\n" ) ; - }else{ - push @h1_folders_wanted_selectable, $folder ; - } - } - @h1_folders_wanted = @h1_folders_wanted_selectable ; - ( $debug or $sync->{debugfolders} ) and myprint( 'Host1: checking folders took ', timenext( ), " s\n" ) ; -}else{ - myprint( "Host1: Not checking that wanted folders are selectable. Remove --nocheckselectable to get this check.\n" ) ; -} - -$sync->{h1_folders_wanted} = \@h1_folders_wanted ; - - -my( $h1_sep, $h2_sep ) ; -# what are the private folders separators for each server ? +checkselectable( $sync ) ; -( $debug or $sync->{debugfolders} ) and myprint( "Getting separators\n" ) ; -$h1_sep = get_separator( $imap1, $sep1, '--sep1', 'Host1', \@h1_folders_all ) ; -$h2_sep = get_separator( $imap2, $sep2, '--sep2', 'Host2', \@h2_folders_all ) ; -$sync->{ h1_prefix } = get_prefix( $imap1, $prefix1, '--prefix1', 'Host1', \@h1_folders_all ) ; -$sync->{ h2_prefix } = get_prefix( $imap2, $prefix2, '--prefix2', 'Host2', \@h2_folders_all ) ; +# Bugfix OpenFind folders named like "kk \*123" are in fact "kk *123" (no \) +#foreach my $folder ( @{ $sync->{ h1_folders_wanted } } ) +#{ +# $folder =~ s{ \\\*}{ *}g ; +#} -myprint( "Host1 separator and prefix: [$h1_sep][$sync->{ h1_prefix }]\n" ) ; -myprint( "Host2 separator and prefix: [$h2_sep][$sync->{ h2_prefix }]\n" ) ; - # this hack is because LWP post does not pass well a hash in the $form parameter # but it does pass well an array %{ $sync->{f1f2h} } = split_around_equal( @{ $sync->{f1f2} } ) ; @@ -1641,20 +2216,29 @@ myprint( "Host2 separator and prefix: [$h2_sep][$sync->{ h2_prefix }]\n" ) ; automap( $sync ) ; -foreach my $h1_fold ( @h1_folders_wanted ) { +foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } ) { my $h2_fold ; - $h2_fold = imap2_folder_name( $h1_fold ) ; + $h2_fold = imap2_folder_name( $sync, $h1_fold ) ; $h2_folders_from_1_wanted{ $h2_fold }++ ; if ( 1 < $h2_folders_from_1_wanted{ $h2_fold } ) { $h2_folders_from_1_several{ $h2_fold }++ ; } } + @h2_folders_from_1_wanted = sort keys %h2_folders_from_1_wanted; + foreach my $h1_fold ( @h1_folders_all ) { my $h2_fold ; - $h2_fold = imap2_folder_name( $h1_fold ) ; + $h2_fold = imap2_folder_name( $sync, $h1_fold ) ; $h2_folders_from_1_all{ $h2_fold }++ ; + # Follows a fix to avoid deleting folder $sync->{ subfolder2 } + # because it usually does not exist on host1. + if ( $sync->{ subfolder2 } ) + { + $h2_folders_from_1_all{ $sync->{ h2_prefix } . $sync->{ subfolder2 } }++ ; + $h2_folders_from_1_all{ $sync->{ subfolder2 } }++ ; + } } @@ -1666,33 +2250,33 @@ All foldernames are presented between brackets like [X] where X is the foldernam When a foldername contains non-ASCII characters it is presented in the form [X] = [Y] where X is the imap foldername you have to use in command line options and -Y is the uft8 output just printed for convenience, to recognize it. +Y is the utf8 output just printed for convenience, to recognize it. END_LISTING myprint( - "Host1 folders list (first the raw imap format then the [X] = [Y]):\n", - $imap1->list( ), + "Host1: folders list (first the raw imap format then the [X] = [Y]):\n", + $sync->{imap1}->list( ), "\n", jux_utf8_list( @h1_folders_all ), "\n", - "Host2 folders list (first the raw imap format then the [X] = [Y]):\n", - $imap2->list( ), + "Host2: folders list (first the raw imap format then the [X] = [Y]):\n", + $sync->{imap2}->list( ), "\n", jux_utf8_list( @h2_folders_all ), - "\n", - q{} + "\n", + q{} ) ; if ( $subscribed ) { - myprint( + myprint( 'Host1 subscribed folders list: ', jux_utf8_list( sort keys %h1_subscribed_folder ), "\n", ) ; } - -my @h2_folders_not_in_1; + + @h2_folders_not_in_1 = list_folders_in_2_not_in_1( ) ; if ( @h2_folders_not_in_1 ) { @@ -1725,28 +2309,59 @@ if ( keys %{ $sync->{f1f2h} } ) { myprint( "\n" ) ; } -exit_clean( $sync, $EX_OK ) if ( $sync->{justfolderlists} ) ; -exit_clean( $sync, $EX_OK ) if ( $sync->{justautomap} ) ; +exit_clean( $sync, $EX_OK, "Exiting because of --justfolderlists\n" ) if ( $sync->{ justfolderlists } ) ; +exit_clean( $sync, $EX_OK, "Exiting because of --justautomap\n" ) if ( $sync->{ justautomap } ) ; debugsleep( $sync ) ; -if ( $foldersizes ) { - foldersizes_on_h1h2( ) ; +if ( $sync->{ skipemptyfolders } ) +{ + myprint( "Host1: will not syncing empty folders on host1. Use --noskipemptyfolders to create them anyway on host2\n") ; +} + +if ( $sync->{ checknoabletosearch } ) +{ + myprint( "Checking SEARCH ALL works on both accounts. To avoid that check, use --nochecknoabletosearch\n" ) ; + my $check1 = checknoabletosearch( $sync, $sync->{ imap1 }, 'INBOX', 'Host1' ) ; + my $check2 = checknoabletosearch( $sync, $sync->{ imap2 }, 'INBOX', 'Host2' ) ; + if ( $check1 or $check2 ) + { + myprint( "At least one account can not SEARCH ALL. So acting like --noabletosearch\n" ) ; + $sync->{abletosearch} = 0 ; + $sync->{abletosearch1} = 0 ; + $sync->{abletosearch2} = 0 ; + } + else + { + myprint( "Good! SEARCH ALL works on both accounts.\n" ) ; + } +} + + + +if ( $sync->{ foldersizes } ) { + + foldersizes_at_the_beggining( $sync ) ; + #foldersizes_at_the_beggining_old( $sync ) ; } -exit_clean( $sync, $EX_OK ) if ( $justfoldersizes ) ; -$sync->{stats} = 1 ; +if ( $sync->{ justfoldersizes } ) +{ + exit_clean( $sync, $EX_OK, "Exiting because of --justfoldersizes\n" ) ; +} + +$sync->{can_do_stats} = 1 ; -if ( $sync->{'delete1emptyfolders'} ) { +if ( $sync->{ delete1emptyfolders } ) { delete1emptyfolders( $sync ) ; } delete_folders_in_2_not_in_1( ) if $delete2folders ; # folder loop -$h1_folders_wanted_nb = scalar @h1_folders_wanted ; +$h1_folders_wanted_nb = scalar @{ $sync->{h1_folders_wanted} } ; myprint( "++++ Looping on each one of $h1_folders_wanted_nb folders to sync\n" ) ; @@ -1755,119 +2370,163 @@ $sync->{begin_transfer_time} = time ; my %uid_candidate_for_deletion ; my %uid_candidate_no_deletion ; -my %h2_folders_of_md5 = ( ) ; +$sync->{ h2_folders_of_md5 } = { } ; -FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) { - if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } +FOLDER: foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } ) +{ + $sync->{ h1_current_folder } = $h1_fold ; + eta_print( $sync ) ; + abortifneeded( $sync ) ; + if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } - my $h2_fold = imap2_folder_name( $h1_fold ) ; + my $h2_fold = imap2_folder_name( $sync, $h1_fold ) ; + $sync->{ h2_current_folder } = $h2_fold ; $h1_folders_wanted_ct++ ; myprintf( "Folder %7s %-35s -> %-35s\n", "$h1_folders_wanted_ct/$h1_folders_wanted_nb", jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ; myprint( debugmemory( $sync, " at folder loop" ) ) ; - + # host1 can not be fetched read only, select is needed because of expunge. - select_folder( $imap1, $h1_fold, 'Host1' ) or next FOLDER ; + select_folder( $sync, $sync->{imap1}, $h1_fold, 'Host1' ) or next FOLDER ; debugsleep( $sync ) ; - my $h1_fold_nb_messages = count_from_select( $imap1->History ) ; - myprint( "Host1 folder [$h1_fold] has $h1_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ; + my $h1_msgs_all_hash_ref ; + my @h1_msgs ; + my $h1_msgs_nb ; + my $h1_msgs_nb_from_select ; + + $h1_msgs_nb_from_select = count_from_select( $sync->{imap1}->History ) ; + myprint( "Host1: folder [$h1_fold] has $h1_msgs_nb_from_select messages in total (mentioned by SELECT)\n" ) ; - if ( $skipemptyfolders and 0 == $h1_fold_nb_messages ) { - myprint( "Skipping empty host1 folder [$h1_fold]\n" ) ; + if ( $sync->{ skipemptyfolders } and 0 == $h1_msgs_nb_from_select ) { + myprint( "Host1: skipping empty host1 folder [$h1_fold]\n" ) ; next FOLDER ; } + # Code added from https://github.com/imapsync/imapsync/issues/95 + # Thanks jh1995 + # Goal: do not create folder if --search or --max/minage return 0 message. + # even if there are messages by SELECT (no not real empty, empty for the user point of vue). + if ( $sync->{ skipemptyfolders } or $sync->{ dry } ) + { + $h1_msgs_all_hash_ref = { } ; + @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $sync->{ search1 }, $sync->{abletosearch1}, $h1_fold ) ; + + $h1_msgs_nb = scalar( @h1_msgs ) ; + if ( 0 == $h1_msgs_nb and $sync->{ skipemptyfolders } ) { + myprint( "Host1: skipping empty host1 folder [$h1_fold] (0 message found by SEARCH)\n" ) ; + next FOLDER ; + } + } + if ( ! exists $h2_folders_all{ $h2_fold } ) { - create_folder( $imap2, $h2_fold, $h1_fold ) or next FOLDER ; + # In --dry mode I could count the messages to be transfered instead of 0 + # Messages transferred : 0 (could be 0 without dry mode) + if ( ! create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold ) ) + { + if ( $sync->{ dry } ) + { + $nb_msg_skipped_dry_mode += $h1_msgs_nb ; + } + next FOLDER ; + } } - acls_sync( $h1_fold, $h2_fold ) ; + acls_sync( $sync, $h1_fold, $h2_fold ) ; # Sometimes the folder on host2 is listed (it exists) but is # not selectable but becomes selectable by a create (Gmail) - select_folder( $imap2, $h2_fold, 'Host2' ) - or ( create_folder( $imap2, $h2_fold, $h1_fold ) - and select_folder( $imap2, $h2_fold, 'Host2' ) ) + select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' ) + or ( create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold ) + and select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' ) ) or next FOLDER ; - my @select_results = $imap2->Results( ) ; + my @select_results = $sync->{imap2}->Results( ) ; my $h2_fold_nb_messages = count_from_select( @select_results ) ; - myprint( "Host2 folder [$h2_fold] has $h2_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ; + myprint( "Host2: folder [$h2_fold] has $h2_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ; - my $permanentflags2 = permanentflags( @select_results ) ; - myprint( "Host2 folder [$h2_fold] permanentflags: $permanentflags2\n" ) ; + my $permanentflags2 = permanentflags( $sync, @select_results ) ; + myprint( "Host2: folder [$h2_fold] permanentflags: $permanentflags2\n" ) ; - if ( $expunge1 ){ + if ( $sync->{ expunge1 } ) + { myprint( "Host1: Expunging $h1_fold $sync->{dry_message}\n" ) ; - if ( ! $sync->{dry} ) { $imap1->expunge( ) } ; + if ( ! $sync->{dry} ) + { + $sync->{imap1}->expunge( ) ; + } } if ( ( ( $subscribe and exists $h1_subscribed_folder{ $h1_fold } ) or $subscribeall ) - and not exists $h2_subscribed_folder{ $h2_fold } ) { + and not exists $h2_subscribed_folder{ $h2_fold } ) + { myprint( "Host2: Subscribing to folder $h2_fold\n" ) ; - if ( ! $sync->{dry} ) { $imap2->subscribe( $h2_fold ) } ; + if ( ! $sync->{dry} ) { $sync->{imap2}->subscribe( $h2_fold ) } ; } - next FOLDER if ( $justfolders ) ; + next FOLDER if ( $sync->{ justfolders } ) ; - if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } - my $h1_msgs_all_hash_ref = { } ; - my @h1_msgs = select_msgs( $imap1, $h1_msgs_all_hash_ref, $search1, $sync->{abletosearch1}, $h1_fold ); - if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + if ( ! defined $h1_msgs_nb ) + { + $h1_msgs_all_hash_ref = { } ; + @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $sync->{ search1 }, $sync->{abletosearch1}, $h1_fold ); + $h1_msgs_nb = scalar @h1_msgs ; + }else{ + # select_msgs already done. + } - my $h1_msgs_nb = scalar @h1_msgs ; - $h1{ $h1_fold }{ 'messages_nb' } = $h1_msgs_nb ; + if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } - myprint( "Host1 folder [$h1_fold] considering $h1_msgs_nb messages\n" ) ; - ( $debug or $debuglist ) and myprint( "Host1 folder [$h1_fold] considering $h1_msgs_nb messages, LIST gives: @h1_msgs\n" ) ; - $debug and myprint( "Host1 selecting messages of folder [$h1_fold] took ", timenext(), " s\n" ) ; + myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages\n" ) ; + ( $sync->{ debug } or $debuglist ) and myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages, LIST gives: @h1_msgs\n" ) ; + $sync->{ debug } and myprint( "Host1: selecting messages of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ; my $h2_msgs_all_hash_ref = { } ; - my @h2_msgs = select_msgs( $imap2, $h2_msgs_all_hash_ref, $search2, $sync->{abletosearch2}, $h2_fold ) ; + my @h2_msgs = select_msgs( $sync->{imap2}, $h2_msgs_all_hash_ref, $sync->{ search2 }, $sync->{abletosearch2}, $h2_fold ) ; - if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } my $h2_msgs_nb = scalar @h2_msgs ; - $h2{ $h2_fold }{ 'messages_nb' } = $h2_msgs_nb ; - myprint( "Host2 folder [$h2_fold] considering $h2_msgs_nb messages\n" ) ; - ( $debug or $debuglist ) and myprint( "Host2 folder [$h2_fold] considering $h2_msgs_nb messages, LIST gives: @h2_msgs\n" ) ; - $debug and myprint( "Host2 selecting messages of folder [$h2_fold] took ", timenext(), " s\n" ) ; + myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages\n" ) ; + ( $sync->{ debug } or $debuglist ) and myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages, LIST gives: @h2_msgs\n" ) ; + $sync->{ debug } and myprint( "Host2: selecting messages of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ; my $cache_base = "$sync->{ tmpdir }/imapsync_cache/" ; - my $cache_dir = cache_folder( $cache_base, "$sync->{host1}/$sync->{user1}/$sync->{host2}/$sync->{user2}", $h1_fold, $h2_fold ) ; + my $cache_dir = cache_folder( $cache_base, + "$sync->{host1}/$sync->{user1}/$sync->{host2}/$sync->{user2}", $h1_fold, $h2_fold ) ; my ( $cache_1_2_ref, $cache_2_1_ref ) = ( {}, {} ) ; - my $h1_uidvalidity = $imap1->uidvalidity( ) || q{} ; - my $h2_uidvalidity = $imap2->uidvalidity( ) || q{} ; + my $h1_uidvalidity = $sync->{imap1}->uidvalidity( ) || q{} ; + my $h2_uidvalidity = $sync->{imap2}->uidvalidity( ) || q{} ; - if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } if ( $usecache ) { - myprint( "cache directory: $cache_dir\n" ) ; + myprint( "Local cache directory: $cache_dir ( " . length( $cache_dir ) . " characters long )\n" ) ; mkpath( "$cache_dir" ) ; ( $cache_1_2_ref, $cache_2_1_ref ) = get_cache( $cache_dir, \@h1_msgs, \@h2_msgs, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ; myprint( 'CACHE h1 h2: ', scalar keys %{ $cache_1_2_ref } , " files\n" ) ; - $debug and myprint( '[', + $sync->{ debug } and myprint( '[', map ( { "$_->$cache_1_2_ref->{$_} " } keys %{ $cache_1_2_ref } ), " ]\n" ) ; } - my %h1_hash = () ; - my %h2_hash = () ; + my %h1_hash = ( ) ; + my %h2_hash = ( ) ; my ( %h1_msgs, %h2_msgs ) ; - @h1_msgs{ @h1_msgs } = (); - @h2_msgs{ @h2_msgs } = (); + @h1_msgs{ @h1_msgs } = ( ) ; + @h2_msgs{ @h2_msgs } = ( ) ; my @h1_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_1_2_ref } ; - my @h2_msgs_in_cache = keys %{ $cache_2_1_ref } ; + my @h2_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_2_1_ref } ; my ( %h1_msgs_not_in_cache, %h2_msgs_not_in_cache ) ; %h1_msgs_not_in_cache = %h1_msgs ; @@ -1875,9 +2534,9 @@ FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) { delete @h1_msgs_not_in_cache{ @h1_msgs_in_cache } ; delete @h2_msgs_not_in_cache{ @h2_msgs_in_cache } ; - my @h1_msgs_not_in_cache = keys %h1_msgs_not_in_cache ; + my @h1_msgs_not_in_cache = sort { $a <=> $b } keys %h1_msgs_not_in_cache ; #myprint( "h1_msgs_not_in_cache: [@h1_msgs_not_in_cache]\n" ) ; - my @h2_msgs_not_in_cache = keys %h2_msgs_not_in_cache ; + my @h2_msgs_not_in_cache = sort { $a <=> $b } keys %h2_msgs_not_in_cache ; my @h2_msgs_delete2_not_in_cache = () ; %h1_msgs_copy_by_uid = ( ) ; @@ -1892,112 +2551,134 @@ FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) { #myprint( "delete2: @h2_msgs_delete2_not_in_cache\n" ) ; } - $debug and myprint( "Host1 parsing headers of folder [$h1_fold]\n" ) ; + $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold]\n" ) ; my ($h1_heads_ref, $h1_fir_ref) = ({}, {}); - $h1_heads_ref = $imap1->parse_headers([@h1_msgs_not_in_cache], @useheader) if (@h1_msgs_not_in_cache); - $debug and myprint( "Host1 parsing headers of folder [$h1_fold] took ", timenext(), " s\n" ) ; + $h1_heads_ref = $sync->{imap1}->parse_headers([@h1_msgs_not_in_cache], @useheader) if (@h1_msgs_not_in_cache); + $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ; @{ $h1_fir_ref }{@h1_msgs} = ( undef ) ; - $debug and myprint( "Host1 getting flags idate and sizes of folder [$h1_fold]\n" ) ; - if ( $sync->{abletosearch1} ) { - $h1_fir_ref = $imap1->fetch_hash( \@h1_msgs, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h1_fir_ref ) + $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold]\n" ) ; + + my @h1_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ; + if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h1_common_fetch_param, 'X-GM-LABELS' ; } + + if ( $sync->{abletosearch1} ) + { + $h1_fir_ref = $sync->{imap1}->fetch_hash( \@h1_msgs, @h1_common_fetch_param, $h1_fir_ref ) if ( @h1_msgs ) ; - }else{ - my $uidnext = $imap1->uidnext( $h1_fold ) || $uidnext_default ; - my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ; - $h1_fir_ref = $imap1->fetch_hash( $fetch_hash_uids, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h1_fir_ref ) + } + else + { + my $fetch_hash_uids = $fetch_hash_set || "1:*" ; + $h1_fir_ref = $sync->{imap1}->fetch_hash( $fetch_hash_uids, @h1_common_fetch_param, $h1_fir_ref ) if ( @h1_msgs ) ; } - $debug and myprint( "Host1 getting flags idate and sizes of folder [$h1_fold] took ", timenext(), " s\n" ) ; - if ( ! $h1_fir_ref ) { - my $error = join( q{}, "Host1 folder $h1_fold: Could not fetch_hash ", - scalar @h1_msgs, ' msgs: ', $imap1->LastError || q{}, "\n" ) ; + + $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ; + if ( ! $h1_fir_ref ) + { + my $error = join( q{}, "Host1: folder $h1_fold : Could not fetch_hash ", + scalar @h1_msgs, ' msgs: ', $sync->{imap1}->LastError || q{}, "\n" ) ; errors_incr( $sync, $error ) ; next FOLDER ; } my @h1_msgs_duplicate; - foreach my $m (@h1_msgs_not_in_cache) { - my $rc = parse_header_msg( $sync, $imap1, $m, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash ) ; - if ( ! defined $rc ) { + foreach my $m ( @h1_msgs_not_in_cache ) + { + my $rc = parse_header_msg( $sync, $sync->{imap1}, $m, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash ) ; + if ( ! defined $rc ) + { my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0; - myprint( "Host1 $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n" ) ; - $total_bytes_skipped += $h1_size; - $nb_msg_skipped += 1; - $h1_nb_msg_noheader +=1; - $h1_nb_msg_processed +=1 ; - } elsif(0 == $rc) { + myprint( "Host1: $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n" ) ; + $sync->{ total_bytes_skipped } += $h1_size ; + $sync->{ nb_msg_skipped } += 1 ; + $sync->{ h1_nb_msg_noheader } +=1 ; + $sync->{ h1_nb_msg_processed } +=1 ; + } elsif( 0 == $rc ) + { # duplicate push @h1_msgs_duplicate, $m; # duplicate, same id same size? my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0; - $nb_msg_skipped += 1; - $h1_total_bytes_duplicate += $h1_size; - $h1_nb_msg_duplicate += 1; - $h1_nb_msg_processed +=1 ; + + $sync->{ acc1 }->{ nb_msg_duplicate } += 1; + if ( ! $sync->{ syncduplicates } ) { + $sync->{ nb_msg_skipped } += 1 ; + $sync->{ h1_nb_msg_processed } +=1 ; + } } } + + my $h1_msgs_duplicate_nb = scalar @h1_msgs_duplicate ; - $h1{ $h1_fold }{ 'duplicates_nb' } = $h1_msgs_duplicate_nb ; - $debug and myprint( "Host1 selected: $h1_msgs_nb duplicates: $h1_msgs_duplicate_nb\n" ) ; - $debug and myprint( 'Host1 whole time parsing headers took ', timenext(), " s\n" ) ; - # Getting headers and metada can be so long that host2 might be disconnected here + myprint( "Host1: folder [$h1_fold] selected $h1_msgs_nb messages, duplicates $h1_msgs_duplicate_nb\n" ) ; + + $sync->{ debug } and myprint( 'Host1: whole time parsing headers took ', timenext( $sync ), " s\n" ) ; + + + + # Getting headers and metada can be so long that host2 might be disconnected here if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } - $debug and myprint( "Host2 parsing headers of folder [$h2_fold]\n" ) ; + $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold]\n" ) ; my ($h2_heads_ref, $h2_fir_ref) = ( {}, {} ); - $h2_heads_ref = $imap2->parse_headers([@h2_msgs_not_in_cache], @useheader) if (@h2_msgs_not_in_cache); - $debug and myprint( "Host2 parsing headers of folder [$h2_fold] took ", timenext(), " s\n" ) ; + $h2_heads_ref = $sync->{imap2}->parse_headers([@h2_msgs_not_in_cache], @useheader) if (@h2_msgs_not_in_cache); + $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ; - $debug and myprint( "Host2 getting flags idate and sizes of folder [$h2_fold]\n" ) ; + $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold]\n" ) ; @{ $h2_fir_ref }{@h2_msgs} = ( ); # fetch_hash can select by uid with last arg as ref + my @h2_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ; + if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h2_common_fetch_param, 'X-GM-LABELS' ; } + if ( $sync->{abletosearch2} and scalar( @h2_msgs ) ) { - $h2_fir_ref = $imap2->fetch_hash( \@h2_msgs, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h2_fir_ref) ; + $h2_fir_ref = $sync->{imap2}->fetch_hash( \@h2_msgs, @h2_common_fetch_param, $h2_fir_ref) ; }else{ - my $uidnext = $imap2->uidnext( $h2_fold ) || $uidnext_default ; - my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ; - $h2_fir_ref = $imap2->fetch_hash( $fetch_hash_uids, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h2_fir_ref ) + my $fetch_hash_uids = $fetch_hash_set || "1:*" ; + $h2_fir_ref = $sync->{imap2}->fetch_hash( $fetch_hash_uids, @h2_common_fetch_param, $h2_fir_ref ) if ( @h2_msgs ) ; } - $debug and myprint( "Host2 getting flags idate and sizes of folder [$h2_fold] took ", timenext(), " s\n" ) ; + $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ; my @h2_msgs_duplicate; foreach my $m (@h2_msgs_not_in_cache) { - my $rc = parse_header_msg( $sync, $imap2, $m, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash ) ; + my $rc = parse_header_msg( $sync, $sync->{imap2}, $m, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash ) ; my $h2_size = $h2_fir_ref->{$m}->{'RFC822.SIZE'} || 0 ; if (! defined $rc ) { - myprint( "Host2 $h2_fold/$m size $h2_size ignored (no wanted headers so we ignore this message)\n" ) ; + myprint( "Host2: $h2_fold/$m size $h2_size ignored (no wanted headers so we ignore this message)\n" ) ; $h2_nb_msg_noheader += 1 ; } elsif( 0 == $rc ) { # duplicate - $h2_nb_msg_duplicate += 1 ; - $h2_total_bytes_duplicate += $h2_size ; + $sync->{ acc2 }->{ nb_msg_duplicate } += 1 ; push @h2_msgs_duplicate, $m ; } } # %h2_folders_of_md5 foreach my $md5 ( keys %h2_hash ) { - $h2_folders_of_md5{ $md5 }->{ $h2_fold } ++ ; + $sync->{ h2_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ; + } + # %h1_folders_of_md5 + foreach my $md5 ( keys %h1_hash ) { + $sync->{ h1_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ; } my $h2_msgs_duplicate_nb = scalar @h2_msgs_duplicate ; - $h2{ $h2_fold }{ 'duplicates_nb' } = $h2_msgs_duplicate_nb ; - myprint( "Host2 folder $h2_fold selected: $h2_msgs_nb messages, duplicates: $h2_msgs_duplicate_nb\n" ) - if ( $debug or $delete2duplicates or $h2_msgs_duplicate_nb ) ; - $debug and myprint( 'Host2 whole time parsing headers took ', timenext( ), " s\n" ) ; + myprint( "Host2: folder [$h2_fold] selected $h2_msgs_nb messages, duplicates $h2_msgs_duplicate_nb\n" ) ; + + $sync->{ debug } and myprint( 'Host2 whole time parsing headers took ', timenext( $sync ), " s\n" ) ; - $debug and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ; + $sync->{ debug } and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ; # messages in host1 that are not in host2 my @h1_hash_keys_sorted_by_uid @@ -2008,30 +2689,31 @@ FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) { my @h2_hash_keys_sorted_by_uid = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys %h2_hash; + # Deletions on account2. - if( $delete2duplicates and not exists $h2_folders_from_1_several{ $h2_fold } ) { + if( $sync->{ delete2duplicates } and not exists $h2_folders_from_1_several{ $h2_fold } ) { my @h2_expunge ; foreach my $h2_msg ( @h2_msgs_duplicate ) { - myprint( "msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $sync->{dry_message}\n" ) ; - push @h2_expunge, $h2_msg if $uidexpunge2 ; - if ( ! $sync->{dry} ) { - $imap2->delete_message( $h2_msg ) ; - $h2_nb_msg_deleted += 1 ; + myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $sync->{dry_message}\n" ) ; + push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ; + if ( ! $sync->{ dry } ) { + $sync->{ imap2 }->delete_message( $h2_msg ) ; + $sync->{ acc2 }->{ nb_msg_deleted } += 1 ; } } my $cnt = scalar @h2_expunge ; - if( @h2_expunge and not $expunge2 ) { + if( @h2_expunge and not $sync->{ expunge2 } ) { myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ; - $imap2->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; + $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; } - if ( $expunge2 ){ + if ( $sync->{ expunge2 } ){ myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ; - $imap2->expunge( ) if ! $sync->{dry} ; + $sync->{imap2}->expunge( ) if ! $sync->{dry} ; } } - if( $delete2 and not exists $h2_folders_from_1_several{ $h2_fold } ) { + if( $sync->{ delete2 } and not exists $h2_folders_from_1_several{ $h2_fold } ) { # No host1 folders f1a f1b ... going all to same f2 (via --regextrans2) my @h2_expunge; foreach my $m_id (@h2_hash_keys_sorted_by_uid) { @@ -2042,35 +2724,35 @@ FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) { my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0; myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted on host2 [$m_id] $sync->{dry_message}\n" ) if ! $isdel; - push @h2_expunge, $h2_msg if $uidexpunge2; - if ( ! ( $sync->{dry} or $isdel ) ) { - $imap2->delete_message($h2_msg); - $h2_nb_msg_deleted += 1; + push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 }; + if ( ! ( $sync->{ dry } or $isdel ) ) { + $sync->{ imap2 }->delete_message( $h2_msg ); + $sync->{ acc2 }->{ nb_msg_deleted } += 1; } } } foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) { myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [not in cache] on host2 $sync->{dry_message}\n" ) ; - push @h2_expunge, $h2_msg if $uidexpunge2; + push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 }; if ( ! $sync->{dry} ) { - $imap2->delete_message($h2_msg); - $h2_nb_msg_deleted += 1; + $sync->{ imap2 }->delete_message( $h2_msg ); + $sync->{ acc2 }->{ nb_msg_deleted } += 1; } } my $cnt = scalar @h2_expunge ; - if( @h2_expunge and not $expunge2 ) { + if( @h2_expunge and not $sync->{ expunge2 } ) { myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ; - $imap2->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; + $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; } - if ( $expunge2 ) { + if ( $sync->{ expunge2 } ) { myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ; - $imap2->expunge( ) if ! $sync->{dry} ; + $sync->{imap2}->expunge( ) if ! $sync->{dry} ; } } - if( $delete2 and exists $h2_folders_from_1_several{ $h2_fold } ) { - myprint( "Host2 folder $h2_fold $h2_folders_from_1_several{ $h2_fold } folders left to sync there\n" ) ; + if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } ) { + myprint( "Host2: folder $h2_fold $h2_folders_from_1_several{ $h2_fold } folders left to sync there\n" ) ; my @h2_expunge; foreach my $m_id ( @h2_hash_keys_sorted_by_uid ) { my $h2_msg = $h2_hash{ $m_id }{ 'm' } ; @@ -2078,11 +2760,11 @@ FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) { my $h2_flags = $h2_hash{ $m_id }{ 'F' } || q{} ; my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0 ; if ( ! $isdel ) { - $debug and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [$m_id]\n" ) ; + $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [$m_id]\n" ) ; $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ; } }else{ - $debug and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n" ) ; + $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n" ) ; $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ; } } @@ -2101,192 +2783,265 @@ FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) { # last host1 folder going to $h2_fold myprint( "Last host1 folder going to $h2_fold\n" ) ; foreach my $h2_msg ( keys %{ $uid_candidate_for_deletion{ $h2_fold } } ) { - $debug and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion\n" ) ; + $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion\n" ) ; if ( exists $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg } ) { - $debug and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n" ) ; + $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n" ) ; }else{ myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted $sync->{dry_message}\n" ) ; - push @h2_expunge, $h2_msg if $uidexpunge2 ; - if ( ! $sync->{dry} ) { - $imap2->delete_message( $h2_msg ) ; - $h2_nb_msg_deleted += 1 ; + push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ; + if ( ! $sync->{ dry} ) { + $sync->{ imap2 }->delete_message( $h2_msg ) ; + $sync->{ acc2 }->{ nb_msg_deleted } += 1 ; } } } } my $cnt = scalar @h2_expunge ; - if( @h2_expunge and not $expunge2 ) { + if( @h2_expunge and not $sync->{ expunge2 } ) { myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ; - $imap2->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; + $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; } - if ( $expunge2 ) { + if ( $sync->{ expunge2 } ) { myprint( "Host2: Expunging host2 folder $h2_fold $sync->{dry_message}\n" ) ; - $imap2->expunge( ) if ! $sync->{dry} ; + $sync->{imap2}->expunge( ) if ! $sync->{dry} ; } $h2_folders_from_1_several{ $h2_fold }-- ; } - my $h2_uidnext = $imap2->uidnext( $h2_fold ) ; - $debug and myprint( "Host2 uidnext: $h2_uidnext\n" ) ; + my $h2_uidnext = $sync->{imap2}->uidnext( $h2_fold ) ; + $sync->{ debug } and myprint( "Host2: uidnext is $h2_uidnext\n" ) ; $h2_uidguess = $h2_uidnext ; # Getting host2 headers, metada and delete2 stuff can be so long that host1 might be disconnected here if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + my @h1_msgs_to_delete ; MESS: foreach my $m_id (@h1_hash_keys_sorted_by_uid) { + abortifneeded( $sync ) ; if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } - #myprint( "h1_nb_msg_processed: $h1_nb_msg_processed\n" ) ; + #myprint( "h1_nb_msg_processed: $sync->{ h1_nb_msg_processed }\n" ) ; my $h1_size = $h1_hash{$m_id}{'s'}; my $h1_msg = $h1_hash{$m_id}{'m'}; my $h1_idate = $h1_hash{$m_id}{'D'}; + #my $labels = labels( $sync->{imap1}, $h1_msg ) ; + #print "LABELS: $labels\n" ; + if ( ( not exists $h2_hash{ $m_id } ) - and ( not ( exists $h2_folders_of_md5{ $m_id } ) - or not $skipcrossduplicates ) ) { + and ( not ( exists $sync->{ h2_folders_of_md5 }->{ $m_id } ) + or not $skipcrossduplicates ) ) + { # copy my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ; - $h2_folders_of_md5{ $m_id }->{ $h2_fold } ++ ; - if( $delete2 and ( exists $h2_folders_from_1_several{ $h2_fold } ) and $h2_msg ) { + if ( $h2_msg and $sync->{ delete1 } and not $sync->{ expungeaftereach } ) { + # not expunged + push @h1_msgs_to_delete, $h1_msg ; + } + + # A bug here with imapsync 1.920, fixed in 1.921 + # Added $h2_msg in the condition. Errors of APPEND were not counted as missing messages on host2! + if ( $h2_msg and not $sync->{ dry } ) + { + $sync->{ h2_folders_of_md5 }->{ $m_id }->{ $h2_fold } ++ ; + } + + # + if( $sync->{ delete2 } and ( exists $h2_folders_from_1_several{ $h2_fold } ) and $h2_msg ) { myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ; $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ; } - last FOLDER if total_bytes_max_reached( ) ; + + if ( total_bytes_max_reached( $sync ) ) { + # Still a bug when using --delete1 --noexpungeaftereach + # same thing below on all total_bytes_max_reached! + last FOLDER ; + } next MESS; } - else{ + else + { # already on host2 - if ( exists $h2_hash{ $m_id } ) { + if ( exists $h2_hash{ $m_id } ) + { my $h2_msg = $h2_hash{$m_id}{'m'} ; - $debug and myprint( "Host1 found msg $h1_fold/$h1_msg equals Host2 $h2_fold/$h2_msg\n" ) ; - if ( $usecache ) { + $sync->{ debug } and myprint( "Host1: found that msg $h1_fold/$h1_msg equals Host2 $h2_fold/$h2_msg\n" ) ; + if ( $usecache ) + { $debugcache and myprint( "touch $cache_dir/${h1_msg}_$h2_msg\n" ) ; touch( "$cache_dir/${h1_msg}_$h2_msg" ) or croak( "Couldn't touch $cache_dir/${h1_msg}_$h2_msg" ) ; } - }elsif( exists $h2_folders_of_md5{ $m_id } ) { - my @folders_dup = keys %{ $h2_folders_of_md5{ $m_id } } ; - ( $debug or $debugcrossduplicates ) and myprint( "Host1 found msg $h1_fold/$h1_msg is also in Host2 folders @folders_dup\n" ) ; } - $total_bytes_skipped += $h1_size ; - $nb_msg_skipped += 1 ; - $h1_nb_msg_processed +=1 ; + elsif( exists $sync->{ h2_folders_of_md5 }->{ $m_id } ) + { + my @folders_dup = keys %{ $sync->{ h2_folders_of_md5 }->{ $m_id } } ; + ( $sync->{ debug } or $debugcrossduplicates ) and myprint( "Host1: found that msg $h1_fold/$h1_msg is also in Host2 folders @folders_dup\n" ) ; + $sync->{ h2_nb_msg_crossdup } +=1 ; + } + $sync->{ total_bytes_skipped } += $h1_size ; + $sync->{ nb_msg_skipped } += 1 ; + $sync->{ h1_nb_msg_processed } +=1 ; } if ( exists $h2_hash{ $m_id } ) { #$debug and myprint( "MESSAGE $m_id\n" ) ; my $h2_msg = $h2_hash{$m_id}{'m'}; if ( $sync->{resyncflags} ) { - sync_flags_fir( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ; + sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ; } # Good my $h2_size = $h2_hash{$m_id}{'s'}; - $debug and myprint( - "Host1 size msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ; - } + $sync->{ debug } and myprint( + "Host1: size msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ; - if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + if ( $sync->{ resynclabels } ) + { + resynclabels( $sync, $h1_msg, $h2_msg, $h1_fir_ref, $h2_fir_ref, $h1_fold ) + } + } + if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } - if ( $delete1 ) { - delete_message_on_host1( $h1_msg, $h1_fold ) ; + if ( $sync->{ delete1 } ) { + push @h1_msgs_to_delete, $h1_msg ; } } # END MESS: loop + + # @h1_msgs_in_cache are already synced too. + delete_message_on_host1( $sync, $h1_fold, $sync->{ expunge1 }, @h1_msgs_to_delete, @h1_msgs_in_cache ) ; + if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } - MESS_IN_CACHE: foreach my $h1_msg ( @h1_msgs_in_cache ) { - my $h2_msg = $cache_1_2_ref->{ $h1_msg } ; - $debugcache and myprint( "cache messages update flags $h1_msg->$h2_msg\n" ) ; - if ( $sync->{resyncflags} ) { - sync_flags_fir( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ; - } - my $h1_size = $h1_fir_ref->{ $h1_msg }->{ 'RFC822.SIZE' } || 0 ; - $total_bytes_skipped += $h1_size; - $nb_msg_skipped += 1; - $h1_nb_msg_processed +=1 ; - if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + # MESS_IN_CACHE: + if ( ! $sync->{ delete1 } ) + { + foreach my $h1_msg ( @h1_msgs_in_cache ) + { + my $h2_msg = $cache_1_2_ref->{ $h1_msg } ; + $debugcache and myprint( "cache messages update flags $h1_msg->$h2_msg\n" ) ; + if ( $sync->{resyncflags} ) + { + sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ; + } + my $h1_size = $h1_fir_ref->{ $h1_msg }->{ 'RFC822.SIZE' } || 0 ; + $sync->{ total_bytes_skipped } += $h1_size; + $sync->{ nb_msg_skipped } += 1; + $sync->{ h1_nb_msg_processed } +=1 ; + } } + if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + + @h1_msgs_to_delete = ( ) ; #myprint( "Messages by uid: ", map { "$_ " } keys %h1_msgs_copy_by_uid, "\n" ) ; - MESS_BY_UID: foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid ) { - # - $debug and myprint( "Copy by uid $h1_fold/$h1_msg\n" ) ; + # MESS_BY_UID: + foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid ) + { + abortifneeded( $sync ) ; + $sync->{ debug } and myprint( "Copy by uid $h1_fold/$h1_msg\n" ) ; if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ; - if( $delete2 and exists $h2_folders_from_1_several{ $h2_fold } and $h2_msg ) { + if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } and $h2_msg ) { myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ; $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ; } - last FOLDER if total_bytes_max_reached( ) ; + last FOLDER if total_bytes_max_reached( $sync ) ; } - if ( $expunge1 ){ + if ( $sync->{ expunge1 } ){ myprint( "Host1: Expunging folder $h1_fold $sync->{dry_message}\n" ) ; - if ( ! $sync->{dry} ) { $imap1->expunge( ) } ; + if ( ! $sync->{dry} ) { $sync->{imap1}->expunge( ) } ; } - if ( $expunge2 ){ + if ( $sync->{ expunge2 } ){ myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ; - if ( ! $sync->{dry} ) { $imap2->expunge( ) } ; + if ( ! $sync->{dry} ) { $sync->{imap2}->expunge( ) } ; } - $debug and myprint( 'Time: ', timenext( ), " s\n" ) ; + $sync->{ debug } and myprint( 'Time: ', timenext( $sync ), " s\n" ) ; } +eta_print( $sync ) ; myprint( "++++ End looping on each folder\n" ) ; -if ( $delete1 and $sync->{'delete1emptyfolders'} ) { +if ( $sync->{ delete1 } and $sync->{ delete1emptyfolders } ) { delete1emptyfolders( $sync ) ; } -( $debug or $sync->{debugfolders} ) and myprint( 'Time: ', timenext( ), " s\n" ) ; +( $sync->{ debug } or $sync->{debugfolders} ) and myprint( 'Time: ', timenext( $sync ), " s\n" ) ; -if ( $foldersizesatend ) { +if ( $sync->{ foldersizesatend } ) { myprint( << 'END_SIZE' ) ; Folders sizes after the synchronization. You can remove this foldersizes listing by using "--nofoldersizesatend" END_SIZE - foldersizesatend( ) ; + foldersizesatend( $sync ) ; +} + +#$sync->{imap1}->State( 0 ); # Unconnected +if ( ! lost_connection( $sync, $sync->{imap1}, "for host1 [$sync->{host1}]" ) ) { $sync->{imap1}->logout( ) ; } +if ( ! lost_connection( $sync, $sync->{imap2}, "for host2 [$sync->{host2}]" ) ) { $sync->{imap2}->logout( ) ; } + +do_and_print_stats( $sync ) ; + + +if ( $sync->{ nb_errors } ) +{ + myprint( errors_listing( $sync ) ) ; } -if ( ! lost_connection( $imap1, "for host1 [$sync->{host1}]" ) ) { $imap1->logout( ) ; } -if ( ! lost_connection( $imap2, "for host2 [$sync->{host2}]" ) ) { $imap2->logout( ) ; } -stats( $sync ) ; -myprint( errorsdump( $sync->{nb_errors}, errors_log( $sync ) ) ) if ( $sync->{errorsdump} ) ; -tests_live_result( $sync->{nb_errors} ) if ( $sync->{testslive} or $sync->{testslive6} ) ; +if ( $sync->{ testslive } or $sync->{ testslive6 } ) +{ + tests_live_result( $sync->{ nb_errors } ) ; +} + +if ( $sync->{ nb_errors } ) +{ + my $exit_value = exit_value( $sync, $sync->{ most_common_error } ) ; + exit_clean( $sync, $exit_value ) ; +} +else +{ + exit_clean( $sync, $EX_OK ) ; +} -exit_clean( $sync, $EXIT_WITH_ERRORS ) if ( $sync->{nb_errors} ) ; -exit_clean( $sync, $EX_OK ) ; +return ; +} -# END of main program +# END of sub single_sync # subroutines -sub myprint { +sub myprint +{ #print @ARG ; print { $sync->{ tee } || \*STDOUT } @ARG ; return ; } -sub myprintf { +sub myprintf +{ printf { $sync->{ tee } || \*STDOUT } @ARG ; return ; } -sub mysprintf { +sub mysprintf +{ my( $format, @list ) = @ARG ; return sprintf $format, @list ; } -sub output_start { +sub output_start +{ my $mysync = shift @ARG ; if ( not $mysync ) { return ; } @@ -2297,7 +3052,8 @@ sub output_start { } -sub tests_output_start { +sub tests_output_start +{ note( 'Entering tests_output_start()' ) ; my $mysync = { } ; @@ -2313,7 +3069,8 @@ sub tests_output_start { return ; } -sub tests_output { +sub tests_output +{ note( 'Entering tests_output()' ) ; my $mysync = { } ; @@ -2329,7 +3086,8 @@ sub tests_output { return ; } -sub output { +sub output +{ my $mysync = shift @ARG ; if ( not $mysync ) { return ; } @@ -2341,7 +3099,8 @@ sub output { -sub tests_output_reset_with { +sub tests_output_reset_with +{ note( 'Entering tests_output_reset_with()' ) ; my $mysync = { } ; @@ -2356,7 +3115,8 @@ sub tests_output_reset_with { return ; } -sub output_reset_with { +sub output_reset_with +{ my $mysync = shift @ARG ; if ( not $mysync ) { return ; } @@ -2367,4357 +3127,9045 @@ sub output_reset_with { } +sub tests_print_output_if_needed +{ + note( 'Entering tests_print_output_if_needed()' ) ; -sub abort { - my $mysync = shift @ARG ; - if ( ! -r $sync->{pidfile} ) { - myprint( "Can not read pidfile $sync->{pidfile}. Exiting.\n" ) ; - exit $EX_OK ; - } - my $pidtokill = firstline( $sync->{pidfile} ) ; - if ( ! $pidtokill ) { - myprint( "No process to abort. Exiting.\n" ) ; - exit $EX_OK ; - } - # First ask for suicide - if ( kill 'ZERO', $pidtokill ) { - myprint( "Sending signal QUIT to PID $pidtokill \n" ) ; - kill 'QUIT', $pidtokill ; - sleep 1 ; - }else{ - myprint( "Can not send signal to PID $pidtokill. Exiting.\n" ) ; - exit $EX_OK ; - } - # Then murder - if ( kill 'ZERO', $pidtokill ) { - myprint( "Sending signal KILL to PID $pidtokill \n" ) ; - kill 'KILL', $pidtokill ; - sleep 1 ; - }else{ - myprint( "Process PID $pidtokill ended. Exiting.\n" ) ; - exit $EX_OK ; - } - # Well ... - if ( kill 'ZERO', $pidtokill ) { - myprint( "Process PID $pidtokill still there. Can not do much. Exiting.\n" ) ; - exit $EX_OK ; - } + is( undef, print_output_if_needed( ), 'print_output_if_needed: no args => undef' ) ; + my $mysync = { } ; + is( q{}, print_output_if_needed( $mysync ), 'print_output_if_needed: undef => undef' ) ; - return ; -} + output( $mysync, "Hello\n" ) ; + is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: Hello => Hello' ) ; + $mysync->{ dockercontext } = 1 ; + is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: dockercontext + Hello => Hello' ) ; + $mysync->{ version } = 1 ; + is( q{}, print_output_if_needed( $mysync ), 'print_output_if_needed: dockercontext + Hello + --version => ""' ) ; -sub docker_context { - my $mysync = shift ; - -e '/.dockerenv' || return ; - myprint( "Docker context detected with /.dockerenv\n" ) ; - # No pidfile - $mysync->{pidfile} = q{} ; - # No log - $mysync->{log} = 0 ; - # In case - myprint( "Changing current directory to /var/tmp/\n" ) ; - chdir '/var/tmp/' ; - - return ; -} + $mysync->{ dockercontext } = 0 ; + is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: Hello + --version => Hello' ) ; -sub cgibegin { - my $mysync = shift ; - if ( ! under_cgi_context( $mysync ) ) { return ; } - require CGI ; - CGI->import( qw( -no_debug ) ) ; - require CGI::Carp ; - CGI::Carp->import( qw( fatalsToBrowser ) ) ; - $mysync->{cgi} = CGI->new( ) ; - return ; + note( 'Leaving tests_print_output_if_needed()' ) ; + return ; } -sub tests_under_cgi_context { - note( 'Entering tests_under_cgi_context()' ) ; - # $ENV{SERVER_SOFTWARE} = 'under imapsync' ; - do { - # Not in cgi context - delete local $ENV{SERVER_SOFTWARE} ; - is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ; - } ; - do { - # In cgi context - local $ENV{SERVER_SOFTWARE} = 'under imapsync' ; - is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ; - } ; - do { - # Not in cgi context - delete local $ENV{SERVER_SOFTWARE} ; - is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ; - } ; - do { - # In cgi context - local $ENV{SERVER_SOFTWARE} = 'under imapsync' ; - is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ; - } ; - note( 'Leaving tests_under_cgi_context()' ) ; - return ; -} +sub print_output_if_needed +{ + + my $mysync = shift @ARG ; + if ( ! defined $mysync ) { return ; } + my $output = output( $mysync ) ; + + if ( $mysync->{ version } && under_docker_context( $mysync ) ) + { + return q{} ; + } + else + { + myprint( $output ) ; + return $output ; + } -sub under_cgi_context { - my $mysync = shift ; - # Under cgi context - if ( $ENV{SERVER_SOFTWARE} ) { - return 1 ; - } - # Not in cgi context - return ; } -sub cgibuildheader { - my $mysync = shift ; - if ( ! under_cgi_context( $mysync ) ) { return ; } - my $imapsync_runs = $mysync->{cgi}->cookie( 'imapsync_runs' ) || 0 ; - my $cookie = $mysync->{cgi}->cookie( - -name => 'imapsync_runs', - -value => 1 + $imapsync_runs, - -expires => '+20y', - -path => '/cgi-bin/imapsync', - ) ; - my $httpheader ; - if ( $mysync->{ abort } ) { - $httpheader = $mysync->{cgi}->header( - -type => 'text/plain', - -status => '200 OK to abort syncing IMAP boxes' . ". Here is " . hostname(), - ) ; - }elsif( $mysync->{ loaddelay } ) { -# https://tools.ietf.org/html/rfc2616#section-10.5.4 -# 503 Service Unavailable -# The server is currently unable to handle the request due to a temporary overloading or maintenance of the server. - $httpheader = $mysync->{cgi}->header( - -type => 'text/plain', - -status => '503 Service Unavailable' . ". Be back in $mysync->{ loaddelay } min. Load on " . hostname() . " is $mysync->{ loadavg }", - ) ; - }else{ - $httpheader = $mysync->{cgi}->header( - -type => 'text/plain', - -status => '200 OK to sync IMAP boxes' . ". Load on " . hostname() . " is $mysync->{ loadavg }", - -cookie => $cookie, - ) ; - } - output_start( $mysync, $httpheader ) ; - return ; -} +sub define_pidfile +{ + my $mysync = shift @ARG ; -sub cgiload { - my $mysync = shift ; - if ( ! under_cgi_context( $mysync ) ) { return ; } - if ( $mysync->{ abort } ) { return ; } # keep going to abort - if ( $mysync->{ loaddelay } ) { - myprint( "Server is on heavy load. Be back in $mysync->{ loaddelay } min. Load is $mysync->{ loadavg }\n") ; - exit_clean( $mysync, $EX_UNAVAILABLE ) ; - } - return ; -} + $mysync->{ pidfilelocking } = defined $mysync->{ pidfilelocking } ? $mysync->{ pidfilelocking } : 0 ; -sub tests_set_umask { - note( 'Entering tests_set_umask()' ) ; - my $save_umask = umask ; + my $host1 = $mysync->{ host1 } || q{} ; + my $user1 = $mysync->{ user1 } || q{} ; + my $host2 = $mysync->{ host2 } || q{} ; + my $user2 = $mysync->{ user2 } || q{} ; - my $mysync = {} ; - if ( 'MSWin32' eq $OSNAME ) { - is( undef, set_umask( $mysync ), "set_umask: set failure to $UMASK_PARANO on MSWin32" ) ; - }else{ - is( 1, set_umask( $mysync ), "set_umask: set to $UMASK_PARANO" ) ; - } + my $account1_filtered = filter_forbidden_characters( slash_to_underscore( $host1 . '_' . $user1 ) ) || q{} ; + my $account2_filtered = filter_forbidden_characters( slash_to_underscore( $host2 . '_' . $user2 ) ) || q{} ; - umask $save_umask ; - note( 'Leaving tests_set_umask()' ) ; - return ; + my $pidfile_basename ; + + if ( $ENV{ 'NET_SERVER_SOFTWARE' } and ( $ENV{ 'NET_SERVER_SOFTWARE' } =~ /Net::Server::HTTP/ ) ) + { + # under local webserver + $pidfile_basename = 'imapsync' . '_' . $account1_filtered . '_' . $account2_filtered . '.pid' ; + } + else + { + $pidfile_basename = 'imapsync.pid' ; + } + + $mysync->{ pidfile } = defined $mysync->{ pidfile } ? $mysync-> { pidfile } : $mysync->{ tmpdir } . "/$pidfile_basename" ; + $mysync->{ abortfile } = abortfile( $mysync, $PROCESS_ID ) ; + return ; } -sub set_umask { - my $mysync = shift ; - my $previous_umask = umask_str( ) ; - my $new_umask = umask_str( $UMASK_PARANO ) ; - output( $mysync, "Umask set with $new_umask (was $previous_umask)\n" ) ; - if ( $new_umask eq $UMASK_PARANO ) { - return 1 ; - } - return ; +sub abortfile +{ + my $mysync = shift @ARG ; + my $pid = shift @ARG ; + + my $abortfile ; + if ( $mysync->{ abort } ) + { + $abortfile = $mysync->{ pidfile } . "abort$pid" ; + } + else + { + $abortfile = $mysync->{ pidfile } . "abort$PROCESS_ID" ; + } + return $abortfile ; } -sub tests_umask_str { - note( 'Entering tests_umask_str()' ) ; - my $save_umask = umask ; +sub tests_kill_zero +{ + note( 'Entering tests_kill_zero()' ) ; - is( umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent' ) ; - is( my $save_umask_str = umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent + save' ) ; - is( '0000', umask_str( q{ } ), 'umask_str: q{ } => 0000' ) ; - is( '0000', umask_str( q{} ), 'umask_str: q{} => 0000' ) ; - is( '0000', umask_str( '0000' ), 'umask_str: 0000 => 0000' ) ; - is( '0000', umask_str( '0' ), 'umask_str: 0 => 0000' ) ; - is( '0200', umask_str( '0200' ), 'umask_str: 0200 => 0200' ) ; - is( '0400', umask_str( '0400' ), 'umask_str: 0400 => 0400' ) ; - is( '0600', umask_str( '0600' ), 'umask_str: 0600 => 0600' ) ; - SKIP: { - if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 6 ) ; } - is( '0100', umask_str( '0100' ), 'umask_str: 0100 => 0100' ) ; - is( '0001', umask_str( '0001' ), 'umask_str: 0001 => 0001' ) ; - is( '0777', umask_str( '0777' ), 'umask_str: 0777 => 0777' ) ; - is( '0777', umask_str( '00777' ), 'umask_str: 00777 => 0777' ) ; - is( '0777', umask_str( ' 777 ' ), 'umask_str: 777 => 0777' ) ; - is( "$UMASK_PARANO", umask_str( $UMASK_PARANO ), "umask_str: UMASK_PARANO $UMASK_PARANO => $UMASK_PARANO" ) ; - } - is( $save_umask_str, umask_str( $save_umask_str ), 'umask_str: restore with str' ) ; - is( $save_umask, umask, 'umask_str: umask is restored, controlled by direct umask' ) ; - is( $save_umask, umask $save_umask, 'umask_str: umask is restored by direct umask' ) ; - is( $save_umask, umask, 'umask_str: umask initial controlled by direct umask' ) ; + SKIP: { + if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_kill_zero avoided on Windows', 8 ) ; } - note( 'Leaving tests_umask_str()' ) ; - return ; -} -sub umask_str { - my $value = shift ; + is( 1, kill( 'ZERO', $PROCESS_ID ), "kill ZERO : myself $PROCESS_ID => 1" ) ; + is( 2, kill( 'ZERO', $PROCESS_ID, $PROCESS_ID ), "kill ZERO : myself $PROCESS_ID $PROCESS_ID => 2" ) ; - if ( defined $value ) { - umask oct( $value ) ; - } - my $current = umask ; + if ( (-e '/.dockerenv' ) or ( 0 == $EFFECTIVE_USER_ID) ) + { + is( 1, kill( 'ZERO', 1 ), "kill ZERO : pid 1 => 1 (docker context or root)" ) ; + is( 2, kill( 'ZERO', $PROCESS_ID, 1 ), "kill ZERO : myself + pid 1, $PROCESS_ID 1 => 2 (docker context or root)" ) ; + } + else + { + is( 0, kill( 'ZERO', 1 ), "kill ZERO : pid 1 => 0 (non root)" ) ; + is( 1, kill( 'ZERO', $PROCESS_ID, 1 ), "kill ZERO : myself + pid 1, $PROCESS_ID 1 => 1 (one is non root)" ) ; - return( sprintf( '%#04o', $current ) ) ; -} + } -sub tests_umask { - note( 'Entering tests_umask()' ) ; - my $save_umask ; - is( umask, umask, 'umask: umask is umask' ) ; - is( $save_umask = umask, umask, "umask: umask is umask again + save it: $save_umask" ) ; - is( $save_umask, umask oct(0000), 'umask: umask 0000' ) ; - is( oct(0000), umask, 'umask: umask is now 0000' ) ; - is( oct(0000), umask oct(777), 'umask: umask 0777 call, previous 0000' ) ; - SKIP: { - if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 2 ) ; } - is( oct(777), umask, 'umask: umask is now 0777' ) ; - is( oct(777), umask $save_umask, "umask: umask $save_umask restore inital value, previous 0777" ) ; - } + my $pid_1 = fork( ) ; + if ( $pid_1 ) + { + # parent + } + else + { + # child + sleep 3 ; + exit ; + } - ok( defined umask $save_umask, "umask: umask $save_umask restore inital value, previous defined" ) ; - is( $save_umask, umask, 'umask: umask is umask restored' ) ; - note( 'Leaving tests_umask()' ) ; + my $pid_2 ; + $pid_2 = fork( ) ; + if ( $pid_2 ) + { + # I am the parent + ok( defined( $pid_2 ), "kill_zero: initial fork ok. I am the parent $PROCESS_ID" ) ; + ok( $pid_2 , "kill_zero: initial fork ok, child pid is $pid_2" ) ; + is( 3, kill( 'ZERO', $PROCESS_ID, $pid_2, $pid_1 ), "kill ZERO : myself $PROCESS_ID and child $pid_2 and brother $pid_1 => 3" ) ; - return ; -} + is( $pid_2, waitpid( $pid_2, 0 ), "kill_zero: child $pid_2 no more there => waitpid return $pid_2" ) ; + } + else + { + # I am the child + note( 'This one fails under Windows, kill ZERO returns 0 instead of 2' ) ; + is( 2, kill( 'ZERO', $PROCESS_ID, $pid_1 ), "kill ZERO : myself child $PROCESS_ID brother $pid_1 => 2" ) ; + myprint( "I am the child pid $PROCESS_ID, Exiting\n" ) ; + exit ; + } + wait( ) ; -sub cgisetcontext { - my $mysync = shift ; - if ( ! under_cgi_context( $mysync ) ) { return ; } + # End of SKIP block + } - output( $mysync, "Under cgi context\n" ) ; - set_umask( $mysync ) ; + note( 'Leaving tests_kill_zero()' ) ; + return ; +} - # Remove all content in unsafe evaled options - @regextrans2 = ( ) ; - @regexflag = ( ) ; - @regexmess = ( ) ; - @skipmess = ( ) ; - @pipemess = ( ) ; - $delete2foldersonly = undef ; - $delete2foldersbutnot = undef ; - $maxlinelengthcmd = undef ; - # Set safe default values (I hope...) - $mysync->{pidfile} = 'imapsync.pid' ; - $mysync->{pidfilelocking} = 1 ; - $mysync->{errorsmax} = $ERRORS_MAX_CGI ; - $modulesversion = 0 ; - $mysync->{releasecheck} = defined $mysync->{releasecheck} ? $mysync->{releasecheck} : 1 ; - $usecache = 0 ; - $mysync->{showpasswords} = 0 ; - $debugimap1 = $debugimap2 = $debugimap = 0 ; - $reconnectretry1 = $reconnectretry2 = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ; - $pipemesscheck = 0 ; - $mysync->{hashfile} = $CGI_HASHFILE ; - my $hashsynclocal = hashsynclocal( $mysync ) || die "Can not get hashsynclocal. Exiting\n" ; - $cgidir = $CGI_TMPDIR_TOP . '/' . $hashsynclocal ; +sub tests_killpid_by_parent +{ + note( 'Entering tests_killpid_by_parent()' ) ; - -d $cgidir or mkpath $cgidir or die "Can not create $cgidir: $OS_ERROR\n" ; - chdir $cgidir or die "Can not cd to $cgidir: $OS_ERROR\n" ; - $mysync->{ tmpdir } = $cgidir ; - cgioutputenvcontext( $mysync ) ; - $debug and output( $mysync, 'Current directory is ' . getcwd( ) . "\n" ) ; - $debug and output( $mysync, 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ; - $debug and output( $mysync, 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ; - # @{ $mysync->{ sigexit } } = ( 'QUIT' ) ; - # output( $mysync, "Setting the QUIT signal to exit properly\n" ) ; - return ; -} + SKIP: { + if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_killpid_by_parent avoided on Windows', 7 ) ; } -sub cgioutputenvcontext { - my $mysync = shift @ARG ; + is( undef, killpid( ), 'killpid: no args => undef' ) ; + note( "killpid: trying to kill myself pid $PROCESS_ID, hope I will not succeed" ) ; + is( undef, killpid( $PROCESS_ID ), 'killpid: myself => undef' ) ; - for my $envvar ( qw( REMOTE_ADDR REMOTE_HOST HTTP_REFERER HTTP_USER_AGENT SERVER_SOFTWARE SERVER_PORT HTTP_COOKIE ) ) { + local $SIG{'QUIT'} = sub { myprint "GOT SIG QUIT! I am PID $PROCESS_ID. Exiting\n" ; exit ; } ; - my $envval = $ENV{ $envvar } || q{} ; - if ( $envval ) { output( $mysync, "$envvar is $envval\n" ) } ; - } + my $pid ; + $pid = fork( ) ; + if ( $pid ) + { + # I am the parent + ok( defined( $pid ), "killpid: initial fork ok. I am the parent $PROCESS_ID" ) ; + ok( $pid , "killpid: initial fork ok, child pid is $pid" ) ; + + is( 2, kill( 'ZERO', $PROCESS_ID, $pid ), "kill ZERO : myself $PROCESS_ID and child $pid => 2" ) ; + is( 1, killpid( $pid ), "killpid: child $pid killed => 1" ) ; + is( -1, waitpid( $pid, 0 ), "killpid: child $pid no more there => waitpid return -1" ) ; + } + else + { + # I am the child + myprint( "I am the child pid $PROCESS_ID, sleeping 1 + 3 seconds then kill myself\n" ) ; + sleep 1 ; + myprint( "I am the child pid $PROCESS_ID, slept 1 second, should be killed by my parent now, PPID " . mygetppid( ) . "\n" ) ; + sleep 3 ; + # this test should not be run. If it happens => failure. + ok( 0 == 1, "killpid: child pid $PROCESS_ID not dead => failure" ) ; + myprint( "I am the child pid $PROCESS_ID, killing myself failure... Exiting\n" ) ; + exit ; + } - return ; + # End of SKIP block + } + note( 'Leaving tests_killpid_by_parent()' ) ; + return ; } +sub tests_killpid_by_brother +{ + note( 'Entering tests_killpid_by_brother()' ) ; + + + SKIP: { + if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_killpid_by_brother avoided on Windows', 2 ) ; } + + local $SIG{'QUIT'} = sub { myprint "GOT SIG QUIT! I am PID $PROCESS_ID. Exiting\n" ; exit ; } ; + + my $pid_parent = $PROCESS_ID ; + myprint( "I am the parent pid $pid_parent\n" ) ; + my $pid_1 = fork( ) ; + if ( $pid_1 ) + { + # parent + } + else + { + # child + #while ( 1 ) { } ; + sleep 2 ; + sleep 2 ; + # this test should not be run. If it happens => failure. + # Well under Windows this always fails, shit! + ok( 0 == 1 or ( 'MSWin32' eq $OSNAME ) , "killpid: child pid $PROCESS_ID killing by brother but not dead => failure" ) ; + myprint( "I am the child pid $PROCESS_ID, killing by brother failed... Exiting\n" ) ; + exit ; + } + my $pid_2 ; + $pid_2 = fork( ) ; + if ( $pid_2 ) + { + # parent + } + else + { + # I am the child + myprint( "I am the child pid $PROCESS_ID, my brother has pid $pid_1\n" ) ; + is( 1, killpid( $pid_1 ), "killpid: brother $pid_1 killed => 1" ) ; + sleep 2 ; + exit ; + } + #sleep 1 ; + is( $pid_1, waitpid( $pid_1, 0), "I am the parent $PROCESS_ID waitpid _1( $pid_1 )" ) ; + is( $pid_2, waitpid( $pid_2, 0 ), "I am the parent $PROCESS_ID waitpid _2( $pid_2 )" ) ; -sub debugsleep { - my $mysync = shift @ARG ; - if ( defined $mysync->{debugsleep} ) { - myprint( "Info: sleeping $mysync->{debugsleep}s\n" ) ; - sleep $mysync->{debugsleep} ; + # End of SKIP block } + + note( 'Leaving tests_killpid_by_brother()' ) ; return ; } -sub foldersizes_on_h1h2 { - myprint( << 'END_SIZE' ) ; -Folders sizes before the synchronization. -You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend" -but then you will also loose the ETA (Estimation Time of Arrival) given after each message copy. -END_SIZE +sub killpid +{ + my $pidtokill = shift ; - ( $h1_nb_msg_start, $h1_bytes_start ) = foldersizes( 'Host1', $imap1, $search1, $sync->{abletosearch1}, @h1_folders_wanted ) ; - ( $h2_nb_msg_start, $h2_bytes_start ) = foldersizes( 'Host2', $imap2, $search2, $sync->{abletosearch2}, @h2_folders_from_1_wanted ) ; + if ( ! $pidtokill ) { + myprint( "No process to kill.\n" ) ; + return ; + } - if ( not all_defined( $h1_nb_msg_start, $h1_bytes_start, $h2_nb_msg_start, $h2_bytes_start ) ) { - my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ; - errors_incr( $sync, $error ) ; - $foldersizes = 0 ; - $foldersizesatend = 0 ; + if ( $PROCESS_ID == $pidtokill ) { + myprint( "I will not kill myself pid $PROCESS_ID via killpid. Sractch it!\n" ) ; return ; } - my $h2_bytes_limit = $sync->{h2}->{quota_limit_bytes} || 0 ; - if ( $h2_bytes_limit and ( $h2_bytes_limit < $h1_bytes_start ) ) { - my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $h1_bytes_start / $h2_bytes_limit ) ; - my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $h1_bytes_start bytes / $h2_bytes_limit bytes )\n" ; - errors_incr( $sync, $error ) ; + + # First ask for suicide + if ( kill( 'ZERO', $pidtokill ) or ( 'MSWin32' eq $OSNAME ) ) { + myprint( "Sending signal QUIT to PID $pidtokill \n" ) ; + kill 'QUIT', $pidtokill ; + sleep 3 ; + waitpid( $pidtokill, WNOHANG) ; + }else{ + myprint( "Can not send signal kill ZERO to PID $pidtokill.\n" ) ; + return ; } + + #while ( waitpid( $pidtokill, WNOHANG) > 0 ) { } ; + + # Then murder + if ( kill( 'ZERO', $pidtokill ) or ( 'MSWin32' eq $OSNAME ) ) { + myprint( "Sending signal KILL to PID $pidtokill \n" ) ; + kill 'KILL', $pidtokill ; + sleep 1 ; + waitpid( $pidtokill, WNOHANG) ; + }else{ + myprint( "Process PID $pidtokill ended.\n" ) ; + return 1; + } + # Well ... + if ( kill( 'ZERO', $pidtokill ) or ( 'xMSWin32' eq $OSNAME ) ) { + myprint( "Process PID $pidtokill seems still there. Can not do much.\n" ) ; + return ; + }else{ + myprint( "Process PID $pidtokill ended.\n" ) ; + return 1; + } + return ; } +sub tests_abort +{ + note( 'Entering tests_abort()' ) ; + # Well, the abort behavior is tested by test.sh + is( undef, abort( ), 'abort: no args => undef' ) ; + note( 'Leaving tests_abort()' ) ; + return ; +} -sub total_bytes_max_reached { - return( 0 ) if not $exitwhenover ; - if ( $sync->{total_bytes_transferred} >= $exitwhenover ) { - myprint( "Maximum bytes transferred reached, $sync->{total_bytes_transferred} >= $exitwhenover, ending sync\n" ) ; - return( 1 ) ; - } -} +sub abort +{ + my $mysync = shift @ARG ; + myprint( "In abort\n" ) ; + if ( not $mysync ) { return ; } -sub all_defined { - if ( not @ARG ) { - return 0 ; + if ( ! -r $mysync->{pidfile} ) { + myprint( "In abort: Can not read pidfile $mysync->{pidfile}\n" ) ; + return ; } - foreach my $elem ( @ARG ) { - if ( not defined $elem ) { - return 0 ; - } + my $pidtokill = firstline( $mysync->{pidfile} ) ; + if ( ! $pidtokill ) { + myprint( "In abort: No process to abort in $mysync->{pidfile}\n" ) ; + return ; } - return 1 ; -} -sub tests_all_defined { - note( 'Entering tests_all_defined()' ) ; + if ( ! match_a_pid_number( $pidtokill ) ) + { + myprint( "In abort: pid $pidtokill in $mysync->{pidfile} is not a pid number\n" ) ; + return ; + } - is( 0, all_defined( ), 'all_defined: no param => 0' ) ; - is( 0, all_defined( () ), 'all_defined: void list => 0' ) ; - is( 0, all_defined( undef ), 'all_defined: undef => 0' ) ; - is( 0, all_defined( undef, undef ), 'all_defined: undef => 0' ) ; - is( 0, all_defined( 1, undef ), 'all_defined: 1 undef => 0' ) ; - is( 0, all_defined( undef, 1 ), 'all_defined: undef 1 => 0' ) ; - is( 1, all_defined( 1, 1 ), 'all_defined: 1 1 => 1' ) ; - is( 1, all_defined( (1, 1) ), 'all_defined: (1 1) => 1' ) ; - note( 'Leaving tests_all_defined()' ) ; + if ( $mysync->{abortbyfile} ) + { + abortbyfile( $mysync, $pidtokill ) ; + } + else + { + killpid( $pidtokill ) ; + } return ; } +sub abortbyfile +{ + my $mysync = shift @ARG ; + my $pidtokill = shift @ARG ; -sub tests_hashsynclocal { - note( 'Entering tests_hashsynclocal()' ) ; - - my $mysync = { - host1 => '', - user1 => '', - password1 => '', - host2 => '', - user2 => '', - password2 => '', - } ; + my $abortfile = abortfile( $mysync, $pidtokill ) ; + myprint( "touching $abortfile\n" ) ; + touch( $abortfile ) ; + return ; +} - is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no hashfile name' ) ; - $mysync->{ hashfile } = '' ; - is( undef, hashsynclocal( $mysync ), 'hashsynclocal: empty hashfile name' ) ; +sub tests_under_docker_context +{ + note( 'Entering tests_under_docker_context()' ) ; - $mysync->{ hashfile } = './noexist/rrr' ; - is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no exists hashfile dir' ) ; + is( undef, under_docker_context( ), 'under_docker_context: no args => undef' ) ; - SKIP: { - if ( 'MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) { skip( 'Tests only for non-root Unix', 1 ) ; } - $mysync->{ hashfile } = '/rrr' ; - is( undef, hashsynclocal( $mysync ), 'hashsynclocal: permission denied' ) ; - } - ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'hashsynclocal: mkpath W/tmp/tests/' ) ; - $mysync->{ hashfile } = 'W/tmp/tests/imapsync_hash' ; + my $mysync = { } ; + $mysync->{ dockercontext } = 1 ; + is( 1, under_docker_context( $mysync ), 'under_docker_context: --dockercontext => 1' ) ; + $mysync->{ dockercontext } = 0 ; + is( 0, under_docker_context( $mysync ), 'under_docker_context: --nodockercontext => 0' ) ; - ok( ! -e 'W/tmp/tests/imapsync_hash' || unlink 'W/tmp/tests/imapsync_hash', 'hashsynclocal: unlink W/tmp/tests/imapsync_hash' ) ; - ok( ! -e 'W/tmp/tests/imapsync_hash', 'hashsynclocal: verify there is no W/tmp/tests/imapsync_hash' ) ; - is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync, 'mukksyhpmbixkxkpjlqivmlqsulpictj' ), 'hashsynclocal: creating/reading W/tmp/tests/imapsync_hash' ) ; - # A second time now - is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync ), 'hashsynclocal: reading W/tmp/tests/imapsync_hash second time => same' ) ; + $mysync = { } ; + # Is not it a stupid test? + if ( under_docker_context( $mysync ) ) + { + is( 1, under_docker_context( $mysync ), 'under_docker_context: docker context => 1' ) ; + } + else + { + is( 0, under_docker_context( $mysync ), 'under_docker_context: not docker context => 0' ) ; + } - note( 'Leaving tests_hashsynclocal()' ) ; - return ; + note( 'Leaving tests_under_docker_context()' ) ; + return ; } -sub hashsynclocal { - my $mysync = shift ; - my $hashkey = shift ; # Optional, only there for tests - my $hashfile = $mysync->{ hashfile } ; - $hashfile = createhashfileifneeded( $hashfile, $hashkey ) ; - if ( ! $hashfile ) { - return ; - } - $hashkey = firstline( $hashfile ) ; - if ( ! $hashkey ) { - myprint( "No hashkey!\n" ) ; - return ; - } - my $hashsynclocal = hashsync( $mysync, $hashkey ) ; - return( $hashsynclocal ) ; -} +sub under_docker_context +{ + my $mysync = shift ; -sub tests_hashsync { - note( 'Entering tests_hashsync()' ) ; + if ( ! defined $mysync ) { return ; } + if ( defined $mysync->{ dockercontext } ) + { + return( $mysync->{ dockercontext } ) ; + } - is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( {}, q{} ), 'hashsync: empty args' ) ; - my $mysync ; - $mysync->{ host1 } = 'zzz' ; - is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ; - is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ; - $mysync->{ host2 } = 'zzz' ; - is( '15959573e4a86763253a7aedb1a2b0c60d133dc2', hashsync( $mysync, q{} ), 'hashsync: + host2 zzz => ' ) ; - is( 'b8d4ab541b209c75928528020ca28ee43488bd8f', hashsync( $mysync, 'A' ), 'hashsync: + hashkey A => ' ) ; - - note( 'Leaving tests_hashsync()' ) ; - return ; + if ( -e '/.dockerenv' ) + { + return 1 ; + } + else + { + return 0 ; + } + + return ; } -sub hashsync { - my $mysync = shift ; - my $hashkey = shift ; - my $mystring = join( q{}, - $mysync->{ host1 } || q{}, - $mysync->{ user1 } || q{}, - $mysync->{ password1 } || q{}, - $mysync->{ host2 } || q{}, - $mysync->{ user2 } || q{}, - $mysync->{ password2 } || q{}, - ) ; - my $hashsync = hmac_sha1_hex( $mystring, $hashkey ) ; - #myprint( "$hashsync\n" ) ; - return( $hashsync ) ; -} +sub docker_context +{ + my $mysync = shift ; + if ( ! under_docker_context( $mysync ) ) + { + return ; + } -sub tests_createhashfileifneeded { - note( 'Entering tests_createhashfileifneeded()' ) ; + output( $mysync, "Docker context detected with the file /.dockerenv\n" ) ; + # No pidfile by default - is( undef, createhashfileifneeded( ), 'createhashfileifneeded: no parameters => undef' ) ; + $mysync->{ pidfile } = defined( $mysync->{ pidfile } ) ? $mysync->{ pidfile } : q{} ; + # No log by default + if ( defined( $mysync->{ log } ) ) + { + output( $mysync, "Logging in Docker context. Be sure you added access to it with a mount or similar. See https://docs.docker.com/storage/volumes/\n" ) ; + } + else + { + output( $mysync, "No log by default in Docker context. Use --log to trigger logging to the logfile.\n" ) ; + $mysync->{ log } = 0 ; + } - note( 'Leaving tests_createhashfileifneeded()' ) ; + # In case something is written relatively to . + my $tmp_dir = "/var/tmp/uid_$EFFECTIVE_USER_ID" ; + mkpath( $tmp_dir ) ; # silly? No. it is for imapsync --version being ok. + do_valid_directory( $tmp_dir ) ; + output( $mysync, "Changing current directory to $tmp_dir\n" ) ; + chdir $tmp_dir ; + + return ; +} + +sub cgibegin +{ + my $mysync = shift ; + if ( ! under_cgi_context( $mysync ) ) { return ; } + require CGI ; + CGI->import( qw( -no_debug -utf8 ) ) ; + require CGI::Carp ; + CGI::Carp->import( qw( fatalsToBrowser ) ) ; + $mysync->{cgi} = CGI->new( ) ; return ; } -sub createhashfileifneeded { - my $hashfile = shift ; - my $hashkey = shift || rand32( ) ; +sub tests_under_cgi_context +{ + note( 'Entering tests_under_cgi_context()' ) ; - # no name - if ( ! $hashfile ) { - return ; - } - # already there - if ( -e -r $hashfile ) { - return $hashfile ; - } - # not creatable - if ( ! -w dirname( $hashfile ) ) { - return ; - } - # creatable - open my $FILE_HANDLE, '>', $hashfile - or do { - myprint( "Could not open $hashfile for writing. Check permissions or disk space." ) ; - return ; - } ; - myprint( "Writing random hashkey in $hashfile, once for all times\n" ) ; - print $FILE_HANDLE $hashkey ; - close $FILE_HANDLE ; - # Should be there now - if ( -e -r $hashfile ) { - return $hashfile ; + # $ENV{SERVER_SOFTWARE} = 'under imapsync' ; + do { + # Not in cgi context + delete local $ENV{SERVER_SOFTWARE} ; + is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ; + } ; + do { + # In cgi context + local $ENV{SERVER_SOFTWARE} = 'under imapsync' ; + is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ; + } ; + do { + # Not in cgi context + delete local $ENV{SERVER_SOFTWARE} ; + is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ; + } ; + do { + # In cgi context + local $ENV{SERVER_SOFTWARE} = 'under imapsync' ; + is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ; + } ; + note( 'Leaving tests_under_cgi_context()' ) ; + return ; +} + + +sub under_cgi_context +{ + my $mysync = shift ; + # Under cgi context + if ( $ENV{SERVER_SOFTWARE} ) { + return 1 ; + } + # Not in cgi context + return ; +} + +sub cgibuildheader +{ + my $mysync = shift ; + if ( ! under_cgi_context( $mysync ) ) { return ; } + + my $imapsync_runs = $mysync->{cgi}->cookie( 'imapsync_runs' ) || 0 ; + my $cookie = $mysync->{cgi}->cookie( + -name => 'imapsync_runs', + -value => 1 + $imapsync_runs, + -expires => '+20y', + -path => '/cgi-bin/imapsync', + ) ; + my $httpheader ; + if ( $mysync->{ abort } ) { + $httpheader = $mysync->{cgi}->header( + -type => 'text/plain; charset=UTF-8', + -status => '200 OK to abort syncing IMAP boxes' . ". Here is " . hostname(), + ) ; + }elsif( $mysync->{ loaddelay } ) { +# https://tools.ietf.org/html/rfc2616#section-10.5.4 +# 503 Service Unavailable +# The server is currently unable to handle the request due to a temporary overloading or maintenance of the server. + $httpheader = $mysync->{cgi}->header( + -type => 'text/plain; charset=UTF-8', + -status => '503 Service Unavailable' . ". Be back in $mysync->{ loaddelay } min. Load on " . hostname() . " is $mysync->{ loadavg }", + ) ; + }else{ + $httpheader = $mysync->{cgi}->header( + -type => 'text/plain; charset=UTF-8', + -status => '200 OK to sync IMAP boxes' . ". Load on " . hostname() . " is $mysync->{ loadavg }", + -cookie => $cookie, + ) ; } - # unknown failure + output_start( $mysync, $httpheader ) ; + + return ; +} + +sub cgiload +{ + # Exit on heavy load in CGI context + my $mysync = shift ; + if ( ! under_cgi_context( $mysync ) ) { return ; } + if ( $mysync->{ abort } ) { return ; } # keep going to abort since some ressources will be free soon + if ( $mysync->{ loaddelay } ) + { + $mysync->{nb_errors}++ ; + exit_clean( $mysync, $EX_UNAVAILABLE, + "Server is on heavy load. Be back in $mysync->{ loaddelay } min. Load is $mysync->{ loadavg }\n" + ) ; + } + return ; +} + +sub tests_set_umask +{ + note( 'Entering tests_set_umask()' ) ; + + my $save_umask = umask ; + + my $mysync = {} ; + if ( 'MSWin32' eq $OSNAME ) { + is( undef, set_umask( $mysync ), "set_umask: set failure to $UMASK_PARANO on MSWin32" ) ; + }else{ + is( 1, set_umask( $mysync ), "set_umask: set to $UMASK_PARANO" ) ; + } + + umask $save_umask ; + note( 'Leaving tests_set_umask()' ) ; + return ; +} + +sub set_umask +{ + my $mysync = shift ; + my $previous_umask = umask_str( ) ; + my $new_umask = umask_str( $UMASK_PARANO ) ; + output( $mysync, "Umask set with $new_umask (was $previous_umask)\n" ) ; + if ( $new_umask eq $UMASK_PARANO ) { + return 1 ; + } + return ; +} + +sub tests_umask_str +{ + note( 'Entering tests_umask_str()' ) ; + + my $save_umask = umask ; + + is( umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent' ) ; + is( my $save_umask_str = umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent + save' ) ; + is( '0000', umask_str( q{ } ), 'umask_str: q{ } => 0000' ) ; + is( '0000', umask_str( q{} ), 'umask_str: q{} => 0000' ) ; + is( '0000', umask_str( '0000' ), 'umask_str: 0000 => 0000' ) ; + is( '0000', umask_str( '0' ), 'umask_str: 0 => 0000' ) ; + is( '0200', umask_str( '0200' ), 'umask_str: 0200 => 0200' ) ; + is( '0400', umask_str( '0400' ), 'umask_str: 0400 => 0400' ) ; + is( '0600', umask_str( '0600' ), 'umask_str: 0600 => 0600' ) ; + + SKIP: { + if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 6 ) ; } + is( '0100', umask_str( '0100' ), 'umask_str: 0100 => 0100' ) ; + is( '0001', umask_str( '0001' ), 'umask_str: 0001 => 0001' ) ; + is( '0777', umask_str( '0777' ), 'umask_str: 0777 => 0777' ) ; + is( '0777', umask_str( '00777' ), 'umask_str: 00777 => 0777' ) ; + is( '0777', umask_str( ' 777 ' ), 'umask_str: 777 => 0777' ) ; + is( "$UMASK_PARANO", umask_str( $UMASK_PARANO ), "umask_str: UMASK_PARANO $UMASK_PARANO => $UMASK_PARANO" ) ; + } + + is( $save_umask_str, umask_str( $save_umask_str ), 'umask_str: restore with str' ) ; + is( $save_umask, umask, 'umask_str: umask is restored, controlled by direct umask' ) ; + is( $save_umask, umask $save_umask, 'umask_str: umask is restored by direct umask' ) ; + is( $save_umask, umask, 'umask_str: umask initial controlled by direct umask' ) ; + + note( 'Leaving tests_umask_str()' ) ; return ; } -sub tests_rand32 { - note( 'Entering tests_rand32()' ) ; +sub umask_str +{ + my $value = shift ; + + if ( defined $value ) { + umask oct( $value ) ; + } + my $current = umask ; + + return( sprintf( '%#04o', $current ) ) ; +} + +sub tests_umask +{ + note( 'Entering tests_umask()' ) ; + + my $save_umask ; + is( umask, umask, 'umask: umask is umask' ) ; + is( $save_umask = umask, umask, "umask: umask is umask again + save it: $save_umask" ) ; + is( $save_umask, umask oct(0000), 'umask: umask 0000' ) ; + is( oct(0000), umask, 'umask: umask is now 0000' ) ; + is( oct(0000), umask oct(777), 'umask: umask 0777 call, previous 0000' ) ; + + SKIP: { + if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 2 ) ; } + is( oct(777), umask, 'umask: umask is now 0777' ) ; + is( oct(777), umask $save_umask, "umask: umask $save_umask restore inital value, previous 0777" ) ; + } + + ok( defined umask $save_umask, "umask: umask $save_umask restore inital value, previous defined" ) ; + is( $save_umask, umask, 'umask: umask is umask restored' ) ; + note( 'Leaving tests_umask()' ) ; + + return ; +} + +sub buggyflagsregex +{ + # From /X analyse + # cut -d: -f1 Error_112_all_syncs.txt | xargs egrep -oih 'Invalid system flag [^( ]+' | sort | uniq -c | sort -g + my @buggyflagsregex = ( 's/\\\\RECEIPTCHECKED|\\\\Indexed|\\\\X-EON-HAS-ATTACHMENT|\\\\UNSEEN|\\\\ATTACHED|\\\\X-HAS-ATTACH|\\\\FORWARDED|\\\\FORWARD|\\\\X-FORWARDED|\\\\\$FORWARDED|\\\\PRIORITY|\\\\READRCPT//g' ) ; + return( @buggyflagsregex ) ; +} + +sub cgisetcontext +{ + my $mysync = shift ; + if ( ! under_cgi_context( $mysync ) ) { return ; } + + output( $mysync, "Under cgi context\n" ) ; + + + set_umask( $mysync ) ; + + # Remove all content in unsafe evaled options + @{ $mysync->{ regextrans2 } } = ( ) ; + + @{ $mysync->{ regexflag } } = buggyflagsregex( ) ; + + @regexmess = ( ) ; + @skipmess = ( ) ; + @pipemess = ( ) ; + $delete2foldersonly = undef ; + $delete2foldersbutnot = undef ; + $maxlinelengthcmd = undef ; + + # Set safe default values (I hope...) + + + #$mysync->{pidfile} = 'imapsync.pid' ; + $mysync->{ pidfilelocking } = 1 ; + $mysync->{ errorsmax } = $ERRORS_MAX_CGI ; + $modulesversion = 0 ; + $mysync->{ releasecheck } = defined $mysync->{ releasecheck } ? $mysync->{ releasecheck } : 1 ; + $usecache = 0 ; + $mysync->{ showpasswords } = 0 ; + $mysync->{ acc1 }->{ debugimap } = 0 ; + $mysync->{ acc2 }->{ debugimap } = 0 ; + + $mysync->{ acc1 }->{ reconnectretry } = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ; + $mysync->{ acc2 }->{ reconnectretry } = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ; + + $pipemesscheck = 0 ; + + $mysync->{ hashfile } = $CGI_HASHFILE ; + my $hashsynclocal = hashsynclocal( $mysync ) || die "Can not get hashsynclocal. Exiting\n" ; + + if ( $ENV{ 'NET_SERVER_SOFTWARE' } and ( $ENV{ 'NET_SERVER_SOFTWARE' } =~ /Net::Server::HTTP/ ) ) + { + # under local webserver + $cgidir = q{.} ; + } + else + { + $cgidir = $CGI_TMPDIR_TOP . '/' . $hashsynclocal ; + } + -d $cgidir or mkpath $cgidir or die "Can not create $cgidir: $OS_ERROR\n" ; + $mysync->{ tmpdir } = $cgidir ; + $mysync->{ logdir } = '' ; + + chdir $cgidir or die "Can not cd to $cgidir: $OS_ERROR\n" ; + cgioutputenvcontext( $mysync ) ; + $mysync->{ debug } and output( $mysync, 'Current directory is ' . getcwd( ) . "\n" ) ; + $mysync->{ debug } and output( $mysync, 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ; + $mysync->{ debug } and output( $mysync, 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ; + + $mysync->{ skipemptyfolders } = defined $mysync->{ skipemptyfolders } ? $mysync->{ skipemptyfolders } : 1 ; + + # Out of memory with messages over 1 GB ? + $mysync->{ maxsize } = defined $mysync->{ maxsize } ? $mysync->{ maxsize } : 1_000_000_000 ; + + # tail -f behaviour on by default + $mysync->{ tail } = defined $mysync->{ tail } ? $mysync->{ tail } : 1 ; + + # not sure it's for good + @useheader = qw( Message-Id Received ) ; + + # addheader on by default + $mysync->{ addheader } = defined $mysync->{ addheader } ? $mysync->{ addheader } : 1 ; + + # sync duplicates by default in cgi context + $mysync->{ syncduplicates } = defined $mysync->{ syncduplicates } ? $mysync->{ syncduplicates } : 1 ; + + # log the logfile name by default in cgi context + $mysync->{ loglogfile } = defined $mysync->{ loglogfile } ? $mysync->{ loglogfile } : 1 ; + return ; +} + +sub cgioutputenvcontext +{ + my $mysync = shift @ARG ; + + for my $envvar ( qw( REMOTE_ADDR REMOTE_HOST HTTP_REFERER HTTP_USER_AGENT SERVER_SOFTWARE SERVER_PORT HTTP_COOKIE ) ) { + + my $envval = $ENV{ $envvar } || q{} ; + if ( $envval ) { output( $mysync, "$envvar is $envval\n" ) } ; + } + + return ; +} + +sub announcelogfile +{ + my $mysync = shift ; + + if ( $mysync->{ log } ) + { + myprint( "Log file is $mysync->{ logfile } ( to change it, use --logfile path ; or use --nolog to turn off logging )\n" ) ; + loglogfile( $mysync ) ; + } + else + { + myprint( "No log file because of option --nolog\n" ) ; + } + return ; +} + + +sub loglogfile +{ + my $mysync = shift ; + if ( ! $mysync->{ loglogfile } ) { return ; } + if ( ! $mysync->{ log } ) { return ; } + + my $cwd = getcwd( ) ; + my $absolutelogfilepath ; + # Fixme: add case when the logfile name is already absolute + $absolutelogfilepath = "$cwd/$mysync->{ logfile }" ; + my $loglogfilename = '../list_all_logs_auto.txt' ; + myprint( "Writing log file name $absolutelogfilepath to $loglogfilename\n" ) ; + if ( open( my $fh, '>>', $loglogfilename ) ) + { + print $fh "$absolutelogfilepath\n" ; + close $fh ; + } + else + { + myprint( "Could not open loglogfile $loglogfilename $!\n" ) ; + } + return ; +} + + +sub checkselectable +{ + my $mysync = shift ; + + if ( $mysync->{ checkselectable } ) { + my @h1_folders_wanted_selectable ; + myprint( "Host1: Checking wanted folders are selectable. Use --nocheckselectable to avoid this check.\n" ) ; + foreach my $folder ( @{ $mysync->{ h1_folders_wanted } } ) + { + ( $mysync->{ debug } or $mysync->{ debugfolders } ) and myprint( "Checking $folder is selectable on host1\n" ) ; + # It does an imap command LIST "" $folder and then search for no \Noselect + if ( ! $mysync->{ imap1 }->selectable( $folder ) ) + { + myprint( "Host1: warning! ignoring folder $folder because it is not selectable\n" ) ; + }else + { + push @h1_folders_wanted_selectable, $folder ; + } + } + @{ $mysync->{ h1_folders_wanted } } = @h1_folders_wanted_selectable ; + ( $mysync->{ debug } or $mysync->{ debugfolders } ) + and myprint( 'Host1: checking folders took ', timenext( $mysync ), " s\n" ) ; + } + else + { + myprint( "Host1: Not checking that wanted folders are selectable. Use --checkselectable to force this check.\n" ) ; + } + return ; +} + +sub setcheckselectable +{ + my $mysync = shift ; + + my $h1_folders_wanted_nb = scalar @{ $mysync->{ h1_folders_wanted } } ; + # 152 because 98% of host1 accounts have less than 152 folders on /X service. + # command to get this value: + # datamash_file_op_index G_Host1_Nb_folders.txt perc:98 4 %16.1f + if ( ! defined $mysync->{ checkselectable } ) + { + if ( 152 >= $h1_folders_wanted_nb ) + { + $mysync->{ checkselectable } = 1 ; + }else{ + myprint( "Host1: Not checking that $h1_folders_wanted_nb wanted folders are selectable. Use --checkselectable to force this check.\n" ) ; + $mysync->{ checkselectable } = 0 ; + } + } + return ; +} + + + +sub debugsleep +{ + my $mysync = shift @ARG ; + if ( defined $mysync->{debugsleep} ) { + myprint( "Info: sleeping $mysync->{debugsleep}s\n" ) ; + sleep $mysync->{debugsleep} ; + } + return ; +} + +sub tests_foldersize +{ + note( 'Entering tests_foldersize()' ) ; + + is( undef, foldersize( ), 'foldersize: no args => undef' ) ; + + + #is_deeply( {}, {}, 'foldersize: a hash is a hash' ) ; + #is_deeply( [], [], 'foldersize: an array is an array' ) ; + note( 'Leaving tests_foldersize()' ) ; + return ; +} + + + +# Globals: +# $fetch_hash_set +# +sub foldersize +{ + # size of one folder + my ( $mysync, $side, $imap, $search_cmd, $abletosearch, $folder ) = @ARG ; + + if ( ! all_defined( $mysync, $side, $imap, $folder ) ) + { + return ; + } + + # FTGate is RFC buggy with EXAMINE it does not act as SELECT + #if ( ! $imap->examine( $folder ) ) { + if ( ! $imap->select( $folder ) ) { + my $error = join q{}, + "$side Folder $folder: Could not select: ", + $imap->LastError, "\n" ; + errors_incr( $mysync, $error ) ; + return ; + } + + if ( $imap->IsUnconnected( ) ) + { + return ; + } + + my $hash_ref = { } ; + my @msgs = select_msgs( $imap, undef, $search_cmd, $abletosearch, $folder ) ; + my $nb_msgs = scalar @msgs ; + my $biggest_in_folder = 0 ; + @{ $hash_ref }{ @msgs } = ( undef ) if @msgs ; + + + my $stot = 0 ; + + if ( $imap->IsUnconnected( ) ) + { + return ; + } + + if ( $nb_msgs > 0 and @msgs ) { + if ( $abletosearch ) { + if ( ! $imap->fetch_hash( \@msgs, 'RFC822.SIZE', $hash_ref) ) { + my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ; + errors_incr( $mysync, $error ) ; + return ; + } + } + else + { + my $fetch_hash_uids = $fetch_hash_set || "1:*" ; + if ( ! $imap->fetch_hash( $fetch_hash_uids, 'RFC822.SIZE', $hash_ref ) ) { + my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ; + errors_incr( $mysync, $error ) ; + return ; + } + } + for ( keys %{ $hash_ref } ) { + my $size = $hash_ref->{ $_ }->{ 'RFC822.SIZE' } ; + if ( defined $size ) + { + $stot += $size ; + $biggest_in_folder = max( $biggest_in_folder, $size ) ; + } + } + } + return( $stot, $nb_msgs, $biggest_in_folder ) ; + +} + + +# The old subroutine that performed just one side at a time. +# Still here for a while, until confident with sub foldersize_diff_compute() +sub foldersizes +{ + my ( $mysync, $side, $imap, $search_cmd, $abletosearch, @folders ) = @_ ; + my $total_size = 0 ; + my $total_nb = 0 ; + my $biggest_in_all = 0 ; + + my $nb_folders = scalar @folders ; + my $ct_folders = 0 ; # folder counter. + myprint( "++++ Calculating sizes of $nb_folders folders on $side\n" ) ; + foreach my $folder ( @folders ) { + my $stot = 0 ; + my $nb_msgs = 0 ; + my $biggest_in_folder = 0 ; + + $ct_folders++ ; + myprintf( "$side folder %7s %-35s", "$ct_folders/$nb_folders", jux_utf8( $folder ) ) ; + if ( 'Host2' eq $side and not exists $mysync->{h2_folders_all_UPPER}{ uc $folder } ) { + myprint( " does not exist yet\n") ; + next ; + } + if ( 'Host1' eq $side and not exists $h1_folders_all{ $folder } ) { + myprint( " does not exist\n" ) ; + next ; + } + + last if $imap->IsUnconnected( ) ; + + ( $stot, $nb_msgs, $biggest_in_folder ) = foldersize( $mysync, $side, $imap, $search_cmd, $abletosearch, $folder ) ; + + myprintf( ' Size: %9s', $stot ) ; + myprintf( ' Messages: %5s', $nb_msgs ) ; + myprintf( " Biggest: %9s\n", $biggest_in_folder ) ; + $total_size += $stot ; + $total_nb += $nb_msgs ; + $biggest_in_all = max( $biggest_in_all, $biggest_in_folder ) ; + } + myprintf( "%s Nb folders: %11s folders\n", $side, $nb_folders ) ; + myprintf( "%s Nb messages: %11s messages\n", $side, $total_nb ) ; + myprintf( "%s Total size: %11s bytes (%s)\n", $side, $total_size, bytes_display_string_bin( $total_size ) ) ; + myprintf( "%s Biggest message: %11s bytes (%s)\n", $side, $biggest_in_all, bytes_display_string_bin( $biggest_in_all ) ) ; + myprintf( "%s Time spent on sizing: %11.1f seconds\n", $side, timenext( $mysync ) ) ; + return( $total_nb, $total_size ) ; +} + + +sub foldersize_diff_present +{ + my $mysync = shift ; + my $folder1 = shift ; + my $folder2 = shift ; + my $counter_str = shift ; + my $force = shift ; + + my $values1_str ; + my $values2_str ; + + if ( ! defined $mysync->{ folder1 }->{ $folder1 }->{ size } || $force ) + { + foldersize_diff_compute( $mysync, $folder1, $folder2, $force ) ; + } + + # again, but this time it means no availaible data. + if ( defined $mysync->{ folder1 }->{ $folder1 }->{ size } ) + { + $values1_str = sprintf( "Size: %9s Messages: %5s Biggest: %9s\n", + $mysync->{ folder1 }->{ $folder1 }->{ size }, + $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs }, + $mysync->{ folder1 }->{ $folder1 }->{ biggest }, + ) ; + } + else + { + $values1_str = " does not exist\n" ; + } + + if ( defined $mysync->{ folder2 }->{ $folder2 }->{ size } ) + { + $values2_str = sprintf( "Size: %9s Messages: %5s Biggest: %9s\n", + $mysync->{ folder2 }->{ $folder2 }->{ size }, + $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs }, + $mysync->{ folder2 }->{ $folder2 }->{ biggest }, + ) ; + } + else + { + $values2_str = " does not exist yet\n" ; + } + + myprintf( "Host1 folder %7s %-35s %s", + "$counter_str", + jux_utf8( $folder1 ), + $values1_str + ) ; + + myprintf( "Host2 folder %7s %-35s %s", + "$counter_str", + jux_utf8( $folder2 ), + $values2_str + ) ; + + myprintf( "Host2-Host1 %7s %-35s %9s %5s %9s\n\n", + "", + "", + $mysync->{ folder1 }->{ $folder1 }->{ size_diff }, + $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs_diff }, + $mysync->{ folder1 }->{ $folder1 }->{ biggest_diff }, + + ) ; + + + + + return ; +} + +sub foldersize_diff_compute +{ + my $mysync = shift ; + my $folder1 = shift ; + my $folder2 = shift ; + my $force = shift ; + + + + my ( $size_1, $nb_msgs_1, $biggest_1 ) ; + # memoization + if ( + exists $h1_folders_all{ $folder1 } + && + ( + ! defined $mysync->{ folder1 }->{ $folder1 }->{ size } + || $force + ) + ) + { + #myprint( "foldersize folder1 $h1_folders_all{ $folder1 }\n" ) ; + ( $size_1, $nb_msgs_1, $biggest_1 ) = + foldersize( $mysync, + 'Host1', + $mysync->{ imap1 }, + $mysync->{ search1 }, + $mysync->{ abletosearch1 }, + $folder1 + ) ; + $mysync->{ folder1 }->{ $folder1 }->{ size } = $size_1 ; + $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } = $nb_msgs_1 ; + $mysync->{ folder1 }->{ $folder1 }->{ biggest } = $biggest_1 ; + } + else + { + $size_1 = $mysync->{ folder1 }->{ $folder1 }->{ size } ; + $nb_msgs_1 = $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } ; + $biggest_1 = $mysync->{ folder1 }->{ $folder1 }->{ biggest } ; + + } + + + my ( $size_2, $nb_msgs_2, $biggest_2 ) ; + if ( + exists $mysync->{ h2_folders_all_UPPER }{ uc $folder2 } + && + ( + ! defined $mysync->{ folder2 }->{ $folder2 }->{ size } + || $force + ) + ) + { + #myprint( "foldersize folder2\n" ) ; + ( $size_2, $nb_msgs_2, $biggest_2 ) = + foldersize( $mysync, + 'Host2', + $mysync->{ imap2 }, + $mysync->{ search2 }, + $mysync->{ abletosearch2 }, + $folder2 + ) ; + + $mysync->{ folder2 }->{ $folder2 }->{ size } = $size_2 ; + $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } = $nb_msgs_2 ; + $mysync->{ folder2 }->{ $folder2 }->{ biggest } = $biggest_2 ; + } + else + { + $size_2 = $mysync->{ folder2 }->{ $folder2 }->{ size } ; + $nb_msgs_2 = $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } ; + $biggest_2 = $mysync->{ folder2 }->{ $folder2 }->{ biggest } ; + + } + + + my $size_diff = diff( $size_2, $size_1 ) ; + my $nb_msgs_diff = diff( $nb_msgs_2, $nb_msgs_1 ) ; + my $biggest_diff = diff( $biggest_2, $biggest_1 ) ; + + $mysync->{ folder1 }->{ $folder1 }->{ size_diff } = $size_diff ; + $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs_diff } = $nb_msgs_diff ; + $mysync->{ folder1 }->{ $folder1 }->{ biggest_diff } = $biggest_diff ; + + # It's redundant but easier to access later + $mysync->{ folder2 }->{ $folder2 }->{ size_diff } = $size_diff ; + $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs_diff } = $nb_msgs_diff ; + $mysync->{ folder2 }->{ $folder2 }->{ biggest_diff } = $biggest_diff ; + + return ; +} + +sub diff +{ + my $x = shift ; + my $y = shift ; + + $x ||= 0 ; + $y ||= 0 ; + + return $x - $y ; +} + +sub add +{ + my $x = shift ; + my $y = shift ; + + $x ||= 0 ; + $y ||= 0 ; + + return $x + $y ; +} + +sub tests_checknoabletosearch +{ + note( 'Entering checknoabletosearch()' ) ; + + is( undef, checknoabletosearch( ), 'checknoabletosearch: no args => undef' ) ; + + note( 'Leaving checknoabletosearch()' ) ; + return ; +} + + + + +sub checknoabletosearch +{ + # call example: checknoabletosearch( $sync, $sync->{ imap1 }, 'INBOX', 'Host1' ) ; + # output: + # * undef if something is not ok to decide + # * 1 if SEARCH ALL failed + + my( $mysync, $imap, $folder, $HostX ) = @ARG ; + + if ( ! all_defined( $mysync, $imap, $folder, $HostX ) ) + { + return ; + } + + myprint( "$HostX: checking if SEARCH ALL works on $folder\n" ) ; + if ( ! select_folder( $mysync, $imap, $folder, $HostX ) ) + { + myprint( "$HostX: can not SELECT folder [$folder]\n" ) ; + return ; + } + my $count_from_select = count_from_select( $imap->History ) ; + myprint( "$HostX: folder [$folder] has $count_from_select messages mentioned by SELECT\n" ) ; + + my $msgs_all = $imap->messages( ) ; + if ( ! $msgs_all ) + { + myprint( "$HostX: can not SEARCH ALL folder [$folder]\n" ) ; + myprint( "$HostX: ", $imap->LastError(), "\n" ) ; + return 1 ; + } + + my $count_from_search_all = scalar( @{ $msgs_all } ) ; + myprint( "$HostX: folder [$folder] has $count_from_search_all messages found by SEARCH ALL\n" ) ; + + if ( $count_from_select == $count_from_search_all ) + { + myprint( "$HostX: folder [$folder] has the same messages count ($count_from_select) by SELECT and SEARCH ALL\n" ) ; + } + else + { + myprint( "$HostX: Warning, folder [$folder] has not the same count by SELECT ($count_from_select) and SEARCH ALL ($count_from_search_all)\n" ) ; + return 1 ; + } + + return ; +} + + +sub foldersizes_diff_list +{ + my $mysync = shift ; + my $force = shift ; + + my @folders = @{ $mysync->{h1_folders_wanted} } ; + my $nb_folders = scalar @folders ; + my $ct_folders = 0 ; # folder counter. + + foreach my $folder1 ( @folders ) + { + $ct_folders++ ; + my $counter_str = "$ct_folders/$nb_folders" ; + my $folder2 = imap2_folder_name( $mysync, $folder1 ) ; + foldersize_diff_present( $mysync, $folder1, $folder2, $counter_str, $force ) ; + } + + return ; +} + +sub foldersizes_total +{ + my $mysync = shift ; + + my @folders_1 = @{ $mysync->{h1_folders_wanted} } ; + my @folders_2 = @h2_folders_from_1_wanted ; + + my $nb_folders_1 = scalar( @folders_1 ) ; + my $nb_folders_2 = scalar( @folders_2 ) ; + + my ( $total_size_1, $total_nb_1, $biggest_in_all_1 ) = ( 0, 0, 0 ) ; + my ( $total_size_2, $total_nb_2, $biggest_in_all_2 ) = ( 0, 0, 0 ) ; + + foreach my $folder1 ( @folders_1 ) + { + $total_size_1 = add( $total_size_1, $mysync->{ folder1 }->{ $folder1 }->{ size } ) ; + $total_nb_1 = add( $total_nb_1, $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } ) ; + $biggest_in_all_1 = max( $biggest_in_all_1 , $mysync->{ folder1 }->{ $folder1 }->{ biggest } ) ; + } + + foreach my $folder2 ( @folders_2 ) + { + $total_size_2 = add( $total_size_2, $mysync->{ folder2 }->{ $folder2 }->{ size } ) ; + $total_nb_2 = add( $total_nb_2, $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } ) ; + $biggest_in_all_2 = max( $biggest_in_all_2 , $mysync->{ folder2 }->{ $folder2 }->{ biggest } ) ; + + } + + myprintf( "Host1 Nb folders: %11s folders\n", $nb_folders_1 ) ; + myprintf( "Host2 Nb folders: %11s folders\n", $nb_folders_2 ) ; + myprint( "\n" ) ; + myprintf( "Host1 Nb messages: %11s messages\n", $total_nb_1 ) ; + myprintf( "Host2 Nb messages: %11s messages\n", $total_nb_2 ) ; + myprint( "\n" ) ; + myprintf( "Host1 Total size: %11s bytes (%s)\n", $total_size_1, bytes_display_string_bin( $total_size_1 ) ) ; + myprintf( "Host2 Total size: %11s bytes (%s)\n", $total_size_2, bytes_display_string_bin( $total_size_2 ) ) ; + myprint( "\n" ) ; + myprintf( "Host1 Biggest message: %11s bytes (%s)\n", $biggest_in_all_1, bytes_display_string_bin( $biggest_in_all_1 ) ) ; + myprintf( "Host2 Biggest message: %11s bytes (%s)\n", $biggest_in_all_2, bytes_display_string_bin( $biggest_in_all_2 ) ) ; + myprint( "\n" ) ; + myprintf( "Time spent on sizing: %11.1f seconds\n", timenext( $mysync ) ) ; + + my @total_1_2 = ( $total_nb_1, $total_size_1, $total_nb_2, $total_size_2 ) ; + return @total_1_2 ; +} + +sub foldersizesatend_old +{ + my $mysync = shift ; + timenext( $mysync ) ; + return if ( $mysync->{imap1}->IsUnconnected( ) ) ; + return if ( $mysync->{imap2}->IsUnconnected( ) ) ; + # Get all folders on host2 again since new were created + @h2_folders_all = sort $mysync->{imap2}->folders(); + for ( @h2_folders_all ) { + $h2_folders_all{ $_ } = 1 ; + $mysync->{h2_folders_all_UPPER}{ uc $_ } = 1 ; + } ; + ( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( $mysync, 'Host1', $mysync->{imap1}, $mysync->{ search1 }, $mysync->{abletosearch1}, @{ $mysync->{h1_folders_wanted} } ) ; + ( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( $mysync, 'Host2', $mysync->{imap2}, $mysync->{ search2 }, $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ; + if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) { + my $error = "Failure getting foldersizes, final differences will not be calculated\n" ; + errors_incr( $mysync, $error ) ; + } + return ; +} + +sub foldersizesatend +{ + my $mysync = shift ; + timenext( $mysync ) ; + return if ( $mysync->{imap1}->IsUnconnected( ) ) ; + return if ( $mysync->{imap2}->IsUnconnected( ) ) ; + # Get all folders on host2 again since new were created + @h2_folders_all = sort $mysync->{imap2}->folders(); + for ( @h2_folders_all ) { + $h2_folders_all{ $_ } = 1 ; + $mysync->{h2_folders_all_UPPER}{ uc $_ } = 1 ; + } ; + + + foldersizes_diff_list( $mysync, $FORCE ) ; + + ( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) + = foldersizes_total( $mysync ) ; + + + if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) { + my $error = "Failure getting foldersizes, final differences will not be calculated\n" ; + errors_incr( $mysync, $error ) ; + } + return ; +} + + +sub foldersizes_at_the_beggining +{ + my $mysync = shift ; + + myprint( << 'END_SIZE' ) ; + +Folders sizes before the synchronization. +You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend" +but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy. +END_SIZE + + foldersizes_diff_list( $mysync ) ; + + ( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start }, + $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) + = foldersizes_total( $mysync ) ; + + + if ( not all_defined( + $mysync->{ h1_nb_msg_start }, + $mysync->{ h1_bytes_start }, + $mysync->{ h2_nb_msg_start }, + $mysync->{ h2_bytes_start } ) ) + { + my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ; + errors_incr( $mysync, $error ) ; + $mysync->{ foldersizes } = 0 ; + $mysync->{ foldersizesatend } = 0 ; + return ; + } + + my $h2_bytes_limit = $mysync->{ acc2 }->{quota_limit_bytes} || 0 ; + if ( $h2_bytes_limit and ( $h2_bytes_limit < $mysync->{ h1_bytes_start } ) ) + { + my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $mysync->{ h1_bytes_start } / $h2_bytes_limit ) ; + my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $mysync->{ h1_bytes_start } bytes / $h2_bytes_limit bytes )\n" ; + errors_incr( $mysync, $error ) ; + } + return ; +} + + +# Globals: +# @h2_folders_from_1_wanted + +sub foldersizes_at_the_beggining_old +{ + my $mysync = shift ; + + myprint( << 'END_SIZE' ) ; + +Folders sizes before the synchronization. +You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend" +but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy. +END_SIZE + + ( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start } ) = + foldersizes( $mysync, 'Host1', $mysync->{imap1}, $mysync->{ search1 }, + $mysync->{abletosearch1}, @{ $mysync->{h1_folders_wanted} } ) ; + ( $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) = + foldersizes( $mysync, 'Host2', $mysync->{imap2}, $mysync->{ search2 }, + $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ; + + if ( not all_defined( $mysync->{ h1_nb_msg_start }, + $mysync->{ h1_bytes_start }, $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) ) + { + my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ; + errors_incr( $mysync, $error ) ; + $mysync->{ foldersizes } = 0 ; + $mysync->{ foldersizesatend } = 0 ; + return ; + } + + my $h2_bytes_limit = $mysync->{ acc2 }->{quota_limit_bytes} || 0 ; + if ( $h2_bytes_limit and ( $h2_bytes_limit < $mysync->{ h1_bytes_start } ) ) + { + my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $mysync->{ h1_bytes_start } / $h2_bytes_limit ) ; + my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $mysync->{ h1_bytes_start } bytes / $h2_bytes_limit bytes )\n" ; + errors_incr( $mysync, $error ) ; + } + return ; +} + + +sub tests_total_bytes_max_reached +{ + note( 'Entering tests_total_bytes_max_reached()' ) ; + + is( undef, total_bytes_max_reached( ), 'total_bytes_max_reached: no args => undef' ) ; + + my $mysync = {} ; + is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: no exitwhenover => undef' ) ; + + $mysync->{ exitwhenover } = 300 ; + is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but no total_bytes_transferred => undef' ) ; + + $mysync->{ total_bytes_transferred } = 200 ; + is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but total_bytes_transferred 200 => undef' ) ; + + $mysync->{ total_bytes_transferred } = 400 ; + is( 1, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but total_bytes_transferred 400 => 1' ) ; + + + + note( 'Leaving tests_total_bytes_max_reached()' ) ; + return ; +} + + +sub total_bytes_max_reached +{ + my $mysync = shift ; + + if ( ! defined $mysync ) { return ; } + + if ( ! $mysync->{ exitwhenover } ) + { + return ; + } + + if ( ! $mysync->{ total_bytes_transferred } ) + { + return ; + } + + if ( $mysync->{ total_bytes_transferred } >= $mysync->{ exitwhenover } ) + { + my $error = "Maximum bytes transferred reached, $mysync->{total_bytes_transferred} >= $mysync->{ exitwhenover }, ending sync\n" ; + errors_incr( $mysync, $error ) ; + return( 1 ) ; + } + return ; +} + + +sub tests_mock_capability +{ + note( 'Entering tests_mock_capability()' ) ; + + my $myimap ; + ok( $myimap = mock_capability( ), + 'mock_capability: (1) no args => a Test::MockObject' + ) ; + ok( $myimap->isa( 'Test::MockObject' ), + 'mock_capability: (2) no args => a Test::MockObject' + ) ; + + is( undef, $myimap->capability( ), + 'mock_capability: (3) no args => capability undef' + ) ; + + ok( mock_capability( $myimap ), + 'mock_capability: (1) one arg => MockObject' + ) ; + + is( undef, $myimap->capability( ), + 'mock_capability: (2) one arg OO style => capability undef' + ) ; + + ok( mock_capability( $myimap, $NUMBER_123456 ), + 'mock_capability: (1) two args 123456 => capability 123456' + ) ; + + is( $NUMBER_123456, $myimap->capability( ), + 'mock_capability: (2) two args 123456 => capability 123456' + ) ; + + ok( mock_capability( $myimap, 'ABCD' ), + 'mock_capability: (1) two args ABCD => capability ABCD' + ) ; + is( 'ABCD', $myimap->capability( ), + 'mock_capability: (2) two args ABCD => capability ABCD' + ) ; + + ok( mock_capability( $myimap, [ 'ABCD' ] ), + 'mock_capability: (1) two args [ ABCD ] => capability [ ABCD ]' + ) ; + is_deeply( [ 'ABCD' ], $myimap->capability( ), + 'mock_capability: (2) two args [ ABCD ] => capability [ ABCD ]' + ) ; + + ok( mock_capability( $myimap, [ 'ABC', 'DEF' ] ), + 'mock_capability: (1) two args [ ABC, DEF ] => capability [ ABC, DEF ]' + ) ; + is_deeply( [ 'ABC', 'DEF' ], $myimap->capability( ), + 'mock_capability: (2) two args [ ABC, DEF ] => capability capability [ ABC, DEF ]' + ) ; + + ok( mock_capability( $myimap, 'ABC', 'DEF' ), + 'mock_capability: (1) two args ABC, DEF => capability [ ABC, DEF ]' + ) ; + is_deeply( [ 'ABC', 'DEF' ], [ $myimap->capability( ) ], + 'mock_capability: (2) two args ABC, DEF => capability capability [ ABC, DEF ]' + ) ; + + ok( mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ), + 'mock_capability: (1) two args IMAP4rev1, APPENDLIMIT=123456 => capability [ IMAP4rev1, APPENDLIMIT=123456 ]' + ) ; + is_deeply( [ 'IMAP4rev1', 'APPENDLIMIT=123456' ], [ $myimap->capability( ) ], + 'mock_capability: (2) two args IMAP4rev1, APPENDLIMIT=123456 => capability capability [ IMAP4rev1, APPENDLIMIT=123456 ]' + ) ; + + note( 'Leaving tests_mock_capability()' ) ; + return ; +} + +sub sig_install_toggle_sleep +{ + my $mysync = shift ; + if ( 'MSWin32' ne $OSNAME ) { + #myprint( "sig_install( $mysync, \&toggle_sleep, 'USR1' )\n" ) ; + sig_install( $mysync, 'toggle_sleep', 'USR1' ) ; + } + #myprint( "Leaving sig_install_toggle_sleep\n" ) ; + return ; +} + + +sub mock_capability +{ + my $myimap = shift ; + my @has_capability_value = @ARG ; + my ( $has_capability_value ) = @has_capability_value ; + + if ( ! $myimap ) + { + require_ok( "Test::MockObject" ) ; + $myimap = Test::MockObject->new( ) ; + } + + $myimap->mock( + 'capability', + sub { return wantarray ? + @has_capability_value + : $has_capability_value ; + } + ) ; + + return $myimap ; +} + + +sub tests_capability_of +{ + note( 'Entering tests_capability_of()' ) ; + + is( undef, capability_of( ), + 'capability_of: no args => undef' ) ; + + my $myimap ; + is( undef, capability_of( $myimap ), + 'capability_of: undef => undef' ) ; + + + $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ; + + is( undef, capability_of( $myimap, 'CACA' ), + 'capability_of: two args unknown capability => undef' ) ; + + + is( $NUMBER_123456, capability_of( $myimap, 'APPENDLIMIT' ), + 'capability_of: two args APPENDLIMIT 123456 => 123456 yeah!' ) ; + + note( 'Leaving tests_capability_of()' ) ; + return ; +} + + +sub capability_of +{ + my $imap = shift || return ; + my $capability_keyword = shift || return ; + + my @capability = $imap->capability ; + + if ( ! @capability ) { return ; } + my $capability_value = search_in_array( $capability_keyword, @capability ) ; + + return $capability_value ; +} + + +sub tests_search_in_array +{ + note( 'Entering tests_search_in_array()' ) ; + + is( undef, search_in_array( 'KA' ), + 'search_in_array: no array => undef ' ) ; + + is( 'VA', search_in_array( 'KA', ( 'KA=VA' ) ), + 'search_in_array: KA KA=VA => VA ' ) ; + + is( 'VA', search_in_array( 'KA', ( 'KA=VA', 'KB=VB' ) ), + 'search_in_array: KA KA=VA KB=VB => VA ' ) ; + + is( 'VB', search_in_array( 'KB', ( 'KA=VA', 'KB=VB' ) ), + 'search_in_array: KA=VA KB=VB => VB ' ) ; + + note( 'Leaving tests_search_in_array()' ) ; + return ; +} + +sub search_in_array +{ + my ( $key, @array ) = @ARG ; + + foreach my $item ( @array ) + { + + if ( $item =~ /([^=]+)=(.*)/ ) + { + if ( $1 eq $key ) + { + return $2 ; + } + } + } + + return ; +} + + + + +sub tests_appendlimit_from_capability +{ + note( 'Entering tests_appendlimit_from_capability()' ) ; + + is( undef, appendlimit_from_capability( ), + 'appendlimit_from_capability: no args => undef' + ) ; + + my $myimap ; + is( undef, appendlimit_from_capability( $myimap ), + 'appendlimit_from_capability: undef arg => undef' + ) ; + + + $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ; + + # Normal behavior + is( $NUMBER_123456, appendlimit_from_capability( $myimap ), + 'appendlimit_from_capability: APPENDLIMIT=123456 => 123456' + ) ; + + # Not a number + $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=ABC' ) ; + + is( undef, appendlimit_from_capability( $myimap ), + 'appendlimit_from_capability: not a number => undef' + ) ; + + note( 'Leaving tests_appendlimit_from_capability()' ) ; + return ; +} + + +sub appendlimit_from_capability +{ + my $myimap = shift ; + if ( ! $myimap ) + { + myprint( "Warn: no imap with call to appendlimit_from_capability\n" ) ; + return ; + } + + #myprint( Data::Dumper->Dump( [ \$myimap ] ) ) ; + my $appendlimit = capability_of( $myimap, 'APPENDLIMIT' ) ; + #myprint( "has_capability APPENDLIMIT $appendlimit\n" ) ; + if ( is_integer( $appendlimit ) ) + { + return $appendlimit ; + } + return ; +} + + +sub tests_appendlimit +{ + note( 'Entering tests_appendlimit()' ) ; + + is( undef, appendlimit( ), + 'appendlimit: no args => undef' + ) ; + + my $mysync = { } ; + + is( undef, appendlimit( $mysync ), + 'appendlimit: no imap2 => undef' + ) ; + + my $myimap ; + $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ; + + $mysync->{ imap2 } = $myimap ; + + is( 123456, appendlimit( $mysync ), + 'appendlimit: imap2 with APPENDLIMIT=123456 => 123456' + ) ; + + note( 'Leaving tests_appendlimit()' ) ; + return ; +} + +sub appendlimit +{ + my $mysync = shift || return ; + my $myimap = $mysync->{ imap2 } ; + + my $appendlimit = appendlimit_from_capability( $myimap ) ; + if ( defined $appendlimit ) + { + myprint( "Host2: found APPENDLIMIT=$appendlimit in CAPABILITY (use --appendlimit xxxx to override this automatic setting)\n" ) ; + return $appendlimit ; + } + return ; + +} + + +sub tests_maxsize_setting +{ + note( 'Entering tests_maxsize_setting()' ) ; + + is( undef, maxsize_setting( ), + 'maxsize_setting: no args => undef' + ) ; + + my $mysync ; + + is( undef, maxsize_setting( $mysync ), + 'maxsize_setting: undef arg => undef' + ) ; + + $mysync = { } ; + $mysync->{ maxsize } = $NUMBER_123456 ; + + # --maxsize alone + is( $NUMBER_123456, maxsize_setting( $mysync ), + 'maxsize_setting: --maxsize 123456 alone => 123456' + ) ; + + + $mysync = { } ; + my $myimap ; + + $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ; + $mysync->{ imap2 } = $myimap ; + + # APPENDLIMIT alone + is( $NUMBER_654321, maxsize_setting( $mysync ), + 'maxsize_setting: APPENDLIMIT 654321 alone => 654321' + ) ; + + is( $NUMBER_654321, $mysync->{ maxsize }, + 'maxsize_setting: APPENDLIMIT 654321 alone => maxsize 654321' + ) ; + + # APPENDLIMIT with --appendlimit => --appendlimit wins + $mysync->{ appendlimit } = $NUMBER_123456 ; + + is( $NUMBER_123456, maxsize_setting( $mysync ), + 'maxsize_setting: APPENDLIMIT 654321 + --appendlimit 123456 => 123456' + ) ; + + is( $NUMBER_123456, $mysync->{ maxsize }, + 'maxsize_setting: APPENDLIMIT 654321 + --appendlimit 123456 => maxsize 123456' + ) ; + + # Fresh + $mysync = { } ; + $mysync->{ imap2 } = $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ; + + # Case: "APPENDLIMIT >= --maxsize" => maxsize. + $mysync->{ maxsize } = $NUMBER_123456 ; + + is( $NUMBER_123456, maxsize_setting( $mysync ), + 'maxsize_setting: APPENDLIMIT 654321 --maxsize 123456 => 123456' + ) ; + + # Case: "APPENDLIMIT < --maxsize" => APPENDLIMIT. + + + # Fresh + $mysync = { } ; + $mysync->{ imap2 } = $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ; + $mysync->{ maxsize } = $NUMBER_654321 ; + + is( $NUMBER_123456, maxsize_setting( $mysync ), + 'maxsize_setting: APPENDLIMIT 123456 --maxsize 654321 => 123456 ' + ) ; + + # Now --truncmess stuff + + note( 'Leaving tests_maxsize_setting()' ) ; + + return ; +} + +# Three variables to take account of +# appendlimit (given by --appendlimit or CAPABILITY...) +# maxsize +# truncmess + +sub maxsize_setting +{ + my $mysync = shift || return ; + + if ( defined $mysync->{ appendlimit } ) + { + myprint( "Host2: Getting appendlimit from --appendlimit $mysync->{ appendlimit }\n" ) ; + } + else + { + $mysync->{ appendlimit } = appendlimit( $mysync ) ; + } + + + if ( all_defined( $mysync->{ appendlimit }, $mysync->{ maxsize } ) ) + { + my $min_maxsize_appendlimit = min( $mysync->{ maxsize }, $mysync->{ appendlimit } ) ; + myprint( "Host2: Setting maxsize to $min_maxsize_appendlimit (min of --maxsize $mysync->{ maxsize } and appendlimit $mysync->{ appendlimit }\n" ) ; + $mysync->{ maxsize } = $min_maxsize_appendlimit ; + return $mysync->{ maxsize } ; + } + elsif ( defined $mysync->{ appendlimit } ) + { + myprint( "Host2: Setting maxsize to appendlimit $mysync->{ appendlimit }\n" ) ; + $mysync->{ maxsize } = $mysync->{ appendlimit } ; + return $mysync->{ maxsize } ; + }elsif ( defined $mysync->{ maxsize } ) + { + return $mysync->{ maxsize } ; + }else + { + return ; + } +} + + + + +sub all_defined +{ + if ( not @ARG ) { + return 0 ; + } + foreach my $elem ( @ARG ) { + if ( not defined $elem ) { + return 0 ; + } + } + return 1 ; +} + +sub tests_all_defined +{ + note( 'Entering tests_all_defined()' ) ; + + is( 0, all_defined( ), 'all_defined: no param => 0' ) ; + is( 0, all_defined( () ), 'all_defined: void list => 0' ) ; + is( 0, all_defined( undef ), 'all_defined: undef => 0' ) ; + is( 0, all_defined( undef, undef ), 'all_defined: undef => 0' ) ; + is( 0, all_defined( 1, undef ), 'all_defined: 1 undef => 0' ) ; + is( 0, all_defined( undef, 1 ), 'all_defined: undef 1 => 0' ) ; + is( 1, all_defined( 1, 1 ), 'all_defined: 1 1 => 1' ) ; + is( 1, all_defined( (1, 1) ), 'all_defined: (1 1) => 1' ) ; + + note( 'Leaving tests_all_defined()' ) ; + return ; +} + + +sub tests_hashsynclocal +{ + note( 'Entering tests_hashsynclocal()' ) ; + + my $mysync = { + host1 => q{}, + user1 => q{}, + password1 => q{}, + host2 => q{}, + user2 => q{}, + password2 => q{}, + } ; + + is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no hashfile name' ) ; + + $mysync->{ hashfile } = q{} ; + is( undef, hashsynclocal( $mysync ), 'hashsynclocal: empty hashfile name' ) ; + + $mysync->{ hashfile } = './noexist/rrr' ; + is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no exists hashfile dir' ) ; + + SKIP: { + if ( 'MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) { skip( 'Tests only for non-root Unix', 1 ) ; } + $mysync->{ hashfile } = '/rrr' ; + is( undef, hashsynclocal( $mysync ), 'hashsynclocal: permission denied' ) ; + } + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'hashsynclocal: mkpath W/tmp/tests/' ) ; + $mysync->{ hashfile } = 'W/tmp/tests/imapsync_hash' ; + + ok( ! -e 'W/tmp/tests/imapsync_hash' || unlink 'W/tmp/tests/imapsync_hash', 'hashsynclocal: unlink W/tmp/tests/imapsync_hash' ) ; + ok( ! -e 'W/tmp/tests/imapsync_hash', 'hashsynclocal: verify there is no W/tmp/tests/imapsync_hash' ) ; + is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync, 'mukksyhpmbixkxkpjlqivmlqsulpictj' ), 'hashsynclocal: creating/reading W/tmp/tests/imapsync_hash' ) ; + # A second time now + is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync ), 'hashsynclocal: reading W/tmp/tests/imapsync_hash second time => same' ) ; + + note( 'Leaving tests_hashsynclocal()' ) ; + return ; +} + +sub hashsynclocal +{ + my $mysync = shift ; + my $hashkey = shift ; # Optional, only there for tests + my $hashfile = $mysync->{ hashfile } ; + $hashfile = createhashfileifneeded( $hashfile, $hashkey ) ; + if ( ! $hashfile ) { + return ; + } + $hashkey = firstline( $hashfile ) ; + if ( ! $hashkey ) { + myprint( "No hashkey!\n" ) ; + return ; + } + my $hashsynclocal = hashsync( $mysync, $hashkey ) ; + return( $hashsynclocal ) ; + +} + +sub tests_hashsync +{ + note( 'Entering tests_hashsync()' ) ; + + is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( ), 'hashsync: no args' ) ; + + is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( {}, q{} ), 'hashsync: empty args' ) ; + my $mysync ; + $mysync->{ host1 } = 'zzz' ; + is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ; + is( '6a7b451ac99eab1531ad8e6cd544b32420c552ac', hashsync( $mysync, q{A} ), 'hashsync: host1 zzz => ' ) ; + $mysync->{ host2 } = 'zzz' ; + is( '15959573e4a86763253a7aedb1a2b0c60d133dc2', hashsync( $mysync, q{} ), 'hashsync: + host2 zzz => ' ) ; + is( 'b8d4ab541b209c75928528020ca28ee43488bd8f', hashsync( $mysync, 'A' ), 'hashsync: + hashkey A => ' ) ; + + $mysync = undef ; + is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( $mysync, q{} ), 'hashsync: undef $mysync' ) ; + $mysync->{ password1 } = 'abcd' ; + is( 'afa29ab8534495251ac8346a985717c54bc49c26', hashsync( $mysync, q{} ), 'hashsync: password1: abcd' ) ; + + # A user reported a massive failure on /X (Thomas V. 21/04/2020 รƒ 21:41 Subject: Error) + # "Wide character in subroutine entry at /usr/local/lib/perl5/site_perl/Digest/HMAC.pm" + # I can reproduce it now + + + # The eval is there to avoid a complete crash + # this one is fatal so it is commented + # is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', 1 / 0 , 'hashsync: 1 / 0 fatal' ) ; + + my $eval ; + # this one is not fatal + is( undef, $eval = eval { 1 / 0 } , 'hashsync: 1/0 not fatal' ) ; + # this one neither + $mysync->{ password1 } = 'รƒโ€“' ; + is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', $eval = eval { hashsync( $mysync, q{} ) } , 'hashsync: password1: รƒโ€“ with eval' ) ; + + $mysync->{ password1 } = 'รƒโ€“' ; + is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hashsync( $mysync, q{} ), 'hashsync: password1: รƒโ€“ without eval' ) ; + + $mysync->{ password1 } = qq{\x{00D6}} ; + is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', $eval = eval { hashsync( $mysync, q{} ) }, 'hashsync: password1: \x{00D6}' ) ; # + + print qq{1 00D6:รƒโ€“\n} ; + print encode_utf8( qq{2 00D6:รƒโ€“\n} ) ; + print qq{3 00D6:\x{00D6}\n} ; + print encode_utf8( qq{4 00D6:\x{00D6}\n} ) ; + + + print qq{5 6536:รฆโ€ยถ\n} ; + print encode_utf8( qq{6 6536:รฆโ€ยถ\n} ) ; + # the next one prints "Wide character in print at ./imapsync line xxxx" + print qq{7 6536:\x{6536}\n} ; + print encode_utf8( qq{8 6536:\x{6536}\n} ) ; + + $mysync->{ password1 } = qq{รฆโ€ยถ} ; + is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hashsync( $mysync, q{} ), 'hashsync: password1: รฆโ€ยถ' ) ; + + $mysync->{ password1 } = qq{\x{6536}} ; + is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', $eval = eval{ hashsync( $mysync, q{} ) }, 'hashsync: password1: \x{6536} with eval' ) ; + + # No side effect. + $mysync->{ password1 } = 'abcd' ; + is( 'afa29ab8534495251ac8346a985717c54bc49c26', hashsync( $mysync, q{} ), 'hashsync: password1: abcd again' ) ; + + note( 'Leaving tests_hashsync()' ) ; + return ; +} + +sub hashsync +{ + my $mysync = shift ; + my $hashkey = shift ; + + my $mystring = join( q{}, + $mysync->{ host1 } || q{}, + $mysync->{ user1 } || q{}, + $mysync->{ password1 } || q{}, + $mysync->{ host2 } || q{}, + $mysync->{ user2 } || q{}, + $mysync->{ password2 } || q{}, + ) ; + #my $hashsync = hmac_sha1_hex( $mystring, $hashkey ) ; + my $hashsync = hmac_sha1_hex_robust( $mystring, $hashkey ) ; + #myprint( "$hashsync\n" ) ; + return( $hashsync ) ; +} + + +sub tests_hmac_sha1_hex +{ + note( 'Entering tests_hmac_sha1_hex()' ) ; + + is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( ), 'hmac_sha1_hex: no args => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; + is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '' ), 'hmac_sha1_hex: empty string => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; + is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '', '' ), 'hmac_sha1_hex: empty strings => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; + is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '', '', 'caca' ), 'hmac_sha1_hex: empty strings + caca => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; + + # Good + is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex( 'รƒโ€“' ), 'hmac_sha1_hex: รƒโ€“ => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ; + is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex( encode_utf8(qq{\x{00D6}}) ), 'hmac_sha1_hex: encode_utf8 \x{00D6} => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ; + # Bad + is( 'fe8dc3b9ba3e8850bb4a7b070b2279e911003af2', hmac_sha1_hex( encode_utf8( 'รƒโ€“' ) ), 'hmac_sha1_hex: encode_utf8 รƒโ€“ => fe8dc3b9ba3e8850bb4a7b070b2279e911003af2' ) ; + is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', hmac_sha1_hex( qq{\x{00D6}} ), 'hmac_sha1_hex: qq{\x{00D6}} => bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a' ) ; + + # Good + is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( 'A' ), 'hmac_sha1_hex: A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; + is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( encode_utf8(qq{\x{0041}}) ), 'hmac_sha1_hex: encode_utf8 \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; + is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( encode_utf8( 'A' ) ), 'hmac_sha1_hex: encode_utf8 A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; + is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( qq{\x{0041}} ), 'hmac_sha1_hex: \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; + + # Good + is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( 'A', 'B' ), 'hmac_sha1_hex: A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; + is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( encode_utf8(qq{\x{0041}}), 'B' ), 'hmac_sha1_hex: encode_utf8 \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; + is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( encode_utf8( 'A' ), 'B' ), 'hmac_sha1_hex: encode_utf8 A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; + is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( qq{\x{0041}}, 'B' ), 'hmac_sha1_hex: \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; + + # http://unicode.scarfboy.com/?s=U%2B6536 + # Good + is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( 'รฆโ€ยถ' ), 'hmac_sha1_hex: รฆโ€ยถ => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ; + is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( encode_utf8(qq{\x{6536}}) ), 'hmac_sha1_hex: encode_utf8 \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ; + # Bad + is( 'e82217119628ad03e659cc89671d05ea4cee7238', hmac_sha1_hex( encode_utf8( 'รฆโ€ยถ' ) ), 'hmac_sha1_hex: encode_utf8 รฆโ€ยถ => e82217119628ad03e659cc89671d05ea4cee7238' ) ; + # Very very bad, perl dies... + #is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( qq{\x{6536}} ), 'hmac_sha1_hex: \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ; + # Ok but well, bad indeed + is( undef, my $eval = eval{ hmac_sha1_hex( qq{\x{6536}} ) }, 'hmac_sha1_hex: \x{6536} => undef' ) ; + + + note( 'Leaving tests_hmac_sha1_hex()' ) ; + return ; +} + +sub tests_hmac_sha1_hex_robust +{ + note( 'Entering tests_hmac_sha1_hex_robust()' ) ; + + is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( ), 'hmac_sha1_hex_robust: no args => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; + is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '' ), 'hmac_sha1_hex_robust: empty string => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; + is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '', '' ), 'hmac_sha1_hex_robust: empty strings => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; + is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '', '', 'caca' ), 'hmac_sha1_hex_robust: empty strings + caca => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; + + # Good + is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex_robust( 'รƒโ€“' ), 'hmac_sha1_hex_robust: รƒโ€“ => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ; + is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex_robust( encode_utf8(qq{\x{00D6}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{00D6} => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ; + # Bad + is( 'fe8dc3b9ba3e8850bb4a7b070b2279e911003af2', hmac_sha1_hex_robust( encode_utf8( 'รƒโ€“' ) ), 'hmac_sha1_hex_robust: encode_utf8 รƒโ€“ => fe8dc3b9ba3e8850bb4a7b070b2279e911003af2' ) ; + is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', hmac_sha1_hex_robust( qq{\x{00D6}} ), 'hmac_sha1_hex_robust: qq{\x{00D6}} => bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a' ) ; + + # Good + is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( 'A' ), 'hmac_sha1_hex_robust: A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; + is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( encode_utf8(qq{\x{0041}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; + is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( encode_utf8( 'A' ) ), 'hmac_sha1_hex_robust: encode_utf8 A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; + is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( qq{\x{0041}} ), 'hmac_sha1_hex_robust: \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; + + # Good + is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( 'A', 'B' ), 'hmac_sha1_hex_robust: A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; + is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( encode_utf8(qq{\x{0041}}), 'B' ), 'hmac_sha1_hex_robust: encode_utf8 \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; + is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( encode_utf8( 'A' ), 'B' ), 'hmac_sha1_hex_robust: encode_utf8 A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; + is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( qq{\x{0041}}, 'B' ), 'hmac_sha1_hex_robust: \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; + + # http://unicode.scarfboy.com/?s=U%2B6536 + # Good + is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( 'รฆโ€ยถ' ), 'hmac_sha1_hex_robust: รฆโ€ยถ => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ; + is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( encode_utf8(qq{\x{6536}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ; + # Bad + is( 'e82217119628ad03e659cc89671d05ea4cee7238', hmac_sha1_hex_robust( encode_utf8( 'รฆโ€ยถ' ) ), 'hmac_sha1_hex_robust: encode_utf8 รฆโ€ยถ => e82217119628ad03e659cc89671d05ea4cee7238' ) ; + # Good + is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( qq{\x{6536}} ), 'hmac_sha1_hex_robust: \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ; + # Good again + is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', my $eval = eval{ hmac_sha1_hex_robust( qq{\x{6536}} ) }, 'hmac_sha1_hex_robust: \x{6536} => undef' ) ; + + note( 'Leaving tests_hmac_sha1_hex_robust()' ) ; + return ; +} + + +sub hmac_sha1_hex_robust +{ + my $string = shift ; + my $val ; + if ( defined( $val = eval{ hmac_sha1_hex( $string, @ARG ) } ) ) + { + return $val ; + } + elsif( defined( $val = eval{ hmac_sha1_hex( encode_utf8( $string ), @ARG ) } ) ) + { + return $val ; + } + else + { + return ; + } +} + +sub tests_createhashfileifneeded +{ + note( 'Entering tests_createhashfileifneeded()' ) ; + + is( undef, createhashfileifneeded( ), 'createhashfileifneeded: no parameters => undef' ) ; + + note( 'Leaving tests_createhashfileifneeded()' ) ; + return ; +} + +sub createhashfileifneeded +{ + my $hashfile = shift ; + my $hashkey = shift || rand32( ) ; + + # no name + if ( ! $hashfile ) { + return ; + } + # already there + if ( -e -r $hashfile ) { + return $hashfile ; + } + # not creatable + if ( ! -w dirname( $hashfile ) ) { + return ; + } + # creatable + open my $FILE_HANDLE, '>', $hashfile + or do { + myprint( "Could not open $hashfile for writing. Check permissions or disk space." ) ; + return ; + } ; + myprint( "Writing random hashkey in $hashfile, once for all times\n" ) ; + print $FILE_HANDLE $hashkey ; + close $FILE_HANDLE ; + # Should be there now + if ( -e -r $hashfile ) { + return $hashfile ; + } + # unknown failure + return ; +} + +sub tests_rand32 +{ + note( 'Entering tests_rand32()' ) ; + + my $string = rand32( ) ; + myprint( "$string\n" ) ; + is( 32, length( $string ), 'rand32: 32 characters long' ) ; + is( 32, length( rand32( ) ), 'rand32: 32 characters long, another one' ) ; + + note( 'Leaving tests_rand32()' ) ; + return ; +} + +sub rand32 +{ + my @chars = ( "a".."z" ) ; + my $string; + $string .= $chars[rand @chars] for 1..32 ; + return $string ; +} + +sub imap_id_stuff +{ + my $mysync = shift ; + + if ( not $mysync->{id} ) { return ; } ; + + $mysync->{h1_imap_id} = imap_id( $mysync, $mysync->{imap1}, 'Host1' ) ; + #myprint( 'Host1: ' . $mysync->{h1_imap_id} ) ; + $mysync->{h2_imap_id} = imap_id( $mysync, $mysync->{imap2}, 'Host2' ) ; + #myprint( 'Host2: ' . $mysync->{h2_imap_id} ) ; + + return ; +} + +sub imap_id +{ + my ( $mysync, $imap, $Side ) = @_ ; + + if ( not $mysync->{id} ) { return q{} ; } ; + + $Side ||= q{} ; + my $imap_id_response = q{} ; + + if ( not $imap->has_capability( 'ID' ) ) { + $imap_id_response = 'No ID capability' ; + myprint( "$Side: No ID capability\n" ) ; + }else{ + my $id_inp = imapsync_id( $mysync, { side => lc $Side } ) ; + myprint( "\n$Side: found ID capability. Sending/receiving ID, presented in raw IMAP for now.\n" + . "In order to avoid sending/receiving ID, use option --noid\n" ) ; + my $debug_before = $imap->Debug( ) ; + $imap->Debug( 1 ) ; + my $id_out = $imap->tag_and_run( 'ID ' . $id_inp ) ; + #my $id_out = $imap->tag_and_run( 'ID NIL' ) ; + myprint( "\n" ) ; + $imap->Debug( $debug_before ) ; + #$imap_id_response = Data::Dumper->Dump( [ $id_out ], [ 'IMAP_ID' ] ) ; + } + return( $imap_id_response ) ; +} + +sub imapsync_id +{ + my $mysync = shift ; + my $overhashref = shift ; + # See http://tools.ietf.org/html/rfc2971.html + + my $imapsync_id = { } ; + + my $imapsync_id_lamiral = { + name => 'imapsync', + version => imapsync_version( $mysync ), + os => $OSNAME, + vendor => 'Gilles LAMIRAL', + 'support-url' => 'https://imapsync.lamiral.info/', + # Example of date-time: 19-Sep-2015 08:56:07 + date => date_from_rcs( q{$Date: 2022/01/12 21:28:37 $ } ), + } ; + + my $imapsync_id_github = { + name => 'imapsync', + version => imapsync_version( $mysync ), + os => $OSNAME, + vendor => 'github', + 'support-url' => 'https://github.com/imapsync/imapsync', + date => date_from_rcs( q{$Date: 2022/01/12 21:28:37 $ } ), + } ; + + $imapsync_id = $imapsync_id_lamiral ; + #$imapsync_id = $imapsync_id_github ; + my %mix = ( %{ $imapsync_id }, %{ $overhashref } ) ; + my $imapsync_id_str = format_for_imap_arg( \%mix ) ; + #myprint( "$imapsync_id_str\n" ) ; + return( $imapsync_id_str ) ; +} + +sub tests_imapsync_id +{ + note( 'Entering tests_imapsync_id()' ) ; + + my $mysync ; + ok( '("name" "imapsync" "version" "111" "os" "beurk" "vendor" "Gilles LAMIRAL" "support-url" "https://imapsync.lamiral.info/" "date" "22-12-1968" "side" "host1")' + eq imapsync_id( $mysync, + { + version => 111, + os => 'beurk', + date => '22-12-1968', + side => 'host1' + } + ), + 'tests_imapsync_id override' + ) ; + + note( 'Leaving tests_imapsync_id()' ) ; + return ; +} + +sub format_for_imap_arg +{ + my $ref = shift ; + + my $string = q{} ; + my %terms = %{ $ref } ; + my @terms = ( ) ; + if ( not ( %terms ) ) { return( 'NIL' ) } ; + # sort like in RFC then add extra key/values + foreach my $key ( qw( name version os os-version vendor support-url address date command arguments environment) ) { + if ( $terms{ $key } ) { + push @terms, $key, $terms{ $key } ; + delete $terms{ $key } ; + } + } + push @terms, %terms ; + $string = '(' . ( join q{ }, map { '"' . $_ . '"' } @terms ) . ')' ; + return( $string ) ; +} + + + +sub tests_format_for_imap_arg +{ + note( 'Entering tests_format_for_imap_arg()' ) ; + + ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ; + ok( '("name" "toto")' eq format_for_imap_arg( { name => 'toto' } ), 'format_for_imap_arg { name => toto }' ) ; + ok( '("name" "toto" "key" "val")' eq format_for_imap_arg( { name => 'toto', key => 'val' } ), 'format_for_imap_arg 2 x key val' ) ; + + note( 'Leaving tests_format_for_imap_arg()' ) ; + return ; +} + +sub quota +{ + my ( $mysync, $imap, $side ) = @_ ; + + my %side = ( + h1 => 'Host1', + h2 => 'Host2', + ) ; + my $Side = $side{ $side } ; + my $debug_before = $imap->Debug( ) ; + $imap->Debug( 1 ) ; + if ( not $imap->has_capability( 'QUOTA' ) ) + { + myprint( "$Side: No QUOTA capability found, skipping it.\n" ) ; + $imap->Debug( $debug_before ) ; + return ; + } ; + myprint( "\n$Side: QUOTA capability found, presented in raw IMAP on next lines\n" ) ; + my $getquotaroot = $imap->getquotaroot( 'INBOX' ) ; + # Gmail INBOX quotaroot is "" but with it Mail::IMAPClient does a literal GETQUOTA {2} \n "" + #$imap->quota( 'ROOT' ) ; + #$imap->quota( '""' ) ; + myprint( "\n" ) ; + $imap->Debug( $debug_before ) ; + my $quota_limit_bytes = quota_extract_storage_limit_in_bytes( $mysync, $getquotaroot ) ; + my $quota_current_bytes = quota_extract_storage_current_in_bytes( $mysync, $getquotaroot ) ; + $mysync->{$side}->{quota_limit_bytes} = $quota_limit_bytes ; + $mysync->{$side}->{quota_current_bytes} = $quota_current_bytes ; + my $quota_percent ; + if ( $quota_limit_bytes > 0 ) { + $quota_percent = mysprintf( '%.2f', $NUMBER_100 * $quota_current_bytes / $quota_limit_bytes ) ; + }else{ + $quota_percent = 0 ; + } + myprint( "$Side: Quota current storage is $quota_current_bytes bytes. Limit is $quota_limit_bytes bytes. So $quota_percent % full\n" ) ; + if ( $QUOTA_PERCENT_LIMIT < $quota_percent ) { + my $error = "$Side: $quota_percent % full: it is time to find a bigger place! ( $quota_current_bytes bytes / $quota_limit_bytes bytes )\n" ; + errors_incr( $mysync, $error ) ; + } + return ; +} + +sub tests_quota_extract_storage_limit_in_bytes +{ + note( 'Entering tests_quota_extract_storage_limit_in_bytes()' ) ; + + my $mysync = {} ; + my $imap_output = [ + '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"', + '* QUOTA "Storage quota" (STORAGE 1 104857600)', + '* QUOTA "Messages quota" (MESSAGE 2 100000)', + '5 OK Getquotaroot completed.' + ] ; + ok( $NUMBER_104_857_600 * $KIBI == quota_extract_storage_limit_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_limit_in_bytes ') ; + + note( 'Leaving tests_quota_extract_storage_limit_in_bytes()' ) ; + return ; +} + +sub quota_extract_storage_limit_in_bytes +{ + my $mysync = shift ; + my $imap_output = shift ; + + my $limit_kb ; + $limit_kb = ( map { /.*\(\s*STORAGE\s+\d+\s+(\d+)\s*\)/x ? $1 : () } @{ $imap_output } )[0] ; + $limit_kb ||= 0 ; + $mysync->{ debug } and myprint( "storage_limit_kb = $limit_kb\n" ) ; + return( $KIBI * $limit_kb ) ; +} + + +sub tests_quota_extract_storage_current_in_bytes +{ + note( 'Entering tests_quota_extract_storage_current_in_bytes()' ) ; + + my $mysync = {} ; + my $imap_output = [ + '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"', + '* QUOTA "Storage quota" (STORAGE 1 104857600)', + '* QUOTA "Messages quota" (MESSAGE 2 100000)', + '5 OK Getquotaroot completed.' + ] ; + ok( 1*$KIBI == quota_extract_storage_current_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_current_in_bytes: 1 => 1024 ') ; + + note( 'Leaving tests_quota_extract_storage_current_in_bytes()' ) ; + return ; +} + +sub quota_extract_storage_current_in_bytes +{ + my $mysync = shift ; + my $imap_output = shift ; + + my $current_kb ; + $current_kb = ( map { /.*\(\s*STORAGE\s+(\d+)\s+\d+\s*\)/x ? $1 : () } @{ $imap_output } )[0] ; + $current_kb ||= 0 ; + $mysync->{ debug } and myprint( "storage_current_kb = $current_kb\n" ) ; + return( $KIBI * $current_kb ) ; + +} + + +sub automap +{ + my ( $mysync ) = @_ ; + + if ( $mysync->{automap} ) { + myprint( "Turned on automapping folders ( use --noautomap to turn off automapping )\n" ) ; + }else{ + myprint( "Turned off automapping folders ( use --automap to turn on automapping )\n" ) ; + return ; + } + + $mysync->{h1_special} = special_from_folders_hash( $mysync, $mysync->{imap1}, 'Host1' ) ; + $mysync->{h2_special} = special_from_folders_hash( $mysync, $mysync->{imap2}, 'Host2' ) ; + + build_possible_special( $mysync ) ; + build_guess_special( $mysync ) ; + build_automap( $mysync ) ; + + return ; +} + + + + +sub build_guess_special +{ + my ( $mysync ) = shift ; + + foreach my $h1_fold ( sort keys %{ $mysync->{h1_folders_all} } ) { + my $special = guess_special( $h1_fold, $mysync->{possible_special}, $mysync->{h1_prefix} ) ; + if ( $special ) { + $mysync->{h1_special_guessed}{$h1_fold} = $special ; + my $already_guessed = $mysync->{h1_special_guessed}{$special} ; + if ( $already_guessed ) { + myprint( "Host1: $h1_fold not $special because set to $already_guessed\n" ) ; + }else{ + $mysync->{h1_special_guessed}{$special} = $h1_fold ; + } + } + } + foreach my $h2_fold ( sort keys %{ $mysync->{h2_folders_all} } ) { + my $special = guess_special( $h2_fold, $mysync->{possible_special}, $mysync->{h2_prefix} ) ; + if ( $special ) { + $mysync->{h2_special_guessed}{$h2_fold} = $special ; + my $already_guessed = $mysync->{h2_special_guessed}{$special} ; + if ( $already_guessed ) { + myprint( "Host2: $h2_fold not $special because set to $already_guessed\n" ) ; + }else{ + $mysync->{h2_special_guessed}{$special} = $h2_fold ; + } + } + } + return ; +} + +sub guess_special +{ + my( $folder, $possible_special_ref, $prefix ) = @_ ; + + my $folder_no_prefix = $folder ; + $folder_no_prefix =~ s/\Q${prefix}\E//xms ; + #$debug and myprint( "folder_no_prefix: $folder_no_prefix\n" ) ; + + my $guess_special = $possible_special_ref->{ $folder } + || $possible_special_ref->{ $folder_no_prefix } + || q{} ; + + return( $guess_special ) ; +} + +sub tests_guess_special +{ + note( 'Entering tests_guess_special()' ) ; + + my $possible_special_ref = build_possible_special( my $mysync ) ; + ok( '\Sent' eq guess_special( 'Sent', $possible_special_ref, q{} ) ,'guess_special: Sent => \Sent' ) ; + ok( q{} eq guess_special( 'Blabla', $possible_special_ref, q{} ) ,'guess_special: Blabla => q{}' ) ; + ok( '\Sent' eq guess_special( 'INBOX.Sent', $possible_special_ref, 'INBOX.' ) ,'guess_special: INBOX.Sent => \Sent' ) ; + ok( '\Sent' eq guess_special( 'IN BOX.Sent', $possible_special_ref, 'IN BOX.' ) ,'guess_special: IN BOX.Sent => \Sent' ) ; + + note( 'Leaving tests_guess_special()' ) ; + return ; +} + +sub build_automap +{ + my $mysync = shift ; + $mysync->{ debug } and myprint( "Entering build_automap\n" ) ; + foreach my $h1_fold ( @{ $mysync->{h1_folders_wanted} } ) { + my $h2_fold ; + my $h1_special = $mysync->{h1_special}{$h1_fold} ; + my $h1_special_guessed = $mysync->{h1_special_guessed}{$h1_fold} ; + + # Case 1: special on both sides. + if ( $h1_special + and exists $mysync->{h2_special}{$h1_special} ) { + $h2_fold = $mysync->{h2_special}{$h1_special} ; + $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ; + next ; + } + # Case 2: special on host1, not on host2 + if ( $h1_special + and ( not exists $mysync->{h2_special}{$h1_special} ) + and ( exists $mysync->{h2_special_guessed}{$h1_special} ) + ) { + # special_guessed on host2 + $h2_fold = $mysync->{h2_special_guessed}{$h1_special} ; + $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ; + next ; + } + # Case 3: no special on host1, special on host2 + if ( ( not $h1_special ) + and ( $h1_special_guessed ) + and ( exists $mysync->{h2_special}{$h1_special_guessed} ) + ) { + $h2_fold = $mysync->{h2_special}{$h1_special_guessed} ; + $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ; + next ; + } + # Case 4: no special on both sides. + if ( ( not $h1_special ) + and ( $h1_special_guessed ) + and ( not exists $mysync->{h2_special}{$h1_special_guessed} ) + and ( exists $mysync->{h2_special_guessed}{$h1_special_guessed} ) + ) { + $h2_fold = $mysync->{h2_special_guessed}{$h1_special_guessed} ; + $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ; + next ; + } + } + return( $mysync->{f1f2auto} ) ; +} + +# I will not add what there is at: +# http://stackoverflow.com/questions/2185391/localized-gmail-imap-folders/2185548#2185548 +# because it works well without +sub build_possible_special +{ + my $mysync = shift ; + my $possible_special = { } ; + # All|Archive|Drafts|Flagged|Junk|Sent|Trash + + $possible_special->{'\All'} = [ 'All', 'All Messages', '&BBIEQQQ1-' ] ; + $possible_special->{'\Archive'} = [ 'Archive', 'Archives', '&BBAEQARFBDgEMg-' ] ; + $possible_special->{'\Drafts'} = [ 'Drafts', 'DRAFTS', '&BCcENQRABD0EPgQyBDgEOgQ4-', 'Szkice', 'Wersje robocze' ] ; + $possible_special->{'\Flagged'} = [ 'Flagged', 'Starred', '&BB8EPgQ8BDUERwQ1BD0EPQRLBDU-' ] ; + $possible_special->{'\Junk'} = [ 'Junk', 'junk', 'Spam', 'SPAM', '&BCEEPwQwBDw-', + 'Potwierdzony spam', 'Wiadomo&AVs-ci-&AVs-mieci', + 'Junk E-Mail', 'Junk Email'] ; + $possible_special->{'\Sent'} = [ 'Sent', 'Sent Messages', 'Sent Items', + 'Gesendete Elemente', 'Gesendete Objekte', + '&AMk-l&AOk-ments envoy&AOk-s', 'E&AwE-le&AwE-ments envoye&AwE-s', 'Envoy&AOk-', 'Objets envoy&AOk-s', + 'Elementos enviados', + '&kAFP4W4IMH8wojCkMMYw4A-', + '&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-', + 'Elementy wys&AUI-ane'] ; + $possible_special->{'\Trash'} = [ 'Trash', 'TRASH', + '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-', + 'Kosz', + 'Deleted Items', 'Deleted Messages' ] ; + + + foreach my $special ( qw( \All \Archive \Drafts \Flagged \Junk \Sent \Trash ) ){ + foreach my $possible_folder ( @{ $possible_special->{$special} } ) { + $possible_special->{ $possible_folder } = $special ; + } ; + } + $mysync->{possible_special} = $possible_special ; + $mysync->{ debug } and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] ) ) ; + return( $possible_special ) ; +} + +sub tests_special_from_folders_hash +{ + note( 'Entering tests_special_from_folders_hash()' ) ; + + my $mysync = {} ; + require_ok( "Test::MockObject" ) ; + my $imapT = Test::MockObject->new( ) ; + + is( undef, special_from_folders_hash( ), 'special_from_folders_hash: no args' ) ; + is( undef, special_from_folders_hash( $mysync ), 'special_from_folders_hash: undef args' ) ; + is_deeply( {}, special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap void' ) ; + + $imapT->mock( 'folders_hash', sub { return( [ { name => 'Sent', attrs => [ '\Sent' ] } ] ) } ) ; + + is_deeply( { Sent => '\Sent', '\Sent' => 'Sent' }, + special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap \Sent' ) ; + + note( 'Leaving tests_special_from_folders_hash()' ) ; + return( ) ; +} + +sub special_from_folders_hash +{ + my ( $mysync, $imap, $side ) = @_ ; + my %special = ( ) ; + + if ( ! defined $imap ) { return ; } + $side = defined $side ? $side : 'Host?' ; + + if ( ! $imap->can( 'folders_hash' ) ) { + my $error = "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ; + errors_incr( $mysync, $error ) ; + return( \%special ) ; # empty hash ref + } + my $folders_hash = $imap->folders_hash( ) ; + foreach my $fhash (@{ $folders_hash } ) { + my @special = grep { /\\(?:All|Archive|Drafts|Flagged|Junk|Sent|Trash)/x } @{ $fhash->{attrs} } ; + if ( @special ) { + my $special = $special[0] ; # keep first one. Could be not very good. + if ( exists $special{ $special } ) { + myprintf( "%s: special %-20s = %s already assigned to %s\n", + $side, $fhash->{name}, join( q{ }, @special ), $special{ $special } ) ; + }else{ + myprintf( "%s: special %-20s = %s\n", + $side, $fhash->{name}, join( q{ }, @special ) ) ; + $special{ $special } = $fhash->{name} ; + $special{ $fhash->{name} } = $special ; # double entry value => key + } + } + } + myprint( "\n" ) if ( %special ) ; + return( \%special ) ; +} + + +sub tests_errors_log +{ + note( 'Entering tests_errors_log()' ) ; + is( undef, errors_log( ), 'errors_log: no args => undef' ) ; + my $mysync = {} ; + is( undef, errors_log( $mysync ), 'errors_log: empty => undef' ) ; + is_deeply( [ 'aieaie' ], [ errors_log( $mysync, 'aieaie' ) ], 'errors_log: aieaie => aieaie' ) ; + # cumulative + is_deeply( [ 'aieaie' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie' ) ; + is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync, 'ouille' ) ], 'errors_log: ouille => aieaie ouille' ) ; + is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie ouille' ) ; + note( 'Leaving tests_errors_log()' ) ; + return ; +} + +sub errors_log +{ + my ( $mysync, @error ) = @ARG ; + + if ( ! $mysync->{errors_log} ) { + $mysync->{errors_log} = [] ; + } + + if ( @error ) { + push @{ $mysync->{errors_log} }, join( q{}, @error ) ; + } + if ( @{ $mysync->{errors_log} } ) { + return @{ $mysync->{errors_log} } ; + } + else { + return ; + } +} + + + +sub tests_comment_of_error_type +{ + note( 'Entering tests_comment_of_error_type()' ) ; + + is( undef, comment_of_error_type( ), 'comment_of_error_type: no args => undef' ) ; + + my $mysync = { } ; + is( undef, comment_of_error_type( $mysync ), 'comment_of_error_type: undef => undef' ) ; + + is( "", comment_of_error_type( $mysync, '' ), 'comment_of_error_type: "" => ""' ) ; + is( "", comment_of_error_type( $mysync, 'blabla' ), 'comment_of_error_type: blabla => ""' ) ; + + is( "", comment_of_error_type( $mysync, 'ERR_UNCLASSIFIED' ), 'comment_of_error_type: ERR_UNCLASSIFIED => ""' ) ; + + like( comment_of_error_type( $mysync, 'ERR_OVERQUOTA' ), qr{100% full}, 'comment_of_error_type: ERR_OVERQUOTA => matches 100% full' ) ; + + + + note( 'Leaving tests_comment_of_error_type()' ) ; + return ; +} + +sub comment_of_error_type +{ + my $mysync = shift @ARG ; + my $error_type = shift @ARG ; + + if ( ! defined $mysync ) { return ; } + if ( ! defined $error_type ) { return ; } + + my $comment ; + + if ( exists( $COMMENT_OF_ERR_TYPE{ $error_type } ) ) + { + $comment = $COMMENT_OF_ERR_TYPE{ $error_type }->( $mysync ) ; + } + else + { + $comment = "" ; + } + return $comment ; +} + + + +sub tests_error_type +{ + note( 'Entering tests_error_type()' ) ; + + is( 'ERR_NOTHING_REPORTED', error_type( ), 'error_type: no args => ERR_NOTHING_REPORTED' ) ; + is( 'ERR_NOTHING_REPORTED', error_type( '' ), 'error_type: empty string => ERR_NOTHING_REPORTED' ) ; + + is( 'ERR_UNCLASSIFIED', error_type( 'ERR_UNCLASSIFIED' ), 'error_type: ERR_UNCLASSIFIED => ERR_UNCLASSIFIED' ) ; + is( 'ERR_UNCLASSIFIED', error_type( 'aie' ), 'error_type: aie => ERR_UNCLASSIFIED' ) ; + is( 'ERR_UNCLASSIFIED', error_type( 'ouille' ), 'error_type: ouille => ERR_UNCLASSIFIED' ) ; + + is( 'ERR_Host1_FETCH', error_type( 'Message xxx could not be fetched: blabla' ), + 'error_type: could not be fetched => ERR_Host1_FETCH' + ) ; + + is( 'ERR_APPEND_SIZE', + error_type( 'could not append message xxx: BAD maximum message size exceeded' ), + 'error_type: could not append message xxx: BAD maximum message size exceeded => ERR_APPEND_SIZE' + ) ; + + is( 'ERR_OVERQUOTA', + error_type( 'Quota limit will be exceeded' ), + 'error_type: Quota limit will be exceeded => ERR_OVERQUOTA' + ) ; + + is( 'ERR_APPEND', error_type( 'could not append' ), 'error_type: could not append => ERR_APPEND' ) ; + + is( 'ERR_CREATE', + error_type( 'Could not create folder' ), + 'error_type: Could not create folder => ERR_CREATE' + ) ; + + is( 'ERR_SELECT', + error_type( 'Could not select: blabla' ), + 'error_type: Could not select: blabla => ERR_SELECT' + ) ; + + + # + #Maximum bytes transferred reached, 423 >= 100, ending sync + is( 'ERR_TRANSFER_EXCEEDED', + error_type( 'Maximum bytes transferred reached, blabla' ), + 'error_type: Maximum bytes transferred reached, blabla => ERR_TRANSFER_EXCEEDED' + ) ; + + # + is( 'ERR_CONNECTION_FAILURE_HOST1', + error_type( 'Host1 failure: can not open imap connection on host1 [badhostkaka] with user [tata]: Unable to connect to badhostkaka: Invalid argument' ), + 'error_type: can not open imap connection on host1 => ERR_CONNECTION_FAILURE_HOST1' + ) ; + + is( 'ERR_CONNECTION_FAILURE_HOST2', + error_type( 'Host2 failure: can not open imap connection on host2 [badhostkiki] with user [titi]: Unable to connect to badhostkiki: Invalid argument' ), + 'error_type: can not open imap connection on host2 => ERR_CONNECTION_FAILURE_HOST2' + ) ; + + is( 'ERR_APPEND_VIRUS', + error_type( 'could not append ( Subject:[For Your Consideration], Date:["29-Nov-2016 03:21:10 -0800"], Size:[5505], Flags:[\Seen] ) to folder INBOX: 275 NO Message refused because it contains a virus' ), + 'error_type: could not append ... virus => ERR_APPEND_VIRUS' + ) ; + + + is( 'ERR_FLAGS', + error_type( 'Host2: flags msg INBOX/957910 could not add flags [PasGlop \PasGlopRe]: 33 NO Error in IMAP command received by server.' ), + 'error_type: could not add flags => ERR_FLAGS' + ) ; + + + note( 'Leaving tests_error_type()' ) ; + return ; +} + + + +# Could be implemented with https://metacpan.org/pod/Tie::RegexpHash +# with just a hash of error regexes as keys and types as values. + +sub error_type +{ + my $error = shift ; + + if ( ! defined $error ) { return 'ERR_NOTHING_REPORTED' ; } + if ( ! $error ) { return 'ERR_NOTHING_REPORTED' ; } + + # + if ( $error =~ m{Host1 failure: Error login on} ) { return 'ERR_AUTHENTICATION_FAILURE_USER1' } ; + if ( $error =~ m{Host2 failure: Error login on} ) { return 'ERR_AUTHENTICATION_FAILURE_USER2' } ; + + if ( $error =~ m{Host. failure: Can not go to tls encryption on host.} ) { return 'ERR_EXIT_TLS_FAILURE' } ; + # + + if ( $error =~ m{could not be fetched:} ) { return 'ERR_Host1_FETCH' } ; + + # could not append .*BAD maximum message size exceeded + # could not append.*Maximum size of appendable message has been exceeded + if ( $error =~ m{could not append .*BAD maximum message size exceeded} ) + { return 'ERR_APPEND_SIZE' ; } ; + + if ( $error =~ m{could not append.*Maximum size of appendable message has been exceeded} ) + { return 'ERR_APPEND_SIZE' ; } ; + + # Could not create folder *[OVERQUOTA] Not enough disk quota + # could not append .*[OVERQUOTA] Not enough disk quota + # could not append .*[OVERQUOTA] Mailbox is full / Blocks limit exceeded / Inode limit exceeded + if ( $error =~ m{OVERQUOTA} ) { return 'ERR_OVERQUOTA' ; } ; + if ( $error =~ m{Quota limit will be exceeded} ) { return 'ERR_OVERQUOTA' ; } ; + if ( $error =~ m{full: it is time to find a bigger place} ) { return 'ERR_OVERQUOTA' ; } ; + + # could not append ... to folder INBOX: 276 NO Message refused because it contains a virus + if ( $error =~ m{could not append.*virus} ) + { return 'ERR_APPEND_VIRUS' ; } ; + + # could not append .*Write failed 'Broken pipe' + # could not append .*timeout waiting .* for data from server + # could not append .*BAD Invalid Arguments: Unable to parse message + # could not append .*BAD Command Argument Error. 11 + # could not append .*NO header limit reached + if ( $error =~ m{could not append} ) { return 'ERR_APPEND' ; } ; + + # could not add flags + if ( $error =~ m{could not add flags} ) { return 'ERR_FLAGS' ; } ; + + + # Could not create folder .*Invalid mailbox name + if ( $error =~ m{Could not create folder} ) { return 'ERR_CREATE' ; } ; + + + # Could not select:.*NO [NOPERM] Permission denied + # Could not select:.*NO Mailbox doesn't exist + # Could not select:.*NO [SERVERBUG] Internal error occurred. + # Could not select:.*[CANNOT] Mailbox isn't a valid mbox file + if ( $error =~ m{Could not select:} ) { return 'ERR_SELECT' ; } ; + + #Maximum bytes transferred reached, 423 >= 100, ending sync + if ( $error =~ m{Maximum bytes transferred reached} ) { return 'ERR_TRANSFER_EXCEEDED' ; } ; + + if ( $error =~ m{can not open imap connection on host1} ) { return 'ERR_CONNECTION_FAILURE_HOST1' ; } ; + if ( $error =~ m{can not open imap connection on host2} ) { return 'ERR_CONNECTION_FAILURE_HOST2' ; } ; + + # Default is ERR_UNCLASSIFIED + return 'ERR_UNCLASSIFIED' ; + +} + +sub tests_errorclassify +{ + note( 'Entering tests_errorclassify()' ) ; + + is( undef, errorclassify( ), 'errorclassify: no args => undef' ) ; + + is_deeply( { 'ERR_UNCLASSIFIED' => 1 }, errorclassify( 'aie' ), 'errorclassify: aie => { ERR_UNCLASSIFIED => 1 }' ) ; + is_deeply( { 'ERR_UNCLASSIFIED' => 2 }, errorclassify( 'aie', 'ouille' ), 'errorclassify: aie ouille => { ERR_UNCLASSIFIED => 2 }' ) ; + is_deeply( { 'ERR_UNCLASSIFIED' => 2, 'ERR_NOTHING_REPORTED' => 1 }, errorclassify( 'aie', 'ouille', '' ), 'errorclassify: aie ouille "" => { ERR_UNCLASSIFIED => 2 }' ) ; + is_deeply( { 'ERR_UNCLASSIFIED' => 3 }, errorclassify( 'aie', 'ouille', 'aie' ), 'errorclassify: aie ouille aie => { ERR_UNCLASSIFIED => 3 }' ) ; + is_deeply( { 'ERR_UNCLASSIFIED' => 1, 'ERR_OVERQUOTA' => 2 }, errorclassify( 'aie', 'OVERQUOTA pipi', 'OVERQUOTA caca' ), 'errorclassify: aie OVERQUOTA OVERQUOTA' ) ; + is_deeply( { 'ERR_NOTHING_REPORTED' => 1 }, errorclassify( '' ), 'errorclassify: "" => { ERR_NOTHING_REPORTED => 1 }' ) ; + is_deeply( { 'ERR_NOTHING_REPORTED' => 2 }, errorclassify( '', '' ), 'errorclassify: "", "" => { ERR_NOTHING_REPORTED => 1 }' ) ; + + note( 'Leaving tests_errorclassify()' ) ; + return ; +} + + + +sub errorclassify +{ + my @errors = @ARG ; + + if ( ! @errors ) { return ; } ; + + my $error_type_count = { } ; + foreach my $error ( @errors ) + { + my $error_type = error_type( $error ) ; + $error_type_count->{ $error_type }++ ; + } + + return $error_type_count ; +} + +sub tests_most_common_error +{ + note( 'Entering tests_most_common_error()' ) ; + + is( 'ERR_NOTHING_REPORTED', most_common_error( ), 'most_common_error: no args => ERR_NOTHING_REPORTED' ) ; + is( 'ERR_NOTHING_REPORTED', most_common_error( {} ), 'most_common_error: empty hash ref => ERR_NOTHING_REPORTED' ) ; + is( 'ERR_NOTHING_REPORTED', most_common_error( 'blabla' ), 'most_common_error: not a hash ref => ERR_NOTHING_REPORTED' ) ; + + is( 'ERR_FOO', most_common_error( { ERR_FOO => 1 } ), 'most_common_error: { ERR_FOO => 1 } => ERR_FOO' ) ; + is( 'ERR_BAR', most_common_error( { ERR_FOO => 1, ERR_BAR => 2 } ), 'most_common_error: { ERR_FOO => 1, ERR_BAR => 2 } => ERR_BAR' ) ; + is( 'ERR_FOO', most_common_error( { ERR_FOO => 2, ERR_BAR => 1 } ), 'most_common_error: { ERR_FOO => 2, ERR_BAR => 1 } => ERR_FOO' ) ; + # exaequo => first lexical wins. ERR_BAR <= ERR_FOO + is( 'ERR_BAR', most_common_error( { ERR_FOO => 2, ERR_BAR => 2 } ), 'most_common_error: { ERR_FOO => 2, ERR_BAR => 2 } => ERR_BAR' ) ; + + is( 'A', most_common_error( { A => 5, B => 5, C => 5 } ), 'most_common_error: { A => 5, B => 5, C => 5 } => A' ) ; + is( 'B', most_common_error( { A => 5, B => 6, C => 6 } ), 'most_common_error: { A => 5, B => 6, C => 6 } => B' ) ; + is( 'C', most_common_error( { A => 5, B => 5, C => 7 } ), 'most_common_error: { A => 5, B => 5, C => 7 } => C' ) ; + is( 'C', most_common_error( { A => 5, B => 6, C => 7 } ), 'most_common_error: { A => 5, B => 5, C => 7 } => C' ) ; + + note( 'Leaving tests_most_common_error()' ) ; + return ; +} + + + +sub most_common_error +{ + my $errors_counted_ref = shift ; + + if ( ! defined $errors_counted_ref ) { return 'ERR_NOTHING_REPORTED' ; } + + if ( 'HASH' ne ref $errors_counted_ref ) { return 'ERR_NOTHING_REPORTED' ; } + + # empty hash + if ( !%{ $errors_counted_ref } ) { return 'ERR_NOTHING_REPORTED' ; } + + # non empty hash + # in case of equality the winner error is the first in alphabetic order + my $most_common_error = ( sort + { + $errors_counted_ref->{$b} <=> $errors_counted_ref->{$a} + || $a cmp $b + } keys %{$errors_counted_ref} )[0] ; + + return $most_common_error ; + +} + + + +sub tests_errorsanalyse +{ + note( 'Entering tests_errorsanalyse()' ) ; + + is( 'ERR_NOTHING_REPORTED', errorsanalyse( ), 'errorsanalyse: no args => ERR_NOTHING_REPORTED' ) ; + is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( ) ), 'errorsanalyse: empty list => ERR_NOTHING_REPORTED' ) ; + is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ; + + # in case of equality, empty wins + is( 'ERR_NOTHING_REPORTED', errorsanalyse( 'aie', '' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ; + is( 'ERR_NOTHING_REPORTED', errorsanalyse( '', 'aie' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ; + + + is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie', 'ouille' ), 'errorsanalyse: aie, ouille => ERR_UNCLASSIFIED' ) ; + is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie', 'ouille', '' ), 'errorsanalyse: aie, ouille, "" => ERR_UNCLASSIFIED' ) ; + is( 'ERR_UNCLASSIFIED', errorsanalyse( '', 'aie', 'ouille' ), 'errorsanalyse: aie, ouille, "" => ERR_UNCLASSIFIED' ) ; + + is( 'ERR_NOTHING_REPORTED', errorsanalyse( '' ), 'errorsanalyse: "" => ERR_NOTHING_REPORTED' ) ; + is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( '' ) ), 'errorsanalyse: ( "" ) => ERR_NOTHING_REPORTED' ) ; + is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( '', '' ) ), 'errorsanalyse: ( "", "" ) => ERR_NOTHING_REPORTED' ) ; + + note( 'Leaving tests_errorsanalyse()' ) ; + return ; +} + + + +sub errorsanalyse +{ + my @errors = @ARG ; + my $errors_types_counted = errorclassify( @errors ) ; + + my $most_common_error = most_common_error( $errors_types_counted ) ; + + return $most_common_error ; +} + + + +sub tests_errorsdump +{ + note( 'Entering tests_errorsdump()' ) ; + + is( undef, errorsdump( ), 'errorsdump: no args => undef' ) ; + is( undef, errorsdump( ( ) ), 'errorsdump: empty list => undef' ) ; + is( "Err 1/1: ", errorsdump( '' ), 'errorsdump: one empty string => "Err 1/1: "' ) ; + is( "Err 1/1: aieaieaie", errorsdump( 'aieaieaie' ), 'errorsdump: aieaieaie => "Err 1/1: aieaieaie"' ) ; + is( "Err 1/2: Aie Err 2/2: Ouille", errorsdump( 'Aie ', 'Ouille' ), 'errorsdump: Aie Ouille => "Err 1/2: Aie Err 2/2: Ouille"' ) ; + note( 'Leaving tests_errorsdump()' ) ; + return ; +} + + +sub errorsdump +{ + if ( ! @ARG ) { return ; } + + my @errors_log = @ARG ; + my $nb_errors = @errors_log ; + my $error_num = 0 ; + my $errors_list = q{} ; + if ( @errors_log ) { + foreach my $error ( @errors_log ) + { + $error_num++ ; + $errors_list .= "Err $error_num/$nb_errors: $error" ; + } + } + return( $errors_list ) ; +} + + + +sub errors_listing +{ + my $mysync = shift ; + $mysync->{ most_common_error } = errorsanalyse( errors_log( $mysync ) ) ; + + my $errors_listing = '' ; + + if ( $mysync->{ errorsdump } ) + { + $errors_listing = join( '', + "++++ Listing $mysync->{nb_errors} errors encountered during the sync ( avoid this listing with --noerrorsdump ).\n", + errorsdump( errors_log( $mysync ) ), + ) ; + } + + $errors_listing .= join( '', + "The most frequent error is $mysync->{ most_common_error }. ", + comment_of_error_type( $mysync, $mysync->{ most_common_error } ), + "\n", + ) ; + + return $errors_listing ; +} + + +sub errors_incr +{ + my ( $mysync, @error ) = @ARG ; + $mysync->{ nb_errors }++ ; + + if ( @error ) { + errors_log( $mysync, @error ) ; + myprint( @error ) ; + } + + $mysync->{ errorsmax } ||= $ERRORS_MAX ; + + + if ( $mysync->{ nb_errors } >= $mysync->{ errorsmax } ) + { + myprint( errorsmax_msg( $mysync ) ) ; + myprint( errors_listing( $mysync ) ) ; + + if ( $mysync->{ errorsdump } ) + { + # again since errorsdump( ) can be very verbose and masquerade previous warning + myprint( errorsmax_msg( $mysync ) ) ; + } + my $exit_value = exit_value( $mysync, $mysync->{ most_common_error } ) ; + exit_clean( $mysync, $exit_value ) ; + } + return ; +} + + + +sub errorsmax_msg +{ + my $mysync = shift @ARG ; + my $msg = "Maximum number of errors $mysync->{errorsmax} reached " + . "( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). " + . "Exiting.\n" ; + return $msg ; +} + + + + +sub tests_live_result +{ + note( 'Entering tests_live_result()' ) ; + + my $nb_errors = shift ; + if ( $nb_errors ) { + myprint( "Live tests failed with $nb_errors errors\n" ) ; + } else { + myprint( "Live tests ended successfully\n" ) ; + } + note( 'Leaving tests_live_result()' ) ; + return ; +} + + +sub size_filtered_flag +{ + my $mysync = shift ; + my $h1_size = shift ; + + if ( defined $mysync->{ maxsize } and $h1_size >= $mysync->{ maxsize } ) { + return( 1 ) ; + } + if ( defined $minsize and $h1_size <= $minsize ) { + return( 1 ) ; + } + return( 0 ) ; +} + +sub sync_flags_fir +{ + my ( $mysync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) = @_ ; + + if ( not defined $h1_msg ) { return } ; + if ( not defined $h2_msg ) { return } ; + + my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} ; + return if size_filtered_flag( $mysync, $h1_size ) ; + + # used cached flag values for efficiency + my $h1_flags = $h1_fir_ref->{ $h1_msg }->{ 'FLAGS' } || q{} ; + my $h2_flags = $h2_fir_ref->{ $h2_msg }->{ 'FLAGS' } || q{} ; + + sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ; + + return ; +} + +sub sync_flags_after_copy +{ + # Activated with option --syncflagsaftercopy + my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $permanentflags2 ) = @_ ; + + if ( my @h2_flags = $mysync->{imap2}->flags( $h2_msg ) ) { + my $h2_flags = "@h2_flags" ; + ( $mysync->{ debug } or $debugflags ) and myprint( "Host2: msg $h2_fold/$h2_msg flags before sync flags after copy ( $h2_flags )\n" ) ; + sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ; + }else{ + myprint( "Host2: msg $h2_fold/$h2_msg could not get its flags for sync flags after copy\n" ) ; + } + return ; +} + +# Globals +# $debug +# $debugflags +# $permanentflags2 + + +sub sync_flags +{ + my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) = @_ ; + + ( $mysync->{ debug } or $debugflags ) and + myprint( "Host1: flags init msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ; + + $h1_flags = flags_for_host2( $mysync, $h1_flags, $permanentflags2 ) ; + + $h2_flags = flagscase( $h2_flags ) ; + + ( $mysync->{ debug } or $debugflags ) and + myprint( "Host1: flags filt msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ; + + + # compare flags - set flags if there a difference + my @h1_flags = sort split(q{ }, $h1_flags ); + my @h2_flags = sort split(q{ }, $h2_flags ); + my $diff = compare_lists( \@h1_flags, \@h2_flags ); + + $diff and ( $mysync->{ debug } or $debugflags ) + and myprint( "Host2: flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ; + + # This sets flags exactly. So flags can be removed with this. + # When you remove a \Seen flag on host1 you want it + # to be removed on host2. Just add flags is not what + # we need most of the time, so no + like in "+FLAGS.SILENT". + + if ( not $mysync->{dry} and $diff and not $mysync->{imap2}->store( $h2_msg, "FLAGS.SILENT (@h1_flags)" ) ) { + my $error_msg = join q{}, "Host2: flags msg $h2_fold/$h2_msg could not add flags [@h1_flags]: ", + $mysync->{imap2}->LastError || q{}, "\n" ; + errors_incr( $mysync, $error_msg ) ; + } + + return ; +} + + + +sub _filter +{ + my $mysync = shift ; + my $str = shift or return q{} ; + my $sz = $SIZE_MAX_STR ; + my $len = length $str ; + if ( not $mysync->{ debug } and $len > $sz*2 ) { + my $beg = substr $str, 0, $sz ; + my $end = substr $str, -$sz, $sz ; + $str = $beg . '...' . $end ; + } + $str =~ s/\012?\015$//x ; + return "(len=$len) " . $str ; +} + + + +sub lost_connection +{ + my( $mysync, $imap, $error_message ) = @_; + if ( $imap->IsUnconnected( ) ) { + $mysync->{nb_errors}++ ; + my $lcomm = $imap->LastIMAPCommand || q{} ; + + my $einfo = imap_last_error( $imap ) ; + + # if string is long try reduce to a more reasonable size + $lcomm = _filter( $mysync, $lcomm ) ; + $einfo = _filter( $mysync, $einfo ) ; + myprint( "Failure: last command: $lcomm\n") if ( $mysync->{ debug } && $lcomm) ; + myprint( "Failure: lost connection $error_message: ", $einfo, "\n") ; + return( 1 ) ; + } + else{ + return( 0 ) ; + } +} + +sub imap_last_error +{ + my $imap = shift ; + my $einfo = $imap->LastError || @{$imap->History}[$LAST] || q{} ; + chomp( $einfo ) ; + return( $einfo ) ; +} + +sub tests_max +{ + note( 'Entering tests_max()' ) ; + + is( 0, max( 0 ), 'max 0 => 0' ) ; + is( 1, max( 1 ), 'max 1 => 1' ) ; + is( $MINUS_ONE, max( $MINUS_ONE ), 'max -1 => -1') ; + is( undef, max( ), 'max no arg => undef' ) ; + is( undef, max( undef ), 'undef => undef' ) ; + is( undef, max( undef, undef ), 'undef, undef => undef' ) ; + + is( $NUMBER_100, max( 1, $NUMBER_100 ), 'max 1 100 => 100' ) ; + is( $NUMBER_100, max( $NUMBER_100, 1 ), 'max 100 1 => 100' ) ; + is( $NUMBER_100, max( $NUMBER_100, $NUMBER_42, 1 ), 'max 100 42 1 => 100' ) ; + is( $NUMBER_100, max( $NUMBER_100, '42', 1 ), 'max 100 42 1 => 100' ) ; + is( $NUMBER_100, max( '100', '42', 1 ), 'max 100 42 1 => 100' ) ; + is( $NUMBER_100, max( $NUMBER_100, 'haha', 1 ), 'max 100 haha 1 => 100') ; + is( $NUMBER_100, max( 'bb', $NUMBER_100, 'haha' ), 'max bb 100 haha => 100') ; + is( $MINUS_ONE, max( q{}, $MINUS_ONE, 'haha' ), 'max "" -1 haha => -1') ; + is( $MINUS_ONE, max( q{}, $MINUS_ONE, $MINUS_TWO ), 'max "" -1 -2 => -1') ; + is( $MINUS_ONE, max( 'haha', $MINUS_ONE, $MINUS_TWO ), 'max haha -1 -2 => -1') ; + is( 1, max( $MINUS_ONE, 1 ), 'max -1 1 => 1') ; + is( 1, max( undef, 1 ), 'max undef 1 => 1' ) ; + is( 0, max( undef, 0 ), 'max undef 0 => 0' ) ; + is( 'haha', max( 'haha' ), 'max haha => haha') ; + is( 'bb', max( 'aa', 'bb' ), 'max aa bb => bb') ; + is( 'bb', max( 'bb', 'aa' ), 'max bb aa => bb') ; + is( 'bb', max( 'bb', 'aa', 'bb' ), 'max bb aa bb => bb') ; + note( 'Leaving tests_max()' ) ; + return ; +} + +sub max +{ + my @list = @_ ; + return( undef ) if ( 0 == scalar @list ) ; + + my( @numbers, @notnumbers ) ; + foreach my $item ( @list ) + { + if ( is_number( $item ) ) + { + push @numbers, $item ; + } + elsif ( defined $item ) + { + push @notnumbers, $item ; + } + } + + my @sorted ; + + if ( @numbers ) + { + @sorted = sort { $a <=> $b } @numbers ; + } + elsif ( @notnumbers ) + { + @sorted = sort { $a cmp $b } @notnumbers ; + } + else + { + return ; + } + + return( pop @sorted ) ; +} + +sub tests_is_number +{ + note( 'Entering tests_is_number()' ) ; + + is( undef, is_number( ), 'is_number: no args => undef ' ) ; + is( undef, is_number( undef ), 'is_number: undef => undef ' ) ; + ok( is_number( 1 ), 'is_number: 1 => 1' ) ; + ok( is_number( 1.1 ), 'is_number: 1.1 => 1' ) ; + ok( is_number( 0 ), 'is_number: 0 => 1' ) ; + ok( is_number( -1 ), 'is_number: -1 => 1' ) ; + ok( ! is_number( 1.1.1 ), 'is_number: 1.1.1 => no' ) ; + ok( ! is_number( q{} ), 'is_number: q{} => no' ) ; + ok( ! is_number( 'haha' ), 'is_number: haha => no' ) ; + ok( ! is_number( '0haha' ), 'is_number: 0haha => no' ) ; + ok( ! is_number( '2haha' ), 'is_number: 2haha => no' ) ; + ok( ! is_number( 'haha2' ), 'is_number: haha2 => no' ) ; + + note( 'Leaving tests_is_number()' ) ; + return ; +} + + + +sub is_number +{ + my $item = shift ; + + if ( ! defined $item ) { return ; } + + if ( $item =~ /\A$RE{num}{real}\Z/ ) { + return 1 ; + } + return ; +} + +sub tests_min +{ + note( 'Entering tests_min()' ) ; + + is( 0, min( 0 ), 'min 0 => 0' ) ; + is( 1, min( 1 ), 'min 1 => 1' ) ; + is( $MINUS_ONE, min( $MINUS_ONE ), 'min -1 => -1' ) ; + is( undef, min( ), 'min no arg => undef' ) ; + is( 1, min( 1, $NUMBER_100 ), 'min 1 100 => 1' ) ; + is( 1, min( $NUMBER_100, 1 ), 'min 100 1 => 1' ) ; + is( 1, min( $NUMBER_100, $NUMBER_42, 1 ), 'min 100 42 1 => 1' ) ; + is( 1, min( $NUMBER_100, '42', 1 ), 'min 100 42 1 => 1' ) ; + is( 1, min( '100', '42', 1 ), 'min 100 42 1 => 1' ) ; + is( 1, min( $NUMBER_100, 'haha', 1 ), 'min 100 haha 1 => 1') ; + is( $MINUS_ONE, min( $MINUS_ONE, 1 ), 'min -1 1 => -1') ; + + is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ; + is( 0, min( undef, 0 ), 'min undef 0 => 0' ) ; + is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ; + is( 0, min( undef, 2, 0, 1 ), 'min undef, 2, 0, 1 => 0' ) ; + + is( 'haha', min( 'haha' ), 'min haha => haha') ; + is( 'aa', min( 'aa', 'bb' ), 'min aa bb => aa') ; + is( 'aa', min( 'bb', 'aa' ), 'min bb aa bb => aa') ; + is( 'aa', min( 'bb', 'aa', 'bb' ), 'min bb aa bb => aa') ; + + note( 'Leaving tests_min()' ) ; + return ; +} + + +sub min +{ + my @list = @_ ; + return( undef ) if ( 0 == scalar @list ) ; + + my( @numbers, @notnumbers ) ; + foreach my $item ( @list ) { + if ( is_number( $item ) ) { + push @numbers, $item ; + }else{ + push @notnumbers, $item ; + } + } + + my @sorted ; + if ( @numbers ) { + @sorted = sort { $a <=> $b } @numbers ; + }elsif( @notnumbers ) { + @sorted = sort { $a cmp $b } @notnumbers ; + }else{ + return ; + } + + return( shift @sorted ) ; +} + + +sub check_lib_version +{ + my $mysync = shift ; + $mysync->{ debug } and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n" ) ; + if ( '2.2.9' eq $Mail::IMAPClient::VERSION ) { + myprint( "imapsync no longer supports Mail::IMAPClient 2.2.9, upgrade it\n" ) ; + return 0 ; + } + else{ + # 3.x.x is no longer buggy with imapsync. + # 3.30 or currently superior is imposed in the Perl "use Mail::IMAPClient line". + return 1 ; + } + return ; +} + +sub module_version_str +{ + my( $module_name, $module_version ) = @_ ; + my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ; + return( $str ) ; +} + +sub modulesversion +{ + + my @list_version; + + my %modulesversion = ( + 'Authen::NTLM' => sub { $Authen::NTLM::VERSION }, + 'CGI' => sub { $CGI::VERSION }, + 'Compress::Zlib' => sub { $Compress::Zlib::VERSION }, + 'Crypt::OpenSSL::RSA' => sub { $Crypt::OpenSSL::RSA::VERSION }, + 'Data::Uniqid' => sub { $Data::Uniqid::VERSION }, + 'Digest::HMAC_MD5' => sub { $Digest::HMAC_MD5::VERSION }, + 'Digest::HMAC_SHA1' => sub { $Digest::HMAC_SHA1::VERSION }, + 'Digest::MD5' => sub { $Digest::MD5::VERSION }, + 'Encode' => sub { $Encode::VERSION }, + 'Encode::IMAPUTF7' => sub { $Encode::IMAPUTF7::VERSION }, + 'File::Copy::Recursive' => sub { $File::Copy::Recursive::VERSION }, + 'File::Spec' => sub { $File::Spec::VERSION }, + 'Getopt::Long' => sub { $Getopt::Long::VERSION }, + 'HTML::Entities' => sub { $HTML::Entities::VERSION }, + 'IO::Socket' => sub { $IO::Socket::VERSION }, + 'IO::Socket::INET' => sub { $IO::Socket::INET::VERSION }, + 'IO::Socket::INET6' => sub { $IO::Socket::INET6::VERSION }, + 'IO::Socket::IP' => sub { $IO::Socket::IP::VERSION }, + 'IO::Socket::SSL' => sub { $IO::Socket::SSL::VERSION }, + 'IO::Tee' => sub { $IO::Tee::VERSION }, + 'JSON' => sub { $JSON::VERSION }, + 'JSON::WebToken' => sub { $JSON::WebToken::VERSION }, + 'LWP' => sub { $LWP::VERSION }, + 'Mail::IMAPClient' => sub { $Mail::IMAPClient::VERSION }, + 'MIME::Base64' => sub { $MIME::Base64::VERSION }, + 'Net::Ping' => sub { $Net::Ping::VERSION }, + 'Net::SSLeay' => sub { $Net::SSLeay::VERSION }, + 'Term::ReadKey' => sub { $Term::ReadKey::VERSION }, + 'Test::MockObject' => sub { $Test::MockObject::VERSION }, + 'Time::HiRes' => sub { $Time::HiRes::VERSION }, + 'Unicode::String' => sub { $Unicode::String::VERSION }, + 'URI::Escape' => sub { $URI::Escape::VERSION }, + #'Lalala' => sub { $Lalala::VERSION }, + ) ; + + foreach my $module_name ( sort keys %modulesversion ) { + # trick from http://www.perlmonks.org/?node_id=152122 + my $file_name = $module_name . '.pm' ; + $file_name =~s,::,/,xmgs; # Foo::Bar::Baz => Foo/Bar/Baz.pm + my $v ; + eval { + require $file_name ; + $v = defined $modulesversion{ $module_name } ? $modulesversion{ $module_name }->() : q{?} ; + } or $v = q{Not installed} ; + + push @list_version, module_version_str( $module_name, $v ) ; + } + return( @list_version ) ; +} + + +sub tests_command_line_nopassword +{ + note( 'Entering tests_command_line_nopassword()' ) ; + + ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' ); + my $mysync = {} ; + ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla' ), 'command_line_nopassword --blabla' ); + #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ; + ok( '--password1 MASKED' eq command_line_nopassword( $mysync, qw{ --password1 secret1}), 'command_line_nopassword --password1' ); + ok( '--blabla --password1 MASKED --blibli' + eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' ); + $mysync->{showpasswords} = 1 ; + ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' ); + ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla'), 'command_line_nopassword --blabla' ); + #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ; + ok( '--password1 secret1' eq command_line_nopassword( $mysync, qw{ --password1 secret1} ), 'command_line_nopassword --password1' ); + ok( '--blabla --password1 secret1 --blibli' + eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' ); + + note( 'Leaving tests_command_line_nopassword()' ) ; + return ; +} + +# Construct a command line copy with passwords replaced by MASKED. +sub command_line_nopassword +{ + my $mysync = shift @ARG ; + my @argv = @ARG ; + my @argv_nopassword ; + + if ( $mysync->{ cmdcgi } ) { + @argv_nopassword = mask_password_value( @{ $mysync->{ cmdcgi } } ) ; + return( "@argv_nopassword" ) ; + } + + if ( $mysync->{showpasswords} ) + { + return( "@argv" ) ; + } + + @argv_nopassword = mask_password_value( @argv ) ; + return("@argv_nopassword") ; +} + +sub mask_password_value +{ + my @argv = @ARG ; + my @argv_nopassword ; + while ( @argv ) { + my $arg = shift @argv ; # option name or value + if ( $arg =~ m/-password[12]/x ) { + shift @argv ; # password value + push @argv_nopassword, $arg, 'MASKED' ; # option name and fake value + }else{ + push @argv_nopassword, $arg ; # same option or value + } + } + return @argv_nopassword ; +} + + +sub tests_get_stdin_masked +{ + note( 'Entering tests_get_stdin_masked()' ) ; + + is( q{}, get_stdin_masked( ), 'get_stdin_masked: no args' ) ; + is( q{}, get_stdin_masked( 'Please ENTER: ' ), 'get_stdin_masked: ENTER' ) ; + + note( 'Leaving tests_get_stdin_masked()' ) ; + return ; +} + +####################################################### +# The issue is that prompt() does not prompt the prompt +# when the program is used like +# { sleep 2 ; echo blablabla ; } | ./imapsync ...--host1 lo --user1 tata --host2 lo --user2 titi + +# use IO::Prompter ; +sub get_stdin_masked +{ + my $prompt = shift || 'Say something: ' ; + local @ARGV = () ; + my $input = prompt( + -prompt => $prompt, + -echo => '*', + ) ; + #myprint( "You said: $input\n" ) ; + return $input ; +} + +sub ask_for_password_new +{ + my $prompt = shift ; + my $password = get_stdin_masked( $prompt ) ; + return $password ; +} +######################################################### + + +sub ask_for_password +{ + my $prompt = shift ; + myprint( $prompt ) ; + Term::ReadKey::ReadMode( 2 ) ; + ## no critic (InputOutput::ProhibitExplicitStdin) + my $password = ; + chomp $password ; + myprint( "\nGot it\n" ) ; + Term::ReadKey::ReadMode( 0 ) ; + return $password ; +} + +# Have to refactor get_password1() get_password2() +# to have only get_password() and two calls +sub get_password1 +{ + + my $mysync = shift ; + + $mysync->{ password1 } + || $mysync->{ passfile1 } + || 'PREAUTH' eq $mysync->{ acc1 }->{ authmech } + || 'EXTERNAL' eq $mysync->{ acc1 }->{ authmech } + || $ENV{IMAPSYNC_PASSWORD1} + || do + { + myprint( << 'FIN_PASSFILE' ) ; + +If you are afraid of giving password on the command line arguments, you can put the +password of user1 in a file named file1 and use "--passfile1 file1" instead of typing it. +Then give this file restrictive permissions with the command "chmod 600 file1". +An other solution is to set the environment variable IMAPSYNC_PASSWORD1 +FIN_PASSFILE + my $user = $mysync->{ acc1 }->{ authuser } || $mysync->{ user1 } ; + my $host = $mysync->{ host1 } ; + my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ; + $mysync->{password1} = ask_for_password( $prompt ) ; + } ; + + if ( defined $mysync->{ passfile1 } ) { + if ( ! -e -r $mysync->{ passfile1 } ) { + myprint( "Failure: file from parameter --passfile1 $mysync->{ passfile1 } does not exist or is not readable\n" ) ; + $mysync->{nb_errors}++ ; + exit_clean( $mysync, $EX_NOINPUT ) ; + } + # passfile1 readable + $mysync->{password1} = firstline ( $mysync->{ passfile1 } ) ; + return ; + } + if ( $ENV{IMAPSYNC_PASSWORD1} ) { + $mysync->{password1} = $ENV{IMAPSYNC_PASSWORD1} ; + return ; + } + return ; +} + +sub get_password2 +{ + + my $mysync = shift ; + + $mysync->{password2} + || $mysync->{ passfile2 } + || 'PREAUTH' eq $mysync->{ acc2 }->{ authmech } + || 'EXTERNAL' eq $mysync->{ acc2 }->{ authmech } + || $ENV{IMAPSYNC_PASSWORD2} + || do + { + myprint( << 'FIN_PASSFILE' ) ; + +If you are afraid of giving password on the command line arguments, you can put the +password of user2 in a file named file2 and use "--passfile2 file2" instead of typing it. +Then give this file restrictive permissions with the command "chmod 600 file2". +An other solution is to set the environment variable IMAPSYNC_PASSWORD2 +FIN_PASSFILE + my $user = $mysync->{ acc2 }->{ authuser } || $mysync->{ user2 } ; + my $host = $mysync->{ host2 } ; + my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ; + $mysync->{password2} = ask_for_password( $prompt ) ; + } ; + + + if ( defined $mysync->{ passfile2 } ) { + if ( ! -e -r $mysync->{ passfile2 } ) { + myprint( "Failure: file from parameter --passfile2 $mysync->{ passfile2 } does not exist or is not readable\n" ) ; + $mysync->{nb_errors}++ ; + exit_clean( $mysync, $EX_NOINPUT ) ; + } + # passfile2 readable + $mysync->{password2} = firstline ( $mysync->{ passfile2 } ) ; + return ; + } + if ( $ENV{IMAPSYNC_PASSWORD2} ) { + $mysync->{password2} = $ENV{IMAPSYNC_PASSWORD2} ; + return ; + } + return ; +} + + + + +sub remove_tmp_files +{ + my $mysync = shift or return ; + $mysync->{pidfile} or return ; + + if ( -e $mysync->{pidfile} ) { + myprint( "Removing pidfile $mysync->{pidfile}\n" ) ; + unlink $mysync->{pidfile} ; + } + if ( -e $mysync->{abortfile} ) { + myprint( "Removing pidfile $mysync->{abortfile}\n" ) ; + unlink $mysync->{abortfile} ; + } + return ; +} + +sub cleanup_before_exit +{ + my $mysync = shift ; + + remove_tmp_files( $mysync ) ; + + if ( $mysync->{imap1} and $mysync->{imap1}->IsConnected() ) + { + myprint( "Disconnecting from host1 $mysync->{ host1 } user1 $mysync->{ user1 }\n" ) ; + $mysync->{imap1}->logout( ) ; + } + + if ( $mysync->{imap2} and $mysync->{imap2}->IsConnected() ) + { + myprint( "Disconnecting from host2 $mysync->{ host2 } user2 $mysync->{ user2 }\n" ) ; + $mysync->{imap2}->logout( ) ; + } + + if ( $mysync->{log} ) { + myprint( "Log file is $mysync->{logfile} ( to change it, use --logfile filepath ; or use --nolog to turn off logging )\n" ) ; + } + else + { + myprint( "No log file because of option --nolog\n" ) ; + } + + if ( $mysync->{log} and $mysync->{logfile_handle} ) { + #print( "Closing $mysync->{ logfile }\n" ) ; + teefinish( $mysync ) ; + } + + return ; +} + + +sub tests_exit_value +{ + note( 'Entering tests_exit_value()' ) ; + + is( $EXIT_CATCH_ALL, exit_value( ), 'exit_value: no args => EXIT_CATCH_ALL' ) ; + + my $mysync = { } ; + is( $EXIT_CATCH_ALL, exit_value( $mysync ), 'exit_value: undef => EXIT_CATCH_ALL' ) ; + + is( $EXIT_CATCH_ALL, exit_value( $mysync, 'Blabla_unknown' ), 'exit_value: Blabla => EXIT_CATCH_ALL' ) ; + is( $EXIT_CATCH_ALL, exit_value( $mysync, '' ), 'exit_value: empty => EXIT_CATCH_ALL' ) ; + + + is( $EXIT_OVERQUOTA, exit_value( $mysync, 'ERR_OVERQUOTA' ), 'exit_value: ERR_OVERQUOTA => EXIT_OVERQUOTA' ) ; + is( $EXIT_TRANSFER_EXCEEDED, exit_value( $mysync, 'ERR_TRANSFER_EXCEEDED' ), 'exit_value: ERR_TRANSFER_EXCEEDED => EXIT_TRANSFER_EXCEEDED' ) ; + + note( 'Leaving tests_exit_value()' ) ; + return ; +} + +sub exit_value +{ + my $mysync = shift @ARG ; + my $most_common_error = shift @ARG ; + + if ( ! defined $most_common_error ) { return $EXIT_CATCH_ALL ; } + my $exit_value = $EXIT_VALUE_OF_ERR_TYPE{ $most_common_error } || $EXIT_CATCH_ALL ; + + return $exit_value ; +} + + + +sub exit_most_errors +{ + my $mysync = shift @ARG ; + + myprint( errors_listing( $mysync ) ) ; + my $exit_value = exit_value( $mysync, $mysync->{ most_common_error } ) ; + exit_clean( $mysync, $exit_value ) ; + return ; +} + +sub exit_clean +{ + my $mysync = shift @ARG ; + my $status = shift @ARG ; + my @messages = @ARG ; + if ( @messages ) + { + myprint( @messages ) ; + } + myprint( "Exiting with return value $status ($EXIT_TXT{$status}) $mysync->{nb_errors}/$mysync->{errorsmax} nb_errors/max_errors PID $PROCESS_ID\n" ) ; + cleanup_before_exit( $mysync ) ; + + exit $status ; +} + +sub missing_option +{ + my $mysync = shift ; + my $option = shift ; + $mysync->{nb_errors}++ ; + exit_clean( $mysync, $EX_USAGE, "$option option is mandatory, for help run $PROGRAM_NAME --help\n" ) ; + return ; +} + + +sub catch_ignore +{ + my $mysync = shift ; + my $signame = shift ; + + my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ; + myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), + "). Received $sigcounter $signame signals so far. Thanks!\n" ) ; + do_and_print_stats( $mysync ) ; + return ; +} + + + +sub catch_exit +{ + my $mysync = shift ; + my $signame = shift || q{} ; + if ( $signame ) { + myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), + "). Asked to terminate\n" ) ; + if ( $mysync->{can_do_stats} ) { + do_and_print_stats( $mysync ) ; + myprint( "Ended by a signal $signame (my PID is $PROCESS_ID my PPID is ", + getppid( ), "). I am asked to terminate immediately.\n" ) ; + } + myprint( "You should resynchronize those accounts by running a sync again,\n", + "since some messages and entire folders might still be missing on host2.\n" + ) ; + ## no critic (RequireLocalizedPunctuationVars) + # Well, restore default action does not work well + $SIG{ $signame } = 'DEFAULT'; # restore default action + #$SIG{ 'TERM' } = 'DEFAULT'; # restore default action + # kill myself with $signame + # https://www.cons.org/cracauer/sigint.html + myprint( "Killing myself with signal $signame\n" ) ; + #cleanup_before_exit( $mysync ) ; + kill( $signame, $PROCESS_ID ) ; + #kill( 'TERM', $PROCESS_ID ) ; + #sleep 1 ; + #while ( 1 ) { } ; + $mysync->{nb_errors}++ ; + exit_clean( $mysync, $EXIT_BY_SIGNAL, + "Still there after killing myself with signal $signame...\n" + ) ; + } + else + { + $mysync->{nb_errors}++ ; + exit_clean( $mysync, $EXIT_BY_SIGNAL, "Exiting in catch_exit with no signal...\n" ) ; + } + return ; +} + + +sub catch_print +{ + my $mysync = shift ; + my $signame = shift ; + + my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ; + myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), + "). Received $sigcounter $signame signals so far. Thanks!\n" ) ; + return ; +} + +sub here_twice +{ + my $mysync = shift ; + my $now = time ; + my $previous = $mysync->{lastcatch} || 0 ; + $mysync->{lastcatch} = $now ; + + if ( $INTERVAL_TO_EXIT >= $now - $previous ) { + return $TRUE ; + }else{ + return $FALSE ; + } +} + + +sub catch_reconnect +{ + my $mysync = shift ; + my $signame = shift ; + if ( here_twice( $mysync ) ) { + myprint( "Got two signals $signame within $INTERVAL_TO_EXIT seconds. Exiting...\n" ) ; + catch_exit( $mysync, $signame ) ; + }else{ + myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), ")\n", + "Hit 2 ctr-c within 2 seconds to exit the program\n", + "Hit only 1 ctr-c to reconnect to both imap servers\n", + ) ; + myprint( "For now only one signal $signame within $INTERVAL_TO_EXIT seconds.\n" ) ; + + if ( ! defined $mysync->{imap1} ) { return ; } + if ( ! defined $mysync->{imap2} ) { return ; } + + myprint( "Info: reconnecting to host1 imap server $mysync->{host1}\n" ) ; + $mysync->{imap1}->State( Mail::IMAPClient::Unconnected ) ; + $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT} += 1 ; + if ( $mysync->{imap1}->reconnect( ) ) + { + myprint( "Info: reconnected to host1 imap server $mysync->{host1}\n" ) ; + } + else + { + $mysync->{nb_errors}++ ; + exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ; + } + myprint( "Info: reconnecting to host2 imap server\n" ) ; + $mysync->{imap2}->State( Mail::IMAPClient::Unconnected ) ; + $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT} += 1 ; + if ( $mysync->{imap2}->reconnect( ) ) + { + myprint( "Info: reconnected to host2 imap server $mysync->{host2}\n" ) ; + } + else + { + $mysync->{nb_errors}++ ; + exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ; + } + myprint( "Info: reconnected to both imap servers\n" ) ; + } + return ; +} + +sub install_signals +{ + my $mysync = shift ; + + if ( under_docker_context( $mysync ) ) + { + # output( $mysync, "Under docker context so leaving signals as they are\n" ) ; + output( $mysync, "Under docker context so installing only signals to exit\n" ) ; + @{ $mysync->{ sigexit } } = ( defined( $mysync->{ sigexit } ) ) ? @{ $mysync->{ sigexit } } : ( 'INT', 'QUIT', 'TERM' ) ; + sig_install( $mysync, 'catch_exit', @{ $mysync->{ sigexit } } ) ; + } + else + { + # Unix signals + @{ $mysync->{ sigexit } } = ( defined( $mysync->{ sigexit } ) ) ? @{ $mysync->{ sigexit } } : ( 'QUIT', 'TERM' ) ; + @{ $mysync->{ sigreconnect } } = ( defined( $mysync->{ sigreconnect } ) ) ? @{ $mysync->{ sigreconnect } } : ( 'INT' ) ; + @{ $mysync->{ sigprint } } = ( defined( $mysync->{ sigprint } ) ) ? @{ $mysync->{ sigprint } } : ( 'HUP' ) ; + @{ $mysync->{ sigignore } } = ( defined( $mysync->{ sigignore } ) ) ? @{ $mysync->{ sigignore } } : ( ) ; + + #local %SIG = %SIG ; + sig_install( $mysync, 'catch_exit', @{ $mysync->{ sigexit } } ) ; + sig_install( $mysync, 'catch_reconnect', @{ $mysync->{ sigreconnect } } ) ; + sig_install( $mysync, 'catch_print', @{ $mysync->{ sigprint } } ) ; + # --sigignore can override sigexit, sigreconnect and sigprint (for the same signals only) + sig_install( $mysync, 'catch_ignore', @{ $mysync->{ sigignore } } ) ; + + # remove/add sleeping mechanism when receiving USR1 signal (except on Win32) + sig_install_toggle_sleep( $mysync ) ; + } + + return ; +} + + + +sub tests_reconnect_12_if_needed +{ + note( 'Entering tests_reconnect_12_if_needed()' ) ; + + my $mysync ; + + $mysync->{imap1} = Mail::IMAPClient->new( ) ; + $mysync->{imap2} = Mail::IMAPClient->new( ) ; + $mysync->{imap1}->Server( 'test1.lamiral.info' ) ; + $mysync->{imap2}->Server( 'test2.lamiral.info' ) ; + is( 2, reconnect_12_if_needed( $mysync ), 'reconnect_12_if_needed: test1&test2 .lamiral.info => 1' ) ; + is( 1, $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test1.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ; + is( 1, $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test2.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ; + + note( 'Leaving tests_reconnect_12_if_needed()' ) ; + return ; +} + +sub reconnect_12_if_needed +{ + my $mysync = shift ; + #return 2 ; + if ( ! reconnect_if_needed( $mysync->{imap1} ) ) { + return ; + } + if ( ! reconnect_if_needed( $mysync->{imap2} ) ) { + return ; + } + # both were good + return 2 ; +} + + +sub tests_reconnect_if_needed +{ + note( 'Entering tests_reconnect_if_needed()' ) ; + + + my $myimap ; + + is( undef, reconnect_if_needed( ), 'reconnect_if_needed: no args => undef' ) ; + is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: undef arg => undef' ) ; + + $myimap = Mail::IMAPClient->new( ) ; + $myimap->Debug( 1 ) ; + is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: empty new Mail::IMAPClient => undef' ) ; + $myimap->Server( 'test.lamiral.info' ) ; + is( 1, reconnect_if_needed( $myimap ), 'reconnect_if_needed: test.lamiral.info => 1' ) ; + is( 1, $myimap->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_if_needed: test.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ; + + note( 'Leaving tests_reconnect_if_needed()' ) ; + return ; +} + +sub reconnect_if_needed +{ + # return undef upon failure. + # return 1 upon connection success, with or without reconnection. + + my $imap = shift ; + + if ( ! defined $imap ) { return ; } + if ( ! $imap->Server( ) ) { return ; } + + if ( $imap->IsUnconnected( ) ) { + $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ; + if ( $imap->reconnect( ) ) { + return 1 ; + } + }else{ + return 1 ; + } + + # A last forced one + $imap->State( Mail::IMAPClient::Unconnected ) ; + $imap->reconnect( ) ; + $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ; + if ( $imap->noop ) { + # NOOP is ok + return 1 ; + } + + return ; +} + + +sub justconnect +{ + my $mysync = shift ; + my $justconnect1 = justconnect1( $sync ) ; + my $justconnect2 = justconnect2( $sync ) ; + return "$justconnect1 $justconnect2"; +} + +sub justconnect1 +{ + my $mysync = shift ; + if ( $mysync->{host1} ) + { + myprint( "Host1: Will just connect to $mysync->{host1} without login\n" ) ; + $mysync->{imap1} = connect_imap( + $mysync->{host1}, $mysync->{port1}, + $mysync->{ssl1}, $mysync->{tls1}, + $mysync->{ acc1 } ) ; + + imap_id( $mysync, $mysync->{imap1}, $mysync->{ acc1 }->{ Side } ) ; + $mysync->{imap1}->logout( ) ; + return $mysync->{host1} ; + } + + return q{} ; +} + +sub justconnect2 +{ + my $mysync = shift ; + if ( $mysync->{host2} ) + { + myprint( "Host2: Will just connect to $mysync->{host2} without login\n" ) ; + $mysync->{imap2} = connect_imap( + $mysync->{host2}, $mysync->{port2}, + $mysync->{ssl2}, $mysync->{tls2}, + $mysync->{ acc2 } ) ; + + imap_id( $mysync, $mysync->{imap2}, $mysync->{ acc2 }->{ Side } ) ; + $mysync->{imap2}->logout( ) ; + return $mysync->{host2} ; + } + + return q{} ; +} + +sub skip_macosx +{ + #return ; + # hostname is sometimes "macosx.polarhome.com" sometimes "macosx" + return( ( ( 'macosx.polarhome.com' eq hostname( ) ) || ( 'macosx' eq hostname( ) ) ) + && ( 'darwin' eq $OSNAME ) ) ; +} + +sub skip_macosx_binary +{ + #return ; + return( skip_macosx( ) && ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} ) ) ; +} + + + +sub tests_mailimapclient_connect +{ + note( 'Entering tests_mailimapclient_connect()' ) ; + + my $imap ; + # ipv4 + ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4: new' ) ; + is( 'Mail::IMAPClient', ref( $imap ), 'mailimapclient_connect ipv4: ref is Mail::IMAPClient' ) ; + + # Mail::IMAPClient 3.40 die on this... So we skip it, thanks to "mature" IO::Socket::IP + # Mail::IMAPClient 3.42 is ok so this test is back. + is( undef, $imap->connect( ), 'mailimapclient_connect ipv4: connect with no server => failure' ) ; + + + is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4: setting Server(test.lamiral.info)' ) ; + is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4: setting Debug( 1 )' ) ; + is( 143, $imap->Port( 143 ), 'mailimapclient_connect ipv4: setting Port( 143 )' ) ; + is( 10, $imap->Timeout( 10 ), 'mailimapclient_connect ipv4: setting Timeout( 10 )' ) ; + like( ref( $imap->connect( ) ), qr/IO::Socket::INET|IO::Socket::IP/, 'mailimapclient_connect ipv4: connect to test.lamiral.info' ) ; + like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4: logout' ) ; + is( undef, undef $imap, 'mailimapclient_connect ipv4: free variable' ) ; + + # ipv4 + ssl + ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4 + ssl: new' ) ; + is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4 + ssl: setting Server(test.lamiral.info)' ) ; + is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ; + ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH' ] ), 'mailimapclient_connect ipv4 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ; + is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv4 + ssl: setting Port( 993 )' ) ; + like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv4 + ssl: connect to test.lamiral.info' ) ; + like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4 + ssl: logout in ssl does not cause failure' ) ; + is( undef, undef $imap, 'mailimapclient_connect ipv4 + ssl: free variable' ) ; + + # ipv6 + ssl + + ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv6 + ssl: new' ) ; + is( 'petiteipv6.lamiral.info', $imap->Server( 'petiteipv6.lamiral.info' ), 'mailimapclient_connect ipv6 + ssl: setting Server petiteipv6.lamiral.info' ) ; + is( 10, $imap->Timeout( 10 ), 'mailimapclient_connect ipv6: setting Timeout( 10 )' ) ; + ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH' ] ), 'mailimapclient_connect ipv6 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ; + is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv6 + ssl: setting Port( 993 )' ) ; + SKIP: { + if ( + 'CUILLERE' eq hostname() + or + skip_macosx() + or + -e '/.dockerenv' + or + 'pcHPDV7-HP' eq hostname() + ) + { + skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 4 ) ; + } + + is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ; + + # It sounds stupid but it avoids failures on the next test about $imap->connect + is( '2a01:e34:ecde:70d0:223:54ff:fec2:36d7', resolv( 'petiteipv6.lamiral.info' ), 'resolv: petiteipv6.lamiral.info => 2a01:e34:ecde:70d0:223:54ff:fec2:36d7' ) ; + + like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv6 + ssl: connect to petiteipv6.lamiral.info' ) ; + # This one is ok on petite, not on ks2, do not know why, so commented. + like( ref( $imap->logout( ) ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv6 + ssl: logout in ssl is ok on petiteipv6.lamiral.info' ) ; + } + + is( undef, undef $imap, 'mailimapclient_connect ipv6 + ssl: free variable' ) ; + + + note( 'Leaving tests_mailimapclient_connect()' ) ; + return ; +} + + +sub tests_mailimapclient_connect_bug +{ + note( 'Entering tests_mailimapclient_connect_bug()' ) ; + + my $imap ; + + # ipv6 + ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect_bug ipv6: new' ) ; + is( 'ks6ipv6.lamiral.info', $imap->Server( 'ks6ipv6.lamiral.info' ), 'mailimapclient_connect_bug ipv6: setting Server(ks6ipv6.lamiral.info)' ) ; + is( 143, $imap->Port( 143 ), 'mailimapclient_connect_bug ipv6: setting Port( 993 )' ) ; + + SKIP: { + if ( + 'CUILLERE' eq hostname() + or + skip_macosx() + or + -e '/.dockerenv' + or + 'pcHPDV7-HP' eq hostname() + ) + { + skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 1 ) ; + } + like( ref( $imap->connect( ) ), qr/IO::Socket::INET/, 'mailimapclient_connect_bug ipv6: connect to ks6ipv6.lamiral.info' ) + or diag( 'mailimapclient_connect_bug ipv6: ', $imap->LastError( ), $!, ) ; + } + #is( $imap->logout( ), undef, 'mailimapclient_connect_bug ipv6: logout in ssl causes failure' ) ; + is( undef, undef $imap, 'mailimapclient_connect_bug ipv6: free variable' ) ; + + note( 'Leaving tests_mailimapclient_connect_bug()' ) ; + return ; +} + + + +sub tests_connect_socket +{ + note( 'Entering tests_connect_socket()' ) ; + + is( undef, connect_socket( ), 'connect_socket: no args' ) ; + + my $socket ; + my $imap ; + SKIP: { + if ( + 'CUILLERE' eq hostname() + or + skip_macosx() + or + -e '/.dockerenv' + or + 'pcHPDV7-HP' eq hostname() + ) + { + skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 2 ) ; + } + + $socket = IO::Socket::INET6->new( + PeerAddr => 'ks6ipv6.lamiral.info', + PeerPort => 143, + ) ; + + + ok( $imap = connect_socket( $socket ), 'connect_socket: ks6ipv6.lamiral.info port 143 IO::Socket::INET6' ) ; + #$imap->Debug( 1 ) ; + # myprint( $imap->capability( ) ) ; + if ( $imap ) { + $imap->logout( ) ; + } + + $IO::Socket::SSL::DEBUG = 4 ; + $socket = IO::Socket::SSL->new( + PeerHost => 'ks6ipv6.lamiral.info', + PeerPort => 993, + SSL_verify_mode => SSL_VERIFY_NONE, + SSL_cipher_list => 'DEFAULT:!DH', + ) ; + # myprint( $socket ) ; + ok( $imap = connect_socket( $socket ), 'connect_socket: ks6ipv6.lamiral.info port 993 IO::Socket::SSL' ) ; + #$imap->Debug( 1 ) ; + # myprint( $imap->capability( ) ) ; + # $socket->close( ) ; + if ( $imap ) { + $socket->close( ) ; + } + #$socket->close(SSL_no_shutdown => 1) ; + #$imap->logout( ) ; + #myprint( "\n" ) ; + #$imap->logout( ) ; + } + note( 'Leaving tests_connect_socket()' ) ; + return ; +} + +sub connect_socket +{ + my( $socket ) = @ARG ; + + if ( ! defined $socket ) { return ; } + + my $host = $socket->peerhost( ) ; + my $port = $socket->peerport( ) ; + #print "socket->peerhost: ", $socket->peerhost( ), "\n" ; + #print "socket->peerport: ", $socket->peerport( ), "\n" ; + my $imap = Mail::IMAPClient->new( ) ; + $imap->Socket( $socket ) ; + my $banner = $imap->Results()->[0] ; + #myprint( "banner: $banner" ) ; + return $imap ; +} + + +sub tests_probe_imapssl +{ + note( 'Entering tests_probe_imapssl()' ) ; + + is( undef, probe_imapssl( ), 'probe_imapssl: no args => undef' ) ; + is( undef, probe_imapssl( 'unknown' ), 'probe_imapssl: unknown => undef' ) ; + + note( "hostname is: ", hostname() ) ; + SKIP: { + if ( + 'CUILLERE' eq hostname() + or + skip_macosx() + or + -e '/.dockerenv' + or + 'pcHPDV7-HP' eq hostname() + ) + { + skip( 'Tests avoided on CUILLERE or pcHPDV7-HP or Mac or docker: cannot do ipv6', 0 ) ; + } + # fed up with this one + #like( probe_imapssl( 'ks6ipv6.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: ks6ipv6.lamiral.info matches "* OK"' ) ; + } ; + + + # It sounds stupid but it avoids failures on the next test about $imap->connect + ok( resolv( 'imap.gmail.com' ), 'resolv: imap.gmail.com => something' ) ; + like( probe_imapssl( 'imap.gmail.com' ), qr/^\* OK/, 'probe_imapssl: imap.gmail.com matches "* OK"' ) ; + + like( probe_imapssl( 'test1.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: test1.lamiral.info matches "* OK"' ) ; + + note( 'Leaving tests_probe_imapssl()' ) ; + return ; +} + + +sub probe_imapssl +{ + my $host = shift ; + + if ( ! $host ) { return ; } + $sync->{ debug } and $IO::Socket::SSL::DEBUG = 4 ; + my $socket = IO::Socket::SSL->new( + PeerHost => $host, + PeerPort => $IMAP_SSL_PORT, + SSL_verifycn_scheme => 'imap', + SSL_verify_mode => $SSL_VERIFY_POLICY, + SSL_cipher_list => 'DEFAULT:!DH', + ) ; + if ( ! $socket ) { return ; } + $sync->{ debug } and print "socket: $socket\n" ; + + my $banner ; + $socket->sysread( $banner, 65_536 ) ; + $sync->{ debug } and print "banner: $banner" ; + $socket->close( ) ; + return $banner ; + +} + +sub connect_imap +{ + my( $host, $port, $ssl, $tls, $acc ) = @_ ; + my $imap = Mail::IMAPClient->new( ) ; + + if ( $ssl ) { set_ssl( $imap, $acc ) } + $imap->Server( $host ) ; + $imap->Port( $port ) ; + $imap->Debug( $acc->{ debugimap } ) ; + $imap->Timeout( $acc->{ timeout } ) ; + + #$imap->Keepalive( $acc->{ keepalive } ) ; + + + my $side = lc $acc->{ Side } ; + + myprint( "$acc->{ Side }: connecting on $side [$host] port [$port]\n" ) ; + + if ( ! $imap->connect( ) ) + { + $sync->{nb_errors}++ ; + exit_clean( $sync, $EXIT_CONNECTION_FAILURE, + "$acc->{ Side }: Can not open imap connection on [$host]: ", + $imap->LastError, + " $OS_ERROR\n" + ) ; + } + myprint( "$acc->{ Side } IP address: ", $imap->Socket->peerhost(), "\n" ) ; + + my $banner = $imap->Results()->[0] ; + + myprint( "$acc->{ Side } banner: $banner" ) ; + myprint( "$acc->{ Side } capability: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ; + + if ( $tls ) { + set_tls( $imap, $acc ) ; + if ( ! $imap->starttls( ) ) + { + $sync->{nb_errors}++ ; + exit_clean( $sync, $EXIT_TLS_FAILURE, + "$acc->{ Side }: Can not go to tls encryption on $side [$host]:", + $imap->LastError, "\n" + ) ; + } + myprint( "$acc->{ Side }: Socket successfully converted to SSL\n" ) ; + } + return( $imap ) ; +} + +sub tests_compress_ssl +{ + note( 'Entering tests_compress_ssl()' ) ; + + SKIP: { + if ( skip_macosx( ) ) + { + skip( 'Tests avoided on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 12 ) ; + } + else + { + my $myimap ; + my $acc = {} ; + $acc->{ Side } = 'HostK' ; + $acc->{ authmech } = 'LOGIN' ; + $acc->{ debugimap } = 1 ; + $acc->{ compress } = 1 ; + $acc->{ N } = 'K' ; + + ok( + $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1', + 1, undef, + 1, 100, $acc, {}, + ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ; + ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ; + + + is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info ok" ) ; + is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd nok" ) ; + + ok( + $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1', + 0, undef, + 1, 100, $acc, {}, + ), 'acc_compress_imap: test1.lamiral.info test1 tls' ) ; + ok( $myimap && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ; + + is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls ok" ) ; + is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls 2nd nok" ) ; + + # Third, no compression + $acc->{ compress } = 0 ; + ok( + $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1', + 1, undef, + 1, 100, $acc, {}, + ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ; + ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ; + + + is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info off ok" ) ; + is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd off ok" ) ; + + } + } + note( 'Leaving tests_compress_ssl()' ) ; + return ; +} + +sub tests_compress +{ + note( 'Entering tests_compress()' ) ; + + my $myimap ; + my $acc = {} ; + $acc->{ Side } = 'HostK' ; + $acc->{ authmech } = 'LOGIN' ; + $acc->{ debugimap } = 1 ; + $acc->{ compress } = 1 ; + $acc->{ N } = 'K' ; + + ok( + $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1', + 0, 0, + 1, 100, $acc, {}, + ), 'acc_compress_imap: test1.lamiral.info test1' ) ; + ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 IsAuthenticated' ) ; + + + is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info ok" ) ; + is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd nok" ) ; + + ok( + $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1', + 0, 0, + 1, 100, $acc, {}, + ), 'acc_compress_imap: test1.lamiral.info test1 tls' ) ; + ok( $myimap && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ; + + is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls ok" ) ; + is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls 2nd nok" ) ; + + # Third, no compression + $acc->{ compress } = 0 ; + ok( + $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1', + 0, 0, + 1, 100, $acc, {}, + ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ; + ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ; + + + is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info off ok" ) ; + is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd off ok" ) ; + + note( 'Leaving tests_compress()' ) ; + return ; +} + + +sub acc_compress_imap +{ + my $acc = shift ; + + if ( ! defined( $acc ) ) { return ; } + + my $ret ; + my $imap = $acc->{ imap } ; + if ( ! defined $imap ) { return ; } + + if ( $imap && $acc->{ compress } ) + { + myprint( "$acc->{ Side }: Trying to turn imap compression on. Use --nocompress" . $acc->{ N } . " to avoid compression on " . lc( $acc->{ Side } ) . "\n" ) ; + if ( $ret = $imap->compress() ) + { + myprint( "$acc->{ Side }: Compression is on now\n" ) ; + } + else + { + myprint( "$acc->{ Side }: Failed to turn compression on\n" ) ; + } + } + else + { + myprint( "$acc->{ Side }: Compression is off. Use --compress" . $acc->{ N } . " to allow compression on " . lc( $acc->{ Side } ) . "\n" ) ; + } + # $ret is $acc->{ imap } on success, undef on failure or when there is nothing to do. + return $ret ; +} + +sub tests_login_imap +{ + note( 'Entering tests_login_imap()' ) ; + + is( undef, login_imap( ), 'login_imap: no args => undef' ) ; + + SKIP: { + if ( skip_macosx( ) ) + { + skip( 'Tests avoided only on binary on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 15 ) ; + } + else{ + + my $myimap ; + my $acc = {} ; + $acc->{ Side } = 'HostK' ; + $acc->{ authmech } = 'LOGIN' ; + #$IO::Socket::SSL::DEBUG = 4 ; + # Each month (trimester?): + # echo | openssl s_client -crlf -connect test1.lamiral.info:993 + # ... + # certificate has expired + # Fix: ssh root@test1.lamiral.info 'apt update && apt upgrade && /etc/init.d/dovecot restart' + # + # or + # echo | openssl s_client -crlf -connect test1.lamiral.info:993 + # ... + # Verify return code: 9 (certificate is not yet valid) + # Fix: /etc/init.d/openntpd restart + # 2021_09_04 done + ok( + $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1', + 1, undef, + 1, 100, $acc, {}, + ), 'login_imap: test1.lamiral.info test1 ssl' ) ; + ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ; + + is( $myimap, $acc->{ imap }, "login_imap: acc->{ imap } ok test1 ssl") ; + + ok( + $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1', + 0, undef, + 1, 100, $acc, {}, + ), 'login_imap: test1.lamiral.info test1 tls' ) ; + ok( $myimap && $myimap->IsAuthenticated( ), 'login_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ; + is( $myimap, $acc->{ imap }, "login_imap: acc->{ imap } ok test1 tls") ; + + #$IO::Socket::SSL::DEBUG = 4 ; + $acc->{sslargs} = { SSL_version => 'SSLv2' } ; + # SSLv2 not supported + is( + undef, $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1', + 0, undef, + 1, 100, $acc, {}, + ), 'login_imap: test1.lamiral.info test1 tls SSLv2 not supported' ) ; +#SSL_verify_mode => 1 +#SSL_version => 'TLSv1_1' + is( undef, $acc->{ imap }, "login_imap: acc->{ imap } test1 tls error => undef") ; + + + # I have left ? exit_clean to be replaced by errors_incr( $mysync, 'error message' ) + # 1 in login_imap() + + + my $mysync = {} ; + $acc = {} ; + $acc->{ Side } = 'Host2' ; + $acc->{ authmech } = 'LOGIN' ; + is( + undef, login_imap( 'noresol.lamiral.info', 143, 'test1', 'secret1', + 0, undef, + 1, 100, $acc, $mysync, + ), 'login_imap: noresol.lamiral.info undef' ) ; + + is( 'ERR_CONNECTION_FAILURE_HOST2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 noresol.lamiral.info => ERR_CONNECTION_FAILURE_HOST2' ) ; + is( undef, $acc->{ imap }, "login_imap: acc->{ imap } noresol error => undef") ; + + # authentication failure for user2 + $mysync = {} ; + is( + undef, login_imap( 'test1.lamiral.info', 143, 'test1', 'Ce crรƒยฉtin', + 0, undef, + 1, 100, $acc, $mysync, + ), 'login_imap: user2 bad passord => undef' ) ; + + is( 'ERR_AUTHENTICATION_FAILURE_USER2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 bad password => ERR_AUTHENTICATION_FAILURE_USER2' ) ; + + # authentication failure for user1 + $mysync = {} ; + $acc = {} ; + $acc->{ Side } = 'Host1' ; + $acc->{ authmech } = 'LOGIN' ; + is( + undef, login_imap( 'test1.lamiral.info', 143, 'test1', 'Ce crรƒยฉtin', + 0, undef, + 1, 100, $acc, $mysync, + ), 'login_imap: user1 bad passord => undef' ) ; + + is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 bad password => ERR_AUTHENTICATION_FAILURE_USER1' ) ; + + } + } + + note( 'Leaving tests_login_imap()' ) ; + return ; +} + +sub oauthgenerateaccess +{ + if ( "petite" eq hostname() ) + { + myprint( "oauthgenerateaccess\n" ) ; + my @output = backtick( 'cd oauth2 && pwd && ./generate_gmail_token imapsync.gl0@gmail.com' ) ; + myprint( @output ) ; + } + return ; +} + +sub tests_login_imap_oauth +{ + note( 'Entering tests_login_imap_oauth()' ) ; + + oauthgenerateaccess() ; + + SKIP: { + if ( skip_macosx_binary( ) ) + { + skip( 'Tests avoided only on binary on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 6 ) ; + } + else + { + + my $mysync ; + my $acc ; + # oauthdirect authentication failure for user2 + $mysync = {} ; + $acc = {} ; + $acc->{ oauthdirect } = 'caca2' ; + $acc->{ debugimap } = 1 ; + $mysync->{ showpasswords } = 1 ; + $acc->{ Side } = 'Host2' ; + $acc->{ authmech } = 'QQQ' ; + is( + undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crรƒยฉtin', + 1, undef, + 1, 100, $acc, $mysync, + ), 'login_imap: user2 bad oauthdirect => undef' ) ; + + is( 'ERR_AUTHENTICATION_FAILURE_USER2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 bad oauthdirect => ERR_AUTHENTICATION_FAILURE_USER2' ) ; + + # oauthdirect authentication failure for user1 + $mysync = {} ; + $acc = {} ; + $acc->{ Side } = 'Host1' ; + $acc->{ oauthdirect } = 'caca1' ; + $acc->{ debugimap } = 1 ; + $mysync->{ showpasswords } = 1 ; + $acc->{ authmech } = 'QQQ' ; + is( + undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crรƒยฉtin', + 1, undef, + 1, 100, $acc, $mysync, + ), 'login_imap: user1 bad oauthdirect => undef' ) ; + + is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 bad oauthdirect => ERR_AUTHENTICATION_FAILURE_USER1' ) ; + + # oauthdirect authentication failure for user1 + $mysync = {} ; + $acc = {} ; + $acc->{ Side } = 'Host1' ; + $acc->{ oauthdirect } = '' ; + $acc->{ debugimap } = 1 ; + $mysync->{ showpasswords } = 1 ; + $acc->{ authmech } = 'QQQ' ; + is( + undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crรƒยฉtin', + 1, undef, + 1, 100, $acc, $mysync, + ), 'login_imap: user1 bad oauthdirect => undef' ) ; + + is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 no oauthdirect value => ERR_AUTHENTICATION_FAILURE_USER1' ) ; + + } + } + + # oauthdirect authentication success for user1 + SKIP: { + if ( ! -r 'oauth2/D_oauth2_oauthdirect_imapsync.gl0@gmail.com.txt' ) + { + skip( 'oauthdirect: no oauthdirect file', 6 ) ; + } + my $myimap ; + my $mysync = {} ; + my $acc = {} ; + $acc->{ Side } = 'Host1' ; + $acc->{ oauthdirect } = 'oauth2/D_oauth2_oauthdirect_imapsync.gl0@gmail.com.txt' ; + $acc->{ debugimap } = 1 ; + $mysync->{ showpasswords } = 1 ; + $acc->{ authmech } = 'QQQ' ; + isa_ok( + $myimap = login_imap( 'imap.gmail.com', 993, 'user_useless', 'password_useless', + 1, undef, + 1, 100, $acc, $mysync, + ), 'Mail::IMAPClient', 'login_imap: user1 good oauthdirect => Mail::IMAPClient' ) ; + + ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect IsAuthenticated' ) ; + + ok( defined( $myimap ) && $myimap->logout( ), 'login_imap: gmail oauth2 oauthdirect logout' ) ; + ok( defined( $myimap ) && ! $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect not IsAuthenticated after logout' ) ; + ok( defined( $myimap ) && $myimap->reconnect( ), 'login_imap: gmail oauth2 oauthdirect reconnect ok' ) ; + ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect IsAuthenticated after reconnect' ) ; + } + + + + # oauthaccesstoken authentication success for user1 + SKIP: { + if ( ! -r 'oauth2/D_oauth2_access_token_imapsync.gl0@gmail.com.txt' ) + { + skip( 'oauthaccesstoken: no access_token file', 6 ) ; + } + my $myimap ; + my $mysync = {} ; + my $acc = {} ; + $acc->{ Side } = 'Host1' ; + $acc->{ oauthaccesstoken } = 'oauth2/D_oauth2_access_token_imapsync.gl0@gmail.com.txt' ; + $acc->{ debugimap } = 1 ; + $mysync->{ showpasswords } = 1 ; + $acc->{ authmech } = 'QQQ' ; + isa_ok( + $myimap = login_imap( 'imap.gmail.com', 993, 'imapsync.gl0@gmail.com', 'password_useless', + 1, undef, + 1, 100, $acc, $mysync, + ), 'Mail::IMAPClient', 'login_imap: user1 good oauthaccesstoken => Mail::IMAPClient' ) ; + + ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken IsAuthenticated' ) ; + + ok( defined( $myimap ) && $myimap->logout( ), 'login_imap: gmail oauth2 oauthaccesstoken logout' ) ; + ok( defined( $myimap ) && ! $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken not IsAuthenticated after logout' ) ; + ok( defined( $myimap ) && $myimap->reconnect( ), 'login_imap: gmail oauth2 oauthaccesstoken reconnect ok' ) ; + ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken IsAuthenticated after reconnect' ) ; + } + + + note( 'Leaving tests_login_imap_oauth()' ) ; + return ; +} + + + +sub login_imap +{ + my @allargs = @_ ; + my( + $host, $port, $user, $password, + $ssl, $tls, + $uid, $split, $acc, $mysync ) = @allargs ; + + $acc->{ imap } = undef ; + + if ( ! all_defined( $host, $port, $user, $acc->{ Side } ) ) + { + return ; + } + + my $side = lc $acc->{ Side } ; + myprint( "$acc->{ Side }: connecting and login on $side [$host] port [$port] with user [$user]\n" ) ; + + my $imap = init_imap( @allargs ) ; + + if ( ! $imap->connect() ) + { + my $error = "$acc->{ Side } failure: can not open imap connection on $side [$host] with user [$user]: " + . $imap->LastError . " $OS_ERROR\n" ; + errors_incr( $mysync, $error ) ; + return ; + } + myprint( "$acc->{ Side } IP address: ", $imap->Socket->peerhost(), "\n" ) ; + my $banner = $imap->Results()->[0] ; + + myprint( "$acc->{ Side } banner: $banner" ) ; + myprint( "$acc->{ Side } capability before authentication: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ; + + if ( (! $ssl) and (! defined $tls ) and $imap->has_capability( 'STARTTLS' ) ) { + myprint( "$acc->{ Side }: going to ssl because STARTTLS is in CAPABILITY. Use --notls1 or --notls2 to avoid that behavior\n" ) ; + $tls = 1 ; + } + + + #myprint( Data::Dumper->Dump( [ @allargs ] ) ) ; + if ( $tls ) { + set_tls( $imap, $acc ) ; + + if ( ! $imap->starttls( ) ) + { + my $error = "$acc->{ Side } failure: Can not go to tls encryption on $side [$host]: " + . $imap->LastError . "\n" ; + + errors_incr( $mysync, $error ) ; + return ; + } + myprint( "$acc->{ Side }: Socket successfully converted to SSL\n" ) ; + } + + if ( $acc->{ authmech } eq 'PREAUTH' ) { + if ( $imap->IsAuthenticated( ) ) { + $imap->Socket ; + myprintf("%s: Assuming PREAUTH for %s\n", $acc->{ Side }, $imap->Server ) ; + }else{ + $mysync->{nb_errors}++ ; + exit_clean( + $mysync, $EXIT_AUTHENTICATION_FAILURE, + "$acc->{ Side } failure: error login on $side [$host] with user [$user] auth [PREAUTH]\n" + ) ; + } + } + + + + if ( authenticate_imap( $imap, @allargs ) ) + { + myprint( "$acc->{ Side }: success login on [$host] with user [$user] auth [$acc->{ authmech }] or [LOGIN]\n" ) ; + $acc->{ imap } = $imap ; + return( $imap ) ; + } + else + { + # The errors are already printed + myprint( "$acc->{ Side }: failed login on [$host] with user [$user] auth [$acc->{ authmech }]\n" ) ; + return ; + } +} + + + +sub init_imap +{ + my( + $host, $port, $user, $password, + $ssl, $tls, + $uid, $split, $acc, $mysync ) = @_ ; + + my ( $imap ) ; + + $imap = Mail::IMAPClient->new() ; + + if ( $mysync->{ tee } ) + { + # Well, it does not change anything, does it? + # It does when suppressing the hack with *STDERR + $imap->Debug_fh( $mysync->{ tee } ) ; + } + + if ( $ssl ) { set_ssl( $imap, $acc ) } + if ( $tls ) { } # can not do set_tls() here because connect() will directly do a STARTTLS + $imap->Clear( 1 ) ; + $imap->Server( $host ) ; + $imap->Port( $port ) ; + $imap->Fast_io( $acc->{ fastio } ) ; + $imap->Buffer( $buffersize || $DEFAULT_BUFFER_SIZE ) ; + $imap->Uid( $uid ) ; + + $imap->Peek( 1 ) ; + $imap->Debug( $acc->{ debugimap } ) ; + if ( $mysync->{ showpasswords } ) { + $imap->Showcredentials( 1 ) ; + } + + if ( defined( $acc->{ timeout } ) ) + { + $imap->Timeout( $acc->{ timeout } ) ; + } + + if ( defined $acc->{ keepalive } ) + { + $imap->Keepalive( $acc->{ keepalive } ) ; + } + + if ( defined $acc->{ reconnectretry } ) + { + $imap->Reconnectretry( $acc->{ reconnectretry } ) ; + } + + $imap->{IMAPSYNC_RECONNECT_COUNT} = 0 ; + $imap->Ignoresizeerrors( $allowsizemismatch ) ; + $split and $imap->Maxcommandlength( $SPLIT_FACTOR * $split ) ; - my $string = rand32( ) ; - myprint( "$string\n" ) ; - is( 32, length( $string ), 'rand32: 32 characters long' ) ; - is( 32, length( rand32( ) ), 'rand32: 32 characters long, another one' ) ; - note( 'Leaving tests_rand32()' ) ; - return ; -} + return( $imap ) ; -sub rand32 { - my @chars = ( "a".."z" ) ; - my $string; - $string .= $chars[rand @chars] for 1..32 ; - return $string ; } -sub imap_id_stuff { - my $mysync = shift ; +sub authenticate_imap +{ + my( $imap, + $host, $port, $user, $password, + $ssl, $tls, + $uid, $split, $acc, $mysync ) = @_ ; - if ( not $mysync->{id} ) { return ; } ; + check_capability( $imap, $acc->{ authmech }, $acc->{ Side } ) ; + $imap->User( $user ) ; - $mysync->{h1_imap_id} = imap_id( $mysync, $mysync->{imap1}, 'Host1' ) ; - #myprint( 'Host1: ' . $mysync->{h1_imap_id} ) ; - $mysync->{h2_imap_id} = imap_id( $mysync, $mysync->{imap2}, 'Host2' ) ; - #myprint( 'Host2: ' . $mysync->{h2_imap_id} ) ; + if ( defined $acc->{ domain } ) + { + $imap->Domain( $acc->{ domain } ) ; + $mysync->{ debug } and myprint( "Domain: $acc->{ domain }\n" ) ; + } - return ; -} + $imap->Authuser( $acc->{ authuser } ) ; + $imap->Password( $password ) ; -sub imap_id { - my ( $mysync, $imap, $Side ) = @_ ; + if ( 'X-MASTERAUTH' eq $acc->{ authmech } ) + { + xmasterauth( $imap ) ; + return 1 ; + } - $Side ||= q{} ; - my $imap_id_response = q{} ; - if ( not $imap->has_capability( 'ID' ) ) { - $imap_id_response = 'No ID capability' ; - myprint( "$Side: No ID capability\n" ) ; - }else{ - my $id_inp = imapsync_id( $mysync, { side => lc $Side } ) ; - myprint( "\n$Side: found ID capability. Sending/receiving ID, presented in raw IMAP for now.\n" - . "In order to avoid sending/receiving ID, use option --noid\n" ) ; - my $debug_before = $imap->Debug( ) ; - $imap->Debug( 1 ) ; - my $id_out = $imap->tag_and_run( 'ID ' . $id_inp ) ; - #my $id_out = $imap->tag_and_run( 'ID NIL' ) ; - myprint( "\n" ) ; - $imap->Debug( $debug_before ) ; - #$imap_id_response = Data::Dumper->Dump( [ $id_out ], [ 'IMAP_ID' ] ) ; + if ( defined $acc->{ oauthdirect } ) + { + $acc->{ authmech } = 'XOAUTH2 direct' ; + return( oauthdirect( $mysync, $acc, $imap, $host, $user ) ) ; } - return( $imap_id_response ) ; -} -sub imapsync_id { - my $mysync = shift ; - my $overhashref = shift ; - # See http://tools.ietf.org/html/rfc2971.html - my $imapsync_id = { } ; + if ( defined $acc->{ oauthaccesstoken } ) + { + $acc->{ authmech } = 'XOAUTH2 accesstoken' ; + return( oauthaccesstoken( $mysync, $acc, $imap, $host, $user ) ) ; + } - my $imapsync_id_lamiral = { - name => 'imapsync', - version => imapsync_version( $mysync ), - os => $OSNAME, - vendor => 'Gilles LAMIRAL', - 'support-url' => 'https://imapsync.lamiral.info/', - # Example of date-time: 19-Sep-2015 08:56:07 - date => date_from_rcs( q{$Date: 2018/05/05 21:10:43 $ } ), - } ; - my $imapsync_id_github = { - name => 'imapsync', - version => imapsync_version( $mysync ), - os => $OSNAME, - vendor => 'github', - 'support-url' => 'https://github.com/imapsync/imapsync', - date => date_from_rcs( q{$Date: 2018/05/05 21:10:43 $ } ), - } ; - $imapsync_id = $imapsync_id_lamiral ; - #$imapsync_id = $imapsync_id_github ; - my %mix = ( %{ $imapsync_id }, %{ $overhashref } ) ; - my $imapsync_id_str = format_for_imap_arg( \%mix ) ; - #myprint( "$imapsync_id_str\n" ) ; - return( $imapsync_id_str ) ; -} -sub tests_imapsync_id { - note( 'Entering tests_imapsync_id()' ) ; + if ( $acc->{ proxyauth } ) { + $imap->Authmechanism(q{}) ; + $imap->User( $acc->{ authuser } ) ; + } else { + $imap->Authmechanism( $acc->{ authmech } ) unless ( $acc->{ authmech } eq 'LOGIN' or $acc->{ authmech } eq 'PREAUTH' ) ; + } - my $mysync ; - ok( '("name" "imapsync" "version" "111" "os" "beurk" "vendor" "Gilles LAMIRAL" "support-url" "https://imapsync.lamiral.info/" "date" "22-12-1968" "side" "host1")' - eq imapsync_id( $mysync, - { - version => 111, - os => 'beurk', - date => '22-12-1968', - side => 'host1' - } - ), - 'tests_imapsync_id override' - ) ; + $imap->Authcallback(\&xoauth2) if ( 'XOAUTH2' eq $acc->{ authmech } ) ; + $imap->Authcallback(\&plainauth) if ( ( 'PLAIN' eq $acc->{ authmech } ) or ( 'EXTERNAL' eq $acc->{ authmech } ) ) ; - note( 'Leaving tests_imapsync_id()' ) ; - return ; -} -sub format_for_imap_arg { - my $ref = shift ; + unless ( $acc->{ authmech } eq 'PREAUTH' or $imap->login( ) ) { + my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ; + my $einfo = imap_last_error( $imap ) ; + my $error = "$info [$acc->{ authmech }]: $einfo\n" ; - my $string = q{} ; - my %terms = %{ $ref } ; - my @terms = ( ) ; - if ( not ( %terms ) ) { return( 'NIL' ) } ; - # sort like in RFC then add extra key/values - foreach my $key ( qw( name version os os-version vendor support-url address date command arguments environment) ) { - if ( $terms{ $key } ) { - push @terms, $key, $terms{ $key } ; - delete $terms{ $key } ; + + if ( ( $acc->{ authmech } eq 'LOGIN' ) or $imap->IsUnconnected( ) or $acc->{ authuser } ) { + $acc->{ authuser } ||= "" ; + myprint( "$acc->{ Side } info: authmech [$acc->{ authmech }] user [$user] authuser [$acc->{ authuser }] IsUnconnected [", $imap->IsUnconnected( ), "]\n" ) ; + errors_incr( $mysync, $error ) ; + return ; + }else{ + errors_incr( $mysync, $error ) ; + } + + # It is not secure to try plain text LOGIN when another authmech failed + # but I do it anyway. This behavior is optional as option --notrylogin will skip it. + if ( $mysync->{ trylogin } ) + { + myprint( "$acc->{ Side } info: trying LOGIN Auth mechanism on [$host] with user [$user]. Use option --notrylogin to avoid this second chance to login via LOGIN auth\n" ) ; + $imap->Authmechanism(q{}) ; + if ( ! $imap->login( ) ) + { + failure_login( $mysync, $acc, 'LOGIN', $imap, $host, $user ) ; + return ; + } + else + { + myprint( "$acc->{ Side }: success login on [$host] with user [$user] auth [LOGIN] after [$acc->{ authmech }] failure\n" ) ; + } + } + else + { + myprint( "$acc->{ Side } info: not trying LOGIN Auth mechanism on [$host] with user [$user]. Use option --trylogin to have this second chance to login via LOGIN auth\n" ) ; + return ; } } - push @terms, %terms ; - $string = '(' . ( join q{ }, map { '"' . $_ . '"' } @terms ) . ')' ; - return( $string ) ; -} + if ( $acc->{ proxyauth } ) { + if ( ! $imap->proxyauth( $user ) ) { + failure_proxyauth( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ; + return ; + } + } + return 1; +} -sub tests_format_for_imap_arg { - note( 'Entering tests_format_for_imap_arg()' ) ; - ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ; - ok( '("name" "toto")' eq format_for_imap_arg( { name => 'toto' } ), 'format_for_imap_arg { name => toto }' ) ; - ok( '("name" "toto" "key" "val")' eq format_for_imap_arg( { name => 'toto', key => 'val' } ), 'format_for_imap_arg 2 x key val' ) ; +sub failure_login +{ + my( $mysync, $acc, $authmech, $imap, $host, $user ) = @ARG ; + my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ; + my $einfo = imap_last_error( $imap ) ; + my $error = "$info [$authmech]: $einfo\n" ; + errors_incr( $mysync, $error ) ; + return ; +} - note( 'Leaving tests_format_for_imap_arg()' ) ; +# failure_login and failure_proxyauth function are similar but +# variable $error so no factoring +sub failure_proxyauth +{ + my( $mysync, $acc, $authmech, $imap, $host, $user ) = @ARG ; + my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ; + my $einfo = imap_last_error( $imap ) ; + my $error = "$info [$authmech] using proxy-login as [$acc->{ authuser }]: $einfo\n" ; + errors_incr( $mysync, $error ) ; return ; } -sub quota { - my ( $imap, $side, $mysync ) = @_ ; - my %side = ( - h1 => 'Host1', - h2 => 'Host2', - ) ; - my $Side = $side{ $side } ; - my $debug_before = $imap->Debug( ) ; - $imap->Debug( 1 ) ; - if ( not $imap->has_capability( 'QUOTA' ) ) { - $imap->Debug( $debug_before ) ; - return ; - } ; - myprint( "\n$Side: found quota, presented in raw IMAP\n" ) ; - my $getquotaroot = $imap->getquotaroot( 'INBOX' ) ; - # Gmail INBOX quotaroot is "" but with it Mail::IMAPClient does a literal GETQUOTA {2} \n "" - #$imap->quota( 'ROOT' ) ; - #$imap->quota( '""' ) ; - myprint( "\n" ) ; - $imap->Debug( $debug_before ) ; - my $quota_limit_bytes = quota_extract_storage_limit_in_bytes( $getquotaroot ) ; - my $quota_current_bytes = quota_extract_storage_current_in_bytes( $getquotaroot ) ; - $mysync->{$side}->{quota_limit_bytes} = $quota_limit_bytes ; - $mysync->{$side}->{quota_current_bytes} = $quota_current_bytes ; - my $quota_percent ; - if ( $quota_limit_bytes > 0 ) { - $quota_percent = mysprintf( '%.2f', $NUMBER_100 * $quota_current_bytes / $quota_limit_bytes ) ; - }else{ - $quota_percent = 0 ; + + +sub oauthdirect +{ + my( $mysync, $acc, $imap, $host, $user ) = @_ ; + + my $oauthdirect_str ; + if ( -f -r $acc->{ oauthdirect } ) + { + $oauthdirect_str = firstline( $acc->{ oauthdirect } ) ; } - myprint( "$Side: Quota current storage is $quota_current_bytes bytes. Limit is $quota_limit_bytes bytes. So $quota_percent % full\n" ) ; - if ( $QUOTA_PERCENT_LIMIT < $quota_percent ) { - my $error = "$Side: $quota_percent % full: it is time to find a bigger place! ( $quota_current_bytes bytes / $quota_limit_bytes bytes )\n" ; - errors_incr( $mysync, $error ) ; + else + { + $oauthdirect_str = $acc->{ oauthdirect } || 'Please define oauthdirect value' ; + } + + $imap->Authmechanism( 'XOAUTH2' ) ; + $imap->Authcallback( sub { return $oauthdirect_str } ) ; + + #if ( $imap->authenticate('XOAUTH2', sub { return $oauthdirect_str } ) ) + if ( $imap->login( ) ) + { + return 1 ; + } + else + { + failure_login( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ; + return ; } return ; } -sub tests_quota_extract_storage_limit_in_bytes { - note( 'Entering tests_quota_extract_storage_limit_in_bytes()' ) ; - my $imap_output = [ - '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"', - '* QUOTA "Storage quota" (STORAGE 1 104857600)', - '* QUOTA "Messages quota" (MESSAGE 2 100000)', - '5 OK Getquotaroot completed.' - ] ; - ok( $NUMBER_104_857_600 * $KIBI == quota_extract_storage_limit_in_bytes( $imap_output ), 'quota_extract_storage_limit_in_bytes ') ; - note( 'Leaving tests_quota_extract_storage_limit_in_bytes()' ) ; - return ; -} -sub quota_extract_storage_limit_in_bytes { - my $imap_output = shift ; +sub oauthaccesstoken +{ + my( $mysync, $acc, $imap, $host, $user ) = @_ ; - my $limit_kb ; - $limit_kb = ( map { /.*\(\s*STORAGE\s+\d+\s+(\d+)\s*\)/x ? $1 : () } @{ $imap_output } )[0] ; - $limit_kb ||= 0 ; - $debug and myprint( "storage_limit_kb = $limit_kb\n" ) ; - return( $KIBI * $limit_kb ) ; -} + my $oauthaccesstoken_str ; + if ( -f -r $acc->{ oauthaccesstoken } ) + { + $oauthaccesstoken_str = firstline( $acc->{ oauthaccesstoken } ) ; + } + else + { + $oauthaccesstoken_str = $acc->{ oauthaccesstoken } || 'Please define oauthaccesstoken value' ; + } + my $oauth_string = "user=" . $user . "\x01auth=Bearer ". $oauthaccesstoken_str . "\x01\x01" ; + #myprint "oauth_string: $oauth_string\n" ; -sub tests_quota_extract_storage_current_in_bytes { - note( 'Entering tests_quota_extract_storage_current_in_bytes()' ) ; + my $oauth_string_base64 = encode_base64( $oauth_string , '' ) ; + #myprint "oauth_string_base64: $oauth_string_base64\n" ; + my $oauthdirect_str = $oauth_string_base64 ; - my $imap_output = [ - '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"', - '* QUOTA "Storage quota" (STORAGE 1 104857600)', - '* QUOTA "Messages quota" (MESSAGE 2 100000)', - '5 OK Getquotaroot completed.' - ] ; - ok( 1*$KIBI == quota_extract_storage_current_in_bytes( $imap_output ), 'quota_extract_storage_current_in_bytes: 1 => 1024 ') ; + $imap->Authmechanism( 'XOAUTH2' ) ; + $imap->Authcallback( sub { return $oauthdirect_str } ) ; - note( 'Leaving tests_quota_extract_storage_current_in_bytes()' ) ; + #if ( $imap->authenticate('XOAUTH2', sub { return $oauthdirect_str } ) ) + if ( $imap->login( ) ) + { + return 1 ; + } + else + { + failure_login( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ; + return ; + } return ; } -sub quota_extract_storage_current_in_bytes { - my $imap_output = shift ; - my $current_kb ; - $current_kb = ( map { /.*\(\s*STORAGE\s+(\d+)\s+\d+\s*\)/x ? $1 : () } @{ $imap_output } )[0] ; - $current_kb ||= 0 ; - $debug and myprint( "storage_current_kb = $current_kb\n" ) ; - return( $KIBI * $current_kb ) ; -} +sub check_capability +{ -sub automap { - my ( $mysync ) = @_ ; + my( $imap, $authmech, $Side ) = @_ ; - if ( $mysync->{automap} ) { - myprint( "Turned on automapping folders ( use --noautomap to turn off automapping )\n" ) ; - }else{ - myprint( "Turned off automapping folders ( use --automap to turn on automapping )\n" ) ; + + if ( $imap->has_capability( "AUTH=$authmech" ) + or $imap->has_capability( $authmech ) ) + { + myprintf("%s: %s says it has CAPABILITY for AUTHENTICATE %s\n", + $Side, $imap->Server, $authmech) ; + return ; + } + + if ( $authmech eq 'LOGIN' ) + { + # Well, the warning is so common and useless that I prefer to remove it + # No more "... says it has NO CAPABILITY for AUTHENTICATE LOGIN" return ; } - $mysync->{h1_special} = special_from_folders_hash( $mysync->{imap1}, 'Host1' ) ; - $mysync->{h2_special} = special_from_folders_hash( $mysync->{imap2}, 'Host2' ) ; - build_possible_special( $mysync ) ; - build_guess_special( $mysync ) ; - build_automap( $mysync ) ; + myprintf( "%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n", + $Side, $imap->Server, $authmech ) ; + + if ( $authmech eq 'PLAIN' ) + { + myprint( "$Side: frequently PLAIN is only supported with SSL, try --ssl or --tls options\n" ) ; + } + + return ; +} + +sub set_ssl +{ + my ( $imap, $acc ) = @_ ; + # SSL_version can be + # SSLv3 SSLv2 SSLv23 SSLv23:!SSLv2 (last one is the default in IO-Socket-SSL-1.953) + # + + my $sslargs_hash = $acc->{sslargs} ; + + my $sslargs_default = { + SSL_verify_mode => $SSL_VERIFY_POLICY, + SSL_verifycn_scheme => 'imap', + SSL_cipher_list => 'DEFAULT:!DH', + } ; + + # initiate with default values + my %sslargs_mix = %{ $sslargs_default } ; + # now override with passed values + @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ; + # remove keys with undef values + foreach my $key ( keys %sslargs_mix ) { + delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ; + } + # back to an ARRAY + my @sslargs_mix = %sslargs_mix ; + #myprint( Data::Dumper->Dump( [ $sslargs_hash, $sslargs_default, \%sslargs_mix, \@sslargs_mix ] ) ) ; + $imap->Ssl( \@sslargs_mix ) ; + return ; +} + +sub set_tls +{ + my ( $imap, $acc ) = @_ ; + + my $sslargs_hash = $acc->{sslargs} ; + + my $sslargs_default = { + SSL_verify_mode => $SSL_VERIFY_POLICY, + SSL_cipher_list => 'DEFAULT:!DH', + } ; + #myprint( Data::Dumper->Dump( [ $acc, $sslargs_hash, $sslargs_default ] ) ) ; + + # initiate with default values + my %sslargs_mix = %{ $sslargs_default } ; + # now override with passed values + @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ; + # remove keys with undef values + foreach my $key ( keys %sslargs_mix ) { + delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ; + } + # back to an ARRAY + my @sslargs_mix = %sslargs_mix ; + $imap->Starttls( \@sslargs_mix ) ; return ; } +sub plainauth +{ + my $code = shift; + my $imap = shift; -sub build_guess_special { - my ( $mysync ) = shift ; - - foreach my $h1_fold ( sort keys %{ $mysync->{h1_folders_all} } ) { - my $special = guess_special( $h1_fold, $mysync->{possible_special}, $mysync->{h1_prefix} ) ; - if ( $special ) { - $mysync->{h1_special_guessed}{$h1_fold} = $special ; - my $already_guessed = $mysync->{h1_special_guessed}{$special} ; - if ( $already_guessed ) { - myprint( "Host1: $h1_fold not $special because set to $already_guessed\n" ) ; - }else{ - $mysync->{h1_special_guessed}{$special} = $h1_fold ; - } - } - } - foreach my $h2_fold ( sort keys %{ $mysync->{h2_folders_all} } ) { - my $special = guess_special( $h2_fold, $mysync->{possible_special}, $mysync->{h2_prefix} ) ; - if ( $special ) { - $mysync->{h2_special_guessed}{$h2_fold} = $special ; - my $already_guessed = $mysync->{h2_special_guessed}{$special} ; - if ( $already_guessed ) { - myprint( "Host2: $h2_fold not $special because set to $already_guessed\n" ) ; - }else{ - $mysync->{h2_special_guessed}{$special} = $h2_fold ; - } - } - } - return ; + my $string = mysprintf("%s\x00%s\x00%s", $imap->User, + defined $imap->Authuser ? $imap->Authuser : "", $imap->Password); + return encode_base64("$string", q{}); } -sub guess_special { - my( $folder, $possible_special_ref, $prefix ) = @_ ; +# Copy from https://github.com/imapsync/imapsync/pull/25/files +# Changes "use" pragmas to "require". +# The openssl system call shall be replaced by pure Perl and +# https://metacpan.org/pod/Crypt::OpenSSL::PKCS12 - my $folder_no_prefix = $folder ; - $folder_no_prefix =~ s/\Q${prefix}\E//xms ; - #$debug and myprint( "folder_no_prefix: $folder_no_prefix\n" ) ; +# Now the Joaquin Lopez code: +# +# Used this as an example: https://gist.github.com/gsainio/6322375 +# +# And this as a reference: https://developers.google.com/accounts/docs/OAuth2ServiceAccount +# (note there is an http/rest tab, where the real info is hidden away... went on a witch hunt +# until I noticed that...) +# +# This is targeted at gmail to maintain compatibility after google's oauth1 service is deactivated +# on May 5th, 2015: https://developers.google.com/gmail/oauth_protocol +# If there are other oauth2 implementations out there, this would need to be modified to be +# compatible +# +# This is a good guide on setting up the google api/apps side of the equation: +# http://www.limilabs.com/blog/oauth2-gmail-imap-service-account +# +# 2016/05/27: Updated to support oauth/key data in the .json files Google now defaults to +# when creating gmail service accounts. They're easier to work with since they neither +# requiring decrypting nor specifying the oauth2 client id separately. +# +# If the password arg ends in .json, it will assume this new json method, otherwise it +# will fallback to the "oauth client id;.p12" format it was previously using. +sub xoauth2 +{ + require JSON::WebToken ; + require LWP::UserAgent ; + require HTML::Entities ; + require JSON ; + require JSON::WebToken::Crypt::RSA ; + require Crypt::OpenSSL::PKCS12; + require Crypt::OpenSSL::RSA ; + require Encode::Byte ; + require IO::Socket::SSL ; - my $guess_special = $possible_special_ref->{ $folder } - || $possible_special_ref->{ $folder_no_prefix } - || q{} ; + my $code = shift; + my $imap = shift; - return( $guess_special ) ; -} + my ($iss,$key); -sub tests_guess_special { - note( 'Entering tests_guess_special()' ) ; + if( $imap->Password =~ /^(.*\.json)$/x ) + { + my $json = JSON->new( ) ; + my $filename = $1; + $sync->{ debug } and myprint( "XOAUTH2 json file: $filename\n" ) ; + my $FILE ; + if ( ! open( $FILE, '<', $filename ) ) + { + $sync->{nb_errors}++ ; + exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, + "error [$filename]: $OS_ERROR\n" + ) ; + } + my $jsonfile = $json->decode( join q{}, <$FILE> ) ; + close $FILE ; - my $possible_special_ref = build_possible_special( my $mysync ) ; - ok( '\Sent' eq guess_special( 'Sent', $possible_special_ref, q{} ) ,'guess_special: Sent => \Sent' ) ; - ok( q{} eq guess_special( 'Blabla', $possible_special_ref, q{} ) ,'guess_special: Blabla => q{}' ) ; - ok( '\Sent' eq guess_special( 'INBOX.Sent', $possible_special_ref, 'INBOX.' ) ,'guess_special: INBOX.Sent => \Sent' ) ; - ok( '\Sent' eq guess_special( 'IN BOX.Sent', $possible_special_ref, 'IN BOX.' ) ,'guess_special: IN BOX.Sent => \Sent' ) ; + $iss = $jsonfile->{client_id}; + $key = $jsonfile->{private_key}; + $sync->{ debug } and myprint( "Service account: $iss\n"); + $sync->{ debug } and myprint( "Private key:\n$key\n"); + } + else + { + # Get iss (service account address), keyfile name, and keypassword if necessary + ( $iss, my $keyfile, my $keypass ) = $imap->Password =~ /([\-\d\w\@\.]+);([a-zA-Z0-9 \_\-\.\/]+);?(.*)?/x ; - note( 'Leaving tests_guess_special()' ) ; - return ; -} + # Assume key password is google default if not provided + $keypass = 'notasecret' if not $keypass; -sub build_automap { - my $mysync = shift ; - $debug and myprint( "Entering build_automap\n" ) ; - foreach my $h1_fold ( @{ $mysync->{h1_folders_wanted} } ) { - my $h2_fold ; - my $h1_special = $mysync->{h1_special}{$h1_fold} ; - my $h1_special_guessed = $mysync->{h1_special_guessed}{$h1_fold} ; + $sync->{ debug } and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n"); - # Case 1: special on both sides. - if ( $h1_special - and exists $mysync->{h2_special}{$h1_special} ) { - $h2_fold = $mysync->{h2_special}{$h1_special} ; - $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ; - next ; - } - # Case 2: special on host1, not on host2 - if ( $h1_special - and ( not exists $mysync->{h2_special}{$h1_special} ) - and ( exists $mysync->{h2_special_guessed}{$h1_special} ) - ) { - # special_guessed on host2 - $h2_fold = $mysync->{h2_special_guessed}{$h1_special} ; - $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ; - next ; - } - # Case 3: no special on host1, special on host2 - if ( ( not $h1_special ) - and ( $h1_special_guessed ) - and ( exists $mysync->{h2_special}{$h1_special_guessed} ) - ) { - $h2_fold = $mysync->{h2_special}{$h1_special_guessed} ; - $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ; - next ; - } - # Case 4: no special on both sides. - if ( ( not $h1_special ) - and ( $h1_special_guessed ) - and ( not exists $mysync->{h2_special}{$h1_special_guessed} ) - and ( exists $mysync->{h2_special_guessed}{$h1_special_guessed} ) - ) { - $h2_fold = $mysync->{h2_special_guessed}{$h1_special_guessed} ; - $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ; - next ; - } + # Get private key from p12 file + my $pkcs12 = Crypt::OpenSSL::PKCS12->new_from_file($keyfile); + $key = $pkcs12->private_key($keypass); + + $sync->{ debug } and myprint( "Private key:\n$key\n"); } - return( $mysync->{f1f2auto} ) ; -} -# I will not add what there is at: -# http://stackoverflow.com/questions/2185391/localized-gmail-imap-folders/2185548#2185548 -# because it works well without -sub build_possible_special { - my $mysync = shift ; - my $possible_special = { } ; - # All|Archive|Drafts|Flagged|Junk|Sent|Trash + # Create jwt of oauth2 request + my $time = time ; + my $jwt = JSON::WebToken->encode( { + 'iss' => $iss, # service account + 'scope' => 'https://mail.google.com/', + 'aud' => 'https://www.googleapis.com/oauth2/v3/token', + 'exp' => $time + $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12, + 'iat' => $time, + 'prn' => $imap->User # user to auth as + }, + $key, 'RS256', {'typ' => 'JWT'} ); # Crypt::OpenSSL::RSA needed here. - $possible_special->{'\All'} = [ 'All', 'All Messages', '&BBIEQQQ1-' ] ; - $possible_special->{'\Archive'} = [ 'Archive', 'Archives', '&BBAEQARFBDgEMg-' ] ; - $possible_special->{'\Drafts'} = [ 'Drafts', 'DRAFTS', '&BCcENQRABD0EPgQyBDgEOgQ4-', 'Szkice', 'Wersje robocze' ] ; - $possible_special->{'\Flagged'} = [ 'Flagged', 'Starred', '&BB8EPgQ8BDUERwQ1BD0EPQRLBDU-' ] ; - $possible_special->{'\Junk'} = [ 'Junk', 'Spam', 'SPAM', '&BCEEPwQwBDw-', 'Potwierdzony spam', 'Wiadomo&AVs-ci-&AVs-mieci' ] ; - $possible_special->{'\Sent'} = [ 'Sent', 'Sent Messages', 'Sent Items', - 'Gesendete Elemente', 'Gesendete Objekte', - '&AMk-l&AOk-ments envoy&AOk-s', 'Envoy&AOk-', 'Objets envoy&AOk-s', - 'Elementos enviados', - '&kAFP4W4IMH8wojCkMMYw4A-', - '&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-', - 'Elementy wys&AUI-ane'] ; - $possible_special->{'\Trash'} = [ 'Trash', 'TRASH', '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-', 'Kosz', 'Deleted Items' ] ; + # Post oauth2 request + my $ua = LWP::UserAgent->new( ) ; + $ua->env_proxy( ) ; - - foreach my $special ( qw( \All \Archive \Drafts \Flagged \Junk \Sent \Trash ) ){ - foreach my $possible_folder ( @{ $possible_special->{$special} } ) { - $possible_special->{ $possible_folder } = $special ; - } ; + my $response = $ua->post('https://www.googleapis.com/oauth2/v3/token', + { grant_type => HTML::Entities::encode_entities('urn:ietf:params:oauth:grant-type:jwt-bearer'), + assertion => $jwt } ) ; + + unless( $response->is_success( ) ) { + $sync->{nb_errors}++ ; + exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, + $response->code, "\n", $response->content, "\n" + ) ; + }else{ + $sync->{ debug } and myprint( $response->content ) ; } - $mysync->{possible_special} = $possible_special ; - $debug and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] ) ) ; - return( $possible_special ) ; -} -sub special_from_folders_hash { - my ( $imap, $side ) = @_ ; - my %special = ( ) ; + # access_token in response is what we need + my $data = JSON::decode_json( $response->content ) ; - if ( ! defined $imap ) { return ; } - $side = defined $side ? $side : 'Host?' ; + # format as oauth2 auth data + my $xoauth2_string = encode_base64( 'user=' . $imap->User . "\1auth=Bearer " . $data->{access_token} . "\1\1", q{} ) ; - if ( ! $imap->can( 'folders_hash' ) ) { - my $error = "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ; - errors_incr( $sync, $error ) ; - return( \%special ) ; # empty hash ref - } - my $folders_hash = $imap->folders_hash( ) ; - foreach my $fhash (@{ $folders_hash } ) { - my @special = grep { /\\(?:All|Archive|Drafts|Flagged|Junk|Sent|Trash)/x } @{ $fhash->{attrs} } ; - if ( @special ) { - my $special = $special[0] ; # keep first one. Could be not very good. - if ( exists $special{ $special } ) { - myprintf( "%s: special %-20s = %s already assigned to %s\n", - $side, $fhash->{name}, join( q{ }, @special ), $special{ $special } ) ; - }else{ - myprintf( "%s: special %-20s = %s\n", - $side, $fhash->{name}, join( q{ }, @special ) ) ; - $special{ $special } = $fhash->{name} ; - $special{ $fhash->{name} } = $special ; # double entry value => key - } - } - } - myprint( "\n" ) if ( %special ) ; - return( \%special ) ; + $sync->{ debug } and myprint( "XOAUTH2 String: $xoauth2_string\n"); + return($xoauth2_string); } -sub tests_special_from_folders_hash { - note( 'Entering tests_special_from_folders_hash()' ) ; - require Test::MockObject ; - my $imapT = Test::MockObject->new( ) ; - is( undef, special_from_folders_hash( ), 'special_from_folders_hash: no args' ) ; - is_deeply( {}, special_from_folders_hash( $imapT ), 'special_from_folders_hash: $imap void' ) ; +sub xmasterauth +{ + # This is Kerio auth admin + # This code comes from + # https://github.com/imapsync/imapsync/pull/53/files - $imapT->mock( 'folders_hash', sub { return( [ { name => 'Sent', attrs => [ '\Sent' ] } ] ) } ) ; - is_deeply( { Sent => '\Sent', '\Sent' => 'Sent' }, special_from_folders_hash( $imapT ), 'special_from_folders_hash: $imap \Sent' ) ; + my $imap = shift ; - note( 'Leaving tests_special_from_folders_hash()' ) ; - return( ) ; -} + my $user = $imap->User( ) ; + my $password = $imap->Password( ) ; + my $authmech = 'X-MASTERAUTH' ; -sub errors_incr { - my ( $mysync, @error ) = @ARG ; - $sync->{nb_errors}++ ; + my @challenge = $imap->tag_and_run( $authmech, "+" ) ; + if ( not defined $challenge[0] ) + { + $sync->{nb_errors}++ ; + exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, + "Failure authenticate with $authmech: ", + $imap->LastError, "\n" + ) ; + return ; # hahaha! + } + $sync->{ debug } and myprint( "X-MASTERAUTH challenge: [@challenge]\n" ) ; - if ( @error ) { - errors_log( $mysync, @error ) ; - myprint( @error ) ; + $challenge[1] =~ s/^\+ |^\s+|\s+$//g ; + if ( ! $imap->_imap_command( { addcrlf => 1, addtag => 0, tag => $imap->Count }, md5_hex( $challenge[1] . $password ) ) ) + { + $sync->{nb_errors}++ ; + exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, + "Failure authenticate with $authmech: ", + $imap->LastError, "\n" + ) ; } - $mysync->{errorsmax} ||= $ERRORS_MAX ; - if ( $sync->{nb_errors} >= $mysync->{errorsmax} ) { - myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). Exiting.\n" ) ; - if ( $mysync->{errorsdump} ) { - myprint( errorsdump( $sync->{nb_errors}, errors_log( $mysync ) ) ) ; - # again since errorsdump( ) can be very verbose and masquerade previous warning - myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). Exiting.\n" ) ; - } - exit_clean( $mysync, $EXIT_WITH_ERRORS_MAX ) ; + if ( ! $imap->tag_and_run( 'X-SETUSER ' . $user ) ) + { + $sync->{nb_errors}++ ; + exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, + "Failure authenticate with $authmech: ", + "X-SETUSER ", $imap->LastError, "\n" + ) ; } + + $imap->State( Mail::IMAPClient::Authenticated ) ; + # I comment this state because "Selected" state is usually done by SELECT or EXAMINE imap commands + # $imap->State( Mail::IMAPClient::Selected ) ; + return ; } -sub errors_log { - my ( $mysync, @error ) = @ARG ; +sub keepalive1 +{ + my $mysync = shift ; - if ( ! $mysync->{errors_log} ) { - $mysync->{errors_log} = [] ; - } + $mysync->{ acc1 }->{ keepalive } = defined $mysync->{ acc1 }->{ keepalive } ? $mysync->{ acc1 }->{ keepalive } : 1 ; - if ( @error ) { - push @{ $mysync->{errors_log} }, join( q{}, @error ) ; + if ( $mysync->{ acc1 }->{ keepalive } ) + { + myprint( "Host1: imap connection keepalive is on on host1. Use --nokeepalive1 to disable it.\n" ) ; } - if ( @{ $mysync->{errors_log} } ) { - return @{ $mysync->{errors_log} } ; + else + { + myprint( "Host1: imap connection keepalive is off on host1. Use --keepalive1 to enable it.\n" ) ; + } +} + +sub keepalive2 +{ + my $mysync = shift ; + + $mysync->{ acc2 }->{ keepalive } = defined $mysync->{ acc2 }->{ keepalive } ? $mysync->{ acc2 }->{ keepalive } : 1 ; + + if ( $mysync->{ acc2 }->{ keepalive } ) + { + myprint( "Host2: imap connection keepalive is on on host2. Use --nokeepalive2 to disable it.\n" ) ; } - else { - return ; + else + { + myprint( "Host2: imap connection keepalive is off on host2. Use --keepalive2 to enable it.\n" ) ; } } -sub tests_errors_log { - note( 'Entering tests_errors_log()' ) ; - note( 'Leaving tests_errors_log()' ) ; - return ; -} +sub banner_imapsync +{ + my $mysync = shift @ARG ; + my @argv = @ARG ; + my $banner_imapsync = join q{}, + q{$RCSfile: imapsync,v $ }, + q{$Revision: 2.178 $ }, + q{$Date: 2022/01/12 21:28:37 $ }, + "\n", + "Command line used, run by $EXECUTABLE_NAME:\n", + "$PROGRAM_NAME ", command_line_nopassword( $mysync, @argv ), "\n" ; -sub errorsdump { - my( $nb_errors, @errors_log ) = @ARG ; - my $error_num = 0 ; - my $errors_list = q{} ; - if ( @errors_log ) { - $errors_list = "++++ Listing $nb_errors errors encountered during the sync ( avoid this listing with --noerrorsdump ).\n" ; - foreach my $error ( @errors_log ) { - $error_num++ ; - $errors_list .= "Err $error_num/$nb_errors: $error" ; - } - } - return( $errors_list ) ; + return( $banner_imapsync ) ; } +sub tests_do_valid_directory +{ + note( 'Entering tests_do_valid_directory()' ) ; -sub tests_live_result { - note( 'Entering tests_live_result()' ) ; + is( 1, do_valid_directory( '.'), 'do_valid_directory: . good' ) ; + is( 1, do_valid_directory( './W/tmp/tests/valid/sub'), 'do_valid_directory: ./W/tmp/tests/valid/sub good' ) ; - my $nb_errors = shift ; - if ( $nb_errors ) { - myprint( "Live tests failed with $nb_errors errors\n" ) ; - } else { - myprint( "Live tests ended successfully\n" ) ; - } - note( 'Leaving tests_live_result()' ) ; - return ; -} + Readonly my $NB_UNIX_tests_do_valid_directory_non_root => 2 ; + diag( "OSNAME=$OSNAME EFFECTIVE_USER_ID=$EFFECTIVE_USER_ID" ) ; -sub foldersizesatend { - timenext( ) ; - return if ( $imap1->IsUnconnected( ) ) ; - return if ( $imap2->IsUnconnected( ) ) ; - # Get all folders on host2 again since new were created - @h2_folders_all = sort $imap2->folders(); - for ( @h2_folders_all ) { - $h2_folders_all{ $_ } = 1 ; - $h2_folders_all_UPPER{ uc $_ } = 1 ; - } ; - ( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( 'Host1', $imap1, $search1, $sync->{abletosearch1}, @h1_folders_wanted ) ; - ( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( 'Host2', $imap2, $search2, $sync->{abletosearch2}, @h2_folders_from_1_wanted ) ; - if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) { - my $error = "Failure getting foldersizes, final differences will not be calculated\n" ; - errors_incr( $sync, $error ) ; + SKIP: { + skip( 'Tests only for non roor user', $NB_UNIX_tests_do_valid_directory_non_root ) if ( '0' eq $EFFECTIVE_USER_ID ) ; + diag( 'The "Error / is not writable" is on purpose' ) ; + ok( 0 == do_valid_directory( '/'), 'do_valid_directory: / bad' ) ; + diag( 'The "Error permission denied" on /noway is on purpose' ) ; + ok( 0 == do_valid_directory( '/noway'), 'do_valid_directory: /noway bad' ) ; } + + + note( 'Leaving tests_do_valid_directory()' ) ; return ; } -sub size_filtered_flag { - my $h1_size = shift ; +sub do_valid_directory +{ + my $dir = shift @ARG ; - if (defined $maxsize and $h1_size >= $maxsize) { - return( 1 ) ; + # all good => return ok. + return( 1 ) if ( -d $dir and -r _ and -w _ ) ; + + # exist but bad + if ( -e $dir and not -d _ ) { + myprint( "Error: $dir exists but is not a directory\n" ) ; + return( 0 ) ; } - if (defined $minsize and $h1_size <= $minsize) { - return( 1 ) ; + if ( -e $dir and not -w _ ) { + my $sb = stat $dir ; + myprintf( "Error: directory %s is not writable for user %s, permissions are %04o and owner is %s ( uid %s )\n", + $dir, getpwuid_any_os( $EFFECTIVE_USER_ID ), ($sb->mode & oct($PERMISSION_FILTER) ), getpwuid_any_os( $sb->uid ), $sb->uid( ) ) ; + return( 0 ) ; + } + # Trying to create it + myprint( "Creating directory $dir (current directory is " . getcwd( ) . ")\n" ) ; + if ( ! eval { mkpath( $dir ) } ) { + myprint( "$EVAL_ERROR" ) if ( $EVAL_ERROR ) ; } + return( 1 ) if ( -d $dir and -r _ and -w _ ) ; return( 0 ) ; } -sub sync_flags_fir { - my ( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) = @_ ; - if ( not defined $h1_msg ) { return } ; - if ( not defined $h2_msg ) { return } ; +sub tests_match_a_pid_number +{ + note( 'Entering tests_match_a_pid_number()' ) ; - my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} ; - return if size_filtered_flag( $h1_size ) ; + is( undef, match_a_pid_number( ), 'match_a_pid_number: no args => undef' ) ; + is( undef, match_a_pid_number( q{} ), 'match_a_pid_number: "" => undef' ) ; + is( undef, match_a_pid_number( 'lalala' ), 'match_a_pid_number: lalala => undef' ) ; + is( 1, match_a_pid_number( 1 ), 'match_a_pid_number: 1 => 1' ) ; + is( 1, match_a_pid_number( 123 ), 'match_a_pid_number: 123 => 1' ) ; + is( 1, match_a_pid_number( -123 ), 'match_a_pid_number: -123 => 1' ) ; + is( 1, match_a_pid_number( '123' ), 'match_a_pid_number: "123" => 1' ) ; + is( 1, match_a_pid_number( '-123' ), 'match_a_pid_number: "-123" => 1' ) ; + is( undef, match_a_pid_number( 'a123' ), 'match_a_pid_number: a123 => undef' ) ; + is( undef, match_a_pid_number( '-a123' ), 'match_a_pid_number: -a123 => undef' ) ; + is( 1, match_a_pid_number( 99999 ), 'match_a_pid_number: 99999 => 1' ) ; + is( 1, match_a_pid_number( -99999 ), 'match_a_pid_number: -99999 => 1' ) ; + is( undef, match_a_pid_number( 0 ), 'match_a_pid_number: 0 => undef' ) ; + is( 1, match_a_pid_number( 100000 ), 'match_a_pid_number: 100000 => 1' ) ; + is( 1, match_a_pid_number( 123456 ), 'match_a_pid_number: 123456 => 1' ) ; + is( undef, match_a_pid_number( '-0' ), 'match_a_pid_number: "-0" => undef' ) ; + is( 1, match_a_pid_number( -100000 ), 'match_a_pid_number: -100000 => 1' ) ; + is( 1, match_a_pid_number( -123456 ), 'match_a_pid_number: -123456 => 1' ) ; + is( 1, match_a_pid_number( 2**22 ), 'match_a_pid_number: 2**22 => 1' ) ; + is( undef, match_a_pid_number( 2**22 + 1 ), 'match_a_pid_number: 2**22 + 1 => undef' ) ; + is( undef, match_a_pid_number( 4194304 + 1 ), 'match_a_pid_number: 2**22 + 1 = 4194305 => undef' ) ; + + note( 'Leaving tests_match_a_pid_number()' ) ; + return ; +} - # used cached flag values for efficiency - my $h1_flags = $h1_fir_ref->{ $h1_msg }->{ 'FLAGS' } || q{} ; - my $h2_flags = $h2_fir_ref->{ $h2_msg }->{ 'FLAGS' } || q{} ; +sub match_a_pid_number +{ + my $pid = shift @ARG ; + if ( ! defined $pid ) { return ; } + #print "$pid\n" ; + if ( ! match( $pid, '^-?\d+$' ) ) { return ; } + #print "$pid\n" ; + # can be negative on Windows + #if ( 0 > $pid ) { return ; } + #if ( 65535 < $pid ) { return ; } + if ( 2**22 < abs( $pid ) ) { return ; } + if ( 0 == abs( $pid ) ) { return ; } + return 1 ; +} + +sub tests_remove_pidfile_not_running +{ + note( 'Entering tests_remove_pidfile_not_running()' ) ; - sync_flags( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ; + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'remove_pidfile_not_running: mkpath W/tmp/tests/' ) ; + is( undef, remove_pidfile_not_running( ), 'remove_pidfile_not_running: no args => undef' ) ; + is( undef, remove_pidfile_not_running( './W' ), 'remove_pidfile_not_running: a dir => undef' ) ; + is( undef, remove_pidfile_not_running( 'noexists' ), 'remove_pidfile_not_running: noexists => undef' ) ; + is( 1, touch( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: prepa empty W/tmp/tests/empty.pid' ) ; + is( undef, remove_pidfile_not_running( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: W/tmp/tests/empty.pid => undef' ) ; + is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/lalala.pid' ) ; + is( undef, remove_pidfile_not_running( 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: W/tmp/tests/lalala.pid => undef' ) ; + is( '55555', string_to_file( '55555', 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/notrunning.pid' ) ; + is( 1, remove_pidfile_not_running( 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: W/tmp/tests/notrunning.pid => 1' ) ; + is( $PROCESS_ID, string_to_file( $PROCESS_ID, 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/running.pid' ) ; + is( undef, remove_pidfile_not_running( 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: W/tmp/tests/running.pid => undef' ) ; + note( 'Leaving tests_remove_pidfile_not_running()' ) ; return ; } -sub sync_flags_after_copy { - my( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $permanentflags2 ) = @_ ; +sub remove_pidfile_not_running +{ + # + my $pid_filename = shift @ARG ; + + #myprint( "In remove_pidfile_not_running $pid_filename\n" ) ; + if ( ! $pid_filename ) { myprint( "No variable pid_filename\n" ) ; return } ; + if ( ! -e $pid_filename ) + { + myprint( "File $pid_filename does not exist\n" ) ; + return ; + } + #myprint( "Still In remove_pidfile_not_running $pid_filename\n" ) ; + + if ( ! -f $pid_filename ) { myprint( "File $pid_filename is not a file\n" ) ; return } ; - my @h2_flags = $imap2->flags( $h2_msg ) ; - my $h2_flags = "@h2_flags" ; - ( $debug or $debugflags ) and myprint( "Host2 flags before resync by STORE on msg $h2_msg: $h2_flags\n" ) ; - sync_flags( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ; + my $pid = firstline( $pid_filename ) ; + if ( ! match_a_pid_number( $pid ) ) { myprint( "In remove_pidfile_not_running: pid $pid in $pid_filename is not a pid number\n" ) ; return } ; + # can't kill myself => do nothing + if ( ! kill 'ZERO', $PROCESS_ID ) { myprint( "Can not kill ZERO myself $PROCESS_ID\n" ) ; return } ; + + # can't kill ZERO the pid => it is gone or own by another user => remove pidfile + if ( ! kill 'ZERO', $pid ) { + myprint( "Removing old $pid_filename since its PID $pid is not running anymore (oo-killed?)\n" ) ; + if ( unlink $pid_filename ) { + myprint( "Removed old $pid_filename\n" ) ; + return 1 ; + }else{ + myprint( "Could not remove old $pid_filename because $!\n" ) ; + return ; + } + } + myprint( "Another imapsync process $pid is running as says pidfile $pid_filename\n" ) ; return ; } -sub sync_flags { - my( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) = @_ ; - ( $debug or $debugflags ) and - myprint( "Host1: flags init msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 $h2_fold/$h2_msg flags( $h2_flags )\n" ) ; +sub tests_tail +{ + note( 'Entering tests_tail()' ) ; - $h1_flags = flags_for_host2( $h1_flags, $permanentflags2 ) ; + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'tail: mkpath W/tmp/tests/' ) ; + ok( ( ! -e 'W/tmp/tests/tail.pid' || unlink 'W/tmp/tests/tail.pid' ), 'tail: unlink W/tmp/tests/tail.pid' ) ; + ok( ( ! -e 'W/tmp/tests/tail.txt' || unlink 'W/tmp/tests/tail.txt' ), 'tail: unlink W/tmp/tests/tail.txt' ) ; - $h2_flags = flagscase( $h2_flags ) ; + is( undef, tail( ), 'tail: no args => undef' ) ; + my $mysync ; + is( undef, tail( $mysync ), 'tail: no pidfile => undef' ) ; - ( $debug or $debugflags ) and - myprint( "Host1 flags filt msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 $h2_fold/$h2_msg flags( $h2_flags )\n" ) ; + $mysync->{pidfile} = 'W/tmp/tests/tail.pid' ; + is( undef, tail( $mysync ), 'tail: no pidfilelocking => undef' ) ; + $mysync->{pidfilelocking} = 1 ; + is( undef, tail( $mysync ), 'tail: pidfile no exists => undef' ) ; - # compare flags - set flags if there a difference - my @h1_flags = sort split(q{ }, $h1_flags ); - my @h2_flags = sort split(q{ }, $h2_flags ); - my $diff = compare_lists( \@h1_flags, \@h2_flags ); - $diff and ( $debug or $debugflags ) - and myprint( "Host2 flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ; - # This sets flags so flags can be removed with this - # When you remove a \Seen flag on host1 you want to it - # to be removed on host2. Just add flags is not what - # we need most of the time. + my $pidandlog = "33333\nW/tmp/tests/tail.txt\n" ; + is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put pid 33333 and tail.txt in pidfile' ) ; + is( undef, tail( $mysync ), 'tail: logfile to tail no exists => undef' ) ; - if ( not $sync->{dry} and $diff and not $imap2->store( $h2_msg, "FLAGS.SILENT (@h1_flags)" ) ) { - my $error_msg = join q{}, "Host2 flags msg $h2_fold/$h2_msg could not add flags [@h1_flags]: ", - $imap2->LastError || q{}, "\n" ; - errors_incr( $sync, $error_msg ) ; - } + my $tailcontent = "L1\nL2\nL3\nL4\nL5\n" ; + is( $tailcontent, string_to_file( $tailcontent, 'W/tmp/tests/tail.txt' ), + 'tail: put L1\nL2\nL3\nL4\nL5\n in W/tmp/tests/tail.txt' ) ; - return ; -} + is( undef, tail( $mysync ), 'tail: fake pid in pidfile + tail off => 1' ) ; + $mysync->{ tail } = 1 ; + is( 1, tail( $mysync ), 'tail: fake pid in pidfile + tail on=> 1' ) ; + # put my own pid, won't do tail + $pidandlog = "$PROCESS_ID\nW/tmp/tests/tail.txt\n" ; + is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put my own PID in pidfile' ) ; + is( undef, tail( $mysync ), 'tail: my own pid in pidfile => undef' ) ; -sub _filter { - my $str = shift or return q{} ; - my $sz = $SIZE_MAX_STR ; - my $len = length $str ; - if ( not $debug and $len > $sz*2 ) { - my $beg = substr $str, 0, $sz ; - my $end = substr $str, -$sz, $sz ; - $str = $beg . '...' . $end ; - } - $str =~ s/\012?\015$//x ; - return "(len=$len) " . $str ; + note( 'Leaving tests_tail()' ) ; + return ; } -sub lost_connection { - my( $imap, $error_message ) = @_; - if ( $imap->IsUnconnected( ) ) { - $sync->{nb_errors}++ ; - my $lcomm = $imap->LastIMAPCommand || q{} ; - my $einfo = $imap->LastError || @{$imap->History}[$LAST] || q{} ; +sub tail +{ + # return undef on failures + # return 1 on success - # if string is long try reduce to a more reasonable size - $lcomm = _filter( $lcomm ) ; - $einfo = _filter( $einfo ) ; - myprint( "Failure: last command: $lcomm\n") if ($debug && $lcomm) ; - myprint( "Failure: lost connection $error_message: ", $einfo, "\n") ; - return( 1 ) ; - } - else{ - return( 0 ) ; - } -} + my $mysync = shift ; -sub tests_max { - note( 'Entering tests_max()' ) ; - is( 0, max( 0 ), 'max 0 => 0' ) ; - is( 1, max( 1 ), 'max 1 => 1' ) ; - is( $MINUS_ONE, max( $MINUS_ONE ), 'max -1 => -1') ; - is( undef, max( ), 'max no arg => undef' ) ; - is( $NUMBER_100, max( 1, $NUMBER_100 ), 'max 1 100 => 100' ) ; - is( $NUMBER_100, max( $NUMBER_100, 1 ), 'max 100 1 => 100' ) ; - is( $NUMBER_100, max( $NUMBER_100, $NUMBER_42, 1 ), 'max 100 42 1 => 100' ) ; - is( $NUMBER_100, max( $NUMBER_100, '42', 1 ), 'max 100 42 1 => 100' ) ; - is( $NUMBER_100, max( '100', '42', 1 ), 'max 100 42 1 => 100' ) ; - is( $NUMBER_100, max( $NUMBER_100, 'haha', 1 ), 'max 100 haha 1 => 100') ; - is( $NUMBER_100, max( 'bb', $NUMBER_100, 'haha' ), 'max bb 100 haha => 100') ; - is( $MINUS_ONE, max( q{}, $MINUS_ONE, 'haha' ), 'max "" -1 haha => -1') ; - is( $MINUS_ONE, max( q{}, $MINUS_ONE, $MINUS_TWO ), 'max "" -1 -2 => -1') ; - is( $MINUS_ONE, max( 'haha', $MINUS_ONE, $MINUS_TWO ), 'max haha -1 -2 => -1') ; - is( 1, max( $MINUS_ONE, 1 ), 'max -1 1 => 1') ; - is( 1, max( undef, 1 ), 'max undef 1 => 1' ) ; - is( 0, max( undef, 0 ), 'max undef 0 => 0' ) ; - is( 'haha', max( 'haha' ), 'max haha => haha') ; - is( 'bb', max( 'aa', 'bb' ), 'max aa bb => bb') ; - is( 'bb', max( 'bb', 'aa' ), 'max bb aa => bb') ; - is( 'bb', max( 'bb', 'aa', 'bb' ), 'max bb aa bb => bb') ; - note( 'Leaving tests_max()' ) ; - return ; -} + # no tail when aborting! + if ( $mysync->{ abort } ) { return ; } -sub max { - my @list = @_ ; - return( undef ) if ( 0 == scalar @list ) ; - - my( @numbers, @notnumbers ) ; - foreach my $item ( @list ) { - if ( is_number( $item ) ) { - push @numbers, $item ; - }else{ - push @notnumbers, $item ; - } - } - - my @sorted ; - if ( @numbers ) { - @sorted = sort { $a <=> $b } @numbers ; - }elsif( @notnumbers ) { - @sorted = sort { $a cmp $b } @notnumbers ; - }else{ - return ; - } - - return( pop @sorted ) ; -} + my $pidfile = $mysync->{pidfile} ; + my $lock = $mysync->{pidfilelocking} ; + my $tail = $mysync->{tail} ; -sub tests_is_number { - ok( ! is_number( ), 'is_number: no args => undef ' ) ; - ok( is_number( 1 ), 'is_number: 1 => 1' ) ; - ok( is_number( 1.1 ), 'is_number: 1.1 => 1' ) ; - ok( is_number( 0 ), 'is_number: 0 => 1' ) ; - ok( is_number( -1 ), 'is_number: -1 => 1' ) ; - ok( ! is_number( 1.1.1 ), 'is_number: 1.1.1 => no' ) ; - ok( ! is_number( q{} ), 'is_number: q{} => no' ) ; - ok( ! is_number( 'haha' ), 'is_number: haha => no' ) ; - ok( ! is_number( '0haha' ), 'is_number: 0haha => no' ) ; - ok( ! is_number( '2haha' ), 'is_number: 2haha => no' ) ; - ok( ! is_number( 'haha2' ), 'is_number: haha2 => no' ) ; - return ; -} + if ( ! $pidfile ) { return ; } + if ( ! $lock ) { return ; } + if ( ! $tail ) { return ; } + if ( ! -e $pidfile ) { return ; } + my $pidtotail = firstline( $pidfile ) ; + if ( ! $pidtotail ) { return ; } -sub is_number { - my $item = shift ; - - if ( ! defined $item ) { return ; } - - if ( $item =~ /\A$RE{num}{real}\Z/ ) { - return 1 ; - } - return ; -} -sub tests_min { - note( 'Entering tests_min()' ) ; - is( 0, min( 0 ), 'min 0 => 0' ) ; - is( 1, min( 1 ), 'min 1 => 1' ) ; - is( $MINUS_ONE, min( $MINUS_ONE ), 'min -1 => -1' ) ; - is( undef, min( ), 'min no arg => undef' ) ; - is( 1, min( 1, $NUMBER_100 ), 'min 1 100 => 1' ) ; - is( 1, min( $NUMBER_100, 1 ), 'min 100 1 => 1' ) ; - is( 1, min( $NUMBER_100, $NUMBER_42, 1 ), 'min 100 42 1 => 1' ) ; - is( 1, min( $NUMBER_100, '42', 1 ), 'min 100 42 1 => 1' ) ; - is( 1, min( '100', '42', 1 ), 'min 100 42 1 => 1' ) ; - is( 1, min( $NUMBER_100, 'haha', 1 ), 'min 100 haha 1 => 1') ; - is( $MINUS_ONE, min( $MINUS_ONE, 1 ), 'min -1 1 => -1') ; - - is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ; - is( 0, min( undef, 0 ), 'min undef 0 => 0' ) ; - is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ; - is( 0, min( undef, 2, 0, 1 ), 'min undef, 2, 0, 1 => 0' ) ; + # It should not happen but who knows... + if ( $pidtotail eq $PROCESS_ID ) { return ; } - is( 'haha', min( 'haha' ), 'min haha => haha') ; - is( 'aa', min( 'aa', 'bb' ), 'min aa bb => aa') ; - is( 'aa', min( 'bb', 'aa' ), 'min bb aa bb => aa') ; - is( 'aa', min( 'bb', 'aa', 'bb' ), 'min bb aa bb => aa') ; - note( 'Leaving tests_min()' ) ; - return ; -} + my $filetotail = secondline( $pidfile ) ; + if ( ! $filetotail ) { return ; } + if ( ! -r $filetotail ) + { + #myprint( "Error: can not read $filetotail\n" ) ; + return ; + } -sub min { - my @list = @_ ; - return( undef ) if ( 0 == scalar @list ) ; + myprint( "Doing a tail -f on $filetotail for processus pid $pidtotail until it is finished.\n" ) ; + my $file = File::Tail->new( + name => $filetotail, + nowait => 1, + interval => 1, + tail => 1, + adjustafter => 2 + ); - my( @numbers, @notnumbers ) ; - foreach my $item ( @list ) { - if ( is_number( $item ) ) { - push @numbers, $item ; - }else{ - push @notnumbers, $item ; - } - } - - my @sorted ; - if ( @numbers ) { - @sorted = sort { $a <=> $b } @numbers ; - }elsif( @notnumbers ) { - @sorted = sort { $a cmp $b } @notnumbers ; - }else{ - return ; + my $moretimes = 200 ; + # print one line at least + my $line = $file->read ; + myprint( $line ) ; + while ( isrunning( $pidtotail, \$moretimes ) and defined( $line = $file->read ) ) + { + myprint( $line ); + sleep( 0.02 ) ; } - return( shift @sorted ) ; + return 1 ; } +sub isrunning +{ + my $pidtocheck = shift ; + my $moretimes_ref = shift ; -sub check_lib_version { - $debug and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n" ) ; - if ( '2.2.9' eq $Mail::IMAPClient::VERSION ) { - myprint( "imapsync no longer supports Mail::IMAPClient 2.2.9, upgrade it\n" ) ; - return 0 ; + if ( kill 'ZERO', $pidtocheck ) + { + #myprint( "$pidtocheck running\n" ) ; + return 1 ; } - else{ - # 3.x.x is no longer buggy with imapsync. - # 3.30 or currently superior is imposed in the Perl "use Mail::IMAPClient line". + elsif ( $$moretimes_ref >= 0 ) + { + # continue to consider it running + $$moretimes_ref-- ; return 1 ; } - return ; + else + { + myprint( "Tailed processus $pidtocheck ended\n" ) ; + return ; + } } -sub module_version_str { - my( $module_name, $module_version ) = @_ ; - my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ; - return( $str ) ; -} +sub tests_write_pidfile +{ + note( 'Entering tests_write_pidfile()' ) ; -sub modulesversion { + my $mysync ; - my @list_version; + is( 1, write_pidfile( ), 'write_pidfile: no args => 1' ) ; - my %modulesversion = ( - 'Authen::NTLM' => sub { $Authen::NTLM::VERSION }, - 'Compress::Zlib' => sub { $Compress::Zlib::VERSION }, - 'Crypt::OpenSSL::RSA' => sub { $Crypt::OpenSSL::RSA::VERSION }, - 'Data::Uniqid' => sub { $Data::Uniqid::VERSION }, - 'Digest::HMAC_MD5' => sub { $Digest::HMAC_MD5::VERSION }, - 'Digest::HMAC_SHA1' => sub { $Digest::HMAC_SHA1::VERSION }, - 'Digest::MD5' => sub { $Digest::MD5::VERSION }, - 'File::Copy::Recursive' => sub { $File::Copy::Recursive::VERSION }, - 'File::Spec' => sub { $File::Spec::VERSION }, - 'Getopt::Long' => sub { $Getopt::Long::VERSION }, - 'HTML::Entities' => sub { $HTML::Entities::VERSION }, - 'IO::Socket::INET6' => sub { $IO::Socket::INET6::VERSION }, - 'IO::Socket::INET' => sub { $IO::Socket::INET::VERSION }, - 'IO::Socket::SSL' => sub { $IO::Socket::SSL::VERSION }, - 'IO::Socket' => sub { $IO::Socket::VERSION }, - 'IO::Tee' => sub { $IO::Tee::VERSION }, - 'JSON' => sub { $JSON::VERSION }, - 'JSON::WebToken' => sub { $JSON::WebToken::VERSION }, - 'LWP' => sub { $LWP::VERSION }, - 'Mail::IMAPClient' => sub { $Mail::IMAPClient::VERSION }, - 'Net::Ping' => sub { $Net::Ping::VERSION }, - 'Net::SSLeay' => sub { $Net::SSLeay::VERSION }, - 'Term::ReadKey' => sub { $Term::ReadKey::VERSION }, - 'Test::MockObject' => sub { $Test::MockObject::VERSION }, - 'Time::HiRes' => sub { $Time::HiRes::VERSION }, - 'Unicode::String' => sub { $Unicode::String::VERSION }, - 'URI::Escape' => sub { $URI::Escape::VERSION }, - #'Lalala' => sub { $Lalala::VERSION }, - ) ; + # no pidfile => ok + $mysync->{pidfile} = q{} ; + is( 1, write_pidfile( $mysync ), 'write_pidfile: no pidfile => undef' ) ; - foreach my $module_name ( sort keys %modulesversion ) { - # trick from http://www.perlmonks.org/?node_id=152122 - my $file_name = $module_name . '.pm' ; - $file_name =~s,::,/,xmgs; # Foo::Bar::Baz => Foo/Bar/Baz.pm - my $v ; - eval { - require $file_name ; - $v = defined $modulesversion{ $module_name } ? $modulesversion{ $module_name }->() : q{?} ; - } or $v = q{Not installed} ; + # The pidfile path is bad => failure + $mysync->{pidfile} = '/no/no/no.pid' ; + is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid, no lock => undef' ) ; - push @list_version, module_version_str( $module_name, $v ) ; - } + $mysync->{pidfilelocking} = 1 ; + is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid + lock => undef' ) ; - return( @list_version ) ; -} + $mysync->{pidfile} = 'W/tmp/tests/test.pid' ; + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'write_pidfile: mkpath W/tmp/tests/' ) ; + is( 1, touch( $mysync->{pidfile} ), 'write_pidfile: lock prepa' ) ; + $mysync->{pidfilelocking} = 0 ; + is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock => 1' ) ; + is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains $PROCESS_ID" ) ; + is( q{}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains no second line" ) ; -# Construct a command line copy with passwords replaced by MASKED. -sub command_line_nopassword { - my @argv = @_ ; - my @argv_nopassword ; + $mysync->{pidfilelocking} = 1 ; + is( undef, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + lock => undef' ) ; - return( "@argv" ) if $sync->{showpasswords} ; - while ( @argv ) { - my $arg = shift @argv ; # option name or value - if ( $arg =~ m/-password[12]/x ) { - shift @argv ; # password value - push @argv_nopassword, $arg, 'MASKED' ; # option name and fake value - }else{ - push @argv_nopassword, $arg ; # same option or value - } - } - return("@argv_nopassword") ; -} -sub tests_command_line_nopassword { - note( 'Entering tests_command_line_nopassword()' ) ; + $mysync->{pidfilelocking} = 0 ; + $mysync->{ logfile } = 'rrrr.txt' ; + is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock + logfile => 1' ) ; + is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains $PROCESS_ID" ) ; + is( q{rrrr.txt}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains rrrr.txt" ) ; - ok(q{} eq command_line_nopassword(), 'command_line_nopassword void'); - ok('--blabla' eq command_line_nopassword('--blabla'), 'command_line_nopassword --blabla'); - #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ; - ok('--password1 MASKED' eq command_line_nopassword(qw{ --password1 secret1}), 'command_line_nopassword --password1'); - ok('--blabla --password1 MASKED --blibli' - eq command_line_nopassword(qw{ --blabla --password1 secret1 --blibli }), 'command_line_nopassword --password1 --blibli'); - $sync->{showpasswords} = 1 ; - ok(q{} eq command_line_nopassword(), 'command_line_nopassword void'); - ok('--blabla' eq command_line_nopassword('--blabla'), 'command_line_nopassword --blabla'); - #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ; - ok('--password1 secret1' eq command_line_nopassword(qw{ --password1 secret1}), 'command_line_nopassword --password1'); - ok('--blabla --password1 secret1 --blibli' - eq command_line_nopassword(qw{ --blabla --password1 secret1 --blibli }), 'command_line_nopassword --password1 --blibli'); - note( 'Leaving tests_command_line_nopassword()' ) ; + note( 'Leaving tests_write_pidfile()' ) ; return ; } -sub ask_for_password { - my ( $user, $host ) = @ARG ; - myprint( "What's the password for $user" . '@' . "$host? (not visible while you type, then enter RETURN) " ) ; - Term::ReadKey::ReadMode( 2 ) ; - my $password = ; - chomp $password ; - myprint( "\nGot it\n" ) ; - Term::ReadKey::ReadMode( 0 ) ; - return $password ; -} -# Have to refactor get_password1() get_password2() -# to have only get_password() and two calls -sub get_password1 { - my $mysync = shift ; +sub write_pidfile +{ + # returns undef if something is considered fatal + # returns 1 otherwise - $mysync->{password1} - || $passfile1 - || 'PREAUTH' eq $authmech1 - || 'EXTERNAL' eq $authmech1 - || $ENV{IMAPSYNC_PASSWORD1} - || do { - myprint( << 'FIN_PASSFILE' ) ; + #myprint( "In write_pidfile\n" ) ; + if ( ! @ARG ) { return 1 ; } -If you are afraid of giving password on the command line arguments, you can put the -password of user1 in a file named file1 and use "--passfile1 file1" instead of typing it. -Then give this file restrictive permissions with the command "chmod 600 file1". -An other solution is to set the environment variable IMAPSYNC_PASSWORD1 -FIN_PASSFILE + my $mysync = shift @ARG ; - $mysync->{password1} = ask_for_password( $authuser1 || $mysync->{user1}, $mysync->{host1} ) ; - } ; + # Do not write the pid file if the current process goal is to abort the process designed by the pid file + if ( $mysync->{ abort } ) { return 1 ; } - if ( defined $passfile1 ) { - if ( ! -e -r $passfile1 ) { - myprint( "Failure: file from parameter --passfile1 $passfile1 does not exist or is not readable\n" ) ; - exit_clean( $mysync, $EX_NOINPUT ) ; - } - # passfile1 readable - $mysync->{password1} = firstline ( $passfile1 ) ; - return ; - } - if ( $ENV{IMAPSYNC_PASSWORD1} ) { - $mysync->{password1} = $ENV{IMAPSYNC_PASSWORD1} ; - return ; - } - return ; -} + # + my $pid_filename = $mysync->{ pidfile } ; + my $lock = $mysync->{ pidfilelocking } ; -sub get_password2 { + if ( ! $pid_filename ) + { + myprint( "PID file is unset ( to set it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ; + return( 1 ) ; + } - my $mysync = shift ; + myprint( "PID file is $pid_filename ( to change it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ; + if ( -e $pid_filename and $lock ) { + myprint( "$pid_filename already exists, another imapsync may be curently running. Aborting imapsync.\n" ) ; + return ; - $mysync->{password2} - || $passfile2 - || 'PREAUTH' eq $authmech2 - || 'EXTERNAL' eq $authmech2 - || $ENV{IMAPSYNC_PASSWORD2} - || do { - myprint( << 'FIN_PASSFILE' ) ; + } -If you are afraid of giving password on the command line arguments, you can put the -password of user2 in a file named file2 and use "--passfile2 file2" instead of typing it. -Then give this file restrictive permissions with the command "chmod 600 file2". -An other solution is to set the environment variable IMAPSYNC_PASSWORD2 -FIN_PASSFILE + if ( -e $pid_filename ) { + myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n" ) ; + } - $mysync->{password2} = ask_for_password( $authuser2 || $mysync->{user2}, $mysync->{host2} ) ; - } ; + my $pid_string = "$PROCESS_ID\n" ; + my $pid_message = "Writing my PID $PROCESS_ID in $pid_filename\n" ; + if ( $mysync->{ logfile } ) + { + $pid_string .= "$mysync->{ logfile }\n" ; + $pid_message .= "Writing also my logfile name in $pid_filename : $mysync->{ logfile }\n" ; + } - if ( defined $passfile2 ) { - if ( ! -e -r $passfile2 ) { - myprint( "Failure: file from parameter --passfile2 $passfile2 does not exist or is not readable\n" ) ; - exit_clean( $mysync, $EX_NOINPUT ) ; - } - # passfile2 readable - $mysync->{password2} = firstline ( $passfile2 ) ; - return ; - } - if ( $ENV{IMAPSYNC_PASSWORD2} ) { - $mysync->{password2} = $ENV{IMAPSYNC_PASSWORD2} ; - return ; - } - return ; + if ( open my $FILE_HANDLE, '>', $pid_filename ) { + myprint( $pid_message ) ; + print $FILE_HANDLE $pid_string ; + close $FILE_HANDLE ; + return( 1 ) ; + } + else + { + myprint( "Could not open $pid_filename for writing. Check permissions or disk space: $OS_ERROR\n" ) ; + return ; + } } +sub fix_Inbox_INBOX_mapping +{ + my( $h1_all, $h2_all ) = @_ ; -sub catch_ignore { - my $mysync = shift ; - my $signame = shift ; - - my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ; - myprint( "\nGot a signal $signame (my PID is $PROCESS_ID). Received $sigcounter $signame signals so far. Thanks!\n" ) ; - stats( $mysync ) ; - return ; + my $regex = q{} ; + SWITCH: { + if ( exists $h1_all->{INBOX} and exists $h2_all->{INBOX} ) { $regex = q{} ; last SWITCH ; } ; + if ( exists $h1_all->{Inbox} and exists $h2_all->{Inbox} ) { $regex = q{} ; last SWITCH ; } ; + if ( exists $h1_all->{INBOX} and exists $h2_all->{Inbox} ) { $regex = q{s/^INBOX$/Inbox/x} ; last SWITCH ; } ; + if ( exists $h1_all->{Inbox} and exists $h2_all->{INBOX} ) { $regex = q{s/^Inbox$/INBOX/x} ; last SWITCH ; } ; + } ; + return( $regex ) ; } +sub tests_fix_Inbox_INBOX_mapping +{ + note( 'Entering tests_fix_Inbox_INBOX_mapping()' ) ; -sub catch_exit { - my $mysync = shift ; - my $signame = shift || q{} ; - if ( $signame ) { - myprint( "\nGot a signal $signame (my PID is $PROCESS_ID). Asked to terminate\n" ) ; - } - myprint( "Here are the final stats of this sync not completely finished so far\n" ) ; - stats( $mysync ) ; - myprint( "Ended by a signal $signame (my PID is $PROCESS_ID). I am asked to terminate immediately.\n" ) ; - myprint( "You should resynchronize those accounts by running a sync again,\n", - "since some messages and entire folders might still be missing on host2.\n" ) ; - exit_clean( $mysync, $EXIT_BY_SIGNAL ) ; + + my( $h1_all, $h2_all ) ; + + $h1_all = { 'INBOX' => q{} } ; + $h2_all = { 'INBOX' => q{} } ; + ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX INBOX' ) ; + + $h1_all = { 'Inbox' => q{} } ; + $h2_all = { 'Inbox' => q{} } ; + ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox Inbox' ) ; + + $h1_all = { 'INBOX' => q{} } ; + $h2_all = { 'Inbox' => q{} } ; + ok( q{s/^INBOX$/Inbox/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX Inbox' ) ; + + $h1_all = { 'Inbox' => q{} } ; + $h2_all = { 'INBOX' => q{} } ; + ok( q{s/^Inbox$/INBOX/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox INBOX' ) ; + + $h1_all = { 'INBOX' => q{} } ; + $h2_all = { 'rrrrr' => q{} } ; + ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX rrrrrr' ) ; + + $h1_all = { 'rrrrr' => q{} } ; + $h2_all = { 'Inbox' => q{} } ; + ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: rrrrr Inbox' ) ; + + note( 'Leaving tests_fix_Inbox_INBOX_mapping()' ) ; return ; } -sub catch_reconnect { - my $mysync = shift ; - my $signame = shift ; - myprint( "\nGot a signal $signame (my PID is $PROCESS_ID)\n", - "Hit 2 ctr-c within 2 seconds to exit the program\n", - "Hit only 1 ctr-c to reconnect to both imap servers\n", - ) ; - if ( here_twice( $mysync ) ) { - myprint( "Got two signals $signame within $INTERVAL_TO_EXIT seconds. Exiting...\n" ) ; - catch_exit( $mysync ) ; - }else{ - myprint( "For now only one signal $signame within $INTERVAL_TO_EXIT seconds.\n" ) ; + +sub jux_utf8_list +{ + my @s_inp = @_ ; + my $s_out = q{} ; + foreach my $s ( @s_inp ) { + $s_out .= jux_utf8( $s ) . "\n" ; } + return( $s_out ) ; +} - if ( ! defined $mysync->{imap1} ) { return ; } - if ( ! defined $mysync->{imap2} ) { return ; } +sub tests_jux_utf8_list +{ + note( 'Entering tests_jux_utf8_list()' ) ; + use utf8 ; + is( q{}, jux_utf8_list( ), 'jux_utf8_list: void' ) ; + is( "[]\n", jux_utf8_list( q{} ), 'jux_utf8_list: empty string' ) ; + is( "[INBOX]\n", jux_utf8_list( 'INBOX' ), 'jux_utf8_list: INBOX' ) ; + is( "[&ANY-] = [รƒโ€“]\n", jux_utf8_list( '&ANY-' ), 'jux_utf8_list: [&ANY-] = [รƒโ€“]' ) ; - myprint( "Info: reconnecting to host1 imap server\n" ) ; - $mysync->{imap1}->State( Mail::IMAPClient::Unconnected ) ; - $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT} += 1 ; - $mysync->{imap1}->reconnect( ) ; - myprint( "Info: reconnecting to host2 imap server\n" ) ; - $mysync->{imap2}->State( Mail::IMAPClient::Unconnected ) ; - $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT} += 1 ; - $mysync->{imap2}->reconnect( ) ; - myprint( "Info: reconnected to both imap servers\n" ) ; - return ; + note( 'Leaving tests_jux_utf8_list()' ) ; + return( 0 ) ; } -sub tests_reconnect_12_if_needed { - note( 'Entering tests_reconnect_12_if_needed()' ) ; +# editing utf8 can be tricky without an utf8 editor +sub tests_jux_utf8_old +{ + note( 'Entering tests_jux_utf8_old()' ) ; - my $mysync ; + no utf8 ; - $mysync->{imap1} = Mail::IMAPClient->new( ) ; - $mysync->{imap2} = Mail::IMAPClient->new( ) ; - $mysync->{imap1}->Server( 'test1.lamiral.info' ) ; - $mysync->{imap2}->Server( 'test2.lamiral.info' ) ; - is( 2, reconnect_12_if_needed( $mysync ), 'reconnect_12_if_needed: test1&test2 .lamiral.info => 1' ) ; - is( 1, $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test1.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ; - is( 1, $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test2.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ; + is( '[]', jux_utf8_old( q{} ), 'jux_utf8_old: void => []' ) ; + is( '[INBOX]', jux_utf8_old( 'INBOX'), 'jux_utf8_old: INBOX => [INBOX]' ) ; + is( '[&ZTZO9nux-] = [รฆโ€ยถรคยปยถรงยฎยฑ]', jux_utf8_old( '&ZTZO9nux-'), 'jux_utf8_old: => [&ZTZO9nux-] = [รฆโ€ยถรคยปยถรงยฎยฑ]' ) ; + is( '[&ANY-] = [รƒโ€“]', jux_utf8_old( '&ANY-'), 'jux_utf8_old: &ANY- => [&ANY-] = [รƒโ€“]' ) ; + # +BD8EQAQ1BDQEOwQ+BDM- SHOULD stay as is! + is( '[+BD8EQAQ1BDQEOwQ+BDM-] = [รยฟร‘โ‚ฌรยตรยดรยปรยพรยณ]', jux_utf8_old( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8_old: => [+BD8EQAQ1BDQEOwQ+BDM-] = [รยฟร‘โ‚ฌรยตรยดรยปรยพรยณ]' ) ; + is( '[&BB8EQAQ+BDUEOgRC-] = [รลธร‘โ‚ฌรยพรยตรยบร‘โ€š]', jux_utf8_old( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8_old: => [&BB8EQAQ+BDUEOgRC-] = [รลธร‘โ‚ฌรยพรยตรยบร‘โ€š]' ) ; - note( 'Leaving tests_reconnect_12_if_needed()' ) ; - return ; + note( 'Leaving tests_jux_utf8_old()' ) ; + return ; } -sub reconnect_12_if_needed { - my $mysync = shift ; - #return 2 ; - if ( ! reconnect_if_needed( $mysync->{imap1} ) ) { - return ; - } - if ( ! reconnect_if_needed( $mysync->{imap2} ) ) { - return ; - } - # both were good - return 2 ; +sub jux_utf8_old +{ + # juxtapose utf8 at the right if different + my ( $s_utf7 ) = shift ; + my ( $s_utf8 ) = imap_utf7_decode_old( $s_utf7 ) ; + + if ( $s_utf7 eq $s_utf8 ) { + #myprint( "[$s_utf7]\n" ) ; + return( "[$s_utf7]" ) ; + }else{ + #myprint( "[$s_utf7] = [$s_utf8]\n" ) ; + return( "[$s_utf7] = [$s_utf8]" ) ; + } } +# Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm +# and then fixed with +# https://rt.cpan.org/Public/Bug/Display.html?id=11172 +sub imap_utf7_decode_old +{ + my ( $s ) = shift ; -sub tests_reconnect_if_needed { - note( 'Entering tests_reconnect_if_needed()' ) ; + # Algorithm + # On remplace , par / dans les BASE 64 (, entre & et -) + # On remplace les &, non suivi d'un - par + + # On remplace les &- par & + $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/xg ; + $s =~ s/&(?!\-)/\+/xg ; + $s =~ s/&\-/&/xg ; + return( Unicode::String::utf7( $s )->utf8 ) ; +} - my $myimap ; - is( undef, reconnect_if_needed( ), 'reconnect_if_needed: no args => undef' ) ; - is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: undef arg => undef' ) ; - $myimap = Mail::IMAPClient->new( ) ; - $myimap->Debug( 1 ) ; - is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: empty new Mail::IMAPClient => undef' ) ; - $myimap->Server( 'test.lamiral.info' ) ; - is( 1, reconnect_if_needed( $myimap ), 'reconnect_if_needed: test.lamiral.info => 1' ) ; - is( 1, $myimap->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_if_needed: test.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ; - note( 'Leaving tests_reconnect_if_needed()' ) ; - return ; -} +sub tests_jux_utf8 +{ + note( 'Entering tests_jux_utf8()' ) ; + #no utf8 ; + use utf8 ; -sub reconnect_if_needed { - # return undef upon failure. - # return 1 upon connection success, with or without reconnection. + #binmode STDOUT, ":encoding(UTF-8)" ; + binmode STDERR, ":encoding(UTF-8)" ; - my $imap = shift ; + # This test is because the binary can fail on it, a PAR.pm issue. + # The failure was with the underlying Encode::IMAPUTF7 module line 66 release 1.05 + # Was solved by including Encode in imapsync and using "pp -x". + ok( find_encoding( "UTF-16BE"), 'jux_utf8: Encode::find_encoding: UTF-16BE' ) ; - if ( ! defined $imap ) { return ; } - if ( ! $imap->Server( ) ) { return ; } + # + is( '[]', jux_utf8( q{} ), 'jux_utf8: void => []' ) ; + is( '[INBOX]', jux_utf8( 'INBOX'), 'jux_utf8: INBOX => [INBOX]' ) ; + is( '[&ANY-] = [รƒโ€“]', jux_utf8( '&ANY-'), 'jux_utf8: &ANY- => [&ANY-] = [รƒโ€“]' ) ; + # +BD8EQAQ1BDQEOwQ+BDM- must stay as is + is( '[+BD8EQAQ1BDQEOwQ+BDM-]', jux_utf8( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8: => [+BD8EQAQ1BDQEOwQ+BDM-] = [+BD8EQAQ1BDQEOwQ+BDM-]' ) ; + is( '[&BB8EQAQ+BDUEOgRC-] = [รลธร‘โ‚ฌรยพรยตรยบร‘โ€š]', jux_utf8( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8: => [&BB8EQAQ+BDUEOgRC-] = [รลธร‘โ‚ฌรยพรยตรยบร‘โ€š]' ) ; - if ( $imap->IsUnconnected( ) ) { - $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ; - if ( $imap->reconnect( ) ) { - return 1 ; - } - }else{ - return 1 ; - } + is( '[R&AOk-ponses 1200+1201+1202] = [Rรƒยฉponses 1200+1201+1202]', jux_utf8( q{R&AOk-ponses 1200+1201+1202} ), 'jux_utf8: [R&AOk-ponses 1200+1201+1202] = [Rรƒยฉponses 1200+1201+1202]' ) ; + my $str = Encode::IMAPUTF7::encode("IMAP-UTF-7", 'Rรƒยฉponses 1200+1201+1202' ) ; + is( '[R&AOk-ponses 1200+1201+1202] = [Rรƒยฉponses 1200+1201+1202]', jux_utf8( $str ), "jux_utf8: [$str] = [Rรƒยฉponses 1200+1201+1202]" ) ; - # A last forced one - $imap->State( Mail::IMAPClient::Unconnected ) ; - $imap->reconnect( ) ; - $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ; - if ( $imap->noop ) { - # NOOP is ok - return 1 ; - } + is( '[INBOX.&AOkA4ADnAPk-&-*] = [INBOX.รƒยฉรƒ รƒยงรƒยน&*]', jux_utf8( 'INBOX.&AOkA4ADnAPk-&-*' ), "jux_utf8: [INBOX.&AOkA4ADnAPk-&-*] = [INBOX.รƒยฉรƒ รƒยงรƒยน&*]" ) ; - return ; + is( '[&ZTZO9nux-] = [รฆโ€ยถรคยปยถรงยฎยฑ]', jux_utf8( '&ZTZO9nux-'), 'jux_utf8: => [&ZTZO9nux-] = [รฆโ€ยถรคยปยถรงยฎยฑ]' ) ; + # + # + is( '[!Old Emails]', jux_utf8( '!Old Emails'), 'jux_utf8: !Old Emails => [!Old Emails]' ) ; + is( '[2006 Budget & Fcst]', jux_utf8( '2006 Budget & Fcst'), 'jux_utf8: 2006 Budget & Fcst => [2006 Budget & Fcst]' ) ; + note( 'Leaving tests_jux_utf8()' ) ; + return ; } +sub jux_utf8 +{ + #use utf8 ; + # juxtapose utf8 at the right if different + my ( $s_utf7 ) = shift ; + my ( $s_utf8 ) = imap_utf7_decode( $s_utf7 ) ; - -sub here_twice { - my $mysync = shift ; - my $now = time ; - my $previous = $mysync->{lastcatch} || 0 ; - $mysync->{lastcatch} = $now ; - - if ( $INTERVAL_TO_EXIT >= $now - $previous ) { - return $TRUE ; + if ( $s_utf7 eq $s_utf8 ) { + #myprint( "[$s_utf7]\n" ) ; + return( "[$s_utf7]" ) ; }else{ - return $FALSE ; + #myprint( "[$s_utf7] = [$s_utf8]\n" ) ; + return( "[$s_utf7] = [$s_utf8]" ) ; } } +sub imap_utf7_decode +{ + #use utf8 ; + my ( $s ) = shift ; + return( Encode::IMAPUTF7::decode("IMAP-UTF-7", $s ) ) ; +} -sub justconnect { - - $imap1 = connect_imap( $sync->{host1}, $sync->{port1}, $debugimap1, $sync->{ssl1}, $sync->{tls1}, 'Host1', $sync->{h1}->{timeout}, $sync->{h1} ) ; - $imap2 = connect_imap( $sync->{host2}, $sync->{port2}, $debugimap2, $sync->{ssl2}, $sync->{tls2}, 'Host2', $sync->{h2}->{timeout}, $sync->{h2} ) ; - $imap1->logout( ) ; - $imap2->logout( ) ; - return ; +sub imap_utf7_encode +{ + #use utf8 ; + my ( $s ) = shift ; + return( Encode::IMAPUTF7::encode("IMAP-UTF-7", $s ) ) ; } -sub tests_mailimapclient_connect { - note( 'Entering tests_mailimapclient_connect()' ) ; - my $imap ; - # ipv4 - ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4: new' ) ; - is( 'Mail::IMAPClient', ref( $imap ), 'mailimapclient_connect ipv4: ref is Mail::IMAPClient' ) ; - SKIP: { - if ( 'macosx' eq hostname() - or 'macosx.polarhome.com' eq hostname() - ) { skip( 'Tests avoided on macosx get stuck', 1 ) ; } - is( undef, $imap->connect( ), 'mailimapclient_connect ipv4: connect with no server => failure' ) ; - } +sub imap_utf7_encode_old +{ + my ( $s ) = @_ ; - is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4: setting Server(test.lamiral.info)' ) ; - is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4: setting Debug( 1 )' ) ; - is( 143, $imap->Port( 143 ), 'mailimapclient_connect ipv4: setting Port( 143 )' ) ; - is( 3, $imap->Timeout( 3 ), 'mailimapclient_connect ipv4: setting Timout( 30 )' ) ; - like( ref( $imap->connect( ) ), qr/IO::Socket::INET/, 'mailimapclient_connect ipv4: connect to test.lamiral.info' ) ; - like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4: logout' ) ; - is( undef, undef $imap, 'mailimapclient_connect ipv4: free variable' ) ; + $s = Unicode::String::utf8( $s )->utf7 ; - # ipv4 + ssl - ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4 + ssl: new' ) ; - is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4 + ssl: setting Server(test.lamiral.info)' ) ; - is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ; - ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE ] ), 'mailimapclient_connect ipv4 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ; - is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv4 + ssl: setting Port( 993 )' ) ; - like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv4 + ssl: connect to test.lamiral.info' ) ; - is( $imap->logout( ), undef, 'mailimapclient_connect ipv4 + ssl: logout in ssl causes failure' ) ; - is( undef, undef $imap, 'mailimapclient_connect ipv4 + ssl: free variable' ) ; - - # ipv6 + ssl - ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv6 + ssl: new' ) ; - is( 'ks2ipv6.lamiral.info', $imap->Server( 'ks2ipv6.lamiral.info' ), 'mailimapclient_connect ipv6 + ssl: setting Server(ks2ipv6.lamiral.info)' ) ; - ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE ] ), 'mailimapclient_connect ipv6 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ; - is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv6 + ssl: setting Port( 993 )' ) ; - SKIP: { - if ( 'CUILLERE' eq hostname() ) { skip( 'Tests avoided on CUILLERE can not do ipv6', 2 ) ; } - like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv6 + ssl: connect to ks2ipv6.lamiral.info' ) ; - is( $imap->logout( ), undef, 'mailimapclient_connect ipv6 + ssl: logout in ssl causes failure' ) ; - } - is( undef, undef $imap, 'mailimapclient_connect ipv6 + ssl: free variable' ) ; + $s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/xg ; + $s =~ s/&/&\-/xg ; + $s =~ s/\+([^+\-]+)?\-/&$1\-/xg ; + return( $s ) ; +} - note( 'Leaving tests_mailimapclient_connect()' ) ; - return ; -} -sub tests_mailimapclient_connect_bug { - note( 'Entering tests_mailimapclient_connect_bug()' ) ; - my $imap ; - # ipv6 - ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv6: new' ) ; - is( 'ks2ipv6.lamiral.info', $imap->Server( 'ks2ipv6.lamiral.info' ), 'mailimapclient_connect ipv6: setting Server(ks2ipv6.lamiral.info)' ) ; - is( 143, $imap->Port( 143 ), 'mailimapclient_connect ipv6: setting Port( 993 )' ) ; - - SKIP: { - if ( 'CUILLERE' eq hostname() ) { skip( 'Tests avoided on CUILLERE can not do ipv6', 1 ) ; } - like( ref( $imap->connect( ) ), qr/IO::Socket::INET/, 'mailimapclient_connect ipv6: connect to ks2ipv6.lamiral.info' ) - or diag( 'mailimapclient_connect ipv6: ', $imap->LastError( ), $!, ) ; +sub select_folder +{ + my ( $mysync, $imap, $folder, $hostside ) = @_ ; + if ( ! $imap->select( $folder ) ) { + my $error = join q{}, + "$hostside folder $folder: Could not select: ", + $imap->LastError, "\n" ; + errors_incr( $mysync, $error ) ; + return( 0 ) ; + }else{ + # ok select succeeded + return( 1 ) ; } - #is( $imap->logout( ), undef, 'mailimapclient_connect ipv6: logout in ssl causes failure' ) ; - is( undef, undef $imap, 'mailimapclient_connect ipv6: free variable' ) ; - - note( 'Leaving tests_mailimapclient_connect_bug()' ) ; - return ; } -sub mailimapclient_connect { +sub examine_folder +{ + my ( $mysync, $imap, $folder, $hostside ) = @_ ; + if ( ! $imap->examine( $folder ) ) { + my $error = join q{}, + "$hostside folder $folder: Could not examine: ", + $imap->LastError, "\n" ; + errors_incr( $mysync, $error ) ; + return( 0 ) ; + }else{ + # ok select succeeded + return( 1 ) ; + } +} - return ; +sub count_from_select +{ + my @lines = @ARG ; + my $count ; + foreach my $line ( @lines ) { + #myprint( "line = [$line]\n" ) ; + if ( $line =~ m/^\*\s+(\d+)\s+EXISTS/x ) { + $count = $1 ; + return( $count ) ; + } + } + return( undef ) ; } - -sub tests_connect_socket { - note( 'Entering tests_connect_socket()' ) ; - - is( undef, connect_socket( ), 'connect_socket: no args' ) ; - my $socket ; - my $imap ; - SKIP: { - if ( 'CUILLERE' eq hostname() ) { skip( 'Tests avoided on CUILLERE cannot do ipv6', 2 ) ; } - - $socket = IO::Socket::INET6->new( - PeerAddr => 'ks2ipv6.lamiral.info', - PeerPort => 143, - ) ; +sub create_folder_old +{ + my $mysync = shift @ARG ; + my( $imap, $h2_fold, $h1_fold ) = @ARG ; - - ok( $imap = connect_socket( $socket ), 'connect_socket: ks2ipv6.lamiral.info port 143 IO::Socket::INET6' ) ; - #$imap->Debug( 1 ) ; - # myprint( $imap->capability( ) ) ; - if ( $imap ) { - $imap->logout( ) ; - } - - #$IO::Socket::SSL::DEBUG = 4 ; - $socket = IO::Socket::SSL->new( - PeerHost => 'ks2ipv6.lamiral.info', - PeerPort => 993, - SSL_verify_mode => SSL_VERIFY_NONE, - ) ; - # myprint $socket ; - ok( $imap = connect_socket( $socket ), 'connect_socket: ks2ipv6.lamiral.info port 993 IO::Socket::SSL' ) ; - #$imap->Debug( 1 ) ; - # myprint $imap->capability( ) ; - $socket->close( ) ; - if ( $imap ) { - $socket->close( ) ; + myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ; + if ( ( 'INBOX' eq uc $h2_fold ) + and ( $imap->exists( $h2_fold ) ) ) { + myprint( "Folder [$h2_fold] already exists\n" ) ; + return( 1 ) ; } - #$socket->close(SSL_no_shutdown => 1) ; - #$imap->logout( ) ; - #myprint "\n" ; - #$imap->logout( ) ; + if ( ! $mysync->{dry} ){ + if ( ! $imap->create( $h2_fold ) ) { + my $error = join q{}, + "Could not create folder [$h2_fold] from [$h1_fold]: ", + $imap->LastError( ), "\n" ; + errors_incr( $mysync, $error ) ; + # success if folder exists ("already exists" error) + return( 1 ) if $imap->exists( $h2_fold ) ; + # failure since create failed + return( 0 ) ; + }else{ + #create succeeded + myprint( "Created ( the old way ) folder [$h2_fold] on host2\n" ) ; + return( 1 ) ; + } + }else{ + # dry mode, no folder so many imap will fail, assuming failure + myprint( "Created ( the old way ) folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ; + return( 0 ) ; } - - note( 'Leaving tests_connect_socket()' ) ; - return ; } -sub connect_socket { - my( $socket ) = @ARG ; - if ( ! defined $socket ) { return ; } - - my $host = $socket->peerhost( ) ; - my $port = $socket->peerport( ) ; - #print "socket->peerhost: ", $socket->peerhost( ), "\n" ; - #print "socket->peerport: ", $socket->peerport( ), "\n" ; - my $imap = Mail::IMAPClient->new( ) ; - $imap->Socket( $socket ) ; - my $banner = $imap->Results()->[0] ; - #myprint( "banner: $banner" ) ; - return $imap ; -} +sub create_folder +{ + my $mysync = shift @ARG ; + my( $myimap2 , $h2_fold , $h1_fold ) = @ARG ; + my( @parts , $parent ) ; + if ( $myimap2->IsUnconnected( ) ) { + myprint( "Host2: Unconnected state\n" ) ; + return( 0 ) ; + } -sub tests_probe_imapssl { - note( 'Entering tests_probe_imapssl()' ) ; + if ( $create_folder_old ) { + return( create_folder_old( $mysync, $myimap2 , $h2_fold , $h1_fold ) ) ; + } - is( undef, probe_imapssl( ), 'probe_imapssl: no args => undef' ) ; - is( undef, probe_imapssl( 'unknown' ), 'probe_imapssl: unknown => undef' ) ; + # $imap->exists() calls $imap->status() that does an IMAP STATUS folder + myprint( "Creating folder [$h2_fold] on host2\n" ) ; + if ( ( 'INBOX' eq uc $h2_fold ) + and ( $myimap2->exists( $h2_fold ) ) ) { + myprint( "Folder [$h2_fold] already exists\n" ) ; + return( 1 ) ; + } - SKIP: { - if ( 'CUILLERE' eq hostname() ) { skip( 'Tests avoided on CUILLERE cannot do ipv6', 1 ) ; } - like( probe_imapssl( 'ks2ipv6.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: ks2ipv6.lamiral.info matches "* OK"' ) ; - } ; + if ( $mixfolders and $myimap2->exists( $h2_fold ) ) { + myprint( "Folder [$h2_fold] already exists (--nomixfolders is not set)\n" ) ; + return( 1 ) ; + } - like( probe_imapssl( 'test1.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: test1.lamiral.info matches "* OK"' ) ; - like( probe_imapssl( 'imap.gmail.com' ), qr/^\* OK/, 'probe_imapssl: imap.gmail.com matches "* OK"' ) ; - note( 'Leaving tests_probe_imapssl()' ) ; - return ; -} + if ( ( not $mixfolders ) and ( $myimap2->exists( $h2_fold ) ) ) { + myprint( "Folder [$h2_fold] already exists and --nomixfolders is set\n" ) ; + return( 0 ) ; + } -sub probe_imapssl { - my $host = shift ; - - if ( ! $host ) { return ; } - - my $socket = IO::Socket::SSL->new( - PeerHost => $host, - PeerPort => $IMAP_SSL_PORT, - SSL_verify_mode => SSL_VERIFY_NONE, - ) ; - #print "$socket\n" ; - if ( ! $socket ) { return ; } - - my $banner ; - $socket->sysread( $banner, 65_536 ) ; - #print "$banner" ; - $socket->close( ) ; - return $banner ; + @parts = split /\Q$mysync->{ h2_sep }\E/x, $h2_fold ; + pop @parts ; + $parent = join $mysync->{ h2_sep }, @parts ; + $parent =~ s/^\s+|\s+$//xg ; + if ( ( $parent ne q{} ) and ( ! $myimap2->exists( $parent ) ) ) { + create_folder( $mysync, $myimap2 , $parent , $h1_fold ) ; + } + if ( ! $mysync->{dry} ) { + if ( ! $myimap2->create( $h2_fold ) ) { + my $error = join q{}, + "Could not create folder [$h2_fold] from [$h1_fold]: " , + $myimap2->LastError( ), "\n" ; + errors_incr( $mysync, $error ) ; + # success if folder exists ("already exists" error) or selectable + if ( $myimap2->exists( $h2_fold ) or select_folder( $mysync, $myimap2, $h2_fold, 'Host2' ) ) + { + return( 1 ) ; + } + # failure since create failed + not exist + not selectable + return( 0 ) ; + }else{ + #create succeeded + myprint( "Created folder [$h2_fold] on host2\n" ) ; + return( 1 ) ; + } + }else{ + # dry mode, no folder so many imap will fail, assuming failure + myprint( "Created folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ; + if ( ! $mysync->{ justfolders } ) { + myprint( "Since --dry mode is on and folder [$h2_fold] on host2 does not exist yet, syncing messages will not be simulated.\n" + . "To simulate message syncing, use --justfolders without --dry to first create the missing folders then rerun the --dry sync.\n" ) ; + # The messages that could be transferred are counted and the number is given at the end. + } + return( 0 ) ; + } } -sub connect_imap { - my( $host, $port, $mydebugimap, $ssl, $tls, $Side, $mytimeout, $h ) = @_ ; - my $imap = Mail::IMAPClient->new( ) ; - if ( $ssl ) { set_ssl( $imap, $h ) } - $imap->Server( $host ) ; - $imap->Port( $port ) ; - $imap->Debug( $mydebugimap ) ; - $imap->Timeout( $mytimeout ) ; - my $side = lc $Side ; - myprint( "$Side: connecting on $side [$host] port [$port]\n" ) ; - $imap->connect( ) - or die_clean( "$Side: Can not open imap connection on [$host]: " . $imap->LastError . " $OS_ERROR\n" ) ; - myprint( "$Side IP address: ", $imap->Socket->peerhost(), "\n" ) ; - my $banner = $imap->Results()->[0] ; +sub tests_folder_routines +{ + note( 'Entering tests_folder_routines()' ) ; - myprint( "$Side banner: $banner" ) ; - myprint( "$Side capability: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ; + ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 1' ); + ok( add_to_requested_folders('folder_foo'), 'add_to_requested_folders folder_foo' ); + ok( is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 2' ); + ok( !is_requested_folder('folder_NO_EXIST'), 'is_requested_folder folder_NO_EXIST' ); - if ( $tls ) { - set_tls( $imap, $h ) ; - $imap->starttls( ) - or die_clean("$Side: Can not go to tls encryption on $side [$host]:", $imap->LastError, "\n" ) ; - myprint( "$Side: Socket successfuly converted to SSL\n" ) ; - } - return( $imap ) ; -} + is_deeply( [ 'folder_foo' ], [ remove_from_requested_folders( 'folder_foo' ) ], 'removed folder_foo => folder_foo' ) ; + ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 3' ); + my @f ; + ok( @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f" ); + ok( is_requested_folder('folder_bar'), 'is_requested_folder 4' ); + ok( is_requested_folder('folder_toto'), 'is_requested_folder 5' ); + ok( remove_from_requested_folders('folder_toto'), 'remove_from_requested_folders: ' ); + ok( !is_requested_folder('folder_toto'), 'is_requested_folder 6' ); + is_deeply( [ 'folder_bar' ], [ remove_from_requested_folders('folder_bar') ], 'remove_from_requested_folders: empty' ) ; -sub login_imap { + ok( 0 == compare_lists( [ sort_requested_folders( ) ], [] ), 'sort_requested_folders: all empty' ) ; + ok( add_to_requested_folders( 'A_99', 'M_55', 'Z_11' ), 'add_to_requested_folders M_55 Z_11' ); + ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'A_99', 'M_55', 'Z_11' ] ), 'sort_requested_folders: middle' ) ; - my @allargs = @_ ; - my( - $host, $port, $user, $domain, $password, - $mydebugimap, $mytimeout, $fastio, - $ssl, $tls, $authmech, $authuser, $reconnectretry, - $proxyauth, $uid, $split, $Side, $h, $mysync ) = @allargs ; - my $side = lc $Side ; - myprint( "$Side: connecting and login on $side [$host] port [$port] with user [$user]\n" ) ; + @folderfirst = ( 'Z_11' ) ; - my $imap = init_imap( @allargs ) ; + ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'A_99', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ; - $imap->connect() - or die_clean("$Side failure: can not open imap connection on $side [$host] with user [$user]: " . $imap->LastError . " $OS_ERROR\n" ) ; - myprint( "$Side IP address: ", $imap->Socket->peerhost(), "\n" ) ; - my $banner = $imap->Results()->[0] ; + is_deeply( [ 'Z_11', 'A_99', 'M_55' ], [ sort_requested_folders( ) ], 'sort_requested_folders: first+middle is_deeply' ) ; - myprint( "$Side banner: $banner" ) ; - myprint( "$Side capability before authentication: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ; + @folderlast = ( 'A_99' ) ; + ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 1' ) ; - if ( (! $ssl) and (! defined $tls ) and $imap->has_capability( 'STARTTLS' ) ) { - myprint( "$Side: going to ssl because STARTTLS is in CAPABILITY. Use --notls1 or --notls2 to avoid that behavior\n" ) ; - $tls = 1 ; - } + ok( add_to_requested_folders('M_55', 'M_44',), 'add_to_requested_folders M_55 M_44' ) ; - if ( $authmech eq 'PREAUTH' ) { - if ( $imap->IsAuthenticated( ) ) { - $imap->Socket ; - myprintf("%s: Assuming PREAUTH for %s\n", $Side, $imap->Server ) ; - }else{ - die_clean( "$Side failure: error login on $side [$host] with user [$user] auth [PREAUTH]" ) ; - } - } + ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_44', 'M_55', 'A_99'] ), 'sort_requested_folders: first+middle+last 2' ) ; - if ( $tls ) { - set_tls( $imap, $h ) ; - $imap->starttls( ) - or die_clean("$Side failure: Can not go to tls encryption on $side [$host]:", $imap->LastError, "\n" ) ; - myprint( "$Side: Socket successfuly converted to SSL\n" ) ; - } - authenticate_imap( $imap, @allargs ) ; + ok( add_to_requested_folders('A_88', 'Z_22',), 'add_to_requested_folders A_88 Z_22' ) ; + @folderfirst = qw( Z_22 Z_11 ) ; + @folderlast = qw( A_99 A_88 ) ; + ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_22', 'Z_11', 'M_44', 'M_55', 'A_99', 'A_88' ] ), 'sort_requested_folders: first+middle+last 3' ) ; + undef @folderfirst ; + undef @folderlast ; - myprint( "$Side: success login on [$host] with user [$user] auth [$authmech]\n" ) ; - return( $imap ) ; + note( 'Leaving tests_folder_routines()' ) ; + return ; } -sub authenticate_imap { - - my($imap, - $host, $port, $user, $domain, $password, - $mydebugimap, $mytimeout, $fastio, - $ssl, $tls, $authmech, $authuser, $reconnectretry, - $proxyauth, $uid, $split, $Side, $h, $mysync ) = @_ ; +sub sort_requested_folders +{ + my @requested_folders_sorted = () ; - check_capability( $imap, $authmech, $Side ) ; + $sync->{ debug } and myprint "folderfirst: @folderfirst\n" ; + my @folderfirst_requested = remove_from_requested_folders( @folderfirst ) ; + #myprint "folderfirst_requested: @folderfirst_requested\n" ; - if ( $proxyauth ) { - $imap->Authmechanism(q{}) ; - $imap->User($authuser) ; - } else { - $imap->Authmechanism( $authmech ) unless ( $authmech eq 'LOGIN' or $authmech eq 'PREAUTH' ) ; - $imap->User($user) ; - } + my @folderlast_requested = remove_from_requested_folders( @folderlast ) ; - $imap->Authcallback(\&xoauth) if ( 'XOAUTH' eq $authmech ) ; - $imap->Authcallback(\&xoauth2) if ( 'XOAUTH2' eq $authmech ) ; - $imap->Authcallback(\&plainauth) if ( ( 'PLAIN' eq $authmech ) or ( 'EXTERNAL' eq $authmech ) ) ; + my @middle = sort keys %requested_folder ; - $imap->Domain($domain) if (defined $domain) ; - $imap->Authuser($authuser) ; - $imap->Password($password) ; + @requested_folders_sorted = ( @folderfirst_requested, @middle, @folderlast_requested ) ; + $sync->{ debug } and myprint "requested_folders_sorted: @requested_folders_sorted\n" ; + add_to_requested_folders( @requested_folders_sorted ) ; - unless ( $authmech eq 'PREAUTH' or $imap->login( ) ) { - my $info = "$Side failure: Error login on [$host] with user [$user] auth" ; - my $einfo = $imap->LastError || @{$imap->History}[$LAST] ; - chomp $einfo ; - my $error = "$info [$authmech]: $einfo\n" ; - if ( $authmech eq 'LOGIN' or $imap->IsUnconnected( ) or $authuser ) { - die_clean( $error ) ; - }else{ - myprint( $error ) ; - } - myprint( "$Side info: trying LOGIN Auth mechanism on [$host] with user [$user]\n" ) ; - $imap->Authmechanism(q{}) ; - $imap->login() or - die_clean("$info [LOGIN]: ", $imap->LastError, "\n") ; - } + return( @requested_folders_sorted ) ; +} - if ( $proxyauth ) { - if ( ! $imap->proxyauth( $user ) ) { - my $info = "$Side failure: Error doing proxyauth as user [$user] on [$host] using proxy-login as [$authuser]" ; - my $einfo = $imap->LastError || @{$imap->History}[$LAST] ; - chomp $einfo ; - die_clean( "$info: $einfo\n" ) ; - } - } +sub is_requested_folder +{ + my ( $folder ) = @_; - return ; + return( defined $requested_folder{ $folder } ) ; } -sub check_capability { - - my( $imap, $authmech, $Side ) = @_ ; +sub add_to_requested_folders +{ + my @wanted_folders = @_ ; - if ($imap->has_capability( "AUTH=$authmech" ) - or $imap->has_capability( $authmech ) ) { - myprintf("%s: %s says it has CAPABILITY for AUTHENTICATE %s\n", - $Side, $imap->Server, $authmech) ; - return ; + foreach my $folder ( @wanted_folders ) { + ++$requested_folder{ $folder } ; } + return( keys %requested_folder ) ; +} - if ( $authmech eq 'LOGIN' ) { - # Well, the warning is so common and useless that I prefer to remove it - # No more "... says it has NO CAPABILITY for AUTHENTICATE LOGIN" - return ; - } - +sub tests_remove_from_requested_folders +{ + note( 'Entering tests_remove_from_requested_folders()' ) ; - myprintf( "%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n", - $Side, $imap->Server, $authmech ) ; + is( undef, undef, 'remove_from_requested_folders: undef is undef' ) ; + is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: no args' ) ; + %requested_folder = ( + 'F1' => 1, + ) ; + is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 => nothing' ) ; + is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 => nothing' ) ; + is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 => F1' ) ; + is_deeply( { }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 => %requested_folder emptied' ) ; + + %requested_folder = ( + 'F1' => 1, + 'F2' => 1, + ) ; + is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 F2 => nothing' ) ; + is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 F2 => nothing' ) ; + is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 F2 => F1' ) ; + is_deeply( { 'F2' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ; + + is_deeply( [], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F2 => nothing' ) ; + is_deeply( [ 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F2 => F2' ) ; + is_deeply( {}, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ; + + %requested_folder = ( + 'F1' => 1, + 'F2' => 1, + 'F3' => 1, + ) ; + is_deeply( [ 'F1', 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => F1 F2' ) ; + is_deeply( { 'F3' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => %requested_folder F3' ) ; - if ($authmech eq 'PLAIN') { - myprint( "$Side: frequently PLAIN is only supported with SSL, try --ssl or --tls options\n" ) ; - } + undef %requested_folder ; + note( 'Leaving tests_remove_from_requested_folders()' ) ; return ; } -sub set_ssl { - my ( $imap, $h ) = @_ ; - # SSL_version can be - # SSLv3 SSLv2 SSLv23 SSLv23:!SSLv2 (last one is the default in IO-Socket-SSL-1.953) - # - - my $sslargs_hash = $h->{sslargs} ; - my $sslargs_default = { - SSL_verify_mode => $SSL_VERIFY_POLICY, - SSL_verifycn_scheme => 'imap', - SSL_cipher_list => 'DEFAULT:!DH', - } ; +sub remove_from_requested_folders +{ + my @unwanted_folders = @_ ; - # initiate with default values - my %sslargs_mix = %{ $sslargs_default } ; - # now override with passed values - @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ; - # remove keys with undef values - foreach my $key ( keys %sslargs_mix ) { - delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ; + my @removed_folders = () ; + foreach my $folder ( @unwanted_folders ) { + if ( exists $requested_folder{ $folder } ) + { + delete $requested_folder{ $folder } ; + push @removed_folders, $folder ; + } } - # back to an ARRAY - my @sslargs_mix = %sslargs_mix ; - #myprint( Data::Dumper->Dump( [ $sslargs_hash, $sslargs_default, \%sslargs_mix, \@sslargs_mix ] ) ) ; - $imap->Ssl( \@sslargs_mix ) ; - return ; + return( @removed_folders ) ; } -sub set_tls { - my ( $imap, $h ) = @_ ; +sub compare_lists +{ + my ($list_1_ref, $list_2_ref) = @_; - my $sslargs_hash = $h->{sslargs} ; + return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref); + return(0) if ((not defined $list_1_ref) and not defined $list_2_ref); # end if no list + return(1) if (not defined $list_2_ref); # end if only one list - my $sslargs_default = { - SSL_verify_mode => $SSL_VERIFY_POLICY, - SSL_cipher_list => 'DEFAULT:!DH', - } ; + if (not ref $list_1_ref ) {$list_1_ref = [$list_1_ref]}; + if (not ref $list_2_ref ) {$list_2_ref = [$list_2_ref]}; - # initiate with default values - my %sslargs_mix = %{ $sslargs_default } ; - # now override with passed values - @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ; - # remove keys with undef values - foreach my $key ( keys %sslargs_mix ) { - delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ; + + my $last_used_indice = $MINUS_ONE; + + + ELEMENT: + foreach my $indice ( 0 .. $#{ $list_1_ref } ) { + $last_used_indice = $indice ; + + # End of list_2 + return 1 if ($indice > $#{ $list_2_ref } ) ; + + my $element_list_1 = $list_1_ref->[$indice] ; + my $element_list_2 = $list_2_ref->[$indice] ; + my $balance = $element_list_1 cmp $element_list_2 ; + next ELEMENT if ($balance == 0) ; + return $balance ; } - # back to an ARRAY - my @sslargs_mix = %sslargs_mix ; + # each element equal until last indice of list_1 + return $MINUS_ONE if ($last_used_indice < $#{ $list_2_ref } ) ; - $imap->Starttls( \@sslargs_mix ) ; - return ; + # same size, each element equal + return 0 ; } +sub tests_compare_lists +{ + note( 'Entering tests_compare_lists()' ) ; + + my $empty_list_ref = []; + + ok( 0 == compare_lists() , 'compare_lists, no args'); + ok( 0 == compare_lists(undef) , 'compare_lists, undef = nothing'); + ok( 0 == compare_lists(undef, undef) , 'compare_lists, undef = undef'); + ok($MINUS_ONE == compare_lists(undef , []) , 'compare_lists, undef < []'); + ok($MINUS_ONE == compare_lists(undef , [1]) , 'compare_lists, undef < [1]'); + ok($MINUS_ONE == compare_lists(undef , [0]) , 'compare_lists, undef < [0]'); + ok(+1 == compare_lists([]) , 'compare_lists, [] > nothing'); + ok(+1 == compare_lists([], undef) , 'compare_lists, [] > undef'); + ok( 0 == compare_lists([] , []) , 'compare_lists, [] = []'); + ok($MINUS_ONE == compare_lists([] , [1]) , 'compare_lists, [] < [1]'); + ok(+1 == compare_lists([1] , []) , 'compare_lists, [1] > []'); -sub init_imap { - my( - $host, $port, $user, $domain, $password, - $mydebugimap, $mytimeout, $fastio, - $ssl, $tls, $authmech, $authuser, $reconnectretry, - $proxyauth, $uid, $split, $Side, $h, $mysync ) = @_ ; + ok( 0 == compare_lists( [1], 1 ) , 'compare_lists, [1] = 1 ') ; + ok( 0 == compare_lists( 1 , [1] ) , 'compare_lists, 1 = [1]') ; + ok( 0 == compare_lists( 1 , 1 ) , 'compare_lists, 1 = 1 ') ; + ok( $MINUS_ONE == compare_lists( 0 , 1 ) , 'compare_lists, 0 < 1 ') ; + ok( $MINUS_ONE == compare_lists( $MINUS_ONE , 0 ) , 'compare_lists, -1 < 0 ') ; + ok( $MINUS_ONE == compare_lists( 1 , 2 ) , 'compare_lists, 1 < 2 ') ; + ok( +1 == compare_lists( 2 , 1 ) , 'compare_lists, 2 > 1 ') ; - my ( $imap ) ; - $imap = Mail::IMAPClient->new() ; + ok( 0 == compare_lists([1,2], [1,2]) , 'compare_lists, [1,2] = [1,2]' ) ; + ok($MINUS_ONE == compare_lists([1], [1,2]) , 'compare_lists, [1] < [1,2]' ) ; + ok(+1 == compare_lists([2], [1,2]) , 'compare_lists, [2] > [1,2]' ) ; + ok($MINUS_ONE == compare_lists([1], [1,1]) , 'compare_lists, [1] < [1,1]' ) ; + ok(+1 == compare_lists([1, 1], [1]) , 'compare_lists, [1, 1] > [1]' ) ; + ok( 0 == compare_lists([1 .. $NUMBER_20_000] , [1 .. $NUMBER_20_000]) + , 'compare_lists, [1..20_000] = [1..20_000]' ) ; + ok($MINUS_ONE == compare_lists([1], [2]) , 'compare_lists, [1] < [2]') ; + ok( 0 == compare_lists([2], [2]) , 'compare_lists, [0] = [2]') ; + ok(+1 == compare_lists([2], [1]) , 'compare_lists, [2] > [1]') ; - if ( $ssl ) { set_ssl( $imap, $h ) } - if ( $tls ) { } # can not do set_tls() here because connect() will directly do a STARTTLS - $imap->Clear(1); - $imap->Server($host); - $imap->Port($port); - $imap->Fast_io($fastio); - $imap->Buffer($buffersize || $DEFAULT_BUFFER_SIZE); - $imap->Uid($uid); - - - $imap->Peek(1); - $imap->Debug($mydebugimap); - if ( $mysync->{ showpasswords } ) { - $imap->Showcredentials( 1 ) ; - } - defined $mytimeout and $imap->Timeout( $mytimeout ) ; + ok($MINUS_ONE == compare_lists(['a'], ['b']) , 'compare_lists, ["a"] < ["b"]') ; + ok( 0 == compare_lists(['a'], ['a']) , 'compare_lists, ["a"] = ["a"]') ; + ok( 0 == compare_lists(['ab'], ['ab']) , 'compare_lists, ["ab"] = ["ab"]') ; + ok(+1 == compare_lists(['b'], ['a']) , 'compare_lists, ["b"] > ["a"]') ; + ok($MINUS_ONE == compare_lists(['a'], ['aa']) , 'compare_lists, ["a"] < ["aa"]') ; + ok($MINUS_ONE == compare_lists(['a'], ['a', 'a']), 'compare_lists, ["a"] < ["a", "a"]') ; + ok( 0 == compare_lists([split q{ }, 'a b' ], ['a', 'b']), 'compare_lists, split') ; + ok( 0 == compare_lists([sort split q{ }, 'b a' ], ['a', 'b']), 'compare_lists, sort split') ; - $imap->Reconnectretry( $reconnectretry ) if ( $reconnectretry ) ; - $imap->{IMAPSYNC_RECONNECT_COUNT} = 0 ; - $imap->Ignoresizeerrors( $allowsizemismatch ) ; - $split and $imap->Maxcommandlength( $SPLIT_FACTOR * $split ) ; + note( 'Leaving tests_compare_lists()' ) ; + return ; +} - return( $imap ) ; +sub guess_prefix +{ + my @foldernames = @_ ; + my $prefix_guessed = q{} ; + foreach my $folder ( @foldernames ) { + next if ( $folder =~ m{^INBOX$}xi ) ; # no guessing from INBOX + if ( $folder !~ m{^INBOX}xi ) { + $prefix_guessed = q{} ; # prefix empty guessed + last ; + } + if ( $folder =~ m{^(INBOX(?:\.|\/))}xi ) { + $prefix_guessed = $1 ; # prefix Inbox/ or INBOX. guessed + } + } + return( $prefix_guessed ) ; } -sub plainauth { - my $code = shift; - my $imap = shift; +sub tests_guess_prefix +{ + note( 'Entering tests_guess_prefix()' ) ; - my $string = mysprintf("%s\x00%s\x00%s", $imap->User, - $imap->Authuser, $imap->Password); - return encode_base64("$string", q{}); + is( guess_prefix( ), q{}, 'guess_prefix: no args => empty string' ) ; + is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ; + is( q{} , guess_prefix( 'Inbox' ), 'guess_prefix: Inbox alone' ) ; + is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ; + is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk' ), 'guess_prefix: INBOX INBOX/Junk' ) ; + is( 'INBOX.' , guess_prefix( 'INBOX', 'INBOX.Junk' ), 'guess_prefix: INBOX INBOX.Junk' ) ; + is( 'Inbox/' , guess_prefix( 'Inbox', 'Inbox/Junk' ), 'guess_prefix: Inbox Inbox/Junk' ) ; + is( 'Inbox.' , guess_prefix( 'Inbox', 'Inbox.Junk' ), 'guess_prefix: Inbox Inbox.Junk' ) ; + is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr' ) ; + is( q{} , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr', 'zzz' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr zzz' ) ; + is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ; + is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ; + + note( 'Leaving tests_guess_prefix()' ) ; + return ; } -# Copy from https://github.com/imapsync/imapsync/pull/25/files -# Changes "use" pragmas to "require". -# The openssl system call shall be replaced by pure Perl and -# https://metacpan.org/pod/Crypt::OpenSSL::PKCS12 +sub get_prefix +{ + my( $imap, $prefix_in, $prefix_opt, $Side, $folders_ref ) = @_ ; + my( $prefix_out, $prefix_guessed ) ; -# Now the Joaquin Lopez code: -# -# Used this as an example: https://gist.github.com/gsainio/6322375 -# -# And this as a reference: https://developers.google.com/accounts/docs/OAuth2ServiceAccount -# (note there is an http/rest tab, where the real info is hidden away... went on a witch hunt -# until I noticed that...) -# -# This is targeted at gmail to maintain compatibility after google's oauth1 service is deactivated -# on May 5th, 2015: https://developers.google.com/gmail/oauth_protocol -# If there are other oauth2 implementations out there, this would need to be modified to be -# compatible -# -# This is a good guide on setting up the google api/apps side of the equation: -# http://www.limilabs.com/blog/oauth2-gmail-imap-service-account -# -# 2016/05/27: Updated to support oauth/key data in the .json files Google now defaults to -# when creating gmail service accounts. They're easier to work with since they neither -# requiring decrypting nor specifying the oauth2 client id separately. -# -# If the password arg ends in .json, it will assume this new json method, otherwise it -# will fallback to the "oauth client id;.p12" format it was previously using. -sub xoauth2 { - require JSON::WebToken ; - require LWP::UserAgent ; - require HTML::Entities ; - require JSON ; - require JSON::WebToken::Crypt::RSA ; - require Crypt::OpenSSL::RSA ; - require Encode::Byte ; - require IO::Socket::SSL ; + ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting prefix\n" ) ; + $prefix_guessed = guess_prefix( @{ $folders_ref } ) ; + myprint( "$Side: guessing prefix from folder listing: [$prefix_guessed]\n" ) ; + ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Calling namespace capability\n" ) ; + if ( $imap->has_capability( 'namespace' ) ) { + my $r_namespace = $imap->namespace( ) ; + $prefix_out = $r_namespace->[0][0][0] ; + myprint( "$Side: prefix given by NAMESPACE: [$prefix_out]\n" ) ; + if ( defined $prefix_in ) { + myprint( "$Side: but using [$prefix_in] given by $prefix_opt\n" ) ; + $prefix_out = $prefix_in ; + return( $prefix_out ) ; + }else{ + # all good + return( $prefix_out ) ; + } + } + else{ + if ( defined $prefix_in ) { + myprint( "$Side: using [$prefix_in] given by $prefix_opt\n" ) ; + $prefix_out = $prefix_in ; + return( $prefix_out ) ; + }else{ + myprint( + "$Side: No NAMESPACE capability so using guessed prefix [$prefix_guessed]\n", + help_to_guess_prefix( $imap, $prefix_opt ) ) ; + return( $prefix_guessed ) ; + } + } + return ; +} - my $code = shift; - my $imap = shift; - my ($iss,$key); +sub guess_separator +{ + my @foldernames = @_ ; - if( $imap->Password =~ /^(.*\.json)$/x ) { - my $json = JSON->new( ) ; - my $filename = $1; - $debug and myprint( "XOAUTH2 json file: $filename\n" ) ; - open( my $FILE, '<', $filename ) or die_clean( "error [$filename]: $OS_ERROR " ) ; - my $jsonfile = $json->decode( join q{}, <$FILE> ) ; - close $FILE ; + #return( undef ) unless ( @foldernames ) ; - $iss = $jsonfile->{client_id}; - $key = $jsonfile->{private_key}; - $debug and myprint( "Service account: $iss\n"); - $debug and myprint( "Private key:\n$key\n"); + my $sep_guessed ; + my %counter ; + foreach my $folder ( @foldernames ) { + $counter{'/'}++ while ( $folder =~ m{/}xg ) ; # count / + $counter{'.'}++ while ( $folder =~ m{\.}xg ) ; # count . + $counter{'\\\\'}++ while ( $folder =~ m{(\\){2}}xg ) ; # count \\ + $counter{'\\'}++ while ( $folder =~ m{[^\\](\\){1}(?=[^\\])}xg ) ; # count \ } - else { - # Get iss (service account address), keyfile name, and keypassword if necessary - ( $iss, my $keyfile, my $keypass ) = $imap->Password =~ /([\-\d\w\@\.]+);([a-zA-Z0-9 \_\-\.\/]+);?(.*)?/x ; + my @race_sorted = sort { $counter{ $b } <=> $counter{ $a } } keys %counter ; + $sync->{ debug } and myprint( "@foldernames\n@race_sorted\n", %counter, "\n" ) ; + $sep_guessed = shift @race_sorted || $LAST_RESSORT_SEPARATOR ; # / when nothing found. + return( $sep_guessed ) ; +} - # Assume key password is google default if not provided - $keypass = 'notasecret' if not $keypass; +sub tests_guess_separator +{ + note( 'Entering tests_guess_separator()' ) ; - $debug and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n"); + ok( '/' eq guess_separator( ), 'guess_separator: no args' ) ; + ok( '/' eq guess_separator( 'abcd' ), 'guess_separator: abcd' ) ; + ok( '/' eq guess_separator( 'a/b/c.d' ), 'guess_separator: a/b/c.d' ) ; + ok( '.' eq guess_separator( 'a.b/c.d' ), 'guess_separator: a.b/c.d' ) ; + ok( '\\\\' eq guess_separator( 'a\\\\b\\\\c.c\\\\d/e/f' ), 'guess_separator: a\\\\b\\\\c.c\\\\d/e/f' ) ; + ok( '\\' eq guess_separator( 'a\\b\\c.c\\d/e/f' ), 'guess_separator: a\\b\\c.c\\d/e/f' ) ; + ok( '\\' eq guess_separator( 'a\\b' ), 'guess_separator: a\\b' ) ; + ok( '\\' eq guess_separator( 'a\\b\\c' ), 'guess_separator: a\\b\\c' ) ; - # Get private key from p12 file (would be better in perl...) - $key = `openssl pkcs12 -in "$keyfile" -nodes -nocerts -passin pass:$keypass -nomacver`; + note( 'Leaving tests_guess_separator()' ) ; + return ; +} - $debug and myprint( "Private key:\n$key\n"); - } +sub get_separator +{ + my( $imap, $sep_in, $sep_opt, $Side, $folders_ref ) = @_ ; + my( $sep_out, $sep_guessed ) ; - # Create jwt of oauth2 request - my $time = time ; - my $jwt = JSON::WebToken->encode( { - 'iss' => $iss, # service account - 'scope' => 'https://mail.google.com/', - 'aud' => 'https://www.googleapis.com/oauth2/v3/token', - 'exp' => $time + $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12, - 'iat' => $time, - 'prn' => $imap->User # user to auth as - }, - $key, 'RS256', {'typ' => 'JWT'} ); # Crypt::OpenSSL::RSA needed here. + ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting separator\n" ) ; + $sep_guessed = guess_separator( @{ $folders_ref } ) ; + myprint( "$Side: guessing separator from folder listing: [$sep_guessed]\n" ) ; - # Post oauth2 request - my $ua = LWP::UserAgent->new( ) ; - $ua->env_proxy( ) ; + ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: calling namespace capability\n" ) ; + if ( $imap->has_capability( 'namespace' ) ) + { + $sep_out = $imap->separator( ) ; + if ( defined $sep_out ) { + myprint( "$Side: separator given by NAMESPACE: [$sep_out]\n" ) ; + if ( defined $sep_in ) { + myprint( "$Side: but using [$sep_in] given by $sep_opt\n" ) ; + $sep_out = $sep_in ; + return( $sep_out ) ; + }else{ + return( $sep_out ) ; + } + }else{ + if ( defined $sep_in ) { + myprint( "$Side: NAMESPACE request failed but using [$sep_in] given by $sep_opt\n" ) ; + $sep_out = $sep_in ; + return( $sep_out ) ; + }else{ + myprint( + "$Side: NAMESPACE request failed so using guessed separator [$sep_guessed]\n", + help_to_guess_sep( $imap, $sep_opt ) ) ; + return( $sep_guessed ) ; + } + } + } + else + { + if ( defined $sep_in ) { + myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n" ) ; + $sep_out = $sep_in ; + return( $sep_out ) ; + }else{ + myprint( + "$Side: No NAMESPACE capability, so using guessed separator [$sep_guessed]\n", + help_to_guess_sep( $imap, $sep_opt ) ) ; + return( $sep_guessed ) ; + } + } + return ; +} - my $response = $ua->post('https://www.googleapis.com/oauth2/v3/token', - { grant_type => HTML::Entities::encode_entities('urn:ietf:params:oauth:grant-type:jwt-bearer'), - assertion => $jwt } ) ; +sub help_to_guess_sep +{ + my( $imap, $sep_opt ) = @_ ; - unless( $response->is_success( ) ) { - die_clean( $response->code, "\n", $response->content, "\n" ) ; - }else{ - $debug and myprint( $response->content ) ; - } + my $help_to_guess_sep = "You can set the separator character with the $sep_opt option,\n" + . "the complete listing of folders may help you to find it\n" + . folders_list_to_help( $imap ) ; - # access_token in response is what we need - my $data = JSON::decode_json( $response->content ) ; + return( $help_to_guess_sep ) ; +} - # format as oauth2 auth data - my $xoauth2_string = encode_base64( 'user=' . $imap->User . "\1auth=Bearer " . $data->{access_token} . "\1\1", q{} ) ; +sub help_to_guess_prefix +{ + my( $imap, $prefix_opt ) = @_ ; - $debug and myprint( "XOAUTH2 String: $xoauth2_string\n"); - return($xoauth2_string); + my $help_to_guess_prefix = "You can set the prefix namespace with the $prefix_opt option,\n" + . "the folowing listing of folders may help you to find it:\n" + . folders_list_to_help( $imap ) ; + + return( $help_to_guess_prefix ) ; } +sub folders_list_to_help +{ + my( $imap ) = shift ; + my @folders = $imap->folders ; + my $listing = join q{}, map { "[$_]\n" } @folders ; + return( $listing ) ; +} -# xoauth() thanks to Eduardo Bortoluzzi Junior -sub xoauth { - require URI::Escape ; - require Data::Uniqid ; +# Globals are $sync @h1_folders_all @h2_folders_all $prefix1 $prefix2 +sub private_folders_separators_and_prefixes +{ +# what are the private folders separators and prefixes for each server ? - my $code = shift; - my $imap = shift; + ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Getting separators\n" ) ; + $sync->{ h1_sep } = get_separator( $sync->{imap1}, $sync->{ sep1 }, '--sep1', 'Host1', \@h1_folders_all ) ; + $sync->{ h2_sep } = get_separator( $sync->{imap2}, $sync->{ sep2 }, '--sep2', 'Host2', \@h2_folders_all ) ; - # The base information needed to construct the OAUTH authentication - my $method = 'GET' ; - my $url = mysprintf( 'https://mail.google.com/mail/b/%s/imap/', $imap->User ) ; - my $urlparm = mysprintf( 'xoauth_requestor_id=%s', URI::Escape::uri_escape( $imap->User ) ) ; - # For Google Apps, the consumer key is the primary domain - # TODO: create a command line argument to define the consumer key - my @user_parts = split /@/x, $imap->User ; - $debug and myprint( "XOAUTH: consumer key: $user_parts[1]\n" ) ; + $sync->{ h1_prefix } = get_prefix( $sync->{imap1}, $prefix1, '--prefix1', 'Host1', \@h1_folders_all ) ; + $sync->{ h2_prefix } = get_prefix( $sync->{imap2}, $prefix2, '--prefix2', 'Host2', \@h2_folders_all ) ; - # All the parameters needed to be signed on the XOAUTH - my %hash = (); - $hash { 'xoauth_requestor_id' } = URI::Escape::uri_escape($imap->User); - $hash { 'oauth_consumer_key' } = $user_parts[1]; - $hash { 'oauth_nonce' } = md5_hex(Data::Uniqid::uniqid(rand(), 1==1)); - $hash { 'oauth_signature_method' } = 'HMAC-SHA1'; - $hash { 'oauth_timestamp' } = time ; - $hash { 'oauth_version' } = '1.0'; + myprint( "Host1: separator and prefix: [$sync->{ h1_sep }][$sync->{ h1_prefix }]\n" ) ; + myprint( "Host2: separator and prefix: [$sync->{ h2_sep }][$sync->{ h2_prefix }]\n" ) ; + return ; +} - # Base will hold the string to be signed - my $base = "$method&" . URI::Escape::uri_escape( $url ) . q{&} ; - # The parameters must be in dictionary order before signing - my $baseparms = q{} ; - foreach my $key ( sort keys %hash ) { - if ( length( $baseparms ) > 0 ) { - $baseparms .= q{&} ; +sub subfolder1 +{ + my $mysync = shift ; + my $subfolder1 = sanitize_subfolder( $mysync->{ subfolder1 } ) ; + + if ( $subfolder1 ) + { + # turns off automap + myprint( "Turning off automapping folders because of --subfolder1\n" ) ; + $mysync->{ automap } = undef ; + myprint( "Sanitizing subfolder1: [$mysync->{ subfolder1 }] => [$subfolder1]\n" ) ; + $mysync->{ subfolder1 } = $subfolder1 ; + if ( ! add_subfolder1_to_folderrec( $mysync ) ) + { + $mysync->{nb_errors}++ ; + exit_clean( $mysync, $EXIT_SUBFOLDER1_NO_EXISTS, "subfolder1 $subfolder1 does not exist\n" ) ; } - - $baseparms .= "$key=$hash{$key}" ; + } + else + { + $mysync->{ subfolder1 } = undef ; } - $base .= URI::Escape::uri_escape($baseparms); - $debug and myprint( "XOAUTH: base request to sign: $base\n" ) ; - # Sign it with the consumer secret, informed on the command line (password) - my $digest = hmac_sha1( $base, URI::Escape::uri_escape( $imap->Password ) . q{&} ) ; + return ; +} - # The parameters signed become a parameter and... - $hash { 'oauth_signature' } = URI::Escape::uri_escape( substr encode_base64( $digest ), 0, $MINUS_ONE ) ; +sub subfolder2 +{ + my $mysync = shift ; + my $subfolder2 = sanitize_subfolder( $mysync->{ subfolder2 } ) ; + if ( $subfolder2 ) + { + # turns off automap + myprint( "Turning off automapping folders because of --subfolder2\n" ) ; + $mysync->{ automap } = undef ; + myprint( "Sanitizing subfolder2: [$mysync->{ subfolder2 }] => [$subfolder2]\n" ) ; + $mysync->{ subfolder2 } = $subfolder2 ; + set_regextrans2_for_subfolder2( $mysync ) ; + } + else + { + $mysync->{ subfolder2 } = undef ; + } - # ... we don't need the requestor_id anymore. - delete $hash{'xoauth_requestor_id'} ; + return ; +} - # Create the final authentication string - my $string = $method . q{ } . $url . q{?} . $urlparm .q{ } ; +sub tests_sanitize_subfolder +{ + note( 'Entering tests_sanitize_subfolder()' ) ; - # All the parameters must be sorted - $baseparms = q{}; - foreach my $key (sort keys %hash) { - if(length($baseparms)>0) { - $baseparms .= q{,} ; - } + is( undef, sanitize_subfolder( ), 'sanitize_subfolder: no args => undef' ) ; + is( undef, sanitize_subfolder( q{} ), 'sanitize_subfolder: empty => undef' ) ; + is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blank => undef' ) ; + is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blanks => undef' ) ; + is( 'abcd', sanitize_subfolder( 'abcd' ), 'sanitize_subfolder: abcd => abcd' ) ; + is( 'ab cd', sanitize_subfolder( ' ab cd ' ), 'sanitize_subfolder: " ab cd " => "ab cd"' ) ; + is( 'abcd', sanitize_subfolder( q{a&~b#\\c[]=d;} ), 'sanitize_subfolder: "a&~b#\\c[]=d;" => "abcd"' ) ; + is( 'aA.b-_ 8c/dD', sanitize_subfolder( 'aA.b-_ 8c/dD' ), 'sanitize_subfolder: aA.b-_ 8c/dD => aA.b-_ 8c/dD' ) ; + note( 'Leaving tests_sanitize_subfolder()' ) ; + return ; +} - $baseparms .= "$key=\"$hash{$key}\""; - } - $string .= $baseparms; +sub sanitize_subfolder +{ + my $subfolder = shift ; - $debug and myprint( "XOAUTH: authentication string: $string\n" ) ; + if ( ! $subfolder ) + { + return ; + } + # Remove edging blanks + $subfolder =~ s,^ +| +$,,g ; + # Keep only abcd...ABCD...0123... and -_./ + $subfolder =~ tr,-_a-zA-Z0-9./ ,,cd ; - # It must be base64 encoded - return encode_base64("$string", q{}); + # A blank subfolder is not a subfolder + if ( ! $subfolder ) + { + return ; + } + else + { + return $subfolder ; + } } +sub tests_sanitize_host +{ + note( 'Entering tests_sanitize_host()' ) ; + is( undef, sanitize_host( ), 'sanitize_host: no args => undef' ) ; + is( '', sanitize_host( '' ), 'sanitize_host: empty => empty' ) ; + is( 'imap.example.org', sanitize_host( 'imap.example.org' ), 'sanitize_host: imap.example.org => imap.example.org' ) ; + is( 'imap.example.org', sanitize_host( ' imap.example.org' ), 'sanitize_host: imap.example.org 1 => imap.example.org' ) ; + is( 'imap.example.org', sanitize_host( 'imap.example.org ' ), 'sanitize_host: imap.example.org 2 => imap.example.org' ) ; + is( 'imap.example.org', sanitize_host( 'imap.exam ple.org' ), 'sanitize_host: imap.example.org 3 => imap.example.org' ) ; + is( 'imap.example.org', sanitize_host( ' imap.exam ple.org ' ), 'sanitize_host: imap.example.org 4 => imap.example.org' ) ; + is( 'imap.example.org', sanitize_host( 'imap.exa/mple.org/' ), 'sanitize_host: imap.example.org/ => imap.example.org' ) ; -sub banner_imapsync { - - my @argv = @_ ; + note( 'Leaving tests_sanitize_host()' ) ; + return ; +} - my $banner_imapsync = join q{}, - q{$RCSfile: imapsync,v $ }, - q{$Revision: 1.882 $ }, - q{$Date: 2018/05/05 21:10:43 $ }, - "\n", - "Command line used:\n", - "$PROGRAM_NAME ", command_line_nopassword( @argv ), "\n" ; - return( $banner_imapsync ) ; +sub sanitize_host +{ + my $host = shift ; + if ( ! defined $host ) { return ; } + + $host =~ tr{ /}{}d ; + return $host ; +} + + +sub tests_add_subfolder1_to_folderrec +{ + note( 'Entering tests_add_subfolder1_to_folderrec()' ) ; + + is( undef, add_subfolder1_to_folderrec( ), 'add_subfolder1_to_folderrec: undef => undef' ) ; + is_deeply( [], [ add_subfolder1_to_folderrec( ) ], 'add_subfolder1_to_folderrec: no args => empty array' ) ; + @folderrec = () ; + my $mysync = {} ; + is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: empty => empty array' ) ; + is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: empty => empty folderrec' ) ; + $mysync->{ subfolder1 } = 'SUBI' ; + $h1_folders_all{ 'SUBI' } = 1 ; + $mysync->{ h1_prefix } = 'INBOX/' ; + is_deeply( [ 'SUBI' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBI => SUBI' ) ; + is_deeply( [ 'SUBI' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBI => folderrec SUBI ' ) ; + + @folderrec = () ; + $mysync->{ subfolder1 } = 'SUBO' ; + is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO no exists => empty array' ) ; + is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO no exists => empty folderrec' ) ; + $h1_folders_all{ 'INBOX/SUBO' } = 1 ; + is_deeply( [ 'INBOX/SUBO' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO' ) ; + is_deeply( [ 'INBOX/SUBO' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO folderrec' ) ; + + note( 'Leaving tests_add_subfolder1_to_folderrec()' ) ; + return ; } -sub do_valid_directory { - my $dir = shift; - # all good => return ok. - return( 1 ) if ( -d $dir and -r _ and -w _ ) ; +sub add_subfolder1_to_folderrec +{ + my $mysync = shift ; + if ( ! $mysync || ! $mysync->{ subfolder1 } ) + { + return ; + } - # exist but bad - if ( -e $dir and not -d _ ) { - myprint( "Error: $dir exists but is not a directory\n" ) ; - return( 0 ) ; + my $subfolder1 = $mysync->{ subfolder1 } ; + my $subfolder1_extended = $mysync->{ h1_prefix } . $subfolder1 ; + + if ( exists $h1_folders_all{ $subfolder1 } ) + { + myprint( qq{Acting like --folderrec "$subfolder1"\n} ) ; + push @folderrec, $subfolder1 ; } - if ( -e $dir and not -w _ ) { - my $sb = stat $dir ; - myprintf( "Error: directory %s is not writable for user %s, permissions are %04o and owner is %s ( uid %s )\n", - $dir, getpwuid_any_os( $EFFECTIVE_USER_ID ), ($sb->mode & oct($PERMISSION_FILTER) ), getpwuid_any_os( $sb->uid ), $sb->uid( ) ) ; - return( 0 ) ; + elsif ( exists $h1_folders_all{ $subfolder1_extended } ) + { + myprint( qq{Acting like --folderrec "$subfolder1_extended"\n} ) ; + push @folderrec, $subfolder1_extended ; } - # Trying to create it - myprint( "Creating directory $dir\n" ) ; - if ( ! eval { mkpath( $dir ) } ) { - myprint( "$EVAL_ERROR" ) if ( $EVAL_ERROR ) ; + else + { + myprint( qq{Nor folder "$subfolder1" nor "$subfolder1_extended" exists on host1\n} ) ; } - return( 1 ) if ( -d $dir and -r _ and -w _ ) ; - return( 0 ) ; + return @folderrec ; } -sub tests_do_valid_directory { - note( 'Entering tests_do_valid_directory()' ) ; +sub set_regextrans2_for_subfolder2 +{ + my $mysync = shift ; - Readonly my $NB_UNIX_tests_do_valid_directory => 2 ; - SKIP: { - skip( 'Tests only for Unix', $NB_UNIX_tests_do_valid_directory ) if ( 'MSWin32' eq $OSNAME ) ; - ok( 1 == do_valid_directory( '.'), 'do_valid_directory: . good' ) ; - ok( 1 == do_valid_directory( './W/tmp/tests/valid/sub'), 'do_valid_directory: ./W/tmp/tests/valid/sub good' ) ; - } - Readonly my $NB_UNIX_tests_do_valid_directory_non_root => 2 ; - SKIP: { - skip( 'Tests only for Unix', $NB_UNIX_tests_do_valid_directory_non_root ) if ( 'MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) ; - diag( 'Error / not writable is on purpose' ) ; - ok( 0 == do_valid_directory( '/'), 'do_valid_directory: / bad' ) ; - diag( 'Error permission denied on /noway is on purpose' ) ; - ok( 0 == do_valid_directory( '/noway'), 'do_valid_directory: /noway bad' ) ; - } - - - note( 'Leaving tests_do_valid_directory()' ) ; - return ; -} + unshift @{ $mysync->{ regextrans2 } }, + q(s,^$mysync->{ h2_prefix }(.*),$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }$1,), + q(s,^INBOX$,$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }INBOX,), + q(s,^($mysync->{ h2_prefix }){2},$mysync->{ h2_prefix },); -sub tests_match_a_pid_number { - is( undef, match_a_pid_number( ), 'match_a_pid_number: no args => undef' ) ; - is( undef, match_a_pid_number( '' ), 'match_a_pid_number: "" => undef' ) ; - is( undef, match_a_pid_number( 'lalala' ), 'match_a_pid_number: lalala => undef' ) ; - is( 1, match_a_pid_number( 1 ), 'match_a_pid_number: 1 => 1' ) ; - is( 1, match_a_pid_number( 123 ), 'match_a_pid_number: 123 => 1' ) ; - is( 1, match_a_pid_number( '123' ), 'match_a_pid_number: "123" => 1' ) ; - is( undef, match_a_pid_number( 'a123' ), 'match_a_pid_number: a123 => undef' ) ; - is( 1, match_a_pid_number( 65535 ), 'match_a_pid_number: 65535 => 1' ) ; - is( undef, match_a_pid_number( 0 ), 'match_a_pid_number: 0 => undef' ) ; - is( undef, match_a_pid_number( 65536 ), 'match_a_pid_number: 65536 => undef' ) ; - is( undef, match_a_pid_number( 99999 ), 'match_a_pid_number: 99999 => undef' ) ; + #myprint( "@{ $mysync->{ regextrans2 } }\n" ) ; return ; } -sub match_a_pid_number { - my $pid = shift ; - if ( ! $pid ) { return ; } - if ( ! match( $pid, '^\d+$' ) ) { return ; } - if ( 0 > $pid ) { return ; } - if ( 65535 < $pid ) { return ; } - return 1 ; -} -sub tests_remove_pidfile_not_running { - is( undef, remove_pidfile_not_running( ), 'remove_pidfile_not_running: no args => undef' ) ; - is( undef, remove_pidfile_not_running( './W' ), 'remove_pidfile_not_running: a dir => undef' ) ; - is( undef, remove_pidfile_not_running( 'noexists' ), 'remove_pidfile_not_running: noexists => undef' ) ; - is( 1, touch( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: prepa empty W/tmp/tests/empty.pid' ) ; - is( undef, remove_pidfile_not_running( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: W/tmp/tests/empty.pid => undef' ) ; - is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/lalala.pid' ) ; - is( undef, remove_pidfile_not_running( 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: W/tmp/tests/lalala.pid => undef' ) ; - is( '55555', string_to_file( '55555', 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/notrunning.pid' ) ; - is( 1, remove_pidfile_not_running( 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: W/tmp/tests/notrunning.pid => 1' ) ; - is( $PROCESS_ID, string_to_file( $PROCESS_ID, 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/running.pid' ) ; - is( undef, remove_pidfile_not_running( 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: W/tmp/tests/running.pid => undef' ) ; - + +# Looks like no globals here + +sub tests_imap2_folder_name +{ + note( 'Entering tests_imap2_folder_name()' ) ; + + my $mysync = {} ; + $mysync->{ h1_prefix } = q{} ; + $mysync->{ h2_prefix } = q{} ; + $mysync->{ h1_sep } = '/'; + $mysync->{ h2_sep } = '.'; + + $mysync->{ debug } and myprint( <<"EOS" +prefix1: [$mysync->{ h1_prefix }] +prefix2: [$mysync->{ h2_prefix }] +sep1: [$sync->{ h1_sep }] +sep2: [$sync->{ h2_sep }] +EOS +) ; + + $mysync->{ fixslash2 } = 0 ; + is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string' ) ; + is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ; + is('spam.spam', imap2_folder_name( $mysync, 'spam/spam' ), 'imap2_folder_name: spam/spam' ) ; + + is( 'spam/spam', imap2_folder_name( $mysync, 'spam.spam' ), 'imap2_folder_name: spam.spam') ; + is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam' ) ; + is( 's pam.spam/sp am', imap2_folder_name( $mysync, 's pam/spam.sp am' ), 'imap2_folder_name: s pam/spam.sp am' ) ; + + $mysync->{f1f2h}{ 'auto' } = 'moto' ; + is( 'moto', imap2_folder_name( $mysync, 'auto' ), 'imap2_folder_name: auto' ) ; + $mysync->{f1f2h}{ 'auto/auto' } = 'moto x 2' ; + is( 'moto x 2', imap2_folder_name( $mysync, 'auto/auto' ), 'imap2_folder_name: auto/auto' ) ; + + @{ $mysync->{ regextrans2 } } = ( 's,/,X,g' ) ; + is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string [s,/,X,g]' ) ; + is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla [s,/,X,g]' ) ; + is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam [s,/,X,g]'); + is('spamXspam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam [s,/,X,g]'); + is('spam.spamXspam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam [s,/,X,g]'); + + @{ $mysync->{ regextrans2 } } = ( 's, ,_,g' ) ; + is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla [s, ,_,g]'); + is('bla_bla', imap2_folder_name( $mysync, 'bla bla'), 'imap2_folder_name: blabla [s, ,_,g]'); + + @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ; + is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), q{imap2_folder_name: blabla [s,\U(.*)\E,$1,]} ) ; + + $mysync->{ fixslash2 } = 1 ; + @{ $mysync->{ regextrans2 } } = ( ) ; + is(q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string'); + is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla'); + is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam'); + is('spam_spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam_spam'); + is('spam.spam_spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam_spam'); + is('s pam.spam_spa m', imap2_folder_name( $mysync, 's pam/spam.spa m'), 'imap2_folder_name: s pam/spam.spa m -> s pam.spam_spa m'); + + $mysync->{ h1_sep } = '.'; + $mysync->{ h2_sep } = '/'; + is( q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string'); + is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla'); + is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam'); + is('spam/spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam/spam'); + is('spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam'); + + + + $mysync->{ fixslash2 } = 0 ; + $mysync->{ h1_prefix } = q{ }; + + is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ; + is( 'spam.spam/spam', imap2_folder_name( $mysync, ' spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ; + + $mysync->{ h1_sep } = '.' ; + $mysync->{ h2_sep } = '/' ; + $mysync->{ h1_prefix } = 'INBOX.' ; + $mysync->{ h2_prefix } = q{} ; + @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ; + is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ; + is( 'TEST/TEST/TEST/TEST', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ; + @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\L$1,} ) ; + is( 'test/test/test/test', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ; + + # INBOX + $mysync = {} ; + $mysync->{ h1_prefix } = q{Pf1.} ; + $mysync->{ h2_prefix } = q{Pf2/} ; + $mysync->{ h1_sep } = '.'; + $mysync->{ h2_sep } = '/'; + + # + #$mysync->{ debug } = 1 ; + is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'F1.F2.F3' ), 'imap2_folder_name: F1.F2.F3 -> Pf2/F1/F2/F3' ) ; + is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'F1.INBOX' ), 'imap2_folder_name: F1.INBOX -> Pf2/F1/INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> INBOX' ) ; + + is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.F1.F2.F3' ), 'imap2_folder_name: Pf1.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; + is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'Pf1.F1.INBOX' ), 'imap2_folder_name: Pf1.F1.INBOX -> Pf2/F1/INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.INBOX' ), 'imap2_folder_name: Pf1.INBOX -> INBOX' ) ; # not Pf2/INBOX: Yes I can! + + + + # subfolder2 + $mysync = {} ; + $mysync->{ h1_prefix } = q{} ; + $mysync->{ h2_prefix } = q{} ; + $mysync->{ h1_sep } = '/'; + $mysync->{ h2_sep } = '.'; + + + set_regextrans2_for_subfolder2( $mysync ) ; + $mysync->{ subfolder2 } = 'S1.S2' ; + is( 'S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.F1.F2.F3' ) ; + is( 'S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.INBOX' ) ; + + $mysync = {} ; + $mysync->{ h1_prefix } = q{Pf1/} ; + $mysync->{ h2_prefix } = q{Pf2.} ; + $mysync->{ h1_sep } = '/'; + $mysync->{ h2_sep } = '.'; + #$mysync->{ debug } = 1 ; + + set_regextrans2_for_subfolder2( $mysync ) ; + $mysync->{ subfolder2 } = 'Pf2.S1.S2' ; + is( 'Pf2.S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> Pf2.S1.S2.F1.F2.F3' ) ; + is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ; + is( 'Pf2.S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'Pf1/F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> Pf2.S1.S2.F1.F2.F3' ) ; + is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'Pf1/INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ; + + # subfolder1 + # scenario as the reverse of the previous tests, separators point of vue + $mysync = {} ; + $mysync->{ h1_prefix } = q{Pf1.} ; + $mysync->{ h2_prefix } = q{Pf2/} ; + $mysync->{ h1_sep } = '.'; + $mysync->{ h2_sep } = '/'; + #$mysync->{ debug } = 1 ; + + $mysync->{ subfolder1 } = 'S1.S2' ; + is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'S1.S2.F1.F2.F3' ), 'imap2_folder_name: S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; + is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.S1.S2.F1.F2.F3' ), 'imap2_folder_name: Pf1.S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; + + is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ; + + is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ; + + + $mysync->{ subfolder1 } = 'S1.S2.' ; + is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'S1.S2.F1.F2.F3' ), 'imap2_folder_name: S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; + is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.S1.S2.F1.F2.F3' ), 'imap2_folder_name: Pf1.S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; + + is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ; + + is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ; + + + # subfolder1 + # scenario as Gmail + $mysync = {} ; + $mysync->{ h1_prefix } = q{} ; + $mysync->{ h2_prefix } = q{} ; + $mysync->{ h1_sep } = '/'; + $mysync->{ h2_sep } = '/'; + #$mysync->{ debug } = 1 ; + + $mysync->{ subfolder1 } = 'S1/S2' ; + is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ; + + $mysync->{ subfolder1 } = 'S1/S2/' ; + is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ; + + + note( 'Leaving tests_imap2_folder_name()' ) ; return ; } -sub remove_pidfile_not_running { - # - my $pid_filename = shift ; - - if ( ! $pid_filename ) { return } ; - if ( ! -e $pid_filename ) { return } ; - if ( ! -f $pid_filename ) { return } ; - - my $pid = firstline( $pid_filename ) ; - if ( ! match_a_pid_number( $pid ) ) { return } ; - # can't kill myself => do nothing - if ( ! kill 'ZERO', $PROCESS_ID ) { return } ; - - # can't kill the pid => it is gone or own by another user => remove pidfile - if ( ! kill 'ZERO', $pid ) { - myprint( "Removing old $pid_filename since its PID $pid is not running anymore (oo-killed?)\n" ) ; - if ( unlink $pid_filename ) { - myprint( "Removed old $pid_filename\n" ) ; - return 1 ; - }else{ - myprint( "Could not remove old $pid_filename because $!\n" ) ; - return ; - } + +# Global variables to remove: +# None? + + +sub imap2_folder_name +{ + my $mysync = shift ; + my ( $h1_fold ) = shift ; + my ( $h2_fold ) ; + if ( $mysync->{f1f2h}{ $h1_fold } ) { + $h2_fold = $mysync->{f1f2h}{ $h1_fold } ; + ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n" ) ; + return( $h2_fold ) ; + } + if ( $mysync->{f1f2auto}{ $h1_fold } ) { + $h2_fold = $mysync->{f1f2auto}{ $h1_fold } ; + ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n" ) ; + return( $h2_fold ) ; } - myprint( "Another imapsync process $pid is running as says pidfile $pid_filename\n" ) ; - return ; -} -sub tests_write_pidfile { - my $mysync ; - - is( 1, write_pidfile( ), 'write_pidfile: no args => 1' ) ; - - $mysync->{pidfile} = '/no/no/no.pid' ; - is( 1, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid, no lock => 1' ) ; - $mysync->{pidfilelocking} = 1 ; - is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid + lock => undef' ) ; - - $mysync->{pidfile} = 'W/tmp/tests/test.pid' ; - ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'write_pidfile: mkpath W/tmp/tests/' ) ; - is( 1, touch( $mysync->{pidfile} ), 'write_pidfile: lock prepa' ) ; - - $mysync->{pidfilelocking} = 0 ; - is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid => 1' ) ; - is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains $PROCESS_ID" ) ; + if ( $mysync->{ subfolder1 } ) + { + my $esc_h1_sep = "\\" . $mysync->{ h1_sep } ; + # case where subfolder1 has the sep1 at the end, then remove it + my $part_to_removed = remove_last_char_if_is( $mysync->{ subfolder1 }, $mysync->{ h1_sep } ) ; + # remove the subfolder1 part and the sep1 if present after + $h1_fold =~ s{$part_to_removed($esc_h1_sep)?}{} ; + #myprint( "h1_fold=$h1_fold\n" ) ; + } + + if ( ( q{} eq $h1_fold ) or ( $mysync->{ h1_prefix } eq $h1_fold ) ) + { + $h1_fold = 'INBOX' ; + } - $mysync->{pidfilelocking} = 1 ; - is( undef, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + lock => undef' ) ; - + $h2_fold = prefix_seperator_invertion( $mysync, $h1_fold ) ; + $h2_fold = regextrans2( $mysync, $h2_fold ) ; + return( $h2_fold ) ; +} + + +sub tests_remove_last_char_if_is +{ + note( 'Entering tests_remove_last_char_if_is()' ) ; + + is( undef, remove_last_char_if_is( ), 'remove_last_char_if_is: no args => undef' ) ; + is( q{}, remove_last_char_if_is( q{} ), 'remove_last_char_if_is: empty => empty' ) ; + is( q{}, remove_last_char_if_is( q{}, 'Z' ), 'remove_last_char_if_is: empty Z => empty' ) ; + is( q{}, remove_last_char_if_is( 'Z', 'Z' ), 'remove_last_char_if_is: Z Z => empty' ) ; + is( 'abc', remove_last_char_if_is( 'abcZ', 'Z' ), 'remove_last_char_if_is: abcZ Z => abc' ) ; + is( 'abcY', remove_last_char_if_is( 'abcY', 'Z' ), 'remove_last_char_if_is: abcY Z => abcY' ) ; + note( 'Leaving tests_remove_last_char_if_is()' ) ; return ; } -sub write_pidfile { - # returns undef if something is considered fatal - # returns 1 otherwise - - if ( ! @ARG ) { return 1 ; } - - my $mysync = shift ; - - # Do not write the pid file if this process goal is to abort the process designed by the pid file - if ( $mysync->{abort} ) { return 1 ; } - - # - my $pid_filename = $mysync->{pidfile} ; - my $lock = $mysync->{pidfilelocking} ; - - myprint( "PID file is $pid_filename ( to change it use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ; - if ( -e $pid_filename and $lock ) { - myprint( "$pid_filename already exists, another imapsync may be curently running. Aborting imapsync.\n" ) ; + + + +sub remove_last_char_if_is +{ + my $string = shift ; + my $char = shift ; + + if ( ! defined $string ) + { return ; - } - if ( -e $pid_filename ) { - myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n" ) ; + + if ( ! defined $char ) + { + return $string ; } - if ( open my $FILE_HANDLE, '>', $pid_filename ) { - myprint( "Writing my PID $PROCESS_ID in $pid_filename\n" ) ; - print $FILE_HANDLE $PROCESS_ID ; - close $FILE_HANDLE ; - return( 1 ) ; - } else { - myprint( "Could not open $pid_filename for writing. Check permissions or disk space.\n" ) ; - if ( $lock ) { - return ; - }else{ - return( 1 ) ; - } + my $last_char = substr $string, -1 ; + if ( $char eq $last_char ) + { + chop $string ; + return $string ; + } + else + { + return $string ; } } +sub tests_prefix_seperator_invertion +{ + note( 'Entering tests_prefix_seperator_invertion()' ) ; + is( undef, prefix_seperator_invertion( ), 'prefix_seperator_invertion: no args => undef' ) ; + is( q{}, prefix_seperator_invertion( undef, q{} ), 'prefix_seperator_invertion: empty string => empty string' ) ; + is( 'lalala', prefix_seperator_invertion( undef, 'lalala' ), 'prefix_seperator_invertion: lalala => lalala' ) ; + is( 'lal/ala', prefix_seperator_invertion( undef, 'lal/ala' ), 'prefix_seperator_invertion: lal/ala => lal/ala' ) ; + is( 'lal.ala', prefix_seperator_invertion( undef, 'lal.ala' ), 'prefix_seperator_invertion: lal.ala => lal.ala' ) ; + is( '////', prefix_seperator_invertion( undef, '////' ), 'prefix_seperator_invertion: //// => ////' ) ; + is( '.....', prefix_seperator_invertion( undef, '.....' ), 'prefix_seperator_invertion: ..... => .....' ) ; -sub remove_tmp_files { - my $mysync = shift or return ; - $mysync->{pidfile} or return ; - if ( -e $mysync->{pidfile} ) { - unlink $mysync->{pidfile} ; - } + my $mysync = { + h1_prefix => q{}, + h2_prefix => q{}, + h1_sep => '/', + h2_sep => '/', + } ; + + is( q{}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: $mysync empty string => empty string' ) ; + is( 'lalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: $mysync lalala => lalala' ) ; + is( 'lal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: $mysync lal/ala => lal/ala' ) ; + is( 'lal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: $mysync lal.ala => lal.ala' ) ; + is( '////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: $mysync //// => ////' ) ; + is( '.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: $mysync ..... => .....' ) ; + + $mysync = { + h1_prefix => 'PPP', + h2_prefix => 'QQQ', + h1_sep => 's', + h2_sep => 't', + } ; + + is( q{QQQ}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: PPPQQQst empty string => QQQ' ) ; + is( 'QQQlalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: PPPQQQst lalala => QQQlalala' ) ; + is( 'QQQlal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: PPPQQQst lal/ala => QQQlal/ala' ) ; + is( 'QQQlal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: PPPQQQst lal.ala => QQQlal.ala' ) ; + is( 'QQQ////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: PPPQQQst //// => QQQ////' ) ; + is( 'QQQ.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: PPPQQQst ..... => QQQ.....' ) ; + + is( 'QQQPlalala', prefix_seperator_invertion( $mysync, 'PPPPlalala' ), 'prefix_seperator_invertion: PPPQQQst PPPPlalala => QQQPlalala' ) ; + is( 'QQQ', prefix_seperator_invertion( $mysync, 'PPP' ), 'prefix_seperator_invertion: PPPQQQst PPP => QQQ' ) ; + is( 'QQQttt', prefix_seperator_invertion( $mysync, 'sss' ), 'prefix_seperator_invertion: PPPQQQst sss => QQQttt' ) ; + is( 'QQQt', prefix_seperator_invertion( $mysync, 's' ), 'prefix_seperator_invertion: PPPQQQst s => QQQt' ) ; + is( 'QQQtAAAtBBB', prefix_seperator_invertion( $mysync, 'PPPsAAAsBBB' ), 'prefix_seperator_invertion: PPPQQQst PPPsAAAsBBB => QQQtAAAtBBB' ) ; + + note( 'Leaving tests_prefix_seperator_invertion()' ) ; return ; } +# Global variables to remove: -sub exit_clean { - my $mysync = shift ; - my $status = shift ; - $status = defined $status ? $status : $EXIT_UNKNOWN ; - remove_tmp_files( $mysync ) ; - myprint( "Exiting with return value $status\n" ) ; - if ( $mysync->{log} ) { - myprint( "Log file is $mysync->{logfile} ( to change it, use --logfile filepath ; or use --nolog to turn off logging )\n" ) ; - close $mysync->{logfile_handle} ; + +sub prefix_seperator_invertion +{ + my $mysync = shift ; + my $h1_fold = shift ; + my $h2_fold ; + + if ( not defined $h1_fold ) { return ; } + + my $my_h1_prefix = $mysync->{ h1_prefix } || q{} ; + my $my_h2_prefix = $mysync->{ h2_prefix } || q{} ; + my $my_h1_sep = $mysync->{ h1_sep } || '/' ; + my $my_h2_sep = $mysync->{ h2_sep } || '/' ; + + # first we remove the prefix + $h1_fold =~ s/^\Q$my_h1_prefix\E//x ; + ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n" ) ; + $h2_fold = separator_invert( $mysync, $h1_fold, $my_h1_sep, $my_h2_sep ) ; + ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "inverted separators: [$h2_fold]\n" ) ; + + # Adding the prefix supplied by namespace or the --prefix2 option + # except for INBOX or Inbox + if ( $h2_fold !~ m/^INBOX$/xi ) + { + $h2_fold = $my_h2_prefix . $h2_fold ; } - exit $status ; -} -sub die_clean { - my @messages = @_ ; - remove_tmp_files( $sync ) ; - myprint( @messages ) ; - exit 255 ; + ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "added host2 prefix: [$h2_fold]\n" ) ; + return( $h2_fold ) ; } -sub missing_option { - my ( $option ) = @_ ; - die_clean( "$option option is mandatory, for help run $PROGRAM_NAME --help\n" ) ; - return ; -} +sub tests_separator_invert +{ + note( 'Entering tests_separator_invert()' ) ; + my $mysync = {} ; + $mysync->{ fixslash2 } = 0 ; + ok( not( defined separator_invert( ) ), 'separator_invert: no args' ) ; + ok( not( defined separator_invert( q{} ) ), 'separator_invert: not enough args' ) ; + ok( not( defined separator_invert( q{}, q{} ) ), 'separator_invert: not enough args' ) ; -sub fix_Inbox_INBOX_mapping { - my( $h1_all, $h2_all ) = @_ ; + ok( q{} eq separator_invert( $mysync, q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ; + ok( 'lalala' eq separator_invert( $mysync, 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ; + ok( 'lalala' eq separator_invert( $mysync, 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ; + ok( 'lal/ala' eq separator_invert( $mysync, 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ; + ok( 'lal.ala' eq separator_invert( $mysync, 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ; + ok( 'lal/ala' eq separator_invert( $mysync, 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ; + ok( 'la.l/ala' eq separator_invert( $mysync, 'la/l.ala', '.', '/' ), 'separator_invert: separators ./' ) ; - my $regex = q{} ; - SWITCH: { - if ( exists $h1_all->{INBOX} and exists $h2_all->{INBOX} ) { $regex = q{} ; last SWITCH ; } ; - if ( exists $h1_all->{Inbox} and exists $h2_all->{Inbox} ) { $regex = q{} ; last SWITCH ; } ; - if ( exists $h1_all->{INBOX} and exists $h2_all->{Inbox} ) { $regex = q{s/^INBOX$/Inbox/x} ; last SWITCH ; } ; - if ( exists $h1_all->{Inbox} and exists $h2_all->{INBOX} ) { $regex = q{s/^Inbox$/INBOX/x} ; last SWITCH ; } ; - } ; - return( $regex ) ; -} + ok( 'l/al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ; + $mysync->{ fixslash2 } = 1 ; + ok( 'l_al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ; -sub tests_fix_Inbox_INBOX_mapping { - note( 'Entering tests_fix_Inbox_INBOX_mapping()' ) ; + note( 'Leaving tests_separator_invert()' ) ; + return ; +} +# Global variables to remove: +# +sub separator_invert +{ + my( $mysync, $h1_fold, $h1_separator, $h2_separator ) = @_ ; - my( $h1_all, $h2_all ) ; + return( undef ) if ( not all_defined( $mysync, $h1_fold, $h1_separator, $h2_separator ) ) ; + # The separator we hope we'll never encounter: 00000000 == 0x00 + my $o_sep = "\000" ; - $h1_all = { 'INBOX' => q{} } ; - $h2_all = { 'INBOX' => q{} } ; - ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX INBOX' ) ; + my $h2_fold = $h1_fold ; + $h2_fold =~ s,\Q$h2_separator,$o_sep,xg ; + $h2_fold =~ s,\Q$h1_separator,$h2_separator,xg ; + $h2_fold =~ s,\Q$o_sep,$h1_separator,xg ; + $h2_fold =~ s,/,_,xg if( $mysync->{ fixslash2 } and '/' ne $h2_separator and '/' eq $h1_separator ) ; + return( $h2_fold ) ; +} - $h1_all = { 'Inbox' => q{} } ; - $h2_all = { 'Inbox' => q{} } ; - ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox Inbox' ) ; - $h1_all = { 'INBOX' => q{} } ; - $h2_all = { 'Inbox' => q{} } ; - ok( q{s/^INBOX$/Inbox/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX Inbox' ) ; +sub regextrans2 +{ + my( $mysync, $h2_fold ) = @_ ; + # Transforming the folder name by the --regextrans2 option(s) + foreach my $regextrans2 ( @{ $mysync->{ regextrans2 } } ) { + my $h2_fold_before = $h2_fold ; + my $ret = eval "\$h2_fold =~ $regextrans2 ; 1 " ; + ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "[$h2_fold_before] -> [$h2_fold] using regextrans2 [$regextrans2]\n" ) ; + if ( not ( defined $ret ) or $EVAL_ERROR ) { + $mysync->{nb_errors}++ ; + exit_clean( $mysync, $EX_USAGE, + "error: eval regextrans2 '$regextrans2': $EVAL_ERROR\n" + ) ; + } + } + return( $h2_fold ) ; +} - $h1_all = { 'Inbox' => q{} } ; - $h2_all = { 'INBOX' => q{} } ; - ok( q{s/^Inbox$/INBOX/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox INBOX' ) ; - $h1_all = { 'INBOX' => q{} } ; - $h2_all = { 'rrrrr' => q{} } ; - ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX rrrrrr' ) ; +sub tests_decompose_regex +{ + note( 'Entering tests_decompose_regex()' ) ; - $h1_all = { 'rrrrr' => q{} } ; - $h2_all = { 'Inbox' => q{} } ; - ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: rrrrr Inbox' ) ; + ok( 1, 'decompose_regex 1' ) ; + ok( 0 == compare_lists( [ q{}, q{} ], [ decompose_regex( q{} ) ] ), 'decompose_regex empty string' ) ; + ok( 0 == compare_lists( [ '.*', 'lala' ], [ decompose_regex( 's/.*/lala/' ) ] ), 'decompose_regex s/.*/lala/' ) ; - note( 'Leaving tests_fix_Inbox_INBOX_mapping()' ) ; + note( 'Leaving tests_decompose_regex()' ) ; return ; } +sub decompose_regex +{ + my $regex = shift ; + my( $left_part, $right_part ) ; -sub jux_utf8_list { - my @s_inp = @_ ; - my $s_out = q{} ; - foreach my $s ( @s_inp ) { - $s_out .= jux_utf8( $s ) . "\n" ; - } - return( $s_out ) ; + ( $left_part, $right_part ) = $regex =~ m{^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/}x; + return( q{}, q{} ) if not $left_part ; + return( $left_part, $right_part ) ; } -sub tests_jux_utf8_list { - note( 'Entering tests_jux_utf8_list()' ) ; - ok( q{} eq jux_utf8_list( ), 'jux_utf8_list: void' ) ; - ok( "[]\n" eq jux_utf8_list( q{} ), 'jux_utf8_list: empty string' ) ; - ok( "[INBOX]\n" eq jux_utf8_list( 'INBOX' ), 'jux_utf8_list: INBOX' ) ; - ok( "[&ANY-] = [ร–]\n" eq jux_utf8_list( '&ANY-' ), 'jux_utf8_list: &ANY-' ) ; - note( 'Leaving tests_jux_utf8_list()' ) ; - return( 0 ) ; +sub tests_timenext +{ + note( 'Entering tests_timenext()' ) ; + + is( undef, timenext( ), 'timenext: no args => undef' ) ; + my $mysync ; + is( undef, timenext( $mysync ), 'timenext: undef => undef' ) ; + $mysync = {} ; + ok( time - timenext( $mysync ) <= 1e-02, 'timenext: defined first time => ~ time' ) ; + ok( timenext( $mysync ) <= 1e-02, 'timenext: second time => less than 1e-02' ) ; + ok( timenext( $mysync ) <= 1e-02, 'timenext: third time => less than 1e-02' ) ; + + note( 'Leaving tests_timenext()' ) ; + return ; } -sub jux_utf8 { - # juxtapose utf8 at the right if different - my ( $s_utf7 ) = shift ; - my ( $s_utf8 ) = imap_utf7_decode( $s_utf7 ) ; - if ( $s_utf7 eq $s_utf8 ) { - #myprint( "[$s_utf7]\n" ) ; - return( "[$s_utf7]" ) ; - }else{ - #myprint( "[$s_utf7] = [$s_utf8]\n" ) ; - return( "[$s_utf7] = [$s_utf8]" ) ; +sub timenext +{ + my $mysync = shift ; + + if ( ! defined $mysync ) + { + return ; } + my ( $timenow, $timediff ) ; + + $mysync->{ timebefore } ||= 0; # epoch... + $timenow = time ; + $timediff = $timenow - $mysync->{ timebefore } ; + $mysync->{ timebefore } = $timenow ; + # myprint( "timenext: $timediff\n" ) ; + return( $timediff ) ; } -# editing utf8 can be tricky without an utf8 editor -sub tests_jux_utf8 { - note( 'Entering tests_jux_utf8()' ) ; - ok( '[INBOX]' eq jux_utf8( 'INBOX'), 'jux_utf8: INBOX => [INBOX]' ) ; - ok( '[&ZTZO9nux-] = [ๆ”ถไปถ็ฎฑ]' eq jux_utf8( '&ZTZO9nux-'), 'jux_utf8: => [&ZTZO9nux-] = [ๆ”ถไปถ็ฎฑ]' ) ; - ok( '[&ANY-] = [ร–]' eq jux_utf8( '&ANY-'), 'jux_utf8: &ANY- => [&ANY-] = [ร–]' ) ; - ok( '[]' eq jux_utf8( q{} ), 'jux_utf8: void => []' ) ; - ok( '[+BD8EQAQ1BDQEOwQ+BDM-] = [ะฟั€ะตะดะปะพะณ]' eq jux_utf8( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8: => [+BD8EQAQ1BDQEOwQ+BDM-] = [ะฟั€ะตะดะปะพะณ]' ) ; - ok( '[&BB8EQAQ+BDUEOgRC-] = [ะŸั€ะพะตะบั‚]' eq jux_utf8( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8: => [&BB8EQAQ+BDUEOgRC-] = [ะŸั€ะพะตะบั‚]' ) ; +sub tests_timesince +{ + note( 'Entering tests_timesince()' ) ; - note( 'Leaving tests_jux_utf8()' ) ; + ok( timesince( time - 1 ) - 1 <= 1e-02, 'timesince: time - 1 => <= 1 + 1e-02' ) ; + ok( timesince( time ) <= 1e-02, 'timesince: time => <= 1e-02' ) ; + ok( timesince( ) - time <= 1e-02, 'timesince: no args => <= time + 1e-02' ) ; + note( 'Leaving tests_timesince()' ) ; return ; } -# Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm -# and then fixed with -# https://rt.cpan.org/Public/Bug/Display.html?id=11172 -sub imap_utf7_decode { - my ( $s ) = shift ; - # Algorithm - # On remplace , par / dans les BASE 64 (, entre & et -) - # On remplace les &, non suivi d'un - par + - # On remplace les &- par & - $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/xg ; - $s =~ s/&(?!\-)/\+/xg ; - $s =~ s/&\-/&/xg ; - return( Unicode::String::utf7( $s )->utf8 ) ; + +sub timesince +{ + my $timeinit = shift || 0 ; + my ( $timenow, $timediff ) ; + $timenow = time ; + $timediff = $timenow - $timeinit ; + # Often used in a division so no 0 but a nano seconde. + return( max( $timediff, min( 1e-09, $timediff ) ) ) ; } -sub imap_utf7_encode { - my ( $s ) = @_ ; - $s = Unicode::String::utf8( $s )->utf7 ; - $s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/xg ; - $s =~ s/&/&\-/xg ; - $s =~ s/\+([^+\-]+)?\-/&$1\-/xg ; - return( $s ) ; -} +sub tests_regexflags +{ + note( 'Entering tests_regexflags()' ) ; + my $mysync = {} ; + ok( q{} eq regexflags( $mysync, q{} ), 'regexflags, null string q{}' ) ; + ok( q{\Seen NonJunk $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, nothing to do} ) ; -sub select_folder { - my ( $imap, $folder, $hostside ) = @_ ; - if ( ! $imap->select( $folder ) ) { - my $error = join q{}, - "$hostside folder $folder: Could not select: ", - $imap->LastError, "\n" ; - errors_incr( $sync, $error ) ; - return( 0 ) ; - }else{ - # ok select succeeded - return( 1 ) ; - } -} + @{ $mysync->{ regexflag } } = ('I am BAD' ) ; + ok( not ( defined regexflags( $mysync, q{} ) ), 'regexflags, bad regex' ) ; -sub examine_folder { - my ( $imap, $folder, $hostside ) = @_ ; - if ( ! $imap->examine( $folder ) ) { - my $error = join q{}, - "$hostside folder $folder: Could not examine: ", - $imap->LastError, "\n" ; - errors_incr( $sync, $error ) ; - return( 0 ) ; - }else{ - # ok select succeeded - return( 1 ) ; - } -} + @{ $mysync->{ regexflag } } = ( 's/NonJunk//g' ) ; + ok( q{\Seen $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove NonJunk: 's/NonJunk//g'} ) ; + @{ $mysync->{ regexflag } } = ( q{s/\$Spam//g} ) ; + ok( q{\Seen NonJunk } eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove $Spam: 's/\$Spam//g'} ) ; + @{ $mysync->{ regexflag } } = ( 's/\\\\Seen//g' ) ; + ok( q{ NonJunk $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove \Seen: 's/\\\\\\\\Seen//g'} ) ; + @{ $mysync->{ regexflag } } = ( 's/(\s|^)[^\\\\]\w+//g' ) ; + ok( q{\Seen \Middle \End} eq regexflags( $mysync, q{\Seen NonJunk \Middle $Spam \End} ), q{regexflags: only \word among \Seen NonJunk \Middle $Spam \End} ) ; + ok( q{ \Seen \Middle \End1} eq regexflags( $mysync, q{Begin \Seen NonJunk \Middle $Spam \End1 End} ), + q{regexflags: only \word among Begin \Seen NonJunk \Middle $Spam \End1 End} ) ; + + @{ $mysync->{ regexflag } } = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g} ) ; + ok( 'Keep1 Keep2 ReB' eq regexflags( $mysync, 'ReA Keep1 REM Keep2 ReB' ), 'Keep only regex' ) ; + + ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM REM Keep1 Keep2' ), 'Keep only regex' ) ; + ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 REM REM Keep2' ), 'Keep only regex' ) ; + ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM REM Keep2' ), 'Keep only regex' ) ; + ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2' ), 'Keep only regex' ) ; + ok( 'Keep1 ' eq regexflags( $mysync, 'REM Keep1' ), 'Keep only regex' ) ; + + @{ $mysync->{ regexflag } } = ( q{s/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g} ) ; + ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2 ReB' ), 'Keep only regex' ) ; + ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2 REM REM REM' ), 'Keep only regex' ) ; + ok( 'Keep2 ' eq regexflags( $mysync, 'Keep2 REM REM REM' ), 'Keep only regex' ) ; -sub count_from_select { - my @lines = @_ ; - my $count ; - foreach my $line ( @lines ) { - #myprint( "line = [$line]\n" ) ; - if ( $line =~ m/^\*\s+(\d+)\s+EXISTS/x ) { - $count = $1 ; - return( $count ) ; - } - } - return( undef ) ; -} + @{ $mysync->{ regexflag } } = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g}, + 's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g' ) ; + ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2 REM' ), 'Keep only regex' ) ; + ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 REM Keep2 REM' ), 'Keep only regex' ) ; + ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 Keep2 REM' ), 'Keep only regex' ) ; + ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2' ), 'Keep only regex' ) ; + ok( 'Keep1 Keep2 Keep3 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2 REM REM Keep3 REM' ), 'Keep only regex' ) ; + ok( 'Keep1 ' eq regexflags( $mysync, 'REM REM Keep1 REM REM REM ' ), 'Keep only regex' ) ; + ok( 'Keep1 Keep3 ' eq regexflags( $mysync, 'RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 ' ), 'Keep only regex' ) ; + + @{ $mysync->{ regexflag } } = ( 's/(.*)/$1 jrdH8u/' ) ; + ok('REM REM REM REM REM jrdH8u' eq regexflags( $mysync, 'REM REM REM REM REM' ), q{Add jrdH8u 's/(.*)/\$1 jrdH8u/'} ) ; + @{ $mysync->{ regexflag } } = ('s/jrdH8u *//' ); + ok('REM REM REM REM REM ' eq regexflags( $mysync, 'REM REM REM REM REM jrdH8u' ), q{Remove jrdH8u s/jrdH8u *//} ) ; + + @{ $mysync->{ regexflag } } = ( + 's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg' + ); + ok( '\\Deleted \\Answered ' + eq regexflags( $mysync, 'Blabla \$Junk \\Deleted machin \\Answered truc' ), + 'Keep only regex: Exchange case (Phil)' ) ; + ok( q{} eq regexflags( $mysync, q{} ), 'Keep only regex: Exchange case, null string (Phil)' ) ; + ok( q{} + eq regexflags( $mysync, 'Blabla $Junk machin truc' ), + 'Keep only regex: Exchange case, no accepted flags (Phil)' ) ; + ok('\\Deleted \\Answered \\Draft \\Flagged ' + eq regexflags( $mysync, '\\Deleted \\Answered \\Draft \\Flagged ' ), + 'Keep only regex: Exchange case (Phil)' ) ; + @{ $mysync->{ regexflag } } = ( 's/\\\\Flagged//g' ) ; + is('\Deleted \Answered \Draft ', + regexflags( $mysync, '\\Deleted \\Answered \\Draft \\Flagged ' ), + 'regexflags: remove \Flagged 1' ) ; + is('\\Deleted \\Answered \\Draft', + regexflags( $mysync, '\\Deleted \\Flagged \\Answered \\Draft' ), + 'regexflags: remove \Flagged 2' ) ; + # I didn't understand why it gives \F + # https://perldoc.perl.org/perlrebackslash.html + # \F Foldcase till \E. Not in []. + # https://perldoc.perl.org/functions/fc.html + # \F Not available in old Perl so I comment the test + # @{ $mysync->{ regexflag } } = ( 's/\\Flagged/X/g' ) ; + #is('\Deleted FX \Answered \FX \Draft \FX', + #regexflags( '\Deleted Flagged \Answered \Flagged \Draft \Flagged' ), + # 'regexflags: remove \Flagged 3 mistery...' ) ; + $mysync->{ regexflag } = [ ] ; + $mysync->{ filterbuggyflags } = 1 ; + filterbuggyflags( $mysync ) ; + is( '\Deleted \Answered \Draft \Flagged', + regexflags( $mysync, '\\Deleted \\Answered \\RECEIPTCHECKED \\Draft \\Indexed \\Flagged' ), + 'regexflags: remove famous /X 1' ) ; + is( '\\Deleted \\Flagged \\Answered \\Draft', + regexflags( $mysync, '\\Deleted \\RECEIPTCHECKED \\Flagged \\Answered \\Indexed \\Draft' ), + 'regexflags: remove famous /X 2' ) ; + is( '\ ', '\\ ', 'regexflags: \ is \\ ' ) ; + is( '\\ ', '\\ ', 'regexflags: \\ is \\ ' ) ; + is( '\\ \ ', '\ \\ ', 'regexflags: \\ \ is \ \\ ' ) ; + note( 'Leaving tests_regexflags()' ) ; + return ; +} +sub regexflags +{ + my $mysync = shift ; + my $flags = shift ; + foreach my $regexflag ( @{ $mysync->{ regexflag } } ) + { + my $flags_orig = $flags ; + $debugflags and myprint( "eval \$flags =~ $regexflag\n" ) ; + my $ret = eval "\$flags =~ $regexflag ; 1 " ; + $debugflags and myprint( "regexflag $regexflag [$flags_orig] -> [$flags]\n" ) ; + if( not ( defined $ret ) or $EVAL_ERROR ) { + myprint( "Error: eval regexflag '$regexflag': $EVAL_ERROR\n" ) ; + return( undef ) ; + } + } + return( $flags ) ; +} -sub create_folder_old { - my( $imap, $h2_fold, $h1_fold ) = @_ ; - myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ; - if ( ( 'INBOX' eq uc $h2_fold ) - and ( $imap->exists( $h2_fold ) ) ) { - myprint( "Folder [$h2_fold] already exists\n" ) ; - return( 1 ) ; - } - if ( ! $sync->{dry} ){ - if ( ! $imap->create( $h2_fold ) ) { - my $error = join q{}, - "Could not create folder [$h2_fold] from [$h1_fold]: ", - $imap->LastError( ), "\n" ; - errors_incr( $sync, $error ) ; - # success if folder exists ("already exists" error) - return( 1 ) if $imap->exists( $h2_fold ) ; - # failure since create failed - return( 0 ) ; - }else{ - #create succeeded - myprint( "Created ( the old way ) folder [$h2_fold] on host2\n" ) ; - return( 1 ) ; - } - }else{ - # dry mode, no folder so many imap will fail, assuming failure - myprint( "Created ( the old way ) folder [$h2_fold] on host2 $sync->{dry_message}\n" ) ; - return( 0 ) ; +sub filterbuggyflags +{ + my $mysync = shift ; + if ( $mysync->{ filterbuggyflags } ) + { + unshift @{ $mysync->{ regexflag } }, buggyflagsregex( ) ; } + return ; } -sub create_folder { - my( $imap2 , $h2_fold , $h1_fold ) = @_ ; - my( @parts , $parent ) ; +sub tests_remove_doublequotes_if_any +{ + note( 'Entering tests_remove_doublequotes_if_any()' ) ; + # the number of tests is stupid here + is( undef, remove_doublequotes_if_any( ), 'remove_doublequotes_if_any: no args => undef' ) ; + is( q{}, remove_doublequotes_if_any( q{} ), 'remove_doublequotes_if_any: empty string => empty string' ) ; + is( q{}, remove_doublequotes_if_any( q{""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ; + is( q{}, remove_doublequotes_if_any( q{"""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ; + is( q{}, remove_doublequotes_if_any( q{"""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ; + is( q{toto}, remove_doublequotes_if_any( q{"toto"} ), 'remove_doublequotes_if_any: "toto" => toto' ) ; + is( q{toto}, remove_doublequotes_if_any( q{toto} ), 'remove_doublequotes_if_any: toto => toto' ) ; + is( q{toto}, remove_doublequotes_if_any( q{to"to} ), 'remove_doublequotes_if_any: to"to => toto' ) ; + is( q{toto}, remove_doublequotes_if_any( q{toto"} ), 'remove_doublequotes_if_any: toto" => toto' ) ; + is( q{toto}, remove_doublequotes_if_any( q{"toto} ), 'remove_doublequotes_if_any: "toto => toto' ) ; + is( q{toto}, remove_doublequotes_if_any( q{"to"to} ), 'remove_doublequotes_if_any: "to"to => toto' ) ; + is( q{toto}, remove_doublequotes_if_any( q{to"to"} ), 'remove_doublequotes_if_any: to"to" => toto' ) ; + + is( q{toto}, remove_doublequotes_if_any( q{to\"to} ), 'remove_doublequotes_if_any: to\"to => toto' ) ; + is( q{toto}, remove_doublequotes_if_any( q{toto\"} ), 'remove_doublequotes_if_any: toto\" => toto' ) ; + is( q{toto}, remove_doublequotes_if_any( q{\"toto} ), 'remove_doublequotes_if_any: \"toto => toto' ) ; + is( q{toto}, remove_doublequotes_if_any( q{\"to\"to} ), 'remove_doublequotes_if_any: \"to\"to => toto' ) ; + is( q{toto}, remove_doublequotes_if_any( q{to\"to\"} ), 'remove_doublequotes_if_any: to\"to" => toto' ) ; + + + note( 'Leaving tests_remove_doublequotes_if_any()' ) ; + return ; +} - if ( $imap2->IsUnconnected( ) ) { - myprint( "Host2: Unconnected state\n" ) ; - return( 0 ) ; - } - if ( $create_folder_old ) { - return( create_folder_old( $imap2 , $h2_fold , $h1_fold ) ) ; - } - myprint( "Creating folder [$h2_fold] on host2\n" ) ; - if ( ( 'INBOX' eq uc $h2_fold ) - and ( $imap2->exists( $h2_fold ) ) ) { - myprint( "Folder [$h2_fold] already exists\n" ) ; - return( 1 ) ; - } - if ( $mixfolders and $imap2->exists( $h2_fold ) ) { - myprint( "Folder [$h2_fold] already exists (--nomixfolders is not set)\n" ) ; - return( 1 ) ; - } +sub remove_doublequotes_if_any +{ + my $string = shift ; + if ( ! defined $string ) { return ; } + $string =~ s/\\\"//g ; + $string =~ tr/"//d ; + return $string ; +} - if ( ( not $mixfolders ) and ( $imap2->exists( $h2_fold ) ) ) { - myprint( "Folder [$h2_fold] already exists and --nomixfolders is set\n" ) ; - return( 0 ) ; - } - @parts = split /\Q$h2_sep\E/x, $h2_fold ; - pop @parts ; - $parent = join $h2_sep, @parts ; - $parent =~ s/^\s+|\s+$//xg ; - if ( ( $parent ne q{} ) and ( ! $imap2->exists( $parent ) ) ) { - create_folder( $imap2 , $parent , $h1_fold ) ; - } +# No globals here +sub acls_sync +{ +# https://tools.ietf.org/html/rfc4314 +# Standard Rights: +# https://tools.ietf.org/html/rfc4314#section-2.1 - if ( ! $sync->{dry} ) { - if ( ! $imap2->create( $h2_fold ) ) { - my $error = join q{}, - "Could not create folder [$h2_fold] from [$h1_fold]: " , - $imap2->LastError( ), "\n" ; - errors_incr( $sync, $error ) ; - # success if folder exists ("already exists" error) - return( 1 ) if $imap2->exists( $h2_fold ) ; - # failure since create failed - return( 0 ) ; - }else{ - #create succeeded - myprint( "Created folder [$h2_fold] on host2\n" ) ; - return( 1 ) ; - } - }else{ - # dry mode, no folder so many imap will fail, assuming failure - myprint( "Created folder [$h2_fold] on host2 $sync->{dry_message}\n" ) ; - if ( ! $justfolders ) { - myprint( "Since --dry mode is on and folder [$h2_fold] on host2 does not exist yet, syncing messages will not be simulated.\n" - . "To simulate message syncing, use --justfolders without --dry to first create the missing folders then rerun the --dry sync.\n" ) ; + my( $mysync, $h1_fold, $h2_fold ) = @_ ; + if ( $mysync->{ syncacls } ) { + my $h1_hash = $mysync->{imap1}->getacl($h1_fold) + or myprint( "Host1: Could not getacl for $h1_fold: $EVAL_ERROR\n" ) ; + my $h2_hash = $mysync->{imap2}->getacl($h2_fold) + or myprint( "Host2: Could not getacl for $h2_fold: $EVAL_ERROR\n" ) ; + + my %users = map { ($_, 1) } ( keys %{ $h1_hash} , keys %{ $h2_hash } ) ; + foreach my $user (sort keys %users ) { + my $h1_acl = remove_doublequotes_if_any( $h1_hash->{$user} ) || '' ; + my $h2_acl = remove_doublequotes_if_any( $h2_hash->{$user} ) || '' ; + myprint( "Host1: user $user has acl [$h1_acl] on host1\n" ) ; + myprint( "Host2: user $user has acl [$h2_acl] on host2\n" ) ; + # removes surrounding double-quotes if any + my $user_no_quotes = remove_doublequotes_if_any( $user ) ; + + if ( $h1_hash->{$user} + && $h2_hash->{$user} + && $h1_hash->{$user} eq $h2_hash->{$user} ) + { + myprint( "Host2: user $user_no_quotes has already the same acl, no need to set it.\n" ) ; + next ; + } + myprint( "Host2: setting acl for folder $h2_fold user $user_no_quotes acl $h1_acl $mysync->{dry_message}\n" ) ; + unless ( $mysync->{dry} ) { + $mysync->{imap2}->setacl( $h2_fold, $user_no_quotes, $h1_acl ) + or myprint( "Could not set acl for user $user_no_quotes on host2: $EVAL_ERROR\n" ) ; + } } - return( 0 ) ; } + return ; } +sub tests_permanentflags +{ + note( 'Entering tests_permanentflags()' ) ; -sub tests_folder_routines { - note( 'Entering tests_folder_routines()' ) ; + my $mysync = { } ; + ok( q{} eq permanentflags( $mysync, ' * OK [PERMANENTFLAGS (\* \Draft \Answered)] Limited' ), + 'permanentflags \*' ) ; - ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 1' ); - ok( add_to_requested_folders('folder_foo'), 'add_to_requested_folders folder_foo' ); - ok( is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 2' ); - ok( !is_requested_folder('folder_NO_EXIST'), 'is_requested_folder folder_NO_EXIST' ); - ok( !remove_from_requested_folders('folder_foo'), 'removed folder_foo' ); - ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 3' ); - my @f ; - ok( @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f" ); - ok( is_requested_folder('folder_bar'), 'is_requested_folder 4' ); - ok( is_requested_folder('folder_toto'), 'is_requested_folder 5' ); - ok( remove_from_requested_folders('folder_toto'), 'remove_from_requested_folders: ' ); - ok( !is_requested_folder('folder_toto'), 'is_requested_folder 6' ); - ok( !remove_from_requested_folders('folder_bar'), 'remove_from_requested_folders: empty' ) ; + ok( '\Draft \Answered' eq permanentflags( $mysync, ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited' ), + 'permanentflags \Draft \Answered' ) ; - ok( 0 == compare_lists( [ sort_requested_folders( ) ], [] ), 'sort_requested_folders: all empty' ) ; - ok( add_to_requested_folders('M_55'), 'add_to_requested_folders M_55' ); - ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'M_55' ] ), 'sort_requested_folders: middle' ) ; - @folderfirst = ( 'Z_11' ) ; - ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ; - @folderlast = ( 'A_99' ) ; - ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 1' ) ; + ok( '\Draft \Answered' + eq permanentflags( $mysync, 'Blabla', + ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited', + 'Blabla' ), + 'permanentflags \Draft \Answered' + ) ; - ok( add_to_requested_folders('M_55', 'M_44',), 'add_to_requested_folders M_55 M_44' ); - ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_44', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 2' ) ; - @folderfirst = qw( Z_22 Z_11 ) ; - @folderlast = qw( A_99 A_88 ) ; - ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_22', 'Z_11', 'M_44', 'M_55', 'A_99', 'A_88' ] ), 'sort_requested_folders: first+middle+last 3' ) ; + ok( q{} eq permanentflags( $mysync, 'Blabla' ), 'permanentflags nothing' ) ; - note( 'Leaving tests_folder_routines()' ) ; + note( 'Leaving tests_permanentflags()' ) ; return ; } +sub permanentflags +{ + my $mysync = shift ; -sub sort_requested_folders { - my @requested_folders_sorted = () ; - - foreach my $folder ( @folderfirst ) { - remove_from_requested_folders( $folder ) ; - } + my @lines = @_ ; - foreach my $folder ( @folderlast ) { - remove_from_requested_folders( $folder ) ; + foreach my $line (@lines) { + if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) { + ( $debugflags or $sync->{ debug } ) and myprint( "permanentflags: $line" ) ; + my $permanentflags = $1 ; + if ( $permanentflags =~ m{\\\*}x ) + { + $permanentflags = q{} ; + } + return( $permanentflags ) ; + } ; } + return( q{} ) ; +} - my @middle = sort keys %requested_folder ; +sub tests_flags_filter +{ + note( 'Entering tests_flags_filter()' ) ; - @requested_folders_sorted = ( @folderfirst, @middle, @folderlast ) ; + ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' ); + ok( q{} eq flags_filter('\Seen', '\Draft \Answered'), 'flags_filter ' ); + ok( '\Seen' eq flags_filter('\Seen', '\Seen'), 'flags_filter ' ); + ok( '\Seen' eq flags_filter('\Seen', ' \Seen '), 'flags_filter ' ); + ok( '\Seen \Draft' + eq flags_filter('\Seen \Draft', '\Draft \Seen \Answered'), 'flags_filter ' ); + ok( '\Seen \Draft' + eq flags_filter('\Seen \Draft', ' \Draft \Seen \Answered '), 'flags_filter ' ); - return( @requested_folders_sorted ) ; + note( 'Leaving tests_flags_filter()' ) ; + return ; } -sub is_requested_folder { - my ( $folder ) = @_; +sub flags_filter +{ + my( $flags, $allowed_flags ) = @_ ; + + my @flags = split /\s+/x, $flags ; + my %allowed_flags = map { $_ => 1 } split q{ }, $allowed_flags ; + my @flags_out = map { exists $allowed_flags{$_} ? $_ : () } @flags ; + + my $flags_out = join q{ }, @flags_out ; - return( defined $requested_folder{ $folder } ) ; + return( $flags_out ) ; } +sub tests_flagscase +{ + note( 'Entering tests_flagscase()' ) ; -sub add_to_requested_folders { - my @wanted_folders = @_ ; + ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ; + ok( '\Seen' eq flagscase( '\SEEN' ), 'flagscase: \SEEN -> \Seen' ) ; - foreach my $folder ( @wanted_folders ) { - ++$requested_folder{ $folder } ; - } - return( keys %requested_folder ) ; + ok( '\Seen \Draft' eq flagscase( '\SEEN \DRAFT' ), 'flagscase: \SEEN \DRAFT -> \Seen \Draft' ) ; + ok( '\Draft \Seen' eq flagscase( '\DRAFT \SEEN' ), 'flagscase: \DRAFT \SEEN -> \Draft \Seen' ) ; + + ok( '\Draft LALA \Seen' eq flagscase( '\DRAFT LALA \SEEN' ), 'flagscase: \DRAFT LALA \SEEN -> \Draft LALA \Seen' ) ; + ok( '\Draft lala \Seen' eq flagscase( '\DRAFT lala \SEEN' ), 'flagscase: \DRAFT lala \SEEN -> \Draft lala \Seen' ) ; + + note( 'Leaving tests_flagscase()' ) ; + return ; } -sub remove_from_requested_folders { - my @wanted_folders = @_ ; +sub flagscase +{ + my $flags = shift ; - foreach my $folder ( @wanted_folders ) { - delete $requested_folder{ $folder } ; - } - return( keys %requested_folder ) ; + my @flags = split /\s+/x, $flags ; + my %rfc_flags = map { $_ => 1 } split q{ }, '\Answered \Flagged \Deleted \Seen \Draft' ; + my @flags_out = map { exists $rfc_flags{ ucsecond( lc $_ ) } ? ucsecond( lc $_ ) : $_ } @flags ; + + my $flags_out = join q{ }, @flags_out ; + + return( $flags_out ) ; } -sub compare_lists { - my ($list_1_ref, $list_2_ref) = @_; - return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref); - return(0) if ((not defined $list_1_ref) and not defined $list_2_ref); # end if no list - return(1) if (not defined $list_2_ref); # end if only one list - if (not ref $list_1_ref ) {$list_1_ref = [$list_1_ref]}; - if (not ref $list_2_ref ) {$list_2_ref = [$list_2_ref]}; +sub tests_flags_for_host2 +{ + note( 'Entering tests_flags_for_host2()' ) ; + is( undef, flags_for_host2( ), 'flags_for_host2: no args => undef' ) ; - my $last_used_indice = $MINUS_ONE; + my $mysync ; + is( undef, flags_for_host2( $mysync ), 'flags_for_host2: undef => undef' ) ; + $mysync = { } ; + is( undef, flags_for_host2( $mysync ), 'flags_for_host2: nothing => undef' ) ; - ELEMENT: - foreach my $indice ( 0 .. $#{ $list_1_ref } ) { - $last_used_indice = $indice ; + is( q{}, flags_for_host2( $mysync, '' ), 'flags_for_host2: no flags => empty string' ) ; - # End of list_2 - return 1 if ($indice > $#{ $list_2_ref } ) ; + is( q{}, flags_for_host2( $mysync, '\Recent' ), 'flags_for_host2: \Recent => empty string' ) ; - my $element_list_1 = $list_1_ref->[$indice] ; - my $element_list_2 = $list_2_ref->[$indice] ; - my $balance = $element_list_1 cmp $element_list_2 ; - next ELEMENT if ($balance == 0) ; - return $balance ; - } - # each element equal until last indice of list_1 - return $MINUS_ONE if ($last_used_indice < $#{ $list_2_ref } ) ; + is( q{\Seen}, flags_for_host2( $mysync, '\Recent \Seen' ), 'flags_for_host2: \Recent \Seen => \Seen' ) ; - # same size, each element equal - return 0 ; + is( q{\Deleted \Seen}, flags_for_host2( $mysync, '\Deleted \Recent \Seen' ), 'flags_for_host2: \Deleted \Recent \Seen => \Deleted \Seen' ) ; + + $mysync->{ flagscase } = 0 ; + is( q{\DELETED \Seen}, flags_for_host2( $mysync, '\DELETED \Seen' ), 'flags_for_host2: flagscase = 0 \DELETED \Seen => \DELETED \Seen' ) ; + + $mysync->{ flagscase } = 1 ; + is( q{\Deleted \Seen}, flags_for_host2( $mysync, '\DELETED \Seen' ), 'flags_for_host2: flagscase = 1 \DELETED \Seen => \Deleted \Seen' ) ; + + $mysync->{ filterflags } = 0 ; + is( q{\Seen \Blabla}, flags_for_host2( $mysync, '\Seen \Blabla', '\Seen \Junk' ), 'flags_for_host2: filterflags = 0 \Seen \Blabla among \Seen \Junk => \Seen \Blabla' ) ; + + $mysync->{ filterflags } = 1 ; + is( q{\Seen}, flags_for_host2( $mysync, '\Seen \Blabla', '\Seen \Junk' ), 'flags_for_host2: filterflags = 1 \Seen \Blabla among \Seen \Junk => \Seen' ) ; + + $mysync->{ filterflags } = 1 ; + is( q{\Seen \Blabla}, flags_for_host2( $mysync, '\Seen \Blabla', '' ), 'flags_for_host2: filterflags = 1 \Seen \Blabla among "" => \Seen \Blabla' ) ; + + + note( 'Leaving tests_flags_for_host2()' ) ; + return ; } -sub tests_compare_lists { - note( 'Entering tests_compare_lists()' ) ; - my $empty_list_ref = []; - ok( 0 == compare_lists() , 'compare_lists, no args'); - ok( 0 == compare_lists(undef) , 'compare_lists, undef = nothing'); - ok( 0 == compare_lists(undef, undef) , 'compare_lists, undef = undef'); - ok($MINUS_ONE == compare_lists(undef , []) , 'compare_lists, undef < []'); - ok($MINUS_ONE == compare_lists(undef , [1]) , 'compare_lists, undef < [1]'); - ok($MINUS_ONE == compare_lists(undef , [0]) , 'compare_lists, undef < [0]'); - ok(+1 == compare_lists([]) , 'compare_lists, [] > nothing'); - ok(+1 == compare_lists([], undef) , 'compare_lists, [] > undef'); - ok( 0 == compare_lists([] , []) , 'compare_lists, [] = []'); - ok($MINUS_ONE == compare_lists([] , [1]) , 'compare_lists, [] < [1]'); - ok(+1 == compare_lists([1] , []) , 'compare_lists, [1] > []'); +sub flags_for_host2 +{ + my $mysync = shift ; + my $h1_flags = shift ; + my $permanentflags2 = shift ; + if ( ! all_defined( $mysync, $h1_flags ) ) { return ; } ; - ok( 0 == compare_lists([1], 1 ) , 'compare_lists, [1] = 1 ') ; - ok( 0 == compare_lists( 1 , [1]) , 'compare_lists, 1 = [1]') ; - ok( 0 == compare_lists( 1 , 1 ) , 'compare_lists, 1 = 1 ') ; - ok($MINUS_ONE == compare_lists( 0 , 1 ) , 'compare_lists, 0 < 1 ') ; - ok($MINUS_ONE == compare_lists($MINUS_ONE , 0 ) , 'compare_lists, -1 < 0 ') ; - ok($MINUS_ONE == compare_lists( 1 , 2 ) , 'compare_lists, 1 < 2 ') ; - ok(+1 == compare_lists( 2 , 1 ) , 'compare_lists, 2 > 1 ') ; + # RFC 2060: This flag can not be altered by any client + $h1_flags =~ s@\\Recent\s?@@xgi ; + + my $h1_flags_re ; + if ( $mysync->{ regexflag } and defined( $h1_flags_re = regexflags( $mysync, $h1_flags ) ) ) { + $h1_flags = $h1_flags_re ; + } + if ( $mysync->{ flagscase } ) + { + $h1_flags = flagscase( $h1_flags ) ; + } - ok( 0 == compare_lists([1,2], [1,2]) , 'compare_lists, [1,2] = [1,2]' ) ; - ok($MINUS_ONE == compare_lists([1], [1,2]) , 'compare_lists, [1] < [1,2]' ) ; - ok(+1 == compare_lists([2], [1,2]) , 'compare_lists, [2] > [1,2]' ) ; - ok($MINUS_ONE == compare_lists([1], [1,1]) , 'compare_lists, [1] < [1,1]' ) ; - ok(+1 == compare_lists([1, 1], [1]) , 'compare_lists, [1, 1] > [1]' ) ; - ok( 0 == compare_lists([1 .. $NUMBER_20_000] , [1 .. $NUMBER_20_000]) - , 'compare_lists, [1..20_000] = [1..20_000]' ) ; - ok($MINUS_ONE == compare_lists([1], [2]) , 'compare_lists, [1] < [2]') ; - ok( 0 == compare_lists([2], [2]) , 'compare_lists, [0] = [2]') ; - ok(+1 == compare_lists([2], [1]) , 'compare_lists, [2] > [1]') ; + if ( $permanentflags2 and $mysync->{ filterflags } ) + { + $h1_flags = flags_filter( $h1_flags, $permanentflags2 ) ; + } - ok($MINUS_ONE == compare_lists(['a'], ['b']) , 'compare_lists, ["a"] < ["b"]') ; - ok( 0 == compare_lists(['a'], ['a']) , 'compare_lists, ["a"] = ["a"]') ; - ok( 0 == compare_lists(['ab'], ['ab']) , 'compare_lists, ["ab"] = ["ab"]') ; - ok(+1 == compare_lists(['b'], ['a']) , 'compare_lists, ["b"] > ["a"]') ; - ok($MINUS_ONE == compare_lists(['a'], ['aa']) , 'compare_lists, ["a"] < ["aa"]') ; - ok($MINUS_ONE == compare_lists(['a'], ['a', 'a']), 'compare_lists, ["a"] < ["a", "a"]') ; - ok( 0 == compare_lists([split q{ }, 'a b' ], ['a', 'b']), 'compare_lists, split') ; - ok( 0 == compare_lists([sort split q{ }, 'b a' ], ['a', 'b']), 'compare_lists, sort split') ; + return( $h1_flags ) ; +} + + + +sub ucsecond +{ + my $string = shift ; + my $output ; + + return( $string ) if ( 1 >= length $string ) ; + + $output = ( substr( $string, 0, 1) ) . ( uc substr $string, 1, 1 ) . ( substr $string, 2 ) ; + #myprint( "UUU $string -> $output\n" ) ; + return( $output ) ; +} - note( 'Leaving tests_compare_lists()' ) ; + +sub tests_ucsecond +{ + note( 'Entering tests_ucsecond()' ) ; + + ok( 'aBcde' eq ucsecond( 'abcde' ), 'ucsecond: abcde -> aBcde' ) ; + ok( 'ABCDE' eq ucsecond( 'ABCDE' ), 'ucsecond: ABCDE -> ABCDE' ) ; + ok( 'ABCDE' eq ucsecond( 'AbCDE' ), 'ucsecond: AbCDE -> ABCDE' ) ; + ok( 'ABCde' eq ucsecond( 'AbCde' ), 'ucsecond: AbCde -> ABCde' ) ; + ok( 'A' eq ucsecond( 'A' ), 'ucsecond: A -> A' ) ; + ok( 'AB' eq ucsecond( 'Ab' ), 'ucsecond: Ab -> AB' ) ; + ok( '\B' eq ucsecond( '\b' ), 'ucsecond: \b -> \B' ) ; + ok( '\Bcde' eq ucsecond( '\bcde' ), 'ucsecond: \bcde -> \Bcde' ) ; + + note( 'Leaving tests_ucsecond()' ) ; return ; } -sub guess_prefix { - my @foldernames = @_ ; +sub select_msgs +{ + my ( $imap, $msgs_all_hash_ref, $search_cmd, $abletosearch, $folder ) = @_ ; + my ( @msgs ) ; - my $prefix_guessed = q{} ; - foreach my $folder ( @foldernames ) { - next if ( $folder =~ m{^INBOX$}xi ) ; # no guessing from INBOX - if ( $folder !~ m{^INBOX}xi ) { - $prefix_guessed = q{} ; # prefix empty guessed - last ; - } - if ( $folder =~ m{^(INBOX(?:\.|\/))}xi ) { - $prefix_guessed = $1 ; # prefix Inbox/ or INBOX. guessed - } + if ( $abletosearch ) { + @msgs = select_msgs_by_search( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ; + }else{ + @msgs = select_msgs_by_fetch( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ; } - return( $prefix_guessed ) ; + return( @msgs ) ; + } -sub tests_guess_prefix { - note( 'Entering tests_guess_prefix()' ) ; +sub select_msgs_by_search +{ + my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ; + my ( @msgs, @msgs_all ) ; - is( guess_prefix( ), q{}, 'guess_prefix: no args => empty string' ) ; - is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ; - is( q{} , guess_prefix( 'Inbox' ), 'guess_prefix: Inbox alone' ) ; - is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ; - is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk' ), 'guess_prefix: INBOX INBOX/Junk' ) ; - is( 'INBOX.' , guess_prefix( 'INBOX', 'INBOX.Junk' ), 'guess_prefix: INBOX INBOX.Junk' ) ; - is( 'Inbox/' , guess_prefix( 'Inbox', 'Inbox/Junk' ), 'guess_prefix: Inbox Inbox/Junk' ) ; - is( 'Inbox.' , guess_prefix( 'Inbox', 'Inbox.Junk' ), 'guess_prefix: Inbox Inbox.Junk' ) ; - is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr' ) ; - is( q{} , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr', 'zzz' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr zzz' ) ; - is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ; - is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ; + # Need to have the whole list in msgs_all_hash_ref + # without calling messages() several times. + # Need all messages list to avoid deleting useful cache part + # in case of --search or --minage or --maxage - note( 'Leaving tests_guess_prefix()' ) ; - return ; -} + if ( ( defined $msgs_all_hash_ref and $usecache ) + or ( not defined $maxage and not defined $minage and not defined $search_cmd ) + ) { -sub get_prefix { - my( $imap, $prefix_in, $prefix_opt, $Side, $folders_ref ) = @_ ; - my( $prefix_out, $prefix_guessed ) ; + $debugdev and myprint( "Calling messages()\n" ) ; + @msgs_all = $imap->messages( ) ; - ( $debug or $sync->{debugfolders} ) and myprint( "$Side: Getting prefix\n" ) ; - $prefix_guessed = guess_prefix( @{ $folders_ref } ) ; - myprint( "$Side: guessing prefix from folder listing: [$prefix_guessed]\n" ) ; - ( $debug or $sync->{debugfolders} ) and myprint( "$Side: Calling namespace capability\n" ) ; - if ( $imap->has_capability( 'namespace' ) ) { - my $r_namespace = $imap->namespace( ) ; - $prefix_out = $r_namespace->[0][0][0] ; - myprint( "$Side: prefix given by NAMESPACE: [$prefix_out]\n" ) ; - if ( defined $prefix_in ) { - myprint( "$Side: but using [$prefix_in] given by $prefix_opt\n" ) ; - $prefix_out = $prefix_in ; - return( $prefix_out ) ; - }else{ - # all good - return( $prefix_out ) ; + return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ; + + if ( defined $msgs_all_hash_ref ) { + @{ $msgs_all_hash_ref }{ @msgs_all } = () ; } - } - else{ - if ( defined $prefix_in ) { - myprint( "$Side: using [$prefix_in] given by $prefix_opt\n" ) ; - $prefix_out = $prefix_in ; - return( $prefix_out ) ; - }else{ - myprint( - "$Side: No NAMESPACE capability so using guessed prefix [$prefix_guessed]\n", - help_to_guess_prefix( $imap, $prefix_opt ) ) ; - return( $prefix_guessed ) ; + # return all messages + if ( not defined $maxage and not defined $minage and not defined $search_cmd ) { + return( @msgs_all ) ; } } - return ; + + if ( defined $search_cmd ) { + @msgs = $imap->search( $search_cmd ) ; + return( @msgs ) ; + } + + # we are here only if $maxage or $minage is defined + @msgs = select_msgs_by_age( $imap ) ; + return( @msgs ); } -sub guess_separator { - my @foldernames = @_ ; +sub select_msgs_by_fetch +{ + my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ; + my ( @msgs, @msgs_all, %fetch ) ; - #return( undef ) unless ( @foldernames ) ; + # Need to have the whole list in msgs_all_hash_ref + # without calling messages() several times. + # Need all messages list to avoid deleting useful cache part + # in case of --search or --minage or --maxage - my $sep_guessed ; - my %counter ; - foreach my $folder ( @foldernames ) { - $counter{'/'}++ while ( $folder =~ m{/}xg ) ; # count / - $counter{'.'}++ while ( $folder =~ m{\.}xg ) ; # count . - $counter{'\\\\'}++ while ( $folder =~ m{(\\){2}}xg ) ; # count \\ - $counter{'\\'}++ while ( $folder =~ m{[^\\](\\){1}(?=[^\\])}xg ) ; # count \ - } - my @race_sorted = sort { $counter{ $b } <=> $counter{ $a } } keys %counter ; - $debug and myprint( "@foldernames\n@race_sorted\n", %counter, "\n" ) ; - $sep_guessed = shift @race_sorted || $LAST_RESSORT_SEPARATOR ; # / when nothing found. - return( $sep_guessed ) ; -} -sub tests_guess_separator { - note( 'Entering tests_guess_separator()' ) ; + $debugdev and myprint( "Calling fetch_hash()\n" ) ; + my $fetch_hash_uids = $fetch_hash_set || "1:*" ; + %fetch = %{$imap->fetch_hash( $fetch_hash_uids, 'INTERNALDATE' ) } ; - ok( '/' eq guess_separator( ), 'guess_separator: no args' ) ; - ok( '/' eq guess_separator( 'abcd' ), 'guess_separator: abcd' ) ; - ok( '/' eq guess_separator( 'a/b/c.d' ), 'guess_separator: a/b/c.d' ) ; - ok( '.' eq guess_separator( 'a.b/c.d' ), 'guess_separator: a.b/c.d' ) ; - ok( '\\\\' eq guess_separator( 'a\\\\b\\\\c.c\\\\d/e/f' ), 'guess_separator: a\\\\b\\\\c.c\\\\d/e/f' ) ; - ok( '\\' eq guess_separator( 'a\\b\\c.c\\d/e/f' ), 'guess_separator: a\\b\\c.c\\d/e/f' ) ; - ok( '\\' eq guess_separator( 'a\\b' ), 'guess_separator: a\\b' ) ; - ok( '\\' eq guess_separator( 'a\\b\\c' ), 'guess_separator: a\\b\\c' ) ; + @msgs_all = sort { $a <=> $b } keys %fetch ; + $debugdev and myprint( "Done fetch_hash()\n" ) ; - note( 'Leaving tests_guess_separator()' ) ; - return ; -} + return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ; -sub get_separator { - my( $imap, $sep_in, $sep_opt, $Side, $folders_ref ) = @_ ; - my( $sep_out, $sep_guessed ) ; + if ( defined $msgs_all_hash_ref ) { + @{ $msgs_all_hash_ref }{ @msgs_all } = () ; + } + # return all messages + if ( not defined $maxage and not defined $minage and not defined $search_cmd ) { + return( @msgs_all ) ; + } - ( $debug or $sync->{debugfolders} ) and myprint( "$Side: Getting separator\n" ) ; - $sep_guessed = guess_separator( @{ $folders_ref } ) ; - myprint( "$Side: guessing separator from folder listing: [$sep_guessed]\n" ) ; + if ( defined $search_cmd ) { + myprint( "Warning: strange to see --search with --noabletosearch, an error can happen\n" ) ; + @msgs = $imap->search( $search_cmd ) ; + return( @msgs ) ; + } - ( $debug or $sync->{debugfolders} ) and myprint( "$Side: calling namespace capability\n" ) ; - if ( $imap->has_capability( 'namespace' ) ) { - $sep_out = $imap->separator( ) ; - if ( defined $sep_out ) { - myprint( "$Side: separator given by NAMESPACE: [$sep_out]\n" ) ; - if ( defined $sep_in ) { - myprint( "$Side: but using [$sep_in] given by $sep_opt\n" ) ; - $sep_out = $sep_in ; - return( $sep_out ) ; - }else{ - return( $sep_out ) ; - } - }else{ - if ( defined $sep_in ) { - myprint( "$Side: NAMESPACE request failed but using [$sep_in] given by $sep_opt\n" ) ; - $sep_out = $sep_in ; - return( $sep_out ) ; - }else{ - myprint( - "$Side: NAMESPACE request failed so using guessed separator [$sep_guessed]\n", - help_to_guess_sep( $imap, $sep_opt ) ) ; - return( $sep_guessed ) ; - } + # we are here only if $maxage or $minage is defined + my( @max, @min, $maxage_epoch, $minage_epoch ) ; + if ( defined $maxage ) { $maxage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ; } + if ( defined $minage ) { $minage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ; } + foreach my $msg ( @msgs_all ) { + my $idate = $fetch{ $msg }->{'INTERNALDATE'} ; + #myprint( "$idate\n" ) ; + if ( defined $maxage and ( epoch( $idate ) >= $maxage_epoch ) ) { + push @max, $msg ; } - } - else{ - if ( defined $sep_in ) { - myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n" ) ; - $sep_out = $sep_in ; - return( $sep_out ) ; - }else{ - myprint( - "$Side: No NAMESPACE capability, so using guessed separator [$sep_guessed]\n", - help_to_guess_sep( $imap, $sep_opt ) ) ; - return( $sep_guessed ) ; + if ( defined $minage and ( epoch( $idate ) <= $minage_epoch ) ) { + push @min, $msg ; } } - return ; + @msgs = msgs_from_maxmin( \@max, \@min ) ; + return( @msgs ) ; } -sub help_to_guess_sep { - my( $imap, $sep_opt ) = @_ ; - - my $help_to_guess_sep = "You can set the separator character with the $sep_opt option,\n" - . "the complete listing of folders may help you to find it\n" - . folders_list_to_help( $imap ) ; - - return( $help_to_guess_sep ) ; -} +sub select_msgs_by_age +{ + my( $imap ) = @_ ; -sub help_to_guess_prefix { - my( $imap, $prefix_opt ) = @_ ; + my( @max, @min, @msgs, @inter, @union ) ; - my $help_to_guess_prefix = "You can set the prefix namespace with the $prefix_opt option,\n" - . "the folowing listing of folders may help you to find it:\n" - . folders_list_to_help( $imap ) ; + if ( defined $maxage ) { + @max = $imap->sentsince( $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ) ; + } + if ( defined $minage ) { + @min = $imap->sentbefore( $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ) ; + } - return( $help_to_guess_prefix ) ; + @msgs = msgs_from_maxmin( \@max, \@min ) ; + return( @msgs ) ; } +sub msgs_from_maxmin +{ + my( $max_ref, $min_ref ) = @_ ; + my( @max, @min, @msgs, @inter, @union ) ; -sub folders_list_to_help { - my( $imap ) = shift ; - - my @folders = $imap->folders ; - my $listing = join q{}, map { "[$_]\n" } @folders ; - return( $listing ) ; -} + @max = @{ $max_ref } ; + @min = @{ $min_ref } ; + SWITCH: { + if ( not ( defined $minage or defined $maxage ) ) + { + return ; + } + unless( defined $minage ) { @msgs = @max ; last SWITCH } ; + unless( defined $maxage ) { @msgs = @min ; last SWITCH } ; + my ( %union, %inter ) ; + foreach my $m ( @min, @max ) { $union{ $m }++ && $inter{ $m }++ } + @inter = sort { $a <=> $b } keys %inter ; + @union = sort { $a <=> $b } keys %union ; + # normal case + if ( $minage <= $maxage ) { @msgs = @inter ; last SWITCH } ; + # just exclude messages between + if ( $minage > $maxage ) { @msgs = @union ; last SWITCH } ; + } + return( @msgs ) ; +} -sub tests_imap2_folder_name { - note( 'Entering tests_imap2_folder_name()' ) ; +sub tests_msgs_from_maxmin +{ + note( 'Entering tests_msgs_from_maxmin()' ) ; -$sync->{ h1_prefix } = $sync->{ h2_prefix } = q{} ; -$h1_sep = '/'; -$h2_sep = '.'; -$debug and myprint( <<"EOS" -prefix1: [$sync->{ h1_prefix }] -prefix2: [$sync->{ h2_prefix }] -sep1:[$h1_sep] -sep2:[$h2_sep] -EOS -) ; + my @msgs ; -$fixslash2 = 0 ; -ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string'); -ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla'); -ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam'); -ok('spam/spam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam'); -ok('spam.spam/spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam'); -ok('s pam.spam/sp am' eq imap2_folder_name('s pam/spam.sp am'), 'imap2_folder_name: s pam/spam.sp am'); - -$sync->{f1f2h}{ 'auto' } = 'moto' ; -ok( 'moto' eq imap2_folder_name( 'auto' ), 'imap2_folder_name: auto' ) ; -$sync->{f1f2h}{ 'auto/auto' } = 'moto x 2' ; -ok( 'moto x 2' eq imap2_folder_name( 'auto/auto' ), 'imap2_folder_name: auto/auto' ) ; - -@regextrans2 = ('s,/,X,g'); -ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string [s,/,X,g]'); -ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla [s,/,X,g]'); -ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam [s,/,X,g]'); -ok('spamXspam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam [s,/,X,g]'); -ok('spam.spamXspam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam [s,/,X,g]'); - -@regextrans2 = ( 's, ,_,g' ) ; -ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla [s, ,_,g]'); -ok('bla_bla' eq imap2_folder_name('bla bla'), 'imap2_folder_name: blabla [s, ,_,g]'); - -@regextrans2 = ( q{s,(.*),\U$1,} ) ; -ok( 'BLABLA' eq imap2_folder_name( 'blabla' ), q{imap2_folder_name: blabla [s,\U(.*)\E,$1,]} ) ; - -$fixslash2 = 1 ; -@regextrans2 = ( ) ; -ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string'); -ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla'); -ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam'); -ok('spam_spam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam -> spam_spam'); -ok('spam.spam_spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam_spam'); -ok('s pam.spam_spa m' eq imap2_folder_name('s pam/spam.spa m'), 'imap2_folder_name: s pam/spam.spa m -> s pam.spam_spa m'); - -$h1_sep = '.'; -$h2_sep = '/'; -ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string'); -ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla'); -ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam'); -ok('spam/spam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam -> spam/spam'); -ok('spam.spam/spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam'); - - - -$fixslash2 = 0 ; -$sync->{ h1_prefix } = q{ }; - -ok('spam.spam/spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam'); -ok('spam.spam/spam' eq imap2_folder_name(' spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam'); - -$h1_sep = '.' ; -$h2_sep = '/' ; -$sync->{ h1_prefix } = 'INBOX.' ; -$sync->{ h2_prefix } = q{} ; -@regextrans2 = ( q{s,(.*),\U$1,} ) ; -ok( 'BLABLA' eq imap2_folder_name( 'blabla' ), 'imap2_folder_name: blabla' ) ; -ok( 'TEST/TEST/TEST/TEST' eq imap2_folder_name( 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ; -@regextrans2 = ( q{s,(.*),\L$1,} ) ; -ok( 'test/test/test/test' eq imap2_folder_name( 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ; - - - note( 'Leaving tests_imap2_folder_name()' ) ; - return ; + # no maxage nor minage + @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ; + is_deeply( [ ], \@msgs , 'msgs_from_maxmin: no maxage nor minage => empty result' ) ; -} + # maxage alone + $maxage = $NUMBER_200 ; + @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ; + is_deeply( [ '1', '2' ], \@msgs , 'msgs_from_maxmin: maxage++' ) ; + # maxage > minage -> intersection + $minage = $NUMBER_100 ; + @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ; + is_deeply( [ '2' ], \@msgs , 'msgs_from_maxmin: -maxage++minage-' ) ; + # maxage < minage -> union + $minage = $NUMBER_300 ; + @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ; + is_deeply( [ '1', '2', '3' ], \@msgs, 'msgs_from_maxmin: ++maxage-minage++' ) ; -# Global variables to remove: -# $debug -# $sync -sub imap2_folder_name { - my $mysync = $sync ; # will be soon next line - #my $mysync = shift ; - my ( $h1_fold ) = shift ; - my ( $h2_fold ) ; - if ( $mysync->{f1f2h}{ $h1_fold } ) { - $h2_fold = $mysync->{f1f2h}{ $h1_fold } ; - ( $debug or $mysync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n" ) ; - return( $h2_fold ) ; - } - if ( $mysync->{f1f2auto}{ $h1_fold } ) { - $h2_fold = $mysync->{f1f2auto}{ $h1_fold } ; - ( $debug or $mysync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n" ) ; - return( $h2_fold ) ; - } + # minage alone + $maxage = undef ; + @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ; + is_deeply( [ '2', '3' ], \@msgs, 'msgs_from_maxmin: ++minage-' ) ; - $h2_fold = prefix_seperator_invertion( $mysync, $h1_fold ) ; - $h2_fold = regextrans2( $h2_fold ) ; - return( $h2_fold ) ; -} -sub tests_prefix_seperator_invertion { - undef $h1_sep; - undef $h2_sep ; - - is( undef, prefix_seperator_invertion( ), 'prefix_seperator_invertion: no args => undef' ) ; - is( q{}, prefix_seperator_invertion( undef, q{} ), 'prefix_seperator_invertion: empty string => empty string' ) ; - is( 'lalala', prefix_seperator_invertion( undef, 'lalala' ), 'prefix_seperator_invertion: lalala => lalala' ) ; - is( 'lal/ala', prefix_seperator_invertion( undef, 'lal/ala' ), 'prefix_seperator_invertion: lal/ala => lal/ala' ) ; - is( 'lal.ala', prefix_seperator_invertion( undef, 'lal.ala' ), 'prefix_seperator_invertion: lal.ala => lal.ala' ) ; - is( '////', prefix_seperator_invertion( undef, '////' ), 'prefix_seperator_invertion: //// => ////' ) ; - is( '.....', prefix_seperator_invertion( undef, '.....' ), 'prefix_seperator_invertion: ..... => .....' ) ; - - my $mysync = { - h1_prefix => '', - h2_prefix => '', - h1_sep => '/', - h2_sep => '/', - } ; - - is( q{}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: $mysync empty string => empty string' ) ; - is( 'lalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: $mysync lalala => lalala' ) ; - is( 'lal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: $mysync lal/ala => lal/ala' ) ; - is( 'lal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: $mysync lal.ala => lal.ala' ) ; - is( '////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: $mysync //// => ////' ) ; - is( '.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: $mysync ..... => .....' ) ; - - $mysync = { - h1_prefix => 'PPP', - h2_prefix => 'QQQ', - h1_sep => 's', - h2_sep => 't', - } ; - - is( q{QQQ}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: PPPQQQst empty string => QQQ' ) ; - is( 'QQQlalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: PPPQQQst lalala => QQQlalala' ) ; - is( 'QQQlal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: PPPQQQst lal/ala => QQQlal/ala' ) ; - is( 'QQQlal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: PPPQQQst lal.ala => QQQlal.ala' ) ; - is( 'QQQ////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: PPPQQQst //// => QQQ////' ) ; - is( 'QQQ.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: PPPQQQst ..... => QQQ.....' ) ; - - is( 'QQQPlalala', prefix_seperator_invertion( $mysync, 'PPPPlalala' ), 'prefix_seperator_invertion: PPPQQQst PPPPlalala => QQQPlalala' ) ; - is( 'QQQ', prefix_seperator_invertion( $mysync, 'PPP' ), 'prefix_seperator_invertion: PPPQQQst PPP => QQQ' ) ; - is( 'QQQttt', prefix_seperator_invertion( $mysync, 'sss' ), 'prefix_seperator_invertion: PPPQQQst sss => QQQttt' ) ; - is( 'QQQt', prefix_seperator_invertion( $mysync, 's' ), 'prefix_seperator_invertion: PPPQQQst s => QQQt' ) ; - is( 'QQQtAAAtBBB', prefix_seperator_invertion( $mysync, 'PPPsAAAsBBB' ), 'prefix_seperator_invertion: PPPQQQst PPPsAAAsBBB => QQQtAAAtBBB' ) ; - + note( 'Leaving tests_msgs_from_maxmin()' ) ; return ; } -# Global variables to remove: -# $h1_sep -# $h2_sep -# $debug - -sub prefix_seperator_invertion { - my $mysync = shift ; - my $h1_fold = shift ; - my $h2_fold ; +sub tests_info_date_from_uid +{ + note( 'Entering tests_info_date_from_uid()' ) ; + note( 'Leaving tests_info_date_from_uid()' ) ; - if ( not defined $h1_fold ) { return ; } - - my $my_h1_prefix = $mysync->{ h1_prefix } || q{} ; - my $my_h2_prefix = $mysync->{ h2_prefix } || q{} ; - my $my_h1_sep = $h1_sep || $mysync->{ h1_sep } || '/' ; - my $my_h2_sep = $h2_sep || $mysync->{ h2_sep } || '/' ; - - # first we remove the prefix - $h1_fold =~ s/^\Q$my_h1_prefix\E//x ; - ( $debug or $mysync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n" ) ; - $h2_fold = separator_invert( $h1_fold, $my_h1_sep, $my_h2_sep ) ; - ( $debug or $mysync->{debugfolders} ) and myprint( "inverted separators: [$h2_fold]\n" ) ; - # Adding the prefix supplied by namespace or the --prefix2 option - $h2_fold = $my_h2_prefix . $h2_fold - unless( ( $my_h2_prefix eq 'INBOX' . $my_h2_sep ) and ( $h2_fold =~ m/^INBOX$/xi ) ) ; - ( $debug or $mysync->{debugfolders} ) and myprint( "added host2 prefix: [$h2_fold]\n" ) ; - return( $h2_fold ) ; + return ; } -sub tests_separator_invert { - note( 'Entering tests_separator_invert()' ) ; +sub info_date_from_uid +{ - $fixslash2 = 0 ; - ok( not( defined separator_invert( ) ), 'separator_invert: no args' ) ; - ok( not( defined separator_invert( q{} ) ), 'separator_invert: not enough args' ) ; - ok( not( defined separator_invert( q{}, q{} ) ), 'separator_invert: not enough args' ) ; + #my $first_uid = $msgs_all[ 0 ] ; + #my $first_idate = $fetch{ $first_uid }->{'INTERNALDATE'} ; + #my $first_epoch = epoch( $first_idate ) ; + #my $first_days = ( $timestart_int - $first_epoch ) / $NB_SECONDS_IN_A_DAY ; + #myprint( "\nOldest msg has UID $first_uid INTERNALDATE $first_idate EPOCH $first_epoch DAYS AGO $first_days\n" ) ; +} - ok( q{} eq separator_invert( q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ; - ok( 'lalala' eq separator_invert( 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ; - ok( 'lalala' eq separator_invert( 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ; - ok( 'lal/ala' eq separator_invert( 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ; - ok( 'lal.ala' eq separator_invert( 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ; - ok( 'lal/ala' eq separator_invert( 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ; - ok( 'la.l/ala' eq separator_invert( 'la/l.ala', '.', '/' ), 'separator_invert: separators ./' ) ; - ok( 'l/al.ala' eq separator_invert( 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ; - $fixslash2 = 1 ; - ok( 'l_al.ala' eq separator_invert( 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ; +sub lastuid +{ + my $imap = shift ; + my $folder = shift ; + my $lastuid_guess = shift ; + my $lastuid ; - note( 'Leaving tests_separator_invert()' ) ; - return ; -} + # rfc3501: The only reliable way to identify recent messages is to + # look at message flags to see which have the \Recent flag + # set, or to do a SEARCH RECENT. + # SEARCH RECENT doesn't work this way on courrier. -# Global variables to remove: -# $fixslash2 -sub separator_invert { - my( $h1_fold, $h1_separator, $h2_separator ) = @_ ; + my @recent_messages ; + # SEARCH RECENT for each transfer can be expensive with a big folder + # Call commented for now + #@recent_messages = $imap->recent( ) ; + #myprint( "Recent: @recent_messages\n" ) ; - return( undef ) if ( not defined $h1_fold or not defined $h1_separator or not defined $h2_separator ) ; - # The separator we hope we'll never encounter: 00000000 == 0x00 - my $o_sep = "\000" ; + my $max_recent ; + $max_recent = max( @recent_messages ) ; - my $h2_fold = $h1_fold ; - $h2_fold =~ s,\Q$h2_separator,$o_sep,xg ; - $h2_fold =~ s,\Q$h1_separator,$h2_separator,xg ; - $h2_fold =~ s,\Q$o_sep,$h1_separator,xg ; - $h2_fold =~ s,/,_,xg if( $fixslash2 and '/' ne $h2_separator and '/' eq $h1_separator ) ; - return( $h2_fold ) ; + if ( defined $max_recent and ($lastuid_guess <= $max_recent ) ) { + $lastuid = $max_recent ; + }else{ + $lastuid = $lastuid_guess + } + return( $lastuid ) ; } +sub size_filtered +{ + my( $h1_size, $h1_msg, $h1_fold, $h2_fold ) = @_ ; -sub regextrans2 { - my( $h2_fold ) = @_ ; - # Transforming the folder name by the --regextrans2 option(s) - foreach my $regextrans2 ( @regextrans2 ) { - my $h2_fold_before = $h2_fold ; - my $ret = eval "\$h2_fold =~ $regextrans2 ; 1 " ; - ( $debug or $sync->{debugfolders} ) and myprint( "[$h2_fold_before] -> [$h2_fold] using regextrans2 [$regextrans2]\n" ) ; - if ( not ( defined $ret ) or $EVAL_ERROR ) { - die_clean( "error: eval regextrans2 '$regextrans2': $EVAL_ERROR\n" ) ; - } + $h1_size = 0 if ( ! $h1_size ) ; # null if empty or undef + if ( defined $sync->{ maxsize } and $h1_size > $sync->{ maxsize } ) { + myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $sync->{ maxsize } bytes)\n" ) ; + $sync->{ total_bytes_skipped } += $h1_size; + $sync->{ nb_msg_skipped } += 1; + return( 1 ) ; } - return( $h2_fold ) ; + if ( defined $minsize and $h1_size <= $minsize ) { + myprint( "msg $h1_fold/$h1_msg skipped ($h1_size smaller than minsize $minsize bytes)\n" ) ; + $sync->{ total_bytes_skipped } += $h1_size; + $sync->{ nb_msg_skipped } += 1; + return( 1 ) ; + } + return( 0 ) ; } +sub message_exists +{ + my( $imap, $msg ) = @_ ; + return( 1 ) if not $imap->Uid( ) ; -sub tests_decompose_regex { - note( 'Entering tests_decompose_regex()' ) ; + my $search_uid ; + ( $search_uid ) = $imap->search( "UID $msg" ) ; + #myprint( "$search ? $msg\n" ) ; + return( 1 ) if ( $search_uid eq $msg ) ; + return( 0 ) ; +} - ok( 1, 'decompose_regex 1' ) ; - ok( 0 == compare_lists( [ q{}, q{} ], [ decompose_regex( q{} ) ] ), 'decompose_regex empty string' ) ; - ok( 0 == compare_lists( [ '.*', 'lala' ], [ decompose_regex( 's/.*/lala/' ) ] ), 'decompose_regex s/.*/lala/' ) ; - note( 'Leaving tests_decompose_regex()' ) ; +# Globals +# $sync->{ total_bytes_skipped } +# $sync->{ nb_msg_skipped } +# $mysync->{ h1_nb_msg_processed } +sub stats_update_skip_message +{ + my $mysync = shift ; # to be used + my $h1_size = shift ; + + $mysync->{ total_bytes_skipped } += $h1_size ; + $mysync->{ nb_msg_skipped } += 1 ; + $mysync->{ h1_nb_msg_processed } +=1 ; return ; } -sub decompose_regex { - my $regex = shift ; - my( $left_part, $right_part ) ; +sub copy_message +{ + # copy - ( $left_part, $right_part ) = $regex =~ m{^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/}x; - return( q{}, q{} ) if not $left_part ; - return( $left_part, $right_part ) ; -} + my ( $mysync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) = @_ ; + ( $mysync->{ debug } or $mysync->{dry} ) + and myprint( "msg $h1_fold/$h1_msg copying to $h2_fold $mysync->{dry_message} " . eta( $mysync ) . "\n" ) ; + if ( $mysync->{dry1} ) + { + $mysync->{ h1_nb_msg_processed } +=1 ; + $nb_msg_skipped_dry_mode += 1 ; + return ; + } -sub foldersizes { + my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} || 0 ; + my $h1_flags = $h1_fir_ref->{$h1_msg}->{'FLAGS'} || q{} ; + my $h1_idate = $h1_fir_ref->{$h1_msg}->{'INTERNALDATE'} || q{} ; - my ( $side, $imap, $search_cmd, $abletosearch, @folders ) = @_ ; - my $total_size = 0 ; - my $total_nb = 0 ; - my $biggest_in_all = 0 ; - my $nb_folders = scalar @folders ; - my $ct_folders = 0 ; # folder counter. - myprint( "++++ Calculating sizes of $nb_folders folders on $side\n" ) ; - foreach my $folder ( @folders ) { - my $stot = 0 ; - my $nb_msgs = 0 ; - $ct_folders++ ; - myprintf( "$side folder %7s %-35s", "$ct_folders/$nb_folders", jux_utf8( $folder ) ) ; - if ( 'Host2' eq $side and not exists $h2_folders_all_UPPER{ uc $folder } ) { - myprint( " does not exist yet\n") ; - next ; - } - if ( 'Host1' eq $side and not exists $h1_folders_all{ $folder } ) { - myprint( " does not exist\n" ) ; - next ; - } + if ( size_filtered( $h1_size, $h1_msg, $h1_fold, $h2_fold ) ) { + $mysync->{ h1_nb_msg_processed } +=1 ; + return ; + } - last if $imap->IsUnconnected( ) ; - # FTGate is RFC buggy with EXAMINE it does not act as SELECT - #unless ( $imap->examine( $folder ) ) { - unless ( $imap->select( $folder ) ) { - my $error = join q{}, - "$side Folder $folder: Could not select: ", - $imap->LastError, "\n" ; - errors_incr( $sync, $error ) ; - next ; - } - last if $imap->IsUnconnected( ) ; + debugsleep( $mysync ) ; + myprint( "- msg $h1_fold/$h1_msg S[$h1_size] F[$h1_flags] I[$h1_idate] has RFC822.SIZE null!\n" ) if ( ! $h1_size ) ; - my $hash_ref = { } ; - my @msgs = select_msgs( $imap, undef, $search_cmd, $abletosearch, $folder ) ; - $nb_msgs = scalar @msgs ; - my $biggest_in_folder = 0 ; - @{ $hash_ref }{ @msgs } = ( undef ) if @msgs ; + if ( $checkmessageexists and not message_exists( $mysync->{imap1}, $h1_msg ) ) { + stats_update_skip_message( $mysync, $h1_size ) ; + return ; + } + myprint( debugmemory( $mysync, " at C1" ) ) ; - last if $imap->IsUnconnected( ) ; - if ( $nb_msgs > 0 and @msgs ) { - if ( $abletosearch ) { - if ( ! $imap->fetch_hash( \@msgs, 'RFC822.SIZE', $hash_ref) ) { - my $error = "$side failure with fetch_hash: $EVAL_ERROR" ; - errors_incr( $sync, $error ) ; - return ; - } - }else{ - my $uidnext = $imap->uidnext( $folder ) || $uidnext_default ; - my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ; - if ( ! $imap->fetch_hash( $fetch_hash_uids, 'RFC822.SIZE', $hash_ref ) ) { - my $error = "$side failure with fetch_hash: $EVAL_ERROR" ; - errors_incr( $sync, $error ) ; - return ; - } - } - for ( keys %{ $hash_ref } ) { - my $size = $hash_ref->{ $_ }->{ 'RFC822.SIZE' } ; - $stot += $size ; - $biggest_in_folder = max( $biggest_in_folder, $size ) ; - } - } + my ( $string, $string_len ) ; + ( $string_len ) = message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ; - myprintf( ' Size: %9s', $stot ) ; - myprintf( ' Messages: %5s', $nb_msgs ) ; - myprintf( " Biggest: %9s\n", $biggest_in_folder ) ; - $total_size += $stot ; - $total_nb += $nb_msgs ; - $biggest_in_all = max( $biggest_in_all, $biggest_in_folder ) ; + myprint( debugmemory( $mysync, " at C2" ) ) ; + + # not defined or empty $string + if ( ( not $string ) or ( not $string_len ) ) { + myprint( "- msg $h1_fold/$h1_msg skipped.\n" ) ; + stats_update_skip_message( $mysync, $h1_size ) ; + return ; } - myprintf( "%s Nb folders: %11s folders\n", $side, $nb_folders ) ; - myprintf( "%s Nb messages: %11s messages\n", $side, $total_nb ) ; - myprintf( "%s Total size: %11s bytes (%s)\n", $side, $total_size, bytes_display_string( $total_size ) ) ; - myprintf( "%s Biggest message: %11s bytes (%s)\n", $side, $biggest_in_all, bytes_display_string( $biggest_in_all ) ) ; - myprintf( "%s Time spent: %11.1f seconds\n", $side, timenext( ) ) ; - return( $total_nb, $total_size ) ; -} -sub timenext { - my ( $timenow, $timediff ) ; - # $timebefore is global, beurk ! - $timenow = time ; - $timediff = $timenow - $timebefore ; - $timebefore = $timenow ; - return( $timediff ) ; -} + # Lines too long (or not enough) => do no copy or fix + if ( ( defined $maxlinelength ) or ( defined $minmaxlinelength ) ) { + $string = linelengthstuff( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) ; + if ( not defined $string ) { + stats_update_skip_message( $mysync, $h1_size ) ; + return ; + } + } -sub timesince { - my $timeinit = shift || 0 ; - my ( $timenow, $timediff ) ; - $timenow = time ; - $timediff = $timenow - $timeinit ; - # Often used in a division so no 0 - return( max( 1, $timediff) ) ; -} + my $h1_date = date_for_host2( $h1_msg, $h1_idate ) ; + + ( $mysync->{ debug } or $debugflags ) and + myprint( "Host1: flags init msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ; + + $h1_flags = flags_for_host2( $mysync, $h1_flags, $permanentflags2 ) ; + + ( $mysync->{ debug } or $debugflags ) and + myprint( "Host1: flags filt msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ; + + $h1_date = undef if ( $h1_date eq q{} ) ; + my $new_id = append_message_on_host2( $mysync, \$string, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) ; -sub tests_flags_regex { - note( 'Entering tests_flags_regex()' ) ; + if ( $new_id and $syncflagsaftercopy ) { + sync_flags_after_copy( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id, $permanentflags2 ) ; + } - ok( q{} eq flags_regex(q{} ), 'flags_regex, null string q{}' ) ; - ok( q{\Seen NonJunk $Spam} eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, nothing to do} ) ; + myprint( debugmemory( $mysync, " at C3" ) ) ; - @regexflag = ('I am BAD' ) ; - ok( not ( defined flags_regex( q{} ) ), 'flags_regex, bad regex' ) ; + return $new_id ; +} - @regexflag = ( 's/NonJunk//g' ) ; - ok( q{\Seen $Spam} eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, remove NonJunk: 's/NonJunk//g'} ) ; - @regexflag = ( q's/\$Spam//g' ) ; - ok( q{\Seen NonJunk } eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, remove $Spam: 's/\$Spam//g'} ) ; - @regexflag = ( 's/\\\\Seen//g' ) ; - ok( q{ NonJunk $Spam} eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, remove \Seen: 's/\\\\\\\\Seen//g'} ) ; +sub linelengthstuff +{ + my( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) = @_ ; + my $maxlinelength_string = max_line_length( $string ) ; + $debugmaxlinelength and myprint( "msg $h1_fold/$h1_msg maxlinelength: $maxlinelength_string\n" ) ; - @regexflag = ( 's/(\s|^)[^\\\\]\w+//g' ) ; - ok( q{\Seen \Middle \End} eq flags_regex( q{\Seen NonJunk \Middle $Spam \End} ), q{flags_regex: only \word among \Seen NonJunk \Middle $Spam \End} ) ; - ok( q{ \Seen \Middle \End1} eq flags_regex( q{Begin \Seen NonJunk \Middle $Spam \End1 End} ), - q{flags_regex: only \word among Begin \Seen NonJunk \Middle $Spam \End1 End} ) ; + if ( ( defined $minmaxlinelength ) and ( $maxlinelength_string <= $minmaxlinelength ) ) { + my $subject = subject( $string ) ; + $debugdev and myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] " + . "(Subject:[$subject]) (max line length under minmaxlinelength $minmaxlinelength bytes)\n" ) ; + return ; + } - @regexflag = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g} ) ; - ok( 'Keep1 Keep2 ReB' eq flags_regex('ReA Keep1 REM Keep2 ReB'), 'Keep only regex' ) ; + if ( ( defined $maxlinelength ) and ( $maxlinelength_string > $maxlinelength ) ) { + my $subject = subject( $string ) ; + if ( $maxlinelengthcmd ) { + $string = pipemess( $string, $maxlinelengthcmd ) ; + # string undef means something was bad. + if ( not ( defined $string ) ) { + myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] " + . "(Subject:[$subject]) could not be successfully transformed by --maxlinelengthcmd option\n" ) ; + return ; + }else{ + return $string ; + } + } + myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] " + . "(Subject:[$subject]) (line length exceeds maxlinelength $maxlinelength bytes)\n" ) ; + return ; + } + return $string ; +} - ok( 'Keep1 Keep2 ' eq flags_regex( 'REM REM Keep1 Keep2'), 'Keep only regex' ) ; - ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 REM REM Keep2'), 'Keep only regex' ) ; - ok( 'Keep1 Keep2 ' eq flags_regex( 'REM Keep1 REM REM Keep2'), 'Keep only regex' ) ; - ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2'), 'Keep only regex' ) ; - ok( 'Keep1 ' eq flags_regex( 'REM Keep1'), 'Keep only regex' ) ; - @regexflag = ( q{s/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g} ) ; - ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2 ReB'), 'Keep only regex' ) ; - ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2 REM REM REM'), 'Keep only regex' ) ; - ok( 'Keep2 ' eq flags_regex('Keep2 REM REM REM'), 'Keep only regex' ) ; +sub message_for_host2 +{ +# global variable list: +# @skipmess +# @regexmess +# @pipemess +# $debugcontent +# $debug +# +# API current +# +# at failure: +# * return nothing ( will then be undef or () ) +# * $string_ref content is undef or empty +# at success: +# * return string length ($string_ref content length) +# * $string_ref content filled with message - @regexflag = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g}, - 's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g' ) ; - ok( 'Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2 REM'), 'Keep only regex' ) ; - ok( 'Keep1 Keep2 ' eq flags_regex('Keep1 REM Keep2 REM'), 'Keep only regex' ) ; - ok( 'Keep1 Keep2 ' eq flags_regex('REM Keep1 Keep2 REM'), 'Keep only regex' ) ; - ok( 'Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2'), 'Keep only regex' ) ; - ok( 'Keep1 Keep2 Keep3 ' eq flags_regex('REM Keep1 REM Keep2 REM REM Keep3 REM'), 'Keep only regex' ) ; - ok( 'Keep1 ' eq flags_regex('REM REM Keep1 REM REM REM '), 'Keep only regex' ) ; - ok( 'Keep1 Keep3 ' eq flags_regex('RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 '), 'Keep only regex' ) ; - - @regexflag = ( 's/(.*)/$1 jrdH8u/' ) ; - ok('REM REM REM REM REM jrdH8u' eq flags_regex('REM REM REM REM REM'), q{Keep only regex 's/(.*)/\$1 jrdH8u/'} ) ; - @regexflag = ('s/jrdH8u *//'); - ok('REM REM REM REM REM ' eq flags_regex('REM REM REM REM REM jrdH8u'), q{Keep only regex s/jrdH8u *//} ) ; - - @regexflag = ( - 's/(.*)/$1 jrdH8u/', - 's/.*?(Keep1|Keep2|Keep3|jrdH8u)/$1 /g', - 's/(Keep1|Keep2|Keep3|jrdH8u) (?!(Keep1|Keep2|Keep3|jrdH8u)).*/$1 /g', - 's/jrdH8u *//' - ); +# API future +# +# + my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) = @_ ; - ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2 REM'), q{Keep only regex 'REM Keep1 REM Keep2 REM'} ) ; - ok('Keep1 Keep2 ' eq flags_regex('Keep1 REM Keep2 REM'), 'Keep only regex'); - ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 Keep2 REM'), 'Keep only regex'); - ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2'), 'Keep only regex'); - ok('Keep1 Keep2 Keep3 ' eq flags_regex('REM Keep1 REM Keep2 REM REM Keep3 REM'), 'Keep only regex'); - ok('Keep1 ' eq flags_regex('REM REM Keep1 REM REM REM '), 'Keep only regex'); - ok('Keep1 Keep3 ' eq flags_regex('RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 '), 'Keep only regex'); - ok(q{} eq flags_regex('REM REM REM REM REM'), 'Keep only regex'); - - @regexflag = ( - 's/(.*)/$1 jrdH8u/', - 's/.*?(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u)/$1 /g', - 's/(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u) (?!(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u)).*/$1 /g', - 's/jrdH8u *//' - ); + # abort when missing a parameter + if ( ( ! $mysync ) or ( ! $h1_msg ) or ( ! $h1_fold ) or ( ! defined $h1_size ) + or ( ! defined $h1_flags) or ( ! defined $h1_idate ) + or ( ! $h1_fir_ref) or ( ! $string_ref ) ) + { + return ; + } - ok('\\Deleted \\Answered ' - eq flags_regex('Blabla $Junk \\Deleted machin \\Answered truc'), 'Keep only regex: Exchange case' ) ; - ok( q{} eq flags_regex( q{} ), 'Keep only regex: Exchange case, null string' ) ; - ok( q{} - eq flags_regex('Blabla $Junk machin truc'), 'Keep only regex: Exchange case, no accepted flags' ) ; - ok( '\\Deleted \\Answered \\Draft \\Flagged ' - eq flags_regex('\\Deleted \\Answered \\Draft \\Flagged '), 'Keep only regex: Exchange case' ) ; + myprint( debugmemory( $mysync, " at M1" ) ) ; - @regexflag = ( - 's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg' - ); + my $string_ok = $mysync->{imap1}->message_to_file( $string_ref, $h1_msg ) ; - ok( '\\Deleted \\Answered ' - eq flags_regex('Blabla \$Junk \\Deleted machin \\Answered truc'), - 'Keep only regex: Exchange case (Phil)' ) ; + myprint( debugmemory( $mysync, " at M2" ) ) ; - ok( q{} eq flags_regex( q{} ), 'Keep only regex: Exchange case, null string (Phil)' ) ; + my $string_len = length_ref( $string_ref ) ; - ok( q{} - eq flags_regex('Blabla $Junk machin truc'), - 'Keep only regex: Exchange case, no accepted flags (Phil)' ) ; - ok('\\Deleted \\Answered \\Draft \\Flagged ' - eq flags_regex('\\Deleted \\Answered \\Draft \\Flagged '), - 'Keep only regex: Exchange case (Phil)' ) ; + unless ( defined $string_ok and $string_len ) { + # undef or 0 length + my $error = join q{}, + "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] could not be fetched: ", + $mysync->{imap1}->LastError || q{}, "\n" ; + errors_incr( $mysync, $error ) ; + $mysync->{ h1_nb_msg_processed } +=1 ; + return ; + } - note( 'Leaving tests_flags_regex()' ) ; - return ; -} + if ( @skipmess ) { + my $match = skipmess( ${ $string_ref } ) ; + # string undef means the eval regex was bad. + if ( not ( defined $match ) ) { + myprint( + "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]" + . " could not be skipped by --skipmess option, bad regex\n" ) ; + return ; + } + if ( $match ) { + my $subject = subject( ${ $string_ref } ) ; + myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]" + . " (Subject:[$subject]) skipped by --skipmess\n" ) ; + return ; + } + } -sub flags_regex { - my ( $h1_flags ) = @_ ; - foreach my $regexflag ( @regexflag ) { - my $h1_flags_orig = $h1_flags ; - $debugflags and myprint( "eval \$h1_flags =~ $regexflag\n" ) ; - my $ret = eval "\$h1_flags =~ $regexflag ; 1 " ; - $debugflags and myprint( "regexflag $regexflag [$h1_flags_orig] -> [$h1_flags]\n" ) ; - if( not ( defined $ret ) or $EVAL_ERROR ) { - myprint( "Error: eval regexflag '$regexflag': $EVAL_ERROR\n" ) ; - return( undef ) ; + if ( @regexmess ) { + ${ $string_ref } = regexmess( ${ $string_ref } ) ; + # string undef means the eval regex was bad. + if ( not ( defined ${ $string_ref } ) ) { + myprint( + "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]" + . " could not be transformed by --regexmess\n" ) ; + return ; } } - return( $h1_flags ) ; -} -sub acls_sync { - my($h1_fold, $h2_fold) = @_ ; - if ( $syncacls ) { - my $h1_hash = $imap1->getacl($h1_fold) - or myprint( "Could not getacl for $h1_fold: $EVAL_ERROR\n" ) ; - my $h2_hash = $imap2->getacl($h2_fold) - or myprint( "Could not getacl for $h2_fold: $EVAL_ERROR\n" ) ; - my %users = map { ($_, 1) } ( keys %{ $h1_hash} , keys %{ $h2_hash } ) ; - foreach my $user (sort keys %users ) { - my $acl = $h1_hash->{$user} || 'none' ; - myprint( "acl $user: [$acl]\n" ) ; - next if ($h1_hash->{$user} && $h2_hash->{$user} && - $h1_hash->{$user} eq $h2_hash->{$user}); - unless ($sync->{dry}) { - myprint( "setting acl $h2_fold $user $acl\n" ) ; - $imap2->setacl($h2_fold, $user, $acl) - or myprint( "Could not set acl: $EVAL_ERROR\n" ) ; - } + if ( @pipemess ) { + ${ $string_ref } = pipemess( ${ $string_ref }, @pipemess ) ; + # string undef means something was bad. + if ( not ( defined ${ $string_ref } ) ) { + myprint( + "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]" + . " could not be successfully transformed by --pipemess option\n" ) ; + return ; } } - return ; -} + if ( $mysync->{addheader} and defined $h1_fir_ref->{$h1_msg}->{'NO_HEADER'} ) { + my $header = add_header( $h1_msg ) ; + $mysync->{ debug } and myprint( "msg $h1_fold/$h1_msg adding custom header [$header]\n" ) ; + ${ $string_ref } = $header . "\r\n" . ${ $string_ref } ; + } -sub tests_permanentflags { - note( 'Entering tests_permanentflags()' ) ; + if ( ( defined $mysync->{ truncmess } ) and is_integer( $mysync->{ truncmess } ) ) + { + ${ $string_ref } = truncmess( ${ $string_ref }, $mysync->{ truncmess } ) ; + } - my $string; - ok(q{} eq permanentflags(' * OK [PERMANENTFLAGS (\* \Draft \Answered)] Limited'), - 'permanentflags \*'); - ok('\Draft \Answered' eq permanentflags(' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited'), - 'permanentflags \Draft \Answered'); - ok('\Draft \Answered' - eq permanentflags('Blabla', - ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited', - 'Blabla'), - 'permanentflags \Draft \Answered' - ); - ok(q{} eq permanentflags('Blabla'), 'permanentflags nothing'); + $string_len = length_ref( $string_ref ) ; - note( 'Leaving tests_permanentflags()' ) ; - return ; -} + $mysync->{ debugcontent } and myprint( debugcontent( $mysync, $string_ref ) ) ; -sub permanentflags { - my @lines = @_ ; + myprint( debugmemory( $mysync, " at M3" ) ) ; - foreach my $line (@lines) { - if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) { - ( $debugflags or $debug ) and myprint( "permanentflags: $line" ) ; - my $permanentflags = $1 ; - if ( $permanentflags =~ m{\\\*}x ) { - $permanentflags = q{} ; - } - return($permanentflags) ; - } ; - } - return( q{} ) ; + return $string_len ; } -sub tests_flags_filter { - note( 'Entering tests_flags_filter()' ) ; - - ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' ); - ok( q{} eq flags_filter('\Seen', '\Draft \Answered'), 'flags_filter ' ); - ok( '\Seen' eq flags_filter('\Seen', '\Seen'), 'flags_filter ' ); - ok( '\Seen' eq flags_filter('\Seen', ' \Seen '), 'flags_filter ' ); - ok( '\Seen \Draft' - eq flags_filter('\Seen \Draft', '\Draft \Seen \Answered'), 'flags_filter ' ); - ok( '\Seen \Draft' - eq flags_filter('\Seen \Draft', ' \Draft \Seen \Answered '), 'flags_filter ' ); - - note( 'Leaving tests_flags_filter()' ) ; - return ; +sub tests_debugcontent +{ + note( 'Entering tests_debugcontent()' ) ; + + is( undef, debugcontent( ), 'debugcontent: no args => undef' ) ; + my $mysync = { } ; + is( undef, debugcontent( $mysync ), 'debugcontent: undef => undef' ) ; + is( undef, debugcontent( $mysync, 'mm' ), 'debugcontent: undef, mm => undef' ) ; + #my $string_ref = \'zztop' ; + my $string = '================================================================================ +F message content begin next line (2 characters long) +mm +F message content ended on previous line +================================================================================ +' ; + is( $string, debugcontent( $mysync, \'mm' ), 'debugcontent: undef, mm => mm' ) ; + + note( 'Leaving tests_debugcontent()' ) ; + return ; } -sub flags_filter { - my( $flags, $allowed_flags ) = @_ ; - - my @flags = split /\s+/x, $flags ; - my %allowed_flags = map { $_ => 1 } split q{ }, $allowed_flags ; - my @flags_out = map { exists $allowed_flags{$_} ? $_ : () } @flags ; - - my $flags_out = join q{ }, @flags_out ; - - return( $flags_out ) ; -} +sub debugcontent +{ + my $mysync = shift @ARG ; + if ( ! defined $mysync ) { return ; } -sub flagscase { - my $flags = shift ; + my $string_ref = shift @ARG ; + if ( ! defined $string_ref ) { return ; } + if ( 'SCALAR' ne ref( $string_ref ) ) { return ; } - my @flags = split /\s+/x, $flags ; - my %rfc_flags = map { $_ => 1 } split q{ }, '\Answered \Flagged \Deleted \Seen \Draft' ; - my @flags_out = map { exists $rfc_flags{ ucsecond( lc $_ ) } ? ucsecond( lc $_ ) : $_ } @flags ; + my $string_len = length_ref( $string_ref ) ; - my $flags_out = join q{ }, @flags_out ; + my $string = join( '', + q{=} x $STD_CHAR_PER_LINE, "\n", + "F message content begin next line ($string_len characters long)\n", + ${ $string_ref }, + "\nF message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n", + ) ; - return( $flags_out ) ; + return $string ; } -sub tests_flagscase { - note( 'Entering tests_flagscase()' ) ; - ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ; - ok( '\Seen' eq flagscase( '\SEEN' ), 'flagscase: \SEEN -> \Seen' ) ; - ok( '\Seen \Draft' eq flagscase( '\SEEN \DRAFT' ), 'flagscase: \SEEN \DRAFT -> \Seen \Draft' ) ; - ok( '\Draft \Seen' eq flagscase( '\DRAFT \SEEN' ), 'flagscase: \DRAFT \SEEN -> \Draft \Seen' ) ; - ok( '\Draft LALA \Seen' eq flagscase( '\DRAFT LALA \SEEN' ), 'flagscase: \DRAFT LALA \SEEN -> \Draft LALA \Seen' ) ; - ok( '\Draft lala \Seen' eq flagscase( '\DRAFT lala \SEEN' ), 'flagscase: \DRAFT lala \SEEN -> \Draft lala \Seen' ) ; - note( 'Leaving tests_flagscase()' ) ; +sub tests_truncmess +{ + note( 'Entering tests_truncmess()' ) ; + + is( undef, truncmess( ), 'truncmess: no args => undef' ) ; + is( 'abc', truncmess( 'abc' ), 'truncmess: abc => abc' ) ; + is( 'ab', truncmess( 'abc', 2 ), 'truncmess: abc 2 => ab' ) ; + is( 'abc', truncmess( 'abc', 3 ), 'truncmess: abc 3 => abc' ) ; + is( 'abc', truncmess( 'abc', 4 ), 'truncmess: abc 4 => abc' ) ; + is( '12345', truncmess( "123456789\n", 5 ), 'truncmess: "123456789\n", 5 => 12345' ) ; + is( "123456789\n" x 5000, truncmess( "123456789\n" x 100000, 50000 ), 'truncmess: "123456789\n" x 100000, 50000 => "123456789\n" x 5000' ) ; + note( 'Leaving tests_truncmess()' ) ; return ; } - - -sub ucsecond { +sub truncmess +{ my $string = shift ; - my $output ; + my $length = shift ; - return( $string ) if ( 1 >= length $string ) ; + if ( not defined $string ) { return ; } + if ( not defined $length ) { return $string ; } - $output = ( substr( $string, 0, 1) ) . ( uc substr $string, 1, 1 ) . ( substr $string, 2 ) ; - #myprint( "UUU $string -> $output\n" ) ; - return( $output ) ; + $string = substr $string, 0, $length ; + return $string ; } +sub tests_message_for_host2 +{ + note( 'Entering tests_message_for_host2()' ) ; -sub tests_ucsecond { - note( 'Entering tests_ucsecond()' ) ; - ok( 'aBcde' eq ucsecond( 'abcde' ), 'ucsecond: abcde -> aBcde' ) ; - ok( 'ABCDE' eq ucsecond( 'ABCDE' ), 'ucsecond: ABCDE -> ABCDE' ) ; - ok( 'ABCDE' eq ucsecond( 'AbCDE' ), 'ucsecond: AbCDE -> ABCDE' ) ; - ok( 'ABCde' eq ucsecond( 'AbCde' ), 'ucsecond: AbCde -> ABCde' ) ; - ok( 'A' eq ucsecond( 'A' ), 'ucsecond: A -> A' ) ; - ok( 'AB' eq ucsecond( 'Ab' ), 'ucsecond: Ab -> AB' ) ; - ok( '\B' eq ucsecond( '\b' ), 'ucsecond: \b -> \B' ) ; - ok( '\Bcde' eq ucsecond( '\bcde' ), 'ucsecond: \bcde -> \Bcde' ) ; - note( 'Leaving tests_ucsecond()' ) ; - return ; -} + my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) ; + is( undef, message_for_host2( ), q{message_for_host2: no args} ) ; + is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), q{message_for_host2: undef args} ) ; -sub select_msgs { - my ( $imap, $msgs_all_hash_ref, $search_cmd, $abletosearch, $folder ) = @_ ; - my ( @msgs ) ; + require_ok( "Test::MockObject" ) ; + my $imapT = Test::MockObject->new( ) ; + $mysync->{imap1} = $imapT ; + my $string ; - if ( $abletosearch ) { - @msgs = select_msgs_by_search( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ; - }else{ - @msgs = select_msgs_by_fetch( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ; - } - return( @msgs ) ; + $h1_msg = 1 ; + $h1_fold = 'FoldFoo'; + $h1_size = 9 ; + $h1_flags = q{} ; + $h1_idate = '10-Jul-2015 09:00:00 +0200' ; + $h1_fir_ref = {} ; + $string_ref = \$string ; + $imapT->mock( 'message_to_file', + sub { + my ( $imap, $mystring_ref, $msg ) = @_ ; + ${$mystring_ref} = 'blablabla' ; + return length ${$mystring_ref} ; + } + ) ; + is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), + q{message_for_host2: msg 1 == "blablabla", length} ) ; + is( 'blablabla', $string, q{message_for_host2: msg 1 == "blablabla", value} ) ; -} + # so far so good + # now the --pipemess stuff -sub select_msgs_by_search { - my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ; - my ( @msgs, @msgs_all ) ; + SKIP: { + Readonly my $NB_WIN_tests_message_for_host2 => 0 ; + skip( 'Not on MSWin32', $NB_WIN_tests_message_for_host2 ) if ('MSWin32' ne $OSNAME) ; + # Windows + # "type" command does not accept redirection of STDIN with < + # "sort" does - # Need to have the whole list in msgs_all_hash_ref - # without calling messages() several times. - # Need all messages list to avoid deleting useful cache part - # in case of --search or --minage or --maxage + } ; - if ( ( defined $msgs_all_hash_ref and $usecache ) - or ( not defined $maxage and not defined $minage and not defined $search_cmd ) - ) { + SKIP: { + Readonly my $NB_UNX_tests_message_for_host2 => 6 ; + skip( 'Not on Unix', $NB_UNX_tests_message_for_host2 ) if ('MSWin32' eq $OSNAME) ; + # Unix - $debugdev and myprint( "Calling messages()\n" ) ; - @msgs_all = $imap->messages( ) ; + # no change by cat + @pipemess = ( 'cat' ) ; + is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), + q{message_for_host2: --pipemess 'cat', length} ) ; + is( 'blablabla', $string, q{message_for_host2: --pipemess 'cat', value} ) ; - return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ; - if ( defined $msgs_all_hash_ref ) { - @{ $msgs_all_hash_ref }{ @msgs_all } = () ; - } - # return all messages - if ( not defined $maxage and not defined $minage and not defined $search_cmd ) { - return( @msgs_all ) ; - } - } + # failure by false + @pipemess = ( 'false' ) ; + is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), + q{message_for_host2: --pipemess 'false', length} ) ; + is( undef, $string, q{message_for_host2: --pipemess 'false', value} ) ; - if ( defined $search_cmd ) { - @msgs = $imap->search( $search_cmd ) ; - return( @msgs ) ; + # failure by true since no output + @pipemess = ( 'true' ) ; + is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), + q{message_for_host2: --pipemess 'true', length} ) ; + is( undef, $string, q{message_for_host2: --pipemess 'true', value} ) ; } - # we are here only if $maxage or $minage is defined - @msgs = select_msgs_by_age( $imap ) ; - return( @msgs ); + note( 'Leaving tests_message_for_host2()' ) ; + return ; } +sub tests_labels_remove_subfolder1 +{ + note( 'Entering tests_labels_remove_subfolder1()' ) ; + is( undef, labels_remove_subfolder1( ), 'labels_remove_subfolder1: no parameters => undef' ) ; + is( 'Blabla', labels_remove_subfolder1( 'Blabla' ), 'labels_remove_subfolder1: one parameter Blabla => Blabla' ) ; + is( 'Blan blue', labels_remove_subfolder1( 'Blan blue' ), 'labels_remove_subfolder1: one parameter Blan blue => Blan blue' ) ; + is( '\Bla "Blan blan" Blabla', labels_remove_subfolder1( '\Bla "Blan blan" Blabla' ), + 'labels_remove_subfolder1: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ; -sub select_msgs_by_fetch { - my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ; - my ( @msgs, @msgs_all, %fetch ) ; - - # Need to have the whole list in msgs_all_hash_ref - # without calling messages() several times. - # Need all messages list to avoid deleting useful cache part - # in case of --search or --minage or --maxage + is( 'Bla', labels_remove_subfolder1( 'Subf/Bla', 'Subf' ), 'labels_remove_subfolder1: Subf/Bla Subf => "Bla"' ) ; - $debugdev and myprint( "Calling fetch_hash()\n" ) ; - my $uidnext = $imap->uidnext( $folder ) || $uidnext_default ; - my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ; - %fetch = %{$imap->fetch_hash( $fetch_hash_uids, 'INTERNALDATE' ) } ; + is( '"\\\\Bla"', labels_remove_subfolder1( '"\\\\Bla"', 'Subf' ), 'labels_remove_subfolder1: "\\\\Bla" Subf => "\\\\Bla"' ) ; - @msgs_all = sort { $a <=> $b } keys %fetch ; - $debugdev and myprint( "Done fetch_hash()\n" ) ; - - return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ; + is( 'Bla Kii', labels_remove_subfolder1( 'Subf/Bla Subf/Kii', 'Subf' ), + 'labels_remove_subfolder1: Subf/Bla Subf/Kii, Subf => "Bla" "Kii"' ) ; - if ( defined $msgs_all_hash_ref ) { - @{ $msgs_all_hash_ref }{ @msgs_all } = () ; - } - # return all messages - if ( not defined $maxage and not defined $minage and not defined $search_cmd ) { - return( @msgs_all ) ; - } + is( '"\\\\Bla" Kii', labels_remove_subfolder1( '"\\\\Bla" Subf/Kii', 'Subf' ), + 'labels_remove_subfolder1: "\\\\Bla" Subf/Kii Subf => "\\\\Bla" Kii' ) ; - if ( defined $search_cmd ) { - myprint( "Warning: strange to see --search with --noabletosearch, an error can happen\n" ) ; - @msgs = $imap->search( $search_cmd ) ; - return( @msgs ) ; - } + is( '"Blan blan"', labels_remove_subfolder1( '"Subf/Blan blan"', 'Subf' ), + 'labels_remove_subfolder1: "Subf/Blan blan" Subf => "Blan blan"' ) ; - # we are here only if $maxage or $minage is defined - my( @max, @min, $maxage_epoch, $minage_epoch ) ; - if ( defined $maxage ) { $maxage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ; } - if ( defined $minage ) { $minage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ; } - foreach my $msg ( @msgs_all ) { - my $idate = $fetch{ $msg }->{'INTERNALDATE'} ; - #myprint( "$idate\n" ) ; - if ( defined $maxage and ( epoch( $idate ) >= $maxage_epoch ) ) { - push @max, $msg ; - } - if ( defined $minage and ( epoch( $idate ) <= $minage_epoch ) ) { - push @min, $msg ; - } - } - @msgs = msgs_from_maxmin( \@max, \@min ) ; - return( @msgs ) ; -} + is( '"\\\\Loo" "Blan blan" Kii', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii', 'Subf' ), + 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii + Subf => "\\\\Loo" "Blan blan" Kii' ) ; -sub select_msgs_by_age { - my( $imap ) = @_ ; + is( '"\\\\Inbox"', labels_remove_subfolder1( 'Subf/INBOX', 'Subf' ), + 'labels_remove_subfolder1: Subf/INBOX + Subf => "\\\\Inbox"' ) ; - my( @max, @min, @msgs, @inter, @union ) ; + is( '"\\\\Loo" "Blan blan" Kii "\\\\Inbox"', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX', 'Subf' ), + 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX + Subf => "\\\\Loo" "Blan blan" Kii "\\\\Inbox"' ) ; - if ( defined $maxage ) { - @max = $imap->sentsince( $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ) ; - } - if ( defined $minage ) { - @min = $imap->sentbefore( $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ) ; - } - @msgs = msgs_from_maxmin( \@max, \@min ) ; - return( @msgs ) ; + note( 'Leaving tests_labels_remove_subfolder1()' ) ; + return ; } -sub msgs_from_maxmin { - my( $max_ref, $min_ref ) = @_ ; - my( @max, @min, @msgs, @inter, @union ) ; - @max = @{ $max_ref } ; - @min = @{ $min_ref } ; - SWITCH: { - unless( defined $minage ) { @msgs = @max ; last SWITCH } ; - unless( defined $maxage ) { @msgs = @min ; last SWITCH } ; - my ( %union, %inter ) ; - foreach my $m ( @min, @max ) { $union{ $m }++ && $inter{ $m }++ } - @inter = sort { $a <=> $b } keys %inter ; - @union = sort { $a <=> $b } keys %union ; - # normal case - if ( $minage <= $maxage ) { @msgs = @inter ; last SWITCH } ; - # just exclude messages between - if ( $minage > $maxage ) { @msgs = @union ; last SWITCH } ; +sub labels_remove_subfolder1 +{ + my $labels = shift ; + my $subfolder1 = shift ; - } - return( @msgs ) ; -} + if ( not defined $labels ) { return ; } + if ( not defined $subfolder1 ) { return $labels ; } -sub tests_msgs_from_maxmin { - note( 'Entering tests_msgs_from_maxmin()' ) ; + my @labels = quotewords('\s+', 1, $labels ) ; + #myprint( "@labels\n" ) ; + my @labels_subfolder2 ; - my @msgs ; - $maxage = $NUMBER_200 ; - @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ; - ok( 0 == compare_lists( [ '1', '2' ], \@msgs ), 'msgs_from_maxmin: maxage++' ) ; - $minage = $NUMBER_100 ; - @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ; - ok( 0 == compare_lists( [ '2' ], \@msgs ), 'msgs_from_maxmin: -maxage++minage-' ) ; - $minage = $NUMBER_300 ; - @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ; - ok( 0 == compare_lists( [ '1', '2', '3' ], \@msgs ), 'msgs_from_maxmin: ++maxage-minage++' ) ; - $maxage = undef ; - @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ; - ok( 0 == compare_lists( [ '2', '3' ], \@msgs ), 'msgs_from_maxmin: ++minage-' ) ; + foreach my $label ( @labels ) + { + if ( $label =~ m{zzzzzzzzzz} ) + { + # \Seen \Deleted ... stay the same + push @labels_subfolder2, $label ; + } + else + { + # Remove surrounding quotes if any, to add them again in case of space + $label = join( q{}, quotewords('\s+', 0, $label ) ) ; + $label =~ s{$subfolder1/?}{} ; + if ( 'INBOX' eq $label ) + { + push @labels_subfolder2, q{"\\\\Inbox"} ; + } + elsif ( $label =~ m{\\} ) + { + push @labels_subfolder2, qq{"\\$label"} ; + } + elsif ( $label =~ m{ } ) + { + push @labels_subfolder2, qq{"$label"} ; + } + else + { + push @labels_subfolder2, $label ; + } + } + } - note( 'Leaving tests_msgs_from_maxmin()' ) ; - return ; -} + my $labels_subfolder2 = join( ' ', sort uniq( @labels_subfolder2 ) ) ; -sub tests_info_date_from_uid { + return $labels_subfolder2 ; +} +sub tests_labels_remove_special +{ + note( 'Entering tests_labels_remove_special()' ) ; + is( undef, labels_remove_special( ), 'labels_remove_special: no parameters => undef' ) ; + is( q{}, labels_remove_special( q{} ), 'labels_remove_special: empty string => empty string' ) ; + is( q{}, labels_remove_special( '"\\\\Inbox"' ), 'labels_remove_special:"\\\\Inbox" => empty string' ) ; + is( q{}, labels_remove_special( '"\\\\Inbox" "\\\\Starred"' ), 'labels_remove_special:"\\\\Inbox" "\\\\Starred" => empty string' ) ; + is( 'Bar Foo', labels_remove_special( 'Foo Bar' ), 'labels_remove_special:Foo Bar => Bar Foo' ) ; + is( 'Bar Foo', labels_remove_special( 'Foo Bar "\\\\Inbox"' ), 'labels_remove_special:Foo Bar "\\\\Inbox" => Bar Foo' ) ; + note( 'Leaving tests_labels_remove_special()' ) ; return ; } -sub info_date_from_uid { - - #my $first_uid = $msgs_all[ 0 ] ; - #my $first_idate = $fetch{ $first_uid }->{'INTERNALDATE'} ; - #my $first_epoch = epoch( $first_idate ) ; - #my $first_days = ( $timestart_int - $first_epoch ) / $NB_SECONDS_IN_A_DAY ; - #myprint( "\nOldest msg has UID $first_uid INTERNALDATE $first_idate EPOCH $first_epoch DAYS AGO $first_days\n" ) ; -} -sub lastuid { - my $imap = shift ; - my $folder = shift ; - my $lastuid_guess = shift ; - my $lastuid ; - # rfc3501: The only reliable way to identify recent messages is to - # look at message flags to see which have the \Recent flag - # set, or to do a SEARCH RECENT. - # SEARCH RECENT doesn't work this way on courrier. +sub labels_remove_special +{ + my $labels = shift ; - my @recent_messages ; - # SEARCH RECENT for each transfer can be expensive with a big folder - # Call commented for now - #@recent_messages = $imap->recent( ) ; - #myprint( "Recent: @recent_messages\n" ) ; + if ( not defined $labels ) { return ; } - my $max_recent ; - $max_recent = max( @recent_messages ) ; + my @labels = quotewords('\s+', 1, $labels ) ; + myprint( "labels before remove_non_folded: @labels\n" ) ; + my @labels_remove_special ; - if ( defined $max_recent and ($lastuid_guess <= $max_recent ) ) { - $lastuid = $max_recent ; - }else{ - $lastuid = $lastuid_guess + foreach my $label ( @labels ) + { + if ( $label =~ m{^\"\\\\} ) + { + # not kept + } + else + { + push @labels_remove_special, $label ; + } } - return( $lastuid ) ; -} -sub size_filtered { - my( $h1_size, $h1_msg, $h1_fold, $h2_fold ) = @_ ; + my $labels_remove_special = join( ' ', sort @labels_remove_special ) ; - $h1_size = 0 if ( ! $h1_size ) ; # null if empty or undef - if (defined $maxsize and $h1_size > $maxsize) { - myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $maxsize bytes)\n" ) ; - $total_bytes_skipped += $h1_size; - $nb_msg_skipped += 1; - return( 1 ) ; - } - if (defined $minsize and $h1_size <= $minsize) { - myprint( "msg $h1_fold/$h1_msg skipped ($h1_size smaller than minsize $minsize bytes)\n" ) ; - $total_bytes_skipped += $h1_size; - $nb_msg_skipped += 1; - return( 1 ) ; - } - return( 0 ) ; + return $labels_remove_special ; } -sub message_exists { - my( $imap, $msg ) = @_ ; - return( 1 ) if not $imap->Uid( ) ; - my $search_uid ; - ( $search_uid ) = $imap->search( "UID $msg" ) ; - #myprint( "$search ? $msg\n" ) ; - return( 1 ) if ( $search_uid eq $msg ) ; - return( 0 ) ; -} +sub tests_labels_add_subfolder2 +{ + note( 'Entering tests_labels_add_subfolder2()' ) ; + is( undef, labels_add_subfolder2( ), 'labels_add_subfolder2: no parameters => undef' ) ; + is( 'Blabla', labels_add_subfolder2( 'Blabla' ), 'labels_add_subfolder2: one parameter Blabla => Blabla' ) ; + is( 'Blan blue', labels_add_subfolder2( 'Blan blue' ), 'labels_add_subfolder2: one parameter Blan blue => Blan blue' ) ; + is( '\Bla "Blan blan" Blabla', labels_add_subfolder2( '\Bla "Blan blan" Blabla' ), + 'labels_add_subfolder2: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ; -sub copy_message { - # copy + is( 'Subf/Bla', labels_add_subfolder2( 'Bla', 'Subf' ), 'labels_add_subfolder2: Bla Subf => "Subf/Bla"' ) ; - my ( $mysync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) = @_ ; - ( $debug or $mysync->{dry}) and myprint( "msg $h1_fold/$h1_msg copying to $h2_fold $mysync->{dry_message}\n" ) ; - my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} || 0 ; - my $h1_flags = $h1_fir_ref->{$h1_msg}->{'FLAGS'} || q{} ; - my $h1_idate = $h1_fir_ref->{$h1_msg}->{'INTERNALDATE'} || q{} ; + is( 'Subf/\Bla', labels_add_subfolder2( '\\\\Bla', 'Subf' ), 'labels_add_subfolder2: \Bla Subf => \Bla' ) ; + is( 'Subf/Bla Subf/Kii', labels_add_subfolder2( 'Bla Kii', 'Subf' ), + 'labels_add_subfolder2: Bla Kii Subf => "Subf/Bla" "Subf/Kii"' ) ; - if ( size_filtered( $h1_size, $h1_msg, $h1_fold, $h2_fold ) ) { - $h1_nb_msg_processed +=1 ; - return ; - } + is( 'Subf/Kii Subf/\Bla', labels_add_subfolder2( '\\\\Bla Kii', 'Subf' ), + 'labels_add_subfolder2: \Bla Kii Subf => \Bla Subf/Kii' ) ; - debugsleep( $mysync ) ; - myprint( "- msg $h1_fold/$h1_msg S[$h1_size] F[$h1_flags] I[$h1_idate] has RFC822.SIZE null!\n" ) if ( ! $h1_size ) ; + is( '"Subf/Blan blan"', labels_add_subfolder2( '"Blan blan"', 'Subf' ), + 'labels_add_subfolder2: "Blan blan" Subf => "Subf/Blan blan"' ) ; + is( '"Subf/Blan blan" Subf/Kii Subf/\Loo', labels_add_subfolder2( '\\\\Loo "Blan blan" Kii', 'Subf' ), + 'labels_add_subfolder2: \Loo "Blan blan" Kii + Subf => "Subf/Blan blan" Subf/Kii Subf/\Loo' ) ; - if ( $checkmessageexists and not message_exists( $imap1, $h1_msg ) ) { - $total_bytes_skipped += $h1_size; - $nb_msg_skipped += 1; - $h1_nb_msg_processed +=1 ; - return ; - } - myprint( debugmemory( $sync, " at C1" ) ) ; + # "\\Inbox" is special, add to subfolder INBOX also because Gmail will but ... + is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox"', 'Subf' ), + 'labels_add_subfolder2: "\\\\Inbox" Subf => "Subf/\\\\Inbox" Subf/INBOX' ) ; - my ( $string, $string_len ) ; - ( $string_len ) = message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ; + # but not with INBOX folder + is( '"Subf/\\\\Inbox"', labels_add_subfolder2( '"\\\\Inbox"', 'Subf', 'INBOX' ), + 'labels_add_subfolder2: "\\\\Inbox" Subf INBOX => "Subf/\\\\Inbox"' ) ; - myprint( debugmemory( $sync, " at C2" ) ) ; + # two times => one time + is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox" "\\\\Inbox"', 'Subf' ), + 'labels_add_subfolder2: "\\\\Inbox" "\\\\Inbox" Subf => "Subf/\\\\Inbox"' ) ; - # not defined or empty $string - if ( ( not $string ) or ( not $string_len ) ) { - myprint( "- msg $h1_fold/$h1_msg skipped.\n" ) ; - $total_bytes_skipped += $h1_size; - $nb_msg_skipped += 1; - $h1_nb_msg_processed += 1 ; - return ; - } + is( '"Subf/\\\\Starred"', labels_add_subfolder2( '"\\\\Starred"', 'Subf' ), + 'labels_add_subfolder2: "\\\\Starred" Subf => "Subf/\\\\Starred"' ) ; - # Lines too long (or not enough) => do no copy or fix - if ( ( defined $maxlinelength ) or ( defined $minmaxlinelength ) ) { - $string = linelengthstuff( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) ; - if ( not defined $string ) { - $h1_nb_msg_processed +=1 ; - $total_bytes_skipped += $h1_size ; - $nb_msg_skipped += 1 ; - return ; - } - } + note( 'Leaving tests_labels_add_subfolder2()' ) ; + return ; +} - my $h1_date = date_for_host2( $h1_msg, $h1_idate ) ; +sub labels_add_subfolder2 +{ + my $labels = shift ; + my $subfolder2 = shift ; + my $h1_folder = shift || q{} ; - ( $debug or $debugflags ) and - myprint( "Host1 flags init msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ; + if ( not defined $labels ) { return ; } + if ( not defined $subfolder2 ) { return $labels ; } - $h1_flags = flags_for_host2( $h1_flags, $permanentflags2 ) ; + # Isn't it messy? + if ( 'INBOX' eq $h1_folder ) + { + $labels .= ' "\\\\Inbox"' ; + } - ( $debug or $debugflags ) and - myprint( "Host1 flags filt msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ; + my @labels = uniq( quotewords('\s+', 1, $labels ) ) ; + myprint( "labels before subfolder2: @labels\n" ) ; + my @labels_subfolder2 ; - $h1_date = undef if ($h1_date eq q{}); - my $new_id = append_message_on_host2( \$string, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) ; + foreach my $label ( @labels ) + { + # Isn't it more messy? + if ( ( q{"\\\\Inbox"} eq $label ) and ( 'INBOX' ne $h1_folder ) ) + { + if ( $subfolder2 =~ m{ } ) + { + push @labels_subfolder2, qq{"$subfolder2/INBOX"} ; + } + else + { + push @labels_subfolder2, "$subfolder2/INBOX" ; + } + } + if ( $label =~ m{^\"\\\\} ) + { + # \Seen \Deleted ... stay the same + #push @labels_subfolder2, $label ; + # Remove surrounding quotes if any, to add them again + $label = join( q{}, quotewords('\s+', 0, $label ) ) ; + push @labels_subfolder2, qq{"$subfolder2/\\$label"} ; - if ( $new_id and $syncflagsaftercopy ) { - sync_flags_after_copy( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id, $permanentflags2 ) ; + } + else + { + # Remove surrounding quotes if any, to add them again in case of space + $label = join( q{}, quotewords('\s+', 0, $label ) ) ; + if ( $label =~ m{ } ) + { + push @labels_subfolder2, qq{"$subfolder2/$label"} ; + } + else + { + push @labels_subfolder2, "$subfolder2/$label" ; + } + } } - myprint( debugmemory( $sync, " at C3" ) ) ; + my $labels_subfolder2 = join( ' ', sort @labels_subfolder2 ) ; - return $new_id ; + return $labels_subfolder2 ; } +sub tests_labels +{ + note( 'Entering tests_labels()' ) ; + is( undef, labels( ), 'labels: no parameters => undef' ) ; + is( undef, labels( undef ), 'labels: undef => undef' ) ; + require_ok( "Test::MockObject" ) ; + my $myimap = Test::MockObject->new( ) ; -sub linelengthstuff { - my( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) = @_ ; - my $maxlinelength_string = max_line_length( $string ) ; - $debugmaxlinelength and myprint( "msg $h1_fold/$h1_msg maxlinelength: $maxlinelength_string\n" ) ; + $myimap->mock( 'fetch_hash', + sub { + return( + { '1' => { + 'X-GM-LABELS' => '\Seen Blabla' + } + } + ) ; + } + ) ; + $myimap->mock( 'Debug' , sub { } ) ; + $myimap->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one - if ( ( defined $minmaxlinelength ) and ( $maxlinelength_string <= $minmaxlinelength ) ) { - my $subject = subject( $string ) ; - $debugdev and myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] " - . "(Subject:[$subject]) (max line length under minmaxlinelength $minmaxlinelength bytes)\n" ) ; - return ; - } + is( undef, labels( $myimap ), 'labels: one parameter => undef' ) ; + is( '\Seen Blabla', labels( $myimap, '1' ), 'labels: $mysync UID_1 => \Seen Blabla' ) ; - if ( ( defined $maxlinelength ) and ( $maxlinelength_string > $maxlinelength ) ) { - my $subject = subject( $string ) ; - if ( $maxlinelengthcmd ) { - $string = pipemess( $string, $maxlinelengthcmd ) ; - # string undef means something was bad. - if ( not ( defined $string ) ) { - myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] " - . "(Subject:[$subject]) could not be successfully transformed by --maxlinelengthcmd option\n" ) ; - return ; - }else{ - return $string ; - } - } - myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] " - . "(Subject:[$subject]) (line length exceeds maxlinelength $maxlinelength bytes)\n" ) ; + note( 'Leaving tests_labels()' ) ; + return ; +} + +sub labels +{ + my ( $myimap, $uid ) = @ARG ; + + if ( not all_defined( $myimap, $uid ) ) { return ; } - return $string ; + + my $hash = $myimap->fetch_hash( [ $uid ], 'X-GM-LABELS' ) ; + + my $labels = $hash->{ $uid }->{ 'X-GM-LABELS' } ; + #$labels = $myimap->Unescape( $labels ) ; + return $labels ; } +sub tests_synclabels +{ + note( 'Entering tests_synclabels()' ) ; -sub message_for_host2 { + is( undef, synclabels( ), 'synclabels: no parameters => undef' ) ; + is( undef, synclabels( undef ), 'synclabels: undef => undef' ) ; + my $mysync ; + is( undef, synclabels( $mysync ), 'synclabels: var undef => undef' ) ; -# global variable list: -# @skipmess -# @regexmess -# @pipemess -# $debugcontent -# $debug -# -# API current -# -# at failure: -# * return nothing ( will then be undef or () ) -# * $string_ref content is undef or empty -# at success: -# * return string length ($string_ref content length) -# * $string_ref content filled with message + require_ok( "Test::MockObject" ) ; + $mysync = {} ; + + my $myimap1 = Test::MockObject->new( ) ; + $myimap1->mock( 'fetch_hash', + sub { + return( + { '1' => { + 'X-GM-LABELS' => '\Seen Blabla' + } + } + ) ; + } + ) ; + $myimap1->mock( 'Debug', sub { } ) ; + $myimap1->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one -# API future -# -# - my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) = @_ ; + my $myimap2 = Test::MockObject->new( ) ; - # abort when missing a parameter - if ( (!$sync) or (!$h1_msg) or (!$h1_fold) or (!$h1_size) or (!defined $h1_flags) or (!defined $h1_idate) or (!$h1_fir_ref) or (!$string_ref) ) { - return ; - } + $myimap2->mock( 'store', + sub { + return 1 ; + } + ) ; - myprint( debugmemory( $sync, " at M1" ) ) ; - my $imap1 = $mysync->{imap1} ; - my $string_ok = $imap1->message_to_file( $string_ref, $h1_msg ) ; + $mysync->{imap1} = $myimap1 ; + $mysync->{imap2} = $myimap2 ; - myprint( debugmemory( $sync, " at M2" ) ) ; + is( undef, synclabels( $mysync ), 'synclabels: fresh $mysync => undef' ) ; - my $string_len = length_ref( $string_ref ) ; + is( undef, synclabels( $mysync, '1' ), 'synclabels: $mysync UID_1 alone => undef' ) ; + is( 1, synclabels( $mysync, '1', '2' ), 'synclabels: $mysync UID_1 UID_2 => 1' ) ; + note( 'Leaving tests_synclabels()' ) ; + return ; +} - unless ( defined $string_ok and $string_len ) { - # undef or 0 length - my $error = join q{}, - "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] could not be fetched: ", - $imap1->LastError || q{}, "\n" ; - errors_incr( $mysync, $error ) ; - $total_bytes_error += $h1_size if ( $h1_size ) ; - $h1_nb_msg_processed +=1 ; - return ; - } - if ( @skipmess ) { - my $match = skipmess( ${ $string_ref } ) ; - # string undef means the eval regex was bad. - if ( not ( defined $match ) ) { - myprint( - "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]" - . " could not be skipped by --skipmess option, bad regex\n" ) ; - return ; - } - if ( $match ) { - my $subject = subject( ${ $string_ref } ) ; - myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]" - . " (Subject:[$subject]) skipped by --skipmess\n" ) ; - return ; - } - } +sub synclabels +{ + my( $mysync, $uid1, $uid2 ) = @ARG ; - if ( @regexmess ) { - ${ $string_ref } = regexmess( ${ $string_ref } ) ; - # string undef means the eval regex was bad. - if ( not ( defined ${ $string_ref } ) ) { - myprint( - "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]" - . " could not be transformed by --regexmess\n" ) ; - return ; - } + if ( not all_defined( $mysync, $uid1, $uid2 ) ) { + return ; } + my $myimap1 = $mysync->{ 'imap1' } || return ; + my $myimap2 = $mysync->{ 'imap2' } || return ; - if ( @pipemess ) { - ${ $string_ref } = pipemess( ${ $string_ref }, @pipemess ) ; - # string undef means something was bad. - if ( not ( defined ${ $string_ref } ) ) { - myprint( - "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]" - . " could not be successfully transformed by --pipemess option\n" ) ; - return ; - } - } + $mysync->{debuglabels} and $myimap1->Debug( 1 ) ; + my $labels1 = labels( $myimap1, $uid1 ) ; + $mysync->{debuglabels} and $myimap1->Debug( 0 ) ; + $mysync->{debuglabels} and myprint( "Host1 labels: $labels1\n" ) ; - if ( $mysync->{addheader} and defined $h1_fir_ref->{$h1_msg}->{'NO_HEADER'} ) { - my $header = add_header( $h1_msg ) ; - $debug and myprint( "msg $h1_fold/$h1_msg adding custom header [$header]\n" ) ; - ${ $string_ref } = $header . "\r\n" . ${ $string_ref } ; - } - $string_len = length_ref( $string_ref ) ; - $debugcontent and myprint( - q{=} x $STD_CHAR_PER_LINE, "\n", - "F message content begin next line ($string_len characters long)\n", - ${ $string_ref }, - "F message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n" ) ; + if ( $mysync->{ subfolder1 } and $labels1 ) + { + $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ; + $mysync->{debuglabels} and myprint( "Host1 labels with subfolder1: $labels1\n" ) ; + } - myprint( debugmemory( $sync, " at M3" ) ) ; + if ( $mysync->{ subfolder2 } and $labels1 ) + { + $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 } ) ; + $mysync->{debuglabels} and myprint( "Host1 labels with subfolder2: $labels1\n" ) ; + } - return $string_len ; + my $store ; + if ( $labels1 and not $mysync->{ dry } ) + { + $mysync->{ debuglabels } and $myimap2->Debug( 1 ) ; + $store = $myimap2->store( $uid2, "X-GM-LABELS ($labels1)" ) ; + $mysync->{ debuglabels } and $myimap2->Debug( 0 ) ; + } + return $store ; } -sub tests_message_for_host2 { - note( 'Entering tests_message_for_host2()' ) ; +sub tests_resynclabels +{ + note( 'Entering tests_resynclabels()' ) ; - my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) ; + is( undef, resynclabels( ), 'resynclabels: no parameters => undef' ) ; + is( undef, resynclabels( undef ), 'resynclabels: undef => undef' ) ; + my $mysync ; + is( undef, resynclabels( $mysync ), 'resynclabels: var undef => undef' ) ; - is( undef, message_for_host2( ), q{message_for_host2: no args} ) ; - is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), q{message_for_host2: undef args} ) ; + my ( $h1_fir_ref, $h2_fir_ref ) ; - require Test::MockObject ; - my $imapT = Test::MockObject->new( ) ; - $mysync->{imap1} = $imapT ; - my $string ; + $mysync->{ debuglabels } = 1 ; + $h1_fir_ref->{ 11 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ; + $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ; - $h1_msg = 1 ; - $h1_fold = 'FoldFoo'; - $h1_size = 9 ; - $h1_flags = '' ; - $h1_idate = '10-Jul-2015 09:00:00 +0200' ; - $h1_fir_ref = {} ; - $string_ref = \$string ; - $imapT->mock( 'message_to_file', + # labels are equal + is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ), + 'resynclabels: $mysync UID_1 UID_2 labels are equal => 1' ) ; + + # labels are different + $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Zuu' ; + require_ok( "Test::MockObject" ) ; + my $myimap2 = Test::MockObject->new( ) ; + $myimap2->mock( 'store', sub { - my ( $imap, $mystring_ref, $msg ) = @_ ; - ${$mystring_ref} = 'blablabla' ; - return length ${$mystring_ref} ; + return 1 ; } ) ; - is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), - q{message_for_host2: msg 1 == "blablabla", length} ) ; - is( 'blablabla', $string, q{message_for_host2: msg 1 == "blablabla", value} ) ; + $myimap2->mock( 'Debug', sub { } ) ; + $mysync->{imap2} = $myimap2 ; - # so far so good - # now the --pipemess stuff + is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ), + 'resynclabels: $mysync UID_1 UID_2 labels are not equal => store => 1' ) ; - SKIP: { - Readonly my $NB_WIN_tests_message_for_host2 => 0 ; - skip( 'Not on MSWin32', $NB_WIN_tests_message_for_host2 ) if ('MSWin32' ne $OSNAME) ; - # Windows - # "type" command does not accept redirection of STDIN with < - # "sort" does + note( 'Leaving tests_resynclabels()' ) ; + return ; +} - } ; - SKIP: { - Readonly my $NB_UNX_tests_message_for_host2 => 6 ; - skip( 'Not on Unix', $NB_UNX_tests_message_for_host2 ) if ('MSWin32' eq $OSNAME) ; - # Unix - # no change by cat - @pipemess = ( 'cat' ) ; - is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), - q{message_for_host2: --pipemess 'cat', length} ) ; - is( 'blablabla', $string, q{message_for_host2: --pipemess 'cat', value} ) ; +sub resynclabels +{ + my( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref, $h1_folder ) = @ARG ; + if ( not all_defined( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref ) ) { + return ; + } - # failure by false - @pipemess = ( 'false' ) ; - is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), - q{message_for_host2: --pipemess 'false', length} ) ; - is( undef, $string, q{message_for_host2: --pipemess 'false', value} ) ; + my $labels1 = $h1_fir_ref->{ $uid1 }->{ 'X-GM-LABELS' } || q{} ; + my $labels2 = $h2_fir_ref->{ $uid2 }->{ 'X-GM-LABELS' } || q{} ; - # failure by true since no output - @pipemess = ( 'true' ) ; - is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), - q{message_for_host2: --pipemess 'true', length} ) ; - is( undef, $string, q{message_for_host2: --pipemess 'true', value} ) ; + if ( $mysync->{ subfolder1 } and $labels1 ) + { + $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ; } - note( 'Leaving tests_message_for_host2()' ) ; + if ( $mysync->{ subfolder2 } and $labels1 ) + { + $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 }, $h1_folder ) ; + $labels2 = labels_remove_special( $labels2 ) ; + } + $mysync->{ debuglabels } and myprint( "Host1 labels fixed: $labels1\n" ) ; + $mysync->{ debuglabels } and myprint( "Host2 labels : $labels2\n" ) ; + + my $store ; + if ( $labels1 eq $labels2 ) + { + # no sync needed + $mysync->{ debuglabels } and myprint( "Labels are already equal\n" ) ; + return 1 ; + } + elsif ( not $mysync->{ dry } ) + { + # sync needed + $mysync->{debuglabels} and $mysync->{imap2}->Debug( 1 ) ; + $store = $mysync->{imap2}->store( $uid2, "X-GM-LABELS ($labels1)" ) ; + $mysync->{debuglabels} and $mysync->{imap2}->Debug( 0 ) ; + } + + return $store ; +} + +sub tests_uniq +{ + note( 'Entering tests_uniq()' ) ; + + is( 0, uniq( ), 'uniq: undef => 0' ) ; + is_deeply( [ 'one' ], [ uniq( 'one' ) ], 'uniq: one => one' ) ; + is_deeply( [ 'one' ], [ uniq( 'one', 'one' ) ], 'uniq: one one => one' ) ; + is_deeply( [ 'one', 'two' ], [ uniq( 'one', 'one', 'two', 'one', 'two' ) ], 'uniq: one one two one two => one two' ) ; + note( 'Leaving tests_uniq()' ) ; return ; } -sub length_ref { +sub uniq +{ + my @list = @ARG ; + my %seen = ( ) ; + my @uniq = ( ) ; + foreach my $item ( @list ) { + if ( ! $seen{ $item } ) { + $seen{ $item } = 1 ; + push( @uniq, $item ) ; + } + } + return @uniq ; +} + + +sub length_ref +{ my $string_ref = shift ; my $string_len = defined ${ $string_ref } ? length( ${ $string_ref } ) : q{} ; # length or empty string return $string_len ; } -sub tests_length_ref { +sub tests_length_ref +{ note( 'Entering tests_length_ref()' ) ; my $notdefined ; @@ -6733,62 +12181,54 @@ sub tests_length_ref { return ; } -sub date_for_host2 { +sub date_for_host2 +{ my( $h1_msg, $h1_idate ) = @_ ; my $h1_date = q{} ; if ( $syncinternaldates ) { $h1_date = $h1_idate ; - $debug and myprint( "internal date from host1: [$h1_date]\n" ) ; + $sync->{ debug } and myprint( "internal date from host1: [$h1_date]\n" ) ; $h1_date = good_date( $h1_date ) ; - $debug and myprint( "internal date from host1: [$h1_date] (fixed)\n" ) ; + $sync->{ debug } and myprint( "internal date from host1: [$h1_date] (fixed)\n" ) ; } if ( $idatefromheader ) { - $h1_date = $imap1->get_header( $h1_msg, 'Date' ) ; - $debug and myprint( "header date from host1: [$h1_date]\n" ) ; + $h1_date = $sync->{imap1}->get_header( $h1_msg, 'Date' ) ; + $sync->{ debug } and myprint( "header date from host1: [$h1_date]\n" ) ; $h1_date = good_date( $h1_date ) ; - $debug and myprint( "header date from host1: [$h1_date] (fixed)\n" ) ; + $sync->{ debug } and myprint( "header date from host1: [$h1_date] (fixed)\n" ) ; } return( $h1_date ) ; } -sub flags_for_host2 { - my( $h1_flags, $permanentflags2 ) = @_ ; - # RFC 2060: This flag can not be altered by any client - $h1_flags =~ s@\\Recent\s?@@xgi ; - my $h1_flags_re ; - if ( @regexflag and defined( $h1_flags_re = flags_regex( $h1_flags ) ) ) { - $h1_flags = $h1_flags_re ; - } - $h1_flags = flagscase( $h1_flags ) if $flagscase ; - $h1_flags = flags_filter( $h1_flags, $permanentflags2) if ( $permanentflags2 and $filterflags ) ; - - return( $h1_flags ) ; -} -sub subject { +sub subject +{ my $string = shift ; my $subject = q{} ; my $header = extract_header( $string ) ; - if( $header =~ m/^Subject:\s*([^\n\r]*)\r?$/msx ) { + if( $header =~ m/^Subject:[ \t]*([^\n\r]*)\r?$/msx ) { #myprint( "MMM[$1]\n" ) ; $subject = $1 ; } return( $subject ) ; } -sub tests_subject { - note( 'Entering tests_subject()' ) ; +sub tests_subject +{ + note( 'Entering tests_subject()' ) ; ok( q{} eq subject( q{} ), 'subject: null') ; - ok( 'toto le hero' eq subject( 'Subject: toto le hero' ), 'subject: toto le hero') ; - ok( 'toto le hero' eq subject( 'Subject:toto le hero' ), 'subject: toto le hero blank') ; - ok( 'toto le hero' eq subject( "Subject:toto le hero\r\n" ), 'subject: toto le hero\r\n') ; + is( '', subject( 'Subject:' ), 'Subject:') ; + is( '', subject( "Subject:\r\n" ), 'Subject:\r\n') ; + ok( 'toto le hero' eq subject( 'Subject: toto le hero' ), 'Subject: toto le hero') ; + ok( 'toto le hero' eq subject( 'Subject:toto le hero' ), 'Subject:toto le hero') ; + ok( 'toto le hero' eq subject( "Subject:toto le hero\r\n" ), 'Subject: toto le hero\r\n') ; my $MESS ; $MESS = <<'EOF'; @@ -6827,38 +12267,43 @@ Subject: toto le hero EOF ok( q{} eq subject( $MESS ), 'subject: null but body could') ; + + $MESS = <<'EOF'; +From: lalala +Subject: +Date: zzzzzz + +Subject: toto le hero +EOF + is( '', subject( $MESS ), 'Subject:') ; + + + note( 'Leaving tests_subject()' ) ; return ; } # GlobVar -# $sync -# $max_msg_size_in_bytes -# $imap2 -# $imap1 -# $total_bytes_error -# $h1_nb_msg_processed # $h2_uidguess # ... # # -sub append_message_on_host2 { - my( $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ; - myprint( debugmemory( $sync, " at A1" ) ) ; +sub append_message_on_host2 +{ + my( $mysync, $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ; + myprint( debugmemory( $mysync, " at A1" ) ) ; my $new_id ; - if ( ! $sync->{dry} ) { - $max_msg_size_in_bytes = max( $h1_size, $max_msg_size_in_bytes ) ; - $new_id = $imap2->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ; - myprint( debugmemory( $sync, " at A2" ) ) ; - if ( ! $new_id){ + if ( ! $mysync->{dry} ) { + $new_id = $mysync->{imap2}->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ; + myprint( debugmemory( $mysync, " at A2" ) ) ; + if ( ! defined $new_id ){ my $subject = subject( ${ $string_ref } ) ; - my $error_imap = $imap2->LastError || q{} ; - my $error = "- msg $h1_fold/$h1_msg {$string_len} could not append ( Subject:[$subject], Date:[$h1_date], Size:[$h1_size] ) to folder $h2_fold: $error_imap\n" ; - errors_incr( $sync, $error ) ; - $total_bytes_error += $h1_size; - $h1_nb_msg_processed +=1 ; + my $error_imap = $mysync->{imap2}->LastError || q{} ; + my $error = "- msg $h1_fold/$h1_msg {$string_len} could not append ( Subject:[$subject], Date:[$h1_date], Size:[$h1_size], Flags:[$h1_flags] ) to folder $h2_fold: $error_imap\n" ; + errors_incr( $mysync, $error ) ; + $mysync->{ h1_nb_msg_processed } +=1 ; return ; } else{ @@ -6866,44 +12311,48 @@ sub append_message_on_host2 { # $new_id is an id if the IMAP server has the # UIDPLUS capability else just a ref if ( $new_id !~ m{^\d+$}x ) { - $new_id = lastuid( $imap2, $h2_fold, $h2_uidguess ) ; + $new_id = lastuid( $mysync->{imap2}, $h2_fold, $h2_uidguess ) ; } + if ( $mysync->{ synclabels } ) { synclabels( $mysync, $h1_msg, $new_id ) } $h2_uidguess += 1 ; - $sync->{total_bytes_transferred} += $h1_size ; - $sync->{nb_msg_transferred} += 1 ; - $h1_nb_msg_processed +=1 ; - - my $time_spent = timesince( $sync->{begin_transfer_time} ) ; - my $rate = bytes_display_string( $sync->{total_bytes_transferred} / $time_spent ) ; - my $eta = eta( $time_spent, - $h1_nb_msg_processed, $h1_nb_msg_start, $sync->{nb_msg_transferred} ) ; - my $amount_transferred = bytes_display_string( $sync->{total_bytes_transferred} ) ; - myprintf( "msg %s/%-19s copied to %s/%-10s %.2f msgs/s %s/s %s copied %s\n", - $h1_fold, "$h1_msg {$string_len}", $h2_fold, $new_id, $sync->{nb_msg_transferred}/$time_spent, $rate, + $mysync->{ total_bytes_transferred } += $string_len ; + $mysync->{ nb_msg_transferred } += 1 ; + $mysync->{ h1_nb_msg_processed } +=1 ; + $mysync->{ biggest_message_transferred } = max( $string_len, $mysync->{ biggest_message_transferred } ) ; + + my $time_spent = timesince( $mysync->{begin_transfer_time} ) ; + my $rate = bytes_display_string_bin( $mysync->{total_bytes_transferred} / $time_spent ) ; + my $eta = eta( $mysync ) ; + my $amount_transferred = bytes_display_string_bin( $mysync->{total_bytes_transferred} ) ; + myprintf( "msg %s/%-19s copied to %s/%-10s %.2f msgs/s %s/s %s copied %s\n", + $h1_fold, "$h1_msg {$string_len}", $h2_fold, $new_id, $mysync->{nb_msg_transferred}/$time_spent, $rate, $amount_transferred, $eta ); - sleep_if_needed( $sync ) ; + sleep_if_needed( $mysync ) ; if ( $usecache and $cacheaftercopy and $new_id =~ m{^\d+$}x ) { $debugcache and myprint( "touch $cache_dir/${h1_msg}_$new_id\n" ) ; touch( "$cache_dir/${h1_msg}_$new_id" ) or croak( "Couldn't touch $cache_dir/${h1_msg}_$new_id" ) ; } - if ( $delete1 ) { - delete_message_on_host1( $h1_msg, $h1_fold ) ; + if ( $mysync->{ delete1 } ) { + delete_message_on_host1( $mysync, $h1_fold, $mysync->{ expungeaftereach }, $h1_msg ) ; } #myprint( "PRESS ENTER" ) and my $a = <> ; + return( $new_id ) ; } } else{ $nb_msg_skipped_dry_mode += 1 ; - $h1_nb_msg_processed +=1 ; + $mysync->{ h1_nb_msg_processed } += 1 ; } return ; } -sub tests_sleep_if_needed { + +sub tests_sleep_if_needed +{ note( 'Entering tests_sleep_if_needed()' ) ; is( undef, sleep_if_needed( ), 'sleep_if_needed: no args => undef' ) ; @@ -6925,7 +12374,7 @@ sub tests_sleep_if_needed { $mysync->{maxsleep} = 0.1 ; $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again is( '0.10', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 4000 since 2s but maxsleep 0.1s => sleep 0.1s' ) ; - + $mysync->{maxbytesafter} = 4000 ; $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytesafter == total_bytes_transferred => no sleep => 0' ) ; @@ -6935,23 +12384,27 @@ sub tests_sleep_if_needed { } -sub sleep_if_needed { +sub sleep_if_needed +{ my( $mysync ) = shift ; - if ( ! $mysync ) { - return ; - } - # No need to go further if there is no limit set - if ( not ( $mysync->{maxmessagespersecond} - or $mysync->{maxbytespersecond} ) - ) { - return ; - } - + if ( ! $mysync ) { + return ; + } + # No need to go further if there is no limit set + if ( + not ( + $mysync->{maxmessagespersecond} + or $mysync->{maxbytespersecond} + ) + ) { + return ; + } + $mysync->{maxsleep} = defined $mysync->{maxsleep} ? $mysync->{maxsleep} : $MAX_SLEEP ; # Must be positive $mysync->{maxsleep} = max( 0, $mysync->{maxsleep} ) ; - + my $time_spent = timesince( $mysync->{begin_transfer_time} ) ; my $sleep_max_messages = sleep_max_messages( $mysync->{nb_msg_transferred}, $time_spent, $mysync->{maxmessagespersecond} ) ; @@ -6975,7 +12428,8 @@ sub sleep_if_needed { return 0 ; } -sub sleep_max_messages { +sub sleep_max_messages +{ # how long we have to sleep to go under max_messages_per_second my( $nb_msg_transferred, $time_spent, $maxmessagespersecond ) = @_ ; if ( ( not defined $maxmessagespersecond ) or $maxmessagespersecond <= 0 ) { return( 0 ) } ; @@ -6985,7 +12439,8 @@ sub sleep_max_messages { } -sub tests_sleep_max_messages { +sub tests_sleep_max_messages +{ note( 'Entering tests_sleep_max_messages()' ) ; ok( 0 == sleep_max_messages( 4, 2, undef ), 'sleep_max_messages: maxmessagespersecond = undef') ; @@ -7000,7 +12455,8 @@ sub tests_sleep_max_messages { } -sub sleep_max_bytes { +sub sleep_max_bytes +{ # how long we have to sleep to go under max_bytes_per_second my( $total_bytes_to_consider, $time_spent, $maxbytespersecond ) = @_ ; $total_bytes_to_consider ||= 0 ; @@ -7014,8 +12470,9 @@ sub sleep_max_bytes { } -sub tests_sleep_max_bytes { - note( 'Entering tests_sleep_max_bytes()' ) ; +sub tests_sleep_max_bytes +{ + note( 'Entering tests_sleep_max_bytes()' ) ; ok( 0 == sleep_max_bytes( 4000, 2, undef ), 'sleep_max_bytes: maxbytespersecond == undef => sleep 0' ) ; ok( 0 == sleep_max_bytes( 4000, 2, 0 ), 'sleep_max_bytes: maxbytespersecond = 0 => sleep 0') ; @@ -7026,61 +12483,268 @@ sub tests_sleep_max_bytes { ok( 0 == sleep_max_bytes( 2000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ; ok( 0 == sleep_max_bytes( -2000, 2, 1000 ), 'sleep_max_bytes: maxbytespersecond = 1k max not reached => sleep 0') ; - note( 'Leaving tests_sleep_max_bytes()' ) ; + note( 'Leaving tests_sleep_max_bytes()' ) ; + return ; +} + + +sub delete_message_on_host1 +{ + my( $mysync, $h1_fold, $expunge, @h1_msg ) = @_ ; + if ( ! $mysync->{ delete1 } ) { return ; } + if ( ! @h1_msg ) { return ; } + delete_messages_on_any( + $mysync, + $mysync->{ acc1 }, + $mysync->{ imap1 }, + "Host1: $h1_fold", + $expunge, + $split1, + @h1_msg ) ; return ; } +sub tests_operators_and_exclam_precedence +{ + note( 'Entering tests_operators_and_exclam_precedence()' ) ; + + is( 1, ! 0, 'tests_operators_and_exclam_precedence: ! 0 => 1' ) ; + is( "", ! 1, 'tests_operators_and_exclam_precedence: ! 1 => ""' ) ; + is( 1, not( 0 ), 'tests_operators_and_exclam_precedence: not( 0 ) => 1' ) ; + is( "", not( 1 ), 'tests_operators_and_exclam_precedence: not( 1 ) => ""' ) ; + + # I wrote those tests to avoid perlcrit "Mixed high and low-precedence booleans" + # and change sub delete_messages_on_any() but got 4 more warnings... So now commented. + #is( 0, ( ! 0 and 0 ), 'tests_operators_and_exclam_precedence: ! 0 and 0 ) => 0' ) ; + #is( 1, ( ! 0 and 1 ), 'tests_operators_and_exclam_precedence: ! 0 and 1 ) => 1' ) ; + #is( "", ( ! 1 and 0 ), 'tests_operators_and_exclam_precedence: ! 1 and 0 ) => ""' ) ; + #is( "", ( ! 1 and 1 ), 'tests_operators_and_exclam_precedence: ! 1 and 1 ) => ""' ) ; + is( 0, ( ! 0 && 0 ), 'tests_operators_and_exclam_precedence: ! 0 && 0 ) => 0' ) ; + is( 1, ( ! 0 && 1 ), 'tests_operators_and_exclam_precedence: ! 0 && 1 ) => 1' ) ; + is( "", ( ! 1 && 0 ), 'tests_operators_and_exclam_precedence: ! 1 && 0 ) => ""' ) ; + is( "", ( ! 1 && 1 ), 'tests_operators_and_exclam_precedence: ! 1 && 1 ) => ""' ) ; -# 6 GlobVar: $sync $imap1 $h1_nb_msg_deleted $expunge1 -sub delete_message_on_host1 { - my( $h1_msg, $h1_fold ) = @_ ; + is( 2, ( ! 0 && 2 ), 'tests_operators_and_exclam_precedence: ! 0 && 2 ) => 1' ) ; + + note( 'Leaving tests_operators_and_exclam_precedence()' ) ; + return ; +} + + +sub delete_messages_on_any +{ + # $acc is not used yet, + # + my( $mysync, $acc, $imap, $hostX_folder, $expunge, $split, @messages ) = @_ ; my $expunge_message = q{} ; - $expunge_message = 'and expunged' if ( $expungeaftereach and $expunge1 ) ; - myprint( "Host1 msg $h1_fold/$h1_msg marked deleted $expunge_message $sync->{dry_message}\n" ) ; - if ( ! $sync->{dry} ) { - $imap1->delete_message( $h1_msg ) ; - $h1_nb_msg_deleted += 1 ; - $imap1->expunge( ) if ( $expungeaftereach and $expunge1 ) ; + + my $dry_message = $mysync->{ dry_message } ; + $expunge_message = 'and expunged' if ( $expunge ) ; + # "Host1: msg " + + # $imap->Debug( 1 ) ; + + my @messages_to_mark_deleted = @messages ; + while ( my @messages_part = splice @messages_to_mark_deleted, 0, $split ) + { + foreach my $message ( @messages_part ) + { + myprint( "$hostX_folder/$message marking deleted $expunge_message $dry_message\n" ) ; + } + if ( ! $mysync->{dry} && @messages_part ) + { + my $nb_deleted = $imap->delete_message( $imap->Range( @messages_part ) ) ; + if ( defined $nb_deleted ) + { + # $nb_deleted is not accurate + $acc->{ nb_msg_deleted } += scalar @messages_part ; + } + else + { + my $error_imap = $imap->LastError || q{} ; + my $error = join( q{}, "$hostX_folder folder, could not delete ", + scalar @messages_part, ' messages: ', $error_imap, "\n" ) ; + errors_incr( $mysync, $error ) ; + } + } + } + + if ( $expunge ) { + uidexpunge_or_expunge( $mysync, $imap, @messages ) ; } + + #$imap->Debug( 0 ) ; + return ; } -sub eta { - my( $my_time_spent, $h1_nb_processed, $my_h1_nb_msg_start, $nb_transferred ) = @_ ; - return( q{} ) if not $foldersizes ; +sub tests_uidexpunge_or_expunge +{ + note( 'Entering tests_uidexpunge_or_expunge()' ) ; + + + is( undef, uidexpunge_or_expunge( ), 'uidexpunge_or_expunge: no args => undef' ) ; + my $mysync ; + is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: undef args => undef' ) ; + $mysync = {} ; + is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: arg empty => undef' ) ; + my $imap ; + is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: undef Mail-IMAPClient instance => undef' ) ; + + require_ok( "Test::MockObject" ) ; + $imap = Test::MockObject->new( ) ; + is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: no message (1) to uidexpunge => undef' ) ; + + my @messages = ( ) ; + is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: no message (2) to uidexpunge => undef' ) ; + + @messages = ( '2', '1' ) ; + $imap->mock( 'uidexpunge', sub { return ; } ) ; + $imap->mock( 'expunge', sub { return ; } ) ; + is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge failure => undef' ) ; - my $time_remaining = time_remaining( $my_time_spent, $h1_nb_processed, $my_h1_nb_msg_start, $nb_transferred ) ; - my $nb_msg_remaining = $my_h1_nb_msg_start - $h1_nb_processed ; - my $eta_date = localtime( time + $time_remaining ) ; - return( mysprintf( 'ETA: %s %1.0f s %s/%s msgs left', $eta_date, $time_remaining, $nb_msg_remaining, $my_h1_nb_msg_start ) ) ; + $imap->mock( 'expunge', sub { return 1 ; } ) ; + is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge ok => 1' ) ; + + $imap->mock( 'uidexpunge', sub { return 1 ; } ) ; + is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: messages to uidexpunge ok => 1' ) ; + + note( 'Leaving tests_uidexpunge_or_expunge()' ) ; + return ; } -sub time_remaining { +sub uidexpunge_or_expunge +{ + my $mysync = shift ; + my $imap = shift ; + my @messages = @ARG ; - my( $my_time_spent, $h1_nb_processed, $my_h1_nb_msg_start, $nb_transferred ) = @_ ; + if ( ! $imap ) { return ; } ; + if ( ! @messages ) { return ; } ; - my $time_remaining = ( $my_time_spent / $nb_transferred ) * ( $my_h1_nb_msg_start - $h1_nb_processed ) ; - return( $time_remaining ) ; + # Doing uidexpunge + my @uidexpunge_result = $imap->uidexpunge( @messages ) ; + if ( @uidexpunge_result ) { + return 1 ; + } + # Failure so doing expunge + my $expunge_result = $imap->expunge( ) ; + if ( $expunge_result ) { + return 1 ; + } + # bad trip + return ; +} + +sub eta_print +{ + my $mysync = shift ; + if ( my $eta = eta( $mysync ) ) + { + myprint( "$eta\n" ) ; + } + return ; +} + +sub tests_eta +{ + note( 'Entering tests_eta()' ) ; + + is( q{}, eta( ), 'eta: no args => ""' ) ; + is( q{}, eta( undef ), 'eta: undef => ""' ) ; + my $mysync = {} ; + # No foldersizes + is( q{}, eta( $mysync ), 'eta: No foldersizes => ""' ) ; + + $mysync->{ foldersizes } = 1 ; + + $mysync->{ begin_transfer_time } = time ; # Now + $mysync->{ h1_nb_msg_processed } = 0 ; + + is( "ETA: " . localtimez( time ) . " 0 s 0/0 msgs left", + eta( $mysync ), + 'eta: no args => ETA: "Now" 0 s 0/0 msgs left' ) ; + + $mysync->{ h1_nb_msg_processed } = 1 ; + $mysync->{ h1_nb_msg_start } = 2 ; + is( "ETA: " . localtimez( time ) . " 0 s 1/2 msgs left", + eta( $mysync ), + 'eta: 1, 1, 2 => ETA: "Now" 0 s 1/2 msgs left' ) ; + + note( 'Leaving tests_eta()' ) ; + return ; +} + + +sub eta +{ + my( $mysync ) = shift ; + + if ( ! $mysync ) + { + return q{} ; + } + + return( q{} ) if not $mysync->{ foldersizes } ; + + my $h1_nb_msg_start = $mysync->{ h1_nb_msg_start } ; + my $h1_nb_processed = $mysync->{ h1_nb_msg_processed } ; + my $nb_msg_transferred = ( $mysync->{dry} ) ? $mysync->{ h1_nb_msg_processed } : $mysync->{ nb_msg_transferred } ; + my $time_spent = timesince( $mysync->{ begin_transfer_time } ) ; + $h1_nb_processed ||= 0 ; + $h1_nb_msg_start ||= 0 ; + $time_spent ||= 0 ; + + my $time_remaining = time_remaining( $time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_msg_transferred ) ; + $mysync->{ debug } and myprint( "time_spent: $time_spent time_remaining: $time_remaining\n" ) ; + my $nb_msg_remaining = $h1_nb_msg_start - $h1_nb_processed ; + my $eta_date = localtimez( time + $time_remaining ) ; + return( mysprintf( 'ETA: %s %1.0f s %s/%s msgs left', + $eta_date, $time_remaining, $nb_msg_remaining, $h1_nb_msg_start ) ) ; } -sub tests_time_remaining { - note( 'Entering tests_time_remaining()' ) ; - ok( 1 == time_remaining( 1, 1, 2, 1 ), 'time_remaining: 1, 1, 2, 1 -> 1' ) ; - ok( 1 == time_remaining( 9, 9, 10, 9 ), 'time_remaining: 9, 9, 10, 9 -> 1' ) ; - ok( 9 == time_remaining( 1, 1, 10, 1 ), 'time_remaining: 1, 1, 10, 1 -> 1' ) ; +sub time_remaining +{ + + my( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) = @_ ; + + $nb_transferred ||= 1 ; # At least one is done (no division by zero) + $h1_nb_processed ||= 0 ; + $h1_nb_msg_start ||= $h1_nb_processed ; + $my_time_spent ||= 0 ; - note( 'Leaving tests_time_remaining()' ) ; + my $time_remaining = ( $my_time_spent / $nb_transferred ) * ( $h1_nb_msg_start - $h1_nb_processed ) ; + return( $time_remaining ) ; +} + + +sub tests_time_remaining +{ + note( 'Entering tests_time_remaining()' ) ; + + # time_spent, nb_processed, nb_to_do_total, nb_transferred + is( 0, time_remaining( ), 'time_remaining: no args -> 0' ) ; + is( 0, time_remaining( 0, 0, 0, 0 ), 'time_remaining: 0, 0, 0, 0 -> 0' ) ; + is( 1, time_remaining( 1, 1, 2, 1 ), 'time_remaining: 1, 1, 2, 1 -> 1' ) ; + is( 1, time_remaining( 9, 9, 10, 9 ), 'time_remaining: 9, 9, 10, 9 -> 1' ) ; + is( 9, time_remaining( 1, 1, 10, 1 ), 'time_remaining: 1, 1, 10, 1 -> 9' ) ; + is( 5, time_remaining( 5, 5, 10, 5 ), 'time_remaining: 5, 5, 10, 5 -> 5' ) ; + is( 25, time_remaining( 5, 5, 10, 0 ), 'time_remaining: 5, 5, 10, 0 -> ( 5 / 1 ) * ( 10 - 5) = 25' ) ; + is( 25, time_remaining( 5, 5, 10, 1 ), 'time_remaining: 5, 5, 10, 1 -> ( 5 / 1 ) * ( 10 - 5) = 25' ) ; + + note( 'Leaving tests_time_remaining()' ) ; return ; } -sub cache_map { +sub cache_map +{ my ( $cache_files_ref, $h1_msgs_ref, $h2_msgs_ref ) = @_; my ( %map1_2, %map2_1, %done2 ) ; @@ -7121,7 +12785,8 @@ sub cache_map { return( \%map1_2, \%map2_1) ; } -sub tests_cache_map { +sub tests_cache_map +{ note( 'Entering tests_cache_map()' ) ; #$debugcache = 1 ; @@ -7163,14 +12828,16 @@ sub tests_cache_map { } -sub cache_dir_fix { +sub cache_dir_fix +{ my $cache_dir = shift ; $cache_dir =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"\\])/\\$1/xg ; #myprint( "cache_dir_fix: $cache_dir\n" ) ; return( $cache_dir ) ; } -sub tests_cache_dir_fix { +sub tests_cache_dir_fix +{ note( 'Entering tests_cache_dir_fix()' ) ; ok( 'lalala' eq cache_dir_fix('lalala'), 'cache_dir_fix: lalala -> lalala' ); @@ -7187,14 +12854,16 @@ sub tests_cache_dir_fix { return ; } -sub cache_dir_fix_win { +sub cache_dir_fix_win +{ my $cache_dir = shift ; $cache_dir =~ s/(\[|\])/[$1]/xg ; #myprint( "cache_dir_fix_win: $cache_dir\n" ) ; return( $cache_dir ) ; } -sub tests_cache_dir_fix_win { +sub tests_cache_dir_fix_win +{ note( 'Entering tests_cache_dir_fix_win()' ) ; ok( 'lalala' eq cache_dir_fix_win('lalala'), 'cache_dir_fix_win: lalala -> lalala' ); @@ -7207,13 +12876,14 @@ sub tests_cache_dir_fix_win { -sub get_cache { +sub get_cache +{ my ( $cache_dir, $h1_msgs_ref, $h2_msgs_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_; $debugcache and myprint( "Entering get_cache\n" ) ; -d $cache_dir or return( undef ); # exit if cache directory doesn't exist - $debugcache and myprint( "cache_dir : $cache_dir\n" ) ; + $debugcache and myprint( "cache_dir : $cache_dir\n" ) ; if ( 'MSWin32' ne $OSNAME ) { @@ -7239,7 +12909,8 @@ sub get_cache { } -sub tests_get_cache { +sub tests_get_cache +{ note( 'Entering tests_get_cache()' ) ; ok( not( get_cache('/cache_no_exist') ), 'get_cache: /cache_no_exist' ); @@ -7330,7 +13001,8 @@ sub tests_get_cache { return ; } -sub match_a_cache_file { +sub match_a_cache_file +{ my $file = shift ; my ( $cache_uid1, $cache_uid2 ) ; @@ -7342,7 +13014,8 @@ sub match_a_cache_file { return( $cache_uid1, $cache_uid2 ) ; } -sub tests_match_a_cache_file { +sub tests_match_a_cache_file +{ note( 'Entering tests_match_a_cache_file()' ) ; my ( $tuid1, $tuid2 ) ; @@ -7378,7 +13051,8 @@ sub tests_match_a_cache_file { return ; } -sub clean_cache { +sub clean_cache +{ my ( $cache_files_ref, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_ ; $debugcache and myprint( "Entering clean_cache\n" ) ; @@ -7404,7 +13078,8 @@ sub clean_cache { return( 1 ) ; } -sub tests_clean_cache { +sub tests_clean_cache +{ note( 'Entering tests_clean_cache()' ) ; ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache: rmtree W/tmp/cache/G1/G2' ) ; @@ -7458,7 +13133,8 @@ sub tests_clean_cache { return ; } -sub tests_clean_cache_2 { +sub tests_clean_cache_2 +{ note( 'Entering tests_clean_cache_2()' ) ; ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache_2: rmtree W/tmp/cache/G1/G2' ) ; @@ -7518,19 +13194,20 @@ sub tests_clean_cache_2 { -sub tests_mkpath { +sub tests_mkpath +{ note( 'Entering tests_mkpath()' ) ; ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'mkpath: mkpath W/tmp/tests/' ) ; - + SKIP: { skip( 'Tests only for Unix', 10 ) if ( 'MSWin32' eq $OSNAME ) ; my $long_path_unix = '123456789/' x 30 ; - ok( ( -d "W/tmp/tests/long/$long_path_unix" or mkpath( "W/tmp/tests/long/$long_path_unix" ) ), 'mkpath: mkpath 300 char' ) ; + ok( ( -d "W/tmp/tests/long/$long_path_unix" or mkpath( "W/tmp/tests/long/$long_path_unix" ) ), 'mkpath: mkpath 300 char' ) ; ok( -d "W/tmp/tests/long/$long_path_unix", 'mkpath: mkpath > 300 char verified' ) ; ok( ( -d "W/tmp/tests/long/$long_path_unix" and rmtree( 'W/tmp/tests/long/' ) ), 'mkpath: rmtree 300 char' ) ; ok( ! -d "W/tmp/tests/long/$long_path_unix", 'mkpath: rmtree 300 char verified' ) ; - + ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ; ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ; ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ; @@ -7559,7 +13236,7 @@ sub tests_mkpath { # Without the eval the following mkpath 300 just kill the whole process without a whisper #myprint( "$long_path_300\n" ) ; - eval { ok( ( -d $long_path_300 or mkpath( $long_path_300 ) ), 'mkpath: create a path with 300 characters' ) ; } + eval { ok( ( -d $long_path_300 or mkpath( $long_path_300 ) ), 'mkpath: create a path with 300 characters' ) ; } or ok( 1, 'mkpath: can not create a path with 300 characters' ) ; ok( ( ( ! -d $long_path_300 ) or -d $long_path_300 and rmtree( $long_path_300 ) ), 'mkpath: rmtree the 300 character path' ) ; ok( 1, 'mkpath: still alive' ) ; @@ -7568,8 +13245,8 @@ sub tests_mkpath { ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ; ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ; ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ; - - + + } ; note( 'Leaving tests_mkpath()' ) ; @@ -7577,7 +13254,8 @@ sub tests_mkpath { return 1 ; } -sub tests_touch { +sub tests_touch +{ note( 'Entering tests_touch()' ) ; ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'touch: mkpath W/tmp/tests/' ) ; @@ -7592,7 +13270,8 @@ sub tests_touch { } -sub touch { +sub touch +{ my @files = @_ ; my $failures = 0 ; @@ -7609,7 +13288,9 @@ sub touch { } -sub tests_tmpdir_has_colon_bug { + +sub tests_tmpdir_has_colon_bug +{ note( 'Entering tests_tmpdir_has_colon_bug()' ) ; ok( 0 == tmpdir_has_colon_bug( q{} ), 'tmpdir_has_colon_bug: ' ) ; @@ -7621,7 +13302,8 @@ sub tests_tmpdir_has_colon_bug { return ; } -sub tmpdir_has_colon_bug { +sub tmpdir_has_colon_bug +{ my $path = shift ; my $path_filtered = filter_forbidden_characters( $path ) ; @@ -7632,7 +13314,8 @@ sub tmpdir_has_colon_bug { return( 0 ) ; } -sub tmpdir_fix_colon_bug { +sub tmpdir_fix_colon_bug +{ my $mysync = shift ; my $err = 0 ; if ( not (-d $mysync->{ tmpdir } and -r _ and -w _) ) { @@ -7672,17 +13355,17 @@ sub tmpdir_fix_colon_bug { myprint( "Old cache directory $cachedir_old still exists\n" ) ; $err++ ; }else{ - myprint( "Old cache directory $cachedir_old successfuly moved\n" ) ; + myprint( "Old cache directory $cachedir_old successfully moved\n" ) ; } } return( not $err ) ; } -sub tests_cache_folder { +sub tests_cache_folder +{ note( 'Entering tests_cache_folder()' ) ; - ok( '/path/fold1/fold2' eq cache_folder( q{}, '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ; ok( '/pa_th/fold1/fold2' eq cache_folder( q{}, '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ; ok( '/_p_a__th/fol_d1/fold2' eq cache_folder( q{}, '/>pp /path/fol_d1/fold2' ) ; @@ -7697,11 +13380,12 @@ sub tests_cache_folder { return ; } -sub cache_folder { +sub cache_folder +{ my( $cache_base, $cache_dir, $h1_fold, $h2_fold ) = @_ ; - my $sep_1 = $h1_sep || '/'; - my $sep_2 = $h2_sep || '/'; + my $sep_1 = $sync->{ h1_sep } || '/'; + my $sep_2 = $sync->{ h2_sep } || '/'; #myprint( "$cache_dir h1_fold $h1_fold sep1 $sep_1 h2_fold $h2_fold sep2 $sep_2\n" ) ; $h1_fold = convert_sep_to_slash( $h1_fold, $sep_1 ) ; @@ -7712,58 +13396,54 @@ sub cache_folder { return( $cache_folder ) ; } -sub filter_forbidden_characters { - my $string = shift ; +sub tests_filter_forbidden_characters +{ + note( 'Entering tests_filter_forbidden_characters()' ) ; - if ( ! defined $string ) { return ; } + is( undef , filter_forbidden_characters( ), 'filter_forbidden_characters: no args -> undef' ) ; - if ( 'MSWin32' eq $OSNAME ) { - # Move trailing whitespace to _ " a b /c d " -> " a b_/c d_" - $string =~ s{\ (/|$)}{_$1}xg ; - } - $string =~ s{[\Q*|?:"<>\E\t\r\n\\]}{_}xg ; - #myprint( "[$string]\n" ) ; - return( $string ) ; -} + is( 'a_b' , filter_forbidden_characters( 'a_b' ), 'filter_forbidden_characters: a_b -> a_b' ) ; + is( 'a_b' , filter_forbidden_characters( 'a*b' ), 'filter_forbidden_characters: a*b -> a_b' ) ; + is( 'a_b' , filter_forbidden_characters( 'a|b' ), 'filter_forbidden_characters: a|b -> a_b' ) ; + is( 'a_b' , filter_forbidden_characters( 'a?b' ), 'filter_forbidden_characters: a?b -> a_b' ) ; + is( 'a________b', filter_forbidden_characters( q{a*|?:"<>'b} ), q{filter_forbidden_characters: a*|?:"<>'b -> a________b} ) ; -sub tests_filter_forbidden_characters { - note( 'Entering tests_filter_forbidden_characters()' ) ; + is( 'a_b_' , filter_forbidden_characters( 'a b ' ), 'filter_forbidden_characters: "a b " -> "a_b_"' ) ; - ok( 'a_b' eq filter_forbidden_characters( 'a_b' ), 'filter_forbidden_characters: a_b -> a_b' ) ; - ok( 'a_b' eq filter_forbidden_characters( 'a*b' ), 'filter_forbidden_characters: a*b -> a_b' ) ; - ok( 'a_b' eq filter_forbidden_characters( 'a|b' ), 'filter_forbidden_characters: a|b -> a_b' ) ; - ok( 'a_b' eq filter_forbidden_characters( 'a?b' ), 'filter_forbidden_characters: a?b -> a_b' ) ; - ok( 'a_______b' eq filter_forbidden_characters( 'a*|?:"<>b' ), 'filter_forbidden_characters: a*|?:"<>b -> a_______b' ) ; - SKIP: { - skip( 'Not on MSWin32', 1 ) if ( 'MSWin32' eq $OSNAME ) ; - ok( ( 'a b ' eq filter_forbidden_characters( 'a b ' ) ), 'filter_forbidden_characters: "a b " -> "a b "' ) ; - } ; + is( 'a_b' , filter_forbidden_characters( "a\tb" ), 'filter_forbidden_characters: a\tb -> a_b' ) ; + is( "a_b" , filter_forbidden_characters( "a\rb" ), 'filter_forbidden_characters: a\rb -> a_b' ) ; + is( "a_b" , filter_forbidden_characters( "a\nb" ), 'filter_forbidden_characters: a\nb -> a_b' ) ; + is( "a_b" , filter_forbidden_characters( "a\\b" ), 'filter_forbidden_characters: a\b -> a_b' ) ; - SKIP: { - skip( 'Only on MSWin32', 2 ) if ( 'MSWin32' ne $OSNAME ) ; - ok( ( ' a b_' eq filter_forbidden_characters( ' a b ' ) ), 'filter_forbidden_characters: "a b " -> "a b_"' ) ; - ok( ( ' a b_/ c d_' eq filter_forbidden_characters( ' a b / c d ' ) ), 'filter_forbidden_characters: " a b / c d " -> "a b_/ c d_"' ) ; - } ; + is( 'a-b' , filter_forbidden_characters( 'a-b' ), 'filter_forbidden_characters: a-b -> a-b' ) ; + is( 'a__-__-__-__-__b' , filter_forbidden_characters( 'aรƒยฉ-รƒยจ-รƒ -รƒยง-รƒโ€“b' ), 'filter_forbidden_characters: aรƒยฉ-รƒยจ-รƒ -รƒยง-รƒโ€“b -> a__-__-__-__-__b' ) ; + + is( 'abcdABCDwxyzWXYZ012789' , filter_forbidden_characters( 'abcdABCDwxyzWXYZ012789' ), + 'filter_forbidden_characters: abcdABCDwxyzWXYZ012789 -> abcdABCDwxyzWXYZ012789' ) ; - ok( 'a_b' eq filter_forbidden_characters( "a\tb" ), 'filter_forbidden_characters: a\tb -> a_b' ) ; - ok( "a_b" eq filter_forbidden_characters( "a\rb" ), 'filter_forbidden_characters: a\rb -> a_b' ) ; - ok( "a_b" eq filter_forbidden_characters( "a\nb" ), 'filter_forbidden_characters: a\nb -> a_b' ) ; - ok( "a_b" eq filter_forbidden_characters( "a\\b" ), 'filter_forbidden_characters: a\b -> a_b' ) ; - note( 'Leaving tests_filter_forbidden_characters()' ) ; + note( 'Leaving tests_filter_forbidden_characters()' ) ; return ; } -sub convert_sep_to_slash { - my ( $folder, $sep ) = @_ ; +sub filter_forbidden_characters +{ + my $string = shift ; - $folder =~ s{\Q$sep\E}{/}xg ; - return( $folder ) ; + if ( ! defined $string ) { return ; } + + $string =~ s{[\Q*|?:"<>' \E\t\r\n\\]}{_}xg ; + # replace all non-ascii and control characters by _ + $string =~ s/[[:^ascii:][:cntrl:]]/_/xg ; + + #myprint( "[$string]\n" ) ; + return( $string ) ; } -sub tests_convert_sep_to_slash { +sub tests_convert_sep_to_slash +{ note( 'Entering tests_convert_sep_to_slash()' ) ; @@ -7779,60 +13459,69 @@ sub tests_convert_sep_to_slash { return ; } +sub convert_sep_to_slash +{ + my ( $folder, $sep ) = @_ ; + + $folder =~ s{\Q$sep\E}{/}xg ; + return( $folder ) ; +} + -sub tests_regexmess { - note( 'Entering tests_regexmess()' ) ; +sub tests_regexmess +{ + note( 'Entering tests_regexmess()' ) ; - ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess, no regexmess, nothing to do' ) ; + ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess: no regexmess, nothing to do' ) ; @regexmess = ( 'lalala' ) ; - ok( not( defined regexmess( 'popopo' ) ), 'regexmess, bad regex lalala' ) ; + ok( not( defined regexmess( 'popopo' ) ), 'regexmess: bad regex lalala' ) ; @regexmess = ( 's/p/Z/g' ) ; - ok( 'ZoZoZo' eq regexmess( 'popopo' ), 'regexmess, s/p/Z/g' ) ; + ok( 'ZoZoZo' eq regexmess( 'popopo' ), 'regexmess: s/p/Z/g' ) ; @regexmess = ( 's{c}{C}gxms' ) ; ok("H1: abC\nH2: Cde\n\nBody abC" eq regexmess( "H1: abc\nH2: cde\n\nBody abc"), - 'regexmess, c->C'); + 'regexmess: c->C'); @regexmess = ( 's{\AFrom\ }{From:}gxms' ) ; ok( q{} eq regexmess(q{}), - 'From mbox 1 add colon blank'); + 'regexmess: From mbox 1 add colon blank'); ok( 'From:' eq regexmess('From '), - 'From mbox 2 add colo'); + 'regexmess: From mbox 2 add colo'); ok( "\n" . 'From ' eq regexmess("\n" . 'From '), - 'From mbox 3 add colo') ; + 'regexmess: From mbox 3 add colo') ; ok( "From: zzz\n" . 'From ' eq regexmess("From zzz\n" . 'From '), - 'From mbox 4 add colo') ; + 'regexmess: From mbox 4 add colo') ; @regexmess = ( 's{\AFrom\ [^\n]*(\n)?}{}gxms' ) ; ok( q{} eq regexmess(q{}), - 'From mbox 1 remove, blank'); + 'regexmess: From mbox 1 remove, blank'); ok( q{} eq regexmess('From '), - 'From mbox 2 remove'); + 'regexmess: From mbox 2 remove'); ok( "\n" . 'From ' eq regexmess("\n" . 'From '), - 'From mbox 3 remove'); + 'regexmess: From mbox 3 remove'); - #myprint( "[", regexmess("From zzz\n" . 'From '), "]" ) ; + #myprint( "[", regexmess("From zzz\n" . 'From '), "]" ) ; ok( q{} . 'From ' eq regexmess("From zzz\n" . 'From '), - 'From mbox 4 remove'); + 'regexmess: From mbox 4 remove'); - ok( + is( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: @@ -7840,7 +13529,7 @@ From: Hello, Bye. EOM - eq regexmess( + , regexmess( <<'EOM' From zzz Date: Sat, 10 Jul 2010 05:34:45 -0700 @@ -7849,7 +13538,7 @@ From: Hello, Bye. EOM -), 'From mbox 5 remove'); + ), 'regexmess: From mbox 5 remove'); @regexmess = ( 's{\A((?:[^\n]+\n)+|)^Disposition-Notification-To:[^\n]*\n(\r?\n|.*\n\r?\n)}{$1$2}xms' ) ; # SUPER SUPER BEST! @@ -7890,7 +13579,7 @@ Disposition-Notification-To: Gilles LAMIRAL Hello, Bye. EOM -), + ), 'regexmess: 2 Delete header Disposition-Notification-To:'); ok( @@ -7910,7 +13599,7 @@ From: Hello, Bye. EOM -), + ), 'regexmess: 3 Delete header Disposition-Notification-To:'); ok( @@ -7930,7 +13619,7 @@ From: Disposition-Notification-To: Gilles LAMIRAL Bye. EOM -), + ), 'regexmess: 4 Delete header Disposition-Notification-To:'); @@ -7950,11 +13639,11 @@ From: Disposition-Notification-To: Gilles LAMIRAL Bye. EOM -), + ), 'regexmess: 5 Delete header Disposition-Notification-To:'); -ok( + ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: @@ -7972,10 +13661,10 @@ Hello, Disposition-Notification-To: Gilles LAMIRAL Bye. EOM -), + ), 'regexmess: 6 Delete header Disposition-Notification-To:'); -ok( + ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: @@ -7995,11 +13684,11 @@ Disposition-Notification-To: Gilles LAMIRAL Bye. EOM -), + ), 'regexmess: 7 Delete header Disposition-Notification-To:'); -ok( + ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: @@ -8019,7 +13708,7 @@ EOM 'regexmess: 8 Delete header Disposition-Notification-To:'); -ok( + ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: @@ -8037,12 +13726,12 @@ Hello, Disposition-Notification-To: Gilles LAMIRAL Bye. EOM -), + ), 'regexmess: 9 Delete header Disposition-Notification-To:'); -ok( + ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: @@ -8064,10 +13753,10 @@ Disposition-Notification-To: Gilles LAMIRAL Bye. EOM -), + ), 'regexmess: 10 Delete header Disposition-Notification-To:'); -ok( + ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: @@ -8092,7 +13781,7 @@ EOM ), 'regexmess: 11 Delete header Disposition-Notification-To:'); -ok( + ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: @@ -8118,15 +13807,15 @@ Disposition-Notification-To: Gilles LAMIRAL Bye. EOM -), + ), 'regexmess: 12 Delete header Disposition-Notification-To:'); -@regexmess = ( 's{\A(.*?(?! ^$))^Disposition-Notification-To:(.*?)$}{$1X-Disposition-Notification-To:$2}igxms' ) ; # BAD! -@regexmess = ( 's{\A((?:[^\n]+\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims' ) ; + @regexmess = ( 's{\A(.*?(?! ^$))^Disposition-Notification-To:(.*?)$}{$1X-Disposition-Notification-To:$2}igxms' ) ; # BAD! + @regexmess = ( 's{\A((?:[^\n]+\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims' ) ; -ok( + ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: @@ -8152,10 +13841,10 @@ Disposition-Notification-To: Gilles LAMIRAL Bye. EOM -), + ), 'regexmess: 13 Delete header Disposition-Notification-To:'); -ok( + ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 X-Disposition-Notification-To: Gilles LAMIRAL @@ -8183,10 +13872,10 @@ Disposition-Notification-To: Gilles LAMIRAL Bye. EOM -), + ), 'regexmess: 14 Delete header Disposition-Notification-To:'); -ok( + ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 X-Disposition-Notification-To: Gilles LAMIRAL @@ -8206,11 +13895,11 @@ Hello, Bye. EOM -), + ), 'regexmess: 15 Delete header Disposition-Notification-To:'); -ok( + ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: @@ -8230,10 +13919,10 @@ Hello, Bye. EOM -), + ), 'regexmess: 16 Delete header Disposition-Notification-To:'); -ok( + ok( <<'EOM' X-Disposition-Notification-To: Gilles LAMIRAL Date: Sat, 10 Jul 2010 05:34:45 -0700 @@ -8256,33 +13945,310 @@ EOM ), 'regexmess: 17 Delete header Disposition-Notification-To:'); + @regexmess = ( 's/.{11}\K.*//gs' ) ; + is( "0123456789\n", regexmess( "0123456789\n" x 100 ), 'regexmess: truncate whole message after 11 characters' ) ; + is( "0123456789\n", regexmess( "0123456789\n" x 100_000 ), 'regexmess: truncate whole message after 11 characters ~ 1MB' ) ; + + @regexmess = ( 's/.{10000}\K.*//gs' ) ; + is( "123456789\n" x 1000, regexmess( "123456789\n" x 100_000 ), 'regexmess: truncate whole message after 10000 characters ~ 1MB' ) ; + + @regexmess = ( 's/^(X-Ham-Report.*?\n)^X-/X-/sm' ) ; + + is( +<<'EOM' +X-Spam-Score: -1 +X-Spam-Bar: / +X-Spam-Flag: NO +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: + +Hello, + +Bye. +EOM +, + regexmess( +<<'EOM' +X-Spam-Score: -1 +X-Spam-Bar: / +X-Ham-Report: =?utf-8?Q?Spam_detection_software=2C_running?= + =?utf-8?Q?_on_the_system_=22ohp-ag006.int200?= +_has_NOT_identified_thi?= + =?utf-8?Q?s_incoming_email_as_spam.__The_o?= +_message_has_been_attac?= + =?utf-8?Q?hed_to_this_so_you_can_view_it_o?= +___________________________?= + =?utf-8?Q?__author's_domain +X-Spam-Flag: NO +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: + +Hello, + +Bye. +EOM + ), + 'regexmess: Delete header X-Ham-Report:'); # regex to play with Date: from the FAQ #@regexmess = 's{\A(.*?(?! ^$))^Date:(.*?)$}{$1Date:$2\nX-Date:$2}gxms' - note( 'Leaving tests_regexmess()' ) ; - return ; +# Change 8bit characters in whole email to X characters + @regexmess = ( 's{[\x80-\xff]}{X}gxms' ) ; + is( 'X-8bit: kaka 1 XX kiki', regexmess('X-8bit: kaka 1 ร‚ยค kiki'), 'regexmess: 1 Change 8bit characters in whole email to X characters'); + +# Same change but using tr + @regexmess = ( 'tr [\x80-\xff] [X]' ) ; + is( 'X-8bit: kaka 1 XXXX kiki', regexmess('X-8bit: kaka 1 ร‚ยคร‚ยฃ kiki'), 'regexmess: 2 Change 8bit characters in whole email to X characters, using tr'); + + + +# Add a final \r\n if missing + @regexmess = ( 's{(? +LaSuite: super + +Hello, +Bye. +EOM + , regexmess( +<<'EOM' +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: +X-Spam-Report: caca +caca + caca +caca +LaSuite: super + +Hello, +Bye. +EOM + ), 'regexmess: 1 remove buggy X-Spam-Report: across several lines, not the final header'); + + + is( +<<'EOM' +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: +LaSuite: super +LaSuite2: super 2 + +Hello, +Bye. +EOM + , regexmess( +<<'EOM' +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: +X-Spam-Report: caca +caca + caca +caca +LaSuite: super +LaSuite2: super 2 + +Hello, +Bye. +EOM + ), 'regexmess: 2 remove buggy X-Spam-Report: across several lines, not the final header'); + + + is( +<<'EOM' +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: +LaSuite: super +LaSuite2: super 2 + +Hello, +Bye. +EOM + , regexmess( +<<'EOM' +X-Spam-Report: caca +caca + caca +caca +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: +LaSuite: super +LaSuite2: super 2 + +Hello, +Bye. +EOM + ), 'regexmess: 3 remove buggy X-Spam-Report: across several lines, first header'); + + + + + is( +<<'EOM' +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: + +Hello, +Bye. +EOM + , regexmess( +<<'EOM' +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: +X-Spam-Report: caca +caca + caca +caca + +Hello, +Bye. +EOM + ), 'regexmess: 4 remove buggy X-Spam-Report: across several lines, final header'); + + + is( +<<'EOM' +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: + +Hello, +Bye. +EOM + , regexmess( +<<'EOM' +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: + +Hello, +Bye. +EOM + ), 'regexmess: 5 remove buggy X-Spam-Report: not there at all'); + + + is( +<<"EOM" +Date: Sat, 10 Jul 2010 05:34:45 -0700\r +From:\r +LaSuite: super\r +LaSuite2: super 2\r +\r +Hello,\r +Bye.\r +EOM + , regexmess( +<<"EOM" +X-Spam-Report: caca\r +caca\r + caca\r +caca\r +Date: Sat, 10 Jul 2010 05:34:45 -0700\r +From:\r +LaSuite: super\r +LaSuite2: super 2\r +\r +Hello,\r +Bye.\r +EOM + ), 'regexmess: 6 remove buggy X-Spam-Report: across several lines, first header, with \r'); + + + is( +<<"EOM" +Date: Sat, 10 Jul 2010 05:34:45 -0700\r +From:\r +LaSuite: super\r +LaSuite2: super 2\r +\r +Hello,\r +Bye.\r +EOM + , regexmess( +<<"EOM" +Date: Sat, 10 Jul 2010 05:34:45 -0700\r +From:\r +X-Spam-Report: caca\r +caca\r + caca\r +caca\r +LaSuite: super\r +LaSuite2: super 2\r +\r +Hello,\r +Bye.\r +EOM + ), 'regexmess: 7 remove buggy X-Spam-Report: across several lines, middle header, with \r'); + + + is( +<<"EOM" +Date: Sat, 10 Jul 2010 05:34:45 -0700\r +From:\r +\r +Hello,\r +Bye.\r +EOM + , regexmess( +<<"EOM" +Date: Sat, 10 Jul 2010 05:34:45 -0700\r +From:\r +X-Spam-Report: caca\r +caca\r + caca\r +caca\r +\r +Hello,\r +Bye.\r +EOM + ), 'regexmess: 8 remove buggy X-Spam-Report: across several lines, final header, with \r'); + + + undef @regexmess ; + note( 'Leaving tests_regexmess()' ) ; + return ; } -sub regexmess { +sub regexmess +{ my ( $string ) = @_ ; foreach my $regexmess ( @regexmess ) { - $debug and myprint( "eval \$string =~ $regexmess\n" ) ; + $sync->{ debug } and myprint( "eval \$string =~ $regexmess\n" ) ; my $ret = eval "\$string =~ $regexmess ; 1" ; - #myprint( "eval [$ret]\n" ) ; + #myprint( "eval [$ret]\n" ) ; if ( ( not $ret ) or $EVAL_ERROR ) { - myprint( "Error: eval regexmess '$regexmess': $EVAL_ERROR" ) ; + myprint( "Error: eval regexmess '$regexmess': $EVAL_ERROR" ) ; return( undef ) ; } } - $debug and myprint( "$string\n" ) ; + $sync->{ debug } and myprint( "$string\n" ) ; return( $string ) ; } -sub tests_skipmess { +sub tests_skipmess +{ note( 'Entering tests_skipmess()' ) ; ok( not( defined skipmess( 'blabla' ) ), 'skipmess, no skipmess, no skip' ) ; @@ -8495,25 +14461,99 @@ Content-Transfer-Encoding: 7bit\r EOM . qq{!#"d%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg\r\n } x 32_730 ), - 'skipmess: 12 not match Content-Type: Message/Partial' ) ; - # Complex regular subexpression recursion limit (32766) exceeded with more lines - # exit; + 'skipmess: 12 not match Content-Type: Message/Partial' ) ; + # Complex regular subexpression recursion limit (32766) exceeded with more lines + # exit; + + + undef @skipmess ; + note( 'Leaving tests_skipmess()' ) ; + return ; +} + + +sub tests_skipmess_neg +{ + note( 'Entering tests_skipmess_neg()' ) ; + + + @skipmess = ('m{i}') ; + ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ; + ok( 0 == skipmess( 'Ho!' ), 'skipmess: i string no' ) ; + + @skipmess = ('m{\A(?!.*i)}') ; + ok( 0 == skipmess( 'Hi!' ), 'skipmess: not i string no' ) ; + ok( 1 == skipmess( 'Ho!' ), 'skipmess: not i string yes' ) ; + + + @skipmess = ('m{\A(?!.*^From:[^\n]*tartanpion\@machin\.truc)}xms') ; + + ok( 0 == skipmess( +<<'EOM' +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: + +Bye. +EOM +), + 'skipmess: 1 not From tartanpion@machin.truc' ) ; + +ok( 1 == skipmess( +<<'EOM' +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: + +Bye. +EOM +), + 'skipmess: 2 not From tartanpion@machin.truc' ) ; + + + + + ok( 0 == skipmess( +<<'EOM' +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: + + From: +Bye. +EOM +), + 'skipmess: 3 not From tartanpion@machin.truc' ) ; + +ok( 1 == skipmess( +<<'EOM' +Date: Sat, 10 Jul 2010 05:34:45 -0700 +From: + + From: +Bye. +EOM +), + 'skipmess: 4 not From tartanpion@machin.truc' ) ; + - note( 'Leaving tests_skipmess()' ) ; + + + undef @skipmess ; + note( 'Leaving tests_skipmess_neg()' ) ; return ; } -sub skipmess { + +sub skipmess +{ my ( $string ) = @_ ; my $match ; - #myprint( "$string\n" ) ; + #myprint( "$string\n" ) ; foreach my $skipmess ( @skipmess ) { - $debug and myprint( "eval \$match = \$string =~ $skipmess\n" ) ; - my $ret = eval "\$match = \$string =~ $skipmess ; 1" ; - #myprint( "eval [$ret]\n" ) ; - $debug and myprint( "match [$match]\n" ) ; + $sync->{ debug } and myprint( "eval \$match = \$string =~ $skipmess\n" ) ; + my $ret = eval "\$match = \$string =~ $skipmess ; 1" ; + #myprint( "eval [$ret]\n" ) ; + $sync->{ debug } and myprint( "match [$match]\n" ) ; if ( ( not $ret ) or $EVAL_ERROR ) { - myprint( "Error: eval skipmess '$skipmess': $EVAL_ERROR" ) ; + myprint( "Error: eval skipmess '$skipmess': $EVAL_ERROR" ) ; return( undef ) ; } return( $match ) if ( $match ) ; @@ -8524,41 +14564,73 @@ sub skipmess { -sub tests_bytes_display_string { - note( 'Entering tests_bytes_display_string()' ) ; +sub tests_bytes_display_string_bin +{ + note( 'Entering tests_bytes_display_string_bin()' ) ; + + is( 'NA', bytes_display_string_bin( ), 'bytes_display_string_bin: no args => NA' ) ; + is( 'NA', bytes_display_string_bin( undef ), 'bytes_display_string_bin: undef => NA' ) ; + is( 'NA', bytes_display_string_bin( 'blabla' ), 'bytes_display_string_bin: blabla => NA' ) ; + is( '0.000 KiB', bytes_display_string_bin( 0 ), 'bytes_display_string_bin: 0 => 0.000 KiB' ) ; + is( '0.001 KiB', bytes_display_string_bin( 1 ), 'bytes_display_string_bin: 1 => 0.001 KiB' ) ; + is( '0.010 KiB', bytes_display_string_bin( 10 ), 'bytes_display_string_bin: 10 => 0.010 KiB' ) ; + is( '0.976 KiB', bytes_display_string_bin( 999 ), 'bytes_display_string_bin: 999 => 0.976 KiB' ) ; + note( bytes_display_string_bin( 999 ) ) ; - is( 'NA', bytes_display_string( ), 'bytes_display_string: no args => NA' ) ; - is( 'NA', bytes_display_string( undef ), 'bytes_display_string: undef => NA' ) ; - is( 'NA', bytes_display_string( 'blabla' ), 'bytes_display_string: blabla => NA' ) ; + is( '0.999 KiB', bytes_display_string_bin( 1023 ), 'bytes_display_string_bin: 1023 => 0.999 KiB' ) ; + note( bytes_display_string_bin( 1023 ) ) ; + is( '1.000 KiB', bytes_display_string_bin( 1024 ), 'bytes_display_string_bin: 1024 => 1.000 KiB' ) ; + note( bytes_display_string_bin( 1024 ) ) ; + is( '1.001 KiB', bytes_display_string_bin( 1025 ), 'bytes_display_string_bin: 1025 => 1.001 KiB' ) ; - ok( '0.000 KiB' eq bytes_display_string( 0 ), 'bytes_display_string: 0' ) ; - ok( '0.001 KiB' eq bytes_display_string( 1 ), 'bytes_display_string: 1' ) ; - ok( '0.010 KiB' eq bytes_display_string( 10 ), 'bytes_display_string: 10' ) ; - ok( '1.000 MiB' eq bytes_display_string( 1_048_575 ), 'bytes_display_string: 1_048_575' ) ; - ok( '1.000 MiB' eq bytes_display_string( 1_048_576 ), 'bytes_display_string: 1_048_576' ) ; + is( '9.999 KiB', bytes_display_string_bin( 10_239 ), 'bytes_display_string_bin: 10_239 => 9.999 KiB' ) ; + note( bytes_display_string_bin( 10_239 ) ) ; - ok( '1.000 GiB' eq bytes_display_string( 1_073_741_823 ), 'bytes_display_string: 1_073_741_823 ' ) ; - ok( '1.000 GiB' eq bytes_display_string( 1_073_741_824 ), 'bytes_display_string: 1_073_741_824 ' ) ; + is( '10.000 KiB', bytes_display_string_bin( 10_240 ), 'bytes_display_string_bin: 10_240 => 10.000 KiB' ) ; + note( bytes_display_string_bin( 10_240 ) ) ; - ok( '1.000 TiB' eq bytes_display_string( 1_099_511_627_775 ), 'bytes_display_string: 1_099_511_627_775' ) ; - ok( '1.000 TiB' eq bytes_display_string( 1_099_511_627_776 ), 'bytes_display_string: 1_099_511_627_776' ) ; + is( '999.999 KiB', bytes_display_string_bin( 1_023_999 ), 'bytes_display_string_bin: 1_023_999 => 999.999 KiB' ) ; + note( bytes_display_string_bin( 1_023_999 ) ) ; - ok( '1.000 PiB' eq bytes_display_string( 1_125_899_906_842_623 ), 'bytes_display_string: 1_125_899_906_842_623' ) ; - ok( '1.000 PiB' eq bytes_display_string( 1_125_899_906_842_624 ), 'bytes_display_string: 1_125_899_906_842_624' ) ; + is( '0.977 MiB', bytes_display_string_bin( 1_024_000 ), 'bytes_display_string_bin: 1_024_000 => 0.977 MiB' ) ; + note( bytes_display_string_bin( 1_024_000 ) ) ; - ok( '1024.000 PiB' eq bytes_display_string( 1_152_921_504_606_846_975 ), 'bytes_display_string: 1_152_921_504_606_846_975' ) ; - ok( '1024.000 PiB' eq bytes_display_string( 1_152_921_504_606_846_976 ), 'bytes_display_string: 1_152_921_504_606_846_976' ) ; + is( '0.999 MiB', bytes_display_string_bin( 1_047_527 ), 'bytes_display_string_bin: 1_047_527 => 0.999 MiB' ) ; + note( bytes_display_string_bin( 1_047_527 ) ) ; - ok( '1048576.000 PiB' eq bytes_display_string( 1_180_591_620_717_411_303_424 ), 'bytes_display_string: 1_180_591_620_717_411_303_424' ) ; + is( '0.999 MiB', bytes_display_string_bin( 1_048_051 ), 'bytes_display_string_bin: 1_048_051 => 0.999 MiB' ) ; + note( bytes_display_string_bin( 1_048_051 ) ) ; - #myprint( bytes_display_string( 1_180_591_620_717_411_303_424 ), "\n" ) ; - note( 'Leaving tests_bytes_display_string()' ) ; + is( '1.000 MiB', bytes_display_string_bin( 1_048_052 ), 'bytes_display_string_bin: 1_048_052 => 1.000 MiB' ) ; + note( bytes_display_string_bin( 1_048_052 ) ) ; + + is( '1.000 MiB', bytes_display_string_bin( 1_048_575 ), 'bytes_display_string_bin: 1_048_575 => 1.000 MiB' ) ; + is( '1.000 MiB', bytes_display_string_bin( 1_048_576 ), 'bytes_display_string_bin: 1_048_576 => 1.000 MiB' ) ; + + is( '1.000 GiB', bytes_display_string_bin( 1_073_741_823 ), 'bytes_display_string_bin: 1_073_741_823 => 1.000 GiB' ) ; + is( '1.000 GiB', bytes_display_string_bin( 1_073_741_824 ), 'bytes_display_string_bin: 1_073_741_824 => 1.000 GiB' ) ; + + + is( '1.000 TiB', bytes_display_string_bin( 1_099_511_627_775 ), 'bytes_display_string_bin: 1_099_511_627_775 => 1.000 TiB' ) ; + is( '1.000 TiB', bytes_display_string_bin( 1_099_511_627_776 ), 'bytes_display_string_bin: 1_099_511_627_776 => 1.000 TiB' ) ; + + is( '1.000 PiB', bytes_display_string_bin( 1_125_899_906_842_623 ), 'bytes_display_string_bin: 1_125_899_906_842_623 => 1.000 PiB' ) ; + is( '1.000 PiB', bytes_display_string_bin( 1_125_899_906_842_624 ), 'bytes_display_string_bin: 1_125_899_906_842_624 => 1.000 PiB' ) ; + + is( '1024.000 PiB', bytes_display_string_bin( 1_152_921_504_606_846_975 ), 'bytes_display_string_bin: 1_152_921_504_606_846_975 => 1024.000 PiB' ) ; + is( '1024.000 PiB', bytes_display_string_bin( 1_152_921_504_606_846_976 ), 'bytes_display_string_bin: 1_152_921_504_606_846_976 => 1024.000 PiB' ) ; + + is( '1048576.000 PiB', bytes_display_string_bin( 1_180_591_620_717_411_303_424 ), 'bytes_display_string_bin: 1_180_591_620_717_411_303_424 => 1048576.000 PiB' ) ; + note( bytes_display_string_bin( 1_180_591_620_717_411_303_424 ) ) ; + note( bytes_display_string_bin( 3_000_000_000 ) ) ; + note( 'Leaving tests_bytes_display_string_bin()' ) ; return ; } -sub bytes_display_string { +sub bytes_display_string_bin +{ my ( $bytes ) = @_ ; my $readable_value = q{} ; @@ -8594,95 +14666,227 @@ sub bytes_display_string { } # if you have exabytes (EiB) of email to transfer, you have too much email! } - #myprint( "$bytes = $readable_value\n" ) ; + #myprint( "$bytes = $readable_value\n" ) ; + return( $readable_value ) ; +} + +sub tests_bytes_display_string_dec +{ + note( 'Entering tests_bytes_display_string_dec()' ) ; + + is( 'NA', bytes_display_string_dec( ), 'bytes_display_string_dec: no args => NA' ) ; + is( 'NA', bytes_display_string_dec( undef ), 'bytes_display_string_dec: undef => NA' ) ; + is( 'NA', bytes_display_string_dec( 'blabla' ), 'bytes_display_string_dec: blabla => NA' ) ; + + is( '0 bytes', bytes_display_string_dec( 0 ), 'bytes_display_string_dec: 0 => 0 bytes' ) ; + is( '1 bytes', bytes_display_string_dec( 1 ), 'bytes_display_string_dec: 1 => 1 bytes' ) ; + is( '10 bytes', bytes_display_string_dec( 10 ), 'bytes_display_string_dec: 10 => 10 bytes' ) ; + is( '999 bytes', bytes_display_string_dec( 999 ), 'bytes_display_string_dec: 999 => 999 bytes' ) ; + + is( '1.000 KB', bytes_display_string_dec( 1000 ), 'bytes_display_string_dec: 1000 => 1.000 KB' ) ; + is( '1.001 KB', bytes_display_string_dec( 1001 ), 'bytes_display_string_dec: 1000 => 1.1001 KB' ) ; + + is( '999.999 KB', bytes_display_string_dec( 999_999 ), 'bytes_display_string_dec: 999_999 => 999.999 KB' ) ; + + is( '1.000 MB', bytes_display_string_dec( 1_000_000 ), 'bytes_display_string_dec: 1_000_000 => 1.000 MB' ) ; + is( '1.000 MB', bytes_display_string_dec( 1_000_500 ), 'bytes_display_string_dec: 1_000_500 => 1.000 MB' ) ; + is( '1.001 MB', bytes_display_string_dec( 1_000_501 ), 'bytes_display_string_dec: 1_000_501 => 1.001 MB' ) ; + is( '999.999 MB', bytes_display_string_dec( 999_999_000 ), 'bytes_display_string_dec: 999_999_000 => 999.999 MB' ) ; + is( '999.999 MB', bytes_display_string_dec( 999_999_499 ), 'bytes_display_string_dec: 999_999_499 => 999.999 MB' ) ; + is( '1.000 GB', bytes_display_string_dec( 999_999_500 ), 'bytes_display_string_dec: 999_999_500 => 1.000 GB' ) ; + + is( '1.000 GB', bytes_display_string_dec( 1_000_000_000 ), 'bytes_display_string_dec: 1_000_000_000 => 1.000 GB' ) ; + is( '1.000 GB', bytes_display_string_dec( 1_000_500_000 ), 'bytes_display_string_dec: 1_000_500_000 => 1.000 GB' ) ; + is( '1.001 GB', bytes_display_string_dec( 1_000_500_001 ), 'bytes_display_string_dec: 1_000_501_000 => 1.001 GB' ) ; + is( '999.999 GB', bytes_display_string_dec( 999_999_000_000 ), 'bytes_display_string_dec: 999_999_000_000 => 999.999 GB' ) ; + is( '999.999 GB', bytes_display_string_dec( 999_999_499_999 ), 'bytes_display_string_dec: 999_999_499_999 => 999.999 GB' ) ; + is( '1.000 TB', bytes_display_string_dec( 999_999_500_000 ), 'bytes_display_string_dec: 999_999_500_000 => 1.000 TB' ) ; + + is( '1.000 TB', bytes_display_string_dec( 1_000_000_000_000 ), 'bytes_display_string_dec: 1_000_000_000_000 => 1.000 TB' ) ; + is( '1.000 TB', bytes_display_string_dec( 1_000_500_000_000 ), 'bytes_display_string_dec: 1_000_500_000_000 => 1.000 TB' ) ; + is( '1.001 TB', bytes_display_string_dec( 1_000_500_000_001 ), 'bytes_display_string_dec: 1_000_500_000_000 => 1.000 TB' ) ; + is( '999.999 TB', bytes_display_string_dec( 999_999_000_000_000 ), 'bytes_display_string_dec: 999_999_000_000_000 => 999.999 TB' ) ; + is( '999.999 TB', bytes_display_string_dec( 999_999_499_999_999 ), 'bytes_display_string_dec: 999_999_499_999_999 => 999.999 TB' ) ; + is( '1.000 PB', bytes_display_string_dec( 999_999_500_000_000 ), 'bytes_display_string_dec: 999_999_500_000_000 => 1.000 PB' ) ; + + is( '3.000 GB', bytes_display_string_dec( 3_000_000_000 ), 'bytes_display_string_dec: 3_000_000_000 => 3.000 GB' ) ; + + note( 'Leaving tests_bytes_display_string_dec()' ) ; + return ; +} + +sub bytes_display_string_dec +{ + my ( $bytes ) = @_ ; + + my $readable_value = q{} ; + + if ( ! defined( $bytes ) ) { + return( 'NA' ) ; + } + + if ( not match_number( $bytes ) ) { + return( 'NA' ) ; + } + + SWITCH: { + if ( abs( $bytes ) < ( 1000 ) ) { + $readable_value = mysprintf( '%.0f bytes', $bytes ) ; + last SWITCH ; + } + if ( abs( $bytes ) < ( 1000**2 ) ) { + $readable_value = mysprintf( '%.3f KB', $bytes / 1000 ) ; + last SWITCH ; + } + if ( abs( $bytes ) < ( 999_999_500 ) ) { + $readable_value = mysprintf( '%.3f MB', $bytes / ( 1000**2 ) ) ; + last SWITCH ; + } + if ( abs( $bytes ) < ( 999_999_500_000 ) ) { + $readable_value = mysprintf( '%.3f GB', $bytes / ( 1000**3 ) ) ; + last SWITCH ; + } + if ( abs( $bytes ) < ( 999_999_500_000_000 ) ) { + $readable_value = mysprintf( '%.3f TB', $bytes / ( 1000**4 ) ) ; + last SWITCH ; + } else { + $readable_value = mysprintf( '%.3f PB', $bytes / ( 1000**5 ) ) ; + } + # if you have exabytes (EiB) of email to transfer, you have too much email! + } + #myprint( "$bytes = $readable_value\n" ) ; + return( $readable_value ) ; } -sub stats { + +sub tests_useheader_suggestion +{ + note( 'Entering tests_useheader_suggestion()' ) ; + + is( undef, useheader_suggestion( ), 'useheader_suggestion: no args => undef' ) ; + my $mysync = {} ; + + $mysync->{ h1_nb_msg_noheader } = 0 ; + is( q{}, useheader_suggestion( $mysync ), 'useheader_suggestion: h1_nb_msg_noheader count null => no suggestion' ) ; + $mysync->{ h1_nb_msg_noheader } = 2 ; + is( q{in order to sync those 2 unidentified messages, add option --addheader}, useheader_suggestion( $mysync ), + 'useheader_suggestion: h1_nb_msg_noheader count 2 => suggestion of --addheader' ) ; + + note( 'Leaving tests_useheader_suggestion()' ) ; + return ; +} + +sub useheader_suggestion +{ + my $mysync = shift ; + if ( ! defined $mysync->{ h1_nb_msg_noheader } ) + { + return ; + } + elsif ( 1 <= $mysync->{ h1_nb_msg_noheader } ) + { + return qq{in order to sync those $mysync->{ h1_nb_msg_noheader } unidentified messages, add option --addheader} ; + } + else + { + return q{} ; + } + return ; +} + +sub do_and_print_stats +{ my $mysync = shift ; - if ( ! $mysync->{stats} ) { + if ( ! $mysync->{can_do_stats} ) { return ; } my $timeend = time ; my $timediff = $timeend - $mysync->{timestart} ; - my $timeend_str = localtime $timeend ; + my $timeend_str = localtimez( $timeend ) ; + + my $cpu_time = cpu_time( $mysync ) ; + my $cpu_percent = cpu_percent( $mysync, $cpu_time, $timediff ) ; + my $cpu_percent_global = cpu_percent_global( $mysync, $cpu_percent ) ; my $memory_consumption_at_end = memory_consumption( ) || 0 ; my $memory_consumption_at_start = $mysync->{ memory_consumption_at_start } || 0 ; - my $memory_ratio = ($max_msg_size_in_bytes) ? - mysprintf('%.1f', $memory_consumption_at_end / $max_msg_size_in_bytes) : 'NA' ; - - - myprint( "++++ Statistics\n" ) ; - myprint( "Transfer started on : $timestart_str\n" ) ; - myprint( "Transfer ended on : $timeend_str\n" ) ; - myprintf( "Transfer time : %.1f sec\n", $timediff ) ; - myprint( "Folders synced : $h1_folders_wanted_ct/$h1_folders_wanted_nb synced\n" ) ; - myprint( "Messages transferred : $mysync->{nb_msg_transferred} " ) ; + my $memory_ratio = ( $mysync->{ biggest_message_transferred } ) ? + mysprintf( '%.1f', $memory_consumption_at_end / $mysync->{ biggest_message_transferred } ) : 'NA' ; + + # my $useheader_suggestion = useheader_suggestion( $mysync ) ; + myprint( "++++ Statistics\n" ) ; + myprint( "Transfer started on : $mysync->{ timestart_str }\n" ) ; + myprint( "Transfer ended on : $timeend_str\n" ) ; + myprintf( "Transfer time : %.1f sec\n", $timediff ) ; + myprint( "Folders synced : $h1_folders_wanted_ct/$h1_folders_wanted_nb synced\n" ) ; + myprint( "Messages transferred : $mysync->{ nb_msg_transferred } " ) ; myprint( "(could be $nb_msg_skipped_dry_mode without dry mode)" ) if ( $mysync->{dry} ) ; myprint( "\n" ) ; - myprint( "Messages skipped : $nb_msg_skipped\n" ) ; - myprint( "Messages found duplicate on host1 : $h1_nb_msg_duplicate\n" ) ; - myprint( "Messages found duplicate on host2 : $h2_nb_msg_duplicate\n" ) ; - myprint( "Messages void (noheader) on host1 : $h1_nb_msg_noheader\n" ) ; - myprint( "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n" ) ; - myprint( "Messages deleted on host1 : $h1_nb_msg_deleted\n" ) ; - myprint( "Messages deleted on host2 : $h2_nb_msg_deleted\n" ) ; - myprintf( "Total bytes transferred : %s (%s)\n", + myprint( "Messages skipped : $mysync->{ nb_msg_skipped }\n" ) ; + myprint( "Messages found duplicate on host1 : $mysync->{ acc1 }->{ nb_msg_duplicate }\n" ) ; + myprint( "Messages found duplicate on host2 : $mysync->{ acc2 }->{ nb_msg_duplicate }\n" ) ; + myprint( "Messages found crossduplicate on host2 : $mysync->{ h2_nb_msg_crossdup }\n" ) ; + myprint( "Messages void (noheader) on host1 : $mysync->{ h1_nb_msg_noheader } ", useheader_suggestion( $mysync ), "\n" ) ; + myprint( "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n" ) ; + nb_messages_in_1_not_in_2( $mysync ) ; + nb_messages_in_2_not_in_1( $mysync ) ; + myprintf( "Messages found in host1 not in host2 : %s messages\n", $mysync->{ nb_messages_in_1_not_in_2 } ) ; + myprintf( "Messages found in host2 not in host1 : %s messages\n", $mysync->{ nb_messages_in_2_not_in_1 } ) ; + myprint( "Messages deleted on host1 : $mysync->{ acc1 }->{ nb_msg_deleted }\n" ) ; + myprint( "Messages deleted on host2 : $mysync->{ acc2 }->{ nb_msg_deleted }\n" ) ; + myprintf( "Total bytes transferred : %s (%s)\n", $mysync->{total_bytes_transferred}, - bytes_display_string( $mysync->{total_bytes_transferred} ) ) ; - myprintf( "Total bytes duplicate host1 : %s (%s)\n", - $h1_total_bytes_duplicate, - bytes_display_string( $h1_total_bytes_duplicate) ) ; - myprintf( "Total bytes duplicate host2 : %s (%s)\n", - $h2_total_bytes_duplicate, - bytes_display_string( $h2_total_bytes_duplicate) ) ; - myprintf( "Total bytes skipped : %s (%s)\n", - $total_bytes_skipped, - bytes_display_string( $total_bytes_skipped ) ) ; - myprintf( "Total bytes error : %s (%s)\n", - $total_bytes_error, - bytes_display_string( $total_bytes_error ) ) ; + bytes_display_string_bin( $mysync->{total_bytes_transferred} ) ) ; + myprintf( "Total bytes skipped : %s (%s)\n", + $mysync->{ total_bytes_skipped }, + bytes_display_string_bin( $mysync->{ total_bytes_skipped } ) ) ; $timediff ||= 1 ; # No division per 0 - myprintf("Message rate : %.1f messages/s\n", $mysync->{nb_msg_transferred} / $timediff ) ; - myprintf("Average bandwidth rate : %.1f KiB/s\n", $mysync->{total_bytes_transferred} / $KIBI / $timediff ) ; - myprint( "Reconnections to host1 : $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ; - myprint( "Reconnections to host2 : $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ; - myprintf("Memory consumption at the end : %.1f MiB (started with %.1f MiB)\n", + myprintf("Message rate : %.1f messages/s\n", $mysync->{nb_msg_transferred} / $timediff ) ; + myprintf("Average bandwidth rate : %.1f KiB/s\n", $mysync->{total_bytes_transferred} / $KIBI / $timediff ) ; + myprint( "Reconnections to host1 : $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ; + myprint( "Reconnections to host2 : $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ; + myprintf("Memory consumption at the end : %.1f MiB (started with %.1f MiB)\n", $memory_consumption_at_end / $KIBI / $KIBI, $memory_consumption_at_start / $KIBI / $KIBI ) ; - myprintf("Biggest message : %s bytes (%s)\n", - $max_msg_size_in_bytes, - bytes_display_string( $max_msg_size_in_bytes) ) ; - myprint( "Memory/biggest message ratio : $memory_ratio\n" ) ; - if ( $foldersizesatend and $foldersizes ) { + myprint( "Load end is : " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $mysync->{cpu_number} cores\n" ) ; + myprint( "CPU time and %cpu : $cpu_time sec $cpu_percent %cpu $cpu_percent_global %allcpus\n" ) ; + myprintf("Biggest message transferred : %s bytes (%s)\n", + $mysync->{ biggest_message_transferred }, + bytes_display_string_bin( $mysync->{ biggest_message_transferred } ) ) ; + myprint( "Memory/biggest message ratio : $memory_ratio\n" ) ; + if ( $mysync->{ foldersizesatend } and $mysync->{ foldersizes } ) { - my $nb_msg_start_diff = diff_or_NA( $h2_nb_msg_start, $h1_nb_msg_start ) ; - my $bytes_start_diff = diff_or_NA( $h2_bytes_start, $h1_bytes_start ) ; + my $nb_msg_start_diff = diff_or_NA( $mysync->{ h2_nb_msg_start }, $mysync->{ h1_nb_msg_start } ) ; + my $bytes_start_diff = diff_or_NA( $mysync->{ h2_bytes_start }, $mysync->{ h1_bytes_start } ) ; - myprintf("Start difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_start_diff, + myprintf("Start difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_start_diff, $bytes_start_diff, - bytes_display_string( $bytes_start_diff ) ) ; + bytes_display_string_bin( $bytes_start_diff ) ) ; my $nb_msg_end_diff = diff_or_NA( $h2_nb_msg_end, $h1_nb_msg_end ) ; my $bytes_end_diff = diff_or_NA( $h2_bytes_end, $h1_bytes_end ) ; - myprintf("Final difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_end_diff, + myprintf("Final difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_end_diff, $bytes_end_diff, - bytes_display_string( $bytes_end_diff ) ) ; + bytes_display_string_bin( $bytes_end_diff ) ) ; } - myprint( "Detected $mysync->{nb_errors} errors\n\n" ) ; - myprint( $warn_release, "\n" ) ; - myprint( homepage( ), "\n" ) ; + comment_on_final_diff_in_1_not_in_2( $mysync ) ; + comment_on_final_diff_in_2_not_in_1( $mysync ) ; + myprint( "Detected $mysync->{nb_errors} errors\n\n" ) ; + + myprint( $mysync->{ warn_release }, "\n" ) ; + myprint( homepage( ), "\n" ) ; return ; } -sub diff_or_NA { +sub diff_or_NA +{ my( $n1, $n2 ) = @ARG ; if ( not defined $n1 or not defined $n2 ) { @@ -8697,7 +14901,8 @@ sub diff_or_NA { return( $n1 - $n2 ) ; } -sub match_number { +sub match_number +{ my $n = shift @ARG ; if ( not defined $n ) { @@ -8712,7 +14917,8 @@ sub match_number { } -sub tests_match_number { +sub tests_match_number +{ note( 'Entering tests_match_number()' ) ; @@ -8730,7 +14936,8 @@ sub tests_match_number { -sub tests_diff_or_NA { +sub tests_diff_or_NA +{ note( 'Entering tests_diff_or_NA()' ) ; @@ -8752,12 +14959,14 @@ sub tests_diff_or_NA { return ; } -sub homepage { +sub homepage +{ return( 'Homepage: https://imapsync.lamiral.info/' ) ; } -sub load_modules { +sub load_modules +{ if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} @@ -8773,16 +14982,17 @@ sub load_modules { } - -sub parse_header_msg { +# Globals: $skipsize $wholeheaderifneeded +sub parse_header_msg +{ my ( $mysync, $imap, $m_uid, $s_heads, $s_fir, $side, $s_hash ) = @_ ; my $head = $s_heads->{$m_uid} ; my $headnum = scalar keys %{ $head } ; - $debug and myprint( "$side uid $m_uid head nb pass one: ", $headnum, "\n" ) ; + $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass one: ", $headnum, "\n" ) ; if ( ( ! $headnum ) and ( $wholeheaderifneeded ) ){ - myprint( "$side uid $m_uid no header by parse_headers so taking whole header with BODY.PEEK[HEADER]\n" ) ; + $mysync->{ debug } and myprint( "$side: uid $m_uid no header by parse_headers so taking whole header with BODY.PEEK[HEADER]\n" ) ; $imap->fetch($m_uid, 'BODY.PEEK[HEADER]' ) ; my $whole_header = $imap->_transaction_literals ; @@ -8790,18 +15000,16 @@ sub parse_header_msg { $head = decompose_header( $whole_header ) ; $headnum = scalar keys %{ $head } ; - $debug and myprint( "$side uid $m_uid head nb pass two: ", $headnum, "\n" ) ; + $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass two: ", $headnum, "\n" ) ; } #myprint( Data::Dumper->Dump( [ $head, \%useheader ] ) ) ; - my $headstr ; - - $headstr = header_construct( $head, $side, $m_uid ) ; + my $headstr = header_construct( $mysync, $head, $side, $m_uid ) ; if ( ( ! $headstr ) and ( $mysync->{addheader} ) and ( $side eq 'Host1' ) ) { my $header = add_header( $m_uid ) ; - myprint( "Host1 uid $m_uid no header found so adding our own [$header]\n" ) ; + $mysync->{ debug } and myprint( "$side: uid $m_uid no header found so adding our own [$header]\n" ) ; $headstr .= uc $header ; $s_fir->{$m_uid}->{NO_HEADER} = 1; } @@ -8813,53 +15021,130 @@ sub parse_header_msg { my $idate = $s_fir->{$m_uid}->{'INTERNALDATE'} ; $size = length $headstr unless ( $size ) ; my $m_md5 = md5_base64( $headstr ) ; - $debug and myprint( "$side uid $m_uid sig $m_md5 size $size idate $idate\n" ) ; + my $key ; - if ($skipsize) { + if ( $skipsize ) { $key = "$m_md5"; } else { $key = "$m_md5:$size"; } - # 0 return code is used to identify duplicate message hash - return 0 if exists $s_hash->{"$key"}; - $s_hash->{"$key"}{'5'} = $m_md5; - $s_hash->{"$key"}{'s'} = $size; - $s_hash->{"$key"}{'D'} = $idate; - $s_hash->{"$key"}{'F'} = $flags; - $s_hash->{"$key"}{'m'} = $m_uid; - return( 1 ) ; + if ( exists $s_hash->{"$key"} ) + { + # 0 return code is used to identify duplicate message hash + my $dup_ref = $s_hash->{"$key"}->{'U'} ; + my $num = scalar( @{ $dup_ref } ) ; + push( @{ $dup_ref }, $m_uid ) ; + my $keydup = "$key#$num" ; + $mysync->{ debug } and myprint( "$side: uid $m_uid sig $keydup size $size idate $idate dup @{ $dup_ref }\n" ) ; + if ( $mysync->{ syncduplicates } ) + { + $s_hash->{"$keydup"}{'5'} = $m_md5 ; + $s_hash->{"$keydup"}{'s'} = $size ; + $s_hash->{"$keydup"}{'D'} = $idate ; + $s_hash->{"$keydup"}{'F'} = $flags ; + $s_hash->{"$keydup"}{'m'} = $m_uid ; + } + return 0 ; + } + else + { + $s_hash->{"$key"}{'5'} = $m_md5 ; + $s_hash->{"$key"}{'s'} = $size ; + $s_hash->{"$key"}{'D'} = $idate ; + $s_hash->{"$key"}{'F'} = $flags ; + $s_hash->{"$key"}{'m'} = $m_uid ; + $s_hash->{"$key"}{'U'} = [ $m_uid ] ; # ? or [ ] ? + $mysync->{ debug } and myprint( "$side: uid $m_uid sig $key size $size idate $idate\n" ) ; + return( 1 ) ; + } + + # we should not be here + return ; +} + +sub tests_header_construct +{ + note( 'Entering tests_header_construct()' ) ; + + is( undef, header_construct( ), 'header_construct: no args => undef' ) ; + my $mysync = {} ; + my $head = { + 'key1' => [ 'val1_key1' ] + } ; + is( undef, header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 no useheader => undef' ) ; + + $mysync->{useheader}->{ 'KEY1' } = 1 ; + is( 'KEY1: VAL1_KEY1', header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 => KEY1: VAL1_KEY1' ) ; + + + + $head = { + 'key1' => [ 'val1_key1', 'val3_key1', 'val2_key1' ] + } ; + is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ), + 'header_construct: key1 val1_key1 val3_key1 val2_key1 => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ; + + $head = { + 'key1' => [ 'val1_key1', 'val3_key1', ' val2_key1' ] + } ; + is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ), + 'header_construct: key1 val1_key1 val3_key1 val2_key1 => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ; + + $mysync->{useheader}->{ 'ALL' } = 1 ; + + is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ), + 'header_construct: key1 val1_key1 val3_key1 val2_key1 useheader ALL => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ; + + $mysync->{skipheader} = 'key1' ; + is( undef, header_construct( $mysync, $head, 'Host1', '1' ), + 'header_construct: key1 val1_key1 val3_key1 val2_key1 useheader ALL => undef' ) ; + + $head = { + 'key1' => [ 'val1_key1', 'val3_key1', ' val2_key1' ], + 'key2' => [ 'val1_key2', 'val3_key2', ' val2_key2' ] + } ; + is( 'KEY2: VAL1_KEY2KEY2: VAL2_KEY2KEY2: VAL3_KEY2', header_construct( $mysync, $head, 'Host1', '1' ), + 'header_construct: ... useheader ALL skipheader key1 => KEY2: VAL1_KEY2KEY2: VAL2_KEY2KEY2: VAL3_KEY2' ) ; + + + note( 'Leaving tests_header_construct()' ) ; + return ; } -sub header_construct { - my( $head, $side, $m_uid ) = @_ ; +# No global in header_construct +sub header_construct +{ + my( $mysync, $head, $side, $m_uid ) = @_ ; - my $headstr ; + my @headstr ; foreach my $h ( sort keys %{ $head } ) { - next if ( not ( exists $useheader{ uc $h } ) - and ( not exists $useheader{ 'ALL' } ) + next if ( not ( exists $mysync->{useheader}->{ uc $h } ) + and ( not exists $mysync->{useheader}->{ 'ALL' } ) ) ; - foreach my $val ( sort @{$head->{$h}} ) { + foreach my $val ( @{$head->{$h}} ) { my $H = header_line_normalize( $h, $val ) ; # show stuff in debug mode - $debug and myprint( "$side uid $m_uid header [$H]", "\n" ) ; + $mysync->{ debug } and myprint( "$side uid $m_uid header [$H]", "\n" ) ; - if ($skipheader and $H =~ m/$skipheader/xi) { - $debug and myprint( "$side uid $m_uid skipping header [$H]\n" ) ; + if ( $mysync->{skipheader} and $H =~ m/$mysync->{skipheader}/xi) { + $mysync->{ debug } and myprint( "$side uid $m_uid skipping header [$H]\n" ) ; next ; } - $headstr .= "$H" ; + push @headstr, $H ; } } + my $headstr = join( '', sort @headstr ) || undef ; return( $headstr ) ; } -sub header_line_normalize { +sub header_line_normalize +{ my( $header_key, $header_val ) = @_ ; # no 8-bit data in headers ! @@ -8888,7 +15173,8 @@ sub header_line_normalize { return( $header_line ) ; } -sub tests_header_line_normalize { +sub tests_header_line_normalize +{ note( 'Entering tests_header_line_normalize()' ) ; @@ -8905,61 +15191,161 @@ sub tests_header_line_normalize { } -sub firstline { +sub tests_firstline +{ + note( 'Entering tests_firstline()' ) ; + + is( q{}, firstline( 'W/tmp/tests/noexist.txt' ), 'firstline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; + + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'firstline: mkpath W/tmp/tests/' ) ; + + is( "blabla\n" , string_to_file( "blabla\n", 'W/tmp/tests/firstline.txt' ), 'firstline: put blabla in W/tmp/tests/firstline.txt' ) ; + is( 'blabla' , firstline( 'W/tmp/tests/firstline.txt' ), 'firstline: get blabla from W/tmp/tests/firstline.txt' ) ; + + is( q{} , string_to_file( q{}, 'W/tmp/tests/firstline2.txt' ), 'firstline: put empty string in W/tmp/tests/firstline2.txt' ) ; + is( q{} , firstline( 'W/tmp/tests/firstline2.txt' ), 'firstline: get empty string from W/tmp/tests/firstline2.txt' ) ; + + is( "\n" , string_to_file( "\n", 'W/tmp/tests/firstline3.txt' ), 'firstline: put CR in W/tmp/tests/firstline3.txt' ) ; + is( q{} , firstline( 'W/tmp/tests/firstline3.txt' ), 'firstline: get empty string from W/tmp/tests/firstline3.txt' ) ; + + is( "blabla\nTiti\n" , string_to_file( "blabla\nTiti\n", 'W/tmp/tests/firstline4.txt' ), 'firstline: put blabla\nTiti\n in W/tmp/tests/firstline4.txt' ) ; + is( 'blabla' , firstline( 'W/tmp/tests/firstline4.txt' ), 'firstline: get blabla from W/tmp/tests/firstline4.txt' ) ; + + note( 'Leaving tests_firstline()' ) ; + return ; +} + +sub firstline +{ # extract the first line of a file (without \n) + # return empty string if error or empty string - my( $file ) = @_ ; - my $line = q{} ; + my $file = shift ; + my $line ; - if ( ! -e $file ) { - myprint( "Cannot open file $file since it does not exist\n" ) ; - return ; - } + $line = nthline( $file, 1 ) ; + return $line ; +} - open my $FILE, '<', $file or do { - myprint( "Error opening file $file : $OS_ERROR\n" ) ; - return ; - } ; - $line = <$FILE> || q{} ; - close $FILE ; - chomp $line ; + + +sub tests_secondline +{ + note( 'Entering tests_secondline()' ) ; + + is( q{}, secondline( 'W/tmp/tests/noexist.txt' ), 'secondline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; + is( q{}, secondline( 'W/tmp/tests/noexist.txt', 2 ), 'secondline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; + + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'secondline: mkpath W/tmp/tests/' ) ; + + is( "L1\nL2\nL3\nL4\n" , string_to_file( "L1\nL2\nL3\nL4\n", 'W/tmp/tests/secondline.txt' ), 'secondline: put L1\nL2\nL3\nL4\n in W/tmp/tests/secondline.txt' ) ; + is( 'L2' , secondline( 'W/tmp/tests/secondline.txt' ), 'secondline: get L2 from W/tmp/tests/secondline.txt' ) ; + + + note( 'Leaving tests_secondline()' ) ; + return ; +} + + +sub secondline +{ + # extract the second line of a file (without \n) + # return empty string if error or empty string + + my $file = shift ; + my $line ; + + $line = nthline( $file, 2 ) ; return $line ; } -sub tests_firstline { - note( 'Entering tests_firstline()' ) ; - is( undef , firstline( 'W/tmp/tests/noexist.txt' ), 'tests_firstline: not getting blabla from W/tmp/tests/noexist.txt' ) ; - is( "blabla\n" , string_to_file( "blabla\n", 'W/tmp/tests/firstline.txt' ), 'tests_firstline: put blabla in W/tmp/tests/firstline.txt' ) ; - is( 'blabla' , firstline( 'W/tmp/tests/firstline.txt' ), 'tests_firstline: get blabla from W/tmp/tests/firstline.txt' ) ; - is( q{} , string_to_file( q{}, 'W/tmp/tests/firstline2.txt' ), 'tests_firstline: put empty string in W/tmp/tests/firstline2.txt' ) ; - is( q{} , firstline( 'W/tmp/tests/firstline2.txt' ), 'tests_firstline: get empty string from W/tmp/tests/firstline2.txt' ) ; - is( "\n" , string_to_file( "\n", 'W/tmp/tests/firstline3.txt' ), 'tests_firstline: put CR in W/tmp/tests/firstline3.txt' ) ; - is( q{} , firstline( 'W/tmp/tests/firstline3.txt' ), 'tests_firstline: get empty string from W/tmp/tests/firstline3.txt' ) ; - note( 'Leaving tests_firstline()' ) ; + +sub tests_nthline +{ + note( 'Entering tests_nthline()' ) ; + + is( q{}, nthline( 'W/tmp/tests/noexist.txt' ), 'nthline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; + is( q{}, nthline( 'W/tmp/tests/noexist.txt', 2 ), 'nthline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; + + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'nthline: mkpath W/tmp/tests/' ) ; + is( "L1\nL2\nL3\nL4\n" , string_to_file( "L1\nL2\nL3\nL4\n", 'W/tmp/tests/nthline.txt' ), 'nthline: put L1\nL2\nL3\nL4\n in W/tmp/tests/nthline.txt' ) ; + is( 'L3' , nthline( 'W/tmp/tests/nthline.txt', 3 ), 'nthline: get L3 from W/tmp/tests/nthline.txt' ) ; + + + note( 'Leaving tests_nthline()' ) ; return ; } +sub nthline +{ + # extract the nth line of a file (without \n) + # return empty string if error or empty string + + my $file = shift ; + my $num = shift ; + + if ( ! all_defined( $file, $num ) ) { return q{} ; } + + my $line ; + + $line = ( file_to_array( $file ) )[$num - 1] ; + if ( ! defined $line ) + { + return q{} ; + } + else + { + chomp $line ; + return $line ; + } +} + +sub tests_file_to_array +{ + note( 'Entering tests_file_to_array()' ) ; + + is( undef, file_to_array( ), 'file_to_array: no args => undef' ) ; + is( undef, file_to_array( '/noexist' ), 'file_to_array: /noexist => undef' ) ; + is( undef, file_to_array( '/' ), 'file_to_array: reading a directory => undef' ) ; + + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_array: mkpath W/tmp/tests/' ) ; + is( "L1\nL2\nL3\nL4\n" , string_to_file( "L1\nL2\nL3\nL4\n", 'W/tmp/tests/file_to_array.txt' ), 'file_to_array: put L1\nL2\nL3\nL4\n in W/tmp/tests/file_to_array.txt' ) ; + is_deeply( [ "L1\n", "L2\n", "L3\n", "L4\n" ] , [ file_to_array( 'W/tmp/tests/file_to_array.txt' ) ], 'file_to_array: get back L1\n L2\n L3\n L4\n from W/tmp/tests/file_to_array.txt' ) ; + + note( 'Leaving tests_file_to_array()' ) ; + return ; +} -# Should be unit tested and then be used by file_to_string, refactoring file_to_string -sub file_to_array { +sub file_to_array +{ my( $file ) = shift ; + if ( ! $file ) { return ; } + if ( ! -e $file ) { return ; } + if ( ! -f $file ) { return ; } + if ( ! -r $file ) { return ; } + my @string ; - open my $FILE, '<', $file or do { - myprint( "Error reading file $file : $OS_ERROR" ) ; + if ( open my $FILE, '<', $file ) + { + @string = <$FILE> ; + close $FILE ; + return( @string ) ; + } + else + { + myprint( "Error reading file $file : $OS_ERROR\n" ) ; return ; - } ; - @string = <$FILE> ; - close $FILE ; - return( @string ) ; + } } -sub tests_file_to_string { +sub tests_file_to_string +{ note( 'Entering tests_file_to_string()' ) ; is( undef, file_to_string( ), 'file_to_string: no args => undef' ) ; @@ -8967,7 +15353,7 @@ sub tests_file_to_string { is( undef, file_to_string( '/' ), 'file_to_string: reading a directory => undef' ) ; ok( file_to_string( $PROGRAM_NAME ), 'file_to_string: reading myself' ) ; - ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_string: mkpath W/tmp/tests/' ) ; + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_string: mkpath W/tmp/tests/' ) ; is( 'lilili', string_to_file( 'lilili', 'W/tmp/tests/canbewritten' ), 'file_to_string: string_to_file filling W/tmp/tests/canbewritten with lilili' ) ; is( 'lilili', file_to_string( 'W/tmp/tests/canbewritten' ), 'file_to_string: reading W/tmp/tests/canbewritten is lilili' ) ; @@ -8979,25 +15365,20 @@ sub tests_file_to_string { return ; } -sub file_to_string { +sub file_to_string +{ my $file = shift ; if ( ! $file ) { return ; } if ( ! -e $file ) { return ; } if ( ! -f $file ) { return ; } if ( ! -r $file ) { return ; } - my @string ; - if ( open my $FILE, '<', $file ) { - @string = <$FILE> ; - close $FILE ; - return( join q{}, @string ) ; - }else{ - myprint( "Error reading file $file : $OS_ERROR\n" ) ; - return ; - } + + return( join q{}, file_to_array( $file ) ) ; } -sub tests_string_to_file { +sub tests_string_to_file +{ note( 'Entering tests_string_to_file()' ) ; is( undef, string_to_file( ), 'string_to_file: no args => undef' ) ; @@ -9017,7 +15398,8 @@ sub tests_string_to_file { return ; } -sub string_to_file { +sub string_to_file +{ my( $string, $file ) = @_ ; if( ! defined $string ) { return ; } if( ! defined $file ) { return ; } @@ -9036,7 +15418,7 @@ sub string_to_file { return $string ; } -q^ +0 and <<'MULTILINE_COMMENT' ; This is a multiline comment. Based on David Carter discussion, to do: * Call parameters stay the same. @@ -9050,9 +15432,11 @@ OK * in case of CHILD_ERROR, return( undef, $error ) * in case of good command and final $string empty, consider it like CHILD_ERROR => return( undef, $error ) and print $error, with folder/UID/maybeSubject context, on console and at the end with the final error listing. Count this as a sync error. -^ if 0 ; # End of multiline comment. +MULTILINE_COMMENT +# End of multiline comment. -sub pipemess { +sub pipemess +{ my ( $string, @commands ) = @_ ; my $error = q{} ; foreach my $command ( @commands ) { @@ -9085,7 +15469,7 @@ sub pipemess { myprint( qq{STDERR of --pipemess "$command": $error_cmd\n} ) ; } } - #myprint( "[$string]\n" ) ; + #myprint( "[$string]\n" ) ; if ( wantarray ) { return ( $string, $error ) ; }else{ @@ -9095,7 +15479,8 @@ sub pipemess { -sub tests_pipemess { +sub tests_pipemess +{ note( 'Entering tests_pipemess()' ) ; @@ -9148,7 +15533,7 @@ sub tests_pipemess { ( $stringT, $errorT ) = pipemess( 'dontcare', 'true' ) ; is( $stringT, undef, 'pipemess: list context, true but no output, string' ) ; - like( $errorT, qr{\QFailure: --pipemess command "true" ended with "0" characters exit value "0" and STDERR ""\E}xm, 'pipemess: list context, true but no output, error' ) ; + like( $errorT, qr{\QFailure: --pipemess command "true" ended with "0" characters exit value "0" and STDERR ""\E}xm, 'pipemess: list context, true but no output, error' ) ; ( $stringT, $errorT ) = pipemess( 'dontcare', 'false' ) ; is( $stringT, undef, 'pipemess: list context, false and no output, string' ) ; @@ -9162,12 +15547,12 @@ sub tests_pipemess { ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ; is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla", string' ) ; - like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla", error' ) ; + like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla", error' ) ; ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )', 'false' ) ; is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla then false", string' ) ; - like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla then false", error' ) ; + like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla then false", error' ) ; ( $stringT, $errorT ) = pipemess( 'dontcare', 'false', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ; is( $stringT, undef, 'pipemess: list context, "false then STDERR blablabla", string' ) ; @@ -9191,9 +15576,10 @@ sub tests_pipemess { -sub tests_is_a_release_number { +sub tests_is_a_release_number +{ note( 'Entering tests_is_a_release_number()' ) ; - + is( undef, is_a_release_number( ), 'is_a_release_number: no args => undef' ) ; ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_1 ), 'is_a_release_number 1.351' ) ; ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_2 ), 'is_a_release_number 42.4242' ) ; @@ -9204,7 +15590,8 @@ sub tests_is_a_release_number { return ; } -sub is_a_release_number { +sub is_a_release_number +{ my $number = shift ; if ( ! defined $number ) { return ; } return( $number =~ m{^\d+\.\d+$}xo ) ; @@ -9212,14 +15599,17 @@ sub is_a_release_number { -sub imapsync_version_public { +sub imapsync_version_public +{ my $local_version = imapsync_version( $sync ) ; my $imapsync_basename = imapsync_basename( ) ; + my $context = imapsync_context( ) ; my $agent_info = "$OSNAME system, perl " . mysprintf( '%vd', $PERL_VERSION) . ", Mail::IMAPClient $Mail::IMAPClient::VERSION" - . " $imapsync_basename" ; + . " $imapsync_basename" + . " $context" ; my $sock = IO::Socket::INET->new( PeerAddr => 'imapsync.lamiral.info', PeerPort => 80, @@ -9237,7 +15627,8 @@ sub imapsync_version_public { return( $last_release ) ; } -sub not_long_imapsync_version_public { +sub not_long_imapsync_version_public +{ #myprint( "Entering not_long_imapsync_version_public\n" ) ; my $fake = shift ; @@ -9254,7 +15645,7 @@ sub not_long_imapsync_version_public { POSIX::sigaction(SIGALRM, POSIX::SigAction->new(sub { croak 'alarm' } ) ) - or myprint( "Error setting SIGALRM handler: $OS_ERROR\n" ) ; + or myprint( "Error setting SIGALRM handler: $OS_ERROR\n" ) ; } my $ret = eval { @@ -9262,12 +15653,12 @@ sub not_long_imapsync_version_public { { $val = imapsync_version_public( ) ; #sleep 4 ; - #myprint( "End of imapsync_version_public\n" ) ; + #myprint( "End of imapsync_version_public\n" ) ; } alarm 0 ; 1 ; } ; - #myprint( "eval [$ret]\n" ) ; + #myprint( "eval [$ret]\n" ) ; if ( ( not $ret ) or $EVAL_ERROR ) { #myprint( "$EVAL_ERROR" ) ; if ($EVAL_ERROR =~ /alarm/) { @@ -9283,7 +15674,8 @@ sub not_long_imapsync_version_public { } } -sub tests_not_long_imapsync_version_public { +sub tests_not_long_imapsync_version_public +{ note( 'Entering tests_not_long_imapsync_version_public()' ) ; @@ -9294,20 +15686,21 @@ sub tests_not_long_imapsync_version_public { return ; } -sub check_last_release { +sub check_last_release +{ my $fake = shift ; my $public_release = not_long_imapsync_version_public( $fake ) ; - $debug and myprint( "check_last_release: [$public_release]\n" ) ; + $sync->{ debug } and myprint( "check_last_release: [$public_release]\n" ) ; my $inline_help_when_on = '( Use --noreleasecheck to avoid this release check. )' ; - + if ( $public_release eq 'unknown' ) { return( 'Imapsync public release is unknown.' . $inline_help_when_on ) ; } - + if ( $public_release eq 'timeout' ) { return( 'Imapsync public release is unknown (timeout).' . $inline_help_when_on ) ; } - + if ( ! is_a_release_number( $public_release ) ) { return( "Imapsync public release is unknown ($public_release)." . $inline_help_when_on ) ; } @@ -9323,7 +15716,8 @@ sub check_last_release { return( 'really unknown' ) ; # Should never arrive here } -sub tests_check_last_release { +sub tests_check_last_release +{ note( 'Entering tests_check_last_release()' ) ; diag( check_last_release( 1.1 ) ) ; @@ -9342,7 +15736,44 @@ sub tests_check_last_release { return ; } -sub imapsync_version { +sub tests_imapsync_context +{ + note( 'Entering tests_imapsync_context()' ) ; + + like( imapsync_context( ), qr/^CGI|^Docker|^DockerCGI|^Standard/, 'imapsync_context: CGI or Docker or DockerCGI or Standard' ) ; + note( 'Leaving tests_imapsync_context()' ) ; + return ; +} + +sub imapsync_context +{ + my $mysync = shift ; + + my $context = q{} ; + + if ( under_docker_context( $mysync ) && under_cgi_context( $mysync ) ) + { + $context = 'DockerCGI' ; + } + elsif ( under_docker_context( $mysync ) ) + { + $context = 'Docker' ; + } + elsif ( under_cgi_context( $mysync ) ) + { + $context = 'CGI' ; + } + else + { + $context = 'Standard' ; + } + + return $context ; + +} + +sub imapsync_version +{ my $mysync = shift ; my $rcs = $mysync->{rcs} ; my $version ; @@ -9352,10 +15783,11 @@ sub imapsync_version { } -sub tests_version_from_rcs { +sub tests_version_from_rcs +{ note( 'Entering tests_version_from_rcs()' ) ; - is( undef, version_from_rcs( ), 'version_from_rcs: no args => UNKNOWN' ) ; + is( undef, version_from_rcs( ), 'version_from_rcs: no args => undef' ) ; is( 1.831, version_from_rcs( q{imapsync,v 1.831 2017/08/27} ), 'version_from_rcs: imapsync,v 1.831 2017/08/27 => 1.831' ) ; is( 'UNKNOWN', version_from_rcs( 1.831 ), 'version_from_rcs: 1.831 => UNKNOWN' ) ; @@ -9364,22 +15796,24 @@ sub tests_version_from_rcs { } -sub version_from_rcs { +sub version_from_rcs +{ my $rcs = shift ; if ( ! $rcs ) { return ; } - + my $version = 'UNKNOWN' ; if ( $rcs =~ m{,v\s+(\d+\.\d+)}mxso ) { $version = $1 } - + return( $version ) ; } -sub tests_imapsync_basename { +sub tests_imapsync_basename +{ note( 'Entering tests_imapsync_basename()' ) ; ok( imapsync_basename() =~ m/imapsync/, 'imapsync_basename: match imapsync'); @@ -9389,14 +15823,16 @@ sub tests_imapsync_basename { return ; } -sub imapsync_basename { +sub imapsync_basename +{ return basename( $PROGRAM_NAME ) ; } -sub localhost_info { +sub localhost_info +{ my $mysync = shift ; my( $infos ) = join( q{}, "Here is imapsync ", imapsync_version( $mysync ), @@ -9411,19 +15847,65 @@ sub localhost_info { return( $infos ) ; } -sub tests_cpu_number { +sub tests_cpu_number +{ note( 'Entering tests_cpu_number()' ) ; - is( 1, is_an_integer( cpu_number( ) ), "cpu_number: is_an_integer" ) ; + is( 1, is_integer( cpu_number( ) ), "cpu_number: is_integer" ) ; ok( 1 <= cpu_number( ), "cpu_number: 1 or more" ) ; is( 1, cpu_number( 1 ), "cpu_number: 1 => 1" ) ; is( 1, cpu_number( $MINUS_ONE ), "cpu_number: -1 => 1" ) ; is( 1, cpu_number( 'lalala' ), "cpu_number: lalala => 1" ) ; is( $NUMBER_42, cpu_number( $NUMBER_42 ), "cpu_number: $NUMBER_42 => $NUMBER_42" ) ; + + note( "cpu_number = " . cpu_number( ) . "\n" ) ; + note( "hostname = " . hostname( ) . "\n" ) ; + SKIP: { + if ( ! ( 'i005' eq hostname() ) ) + { + skip( 'cpu_number on host != i005 (FreeBSD)', 1 ) ; + } + is( 4, cpu_number( ), "cpu_number: on i005 (FreeBSD) => 4" ) ; + } ; + + SKIP: { + if ( ! ( 'petite' eq hostname() ) ) + { + skip( 'cpu_number on host != petite (Linux)', 1 ) ; + } + is( 2, cpu_number( ), "cpu_number: on petite (Linux) => 2" ) ; + } ; + + SKIP: { + if ( ! ( skip_macosx( ) ) ) + { + skip( 'cpu_number on host != polarhome macosx (Darwin MacOS X 10.7.5 Lion)', 1 ) ; + } + is( 2, cpu_number( ), "cpu_number: on polarhome macosx (Darwin MacOS X 10.7.5 Lion) => 2" ) ; + } ; + + SKIP: { + if ( ! ( 'pcHPDV7-HP' eq hostname() ) ) + { + skip( 'cpu_number on host != pcHPDV7-HP (Windows 7, 64bits)', 1 ) ; + } + is( 2, cpu_number( ), "cpu_number: on pcHPDV7-HP (Windows 7, 64bits) => 2" ) ; + } ; + + SKIP: { + if ( ! ( 'CUILLERE' eq hostname() ) ) + { + skip( 'cpu_number on host != CUILLERE (Windows XP, 32bits)', 1 ) ; + } + is( 1, cpu_number( ), "cpu_number: on CUILLERE (Windows XP, 32bits) => 1" ) ; + } ; + + note( 'Leaving tests_cpu_number()' ) ; return ; } + sub cpu_number { my $cpu_number_forced = shift ; @@ -9431,67 +15913,87 @@ sub cpu_number { my $cpu_number = 1 ; # Default value, erased if better found my @cpuinfo ; - if ( $ENV{"NUMBER_OF_PROCESSORS"} ) { + if ( $ENV{"NUMBER_OF_PROCESSORS"} ) + { # might be under a Windows system $cpu_number = $ENV{"NUMBER_OF_PROCESSORS"} ; - $debug and myprint( "Number of processors found by env var NUMBER_OF_PROCESSORS: $cpu_number\n" ) ; - }elsif ( 'darwin' eq $OSNAME ) { + #myprint( "Number of processors found by env var NUMBER_OF_PROCESSORS: $cpu_number\n" ) ; + } + + if ( 'darwin' eq $OSNAME ) + { $cpu_number = backtick( "sysctl -n hw.ncpu" ) ; chomp( $cpu_number ) ; - $debug and myprint( "Number of processors found by cmd 'sysctl -n hw.ncpu': $cpu_number\n" ) ; - }elsif ( ! -e '/proc/cpuinfo' ) { - $debug and myprint( "Number of processors not found so I might assume there is only 1\n" ) ; - $cpu_number = 1 ; - }elsif( @cpuinfo = file_to_array( '/proc/cpuinfo' ) ) { + #myprint( "Number of processors found by cmd 'sysctl -n hw.ncpu': $cpu_number\n" ) ; + } + + if ( 'freebsd' eq $OSNAME ) + { + $cpu_number = backtick( "sysctl -n kern.smp.cpus" ) ; + chomp( $cpu_number ) ; + #myprint( "Number of processors found by cmd 'sysctl -n kern.smp.cpus': $cpu_number\n" ) ; + } + + if ( 'linux' eq $OSNAME && -e '/proc/cpuinfo' ) + { + @cpuinfo = file_to_array( '/proc/cpuinfo' ) ; $cpu_number = grep { /^processor/mxs } @cpuinfo ; - $debug and myprint( "Number of processors found via /proc/cpuinfo: $cpu_number\n" ) ; + #myprint( "Number of processors found via /proc/cpuinfo: $cpu_number\n" ) ; } - - if ( defined $cpu_number_forced ) { + + if ( defined $cpu_number_forced ) + { $cpu_number = $cpu_number_forced ; } + return( integer_or_1( $cpu_number ) ) ; } - -sub tests_integer_or_1 { +sub tests_integer_or_1 +{ + note( 'Entering tests_integer_or_1()' ) ; is( 1, integer_or_1( ), 'integer_or_1: no args => 1' ) ; is( 1, integer_or_1( undef ), 'integer_or_1: undef => 1' ) ; is( $NUMBER_10, integer_or_1( $NUMBER_10 ), 'integer_or_1: 10 => 10' ) ; is( 1, integer_or_1( q{} ), 'integer_or_1: empty string => 1' ) ; is( 1, integer_or_1( 'lalala' ), 'integer_or_1: lalala => 1' ) ; + + note( 'Leaving tests_integer_or_1()' ) ; return ; } -sub integer_or_1 { +sub integer_or_1 +{ my $number = shift ; - if ( is_an_integer( $number ) ) { - return $number ; + if ( is_integer( $number ) ) { + return $number ; } # else return 1 ; } -sub tests_is_an_integer { - note( 'Entering tests_is_an_integer()' ) ; - - is( undef, is_an_integer( ), 'is_an_integer: no args => undef ' ) ; - ok( is_an_integer( 1 ), 'is_an_integer: 1 => yes ') ; - ok( is_an_integer( $NUMBER_42 ), 'is_an_integer: 42 => yes ') ; - ok( is_an_integer( "$NUMBER_42" ), 'is_an_integer: "$NUMBER_42" => yes ') ; - ok( is_an_integer( '42' ), 'is_an_integer: "42" => yes ') ; - ok( is_an_integer( $NUMBER_104_857_600 ), 'is_an_integer: 104_857_600 => yes') ; - ok( is_an_integer( "$NUMBER_104_857_600" ), 'is_an_integer: "$NUMBER_104_857_600" => yes') ; - ok( is_an_integer( '104857600' ), 'is_an_integer: 104857600 => yes') ; - ok( ! is_an_integer( 'blabla' ), 'is_an_integer: blabla => no' ) ; - ok( ! is_an_integer( q{} ), 'is_an_integer: empty string => no' ) ; +sub tests_is_integer +{ + note( 'Entering tests_is_integer()' ) ; - note( 'Leaving tests_is_an_integer()' ) ; + is( undef, is_integer( ), 'is_integer: no args => undef ' ) ; + ok( is_integer( 1 ), 'is_integer: 1 => yes ') ; + ok( is_integer( $NUMBER_42 ), 'is_integer: 42 => yes ') ; + ok( is_integer( "$NUMBER_42" ), 'is_integer: "$NUMBER_42" => yes ') ; + ok( is_integer( '42' ), 'is_integer: "42" => yes ') ; + ok( is_integer( $NUMBER_104_857_600 ), 'is_integer: 104_857_600 => yes') ; + ok( is_integer( "$NUMBER_104_857_600" ), 'is_integer: "$NUMBER_104_857_600" => yes') ; + ok( is_integer( '104857600' ), 'is_integer: 104857600 => yes') ; + ok( ! is_integer( 'blabla' ), 'is_integer: blabla => no' ) ; + ok( ! is_integer( q{} ), 'is_integer: empty string => no' ) ; + + note( 'Leaving tests_is_integer()' ) ; return ; } -sub is_an_integer { +sub is_integer +{ my $number = shift ; if ( ! defined $number ) { return ; } return( $number =~ m{^\d+$}xo ) ; @@ -9500,16 +16002,20 @@ sub is_an_integer { -sub tests_loadavg { +sub tests_loadavg +{ note( 'Entering tests_loadavg()' ) ; - SKIP: { - skip( 'Tests for darwin', 2 ) if ('darwin' ne $OSNAME) ; + skip( 'Tests for darwin', 3 ) if ('darwin' ne $OSNAME) ; is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ; - is_deeply( [ '0.11', '0.22', '0.33' ], - [ loadavg( 'W/t/loadavg.out' ) ], - 'loadavg W/t/loadavg.out => 0.11 0.22 0.33' ) ; + is_deeply( + [ '0.11', '0.22', '0.33' ], + [ loadavg( 'vm.loadavg: { 0.11 0.22 0.33 }' ) ], + 'loadavg: "vm.loadavg: { 0.11 0.22 0.33 }" => 0.11 0.22 0.33' + ) ; + note( join( " ", "loadavg:", loadavg( ) ) ) ; + is( 3, scalar( my @loadavg = loadavg( ) ), 'loadavg: 3 values' ) ; } ; SKIP: { @@ -9527,7 +16033,6 @@ sub tests_loadavg { is_deeply( [ 0 ], [ loadavg( ) ], 'loadavg on MSWin32 => 0' ) ; - } ; note( 'Leaving tests_loadavg()' ) ; @@ -9535,10 +16040,14 @@ sub tests_loadavg { } -sub loadavg { +sub loadavg +{ if ( 'linux' eq $OSNAME ) { return ( loadavg_linux( @ARG ) ) ; } + if ( 'freebsd' eq $OSNAME ) { + return ( loadavg_freebsd( @ARG ) ) ; + } if ( 'darwin' eq $OSNAME ) { return ( loadavg_darwin( @ARG ) ) ; } @@ -9546,48 +16055,73 @@ sub loadavg { return ( loadavg_windows( @ARG ) ) ; } return( 'unknown' ) ; - } -sub loadavg_linux { +sub loadavg_linux +{ my $line = shift ; if ( ! $line ) { $line = firstline( '/proc/loadavg' ) or return ; } - my ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) = split /\s/mxs, $line ; - if ( all_defined( $avg_1_min, $avg_5_min, $avg_15_min ) ) { - $debug and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min $current_runs\n" ) ; - return ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) ; - } - return ; + my ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) = split /\s/mxs, $line ; + if ( all_defined( $avg_1_min, $avg_5_min, $avg_15_min ) ) { + $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min $current_runs\n" ) ; + return ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) ; + } + return ; +} + +sub loadavg_freebsd +{ + my $file = shift ; + # Example of output of command "sysctl vm.loadavg": + # vm.loadavg: { 0.15 0.08 0.08 } + my $loadavg ; + + if ( ! defined $file ) { + eval { + $loadavg = `/sbin/sysctl vm.loadavg` ; + #myprint( "LOADAVG FREEBSD: $loadavg\n" ) ; + } ; + if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; } + }else{ + $loadavg = firstline( $file ) or return ; + } + + my ( $avg_1_min, $avg_5_min, $avg_15_min ) + = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ; + $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ; + return ( $avg_1_min, $avg_5_min, $avg_15_min ) ; } - -sub loadavg_darwin { - my $file = shift ; +sub loadavg_darwin +{ + my $line = shift ; # Example of output of command "sysctl vm.loadavg": # vm.loadavg: { 0.15 0.08 0.08 } my $loadavg ; - if ( ! defined $file ) { + if ( ! defined $line ) { eval { - $loadavg = `/usr/sbin/sysctl vm.loadavg` ; + # $loadavg = `/usr/sbin/sysctl vm.loadavg` ; + $loadavg = `LANG= /usr/sbin/sysctl vm.loadavg` ; #myprint( "LOADAVG DARWIN: $loadavg\n" ) ; } ; if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; } }else{ - $loadavg = firstline( $file ) or return ; + $loadavg = $line ; } my ( $avg_1_min, $avg_5_min, $avg_15_min ) = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ; - $debug and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ; + #$sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ; return ( $avg_1_min, $avg_5_min, $avg_15_min ) ; } -sub loadavg_windows { +sub loadavg_windows +{ my $file = shift ; # Example of output of command "wmic cpu get loadpercentage": # LoadPercentage @@ -9609,7 +16143,7 @@ sub loadavg_windows { my $num = $1 ; $num /= 100 ; - $debug and myprint( "System load: $num\n" ) ; + $sync->{ debug } and myprint( "System load: $num\n" ) ; return ( $num ) ; } @@ -9618,39 +16152,55 @@ sub loadavg_windows { -sub tests_load_and_delay { +sub tests_load_and_delay +{ note( 'Entering tests_load_and_delay()' ) ; is( undef, load_and_delay( ), 'load_and_delay: no args => undef ' ) ; is( undef, load_and_delay( 1 ), 'load_and_delay: not 4 args => undef ' ) ; is( undef, load_and_delay( 0, 1, 1, 1 ), 'load_and_delay: division per 0 => undef ' ) ; - is( 0, load_and_delay( 1, 1, 1, 1 ), 'load_and_delay: one core, loads are all 1 => ok ' ) ; - is( 0, load_and_delay( 2, 2, 2, 2 ), 'load_and_delay: two core, loads are all 2 => ok ' ) ; - is( 0, load_and_delay( 2, 2, 4, 5 ), 'load_and_delay: two core, load1m is 2 => ok ' ) ; - - is( 0, load_and_delay( 1, 0, 0, 0 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=0 => 0 ' ) ; - is( 0, load_and_delay( 1, 0, 0, 2 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=2 => 0 ' ) ; - is( 0, load_and_delay( 1, 0, 2, 0 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=0 => 0 ' ) ; - is( 0, load_and_delay( 1, 0, 2, 2 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=2 => 0 ' ) ; - is( 1, load_and_delay( 1, 2, 0, 0 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=0 => 1 ' ) ; - is( 1, load_and_delay( 1, 2, 0, 2 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=2 => 1 ' ) ; - is( 5, load_and_delay( 1, 2, 2, 0 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=0 => 5 ' ) ; - is( 15, load_and_delay( 1, 2, 2, 2 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=2 => 15 ' ) ; - - is( 0, load_and_delay( 4, 0, 2, 2 ), 'load_and_delay: four core, load1m=0 load5m=2 load15m=2 => 0 ' ) ; - is( 1, load_and_delay( 4, 8, 0, 0 ), 'load_and_delay: four core, load1m=2 load5m=0 load15m=0 => 1 ' ) ; - is( 1, load_and_delay( 4, 8, 0, 2 ), 'load_and_delay: four core, load1m=2 load5m=0 load15m=2 => 1 ' ) ; - is( 5, load_and_delay( 4, 8, 8, 0 ), 'load_and_delay: four core, load1m=2 load5m=2 load15m=0 => 5 ' ) ; - is( 15, load_and_delay( 4, 8, 8, 8 ), 'load_and_delay: four core, load1m=2 load5m=2 load15m=2 => 15 ' ) ; - is( 15, load_and_delay( 4, 8, 8, 8, 'lalala' ), 'load_and_delay: five arguments is ok' ) ; - - note( 'Leaving tests_load_and_delay()' ) ; - return ; + +# ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min ) + + is( 0, load_and_delay( 1, 1, 1, 1 ), 'load_and_delay: one core, loads are all 1 => ok ' ) ; + is( 0, load_and_delay( 1, 1, 1, 1, 'lalala' ), 'load_and_delay: five arguments is ok' ) ; + is( 0, load_and_delay( 2, 2, 2, 2 ), 'load_and_delay: two core, loads are all 2 => ok ' ) ; + is( 0, load_and_delay( 2, 2, 4, 5 ), 'load_and_delay: two core, load1m is 2 => ok ' ) ; + + + is( 0, load_and_delay( 1, 0, 0, 0 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=0 => 0 ' ) ; + is( 0, load_and_delay( 1, 0, 0, 2 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=2 => 0 ' ) ; + is( 0, load_and_delay( 1, 0, 2, 0 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=0 => 0 ' ) ; + is( 0, load_and_delay( 1, 0, 2, 2 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=2 => 0 ' ) ; + is( 0, load_and_delay( 1, 0, 3, 3 ), 'load_and_delay: one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ; + is( 0, load_and_delay( 1, 0, 4, 4 ), 'load_and_delay: one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ; + is( 0, load_and_delay( 1, 2, 0, 0 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=0 => 0 ' ) ; + is( 0, load_and_delay( 1, 2, 0, 2 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=2 => 0 ' ) ; + is( 0, load_and_delay( 1, 2, 2, 0 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=0 => 0 ' ) ; + is( 0, load_and_delay( 1, 2, 2, 2 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=2 => 0 ' ) ; + is( 0, load_and_delay( 1, 2.9, 2.9, 2.9 ), 'load_and_delay: one core, load1m=2.9 load5m=2.9 load15m=2.9 => 0 ' ) ; + + is( 0, load_and_delay( 1, 3, 0, 0 ), 'load_and_delay: one core, load1m=3 load5m=0 load15m=0 => 0 ' ) ; + is( 0, load_and_delay( 1, 3, 2.9, 2.9 ), 'load_and_delay: one core, load1m=3 load5m=2.9 load15m=2.9 => 0 ' ) ; + is( 0, load_and_delay( 1, 3, 3, 2.9 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=2.9 => 0 ' ) ; + is( 0, load_and_delay( 1, 3, 3, 3 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=3 => 0 ' ) ; + + is( 1, load_and_delay( 1, 6, 0, 0 ), 'load_and_delay: one core, load1m=3 load5m=0 load15m=0 => 1 ' ) ; + is( 1, load_and_delay( 1, 6, 5.9, 5.9 ), 'load_and_delay: one core, load1m=3 load5m=2.9 load15m=2.9 => 1 ' ) ; + is( 5, load_and_delay( 1, 6, 6, 5.9 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=2.9 => 5 ' ) ; + is( 15, load_and_delay( 1, 6, 6, 6 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=3 => 15 ' ) ; + + + + note( 'Leaving tests_load_and_delay()' ) ; + return ; } -sub load_and_delay { +sub load_and_delay +{ # Basically return 0 if load is not heavy, ie <= 1 per processor + # Not enough arguments if ( 4 > scalar @ARG ) { return ; } my ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min ) = @ARG ; @@ -9660,17 +16210,129 @@ sub load_and_delay { # Let divide by number of cores ( $avg_1_min, $avg_5_min, $avg_15_min ) = map { $_ / $cpu_num } ( $avg_1_min, $avg_5_min, $avg_15_min ) ; # One of avg ok => ok, for now it is a OR - if ( $avg_1_min <= 1 ) { return 0 ; } - if ( $avg_5_min <= 1 ) { return 1 ; } # Retry in 1 minute - if ( $avg_15_min <= 1 ) { return 5 ; } # Retry in 5 minutes + if ( $avg_1_min < 6 ) { return 0 ; } + if ( $avg_5_min < 6 ) { return 1 ; } # Retry in 1 minute + if ( $avg_15_min < 6 ) { return 5 ; } # Retry in 5 minutes return 15 ; # Retry in 15 minutes } -sub ram_memory_info { + +sub tests_cpu_time +{ + note( 'Entering tests_cpu_time()' ) ; + + ok( is_number( cpu_time( ) ), 'cpu_time: no args => a number' ) ; + + my $mysync = { } ; + $mysync->{ debug } = 1 ; + ok( is_number( cpu_time( $mysync ) ), 'cpu_time: {} => a number' ) ; + + note( 'Leaving tests_cpu_time()' ) ; + return ; +} + +sub cpu_time +{ + my $mysync = shift ; + + my @cpu_times = times ; + if ( ! @cpu_times ) { return ; } + + my $cpu_time = 0 ; + # last element is the sum of all elements + $cpu_time = ( map { $cpu_time += $_ } @cpu_times )[ -1 ] ; + my $cpu_time_round = mysprintf( '%.2f', $cpu_time ) ; + $mysync->{ debug } and myprint( join(' + ', @cpu_times), " = $cpu_time ~ $cpu_time_round\n" ) ; + return $cpu_time ; +} + + +sub tests_cpu_percent +{ + note( 'Entering tests_cpu_percent()' ) ; + + is( '0.0', cpu_percent( ), 'cpu_percent: no args => 0.0' ) ; + my $mysync = { } ; + $mysync->{ debug } = 1 ; + is( '0.0', cpu_percent( $mysync ), 'cpu_percent: {} => 0.0' ) ; + is( '0.0', cpu_percent( $mysync, 0 ), 'cpu_percent: {} 0 => 0.0' ) ; + is( '300.0', cpu_percent( $mysync, 3 ), 'cpu_percent: {} 3 => 300.0' ) ; + is( '30.0', cpu_percent( $mysync, 3, 10 ), 'cpu_percent: {} 3 10 => 30.0' ) ; + is( '0.0', cpu_percent( $mysync, 0, 10 ), 'cpu_percent: {} 0 10 => 0.0' ) ; + + note( 'Leaving tests_cpu_percent()' ) ; + return ; +} + +sub cpu_percent +{ + my $mysync = shift ; + my $cpu_time = shift || 0 ; + my $timediff = shift || 1 ; # no division by 0 + + if ( $cpu_time > $timediff ) + { + myprint( "Strange: cpu_time $cpu_time > timediff $timediff\n" ) ; + } + my $cpu_percent = 0 ; + $cpu_percent = mysprintf( '%.1f', 100 * $cpu_time / $timediff ) ; + $mysync->{ debug } and myprint( "cpu_percent: $cpu_percent \n" ) ; + + return $cpu_percent ; + +} + +sub tests_cpu_percent_global +{ + note( 'Entering tests_cpu_percent_global()' ) ; + + is( '0.0', cpu_percent_global( ), 'cpu_percent_global: no args => 0' ) ; + my $mysync = { } ; + $mysync->{ debug } = 1 ; + is( '0.0', cpu_percent_global( $mysync ), 'cpu_percent_global: {} => 0' ) ; + is( '0.0', cpu_percent_global( $mysync, 0 ), 'cpu_percent_global: {} 0 => 0' ) ; + + SKIP: { + if ( ! ( 'i005' eq hostname() ) ) + { + skip( 'cpu_percent_global on host != i005', 1 ) ; + } + is( '25.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 25 on host i005' ) ; + } ; + + SKIP: { + if ( ! ( 'petite' eq hostname() ) ) + { + skip( 'cpu_percent_global on host != petite', 1 ) ; + } + is( '50.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 50 on host petite' ) ; + } ; + + note( 'Leaving tests_cpu_percent_global()' ) ; + return ; +} + +sub cpu_percent_global +{ + my $mysync = shift ; + my $cpu_percent = shift || 0 ; + + my $cpu_number = cpu_number( ) ; + + my $cpu_percent_global ; + $cpu_percent_global = mysprintf( '%.1f', $cpu_percent / $cpu_number ) ; + $mysync->{ debug } and myprint( "cpu_percent_global: $cpu_percent_global \n" ) ; + + return( $cpu_percent_global ) ; +} + + +sub ram_memory_info +{ # In GigaBytes so division by 1024 * 1024 * 1024 - # + # return( - sprintf( "%.1f/%.1f free GiB of RAM", + sprintf( "%.1f/%.1f free GiB of RAM", Sys::MemInfo::get("freemem") / ( $KIBI ** 3 ), Sys::MemInfo::get("totalmem") / ( $KIBI ** 3 ), ) @@ -9679,30 +16341,32 @@ sub ram_memory_info { -sub tests_memory_stress { +sub tests_memory_stress +{ note( 'Entering tests_memory_stress()' ) ; - + is( undef, memory_stress( ), 'memory_stress: => undef' ) ; - + note( 'Leaving tests_memory_stress()' ) ; return ; } -sub memory_stress { - +sub memory_stress +{ my $total_ram_in_MB = Sys::MemInfo::get("totalmem") / ( $KIBI * $KIBI ) ; my $i = 1 ; - + myprintf("Stress memory consumption before: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ; while ( $i < $total_ram_in_MB / 1.7 ) { $a .= "A" x 1000_000; $i++ } ; myprintf("Stress memory consumption after: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ; return ; - } -sub tests_memory_consumption { +sub tests_memory_consumption +{ note( 'Entering tests_memory_consumption()' ) ; + note( "memory_consumption: " . memory_consumption() . " bytes aka " . bytes_display_string_dec( memory_consumption() ) ) ; like( memory_consumption( ), qr{\d+}xms,'memory_consumption no args') ; like( memory_consumption( 1 ), qr{\d+}xms,'memory_consumption 1') ; like( memory_consumption( $PROCESS_ID ), qr{\d+}xms,"memory_consumption_of_pids $PROCESS_ID") ; @@ -9711,55 +16375,71 @@ sub tests_memory_consumption { like( memory_consumption_ratio(1), qr{\d+}xms, 'memory_consumption_ratio 1' ) ; like( memory_consumption_ratio(10), qr{\d+}xms, 'memory_consumption_ratio 10' ) ; - like( memory_consumption(), qr{\d+}xms, "memory_consumption\n" ) ; note( 'Leaving tests_memory_consumption()' ) ; return ; } -sub memory_consumption { +sub memory_consumption +{ # memory consumed by imapsync until now in bytes return( ( memory_consumption_of_pids( ) )[0] ); } -sub debugmemory { +sub debugmemory +{ my $mysync = shift ; if ( ! $mysync->{debugmemory} ) { return q{} ; } - + my $precision = shift ; return( mysprintf( "Memory consumption$precision: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ) ; } -sub memory_consumption_of_pids { +sub memory_consumption_of_pids +{ my @pid = @_; @pid = ( @pid ) ? @pid : ( $PROCESS_ID ) ; - $debug and myprint( "memory_consumption_of_pids PIDs: @pid\n" ) ; + $sync->{ debug } and myprint( "memory_consumption_of_pids PIDs: @pid\n" ) ; my @val ; - if ( 'MSWin32' eq $OSNAME ) { + if ( ( 'MSWin32' eq $OSNAME ) or ( 'cygwin' eq $OSNAME ) ) { @val = memory_consumption_of_pids_win32( @pid ) ; - }else{ + } + elsif ( 'darwin' eq $OSNAME ) + { + @val = memory_consumption_of_pids_mac( @pid ) ; + } + else + { # Unix my @ps = qx{ ps -o vsz -p @pid } ; - #myprint( "ps: @ps" ) ; - - # Use IPC::Open3 from perlcrit -3 - # It stalls on Darwin, don't understand why! - #my @ps = backtick( "ps -o vsz -p @pid" ) ; - #myprint( "ps: @ps" ) ; - - shift @ps; # First line is column name "VSZ" - chomp @ps; + shift @ps ; # First line is column name "VSZ" + chomp @ps ; # convert to octets - + @val = map { $_ * $KIBI } @ps ; } - $debug and myprint "@val\n" ; return( @val ) ; } -sub memory_consumption_of_pids_win32 { + +sub memory_consumption_of_pids_mac +{ + my @pid = @_ ; + # Use IPC::Open3 from perlcrit -3 + # But it stalls on Darwin, I don't understand why! + #my @ps = backtick( "ps -o rss -p @pid" ) ; + #myprint( "ps: @ps" ) ; + my @ps = qx{ ps -o rss -p @pid } ; + shift @ps ; # First line is column name "RSS" + chomp @ps ; + my @val = map { $_ * $KIBI } @ps ; + return( @val ) ; +} + +sub memory_consumption_of_pids_win32 +{ # Windows my @PID = @_; my %PID; @@ -9791,7 +16471,8 @@ sub memory_consumption_of_pids_win32 { } -sub tests_backtick { +sub tests_backtick +{ note( 'Entering tests_backtick()' ) ; is( undef, backtick( ), 'backtick: no args' ) ; @@ -9803,11 +16484,11 @@ sub tests_backtick { @output = backtick( 'echo Hello World!' ) ; # Add \r on Windows. ok( "Hello World!\r\n" eq $output[0], 'backtick: echo Hello World!' ) ; - $debug and myprint( "[@output]" ) ; + $sync->{ debug } and myprint( "[@output]" ) ; @output = backtick( 'echo Hello & echo World!' ) ; ok( "Hello \r\n" eq $output[0], 'backtick: echo Hello & echo World! line 1' ) ; ok( "World!\r\n" eq $output[1], 'backtick: echo Hello & echo World! line 2' ) ; - $debug and myprint( "[@output][$output[0]][$output[1]]" ) ; + $sync->{ debug } and myprint( "[@output][$output[0]][$output[1]]" ) ; # Scalar context ok( "Hello World!\r\n" eq backtick( 'echo Hello World!' ), 'backtick: echo Hello World! scalar' ) ; @@ -9821,11 +16502,11 @@ sub tests_backtick { my @output ; @output = backtick( 'echo Hello World!' ) ; ok( "Hello World!\n" eq $output[0], 'backtick: echo Hello World!' ) ; - $debug and myprint( "[@output]" ) ; + $sync->{ debug } and myprint( "[@output]" ) ; @output = backtick( "echo Hello\necho World!" ) ; ok( "Hello\n" eq $output[0], 'backtick: echo Hello; echo World! line 1' ) ; ok( "World!\n" eq $output[1], 'backtick: echo Hello; echo World! line 2' ) ; - $debug and myprint( "[@output]" ) ; + $sync->{ debug } and myprint( "[@output]" ) ; # Scalar context ok( "Hello World!\n" eq backtick( 'echo Hello World!' ), 'backtick: echo Hello World! scalar' ) ; @@ -9834,8 +16515,8 @@ sub tests_backtick { # Return error positive value, that's ok is( undef, backtick( 'false' ), 'backtick: false returns no output' ) ; my $mem = backtick( "ps -o vsz -p $PROCESS_ID" ) ; - $debug and myprint "MEM=$mem\n" ; - + $sync->{ debug } and myprint( "MEM=$mem\n" ) ; + } note( 'Leaving tests_backtick()' ) ; @@ -9843,7 +16524,8 @@ sub tests_backtick { } -sub backtick { +sub backtick +{ my $command = shift ; if ( ! $command ) { return ; } @@ -9856,7 +16538,7 @@ sub backtick { } ; if ( $EVAL_ERROR ) { myprint( $EVAL_ERROR ) ; - return ; + return ; } if ( ! $eval ) { return ; } if ( ! $pid ) { return ; } @@ -9876,7 +16558,92 @@ sub backtick { } -sub remove_not_num { + +sub tests_check_binary_embed_all_dyn_libs +{ + note( 'Entering tests_check_binary_embed_all_dyn_libs()' ) ; + + is( 1, check_binary_embed_all_dyn_libs( ), 'check_binary_embed_all_dyn_libs: no args => 1' ) ; + + note( 'Leaving tests_check_binary_embed_all_dyn_libs()' ) ; + + return ; +} + + +sub check_binary_embed_all_dyn_libs +{ + my @search_dyn_lib_locale = search_dyn_lib_locale( ) ; + + if ( @search_dyn_lib_locale ) + { + myprint( "Found myself $PROGRAM_NAME pid $PROCESS_ID using locale dynamic libraries that seems out of myself:\n" ) ; + myprint( @search_dyn_lib_locale ) ; + if ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} ) + { + return 0 ; + } + elsif ( $PROGRAM_NAME =~ m{imapsync.*\.exe} ) + { + return 0 ; + } + else + { + # is always ok for non binary + return 1 ; + } + } + else + { + # Found only embedded dynamic lib + myprint( "Found only embedded dynamic lib. Good!\n" ) ; + return 1 ; + } +} + +sub search_dyn_lib_locale +{ + if ( 'darwin' eq $OSNAME ) + { + return search_dyn_lib_locale_darwin( ) ; + } + if ( 'linux' eq $OSNAME ) + { + return search_dyn_lib_locale_linux( ) ; + } + if ( 'MSWin32' eq $OSNAME ) + { + return search_dyn_lib_locale_MSWin32( ) ; + } + +} + +sub search_dyn_lib_locale_darwin +{ + my $command = qq{ lsof -p $PROCESS_ID | grep ' REG ' | grep .dylib | grep -v '/par-' } ; + myprint( "Search non embeded dynamic libs with the command: $command\n" ) ; + return backtick( $command ) ; +} + +sub search_dyn_lib_locale_linux +{ + my $command = qq{ lsof -p $PROCESS_ID | grep ' REG ' | grep -v '/tmp/par-' | grep '\.so' } ; + myprint( "Search non embeded dynamic libs with the command: $command\n" ) ; + return backtick( $command ) ; +} + +sub search_dyn_lib_locale_MSWin32 +{ + my $command = qq{ Listdlls.exe $PROCESS_ID|findstr Strawberry } ; + # $command = qq{ Listdlls.exe $PROCESS_ID|findstr Strawberry } ; + myprint( "Search non embeded dynamic libs with the command: $command\n" ) ; + return qx( $command ) ; +} + + + +sub remove_not_num +{ my $string = shift ; $string =~ tr/0-9//cd ; @@ -9884,7 +16651,8 @@ sub remove_not_num { return( $string ) ; } -sub tests_remove_not_num { +sub tests_remove_not_num +{ note( 'Entering tests_remove_not_num()' ) ; ok( '123' eq remove_not_num( 123 ), 'remove_not_num( 123 )' ) ; @@ -9896,7 +16664,8 @@ sub tests_remove_not_num { return ; } -sub remove_Ko { +sub remove_Ko +{ my $string = shift; if ($string =~ /^(.*)\sKo$/xo) { return($1); @@ -9905,7 +16674,8 @@ sub remove_Ko { } } -sub remove_qq { +sub remove_qq +{ my $string = shift; if ($string =~ /^"(.*)"$/xo) { return($1); @@ -9914,7 +16684,8 @@ sub remove_qq { } } -sub memory_consumption_ratio { +sub memory_consumption_ratio +{ my ($base) = @_; $base ||= 1; @@ -9923,24 +16694,26 @@ sub memory_consumption_ratio { } -sub date_from_rcs { +sub date_from_rcs +{ my $d = shift ; my %num2mon = qw( 01 Jan 02 Feb 03 Mar 04 Apr 05 May 06 Jun 07 Jul 08 Aug 09 Sep 10 Oct 11 Nov 12 Dec ) ; if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) { # Handles the following format # 2015/07/10 11:05:59 -- Generated by RCS Date tag. - #myprint( "$d\n" ) ; - #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ; + #myprint( "$d\n" ) ; + #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ; my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ; $month = $num2mon{$month} ; $d = "$day-$month-$year $hour:$min:$sec +0000" ; - #myprint( "$d\n" ) ; + #myprint( "$d\n" ) ; } return( $d ) ; } -sub tests_date_from_rcs { +sub tests_date_from_rcs +{ note( 'Entering tests_date_from_rcs()' ) ; ok('19-Sep-2015 16:11:07 +0000' @@ -9950,7 +16723,8 @@ sub tests_date_from_rcs { return ; } -sub good_date { +sub good_date +{ # two incoming formats: # header Tue, 24 Aug 2010 16:00:00 +0200 # internal 24-Aug-2010 16:00:00 +0200 @@ -9963,7 +16737,7 @@ sub good_date { SWITCH: { if ( $d =~ m{(\d?)(\d-...-\d{4})(\s\d{2}:\d{2}:\d{2})(\s(?:\+|-)\d{4})?}xo ) { - #myprint( "internal: [$1][$2][$3][$4]\n" ) ; + #myprint( "internal: [$1][$2][$3][$4]\n" ) ; my ($day_1, $date_rest, $hour, $zone) = ($1,$2,$3,$4) ; $day_1 = '0' if ($day_1 eq q{}) ; $zone = ' +0000' if not defined $zone ; @@ -10017,12 +16791,12 @@ sub good_date { if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) { # Handles the following format # 2015/07/10 11:05:59 -- Generated by RCS Date tag. - #myprint( "$d\n" ) ; - #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ; + #myprint( "$d\n" ) ; + #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ; my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ; $month = $num2mon{$month} ; $d = "$day-$month-$year $hour:$min:$sec +0000" ; - #myprint( "$d\n" ) ; + #myprint( "$d\n" ) ; last SWITCH ; } @@ -10080,7 +16854,8 @@ sub good_date { } -sub tests_good_date { +sub tests_good_date +{ note( 'Entering tests_good_date()' ) ; ok(q{} eq good_date(), 'good_date no arg'); @@ -10102,15 +16877,15 @@ sub tests_good_date { ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 97 16:00:00 +0200'), 'good_date header 2digit year'); ok('"24-Aug-2004 16:00:00 +0200"' eq good_date('Tue, 24 Aug 04 16:00:00 +0200'), 'good_date header 2digit year'); ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 1997 16.00.00 +0200'), 'good_date header period time sep'); - ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 1997 16:00:00 +0200'), 'good_date header extra white space type1'); + ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 1997 16:00:00 +0200'), 'good_date header extra white space type1'); ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24 Aug 1997 5:6:2 +0200'), 'good_date header 1digit time vals'); ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24, Aug 1997 05:06:02 +0200'), 'good_date header extra commas'); ok('"01-Oct-2003 12:45:24 +0000"' eq good_date('Wednesday, 01 October 2003 12:45:24 CDT'), 'good_date header no abbrev'); - ok('"11-Jan-2005 17:58:27 -0500"' eq good_date('Tue, 11 Jan 2005 17:58:27 -0500'), 'good_date extra white space'); + ok('"11-Jan-2005 17:58:27 -0500"' eq good_date('Tue, 11 Jan 2005 17:58:27 -0500'), 'good_date extra white space'); ok('"18-Dec-2002 15:07:00 +0000"' eq good_date('Wednesday, December 18, 2002 03:07 PM'), 'good_date kbtoys.com orders'); ok('"16-Dec-2004 02:01:49 -0500"' eq good_date('Dec 16 2004 02:01:49 -0500'), 'good_date jr.com orders'); ok('"21-Jun-2001 11:11:11 +0000"' eq good_date('21-Jun-2001'), 'good_date register.com domain transfer'); - ok('"18-Nov-2012 18:34:38 +0100"' eq good_date('Sun, 18 Nov 2012 18:34:38 +0100'), 'good_date pop2imap bug (Westeuropรคische Normalzeit)'); + ok('"18-Nov-2012 18:34:38 +0100"' eq good_date('Sun, 18 Nov 2012 18:34:38 +0100'), 'good_date pop2imap bug (Westeuropรƒยคische Normalzeit)'); ok('"19-Sep-2015 16:11:07 +0000"' eq good_date('Date: 2015/09/19 16:11:07 '), 'good_date from RCS date' ) ; note( 'Leaving tests_good_date()' ) ; @@ -10118,7 +16893,8 @@ sub tests_good_date { } -sub tests_list_keys_in_2_not_in_1 { +sub tests_list_keys_in_2_not_in_1 +{ note( 'Entering tests_list_keys_in_2_not_in_1()' ) ; @@ -10135,62 +16911,220 @@ sub tests_list_keys_in_2_not_in_1 { return ; } -sub list_keys_in_2_not_in_1 { - - my $folders1_ref = shift; - my $folders2_ref = shift; +sub list_keys_in_2_not_in_1 +{ + my $hash_1_ref = shift; + my $hash_2_ref = shift; my @list; - foreach my $folder ( sort keys %{ $folders2_ref } ) { - next if exists $folders1_ref->{$folder}; - push @list, $folder; + foreach my $key ( sort keys %{ $hash_2_ref } ) { + #$sync->{ debug } and print "$key\n" ; + if ( exists $hash_1_ref->{$key} ) + { + next ; + } + #$sync->{ debug } and print "list_keys_in_2_not_in_1: $key\n" ; + push @list, $key ; } - return(@list); + #$sync->{ debug } and print "@list\n" ; + return( @list ) ; } -sub list_folders_in_2_not_in_1 { +sub list_folders_in_2_not_in_1 +{ - my (@h2_folders_not_in_h1, %h2_folders_not_in_h1) ; - @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all) ; + my ( @h2_folders_not_in_h1, %h2_folders_not_in_h1 ) ; + @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all ) ; map { $h2_folders_not_in_h1{$_} = 1} @h2_folders_not_in_h1 ; - @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h2_folders_from_1_all, \%h2_folders_not_in_h1) ; + @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h2_folders_from_1_all, \%h2_folders_not_in_h1 ) ; + #$sync->{ debug } and print "h2_folders_not_in_h1: @h2_folders_not_in_h1\n" ; + return( reverse @h2_folders_not_in_h1 ) ; +} + +sub tests_nb_messages_in_2_not_in_1 +{ + note( 'Entering tests_stats_across_folders()' ) ; + is( undef, nb_messages_in_2_not_in_1( ), 'nb_messages_in_2_not_in_1: no args => undef' ) ; + + my $mysync->{ h1_folders_of_md5 }->{ 'some_id_01' }->{ 'some_folder_01' } = 1 ; + is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: no messages in 2 => 0' ) ; + + $mysync->{ h1_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_01' } = 2 ; + $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_02' } = 4 ; - return( reverse @h2_folders_not_in_h1 ); + is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: a common message => 0' ) ; + + $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_2_not_in_1' }->{ 'some_folder_02' } = 1 ; + is( 1, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: one message in_2_not_in_1 => 1' ) ; + + $mysync->{ h2_folders_of_md5 }->{ 'some_other_id_in_2_not_in_1' }->{ 'some_folder_02' } = 3 ; + is( 2, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: two messages in_2_not_in_1 => 2' ) ; + + note( 'Leaving tests_stats_across_folders()' ) ; + return ; } +sub nb_messages_in_2_not_in_1 +{ + my $mysync = shift ; + if ( not defined $mysync ) { return ; } -sub tests_match { - note( 'Entering tests_match()' ) ; + $mysync->{ nb_messages_in_2_not_in_1 } = scalar( + list_keys_in_2_not_in_1( + $mysync->{ h1_folders_of_md5 }, + $mysync->{ h2_folders_of_md5 } ) ) ; - # undef serie - is( undef, match( ), 'match: no args => undef' ) ; - is( undef, match( 'lalala' ), 'match: one args => undef' ) ; - - # This one gives 0 under a binary made by pp - # but 1 under "normal" Perl interpreter. So a PAR bug? - #is( 1, match( q{}, q{} ), 'match: q{} =~ q{} => 1' ) ; - - is( 1, match( 'lalala', 'lalala' ), 'match: lalala =~ lalala => 1' ) ; - is( 1, match( 'lalala', '^lalala' ), 'match: lalala =~ ^lalala => 1' ) ; - is( 1, match( 'lalala', 'lalala$' ), 'match: lalala =~ lalala$ => 1' ) ; - is( 1, match( 'lalala', '^lalala$' ), 'match: lalala =~ ^lalala$ => 1' ) ; - is( 1, match( '_lalala_', 'lalala' ), 'match: _lalala_ =~ lalala => 1' ) ; - is( 1, match( 'lalala', '.*' ), 'match: lalala =~ .* => 1' ) ; - is( 1, match( 'lalala', '.' ), 'match: lalala =~ . => 1' ) ; - is( 1, match( '/lalala/', '/lalala/' ), 'match: /lalala/ =~ /lalala/ => 1' ) ; - - - is( 0, match( 'lalala', 'ooo' ), 'match: lalala =~ ooo => 0' ) ; - is( 0, match( 'lalala', 'lal_ala' ), 'match: lalala =~ lal_ala => 0' ) ; - is( 0, match( 'lalala', '\.' ), 'match: lalala =~ \. => 0' ) ; - is( 0, match( 'lalalaX', '^lalala$' ), 'match: lalalaX =~ ^lalala$ => 0' ) ; - is( 0, match( 'lalala', '/lalala/' ), 'match: lalala =~ /lalala/ => 1' ) ; - - is( 1, match( 'LALALA', '(?i:lalala)' ), 'match: LALALA =~ (?i:lalala) => 1' ) ; + return $mysync->{ nb_messages_in_2_not_in_1 } ; +} + + +sub nb_messages_in_1_not_in_2 +{ + my $mysync = shift ; + if ( not defined $mysync ) { return ; } + + $mysync->{ nb_messages_in_1_not_in_2 } = scalar( + list_keys_in_2_not_in_1( + $mysync->{ h2_folders_of_md5 }, + $mysync->{ h1_folders_of_md5 } ) ) ; + + return $mysync->{ nb_messages_in_1_not_in_2 } ; +} + + + +sub comment_on_final_diff_in_1_not_in_2 +{ + my $mysync = shift ; + + if ( not defined $mysync + or $mysync->{ justfolders } + or $mysync->{ useuid } + ) + { + return ; + } + + my $nb_identified_h1_messages = scalar( keys %{ $mysync->{ h1_folders_of_md5 } } ) ; + my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ; + $mysync->{ debug } and myprint( "nb_keys h1_folders_of_md5 $nb_identified_h1_messages\n" ) ; + $mysync->{ debug } and myprint( "nb_keys h2_folders_of_md5 $nb_identified_h2_messages\n" ) ; + + if ( 0 == $nb_identified_h1_messages ) { return ; } + + # Calculate if not yet done + if ( not defined $mysync->{ nb_messages_in_1_not_in_2 } ) + { + nb_messages_in_1_not_in_2( $mysync ) ; + } + + + if ( 0 == $mysync->{ nb_messages_in_1_not_in_2 } ) + { + myprint( "The sync looks good, all ", + $nb_identified_h1_messages, + " identified messages in host1 are on host2.\n" ) ; + } + else + { + myprint( "The sync is not finished, there are ", + $mysync->{ nb_messages_in_1_not_in_2 }, + " among ", + $nb_identified_h1_messages, + " identified messages in host1 that are not on host2.\n" ) ; + } + + + if ( 1 <= $mysync->{ h1_nb_msg_noheader } ) + { + myprint( "There are ", + $mysync->{ h1_nb_msg_noheader }, + " unidentified messages (usually Sent or Draft messages).", + " To sync them add option --addheader\n" ) ; + } + else + { + myprint( "There is no unidentified message on host1.\n" ) ; + } + + return ; +} + +sub comment_on_final_diff_in_2_not_in_1 +{ + my $mysync = shift ; + + if ( not defined $mysync + or $mysync->{ justfolders } + or $mysync->{ useuid } + ) + { + return ; + } + + my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ; + # Calculate if not done yet + if ( not defined $mysync->{ nb_messages_in_2_not_in_1 } ) + { + nb_messages_in_2_not_in_1( $mysync ) ; + } + + if ( 0 == $mysync->{ nb_messages_in_2_not_in_1 } ) + { + myprint( "The sync is strict, all ", + $nb_identified_h2_messages, + " identified messages in host2 are on host1.\n" ) ; + } + else + { + myprint( "The sync is not strict, there are ", + $mysync->{ nb_messages_in_2_not_in_1 }, + " among ", + $nb_identified_h2_messages, + " identified messages in host2 that are not on host1.", + " Use --delete2 and sync again to delete them and have a strict sync.\n" + ) ; + } + return ; +} + + +sub tests_match +{ + note( 'Entering tests_match()' ) ; + + # undef serie + is( undef, match( ), 'match: no args => undef' ) ; + is( undef, match( 'lalala' ), 'match: one args => undef' ) ; + + # This one gives 0 under a binary made by pp + # but 1 under "normal" Perl interpreter. So a PAR bug? + #is( 1, match( q{}, q{} ), 'match: q{} =~ q{} => 1' ) ; + + is( 'lalala', match( 'lalala', 'lalala' ), 'match: lalala =~ lalala => lalala' ) ; + is( 'lalala', match( 'lalala', '^lalala' ), 'match: lalala =~ ^lalala => lalala' ) ; + is( 'lalala', match( 'lalala', 'lalala$' ), 'match: lalala =~ lalala$ => lalala' ) ; + is( 'lalala', match( 'lalala', '^lalala$' ), 'match: lalala =~ ^lalala$ => lalala' ) ; + is( '_lalala_', match( '_lalala_', 'lalala' ), 'match: _lalala_ =~ lalala => _lalala_' ) ; + is( 'lalala', match( 'lalala', '.*' ), 'match: lalala =~ .* => lalala' ) ; + is( 'lalala', match( 'lalala', '.' ), 'match: lalala =~ . => lalala' ) ; + is( '/lalala/', match( '/lalala/', '/lalala/' ), 'match: /lalala/ =~ /lalala/ => /lalala/' ) ; + + is( 0, match( 'foo', 's/foo/bar/g' ), 'match: foo =~ s/foo/bar/g => 0' ) ; + is( 's/foo/bar/g', match( 's/foo/bar/g', 's/foo/bar/g' ), 'match: s/foo/bar/g =~ s/foo/bar/g => s/foo/bar/g' ) ; + + + is( 0, match( 'lalala', 'ooo' ), 'match: lalala =~ ooo => 0' ) ; + is( 0, match( 'lalala', 'lal_ala' ), 'match: lalala =~ lal_ala => 0' ) ; + is( 0, match( 'lalala', '\.' ), 'match: lalala =~ \. => 0' ) ; + is( 0, match( 'lalalaX', '^lalala$' ), 'match: lalalaX =~ ^lalala$ => 0' ) ; + is( 0, match( 'lalala', '/lalala/' ), 'match: lalala =~ /lalala/ => 0' ) ; + + is( 'LALALA', match( 'LALALA', '(?i:lalala)' ), 'match: LALALA =~ (?i:lalala) => 1' ) ; is( undef, match( 'LALALA', '(?{`ls /`})' ), 'match: LALALA =~ (?{`ls /`}) => undef' ) ; - is( undef, match( 'LALALA', '(?{print "CACA"})' ), 'match: LALALA =~ (?{print "CACA"}) => undef' ) ; + is( undef, match( 'LALALA', '(?{print "CACA"})' ), 'match: LALALA =~ (?{print "CACA"}) => undef' ) ; is( undef, match( 'CACA', '(??{print "CACA"})' ), 'match: CACA =~ (??{print "CACA"}) => undef' ) ; note( 'Leaving tests_match()' ) ; @@ -10198,15 +17132,16 @@ sub tests_match { return ; } -sub match { +sub match +{ my( $var, $regex ) = @ARG ; # undef cases if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; } # normal cases - if ( eval { $var =~ $regex } ) { - return 1 ; + if ( eval { $var =~ qr{$regex} } ) { + return $var ; }elsif ( $EVAL_ERROR ) { myprint( "Fatal regex $regex\n" ) ; return ; @@ -10217,7 +17152,8 @@ sub match { } -sub tests_notmatch { +sub tests_notmatch +{ note( 'Entering tests_notmatch()' ) ; # undef serie @@ -10232,7 +17168,7 @@ sub tests_notmatch { # but 0 under "normal" Perl interpreter. So a PAR bug, same in tests_match . #is( 0, notmatch( q{}, q{} ), 'notmatch: q{} !~ q{} => 0' ) ; - is( 0, notmatch( 'lalala', 'lalala' ), 'notmatch: lalala !~ lalala => 0' ) ; + is( 0, notmatch( 'lalala', 'lalala' ), 'notmatch: lalala !~ lalala => 0' ) ; is( 0, notmatch( 'lalala', '^lalala' ), 'notmatch: lalala !~ ^lalala => 0' ) ; is( 0, notmatch( 'lalala', 'lalala$' ), 'notmatch: lalala !~ lalala$ => 0' ) ; is( 0, notmatch( 'lalala', '^lalala$' ), 'notmatch: lalala !~ ^lalala$ => 0' ) ; @@ -10251,7 +17187,8 @@ sub tests_notmatch { return ; } -sub notmatch { +sub notmatch +{ my( $var, $regex ) = @ARG ; # undef cases @@ -10270,30 +17207,32 @@ sub notmatch { } -sub delete_folders_in_2_not_in_1 { +sub delete_folders_in_2_not_in_1 +{ - foreach my $folder (@h2_folders_not_in_1) { + foreach my $folder ( @h2_folders_not_in_1 ) { if ( defined $delete2foldersonly and eval "\$folder !~ $delete2foldersonly" ) { - myprint( "Not deleting $folder because of --delete2foldersonly $delete2foldersonly\n" ) ; + myprint( "Not deleting $folder because of --delete2foldersonly $delete2foldersonly\n" ) ; next ; } if ( defined $delete2foldersbutnot and eval "\$folder =~ $delete2foldersbutnot" ) { - myprint( "Not deleting $folder because of --delete2foldersbutnot $delete2foldersbutnot\n" ) ; + myprint( "Not deleting $folder because of --delete2foldersbutnot $delete2foldersbutnot\n" ) ; next ; } my $res = $sync->{dry} ; # always success in dry mode! - $imap2->unsubscribe( $folder ) if ( ! $sync->{dry} ) ; - $res = $imap2->delete( $folder ) if ( ! $sync->{dry} ) ; + $sync->{imap2}->unsubscribe( $folder ) if ( ! $sync->{dry} ) ; + $res = $sync->{imap2}->delete( $folder ) if ( ! $sync->{dry} ) ; if ( $res ) { - myprint( "Deleted $folder", "$sync->{dry_message}", "\n" ) ; + myprint( "Deleted $folder", "$sync->{dry_message}", "\n" ) ; }else{ - myprint( "Deleting $folder failed", "\n" ) ; + myprint( "Deleting $folder failed", "\n" ) ; } } return ; } -sub delete_folder { +sub delete_folder +{ my ( $mysync, $imap, $folder, $Side ) = @_ ; if ( ! $mysync ) { return ; } if ( ! $imap ) { return ; } @@ -10306,15 +17245,16 @@ sub delete_folder { $res = $imap->delete( $folder ) ; } if ( $res ) { - myprint( "$Side deleted $folder", $mysync->{dry_message}, "\n" ) ; + myprint( "$Side deleted $folder", $mysync->{dry_message}, "\n" ) ; return 1 ; }else{ - myprint( "$Side deleting $folder failed", "\n" ) ; + myprint( "$Side deleting $folder failed", "\n" ) ; return ; } } -sub delete1emptyfolders { +sub delete1emptyfolders +{ my $mysync = shift ; if ( ! $mysync ) { return ; } # abort if no parameter if ( ! $mysync->{delete1emptyfolders} ) { return ; } # abort if --delete1emptyfolders off @@ -10327,30 +17267,30 @@ sub delete1emptyfolders { foreach my $folder ( reverse sort @{ $mysync->{h1_folders_wanted} } ) { my $parenthood = $imap->is_parent( $folder ) ; if ( defined $parenthood and $parenthood ) { - myprint( "Host1 folder $folder has subfolders\n" ) ; + myprint( "Host1: folder $folder has subfolders\n" ) ; $folders_kept{ $folder }++ ; next ; } - my $nb_messages_select = examine_folder_and_count( $imap, $folder, 'Host1' ) ; + my $nb_messages_select = examine_folder_and_count( $mysync, $imap, $folder, 'Host1' ) ; if ( ! defined $nb_messages_select ) { next ; } # Select failed => Neither continue nor keep this folder } my $nb_messages_search = scalar( @{ $imap->messages( ) } ) ; if ( 0 != $nb_messages_select and 0 != $nb_messages_search ) { - myprint( "Host1 folder $folder has messages: $nb_messages_search (search) $nb_messages_select (select)\n" ) ; + myprint( "Host1: folder $folder has messages: $nb_messages_search (search) $nb_messages_select (select)\n" ) ; $folders_kept{ $folder }++ ; next ; } if ( 0 != $nb_messages_select + $nb_messages_search ) { - myprint( "Host1 folder $folder odd messages count: $nb_messages_search (search) $nb_messages_select (select)\n" ) ; + myprint( "Host1: folder $folder odd messages count: $nb_messages_search (search) $nb_messages_select (select)\n" ) ; $folders_kept{ $folder }++ ; next ; } # Here we must have 0 messages by messages() aka "SEARCH ALL" and also "EXAMINE" if ( uc $folder eq 'INBOX' ) { - myprint( "Host1 Not deleting $folder\n" ) ; + myprint( "Host1: Not deleting $folder\n" ) ; $folders_kept{ $folder }++ ; next ; } - myprint( "Host1 deleting empty folder $folder\n" ) ; + myprint( "Host1: deleting empty folder $folder\n" ) ; # can not delete a SELECTed or EXAMINEd folder so closing it # could changed be SELECT INBOX $imap->close( ) ; # close after examine does not expunge; anyway expunging an empty folder... @@ -10366,7 +17306,8 @@ sub delete1emptyfolders { return ; } -sub remove_deleted_folders_from_wanted_list { +sub remove_deleted_folders_from_wanted_list +{ my ( $mysync, %folders_kept ) = @ARG ; my @h1_folders_wanted_init = @{ $mysync->{h1_folders_wanted} } ; @@ -10380,11 +17321,13 @@ sub remove_deleted_folders_from_wanted_list { return ; } -sub examine_folder_and_count { - my ( $imap, $folder, $Side ) = @_ ; + +sub examine_folder_and_count +{ + my ( $mysync, $imap, $folder, $Side ) = @_ ; $Side ||= 'HostX' ; - if ( ! examine_folder( $imap, $folder, $Side ) ) { + if ( ! examine_folder( $mysync, $imap, $folder, $Side ) ) { return ; } my $nb_messages_select = count_from_select( $imap->History ) ; @@ -10392,7 +17335,8 @@ sub examine_folder_and_count { } -sub tests_delete1emptyfolders { +sub tests_delete1emptyfolders +{ note( 'Entering tests_delete1emptyfolders()' ) ; @@ -10403,7 +17347,7 @@ sub tests_delete1emptyfolders { $syncT->{imap1} = $imapT ; is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef imap} ) ; - require Test::MockObject ; + require_ok( "Test::MockObject" ) ; $imapT = Test::MockObject->new( ) ; $syncT->{imap1} = $imapT ; @@ -10498,7 +17442,8 @@ sub tests_delete1emptyfolders { return ; } -sub tests_delete1emptyfolders_unit { +sub tests_delete1emptyfolders_unit +{ note( 'Entering tests_delete1emptyfolders_unit()' ) ; my $syncT = shift ; @@ -10519,20 +17464,20 @@ sub tests_delete1emptyfolders_unit { return ; } -sub extract_header { +sub extract_header +{ my $string = shift ; my ( $header ) = split /\n\n/x, $string ; if ( ! $header ) { return( q{} ) ; } - #myprint( "[$header]\n" ) ; + #myprint( "[$header]\n" ) ; return( $header ) ; } -sub tests_extract_header { +sub tests_extract_header +{ note( 'Entering tests_extract_header()' ) ; - - my $h = <<'EOM'; Message-Id: <20100428101817.A66CB162474E@plume.est.belle> Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST) @@ -10566,19 +17511,19 @@ sub decompose_header{ my ($key, $val ) ; my @line = split /\n|\r\n/x, $string ; foreach my $line ( @line ) { - #myprint( "DDD $line\n" ) ; + #myprint( "DDD $line\n" ) ; # End of header last if ( $line =~ m{^$}xo ) ; # Key: value if ( $line =~ m/(^[^:]+):\s(.*)/xo ) { $key = $1 ; $val = $2 ; - $debugdev and myprint( "DDD KV [$key] [$val]\n" ) ; + $debugdev and myprint( "DDD KV [$key] [$val]\n" ) ; push @{ $header->{ $key } }, $val ; # blanc and value => value from previous line continues }elsif( $line =~ m/^(\s+)(.*)/xo ) { $val = $2 ; - $debugdev and myprint( "DDD V [$val]\n" ) ; + $debugdev and myprint( "DDD V [$val]\n" ) ; @{ $header->{ $key } }[ $LAST ] .= " $val" if $key ; # dirty line? }else{ @@ -10700,7 +17645,8 @@ EOH return ; } -sub tests_epoch { +sub tests_epoch +{ note( 'Entering tests_epoch()' ) ; ok( '1282658400' eq epoch( '24-Aug-2010 16:00:00 +0200' ), 'epoch 24-Aug-2010 16:00:00 +0200 -> 1282658400' ) ; @@ -10714,7 +17660,7 @@ sub tests_epoch { ok( '1280671200' eq epoch( '1-Aug-2010 12:00:00 -0200' ), 'epoch 1-Aug-2010 12:00:00 -0200 -> 1280671200' ) ; ok( '1280671200' eq epoch( '1-Aug-2010 16:01:00 +0201' ), 'epoch 1-Aug-2010 16:01:00 +0201 -> 1280671200' ) ; ok( '1280671200' eq epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ; - + is( '1280671200', epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ; is( '946684800', epoch( '00-Jan-0000 00:00:00 +0000' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ; @@ -10722,7 +17668,8 @@ sub tests_epoch { return ; } -sub epoch { +sub epoch +{ # incoming format: # internal date 24-Aug-2010 16:00:00 +0200 @@ -10736,10 +17683,10 @@ sub epoch { my $time ; if ( $d =~ m{(\d{1,2})-([A-Z][a-z]{2})-(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-))(\d{2})(\d{2})}xo ) { - #myprint( "internal: [$1][$2][$3][$4][$5][$6][$7][$8][$9]\n" ) ; + #myprint( "internal: [$1][$2][$3][$4][$5][$6][$7][$8][$9]\n" ) ; ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m ) = ( $1, $2, $3, $4, $5, $6, $7, $8, $9 ) ; - #myprint( "( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )\n" ) ; + #myprint( "( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )\n" ) ; $sign = +1 if ( '+' eq $sign ) ; $sign = $MINUS_ONE if ( '-' eq $sign ) ; @@ -10756,7 +17703,8 @@ sub epoch { return( $time ) ; } -sub tests_add_header { +sub tests_add_header +{ note( 'Entering tests_add_header()' ) ; ok( 'Message-Id: ' eq add_header(), 'add_header no arg' ) ; @@ -10766,7 +17714,8 @@ sub tests_add_header { return ; } -sub add_header { +sub add_header +{ my $header_uid = shift || 'mistake' ; my $header_Message_Id = 'Message-Id: <' . $header_uid . '@imapsync>' ; return( $header_Message_Id ) ; @@ -10775,7 +17724,8 @@ sub add_header { -sub tests_max_line_length { +sub tests_max_line_length +{ note( 'Entering tests_max_line_length()' ) ; ok( 0 == max_line_length( q{} ), 'max_line_length: 0 == null string' ) ; @@ -10797,7 +17747,8 @@ sub tests_max_line_length { return ; } -sub max_line_length { +sub max_line_length +{ my $string = shift ; my $max = 0 ; @@ -10807,8 +17758,27 @@ sub max_line_length { return( $max ) ; } +sub set_checknoabletosearch +{ + my $mysync = shift @ARG ; + if ( defined $mysync->{ checknoabletosearch } ) + { + return ; + } + elsif ( $mysync->{ justfolders } ) + { + $mysync->{ checknoabletosearch } = 0 ; + } + else + { + $mysync->{ checknoabletosearch } = 1 ; + } + return ; +} + -sub tests_setlogfile { +sub tests_setlogfile +{ note( 'Entering tests_setlogfile()' ) ; my $mysync = {} ; @@ -10860,7 +17830,7 @@ sub tests_setlogfile { is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt", setlogfile( $mysync ), "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt" ) ; - + $mysync = { timestart => 2, user1 => 'user1', @@ -10894,37 +17864,41 @@ sub tests_setlogfile { "setlogfile: logdir undef, $DEFAULT_LOGDIR/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt" ) ; - + } ; note( 'Leaving tests_setlogfile()' ) ; return ; } -sub setlogfile { +sub setlogfile +{ my( $mysync ) = shift ; - + # When aborting another process the log file name finishes with "_abort.txt" - my $abort_suffix = ( $mysync->{abort} ) ? '_abort' : q{} ; + my $abort_suffix = ( $mysync->{ abort } ) ? '_abort' : q{} ; + # When acting as a proxy the log file name finishes with "_remote.txt" - # proxy mode is not done yet - my $remote_suffix = ( $mysync->{remote} ) ? '_remote' : q{} ; - - my $suffix = ( filter_forbidden_characters( move_slash( $mysync->{user1} ) ) || q{} ) - . '_' - . ( filter_forbidden_characters( move_slash( $mysync->{user2} ) ) || q{} ) - . $remote_suffix . $abort_suffix ; + # proxy mode is not done in imapsync, it is done by proximapsync + my $remote_suffix = ( $mysync->{ remote } ) ? '_remote' : q{} ; + + my $suffix = ( + filter_forbidden_characters( slash_to_underscore( $mysync->{ user1 } ) ) || q{} ) + . '_' + . ( filter_forbidden_characters( slash_to_underscore( $mysync->{ user2 } ) ) || q{} ) + . $remote_suffix . $abort_suffix ; - $mysync->{logdir} = defined $mysync->{logdir} ? $mysync->{logdir} : $DEFAULT_LOGDIR ; - - $mysync->{logfile} = defined $mysync->{logfile} - ? "$mysync->{logdir}/$mysync->{logfile}" - : logfile( $mysync->{timestart}, $suffix, $mysync->{logdir} ) ; + $mysync->{ logdir } = defined $mysync->{ logdir } ? $mysync->{ logdir } : $DEFAULT_LOGDIR ; - return( $mysync->{logfile} ) ; + $mysync->{ logfile } = defined $mysync->{ logfile } + ? "$mysync->{ logdir }/$mysync->{ logfile }" + : logfile( $mysync->{ timestart }, $suffix, $mysync->{ logdir } ) ; + + return( $mysync->{ logfile } ) ; } -sub tests_logfile { +sub tests_logfile +{ note( 'Entering tests_logfile()' ) ; SKIP: { @@ -10932,7 +17906,8 @@ sub tests_logfile { skip( 'Too hard to have a well known timezone on Windows', 10 ) if ( 'MSWin32' eq $OSNAME ) ; local $ENV{TZ} = 'GMT' ; - { POSIX::tzset unless ('MSWin32' eq $OSNAME) ; + { + POSIX::tzset unless ('MSWin32' eq $OSNAME) ; is( '1970_01_01_00_00_00_000.txt', logfile( ), 'logfile: no args => 1970_01_01_00_00_00.txt' ) ; is( '1970_01_01_00_00_00_000.txt', logfile( 0 ), 'logfile: 0 => 1970_01_01_00_00_00.txt' ) ; is( '1970_01_01_00_01_01_000.txt', logfile( 61 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ; @@ -10942,10 +17917,10 @@ sub tests_logfile { is( '2010_08_24_14_01_01_000_poupinette.txt', logfile( 1_282_658_461, 'poupinette' ), 'logfile: 1_282_658_461 poupinette => 2010_08_24_14_01_01_poupinette.txt' ) ; is( '2010_08_24_14_01_01_000_removeblanks.txt', logfile( 1_282_658_461, ' remove blanks ' ), 'logfile: 1_282_658_461 remove blanks => 2010_08_24_14_01_01_000_removeblanks' ) ; - is( '2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup' ), + is( '2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup' ), 'logfile: 1_282_658_461.2347 poup => 2010_08_24_14_01_01_234_poup.txt' ) ; - is( 'dirdir/2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup', 'dirdir' ), + is( 'dirdir/2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup', 'dirdir' ), 'logfile: 1_282_658_461.2347 poup dirdir => dirdir/2010_08_24_14_01_01_234_poup.txt' ) ; @@ -10959,7 +17934,8 @@ sub tests_logfile { } -sub logfile { +sub logfile +{ my ( $time, $suffix, $dir ) = @_ ; $time ||= 0 ; @@ -10970,7 +17946,7 @@ sub logfile { my $sep_dir = ( $dir ) ? '/' : q{} ; my $date_str = POSIX::strftime( '%Y_%m_%d_%H_%M_%S', localtime $time ) ; - # Because of ab tests or web access, more than one sync withing one second is possible + # Because of ab tests or web accesses, more than one sync withing one second is possible # so we add also milliseconds $date_str .= sprintf "_%03d", ($time - int( $time ) ) * 1000 ; # without rounding my $logfile = "${dir}${sep_dir}${date_str}${sep_suffix}${suffix}.txt" ; @@ -10978,18 +17954,52 @@ sub logfile { } +sub tests_localtimez +{ + note( 'Entering tests_localtimez()' ) ; + + SKIP: { + # Too hard to have a well known timezone on Windows + skip( 'Too hard to have a well known timezone on Windows', 1 ) if ( 'MSWin32' eq $OSNAME ) ; + local $ENV{TZ} = 'GMT' ; + like( localtimez( 0 ), qr'1970-01-01 00:00:00 \+0000 (GMT|UTC)', 'localtimez: 0 => match 1970-01-01 00:00:00 +0000 GMT' ) ; + } + + is( localtimez( ), localtimez( time ), 'localtimez: undef => equals currrent' ) ; + note( 'Leaving tests_localtimez()' ) ; + return ; +} + + -sub tests_move_slash { - note( 'Entering tests_move_slash()' ) ; +sub localtimez +{ + my $time = shift ; - is( undef, move_slash( ), 'move_slash: no parameters => undef' ) ; - is( '_', move_slash( '/' ), 'move_slash: / => _' ) ; - is( '_abc_def_', move_slash( '/abc/def/' ), 'move_slash: /abc/def/ => _abc_def_' ) ; - note( 'Leaving tests_move_slash()' ) ; + $time = defined( $time ) ? $time : time ; + + my $datetimestr = POSIX::strftime( '%A %e %B %Y-%m-%d %H:%M:%S %z %Z', localtime( $time ) ) ; + + #myprint( "$datetimestr\n" ) ; + return $datetimestr ; +} + + + + +sub tests_slash_to_underscore +{ + note( 'Entering tests_slash_to_underscore()' ) ; + + is( undef, slash_to_underscore( ), 'slash_to_underscore: no parameters => undef' ) ; + is( '_', slash_to_underscore( '/' ), 'slash_to_underscore: / => _' ) ; + is( '_abc_def_', slash_to_underscore( '/abc/def/' ), 'slash_to_underscore: /abc/def/ => _abc_def_' ) ; + note( 'Leaving tests_slash_to_underscore()' ) ; return ; } -sub move_slash { +sub slash_to_underscore +{ my $string = shift ; if ( ! defined $string ) { return ; } @@ -11002,21 +18012,22 @@ sub move_slash { -sub tests_million_folders_baby_2 { +sub tests_million_folders_baby_2 +{ note( 'Entering tests_million_folders_baby_2()' ) ; my %long ; @long{ 1 .. 900_000 } = (1) x 900_000 ; - #myprint( %long, "\n" ) ; + #myprint( %long, "\n" ) ; my $pasglop = 0 ; foreach my $elem ( 1 .. 900_000 ) { - #$debug and myprint( "$elem " ) ; + #$debug and myprint( "$elem " ) ; if ( not exists $long{ $elem } ) { $pasglop++ ; } } ok( 0 == $pasglop, 'tests_million_folders_baby_2: search among 900_000' ) ; - # myprint( "$pasglop\n" ) ; + # myprint( "$pasglop\n" ) ; note( 'Leaving tests_million_folders_baby_2()' ) ; return ; @@ -11024,7 +18035,8 @@ sub tests_million_folders_baby_2 { -sub tests_always_fail { +sub tests_always_fail +{ note( 'Entering tests_always_fail()' ) ; is( 0, 1, 'always_fail: 0 is 1' ) ; @@ -11033,31 +18045,144 @@ sub tests_always_fail { return ; } -sub logfileprepa { + +sub tests_logfileprepa +{ + note( 'Entering tests_logfileprepa()' ) ; + + is( undef, logfileprepa( ), 'logfileprepa: no args => undef' ) ; + my $logfile = 'W/tmp/tests/tests_logfileprepa.txt' ; + is( 1, logfileprepa( $logfile ), 'logfileprepa: W/tmp/tests/tests_logfileprepa.txt => 1' ) ; + + note( 'Leaving tests_logfileprepa()' ) ; + return ; +} + +sub logfileprepa +{ my $logfile = shift ; - my $dirname = dirname( $logfile ) ; - do_valid_directory( $dirname ) || return( 0 ) ; - return( 1 ) ; + if ( ! defined( $logfile ) ) + { + return ; + }else + { + #myprint( "[$logfile]\n" ) ; + my $dirname = dirname( $logfile ) ; + do_valid_directory( $dirname ) || return( 0 ) ; + return( 1 ) ; + } +} + + +sub tests_teelaunch +{ + note( 'Entering tests_teelaunch()' ) ; + + is( undef, teelaunch( ), 'teelaunch: no args => undef' ) ; + my $mysync = {} ; + is( undef, teelaunch( $mysync ), 'teelaunch: arg empty {} => undef' ) ; + $mysync->{logfile} = q{} ; + is( undef, teelaunch( $mysync ), 'teelaunch: logfile empty string => undef' ) ; + + # First time, learning IO::Tee intrasics + $mysync->{logfile} = 'W/tmp/tests/tests_teelaunch.txt' ; + isa_ok( my $tee = teelaunch( $mysync ), 'IO::Tee' , 'teelaunch: logfile W/tmp/tests/tests_teelaunch.txt' ) ; + is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ; + is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is Hi!\n' ) ; + is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ; + is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is Hi!\nHoo\n' ) ; + + # closing so tee won't be happy + close $mysync->{logfile_handle} ; + is( undef, print( $tee "Argh1\n" ), 'teelaunch: write Argh1') ; + is( undef, print( $tee "Argh2\n" ), 'teelaunch: write Argh2') ; + # write not done + is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is still Hi!\nHoo\n' ) ; + print join( ' ', $tee->handles ), "\n"; + is( 2, scalar $tee->handles, 'teelaunch: 2 handles') ; + shift @{*{$tee}}; + print join(' ', $tee->handles), "\n" ; + is( 1, scalar $tee->handles, 'teelaunch: 1 handle') ; + is( 1, print( $tee "Argh3\n" ), 'teelaunch: write Argh3 yeah') ; + + shift @{*{$tee}}; + # will not print anything now + is( 0, scalar $tee->handles, 'teelaunch: 0 handle') ; + is( 1, print( $tee "Argh 4\n" ), 'teelaunch: write Argh4 no') ; + + # Second time, lesson learnt IO::Tee + $mysync->{logfile} = 'W/tmp/tests/tests_teelaunch2.txt' ; + isa_ok( $tee = teelaunch( $mysync ), 'IO::Tee' , 'teelaunch: logfile W/tmp/tests/tests_teelaunch2.txt' ) ; + is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ; + is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is Hi!\n' ) ; + is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ; + is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is Hi!\nHoo\n' ) ; + + is( 1, teefinish( $mysync ), 'teefinish: return 1') ; + is( 1, print( $tee "Argh1\n" ), 'teelaunch: write Argh1') ; + is( 1, print( $tee "Argh2\n" ), 'teelaunch: write Argh2') ; + is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is still Hi!\nHoo\n' ) ; + is( 1, teefinish( $mysync ), 'teefinish: still return 1') ; + + note( 'Leaving tests_teelaunch()' ) ; + return ; } -sub teelaunch { +sub teelaunch +{ my $mysync = shift ; + + if ( ! defined( $mysync ) ) + { + return ; + } + my $logfile = $mysync->{logfile} ; + + if ( ! $logfile ) + { + return ; + } + logfileprepa( $logfile ) || croak "Error no valid directory to write log file $logfile : $OS_ERROR" ; + # This is a log file opened during the whole sync + ## no critic (InputOutput::RequireBriefOpen) open my $logfile_handle, '>', $logfile or croak( "Can not open $logfile for write: $OS_ERROR" ) ; + binmode $logfile_handle, ":encoding(UTF-8)" ; my $tee = IO::Tee->new( $logfile_handle, \*STDOUT ) ; - #*STDERR = *$tee{IO} ; - #select $tee ; $tee->autoflush( 1 ) ; $mysync->{logfile_handle} = $logfile_handle ; $mysync->{tee} = $tee ; return $tee ; } -sub getpwuid_any_os { +sub teefinish +{ + my $mysync = shift ; + + if ( ! defined( $mysync ) ) { return ; } + + my $tee = $mysync->{tee} ; + + if ( ! defined( $tee ) ) { return ; } + + if ( 2 == scalar $tee->handles ) + { + shift @{*{$tee}}; + } + else + { + # nothing + } + return scalar $tee->handles ; +} + + +sub getpwuid_any_os +{ my $uid = shift ; return( scalar getlogin ) if ( 'MSWin32' eq $OSNAME ) ; # Windows system @@ -11066,14 +18191,44 @@ sub getpwuid_any_os { } -sub simulong { - my $max_seconds = shift ; + + +sub abortifneeded +{ + my $mysync = shift ; + if ( -e $mysync->{ abortfile } ) + { + myprint( "Asked to terminate by file $mysync->{ abortfile }\n" ) ; + do_and_print_stats( $mysync ) ; + myprint( "You should resynchronize those accounts by running a sync again,\n", + "since some messages and entire folders might still be missing on host2.\n" + ) ; + exit_clean( $mysync, $EXIT_BY_FILE ) ; + return ; + } + else + { + return ; + } +} + +sub simulong +{ + my $mysync = shift ; + + my $max_seconds = $mysync->{ simulong } ; + + if ( ! $max_seconds ) { return ; } + my $division = 5 ; - my $last = $division * $max_seconds ; - foreach my $i ( 1 .. ( $last ) ) { - myprint( "Are you still here $i/$last\n" ) ; - #myprint( "Are you still here $i/$last\n" . ( "Ah" x 40 . "\n") x 4000 ) ; + my $last_count = int( $division * $max_seconds ) ; + $mysync->{ debug } and myprint "last_count $last_count = int( division $division * max_seconds $max_seconds)\n" ; + foreach my $i ( 1 .. ( $last_count ) ) { + myprint( "Are you still here ETA: " . ( $last_count - $i ) . "/$last_count msgs left\n" ) ; + #this one is for testing huge page behavior + #myprint( "Are you still here ETA: " . ($last_count - $i) . "/$last_count msgs left\n" . ( "Ah" x 40 . "\n") x 4000 ) ; sleep( 1 / $division ) ; + abortifneeded( $mysync ) ; } return ; @@ -11081,47 +18236,60 @@ sub simulong { -sub printenv { +sub printenv +{ myprint( "Environment variables listing:\n", - ( map { "$_ => $ENV{$_}\n" } sort keys %ENV), - "Environment variables listing end\n" ) ; + ( map { "$_ => $ENV{$_}\n" } sort keys %ENV), + "Environment variables listing end\n" ) ; return ; } -sub testsexit { - my $mysync = shift ; + +sub unittestssuite +{ + my $mysync = shift ; if ( ! ( $mysync->{ tests } or $mysync->{ testsdebug } or $mysync->{ testsunit } ) ) { - return ; - } + return ; + } + my $test_builder = Test::More->builder ; - tests( $mysync ) ; + tests( $mysync ) ; testsdebug( $mysync ) ; - testunitsession( $mysync ) ; - - my @summary = $test_builder->summary() ; - my @details = $test_builder->details() ; - my $nb_tests_run = scalar( @summary ) ; - my $nb_tests_expected = $test_builder->expected_tests() ; - my $nb_tests_failed = count_0s( @summary ) ; - my $tests_failed = report_failures( @details ) ; - if ( $nb_tests_failed or ( $nb_tests_run != $nb_tests_expected ) ) { - #$test_builder->reset( ) ; - myprint( "Summary of tests: failed $nb_tests_failed tests, run $nb_tests_run tests, expected to run $nb_tests_expected tests.\n", + testunitsession( $mysync ) ; + + my @summary = $test_builder->summary() ; + my @details = $test_builder->details() ; + my $nb_tests_run = scalar( @summary ) ; + my $nb_tests_expected = $test_builder->expected_tests() ; + my $nb_tests_failed = count_0s( @summary ) ; + my $tests_failed = report_failures( @details ) ; + if ( $nb_tests_failed or ( $nb_tests_run != $nb_tests_expected ) ) { + #$test_builder->reset( ) ; + myprint( "Summary of tests: failed $nb_tests_failed tests, run $nb_tests_run tests, expected to run $nb_tests_expected tests.\n", "List of failed tests:\n", $tests_failed ) ; - exit $EXIT_TESTS_FAILED ; - } - exit ; - #return ; + return $EXIT_TESTS_FAILED ; + } + + cleanup_mess_from_tests( ) ; + + return 0 ; +} + +sub cleanup_mess_from_tests +{ + undef @pipemess ; + return ; } -sub after_get_options { +sub after_get_options +{ my $mysync = shift ; my $numopt = shift ; # exit with --help option or no option at all - $debug and myprint( "numopt:$numopt\n" ) ; - + $mysync->{ debug } and myprint( "numopt:$numopt\n" ) ; + if ( $help or not $numopt ) { myprint( usage( $mysync ) ) ; exit ; @@ -11130,21 +18298,82 @@ sub after_get_options { return ; } -sub easyany { +sub tests_remove_edging_blanks +{ + note( 'Entering tests_remove_edging_blanks()' ) ; + + is( undef, remove_edging_blanks( ), 'remove_edging_blanks: no args => undef' ) ; + is( 'abcd', remove_edging_blanks( 'abcd' ), 'remove_edging_blanks: abcd => abcd' ) ; + is( 'ab cd', remove_edging_blanks( ' ab cd ' ), 'remove_edging_blanks: " ab cd " => "ab cd"' ) ; + + note( 'Leaving tests_remove_edging_blanks()' ) ; + return ; +} + + + +sub remove_edging_blanks +{ + my $string = shift ; + if ( ! defined $string ) + { + return ; + } + $string =~ s,^ +| +$,,g ; + return $string ; +} + + +sub tests_sanitize +{ + note( 'Entering tests_remove_edging_blanks()' ) ; + + is( undef, sanitize( ), 'sanitize: no args => undef' ) ; + my $mysync = {} ; + + $mysync->{ host1 } = ' example.com ' ; + $mysync->{ user1 } = ' to to ' ; + $mysync->{ password1 } = ' sex is good! ' ; + is( undef, sanitize( $mysync ), 'sanitize: => undef' ) ; + is( 'example.com', $mysync->{ host1 }, 'sanitize: host1 " example.com " => "example.com"' ) ; + is( 'to to', $mysync->{ user1 }, 'sanitize: user1 " to to " => "to to"' ) ; + is( 'sex is good!', $mysync->{ password1 }, 'sanitize: password1 " sex is good! " => "sex is good!"' ) ; + note( 'Leaving tests_remove_edging_blanks()' ) ; + return ; +} + + +sub sanitize +{ + my $mysync = shift ; + if ( ! defined $mysync ) + { + return ; + } + + foreach my $parameter ( qw( host1 host2 user1 user2 password1 password2 ) ) + { + $mysync->{ $parameter } = remove_edging_blanks( $mysync->{ $parameter } ) ; + } + return ; +} + +sub easyany +{ my $mysync = shift ; # Gmail if ( $mysync->{gmail1} and $mysync->{gmail2} ) { - $debug and myprint( "gmail1 gmail2\n") ; + $mysync->{ debug } and myprint( "gmail1 gmail2\n") ; gmail12( $mysync ) ; return ; } if ( $mysync->{gmail1} ) { - $debug and myprint( "gmail1\n" ) ; + $mysync->{ debug } and myprint( "gmail1\n" ) ; gmail1( $mysync ) ; } if ( $mysync->{gmail2} ) { - $debug and myprint( "gmail2\n" ) ; + $mysync->{ debug } and myprint( "gmail2\n" ) ; gmail2( $mysync ) ; } # Office 365 @@ -11156,6 +18385,16 @@ sub easyany { office2( $mysync ) ; } + # Exchange + if ( $mysync->{exchange1} ) { + exchange1( $mysync ) ; + } + + if ( $mysync->{exchange2} ) { + exchange2( $mysync ) ; + } + + # Domino if ( $mysync->{domino1} ) { domino1( $mysync ) ; @@ -11165,249 +18404,312 @@ sub easyany { domino2( $mysync ) ; } - return ; } -# From https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt -sub gmail12 { +# From and for https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt +sub gmail12 +{ my $mysync = shift ; # Gmail at host1 and host2 $mysync->{host1} ||= 'imap.gmail.com' ; $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ; $mysync->{host2} ||= 'imap.gmail.com' ; $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ; - $mysync->{maxbytespersecond} ||= 20_000 ; # should be 10_000 computed from by Gmail documentation - $mysync->{maxbytesafter} ||= 1_000_000_000 ; - $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ; - $mysync->{maxsleep} = $MAX_SLEEP ; - + $mysync->{maxbytespersecond} ||= 20_000 ; # should be less than 10_000 when computed from Gmail documentation + $mysync->{maxbytesafter} ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000 + $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ; + $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ; + $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 0 ; + $mysync->{ synclabels } = ( defined $mysync->{ synclabels } ) ? $mysync->{ synclabels } : 1 ; + $mysync->{ resynclabels } = ( defined $mysync->{ resynclabels } ) ? $mysync->{ resynclabels } : 1 ; + push @useheader, 'X-Gmail-Received', 'Message-Id' ; push @exclude, '\[Gmail\]$' ; + push @folderlast, '[Gmail]/All Mail' ; return ; } -sub gmail1 { + +sub gmail1 +{ my $mysync = shift ; # Gmail at host2 $mysync->{host1} ||= 'imap.gmail.com' ; $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ; - $mysync->{maxbytespersecond} ||= 40_000 ; # should be 20_000 computed from by Gmail documentation - $mysync->{maxbytesafter} ||= 2_500_000_000 ; - $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ; + $mysync->{maxbytespersecond} ||= 40_000 ; # should be 30_000 computed from by Gmail documentation + $mysync->{maxbytesafter} ||= 3_000_000_000 ; # + $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ; + $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ; $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ; - $mysync->{maxsleep} = $MAX_SLEEP ; - + push @useheader, 'X-Gmail-Received', 'Message-Id' ; - push @regextrans2, 's,\[Gmail\].,,' ; - push @folderlast, '[Gmail]/All Mail' ; + push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ; + push @folderlast, '[Gmail]/All Mail' ; return ; } -sub gmail2 { +sub gmail2 +{ my $mysync = shift ; # Gmail at host2 - $mysync->{host2} ||= 'imap.gmail.com' ; - $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ; - $mysync->{maxbytespersecond} ||= 20_000 ; # should be 10_000 computed from by Gmail documentation - $mysync->{maxbytesafter} ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000 - $maxsize ||= 25_000_000 ; - $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ; - $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ; - $expunge1 = ( defined $expunge1 ) ? $expunge1 : 1 ; - $mysync->{addheader} = ( defined $mysync->{addheader} ) ? $mysync->{addheader} : 1 ; - $mysync->{maxsleep} = $MAX_SLEEP ; - - push @exclude, '\[Gmail\]$' ; - push @useheader, 'X-Gmail-Received', 'Message-Id' ; - push @regextrans2, 's,\[Gmail\].,,' ; - push @regextrans2, 's/[ ]+/_/g' ; - push @regextrans2, q{s/['\\^"]/_/g} ; # Verified this - push @folderlast, "[Gmail]/All Mail" ; + $mysync->{ host2 } ||= 'imap.gmail.com' ; + $mysync->{ ssl2 } = ( defined $mysync->{ ssl2 } ) ? $mysync->{ ssl2 } : 1 ; + $mysync->{ maxbytespersecond } ||= 20_000 ; # should be less than 10_000 computed from by Gmail documentation + $mysync->{ maxbytesafter } ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000 + + $mysync->{ automap } = ( defined $mysync->{ automap } ) ? $mysync->{ automap } : 1 ; + #$skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ; + $mysync->{ expunge1 } = ( defined $mysync->{ expunge1 } ) ? $mysync->{ expunge1 } : 1 ; + $mysync->{ addheader } = ( defined $mysync->{ addheader } ) ? $mysync->{ addheader } : 1 ; + $mysync->{ maxsleep } = ( defined $mysync->{ maxsleep } ) ? $mysync->{ maxsleep } : $MAX_SLEEP ; ; + + #$mysync->{ maxsize } = ( defined $mysync->{ maxsize } ) ? $mysync->{ maxsize } : $GMAIL_MAXSIZE ; + + if ( ! $mysync->{ noexclude } ) { + push @exclude, '\[Gmail\]$' ; + } + push @useheader, 'Message-Id' ; + push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ; + + # push @{ $mysync->{ regextrans2 } }, 's/[ ]+/_/g' ; # is now replaced + # by the two more specific following regexes, + # they remove just the beginning and trailing blanks, not all. + push @{ $mysync->{ regextrans2 } }, 's,^ +| +$,,g' ; + push @{ $mysync->{ regextrans2 } }, 's,/ +| +/,/,g' ; + # + push @{ $mysync->{ regextrans2 } }, q{s/['\\^"]/_/g} ; # Verified this + push @folderlast, '[Gmail]/All Mail' ; return ; } # From https://imapsync.lamiral.info/FAQ.d/FAQ.Exchange.txt -sub office1 { - # Office 365 at host1 - my $mysync = shift ; +sub office1 +{ + # Office 365 at host1 + my $mysync = shift ; - $debug and myprint( "office1 configuration\n" ) ; - $mysync->{host1} ||= 'outlook.office365.com' ; - $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ; - return ; + output( $mysync, q{Option --office1 is like: --host1 outlook.office365.com --ssl1 --exclude "^Files$"} . "\n" ) ; + output( $mysync, "Option --office1 (cont) : unless overrided with --host1 otherhost --nossl1 --noexclude\n" ) ; + $mysync->{host1} ||= 'outlook.office365.com' ; + $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ; + if ( ! $mysync->{noexclude} ) { + push @exclude, '^Files$' ; + } + return ; } -sub office2 { - # Office 365 at host1 - my $mysync = shift ; - $mysync->{host2} ||= 'outlook.office365.com' ; - $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ; - $maxsize ||= 45_000_000 ; - $mysync->{maxmessagespersecond} ||= 4 ; - push @regexflag, 's/\\Flagged//g' ; - $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ; - push @regexmess, 's,(.{10500}),$1\r\n,g' ; - return ; -} -sub exchange1 { - # Exchange 2010/2013 at host1 +sub office2 +{ + # Office 365 at host2 + my $mysync = shift ; + output( $mysync, qq{Option --office2 is like: --host2 outlook.office365.com --ssl2 --maxsize 45_000_000 --maxmessagespersecond 4\n} ) ; + output( $mysync, qq{Option --office2 (cont) : --disarmreadreceipts --regexmess "wrap 10500" --f1f2 "Files=Files_renamed_by_imapsync"\n} ) ; + output( $mysync, qq{Option --office2 (cont) : unless overrided with --host2 otherhost --nossl2 ... --nodisarmreadreceipts --noregexmess\n} ) ; + output( $mysync, qq{Option --office2 (cont) : and --nof1f2 to avoid Files folder renamed to Files_renamed_by_imapsync\n} ) ; + $mysync->{host2} ||= 'outlook.office365.com' ; + $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ; + $mysync->{ maxsize } ||= 45_000_000 ; + $mysync->{maxmessagespersecond} ||= 4 ; + #push @{ $mysync->{ regexflag } }, 's/\\\\Flagged//g' ; # No problem without! tested 2018_09_10 + $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ; + # I dislike double negation but here is one + if ( ! $mysync->{noregexmess} ) + { + push @regexmess, 's,(.{10239}),$1\r\n,g' ; + } + # and another... + if ( ! $mysync->{nof1f2} ) + { + push @{ $mysync->{f1f2} }, 'Files=Files_renamed_by_imapsync' ; + } + return ; +} - # Well nothing to do so far - return ; +sub exchange1 +{ + # Exchange 2010/2013 at host1 + my $mysync = shift ; + output( $mysync, "Option --exchange1 does nothing (except printing this line...)\n" ) ; + # Well nothing to do so far + return ; } -sub exchange2 { - # Exchange 2010/2013 at host2 - my $mysync = shift ; - $maxsize ||= 10_000_000 ; - $mysync->{maxmessagespersecond} ||= 4 ; - push @regexflag, 's/\\Flagged//g' ; - $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ; - push @regexmess, 's,(.{10500}),$1\r\n,g' ; - return ; +sub exchange2 +{ + # Exchange 2010/2013 at host2 + my $mysync = shift ; + output( $mysync, "Option --exchange2 is like: --maxsize 10_000_000 --maxmessagespersecond 4 --disarmreadreceipts\n" ) ; + output( $mysync, "Option --exchange2 (cont) : --regexflag del Flagged --regexmess wrap 10500\n" ) ; + output( $mysync, "Option --exchange2 (cont) : unless overrided with --maxsize xxx --nodisarmreadreceipts --noregexflag --noregexmess\n" ) ; + $mysync->{ maxsize } ||= 10_000_000 ; + $mysync->{maxmessagespersecond} ||= 4 ; + $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ; + # I dislike double negation but here are two + if ( ! $mysync->{noregexflag} ) { + push @{ $mysync->{ regexflag } }, 's/\\\\Flagged//g' ; + } + if ( ! $mysync->{noregexmess} ) { + push @regexmess, 's,(.{10239}),$1\r\n,g' ; + } + return ; } -sub domino1 { +sub domino1 +{ # Domino at host1 my $mysync = shift ; - $sep1 = q{\\} ; + $mysync->{ sep1 } = q{\\} ; $prefix1 = q{} ; $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ; return ; } -sub domino2 { +sub domino2 +{ # Domino at host1 my $mysync = shift ; - $sep2 = q{\\} ; + $mysync->{ sep2 } = q{\\} ; $prefix2 = q{} ; $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ; - push @regextrans2, 's,^Inbox\\\\(.*),$1,i' ; + push @{ $mysync->{ regextrans2 } }, 's,^Inbox\\\\(.*),$1,i' ; return ; } -sub tests_resolv { +sub tests_resolv +{ note( 'Entering tests_resolv()' ) ; - + # is( , resolv( ), 'resolv: => ' ) ; is( undef, resolv( ), 'resolv: no args => undef' ) ; - is( undef, resolv( '' ), 'resolv: empty string => undef' ) ; + is( undef, resolv( q{} ), 'resolv: empty string => undef' ) ; is( undef, resolv( 'hostnotexist' ), 'resolv: hostnotexist => undef' ) ; is( '127.0.0.1', resolv( '127.0.0.1' ), 'resolv: 127.0.0.1 => 127.0.0.1' ) ; is( '127.0.0.1', resolv( 'localhost' ), 'resolv: localhost => 127.0.0.1' ) ; - is( '5.135.158.182', resolv( 'imapsync.lamiral.info' ), 'resolv: imapsync.lamiral.info => 5.135.158.182' ) ; - + is( '2001:41d0:2:84e0::1', resolv( 'imapsync.lamiral.info' ), 'resolv: imapsync.lamiral.info => 2001:41d0:2:84e0::1' ) ; + # ip6-localhost ( in /etc/hosts ) is( '::1', resolv( 'ip6-localhost' ), 'resolv: ip6-localhost => ::1' ) ; is( '::1', resolv( '::1' ), 'resolv: ::1 => ::1' ) ; - # ks2 + # ks2ipv6 now has CNAME ks6ipv6 is( '2001:41d0:8:d8b6::1', resolv( '2001:41d0:8:d8b6::1' ), 'resolv: 2001:41d0:8:d8b6::1 => 2001:41d0:8:d8b6::1' ) ; - is( '2001:41d0:8:d8b6::1', resolv( 'ks2ipv6.lamiral.info' ), 'resolv: ks2ipv6.lamiral.info => 2001:41d0:8:d8b6::1' ) ; + is( '2001:41d0:8:9951::1', resolv( 'ks6ipv6.lamiral.info' ), 'resolv: ks6ipv6.lamiral.info => 2001:41d0:8:9951::1' ) ; + # ks6 + is( '2001:41d0:8:9951::1', resolv( '2001:41d0:8:9951::1' ), 'resolv: 2001:41d0:8:9951::1 => 2001:41d0:8:9951::1' ) ; + is( '2001:41d0:8:9951::1', resolv( 'ks6ipv6.lamiral.info' ), 'resolv: ks6ipv6.lamiral.info => 2001:41d0:8:9951::1' ) ; # ks3 is( '2001:41d0:8:bebd::1', resolv( '2001:41d0:8:bebd::1' ), 'resolv: 2001:41d0:8:bebd::1 => 2001:41d0:8:bebd::1' ) ; is( '2001:41d0:8:bebd::1', resolv( 'ks3ipv6.lamiral.info' ), 'resolv: ks3ipv6.lamiral.info => 2001:41d0:8:bebd::1' ) ; - - + + note( 'Leaving tests_resolv()' ) ; return ; } -sub resolv { +sub resolv +{ my $host = shift @ARG ; - + if ( ! $host ) { return ; } my $addr ; if ( defined &Socket::getaddrinfo ) { $addr = resolv_with_getaddrinfo( $host ) ; return( $addr ) ; } - - - + + + my $iaddr = inet_aton( $host ) ; if ( ! $iaddr ) { return ; } $addr = inet_ntoa( $iaddr ) ; - + return $addr ; } -sub resolv_with_getaddrinfo { +sub resolv_with_getaddrinfo +{ my $host = shift @ARG ; - + + $sync->{ debug } and myprint( "Entering resolv_with_getaddrinfo( $host )\n" ) ; if ( ! $host ) { return ; } - my ( $err, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ; - if ( $err ) { - myprint( "Cannot getaddrinfo of $host: $err\n" ) ; + my ( $err_getaddrinfo, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ; + if ( $err_getaddrinfo ) { + myprint( "Cannot getaddrinfo of $host: $err_getaddrinfo\n" ) ; return ; } my @addr ; while( my $ai = shift @res ) { - my ( $err, $ipaddr ) = Socket::getnameinfo( $ai->{addr}, Socket::NI_NUMERICHOST(), Socket::NIx_NOSERV() ) ; - if ( $err ) { - myprint( "Cannot getnameinfo of $host: $err\n" ) ; + my ( $err_getnameinfo, $ipaddr ) = Socket::getnameinfo( $ai->{addr}, Socket::NI_NUMERICHOST(), Socket::NIx_NOSERV() ) ; + if ( $err_getnameinfo ) { + myprint( "Cannot getnameinfo of $host: $err_getnameinfo\n" ) ; return ; - } - $debug and myprint( "$host => $ipaddr\n" ) ; - push @addr, $ipaddr ; + }else{ + $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ; + push @addr, $ipaddr ; + my $reverse ; + ( $err_getnameinfo, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ; + $sync->{ debug } and myprint( "$host => $ipaddr => $reverse\n" ) ; + } + $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ; - my ( $err_r, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ; - $debug and myprint( "$host => $ipaddr => $reverse\n" ) ; } - + $sync->{ debug } and myprint( "Leaving resolv_with_getaddrinfo( $host => $addr[0])\n" ) ; return $addr[0] ; } -sub tests_resolvrev { +sub tests_resolvrev +{ note( 'Entering tests_resolvrev()' ) ; - + # is( , resolvrev( ), 'resolvrev: => ' ) ; is( undef, resolvrev( ), 'resolvrev: no args => undef' ) ; - is( undef, resolvrev( '' ), 'resolvrev: empty string => undef' ) ; + is( undef, resolvrev( q{} ), 'resolvrev: empty string => undef' ) ; is( undef, resolvrev( 'hostnotexist' ), 'resolvrev: hostnotexist => undef' ) ; is( 'localhost', resolvrev( '127.0.0.1' ), 'resolvrev: 127.0.0.1 => localhost' ) ; is( 'localhost', resolvrev( 'localhost' ), 'resolvrev: localhost => localhost' ) ; is( 'ks.lamiral.info', resolvrev( 'imapsync.lamiral.info' ), 'resolvrev: imapsync.lamiral.info => ks.lamiral.info' ) ; - + # ip6-localhost ( in /etc/hosts ) is( 'ip6-localhost', resolvrev( 'ip6-localhost' ), 'resolvrev: ip6-localhost => ip6-localhost' ) ; is( 'ip6-localhost', resolvrev( '::1' ), 'resolvrev: ::1 => ip6-localhost' ) ; # ks2 - is( 'ks2ipv6.lamiral.info', resolvrev( '2001:41d0:8:d8b6::1' ), 'resolvrev: 2001:41d0:8:d8b6::1 => ks2ipv6.lamiral.info' ) ; - is( 'ks2ipv6.lamiral.info', resolvrev( 'ks2ipv6.lamiral.info' ), 'resolvrev: ks2ipv6.lamiral.info => ks2ipv6.lamiral.info' ) ; + is( 'ks6ipv6.lamiral.info', resolvrev( '2001:41d0:8:d8b6::1' ), 'resolvrev: 2001:41d0:8:d8b6::1 => ks6ipv6.lamiral.info' ) ; + is( 'ks6ipv6.lamiral.info', resolvrev( 'ks6ipv6.lamiral.info' ), 'resolvrev: ks6ipv6.lamiral.info => ks6ipv6.lamiral.info' ) ; # ks3 is( 'ks3ipv6.lamiral.info', resolvrev( '2001:41d0:8:bebd::1' ), 'resolvrev: 2001:41d0:8:bebd::1 => ks3ipv6.lamiral.info' ) ; is( 'ks3ipv6.lamiral.info', resolvrev( 'ks3ipv6.lamiral.info' ), 'resolvrev: ks3ipv6.lamiral.info => ks3ipv6.lamiral.info' ) ; - - + + note( 'Leaving tests_resolvrev()' ) ; return ; } -sub resolvrev { +sub resolvrev +{ my $host = shift @ARG ; - + if ( ! $host ) { return ; } if ( defined &Socket::getaddrinfo ) { my $name = resolvrev_with_getaddrinfo( $host ) ; return( $name ) ; } - + return ; } -sub resolvrev_with_getaddrinfo { +sub resolvrev_with_getaddrinfo +{ my $host = shift @ARG ; - + if ( ! $host ) { return ; } my ( $err, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ; @@ -11423,32 +18725,35 @@ sub resolvrev_with_getaddrinfo { myprint( "Cannot getnameinfo of $host: $err\n" ) ; return ; } - $debug and myprint( "$host => $reverse\n" ) ; + $sync->{ debug } and myprint( "$host => $reverse\n" ) ; push @name, $reverse ; } - + return $name[0] ; } -sub tests_imapsping { +sub tests_imapsping +{ note( 'Entering tests_imapsping()' ) ; is( undef, imapsping( ), 'imapsping: no args => undef' ) ; is( undef, imapsping( 'hostnotexist' ), 'imapsping: hostnotexist => undef' ) ; is( 1, imapsping( 'imapsync.lamiral.info' ), 'imapsping: imapsync.lamiral.info => 1' ) ; - is( 1, imapsping( 'ks2ipv6.lamiral.info' ), 'imapsping: ks2ipv6.lamiral.info => 1' ) ; + is( 1, imapsping( 'ks6ipv6.lamiral.info' ), 'imapsping: ks6ipv6.lamiral.info => 1' ) ; note( 'Leaving tests_imapsping()' ) ; return ; } -sub imapsping { +sub imapsping +{ my $host = shift ; return tcpping( $host, $IMAP_SSL_PORT ) ; } -sub tests_tcpping { +sub tests_tcpping +{ note( 'Entering tests_tcpping()' ) ; is( undef, tcpping( ), 'tcpping: no args => undef' ) ; @@ -11472,7 +18777,8 @@ sub tests_tcpping { return ; } -sub tcpping { +sub tcpping +{ if ( 2 != scalar( @ARG ) ) { return ; } @@ -11489,8 +18795,8 @@ sub tcpping { $p->hires( 1 ) ; my ($ping_ok, $rtt, $ip ) = $p->ping( $host, $mytimeout ) ; if ( ! defined $ping_ok ) { return ; } - my $rtt_approx = sprintf( "%.3f", $rtt ) ; - $debug and myprint( "Host $host timeout $mytimeout port $port ok $ping_ok ip $ip acked in $rtt_approx s\n" ) ; + my $rtt_approx = sprintf( "%.3f", $rtt ) ; + $sync->{ debug } and myprint( "Host $host timeout $mytimeout port $port ok $ping_ok ip $ip acked in $rtt_approx s\n" ) ; $p->close( ) ; if( $ping_ok ) { return 1 ; @@ -11499,7 +18805,8 @@ sub tcpping { } } -sub tests_sslcheck { +sub tests_sslcheck +{ note( 'Entering tests_sslcheck()' ) ; my $mysync ; @@ -11514,7 +18821,7 @@ sub tests_sslcheck { $mysync = { sslcheck => 1, - host1 => 'imapsync.lamiral.info', + host1 => 'test1.lamiral.info', tls1 => 1, } ; @@ -11522,45 +18829,46 @@ sub tests_sslcheck { $mysync = { sslcheck => 1, - host1 => 'imapsync.lamiral.info', + host1 => 'test1.lamiral.info', } ; - is( 1, sslcheck( $mysync ), 'sslcheck: imapsync.lamiral.info => 1' ) ; - is( 1, $mysync->{ssl1}, 'sslcheck: imapsync.lamiral.info => ssl1 1' ) ; + is( 1, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info => 1' ) ; + is( 1, $mysync->{ssl1}, 'sslcheck: test1.lamiral.info => ssl1 1' ) ; $mysync->{sslcheck} = 0 ; is( undef, sslcheck( $mysync ), 'sslcheck: sslcheck off => undef' ) ; $mysync = { sslcheck => 1, - host1 => 'imapsync.lamiral.info', + host1 => 'test1.lamiral.info', host2 => 'test2.lamiral.info', } ; - - is( 2, sslcheck( $mysync ), 'sslcheck: imapsync.lamiral.info + test2.lamiral.info => 2' ) ; + + is( 2, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info + test2.lamiral.info => 2' ) ; $mysync = { sslcheck => 1, - host1 => 'imapsync.lamiral.info', + host1 => 'test1.lamiral.info', host2 => 'test2.lamiral.info', tls1 => 1, } ; - - is( 1, sslcheck( $mysync ), 'sslcheck: imapsync.lamiral.info + test2.lamiral.info + tls1 => 1' ) ; - + + is( 1, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info + test2.lamiral.info + tls1 => 1' ) ; + note( 'Leaving tests_sslcheck()' ) ; return ; } -sub sslcheck { +sub sslcheck +{ my $mysync = shift ; if ( ! $mysync->{sslcheck} ) { return ; } my $nb_on = 0 ; - $debug and myprint( "sslcheck\n" ) ; + $mysync->{ debug } and myprint( "sslcheck\n" ) ; if ( ( ! defined $mysync->{port1} ) and @@ -11602,30 +18910,33 @@ sub sslcheck { } -sub testslive { +sub testslive_init +{ my $mysync = shift ; - $mysync->{host1} = 'test1.lamiral.info' ; - $mysync->{user1} = 'test1' ; - $mysync->{password1} = 'secret1' ; - $mysync->{host2} = 'test2.lamiral.info' ; - $mysync->{user2} = 'test2' ; - $mysync->{password2} ='secret2' ; + $mysync->{host1} ||= 'test1.lamiral.info' ; + $mysync->{user1} ||= 'test1' ; + $mysync->{password1} ||= 'secret1' ; + $mysync->{host2} ||= 'test2.lamiral.info' ; + $mysync->{user2} ||= 'test2' ; + $mysync->{password2} ||= 'secret2' ; return ; } -sub testslive6 { +sub testslive6_init +{ my $mysync = shift ; - $mysync->{host1} = 'ks2ipv6.lamiral.info' ; - $mysync->{user1} = 'test1' ; - $mysync->{password1} = 'secret1' ; - $mysync->{host2} = 'ks2ipv6.lamiral.info' ; - $mysync->{user2} = 'test2' ; - $mysync->{password2} ='secret2' ; + $mysync->{host1} ||= 'ks6ipv6.lamiral.info' ; + $mysync->{user1} ||= 'test1' ; + $mysync->{password1} ||= 'secret1' ; + $mysync->{host2} ||= 'ks6ipv6.lamiral.info' ; + $mysync->{user2} ||= 'test2' ; + $mysync->{password2} ||= 'secret2' ; return ; } -sub tests_backslash_caret { +sub tests_backslash_caret +{ note( 'Entering tests_backslash_caret()' ) ; is( "lalala", backslash_caret( "lalala" ), 'backslash_caret: lalala => lalala' ) ; @@ -11643,57 +18954,65 @@ sub tests_backslash_caret { return ; } -sub backslash_caret { +sub backslash_caret +{ my $string = shift ; - + $string =~ s{\\ $ }{^}gxms ; return $string ; } -sub tests_split_around_equal { +sub tests_split_around_equal +{ note( 'Entering tests_split_around_equal()' ) ; + is( undef, split_around_equal( ), 'split_around_equal: no args => undef' ) ; is_deeply( { toto => 'titi' }, { split_around_equal( 'toto=titi' ) }, 'split_around_equal: toto=titi => toto => titi' ) ; is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B=C=D' ) }, 'split_around_equal: toto=titi => toto => titi' ) ; is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B', 'C=D' ) }, 'split_around_equal: A=B C=D => A => B, C=>D' ) ; - + note( 'Leaving tests_split_around_equal()' ) ; return ; } -sub split_around_equal { +sub split_around_equal +{ if ( ! @ARG ) { return ; } ; return map { split /=/mxs, $_ } @ARG ; - + } -sub tests_sig_install { + +sub tests_sig_install +{ note( 'Entering tests_sig_install()' ) ; - my $mysync ; + + my $mysync ; is( undef, sig_install( ), 'sig_install: no args => undef' ) ; is( undef, sig_install( $mysync ), 'sig_install: arg undef => undef' ) ; $mysync = { } ; is( undef, sig_install( $mysync ), 'sig_install: empty hash => undef' ) ; - + SKIP: { Readonly my $SKIP_15 => 15 ; if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_15 ) ; } # Default to ignore USR1 USR2 in case future install fails local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ; kill( 'USR1', $PROCESS_ID ) ; - + $mysync->{ debugsig } = 1 ; - # Assign USR1 to call sub tototo + # Assign USR1 to call sub tototo # Surely a better value than undef should be returned when doing real signal stuff - is( undef, sig_install( $mysync, \&tototo, 'USR1' ), 'sig_install: USR1 tototo' ) ; + is( undef, sig_install( $mysync, 'tototo', 'USR1' ), 'sig_install: USR1 tototo' ) ; is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 1' ) ; is( 1, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 1' ) ; - - # Assign USR2 to call sub tototo - is( undef, sig_install( $mysync, \&tototo, 'USR2' ), 'sig_install: USR2 tototo' ) ; + + #return ; + # Assign USR2 to call sub tototo + is( undef, sig_install( $mysync, 'tototo', 'USR2' ), 'sig_install: USR2 tototo' ) ; is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR2 myself 1' ) ; is( 2, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 2' ) ; @@ -11701,87 +19020,107 @@ sub tests_sig_install { is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ; is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 3' ) ; - + local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ; is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 3' ) ; is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call still nb 3' ) ; - - # Assign USR1 + USR2 to call sub tototo - is( undef, sig_install( $mysync, \&tototo, 'USR1', 'USR2' ), 'sig_install: USR1 USR2 tototo' ) ; + + # Assign USR1 + USR2 to call sub tototo + is( undef, sig_install( $mysync, 'tototo', 'USR1', 'USR2' ), 'sig_install: USR1 USR2 tototo' ) ; is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 4' ) ; is( 4, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 4' ) ; - + is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ; is( 5, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 5' ) ; } - - note( 'Leaving tests_sig_install()' ) ; + + note( 'Leaving tests_sig_install()' ) ; return ; } - -sub sig_install { +# +sub sig_install +{ my $mysync = shift ; if ( ! $mysync ) { return ; } - my $mysub = shift ; - if ( ! $mysub ) { return ; } - + my $mysubname = shift ; + if ( ! $mysubname ) { return ; } + + if ( ! @ARG ) { return ; } + my @signals = @ARG ; - - $mysync->{ debugsig } and myprint( "In sig_install with $mysync and $mysub\n" ) ; - + + my $mysub = \&$mysubname ; + #$mysync->{ debugsig } = 1 ; + $mysync->{ debugsig } and myprint( "In sig_install with sub $mysubname and signal @ARG\n" ) ; + my $subsignal = sub { my $signame = shift ; - $mysync->{ debugsig } and myprint( "In subsignal with $signame and $mysync\n" ) ; + $mysync->{ debugsig } and myprint( "In subsignal with $signame and $mysubname\n" ) ; &$mysub( $mysync, $signame ) ; } ; foreach my $signal ( @signals ) { - $mysync->{ debugsig } and myprint( "Installing signal $signal for $subsignal\n") ; - output( $mysync, "kill -$signal $PROCESS_ID # special behavior\n" ) ; + $mysync->{ debugsig } and myprint( "Installing signal $signal to call sub $mysubname\n") ; + output( $mysync, "kill -$signal $PROCESS_ID # special behavior: call to sub $mysubname\n" ) ; + ## no critic (RequireLocalizedPunctuationVars) $SIG{ $signal } = $subsignal ; } return ; } -sub tototo { +sub tototo +{ my $mysync = shift ; myprint("In tototo with @ARG\n" ) ; $mysync->{ tototo_calls } += 1 ; return ; } -sub tests_toggle_sleep { +sub mygetppid +{ + if ( 'MSWin32' eq $OSNAME ) { + return( 'unknown under MSWin32 (too complicated)' ) ; + } else { + # Unix + return( getppid( ) ) ; + } +} + + +sub tests_toggle_sleep +{ note( 'Entering tests_toggle_sleep()' ) ; + is( undef, toggle_sleep( ), 'toggle_sleep: no args => undef' ) ; my $mysync ; is( undef, toggle_sleep( $mysync ), 'toggle_sleep: undef => undef' ) ; $mysync = { } ; is( undef, toggle_sleep( $mysync ), 'toggle_sleep: no maxsleep => undef' ) ; - + $mysync->{maxsleep} = 3 ; is( 0, toggle_sleep( $mysync ), 'toggle_sleep: 3 => 0' ) ; - + is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ; is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ; is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ; is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ; - + SKIP: { Readonly my $SKIP_9 => 9 ; if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_9 ) ; } # Default to ignore USR1 USR2 in case future install fails local $SIG{ USR1 } = sub { } ; kill( 'USR1', $PROCESS_ID ) ; - + $mysync->{ debugsig } = 1 ; - # Assign USR1 to call sub toggle_sleep + # Assign USR1 to call sub toggle_sleep is( undef, sig_install( $mysync, \&toggle_sleep, 'USR1' ), 'toggle_sleep: install USR1 toggle_sleep' ) ; - + $mysync->{maxsleep} = 4 ; is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ; is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ; @@ -11795,28 +19134,30 @@ sub tests_toggle_sleep { is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ; is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ; } - + note( 'Leaving tests_toggle_sleep()' ) ; return ; } -sub toggle_sleep { +sub toggle_sleep +{ my $mysync = shift ; - + myprint("In toggle_sleep with @ARG\n" ) ; if ( !defined( $mysync ) ) { return ; } if ( !defined( $mysync->{maxsleep} ) ) { return ; } - + $mysync->{ maxsleep } = max( 0, $MAX_SLEEP - $mysync->{maxsleep} ) ; myprint("Resetting maxsleep to ", $mysync->{maxsleep}, "s\n" ) ; return $mysync->{maxsleep} ; } -sub mypod2usage { +sub mypod2usage +{ my $fh_pod2usage = shift ; - + pod2usage( -exitval => 'NOEXIT', -noperldoc => 1, @@ -11830,21 +19171,22 @@ sub mypod2usage { return ; } -sub usage { +sub usage +{ my $mysync = shift ; - + if ( ! defined $mysync ) { return ; } - + my $usage = q{} ; my $usage_from_pod ; my $usage_footer = usage_footer( $mysync ) ; # pod2usage writes on a filehandle only and I want a variable - open my $fh_pod2usage, ">", \$usage_from_pod + open my $fh_pod2usage, ">", \$usage_from_pod or do { warn $OS_ERROR ; return ; } ; mypod2usage( $fh_pod2usage ) ; close $fh_pod2usage ; - + if ( 'MSWin32' eq $OSNAME ) { $usage_from_pod = backslash_caret( $usage_from_pod ) ; } @@ -11853,7 +19195,10 @@ sub usage { return( $usage ) ; } -sub tests_usage { +sub tests_usage +{ + note( 'Entering tests_usage()' ) ; + my $usage ; like( $usage = usage( $sync ), qr/Name:/, 'usage: contains Name:' ) ; myprint( $usage ) ; @@ -11861,72 +19206,219 @@ sub tests_usage { like( $usage, qr/Usage:/, 'usage: contains Usage:' ) ; like( $usage, qr/imapsync/, 'usage: contains imapsync' ) ; - is( undef, usage( ), 'usage: no args => undef' ) ; + is( undef, usage( ), 'usage: no args => undef' ) ; + + note( 'Leaving tests_usage()' ) ; + return ; +} + + +sub usage_footer +{ + my $mysync = shift ; + + my $footer = q{} ; + + my $localhost_info = localhost_info( $mysync ) ; + my $rcs = $mysync->{rcs} ; + my $homepage = homepage( ) ; + + my $imapsync_release = $STR_use_releasecheck ; + + if ( $mysync->{releasecheck} ) { + $imapsync_release = check_last_release( ) ; + } + + $footer = qq{$localhost_info +$rcs +$imapsync_release +$homepage +} ; + return( $footer ) ; +} + + + +sub usage_complete +{ + # Unused, I guess this function could be deleted + my $usage = <<'EOF' ; +--skipheader reg : Don't take into account header keyword + matching reg ex: --skipheader 'X.*' + +--skipsize : Don't take message size into account to compare + messages on both sides. On by default. + Use --no-skipsize for using size comparaison. +--allowsizemismatch : allow RFC822.SIZE != fetched msg size + consider also --skipsize to avoid duplicate messages + when running syncs more than one time per mailbox + +--reconnectretry1 int : reconnect to host1 if connection is lost up to + int times per imap command (default is 3) +--reconnectretry2 int : same as --reconnectretry1 but for host2 +--split1 int : split the requests in several parts on host1. + int is the number of messages handled per request. + default is like --split1 100. +--split2 int : same thing on host2. +--nofixInboxINBOX : Don't fix Inbox INBOX mapping. +EOF + return( $usage ) ; +} + + + + +sub setvalfromcgikey +{ + my ( $mysync, $mycgi, $key, $val ) = @ARG ; + + my $badthings = 0 ; + + + my ( $name, $type, $struct ) ; + if ( $key !~ m/^([\w\d\|]+)([=:][isf])?([\+!\@\%])?$/mxs ) + { + $badthings++ ; + next ; # Unknown item + } + else + { + $name = [ split '|', $1, 1 ]->[0] ; # option name ab|cd|ef => keep only ab + $type = $2 ; # = or : followed by i or s or f + $struct = $3 ; # + or ! or @ or % + } + + if ( ( $struct || q{} ) eq '+' ) + { + ${$val} = $mycgi->param( $name ) ; # "Incremental" integer + } + elsif ( $type ) + { + my @values = $mycgi->multi_param( $name ) ; + + #myprint( "type[$type]values[@values]\$struct[", $struct || q{}, "]val[$val]ref(val)[", ref($val), "]\n" ) ; + if ( ( $struct || q{} ) eq '%' or ref( $val ) eq 'HASH' ) + { + setvalfromhash( $val, $type, @values ) ; + } + else + { + setvalfromlist( $mysync, $val, $name, $type, $struct, @values ) ; + } + } + else + { + setvalfromcheckbox( $mysync, $mycgi, $key, $name, $val ) ; + } + + return $badthings ; +} + +sub setvalfromlist +{ + my ( $mysync, $val, $name, $type, $struct, @values ) = @ARG ; + if ( $type =~ m/i$/mxs ) + { + @values = map { q{} ne $_ ? int $_ : undef } @values ; + } + elsif ( $type =~ m/f$/mxs ) + { + @values = map { 0 + $_ } @values ; + } + + if ( ( $struct || q{} ) eq '@' ) + { + @{ ${$val} } = @values ; + my @option = map { +( "--$name", "$_" ) } @values ; + push @{ $mysync->{ cmdcgi } }, @option ; + } + elsif ( ref( $val ) eq 'ARRAY' ) + { + @{$val} = @values ; + } + elsif ( my $value = $values[0] ) + { + ${$val} = $value ; + push @{ $mysync->{ cmdcgi } }, "--$name", $value ; + } + else + { + } + return ; } +sub setvalfromhash +{ + my ( $val, $type, @values ) = @ARG ; + my %values = map { split /=/mxs, $_ } @values ; -sub usage_footer { - my $mysync = shift ; - - my $footer = q{} ; - - my $localhost_info = localhost_info( $mysync ) ; - my $rcs = $mysync->{rcs} ; - my $homepage = homepage( ) ; - - my $imapsync_release = $STR_use_releasecheck ; - - if ( $mysync->{releasecheck} ) { - $imapsync_release = check_last_release( ) ; + if ( $type =~ m/i$/mxs ) + { + foreach my $k ( keys %values ) + { + $values{$k} = int $values{$k} ; + } + } + elsif ( $type =~ m/f$/mxs ) + { + foreach my $k ( keys %values ) { + $values{$k} = 0 + $values{$k}; + } } - - $footer = qq{$localhost_info -$rcs -$imapsync_release -$homepage -} ; - return( $footer ) ; -} + if ( 'REF' eq ref $val ) + { + %{ ${$val} } = %values ; + } + else + { + %{$val} = %values ; + } + return ; +} -sub usage_complete { - # Unused, I guess this function could be deleted - my $usage = <<'EOF' ; ---skipheader reg : Don't take into account header keyword - matching reg ex: --skipheader 'X.*' ---skipsize : Don't take message size into account to compare - messages on both sides. On by default. - Use --no-skipsize for using size comparaison. ---allowsizemismatch : allow RFC822.SIZE != fetched msg size - consider also --skipsize to avoid duplicate messages - when running syncs more than one time per mailbox +sub setvalfromcheckbox +{ + my ( $mysync, $mycgi, $key, $name, $val ) = @ARG ; ---reconnectretry1 int : reconnect to host1 if connection is lost up to - int times per imap command (default is 3) ---reconnectretry2 int : same as --reconnectretry1 but for host2 ---split1 int : split the requests in several parts on host1. - int is the number of messages handled per request. - default is like --split1 500. ---split2 int : same thing on host2. ---nofixInboxINBOX : Don't fix Inbox INBOX mapping. -EOF - return( $usage ) ; + # Checkbox + # --noname is set by name=0 or name= + my $value = $mycgi->param( $name ) ; + if ( defined $value ) + { + ${$val} = $value ; + if ( $value ) + { + push @{ $mysync->{ cmdcgi } }, "--$name" ; + } + else + { + push @{ $mysync->{ cmdcgi } }, "--no$name" ; + } + } + else + { + ${$val} = undef ; + } + return ; } -sub myGetOptions { +sub myGetOptions +{ # Started as a copy of Luke Ross Getopt::Long::CGI # https://metacpan.org/release/Getopt-Long-CGI # So this sub function is under the same license as Getopt-Long-CGI Luke Ross wants it, # which was Perl 5.6 or later licenses at the date of the copy. + # It also applies for the sub functions called from this one. my $mysync = shift @ARG ; my $arguments_ref = shift @ARG ; my %options = @ARG ; - + my $mycgi = $mysync->{cgi} ; if ( not under_cgi_context() ) { @@ -11936,72 +19428,16 @@ sub myGetOptions { } # We must be in CGI context now - if ( !defined( $mycgi ) ) { return ; } + if ( ! defined( $mycgi ) ) { return ; } my $badthings = 0 ; foreach my $key ( sort keys %options ) { my $val = $options{$key} ; - if ( $key !~ m/^([\w\d\|]+)([=:][isf])?([\+!\@\%])?$/mxs ) { - $badthings++ ; - next ; # Unknown item - } - - my $name = [ split '|', $1, 1 ]->[0] ; + $badthings += setvalfromcgikey( $mysync, $mycgi, $key, $val ) ; - if ( ( $3 || q{} ) eq '+' ) { - ${$val} = $mycgi->param( $name ) ; # "Incremental" integer - } - elsif ( $2 ) { - my @values = $mycgi->multi_param( $name ) ; - my $type = $2 ; - - #myprint( "type[$type]values[@values]\$3[", $3 || q{}, "]val[$val]ref(val)[", ref($val), "]\n" ) ; - if ( ( $3 || q{} ) eq '%' or ref( $val ) eq 'HASH' ) { - my %values = map { split /=/mxs, $_ } @values ; - - if ( $type =~ m/i$/mxs ) { - foreach my $k ( keys %values ) { - $values{$k} = int $values{$k} ; - } - } - elsif ( $type =~ m/f$/mxs ) { - foreach my $k ( keys %values ) { - $values{$k} = 0 + $values{$k}; - } - } - if ( 'REF' eq ref $val ) { - %{ ${$val} } = %values ; - } - else { - %{$val} = %values ; - } - } - else { - if ( $type =~ m/i$/mxs ) { - @values = map { q{} ne $_ ? int $_ : undef } @values ; - } - elsif ( $type =~ m/f$/mxs ) { - @values = map { 0 + $_ } @values ; - } - if ( ( $3 || q{} ) eq '@' ) { - @{ ${$val} } = @values ; - } - elsif ( ref( $val ) eq 'ARRAY' ) { - @{$val} = @values ; - } - else { - ${$val} = $values[0] ; - } - } - } - else { - # Checkbox - # Considers only --name - # Should consider also --no-name and --noname - ${$val} = $mycgi->param( $name ) ? 1 : undef ; - } } + if ( $badthings ) { return ; # undef or () } @@ -12011,10 +19447,144 @@ sub myGetOptions { } -my @blabla ; # just used to check get_options_cgi() with an array -sub tests_get_options_cgi_context { - note( 'Entering tests_get_options_cgi()' ) ; +sub tests_get_options_extra +{ + note( 'Entering tests_get_options_extra()' ) ; + + is( undef, get_options_extra( ), 'get_options_extra: no args => undef' ) ; + + my $mysync = { } ; + is( undef, get_options_extra( $mysync ), 'get_options_extra: undef => undef' ) ; + + my $cwd_save = getcwd( ) ; + + ok( (-d 'W/tmp/tests/options_extra/' or mkpath( 'W/tmp/tests/options_extra/' )), 'get_options_extra: mkpath W/tmp/tests/options_extra/' ) ; + + chdir 'W/tmp/tests/options_extra/' ; + + is( '--debugimap1', string_to_file( '--debugimap1', 'options_extra.txt' ), 'get_options_extra: string_to_file filling options_extra.txt with --debugimap1' ) ; + + is( '--debugimap1', file_to_string( 'options_extra.txt' ), 'get_options_extra: reading options_extra.txt is --debugimap1' ) ; + + is( '', get_options_extra( $mysync ), 'get_options_extra: --debugimap1 in options_extra.txt => nothing left, empty string return' ) ; + + is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_extra: --debugimap1 in options_extra.txt => ok, acc1->debugimap = 1' ) ; + + is( '--tls1 proutcaca', string_to_file( '--tls1 proutcaca', 'options_extra.txt' ), 'get_options_extra: string_to_file filling options_extra.txt with --tls1 proutcaca' ) ; + + is( 'proutcaca', get_options_extra( $mysync ), 'get_options_extra: --tls1 proutcaca in options_extra.txt => proutcaca left, proutcaca return' ) ; + + chdir $cwd_save ; + + note( 'Leaving tests_get_options_extra()' ) ; + return ; +} + +sub get_options_extra +{ + my $mysync = shift @ARG ; + + if ( ! defined $mysync ) { return ; } + + if ( -f -r 'options_extra.txt' ) + { + my $cwd = getcwd( ) ; + my $string = firstline( 'options_extra.txt' ) ; + my $rest = get_options_from_string( $mysync, $string ) ; + output( $mysync, "Reading extra options from file options_extra.txt (cwd: $cwd) : $string\n" ) ; + return $rest ; + } + else + { + return ; + } +} + + +sub tests_get_options_from_string +{ + note( 'Entering tests_get_options_from_string()' ) ; + + is( undef, get_options_from_string( ), 'get_options_from_string: no args => undef' ) ; + my $mysync = { } ; + is( undef, get_options_from_string( $mysync ), 'get_options_from_string: undef => undef' ) ; + + is( '', get_options_from_string( $mysync, '--debugimap1' ), + 'get_options_from_string: --debugimap1 => ok, nothing left, empty string return' ) ; + is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: --debugimap1 => ok, acc1->debugimap = 1' ) ; + + $mysync = { } ; # reset + is( 'caca', get_options_from_string( $mysync, '--debugimap1 caca' ), + 'get_options_from_string: --debugimap1 caca => ok, caca left, caca return' ) ; + is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: --debugimap1 => ok, acc1->debugimap = 1' ) ; + + is( 'popo roro', get_options_from_string( $mysync, '--debugimap2 popo roro' ), + 'get_options_from_string: --debugimap1 popo roro => ok, popo roro left, popo roro return' ) ; + is( 1, $mysync->{ acc2 }->{ debugimap }, 'get_options_from_string: --debugimap2 popo roro => ok, acc2->debugimap = 1' ) ; + is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: acc1->debugimap = 1 still' ) ; + + is( '', get_options_from_string( $mysync, '--nodebugimap1 --debugflags --errorsmax 2' ), + 'get_options_from_string: --nodebugimap1 --debugflags --errorsmax 2 => ok, empty string return' ) ; + + is( 0, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: acc1->debugimap = 0 now' ) ; + is( 1, $debugflags, 'get_options_from_string: debugflags = 1 now' ) ; + is( 2, $mysync->{ errorsmax }, 'get_options_from_string: mysync->errorsmax = 2 now' ) ; + + is( '', get_options_from_string( $mysync, '--folder "IN BOX" --folder JOE' ), + 'get_options_from_string: --folder "IN BOX" --folder JOE => ok, empty string return' ) ; + + is_deeply( [ 'IN BOX', 'JOE' ], [@{$mysync->{ folder }}], 'get_options_from_string: "IN BOX" "JOE"' ) ; + + is( '', get_options_from_string( $mysync, '--debugflags --koko' ), + 'get_options_from_string: --debugflags --koko => ok, empty string return, with "Unknown option: koko" on STDERR' ) ; + + note( 'Leaving tests_get_options_from_string()' ) ; + return ; +} + +sub get_options_from_string +{ + my $mysync = shift @ARG ; + my $mystring = shift @ARG ; + + if ( ! defined $mystring ) { return ; } + + my ( $ret, $args ) = Getopt::Long::GetOptionsFromString( $mystring, + 'debugimap!' => \$mysync->{ debugimap }, + 'debugimap1!' => \$mysync->{ acc1 }->{ debugimap }, + 'debugimap2!' => \$mysync->{ acc2 }->{ debugimap }, + 'debugflags!' => \$debugflags, + 'debugsleep=f' => \$mysync->{ debugsleep }, + 'errorsmax=i' => \$mysync->{ errorsmax }, + 'folder=s@' => \$mysync->{ folder }, + 'timeout=f' => \$mysync->{ timeout }, + 'timeout1=f' => \$mysync->{ acc1 }->{ timeout }, + 'timeout2=f' => \$mysync->{ acc2 }->{ timeout }, + 'keepalive1!' => \$mysync->{ acc1 }->{ keepalive }, + 'keepalive2!' => \$mysync->{ acc2 }->{ keepalive }, + 'reconnectretry1=i' => \$mysync->{ acc1 }->{ reconnectretry }, + 'reconnectretry2=i' => \$mysync->{ acc2 }->{ reconnectretry }, + 'ssl1!' => \$mysync->{ ssl1 }, + 'ssl2!' => \$mysync->{ ssl2 }, + 'tls1!' => \$mysync->{ tls1 }, + 'tls2!' => \$mysync->{ tls2 }, + 'compress1!' => \$mysync->{ acc1 }->{ compress }, + 'compress2!' => \$mysync->{ acc2 }->{ compress }, + ) ; + my $left = join( ' ', @$args ) ; + return $left ; +} + + + + + + +sub tests_get_options_cgi_context +{ + note( 'Entering tests_get_options_cgi_context()' ) ; + # Temporary, have to think harder about testing CGI context in command line --tests # API: @@ -12032,15 +19602,21 @@ sub tests_get_options_cgi_context { my $mysync ; is( undef, get_options( $mysync ), 'get_options cgi context: no CGI module => undef' ) ; - require CGI ; - CGI->import( qw( -no_debug ) ) ; + # skip all next tests if the CGI module is not available + + SKIP: { + if ( ! eval { require CGI ; } ) { + skip( "CGI Perl module is not installed", 19 ) ; + } + + CGI->import( qw( -no_debug -utf8 ) ) ; is( undef, get_options( $mysync ), 'get_options cgi context: no CGI param => undef' ) ; # Testing boolean $mysync->{cgi} = CGI->new( 'version=on&debugenv=on' ) ; local $ENV{'QUERY_STRING'} = 'version=on&debugenv=on' ; is( 22, get_options( $mysync ), 'get_options cgi context: QUERY_STRING => 22' ) ; - is( 1, $mysync->{ version }, 'get_options cgi context: --version => 1' ) ; + is( 'on', $mysync->{ version }, 'get_options cgi context: --version => on' ) ; # debugenv is not allowed in cgi context is( undef, $mysync->{debugenv}, 'get_options cgi context: $mysync->{debugenv} => undef' ) ; @@ -12051,27 +19627,19 @@ sub tests_get_options_cgi_context { is( 36, get_options( $mysync, ), 'get_options cgi context: QUERY_STRING => 36' ) ; is( 'test1', $mysync->{user1}, 'get_options cgi context: $mysync->{user1} => test1' ) ; #local $ENV{'QUERY_STRING'} = undef ; - - # Testing @ - $mysync->{cgi} = CGI->new( 'blabla=fd1' ) ; - get_options( $mysync ) ; - is_deeply( [ 'fd1' ], [ @blabla ], 'get_options cgi context: @blabla => fd1' ) ; - $mysync->{cgi} = CGI->new( 'blabla=fd1&blabla=fd2' ) ; - get_options( $mysync ) ; - is_deeply( [ 'fd1', 'fd2' ], [ @blabla ], 'get_options cgi context: @blabla => fd1, fd2' ) ; # Testing s@ as ref $mysync->{cgi} = CGI->new( 'folder=fd1' ) ; get_options( $mysync ) ; - is_deeply( [ 'fd1' ], $mysync->{folder}, 'get_options cgi context: $mysync->{folder} => fd1' ) ; + is_deeply( [ 'fd1' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1' ) ; $mysync->{cgi} = CGI->new( 'folder=fd1&folder=fd2' ) ; get_options( $mysync ) ; - is_deeply( [ 'fd1', 'fd2' ], $mysync->{folder}, 'get_options cgi context: $mysync->{folder} => fd1, fd2' ) ; + is_deeply( [ 'fd1', 'fd2' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1, fd2' ) ; # Testing % $mysync->{cgi} = CGI->new( 'f1f2h=s1=d1&f1f2h=s2=d2&f1f2h=s3=d3' ) ; get_options( $mysync ) ; - + is_deeply( { 's1' => 'd1', 's2' => 'd2', 's3' => 'd3' }, $mysync->{f1f2h}, 'get_options cgi context: f1f2h => s1=d1 s2=d2 s3=d3' ) ; @@ -12083,29 +19651,47 @@ sub tests_get_options_cgi_context { $mysync->{cgi} = CGI->new( 'host1=example.com' ) ; get_options( $mysync ) ; is( 'example.com', $mysync->{host1}, 'get_options cgi context: --host1=example.com => $mysync->{host1} => example.com' ) ; - + #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; $mysync->{cgi} = CGI->new( 'simulong=' ) ; get_options( $mysync ) ; is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong= => $mysync->{simulong} => undef' ) ; - + $mysync->{cgi} = CGI->new( 'simulong' ) ; get_options( $mysync ) ; is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong => $mysync->{simulong} => undef' ) ; - + $mysync->{cgi} = CGI->new( 'simulong=4' ) ; get_options( $mysync ) ; is( 4, $mysync->{simulong}, 'get_options cgi context: --simulong=4 => $mysync->{simulong} => 4' ) ; - is( undef, $mysync->{folder}, 'get_options cgi context: --simulong=4 => $mysync->{folder} => undef' ) ; + is( undef, $mysync->{ folder }, 'get_options cgi context: --simulong=4 => $mysync->{ folder } => undef' ) ; + #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; + + $mysync ={} ; + $mysync->{cgi} = CGI->new( 'testslive=on' ) ; + get_options( $mysync ) ; + is( 'on', $mysync->{ testslive }, 'get_options cgi context: --testslive=on => testslive => on' ) ; #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; - + + $mysync ={} ; + $mysync->{cgi} = CGI->new( 'log=0' ) ; + get_options( $mysync ) ; + is( 0, $mysync->{ log }, 'get_options cgi context: --log=0 => log => 0' ) ; + #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; + + + # What is this fucked up indentation? + } + + note( 'Leaving tests_get_options_cgi_context()' ) ; return ; } -sub get_options_cgi { +sub get_options_cgi +{ # In CGI context arguments are not in @ARGV but in QUERY_STRING variable (with GET). my $mysync = shift @ARG ; $mysync->{cgi} || return ; @@ -12116,47 +19702,66 @@ sub get_options_cgi { my $opt_ret = myGetOptions( $mysync, \@arguments, - 'abort' => \$mysync->{abort}, - 'host1=s' => \$mysync->{host1}, - 'host2=s' => \$mysync->{host2}, - 'user1=s' => \$mysync->{user1}, - 'user2=s' => \$mysync->{user2}, - 'password1=s' => \$mysync->{password1}, - 'password2=s' => \$mysync->{password2}, - 'dry!' => \$mysync->{dry}, - 'version' => \$mysync->{version}, - 'ssl1!' => \$mysync->{ssl1}, - 'ssl2!' => \$mysync->{ssl2}, - 'tls1!' => \$mysync->{tls1}, - 'tls2!' => \$mysync->{tls2}, - 'justlogin!' => \$mysync->{justlogin}, - 'justconnect!' => \$mysync->{justconnect}, - 'addheader!' => \$mysync->{addheader}, - 'automap!' => \$mysync->{automap}, - 'justautomap!' => \$mysync->{justautomap}, - 'gmail1' => \$mysync->{gmail1}, - 'gmail2' => \$mysync->{gmail2}, - 'office1' => \$mysync->{office1}, - 'office2' => \$mysync->{office2}, - 'exchange1' => \$mysync->{exchange1}, - 'exchange2' => \$mysync->{exchange2}, - 'domino1' => \$mysync->{domino1}, - 'domino2' => \$mysync->{domino2}, - 'f1f2=s@' => \$mysync->{f1f2}, - 'f1f2h=s%' => \$mysync->{f1f2h}, - 'folder=s@' => \$mysync->{folder}, - 'blabla=s' => \@blabla, - 'testslive!' => \$mysync->{testslive}, - 'testslive6!' => \$mysync->{testslive6}, - 'releasecheck!' => \$mysync->{releasecheck}, - 'simulong=i' => \$mysync->{simulong}, - -# blabla and f1f2h=s% could be removed but -# tests_get_options_cgi() should be split before + 'abort' => \$mysync->{ abort }, + 'abortbyfile' => \$mysync->{ abortbyfile }, + 'host1=s' => \$mysync->{ host1 }, + 'host2=s' => \$mysync->{ host2 }, + 'user1=s' => \$mysync->{ user1 }, + 'user2=s' => \$mysync->{ user2 }, + 'password1=s' => \$mysync->{ password1 }, + 'password2=s' => \$mysync->{ password2 }, + 'dry!' => \$mysync->{ dry }, + 'dry1!' => \$mysync->{ dry1 }, + 'version' => \$mysync->{ version }, + 'ssl1!' => \$mysync->{ ssl1 }, + 'ssl2!' => \$mysync->{ ssl2 }, + 'tls1!' => \$mysync->{ tls1 }, + 'tls2!' => \$mysync->{ tls2 }, + 'compress1!' => \$mysync->{ acc1 }->{ compress }, + 'compress2!' => \$mysync->{ acc2 }->{ compress }, + 'justbanner!' => \$mysync->{ justbanner }, + 'justlogin!' => \$mysync->{ justlogin }, + 'justconnect!' => \$mysync->{ justconnect }, + 'addheader!' => \$mysync->{ addheader }, + 'automap!' => \$mysync->{ automap }, + 'justautomap!' => \$mysync->{ justautomap }, + 'gmail1' => \$mysync->{ gmail1 }, + 'gmail2' => \$mysync->{ gmail2 }, + 'office1' => \$mysync->{ office1 }, + 'office2' => \$mysync->{ office2 }, + 'exchange1' => \$mysync->{ exchange1 }, + 'exchange2' => \$mysync->{ exchange2 }, + 'domino1' => \$mysync->{ domino1 }, + 'domino2' => \$mysync->{ domino2 }, + 'f1f2=s@' => \$mysync->{ f1f2 }, + 'f1f2h=s%' => \$mysync->{ f1f2h }, + 'folder=s@' => \$mysync->{ folder }, + 'testslive!' => \$mysync->{ testslive }, + 'testslive6!' => \$mysync->{ testslive6 }, + 'releasecheck!' => \$mysync->{ releasecheck }, + 'simulong=f' => \$mysync->{ simulong }, + 'debugsleep=f' => \$mysync->{ debugsleep }, + 'subfolder1=s' => \$mysync->{ subfolder1 }, + 'subfolder2=s' => \$mysync->{ subfolder2 }, + 'justfolders!' => \$mysync->{ justfolders }, + 'justfoldersizes!' => \$mysync->{ justfoldersizes }, + 'delete1!' => \$mysync->{ delete1 }, + 'delete2!' => \$mysync->{ delete2 }, + 'delete2duplicates!' => \$mysync->{ delete2duplicates }, + 'tail!' => \$mysync->{ tail }, + 'tmphash=s' => \$mysync->{ tmphash }, + 'exitwhenover=i' => \$mysync->{ exitwhenover }, + 'syncduplicates!' => \$mysync->{ syncduplicates }, + 'log!' => \$mysync->{ log }, + 'loglogfile!' => \$mysync->{ loglogfile }, + + +# f1f2h=s% could be removed but +# tests_get_options_cgi() should be split before # with a sub tests_myGetOptions() ) ; - $debug and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ; + $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ; if ( ! $opt_ret ) { return ; @@ -12164,157 +19769,185 @@ sub get_options_cgi { return $numopt ; } -sub get_options_cmd { - my $mysync = shift @ARG ; - my @arguments = @ARG ; - my $mycgi = $mysync->{cgi} ; - # final 0 is used to print usage when no option is given on command line +sub get_options_cmd +{ + my $mysync = shift @ARG ; + my @arguments = @ARG ; + my $mycgi = $mysync->{cgi} ; + # final 0 is used to print usage when no option is given on command line my $numopt = scalar @arguments || 0 ; my $argv = join "\x00", @arguments ; if ( $argv =~ m/-delete\x002/x ) { - output( $mysync, "May be you mean --delete2 instead of --delete 2\n" ) ; + output( $mysync, "May be you mean --delete2 instead of --delete 2\n" ) ; return ; } $mysync->{f1f2h} = {} ; my $opt_ret = myGetOptions( - $mysync, - \@arguments, - 'debug!' => \$debug, + $mysync, + \@arguments, + 'debug!' => \$mysync->{ debug }, 'debuglist!' => \$debuglist, - 'debugcontent!' => \$debugcontent, - 'debugsleep=f' => \$mysync->{debugsleep}, + 'debugcontent!' => \$mysync->{ debugcontent }, + 'debugsleep=f' => \$mysync->{ debugsleep }, 'debugflags!' => \$debugflags, - 'debugimap!' => \$debugimap, - 'debugimap1!' => \$debugimap1, - 'debugimap2!' => \$debugimap2, + 'debugimap!' => \$mysync->{ debugimap }, + 'debugimap1!' => \$mysync->{ acc1 }->{ debugimap }, + 'debugimap2!' => \$mysync->{ acc2 }->{ debugimap }, 'debugdev!' => \$debugdev, 'debugmemory!' => \$mysync->{debugmemory}, 'debugfolders!' => \$mysync->{debugfolders}, 'debugssl=i' => \$mysync->{debugssl}, 'debugcgi!' => \$debugcgi, - 'debugenv' => \$mysync->{debugenv}, - 'debugsig' => \$mysync->{debugsig}, - 'simulong=i' => \$mysync->{simulong}, - 'abort' => \$mysync->{abort}, - 'host1=s' => \$mysync->{host1}, - 'host2=s' => \$mysync->{host2}, - 'port1=i' => \$mysync->{port1}, - 'port2=i' => \$mysync->{port2}, - 'inet4|ipv4' => \$mysync->{inet4}, - 'inet6|ipv6' => \$mysync->{inet6}, - 'user1=s' => \$mysync->{user1}, - 'user2=s' => \$mysync->{user2}, - 'gmail1' => \$mysync->{gmail1}, - 'gmail2' => \$mysync->{gmail2}, - 'office1' => \$mysync->{office1}, - 'office2' => \$mysync->{office2}, - 'exchange1' => \$mysync->{exchange1}, - 'exchange2' => \$mysync->{exchange2}, - 'domino1' => \$mysync->{domino1}, - 'domino2' => \$mysync->{domino2}, - 'domain1=s' => \$domain1, - 'domain2=s' => \$domain2, + 'debugenv!' => \$mysync->{debugenv}, + 'debugsig!' => \$mysync->{debugsig}, + 'debuglabels!' => \$mysync->{debuglabels}, + + 'simulong=f' => \$mysync->{simulong}, + 'abort' => \$mysync->{abort}, + 'abortbyfile' => \$mysync->{abortbyfile}, + + 'host1=s' => \$mysync->{ host1 }, + 'host2=s' => \$mysync->{ host2 }, + 'port1=i' => \$mysync->{ port1 }, + 'port2=i' => \$mysync->{ port2 }, + 'inet4|ipv4' => \$mysync->{ inet4 }, + 'inet6|ipv6' => \$mysync->{ inet6 }, + 'user1=s' => \$mysync->{ user1 }, + 'user2=s' => \$mysync->{ user2 }, + 'gmail1' => \$mysync->{gmail1}, + 'gmail2' => \$mysync->{gmail2}, + 'office1' => \$mysync->{office1}, + 'office2' => \$mysync->{office2}, + 'exchange1' => \$mysync->{exchange1}, + 'exchange2' => \$mysync->{exchange2}, + 'domino1' => \$mysync->{domino1}, + 'domino2' => \$mysync->{domino2}, + 'domain1=s' => \$mysync->{ acc1 }->{ domain }, + 'domain2=s' => \$mysync->{ acc2 }->{ domain }, 'password1=s' => \$mysync->{password1}, 'password2=s' => \$mysync->{password2}, - 'passfile1=s' => \$passfile1, - 'passfile2=s' => \$passfile2, + 'passfile1=s' => \$mysync->{ passfile1 }, + 'passfile2=s' => \$mysync->{ passfile2 }, 'authmd5!' => \$authmd5, 'authmd51!' => \$authmd51, 'authmd52!' => \$authmd52, - 'sep1=s' => \$sep1, - 'sep2=s' => \$sep2, - 'folder=s@' => \$mysync->{folder}, + + 'trylogin!' => \$mysync->{ trylogin }, + + 'oauthdirect1=s' => \$mysync->{ acc1 }->{ oauthdirect }, + 'oauthdirect2=s' => \$mysync->{ acc2 }->{ oauthdirect }, + + 'oauthaccesstoken1=s' => \$mysync->{ acc1 }->{ oauthaccesstoken }, + 'oauthaccesstoken2=s' => \$mysync->{ acc2 }->{ oauthaccesstoken }, + + 'sep1=s' => \$mysync->{ sep1 }, + 'sep2=s' => \$mysync->{ sep2 }, + 'sanitize!' => \$mysync->{ sanitize }, + 'folder=s@' => \$mysync->{ folder }, 'folderrec=s' => \@folderrec, 'include=s' => \@include, 'exclude=s' => \@exclude, + 'noexclude' => \$mysync->{noexclude}, 'folderfirst=s' => \@folderfirst, 'folderlast=s' => \@folderlast, 'prefix1=s' => \$prefix1, 'prefix2=s' => \$prefix2, - 'subfolder2=s' => \$subfolder2, - 'fixslash2!' => \$fixslash2, + 'subfolder1=s' => \$mysync->{ subfolder1 }, + 'subfolder2=s' => \$mysync->{ subfolder2 }, + 'fixslash2!' => \$mysync->{ fixslash2 }, 'fixInboxINBOX!' => \$fixInboxINBOX, - 'regextrans2=s' => \@regextrans2, + 'regextrans2=s@' => \$mysync->{ regextrans2 }, 'mixfolders!' => \$mixfolders, - 'skipemptyfolders!' => \$skipemptyfolders, + 'skipemptyfolders!' => \$mysync->{ skipemptyfolders }, 'regexmess=s' => \@regexmess, + 'noregexmess' => \$mysync->{noregexmess}, 'skipmess=s' => \@skipmess, 'pipemess=s' => \@pipemess, 'pipemesscheck!' => \$pipemesscheck, 'disarmreadreceipts!' => \$disarmreadreceipts, - 'regexflag=s' => \@regexflag, - 'filterflags!' => \$filterflags, - 'flagscase!' => \$flagscase, + 'regexflag=s@' => \$mysync->{ regexflag }, + 'noregexflag' => \$mysync->{ noregexflag }, + 'filterflags!' => \$mysync->{ filterflags }, + 'filterbuggyflags!' => \$mysync->{ filterbuggyflags }, + 'flagscase!' => \$mysync->{ flagscase }, 'syncflagsaftercopy!' => \$syncflagsaftercopy, - 'resyncflags!' => \$mysync->{resyncflags}, - 'delete|delete1!' => \$delete1, - 'delete2!' => \$delete2, - 'delete2duplicates!' => \$delete2duplicates, + 'resyncflags!' => \$mysync->{ resyncflags }, + 'synclabels!' => \$mysync->{ synclabels }, + 'resynclabels!' => \$mysync->{ resynclabels }, + 'delete|delete1!' => \$mysync->{ delete1 }, + 'delete2!' => \$mysync->{ delete2 }, + 'delete2duplicates!' => \$mysync->{ delete2duplicates }, 'delete2folders!' => \$delete2folders, 'delete2foldersonly=s' => \$delete2foldersonly, 'delete2foldersbutnot=s' => \$delete2foldersbutnot, 'syncinternaldates!' => \$syncinternaldates, 'idatefromheader!' => \$idatefromheader, - 'syncacls!' => \$syncacls, - 'maxsize=i' => \$maxsize, - 'minsize=i' => \$minsize, - 'maxage=i' => \$maxage, - 'minage=i' => \$minage, - 'search=s' => \$search, - 'search1=s' => \$search1, - 'search2=s' => \$search2, - 'foldersizes!' => \$foldersizes, - 'foldersizesatend!' => \$foldersizesatend, + 'syncacls!' => \$mysync->{ syncacls }, + 'maxsize=i' => \$mysync->{ maxsize }, + 'appendlimit=i' => \$mysync->{ appendlimit }, + 'truncmess=i' => \$mysync->{ truncmess }, + 'minsize=i' => \$minsize, + 'maxage=f' => \$maxage, + 'minage=f' => \$minage, + 'search=s' => \$search, + 'search1=s' => \$mysync->{ search1 }, + 'search2=s' => \$mysync->{ search2 }, + 'foldersizes!' => \$mysync->{ foldersizes }, + 'foldersizesatend!' => \$mysync->{ foldersizesatend }, 'dry!' => \$mysync->{dry}, - 'expunge1|expunge!' => \$expunge1, - 'expunge2!' => \$expunge2, - 'uidexpunge2!' => \$uidexpunge2, - 'subscribed!' => \$subscribed, + 'dry1!' => \$mysync->{dry1}, + 'expunge1|expunge!' => \$mysync->{ expunge1 }, + 'expunge2!' => \$mysync->{ expunge2 }, + 'uidexpunge2!' => \$mysync->{ uidexpunge2 }, + 'subscribed' => \$subscribed, 'subscribe!' => \$subscribe, 'subscribeall|subscribe_all!' => \$subscribeall, - 'justbanner!' => \$justbanner, - 'justfolders!'=> \$justfolders, - 'justfoldersizes!' => \$justfoldersizes, - 'fast!' => \$fast, + 'justbanner!' => \$mysync->{ justbanner }, + 'justfolders!'=> \$mysync->{ justfolders }, + 'justfoldersizes!' => \$mysync->{ justfoldersizes }, 'version' => \$mysync->{version}, 'help' => \$help, - 'timeout=i' => \$timeout, - 'timeout1=i' => \$mysync->{h1}->{timeout}, - 'timeout2=i' => \$mysync->{h2}->{timeout}, - 'skipheader=s' => \$skipheader, + 'timeout=f' => \$mysync->{timeout}, + 'timeout1=f' => \$mysync->{ acc1 }->{timeout}, + 'timeout2=f' => \$mysync->{ acc2 }->{timeout}, + 'skipheader=s' => \$mysync->{ skipheader }, 'useheader=s' => \@useheader, 'wholeheaderifneeded!' => \$wholeheaderifneeded, 'messageidnodomain!' => \$messageidnodomain, 'skipsize!' => \$skipsize, 'allowsizemismatch!' => \$allowsizemismatch, - 'fastio1!' => \$fastio1, - 'fastio2!' => \$fastio2, - 'sslcheck!' => \$mysync->{sslcheck}, + 'fastio1!' => \$mysync->{ acc1 }->{ fastio }, + 'fastio2!' => \$mysync->{ acc2 }->{ fastio }, + 'sslcheck!' => \$mysync->{sslcheck}, 'ssl1!' => \$mysync->{ssl1}, 'ssl2!' => \$mysync->{ssl2}, - 'ssl1_ssl_version=s' => \$mysync->{h1}->{sslargs}->{SSL_version}, - 'ssl2_ssl_version=s' => \$mysync->{h2}->{sslargs}->{SSL_version}, - 'sslargs1=s%' => \$mysync->{h1}->{sslargs}, - 'sslargs2=s%' => \$mysync->{h2}->{sslargs}, + 'ssl1_ssl_version=s' => \$mysync->{ acc1 }->{sslargs}->{SSL_version}, + 'ssl2_ssl_version=s' => \$mysync->{ acc2 }->{sslargs}->{SSL_version}, + 'sslargs1=s%' => \$mysync->{ acc1 }->{sslargs}, + 'sslargs2=s%' => \$mysync->{ acc2 }->{sslargs}, 'tls1!' => \$mysync->{tls1}, 'tls2!' => \$mysync->{tls2}, 'uid1!' => \$uid1, 'uid2!' => \$uid2, - 'authmech1=s' => \$authmech1, - 'authmech2=s' => \$authmech2, - 'authuser1=s' => \$authuser1, - 'authuser2=s' => \$authuser2, - 'proxyauth1' => \$proxyauth1, - 'proxyauth2' => \$proxyauth2, + 'authmech1=s' => \$mysync->{ acc1 }->{ authmech }, + 'authmech2=s' => \$mysync->{ acc2 }->{ authmech }, + 'authuser1=s' => \$mysync->{ acc1 }->{ authuser }, + 'authuser2=s' => \$mysync->{ acc2 }->{ authuser }, + 'proxyauth1' => \$mysync->{ acc1 }->{ proxyauth }, + 'proxyauth2' => \$mysync->{ acc2 }->{ proxyauth }, + 'compress1!' => \$mysync->{ acc1 }->{ compress }, + 'compress2!' => \$mysync->{ acc2 }->{ compress }, + 'keepalive1!' => \$mysync->{ acc1 }->{ keepalive }, + 'keepalive2!' => \$mysync->{ acc2 }->{ keepalive }, 'split1=i' => \$split1, 'split2=i' => \$split2, 'buffersize=i' => \$buffersize, - 'reconnectretry1=i' => \$reconnectretry1, - 'reconnectretry2=i' => \$reconnectretry2, + 'reconnectretry1=i' => \$mysync->{ acc1 }->{ reconnectretry }, + 'reconnectretry2=i' => \$mysync->{ acc2 }->{ reconnectretry }, 'tests!' => \$mysync->{ tests }, 'testsdebug|tests_debug!' => \$mysync->{ testsdebug }, - 'testsunit=s@' => \$mysync->{testsunit}, + 'testsunit=s@' => \$mysync->{testsunit}, 'testslive!' => \$mysync->{testslive}, 'testslive6!' => \$mysync->{testslive6}, 'justlogin!' => \$mysync->{justlogin}, @@ -12332,11 +19965,11 @@ sub get_options_cmd { 'debugcache!' => \$debugcache, 'useuid!' => \$useuid, 'addheader!' => \$mysync->{addheader}, - 'exitwhenover=i' => \$exitwhenover, + 'exitwhenover=i' => \$mysync->{ exitwhenover }, 'checkselectable!' => \$mysync->{ checkselectable }, 'checkfoldersexist!' => \$mysync->{ checkfoldersexist }, 'checkmessageexists!' => \$checkmessageexists, - 'expungeaftereach!' => \$expungeaftereach, + 'expungeaftereach!' => \$mysync->{ expungeaftereach }, 'abletosearch!' => \$mysync->{abletosearch}, 'abletosearch1!' => \$mysync->{abletosearch1}, 'abletosearch2!' => \$mysync->{abletosearch2}, @@ -12349,41 +19982,56 @@ sub get_options_cmd { 'create_folder_old!' => \$create_folder_old, 'maxmessagespersecond=f' => \$mysync->{maxmessagespersecond}, 'maxbytespersecond=i' => \$mysync->{maxbytespersecond}, - 'maxbytesafter=i' => \$mysync->{maxbytesafter}, - 'maxsleep=f' => \$mysync->{maxsleep}, + 'maxbytesafter=i' => \$mysync->{maxbytesafter}, + 'maxsleep=f' => \$mysync->{maxsleep}, 'skipcrossduplicates!' => \$skipcrossduplicates, 'debugcrossduplicates!' => \$debugcrossduplicates, 'log!' => \$mysync->{log}, + 'tail!' => \$mysync->{tail}, 'logfile=s' => \$mysync->{logfile}, 'logdir=s' => \$mysync->{logdir}, 'errorsmax=i' => \$mysync->{errorsmax}, - 'errorsdump!' => \$mysync->{errorsdump}, + 'errorsdump!' => \$mysync->{ errorsdump }, 'fetch_hash_set=s' => \$fetch_hash_set, 'automap!' => \$mysync->{automap}, 'justautomap!' => \$mysync->{justautomap}, 'id!' => \$mysync->{id}, 'f1f2=s@' => \$mysync->{f1f2}, + 'nof1f2' => \$mysync->{nof1f2}, 'f1f2h=s%' => \$mysync->{f1f2h}, 'justfolderlists!' => \$mysync->{justfolderlists}, - 'delete1emptyfolders' => \$mysync->{delete1emptyfolders}, + 'delete1emptyfolders' => \$mysync->{delete1emptyfolders}, + 'checknoabletosearch!' => \$mysync->{checknoabletosearch}, + 'syncduplicates!' => \$mysync->{ syncduplicates }, + 'dockercontext!' => \$mysync->{ dockercontext }, + + ) ; #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; - $debug and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ; + $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ; my $numopt_after = scalar @arguments ; #myprint( "get options: [$opt_ret][$numopt][$numopt_after]\n" ) ; - if ( $numopt_after ) { - myprint( "Extra arguments found: @arguments\n", "It usually means a quoting issue in the command line\n" ) ; + + # The $arguments[0] test is just because parallel adds "" when it is + # used with {=7=} in sync_parallel_unix.sh + if ( $numopt_after and $arguments[0] ) { + myprint( + "Found ", scalar( @arguments ), " extra arguments : [@arguments]\n", + "It usually means a quoting issue in the command line ", + "or some misspelling options.\n", + ) ; return ; } - if ( ! $opt_ret ) { - return ; - } - return $numopt ; + if ( ! $opt_ret ) { + return ; + } + return $numopt ; } - -sub tests_get_options { + +sub tests_get_options +{ note( 'Entering tests_get_options()' ) ; # CAVEAT: still setting global variables, be careful @@ -12397,45 +20045,46 @@ sub tests_get_options { # * options not known # * --delete 2 input # * number of arguments or QUERY_STRING length - my $mysync3 = { } ; - is( undef, get_options( $mysync3, qw( --noexist ) ), 'get_options: --noexist => undef' ) ; - is( undef, $mysync3->{ noexist }, 'get_options: --noexist => undef' ) ; - $mysync3 = { } ; - is( undef, get_options( $mysync3, qw( --lalala --noexist --version ) ), 'get_options: --lalala --noexist --version => undef' ) ; - is( 1, $mysync3->{ version }, 'get_options: --version => 1' ) ; - is( undef, $mysync3->{ noexist }, 'get_options: --noexist => undef' ) ; - $mysync3 = { } ; - is( 1, get_options( $mysync3, qw( --delete2 ) ), 'get_options: --delete2 => 1' ) ; - is( 1, $delete2, 'get_options: --delete2 => $delete2 = 1' ) ; - $mysync3 = { } ; - is( undef, get_options( $mysync3, qw( --delete 2 ) ), 'get_options: --delete 2 => undef' ) ; - is( undef, $delete1, 'get_options: --delete 2 => $delete1 still undef ; good!' ) ; - $mysync3 = { } ; - is( undef, get_options( $mysync3, "--delete 2" ), 'get_options: --delete 2 => undef' ) ; - - is( 1, get_options( $mysync3, "--version" ), 'get_options: --version => 1' ) ; - is( 1, get_options( $mysync3, "--help" ), 'get_options: --help => 1' ) ; - - is( undef, get_options( $mysync3, qw( --noexist --version ) ), 'get_options: --debug --noexist --version => undef' ) ; - is( 1, get_options( $mysync3, qw( --version ) ), 'get_options: --version => 1' ) ; - is( undef, get_options( $mysync3, qw( extra ) ), 'get_options: extra => undef' ) ; - is( undef, get_options( $mysync3, qw( extra1 --version extra2 ) ), 'get_options: extra1 --version extra2 => undef' ) ; - - $mysync3 = { } ; - is( 2, get_options( $mysync3, qw( --host1 HOST_01) ), 'get_options: --host1 HOST_01 => 1' ) ; - is( 'HOST_01', $mysync3->{ host1 }, 'get_options: --host1 HOST_01 => HOST_01' ) ; - #myprint( Data::Dumper->Dump( [ $mysync3 ] ) ) ; - + my $mysync = { } ; + is( undef, get_options( $mysync, qw( --noexist ) ), 'get_options: --noexist => undef' ) ; + is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ; + $mysync = { } ; + is( undef, get_options( $mysync, qw( --lalala --noexist --version ) ), 'get_options: --lalala --noexist --version => undef' ) ; + is( 1, $mysync->{ version }, 'get_options: --version => 1' ) ; + is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ; + $mysync = { } ; + is( 1, get_options( $mysync, qw( --delete2 ) ), 'get_options: --delete2 => 1' ) ; + is( 1, $mysync->{ delete2 }, 'get_options: --delete2 => var delete2 = 1' ) ; + $mysync = { } ; + is( undef, get_options( $mysync, qw( --delete 2 ) ), 'get_options: --delete 2 => var undef' ) ; + is( undef, $mysync->{ delete1 }, 'get_options: --delete 2 => var still undef ; good!' ) ; + $mysync = { } ; + is( undef, get_options( $mysync, "--delete 2" ), 'get_options: --delete 2 => undef' ) ; + + is( 1, get_options( $mysync, "--version" ), 'get_options: --version => 1' ) ; + is( 1, get_options( $mysync, "--help" ), 'get_options: --help => 1' ) ; + + is( undef, get_options( $mysync, qw( --noexist --version ) ), 'get_options: --debug --noexist --version => undef' ) ; + is( 1, get_options( $mysync, qw( --version ) ), 'get_options: --version => 1' ) ; + is( undef, get_options( $mysync, qw( extra ) ), 'get_options: extra => undef' ) ; + is( undef, get_options( $mysync, qw( extra1 --version extra2 ) ), 'get_options: extra1 --version extra2 => undef' ) ; + + $mysync = { } ; + is( 2, get_options( $mysync, qw( --host1 HOST_01) ), 'get_options: --host1 HOST_01 => 1' ) ; + is( 'HOST_01', $mysync->{ host1 }, 'get_options: --host1 HOST_01 => HOST_01' ) ; + #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; + note( 'Leaving tests_get_options()' ) ; return ; } -sub get_options { +sub get_options +{ my $mysync = shift @ARG ; my @arguments = @ARG ; - #myprint( "1 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ; + #myprint( "1 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ; my $ret ; if ( under_cgi_context( ) ) { # CGI context @@ -12444,13 +20093,14 @@ sub get_options { # Command line context ; $ret = get_options_cmd( $mysync, @arguments ) ; } ; - #myprint( "2 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ; + #myprint( "2 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ; + foreach my $key ( sort keys %{ $mysync } ) { if ( ! defined $mysync->{$key} ) { delete $mysync->{$key} ; next ; } - if ( 'ARRAY' eq ref( $mysync->{$key} ) + if ( 'ARRAY' eq ref( $mysync->{$key} ) and 0 == scalar( @{ $mysync->{$key} } ) ) { delete $mysync->{$key} ; } @@ -12458,14 +20108,59 @@ sub get_options { return $ret ; } -sub testunitsession { + +sub tests_infos +{ + note( 'Entering tests_infos()' ) ; + note( "OSNAME=$OSNAME" ) ; + note( "hostname=". hostname() ) ; + note( "cwd=" . getcwd( ) ) ; + note( "PROGRAM_NAME=$PROGRAM_NAME" ) ; + my $stat = stat("$PROGRAM_NAME") ; + my $perms = sprintf( "%04o\n", $stat->mode & oct($PERMISSION_FILTER) ) ; + note( "permissions=$perms" ) ; + note( "PROCESS_ID=$PROCESS_ID" ) ; + note( "REAL_USER_ID=$REAL_USER_ID" ) ; + note( "EFFECTIVE_USER_ID=$EFFECTIVE_USER_ID" ) ; + note( "context: " . imapsync_context( $sync ) ) ; + note( "memory_consumption: " . memory_consumption() . " bytes aka " . bytes_display_string_dec( memory_consumption() ) ) ; + cpu_number + note( "cpu_number: " . cpu_number() ) ; + note( $sync->{rcs} ) ; + + note( 'Leaving tests_infos()' ) ; + return ; +} + + + +sub condition_to_leave_after_tests +{ + my $mysync = shift ; + if ( $mysync->{ testslive } or $mysync->{ testslive6 } ) + { + return 0 ; + } + + if ( $mysync->{ tests } + or $mysync->{ testsdebug } + or $mysync->{ testsunit } + ) + { + return 1 ; + } +} + + +sub testunitsession +{ my $mysync = shift ; - + if ( ! $mysync ) { return ; } if ( ! $mysync->{ testsunit } ) { return ; } my @functions = @{ $mysync->{ testsunit } } ; - + if ( ! @functions ) { return ; } SKIP: { @@ -12476,7 +20171,8 @@ sub testunitsession { return ; } -sub tests_count_0s { +sub tests_count_0s +{ note( 'Entering tests_count_zeros()' ) ; is( 0, count_0s( ), 'count_0s: no parameters => 0' ) ; is( 1, count_0s( 0 ), 'count_0s: 0 => 1' ) ; @@ -12486,17 +20182,20 @@ sub tests_count_0s { note( 'Leaving tests_count_zeros()' ) ; return ; } -sub count_0s { +sub count_0s +{ my @array = @ARG ; - + if ( ! @array ) { return 0 ; } my $nb_zeros = 0 ; map { $_ == 0 and $nb_zeros += 1 } @array ; return $nb_zeros ; } -sub tests_report_failures { +sub tests_report_failures +{ note( 'Entering tests_report_failures()' ) ; + is( undef, report_failures( ), 'report_failures: no parameters => undef' ) ; is( "nb 1 - first\n", report_failures( ({'ok' => 0, name => 'first'}) ), 'report_failures: "first" failed => nb 1 - first' ) ; is( q{}, report_failures( ( {'ok' => 1, name => 'first'} ) ), 'report_failures: "first" success =>' ) ; @@ -12506,11 +20205,12 @@ sub tests_report_failures { return ; } -sub report_failures { +sub report_failures +{ my @details = @ARG ; - + if ( ! @details ) { return ; } - + my $counter = 1 ; my $report = q{} ; foreach my $details ( @details ) { @@ -12524,15 +20224,19 @@ sub report_failures { } -sub tests_true { +sub tests_true +{ note( 'Entering tests_true()' ) ; + is( 1, 1, 'true: 1 is 1' ) ; note( 'Leaving tests_true()' ) ; return ; } -sub tests_testsunit { +sub tests_testsunit +{ note( 'Entering tests_testunit()' ) ; + is( undef, testsunit( ), 'testsunit: no parameters => undef' ) ; is( undef, testsunit( undef ), 'testsunit: an undef parameter => undef' ) ; is( undef, testsunit( q{} ), 'testsunit: an empty parameter => undef' ) ; @@ -12542,144 +20246,78 @@ sub tests_testsunit { return ; } -sub testsunit { +sub testsunit +{ my @functions = @ARG ; - + if ( ! @functions ) { # myprint( "testsunit warning: no argument given\n" ) ; - return ; + return ; } - + foreach my $function ( @functions ) { if ( ! $function ) { myprint( "testsunit warning: argument is empty\n" ) ; - next ; + next ; } if ( ! exists &$function ) { myprint( "testsunit warning: function $function does not exist\n" ) ; - next ; + next ; } if ( ! defined &$function ) { myprint( "testsunit warning: function $function is not defined\n" ) ; - next ; + next ; } my $function_ref = \&{ $function } ; - &$function_ref() ; + &$function_ref() ; } return ; } -sub testsdebug { - my $mysync = shift ; - if ( ! $mysync->{ testsdebug } ) { return ; } - SKIP: { - if ( ! $mysync->{ testsdebug } ) { - skip 'No test in normal run' ; - } - - note( 'Entering testsdebug()' ) ; - ok( ( ( not -d 'W/tmp/tests' ) or rmtree( 'W/tmp/tests/' ) ), 'testsdebug: rmtree W/tmp/tests' ) ; - #tests_bytes_display_string( ) ; - #tests_ucsecond( ) ; - #tests_mkpath( ) ; - #tests_format_for_imap_arg( ) ; - #tests_is_a_release_number( ) ; - #tests_delete1emptyfolders( ) ; - #tests_memory_consumption( ) ; - #tests_imap2_folder_name() ; - #tests_length_ref( ) ; - #tests_diff_or_NA( ) ; - #tests_match_number( ) ; - #tests_all_defined( ) ; - #tests_guess_separator( ) ; - #tests_message_for_host2( ) ; - #tests_special_from_folders_hash( ) ; - #tests_do_valid_directory( ) ; - #tests_notmatch( ) ; - #tests_match( ) ; - #tests_get_options( ) ; - #tests_rand32( ) ; - #tests_string_to_file( ) ; - #tests_hashsynclocal( ) ; - #tests_output( ) ; - #tests_output_reset_with( ) ; - #tests_output_start( ) ; - #tests_hashsync( ) ; - #tests_check_last_release( ) ; - #tests_cpu_number( ) ; - #tests_load_and_delay( ) ; - #tests_loadavg( ) ; - #tests_backtick( ) ; - #tests_firstline( ) ; - #tests_pipemess( ) ; - #tests_not_long_imapsync_version_public( ) ; - #tests_get_options_cgi( ) ; - #tests_guess_special( ) ; - ####tests_reconnect_if_needed( ) ; - #tests_reconnect_12_if_needed( ) ; - #tests_sleep_max_bytes( ) ; - #tests_file_to_string( ) ; - #tests_under_cgi_context( ) ; - #tests_umask( ) ; - #tests_umask_str( ) ; - #tests_set_umask( ) ; - #tests_createhashfileifneeded( ) ; - #tests_filter_forbidden_characters( ) ; - #tests_logfile( ) ; - #tests_setlogfile( ) ; - #tests_move_slash( ) ; - #tests_testsunit( ) ; - #tests_always_fail( ) ; - #tests_count_0s( ) ; - #tests_report_failures( ) ; - #tests_max( ) ; - #tests_min( ) ; - #tests_sleep_if_needed( ) ; - #tests_imapsping( ) ; - #tests_tcpping( ) ; - #tests_sslcheck( ) ; - #tests_resolv( ) ; - #tests_resolvrev( ) ; - #tests_connect_socket( ) ; +sub testsdebug +{ + # Now a little obsolete since there is + # imapsync ... --testsunit "anyfunction" + my $mysync = shift ; + if ( ! $mysync->{ testsdebug } ) { return ; } + SKIP: { + if ( ! $mysync->{ testsdebug } ) { + skip 'No test in normal run' ; + } + + note( 'Entering testsdebug()' ) ; + #ok( ( ( not -d 'W/tmp/tests' ) or rmtree( 'W/tmp/tests/' ) ), 'testsdebug: rmtree W/tmp/tests' ) ; + #tests_check_binary_embed_all_dyn_libs( ) ; + #tests_killpid_by_parent( ) ; + #tests_killpid_by_brother( ) ; + #tests_kill_zero( ) ; + #tests_connect_socket( ) ; #tests_probe_imapssl( ) ; + #tests_cpu_number( ) ; #tests_mailimapclient_connect( ) ; - #tests_guess_prefix( ) ; - #tests_usage( ) ; - #tests_version_from_rcs( ) ; - #tests_mailimapclient_connect_bug( ) ; # it fails with Mail-IMAPClient <= 3.39 - #tests_backslash_caret( ) ; - tests_write_pidfile( ) ; - tests_remove_pidfile_not_running( ) ; - tests_match_a_pid_number( ) ; - note( 'Leaving testsdebug()' ) ; + tests_loadavg( ) ; + #tests_always_fail( ) ; + + note( 'Leaving testsdebug()' ) ; done_testing( ) ; } return ; } -sub tests_template { - note( 'Entering tests_template()' ) ; - is( undef, undef, 'template: undef is undef' ) ; - is_deeply( {}, {}, 'template: a hash is a hash' ) ; - is_deeply( [], [], 'template: an array is an array' ) ; - note( 'Leaving tests_template()' ) ; - return ; -} - - -sub tests { - my $mysync = shift ; - if ( ! $mysync->{ tests } ) { return ; } +sub tests +{ + my $mysync = shift ; + if ( ! $mysync->{ tests } ) { return ; } - SKIP: { + SKIP: { skip 'No test in normal run' if ( ! $mysync->{ tests } ) ; - note( 'Entering tests()' ) ; + note( 'Entering tests()' ) ; tests_folder_routines( ) ; tests_compare_lists( ) ; tests_regexmess( ) ; tests_skipmess( ) ; - tests_flags_regex(); + tests_regexflags( ); tests_ucsecond( ) ; tests_permanentflags(); tests_flags_filter( ) ; @@ -12713,7 +20351,8 @@ sub tests { tests_time_remaining( ) ; tests_decompose_regex( ) ; tests_backtick( ) ; - tests_bytes_display_string( ) ; + tests_bytes_display_string_bin( ) ; + tests_bytes_display_string_dec( ) ; tests_header_line_normalize( ) ; tests_fix_Inbox_INBOX_mapping( ) ; tests_max_line_length( ) ; @@ -12724,6 +20363,7 @@ sub tests { tests_sleep_max_bytes( ) ; tests_logfile( ) ; tests_setlogfile( ) ; + tests_jux_utf8_old( ) ; tests_jux_utf8( ) ; tests_pipemess( ) ; tests_jux_utf8_list( ) ; @@ -12772,16 +20412,13 @@ sub tests { tests_umask_str( ) ; tests_set_umask( ) ; tests_createhashfileifneeded( ) ; - tests_move_slash( ) ; + tests_slash_to_underscore( ) ; tests_testsunit( ) ; tests_count_0s( ) ; tests_report_failures( ) ; tests_min( ) ; - #tests_resolv( ) ; + #tests_connect_socket( ) ; #tests_resolvrev( ) ; - tests_connect_socket( ) ; - tests_probe_imapssl( ) ; - tests_mailimapclient_connect( ) ; tests_usage( ) ; tests_version_from_rcs( ) ; tests_backslash_caret( ) ; @@ -12790,19 +20427,113 @@ sub tests { tests_remove_pidfile_not_running( ) ; tests_match_a_pid_number( ) ; tests_prefix_seperator_invertion( ) ; - tests_is_an_integer( ) ; + tests_is_integer( ) ; tests_integer_or_1( ) ; tests_is_number( ) ; tests_sig_install( ) ; tests_template( ) ; tests_split_around_equal( ) ; tests_toggle_sleep( ) ; - #tests_always_fail( ) ; - done_testing( 1181 ) ; - note( 'Leaving tests()' ) ; + tests_labels( ) ; + tests_synclabels( ) ; + tests_uidexpunge_or_expunge( ) ; + tests_appendlimit_from_capability( ) ; + tests_maxsize_setting( ) ; + tests_mock_capability( ) ; + tests_appendlimit( ) ; + tests_capability_of( ) ; + tests_search_in_array( ) ; + tests_operators_and_exclam_precedence( ) ; + tests_teelaunch( ) ; + tests_logfileprepa( ) ; + tests_useheader_suggestion( ) ; + tests_nb_messages_in_2_not_in_1( ) ; + tests_labels_add_subfolder2( ) ; + tests_labels_remove_subfolder1( ) ; + tests_resynclabels( ) ; + tests_labels_remove_special( ) ; + tests_uniq( ) ; + tests_remove_from_requested_folders( ) ; + tests_errors_log( ) ; + tests_add_subfolder1_to_folderrec( ) ; + tests_sanitize_subfolder( ) ; + tests_remove_edging_blanks( ) ; + tests_sanitize( ) ; + tests_remove_last_char_if_is( ) ; + tests_check_binary_embed_all_dyn_libs( ) ; + tests_nthline( ) ; + tests_secondline( ) ; + tests_tail( ) ; + tests_truncmess( ) ; + tests_eta( ) ; + tests_timesince( ) ; + tests_timenext( ) ; + tests_foldersize( ) ; + tests_imapsync_context( ) ; + tests_abort( ) ; + tests_probe_imapssl( ) ; + tests_mailimapclient_connect( ) ; + tests_checknoabletosearch( ) ; + tests_errorsdump( ) ; + tests_errorsanalyse( ) ; + tests_most_common_error( ) ; + tests_errorclassify( ) ; + tests_error_type( ) ; + tests_sanitize_host( ) ; + tests_hmac_sha1_hex( ) ; + tests_total_bytes_max_reached( ) ; + tests_header_construct( ) ; + tests_remove_doublequotes_if_any( ) ; + tests_login_imap( ) ; + tests_login_imap_oauth( ) ; + tests_skipmess_neg( ) ; + tests_localtimez( ) ; + tests_file_to_array( ) ; + tests_cpu_time( ) ; + tests_cpu_percent( ) ; + tests_cpu_percent_global( ) ; + tests_flags_for_host2( ) ; + tests_under_docker_context( ) ; + tests_exit_value( ) ; + tests_comment_of_error_type( ) ; + tests_debugcontent( ) ; + tests_compress_ssl( ) ; + tests_compress( ) ; + tests_get_options_extra( ) ; + tests_get_options_from_string( ) ; + tests_infos( ) ; + #tests_resolv( ) ; + + # Those three are for later use, when webserver will be inside imapsync + # or will be deleted them if I abandon the project. + #tests_killpid_by_parent( ) ; + #tests_killpid_by_brother( ) ; + #tests_kill_zero( ) ; + + #tests_always_fail( ) ; + done_testing( 1860 ) ; + note( 'Leaving tests()' ) ; } return ; } +sub tests_template +{ + note( 'Entering tests_template()' ) ; + + is( undef, template( ), 'template: no args => undef' ) ; + my $mysync = { } ; + is( undef, template( $mysync ), 'template: undef => undef' ) ; + is_deeply( {}, {}, 'template: a hash is a hash' ) ; + is_deeply( [], [], 'template: an array is an array' ) ; + + note( 'Leaving tests_template()' ) ; + return ; +} +sub template +{ + my $mysync = shift @ARG ; + return ; +} diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl deleted file mode 100755 index be5cb411d7..0000000000 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/perl - -use DBI; -use LockFile::Simple qw(lock trylock unlock); -use Proc::ProcessTable; -use Data::Dumper qw(Dumper); -use IPC::Run 'run'; -use String::Util 'trim'; -use File::Temp; -use Try::Tiny; -use sigtrap 'handler' => \&sig_handler, qw(INT TERM KILL QUIT); - -my $t = Proc::ProcessTable->new; -my $imapsync_running = grep { $_->{cmndline} =~ /^\/usr\/bin\/perl \/usr\/local\/bin\/imapsync\s/ } @{$t->table}; -if ($imapsync_running eq 1) -{ - print "imapsync is active, exiting..."; - exit; -} - -sub qqw($) { split /\s+/, $_[0] } - -$DBNAME = ''; -$DBUSER = ''; -$DBPASS = ''; - -$run_dir="/tmp"; -$dsn = "DBI:mysql:database=" . $DBNAME . ";host=mysql"; -$lock_file = $run_dir . "/imapsync_busy"; -$lockmgr = LockFile::Simple->make(-autoclean => 1, -max => 1); -$lockmgr->lock($lock_file) || die "can't lock ${lock_file}"; -$dbh = DBI->connect($dsn, $DBUSER, $DBPASS, { - mysql_auto_reconnect => 1, - mysql_enable_utf8mb4 => 1 -}); -sub sig_handler { - # Send die to force exception in "run" - die "sig_handler received signal, preparing to exit...\n"; -}; - -open my $file, '<', "/etc/sogo/sieve.creds"; -my $creds = <$file>; -close $file; -my ($master_user, $master_pass) = split /:/, $creds; -my $sth = $dbh->prepare("SELECT id, - user1, - user2, - host1, - authmech1, - password1, - exclude, - port1, - enc1, - delete2duplicates, - maxage, - subfolder2, - delete1, - delete2, - automap, - skipcrossduplicates, - maxbytespersecond, - custom_params, - subscribeall, - timeout1, - timeout2 - FROM imapsync - WHERE active = 1 - AND is_running = 0 - AND ( - UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_run) > mins_interval * 60 - OR - last_run IS NULL) - ORDER BY last_run"); - -$sth->execute(); -my $row; - -while ($row = $sth->fetchrow_arrayref()) { - - $id = @$row[0]; - $user1 = @$row[1]; - $user2 = @$row[2]; - $host1 = @$row[3]; - $authmech1 = @$row[4]; - $password1 = @$row[5]; - $exclude = @$row[6]; - $port1 = @$row[7]; - $enc1 = @$row[8]; - $delete2duplicates = @$row[9]; - $maxage = @$row[10]; - $subfolder2 = @$row[11]; - $delete1 = @$row[12]; - $delete2 = @$row[13]; - $automap = @$row[14]; - $skipcrossduplicates = @$row[15]; - $maxbytespersecond = @$row[16]; - $custom_params = @$row[17]; - $subscribeall = @$row[18]; - $timeout1 = @$row[19]; - $timeout2 = @$row[20]; - - $is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1 WHERE id = ?"); - $is_running->bind_param( 1, ${id} ); - $is_running->execute(); - - if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; } - - my $template = $run_dir . '/imapsync.XXXXXXX'; - my $passfile1 = File::Temp->new(TEMPLATE => $template); - my $passfile2 = File::Temp->new(TEMPLATE => $template); - - print $passfile1 "$password1\n"; - print $passfile2 trim($master_pass) . "\n"; - - my @custom_params_a = qqw($custom_params); - my $custom_params_ref = \@custom_params_a; - - my $generated_cmds = [ "/usr/local/bin/imapsync", - "--tmpdir", "/tmp", - "--nofoldersizes", - ($timeout1 gt "0" ? () : ('--timeout1', $timeout1)), - ($timeout2 gt "0" ? () : ('--timeout2', $timeout2)), - ($exclude eq "" ? () : ("--exclude", $exclude)), - ($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)), - ($maxage eq "0" ? () : ('--maxage', $maxage)), - ($maxbytespersecond eq "0" ? () : ('--maxbytespersecond', $maxbytespersecond)), - ($delete2duplicates ne "1" ? () : ('--delete2duplicates')), - ($subscribeall ne "1" ? () : ('--subscribeall')), - ($delete1 ne "1" ? () : ('--delete')), - ($delete2 ne "1" ? () : ('--delete2')), - ($automap ne "1" ? () : ('--automap')), - ($skipcrossduplicates ne "1" ? () : ('--skipcrossduplicates')), - (!defined($enc1) ? () : ($enc1)), - "--host1", $host1, - "--user1", $user1, - "--passfile1", $passfile1->filename, - "--port1", $port1, - "--host2", "localhost", - "--user2", $user2 . '*' . trim($master_user), - "--passfile2", $passfile2->filename, - '--no-modulesversion']; - - try { - run [@$generated_cmds, @$custom_params_ref], '&>', \my $stdout; - $update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, last_run = NOW(), is_running = 0 WHERE id = ?"); - $update->bind_param( 1, ${stdout} ); - $update->bind_param( 2, ${id} ); - $update->execute(); - } catch { - $update = $dbh->prepare("UPDATE imapsync SET returned_text = 'Could not start or finish imapsync', last_run = NOW(), is_running = 0 WHERE id = ?"); - $update->bind_param( 1, ${id} ); - $update->execute(); - $lockmgr->unlock($lock_file); - }; - -} - -$sth->finish(); -$dbh->disconnect(); - -$lockmgr->unlock($lock_file); diff --git a/data/Dockerfiles/dovecot/imapsync_runner.pl b/data/Dockerfiles/dovecot/imapsync_runner.pl new file mode 100644 index 0000000000..1030603ce6 --- /dev/null +++ b/data/Dockerfiles/dovecot/imapsync_runner.pl @@ -0,0 +1,196 @@ +#!/usr/bin/perl + +use DBI; +use LockFile::Simple qw(lock trylock unlock); +use Proc::ProcessTable; +use Data::Dumper qw(Dumper); +use IPC::Run 'run'; +use File::Temp; +use Try::Tiny; +use sigtrap 'handler' => \&sig_handler, qw(INT TERM KILL QUIT); + +sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s }; +my $t = Proc::ProcessTable->new; +my $imapsync_running = grep { $_->{cmndline} =~ /imapsync\s/i } @{$t->table}; +if ($imapsync_running ge 1) +{ + print "imapsync is active, exiting..."; + exit; +} + +sub qqw($) { + my @params = (); + my @values = split(/(?=--)/, $_[0]); + foreach my $val (@values) { + my @tmpparam = split(/ /, $val, 2); + foreach my $tmpval (@tmpparam) { + if ($tmpval ne '') { + push @params, $tmpval; + } + } + } + foreach my $val (@params) { + $val=trim($val); + } + return @params; +} + +$run_dir="/tmp"; +$dsn = 'DBI:mysql:database=' . $ENV{'DBNAME'} . ';mysql_socket=/var/run/mysqld/mysqld.sock'; +$lock_file = $run_dir . "/imapsync_busy"; +$lockmgr = LockFile::Simple->make(-autoclean => 1, -max => 1); +$lockmgr->lock($lock_file) || die "can't lock ${lock_file}"; +$dbh = DBI->connect($dsn, $ENV{'DBUSER'}, $ENV{'DBPASS'}, { + mysql_auto_reconnect => 1, + mysql_enable_utf8mb4 => 1 +}); +$dbh->do("UPDATE imapsync SET is_running = 0"); + +sub sig_handler { + # Send die to force exception in "run" + die "sig_handler received signal, preparing to exit...\n"; +}; + +open my $file, '<', "/etc/sogo/sieve.creds"; +my $creds = <$file>; +close $file; +my ($master_user, $master_pass) = split /:/, $creds; +my $sth = $dbh->prepare("SELECT id, + user1, + user2, + host1, + authmech1, + password1, + exclude, + port1, + enc1, + delete2duplicates, + maxage, + subfolder2, + delete1, + delete2, + automap, + skipcrossduplicates, + maxbytespersecond, + custom_params, + subscribeall, + timeout1, + timeout2, + dry + FROM imapsync + WHERE active = 1 + AND is_running = 0 + AND ( + UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_run) > mins_interval * 60 + OR + last_run IS NULL) + ORDER BY last_run"); + +$sth->execute(); +my $row; + +while ($row = $sth->fetchrow_arrayref()) { + + $id = @$row[0]; + $user1 = @$row[1]; + $user2 = @$row[2]; + $host1 = @$row[3]; + $authmech1 = @$row[4]; + $password1 = @$row[5]; + $exclude = @$row[6]; + $port1 = @$row[7]; + $enc1 = @$row[8]; + $delete2duplicates = @$row[9]; + $maxage = @$row[10]; + $subfolder2 = @$row[11]; + $delete1 = @$row[12]; + $delete2 = @$row[13]; + $automap = @$row[14]; + $skipcrossduplicates = @$row[15]; + $maxbytespersecond = @$row[16]; + $custom_params = @$row[17]; + $subscribeall = @$row[18]; + $timeout1 = @$row[19]; + $timeout2 = @$row[20]; + $dry = @$row[21]; + + if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; } + + my $template = $run_dir . '/imapsync.XXXXXXX'; + my $passfile1 = File::Temp->new(TEMPLATE => $template); + my $passfile2 = File::Temp->new(TEMPLATE => $template); + + binmode( $passfile1, ":utf8" ); + + print $passfile1 "$password1\n"; + print $passfile2 trim($master_pass) . "\n"; + + my @custom_params_a = qqw($custom_params); + my $custom_params_ref = \@custom_params_a; + + my $generated_cmds = [ "/usr/local/bin/imapsync", + "--tmpdir", "/tmp", + "--nofoldersizes", + "--addheader", + ($timeout1 le "0" ? () : ('--timeout1', $timeout1)), + ($timeout2 le "0" ? () : ('--timeout2', $timeout2)), + ($exclude eq "" ? () : ("--exclude", $exclude)), + ($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)), + ($maxage eq "0" ? () : ('--maxage', $maxage)), + ($maxbytespersecond eq "0" ? () : ('--maxbytespersecond', $maxbytespersecond)), + ($delete2duplicates ne "1" ? () : ('--delete2duplicates')), + ($subscribeall ne "1" ? () : ('--subscribeall')), + ($delete1 ne "1" ? () : ('--delete')), + ($delete2 ne "1" ? () : ('--delete2')), + ($automap ne "1" ? () : ('--automap')), + ($skipcrossduplicates ne "1" ? () : ('--skipcrossduplicates')), + (!defined($enc1) ? () : ($enc1)), + "--host1", $host1, + "--user1", $user1, + "--passfile1", $passfile1->filename, + "--port1", $port1, + "--host2", "localhost", + "--user2", $user2 . '*' . trim($master_user), + "--passfile2", $passfile2->filename, + ($dry eq "1" ? ('--dry') : ()), + '--no-modulesversion', + '--noreleasecheck']; + + try { + $is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1, success = NULL, exit_status = NULL WHERE id = ?"); + $is_running->bind_param( 1, ${id} ); + $is_running->execute(); + + run [@$generated_cmds, @$custom_params_ref], '&>', \my $stdout; + + # check exit code and status + ($exit_code, $exit_status) = ($stdout =~ m/Exiting\swith\sreturn\svalue\s(\d+)\s\(([^:)]+)/); + + $success = 0; + if (defined $exit_code && $exit_code == 0) { + $success = 1; + } + + $update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, success = ?, exit_status = ? WHERE id = ?"); + $update->bind_param( 1, ${stdout} ); + $update->bind_param( 2, ${success} ); + $update->bind_param( 3, ${exit_status} ); + $update->bind_param( 4, ${id} ); + $update->execute(); + } catch { + $update = $dbh->prepare("UPDATE imapsync SET returned_text = 'Could not start or finish imapsync', success = 0 WHERE id = ?"); + $update->bind_param( 1, ${id} ); + $update->execute(); + } finally { + $update = $dbh->prepare("UPDATE imapsync SET last_run = NOW(), is_running = 0 WHERE id = ?"); + $update->bind_param( 1, ${id} ); + $update->execute(); + }; + + +} + +$sth->finish(); +$dbh->disconnect(); + +$lockmgr->unlock($lock_file); diff --git a/data/Dockerfiles/dovecot/maildir_gc.sh b/data/Dockerfiles/dovecot/maildir_gc.sh index 24c1e46144..21358ccbdc 100755 --- a/data/Dockerfiles/dovecot/maildir_gc.sh +++ b/data/Dockerfiles/dovecot/maildir_gc.sh @@ -1,2 +1,2 @@ -#/bin/bash +#!/bin/bash [ -d /var/vmail/_garbage/ ] && /usr/bin/find /var/vmail/_garbage/ -mindepth 1 -maxdepth 1 -type d -cmin +${MAILDIR_GC_TIME} -exec rm -r {} \; diff --git a/data/Dockerfiles/dovecot/optimize-fts.sh b/data/Dockerfiles/dovecot/optimize-fts.sh new file mode 100644 index 0000000000..c19d6e5a75 --- /dev/null +++ b/data/Dockerfiles/dovecot/optimize-fts.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +if [[ "${SKIP_FTS}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + exit 0 +else + doveadm fts optimize -A +fi diff --git a/data/Dockerfiles/dovecot/postlogin.sh b/data/Dockerfiles/dovecot/postlogin.sh deleted file mode 100755 index 01a45f31f1..0000000000 --- a/data/Dockerfiles/dovecot/postlogin.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -export MASTER_USER=$USER -exec "$@" diff --git a/data/Dockerfiles/dovecot/quarantine_notify.py b/data/Dockerfiles/dovecot/quarantine_notify.py new file mode 100755 index 0000000000..5757cdf53e --- /dev/null +++ b/data/Dockerfiles/dovecot/quarantine_notify.py @@ -0,0 +1,177 @@ +#!/usr/bin/python3 + +import smtplib +import os +import sys +import MySQLdb +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.utils import COMMASPACE, formatdate +import jinja2 +from jinja2 import TemplateError +from jinja2.sandbox import SandboxedEnvironment +import json +import redis +import time +import html2text +import socket + +pid = str(os.getpid()) +pidfile = "/tmp/quarantine_notify.pid" + +if os.path.isfile(pidfile): + print("%s already exists, exiting" % (pidfile)) + sys.exit() + +pid = str(os.getpid()) +f = open(pidfile, 'w') +f.write(pid) +f.close() + +try: + + while True: + try: + r = redis.StrictRedis(host='redis', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS']) + r.ping() + except Exception as ex: + print('%s - trying again...' % (ex)) + time.sleep(3) + else: + break + + time_now = int(time.time()) + mailcow_hostname = os.environ.get('MAILCOW_HOSTNAME') + + max_score = float(r.get('Q_MAX_SCORE') or "9999.0") + if max_score == "": + max_score = 9999.0 + + def query_mysql(query, params = None, headers = True, update = False): + while True: + try: + cnx = MySQLdb.connect(user=os.environ.get('DBUSER'), password=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8mb4", collation="utf8mb4_general_ci") + except Exception as ex: + print('%s - trying again...' % (ex)) + time.sleep(3) + else: + break + cur = cnx.cursor() + if params: + cur.execute(query, params) + else: + cur.execute(query) + if not update: + result = [] + columns = tuple( [d[0] for d in cur.description] ) + for row in cur: + if headers: + result.append(dict(list(zip(columns, row)))) + else: + result.append(row) + cur.close() + cnx.close() + return result + else: + cnx.commit() + cur.close() + cnx.close() + + def notify_rcpt(rcpt, msg_count, quarantine_acl, category): + if category == "add_header": category = "add header" + meta_query = query_mysql('SELECT `qhash`, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = %s AND score < %s AND (action = %s OR "all" = %s)', (rcpt, max_score, category, category)) + print("%s: %d of %d messages qualify for notification" % (rcpt, len(meta_query), msg_count)) + if len(meta_query) == 0: + return + msg_count = len(meta_query) + env = SandboxedEnvironment() + if r.get('Q_HTML'): + try: + template = env.from_string(r.get('Q_HTML')) + except Exception: + print("Error: Cannot parse quarantine template, falling back to default template.") + with open('/templates/quarantine.tpl') as file_: + template = env.from_string(file_.read()) + else: + with open('/templates/quarantine.tpl') as file_: + template = env.from_string(file_.read()) + try: + html = template.render(meta=meta_query, username=rcpt, counter=msg_count, hostname=mailcow_hostname, quarantine_acl=quarantine_acl) + except (jinja2.exceptions.SecurityError, TemplateError) as ex: + print(f"SecurityError or TemplateError in template rendering: {ex}") + return + text = html2text.html2text(html) + count = 0 + while count < 15: + count += 1 + try: + server = smtplib.SMTP('postfix', 590, 'quarantine') + server.ehlo() + msg = MIMEMultipart('alternative') + msg_from = r.get('Q_SENDER') or "quarantine@localhost" + # Remove non-ascii chars from field + msg['From'] = ''.join([i if ord(i) < 128 else '' for i in msg_from]) + msg['Subject'] = r.get('Q_SUBJ') or "Spam Quarantine Notification" + msg['Date'] = formatdate(localtime = True) + text_part = MIMEText(text, 'plain', 'utf-8') + html_part = MIMEText(html, 'html', 'utf-8') + msg.attach(text_part) + msg.attach(html_part) + msg['To'] = str(rcpt) + bcc = r.get('Q_BCC') or "" + redirect = r.get('Q_REDIRECT') or "" + text = msg.as_string() + if bcc == '': + if redirect == '': + server.sendmail(msg['From'], str(rcpt), text) + else: + server.sendmail(msg['From'], str(redirect), text) + else: + if redirect == '': + server.sendmail(msg['From'], [str(rcpt)] + [str(bcc)], text) + else: + server.sendmail(msg['From'], [str(redirect)] + [str(bcc)], text) + server.quit() + for res in meta_query: + query_mysql('UPDATE quarantine SET notified = 1 WHERE id = %s', (res['id'],), update = True) + r.hset('Q_LAST_NOTIFIED', record['rcpt'], time_now) + break + except Exception as ex: + server.quit() + print('%s' % (ex)) + time.sleep(3) + + records = query_mysql('SELECT IFNULL(user_acl.quarantine, 0) AS quarantine_acl, count(id) AS counter, rcpt FROM quarantine LEFT OUTER JOIN user_acl ON user_acl.username = rcpt WHERE notified = 0 AND score < %s AND rcpt in (SELECT username FROM mailbox) GROUP BY rcpt', (max_score,)) + + for record in records: + attrs = '' + attrs_json = '' + time_trans = { + "hourly": 3600, + "daily": 86400, + "weekly": 604800 + } + try: + last_notification = int(r.hget('Q_LAST_NOTIFIED', record['rcpt'])) + if last_notification > time_now: + print('Last notification is > time now, assuming never') + last_notification = 0 + except Exception as ex: + print('Could not determine last notification for %s, assuming never' % (record['rcpt'])) + last_notification = 0 + attrs_json = query_mysql('SELECT attributes FROM mailbox WHERE username = %s', (record['rcpt'],)) + attrs = attrs_json[0]['attributes'] + if isinstance(attrs, str): + # if attr is str then just load it + attrs = json.loads(attrs) + else: + # if it's bytes then decode and load it + attrs = json.loads(attrs.decode('utf-8')) + if attrs['quarantine_notification'] not in ('hourly', 'daily', 'weekly'): + continue + if last_notification == 0 or (last_notification + time_trans[attrs['quarantine_notification']]) <= time_now: + print("Notifying %s: Considering %d new items in quarantine (policy: %s)" % (record['rcpt'], record['counter'], attrs['quarantine_notification'])) + notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl'], attrs['quarantine_category']) + +finally: + os.unlink(pidfile) diff --git a/data/Dockerfiles/dovecot/quota_notify.py b/data/Dockerfiles/dovecot/quota_notify.py new file mode 100755 index 0000000000..99be7cc68e --- /dev/null +++ b/data/Dockerfiles/dovecot/quota_notify.py @@ -0,0 +1,102 @@ +#!/usr/bin/python3 + +import smtplib +import os +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.utils import COMMASPACE, formatdate +import jinja2 +from jinja2.sandbox import SandboxedEnvironment +import redis +import time +import json +import sys +import html2text +from subprocess import Popen, PIPE, STDOUT + +if len(sys.argv) > 2: + percent = int(sys.argv[1]) + username = str(sys.argv[2]) +else: + print("Args missing") + sys.exit(1) + +while True: + try: + r = redis.StrictRedis(host='redis', decode_responses=True, port=6379, db=0, username='quota_notify', password='') + r.ping() + except Exception as ex: + print('%s - trying again...' % (ex)) + time.sleep(3) + else: + break + +if r.get('QW_HTML'): + try: + env = SandboxedEnvironment() + template = env.from_string(r.get('QW_HTML')) + except Exception: + print("Error: Cannot parse quota template, falling back to default template.") + with open('/templates/quota.tpl') as file_: + env = SandboxedEnvironment() + template = env.from_string(file_.read()) +else: + with open('/templates/quota.tpl') as file_: + env = SandboxedEnvironment() + template = env.from_string(file_.read()) + +try: + html = template.render(username=username, percent=percent) +except (jinja2.exceptions.SecurityError, jinja2.TemplateError) as ex: + print(f"SecurityError or TemplateError in template rendering: {ex}") + sys.exit(1) + +text = html2text.html2text(html) + +try: + msg = MIMEMultipart('alternative') + msg['From'] = r.get('QW_SENDER') or "quota-warning@localhost" + msg['Subject'] = r.get('QW_SUBJ') or "Quota warning" + msg['Date'] = formatdate(localtime = True) + text_part = MIMEText(text, 'plain', 'utf-8') + html_part = MIMEText(html, 'html', 'utf-8') + msg.attach(text_part) + msg.attach(html_part) + msg['To'] = username + p = Popen(['/usr/libexec/dovecot/dovecot-lda', '-d', username, '-o', '"plugin/quota=maildir:User quota:noenforcing"'], stdout=PIPE, stdin=PIPE, stderr=STDOUT) + p.communicate(input=bytes(msg.as_string(), 'utf-8')) + + domain = username.split("@")[-1] + if domain and r.hget('QW_BCC', domain): + bcc_data = json.loads(r.hget('QW_BCC', domain)) + bcc_rcpts = bcc_data['bcc_rcpts'] + if bcc_data['active'] == 1: + for rcpt in bcc_rcpts: + msg = MIMEMultipart('alternative') + msg['From'] = username + subject = r.get('QW_SUBJ') or "Quota warning" + msg['Subject'] = subject + ' (' + username + ')' + msg['Date'] = formatdate(localtime = True) + text_part = MIMEText(text, 'plain', 'utf-8') + html_part = MIMEText(html, 'html', 'utf-8') + msg.attach(text_part) + msg.attach(html_part) + msg['To'] = rcpt + server = smtplib.SMTP('postfix', 588, 'quotanotification') + server.ehlo() + server.sendmail(msg['From'], str(rcpt), msg.as_string()) + server.quit() + +except Exception as ex: + print('Failed to send quota notification: %s' % (ex)) + sys.exit(1) + +try: + sys.stdout.close() +except: + pass + +try: + sys.stderr.close() +except: + pass diff --git a/data/Dockerfiles/dovecot/repl_health.sh b/data/Dockerfiles/dovecot/repl_health.sh new file mode 100755 index 0000000000..2d7674bdc9 --- /dev/null +++ b/data/Dockerfiles/dovecot/repl_health.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +source /source_env.sh + +# Do not attempt to write to slave +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning" +else + REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning" +fi + +# Is replication active? +# grep on file is less expensive than doveconf +if [ -n ${MAILCOW_REPLICA_IP} ]; then + ${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null + exit +fi + +FAILED_SYNCS=$(doveadm replicator status | grep "Waiting 'failed' requests" | grep -oE '[0-9]+') + +# Set amount of failed jobs as DOVECOT_REPL_HEALTH +# 1 failed job for mailcow.local is expected and healthy +if [[ "${FAILED_SYNCS}" != 0 ]] && [[ "${FAILED_SYNCS}" != 1 ]]; then + printf "Dovecot replicator has %d failed jobs\n" "${FAILED_SYNCS}" + ${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH "${FAILED_SYNCS}" > /dev/null +else + ${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null +fi diff --git a/data/Dockerfiles/dovecot/rspamd-pipe-ham b/data/Dockerfiles/dovecot/rspamd-pipe-ham index 9b26817cda..b9a84f1bff 100755 --- a/data/Dockerfiles/dovecot/rspamd-pipe-ham +++ b/data/Dockerfiles/dovecot/rspamd-pipe-ham @@ -3,7 +3,8 @@ FILE=/tmp/mail$$ cat > $FILE trap "/bin/rm -f $FILE" 0 1 2 3 13 15 -cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnham -cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd +cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzydel +cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/learnham +cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzyadd exit 0 diff --git a/data/Dockerfiles/dovecot/rspamd-pipe-spam b/data/Dockerfiles/dovecot/rspamd-pipe-spam index d06aa91964..3f02c4872f 100755 --- a/data/Dockerfiles/dovecot/rspamd-pipe-spam +++ b/data/Dockerfiles/dovecot/rspamd-pipe-spam @@ -3,7 +3,8 @@ FILE=/tmp/mail$$ cat > $FILE trap "/bin/rm -f $FILE" 0 1 2 3 13 15 -cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnspam -cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd +cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzydel +cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/learnspam +cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzyadd exit 0 diff --git a/data/Dockerfiles/dovecot/sa-rules.sh b/data/Dockerfiles/dovecot/sa-rules.sh index 0cea240c17..e948d438ce 100755 --- a/data/Dockerfiles/dovecot/sa-rules.sh +++ b/data/Dockerfiles/dovecot/sa-rules.sh @@ -1,25 +1,37 @@ #!/bin/bash + +# Create temp directories [[ ! -d /tmp/sa-rules-heinlein ]] && mkdir -p /tmp/sa-rules-heinlein -if [[ ! -f /etc/rspamd/custom/sa-rules-heinlein ]]; then + +# Hash current SA rules +if [[ ! -f /etc/rspamd/custom/sa-rules ]]; then HASH_SA_RULES=0 else - HASH_SA_RULES=$(cat /etc/rspamd/custom/sa-rules-heinlein | md5sum | cut -d' ' -f1) + HASH_SA_RULES=$(cat /etc/rspamd/custom/sa-rules | md5sum | cut -d' ' -f1) +fi + +# Deploy +if curl --connect-timeout 15 --retry 5 --max-time 30 https://www.spamassassin.heinlein-support.de/$(dig txt 1.4.3.spamassassin.heinlein-support.de +short | tr -d '"' | tr -dc '0-9').tar.gz --output /tmp/sa-rules-heinlein.tar.gz; then + if gzip -t /tmp/sa-rules-heinlein.tar.gz; then + tar xfvz /tmp/sa-rules-heinlein.tar.gz -C /tmp/sa-rules-heinlein + cat /tmp/sa-rules-heinlein/*cf > /etc/rspamd/custom/sa-rules + fi +else + echo "Failed to download SA rules. Exiting." + exit 0 # Must be 0 otherwise dovecot would not start at all fi -curl --connect-timeout 15 --max-time 30 http://www.spamassassin.heinlein-support.de/$(dig txt 1.4.3.spamassassin.heinlein-support.de +short | tr -d '"').tar.gz --output /tmp/sa-rules.tar.gz -if [[ -f /tmp/sa-rules.tar.gz ]]; then - tar xfvz /tmp/sa-rules.tar.gz -C /tmp/sa-rules-heinlein - # create complete list of rules in a single file - cat /tmp/sa-rules-heinlein/*cf > /etc/rspamd/custom/sa-rules-heinlein - # Only restart rspamd-mailcow when rules changed - if [[ $(cat /etc/rspamd/custom/sa-rules-heinlein | md5sum | cut -d' ' -f1) != ${HASH_SA_RULES} ]]; then - CONTAINER_NAME=rspamd-mailcow - CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | \ - jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | \ - jq -rc "select( .name | tostring | contains(\"${CONTAINER_NAME}\")) | .id") - if [[ ! -z ${CONTAINER_ID} ]]; then - curl --silent --insecure -XPOST --connect-timeout 15 --max-time 120 https://dockerapi/containers/${CONTAINER_ID}/restart - fi +sed -i -e 's/\([^\\]\)\$\([^\/]\)/\1\\$\2/g' /etc/rspamd/custom/sa-rules + +if [[ "$(cat /etc/rspamd/custom/sa-rules | md5sum | cut -d' ' -f1)" != "${HASH_SA_RULES}" ]]; then + CONTAINER_NAME=rspamd-mailcow + CONTAINER_ID=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | \ + jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | \ + jq -rc "select( .name | tostring | contains(\"${CONTAINER_NAME}\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id") + if [[ ! -z ${CONTAINER_ID} ]]; then + curl --silent --insecure -XPOST --connect-timeout 15 --max-time 120 https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/restart fi fi -rm -rf /tmp/sa-rules-heinlein /tmp/sa-rules.tar.gz + +# Cleanup +rm -rf /tmp/sa-rules-heinlein /tmp/sa-rules-heinlein.tar.gz diff --git a/data/Dockerfiles/dovecot/stop-supervisor.sh b/data/Dockerfiles/dovecot/stop-supervisor.sh new file mode 100755 index 0000000000..5394490ceb --- /dev/null +++ b/data/Dockerfiles/dovecot/stop-supervisor.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +printf "READY\n"; + +while read line; do + echo "Processing Event: $line" >&2; + kill -3 $(cat "/var/run/supervisord.pid") +done < /dev/stdin diff --git a/data/Dockerfiles/dovecot/supervisord.conf b/data/Dockerfiles/dovecot/supervisord.conf index f517c3884b..5b00500024 100644 --- a/data/Dockerfiles/dovecot/supervisord.conf +++ b/data/Dockerfiles/dovecot/supervisord.conf @@ -1,6 +1,7 @@ [supervisord] nodaemon=true user=root +pidfile=/var/run/supervisord.pid [program:syslog-ng] command=/usr/sbin/syslog-ng --foreground --no-caps @@ -11,9 +12,13 @@ stderr_logfile_maxbytes=0 autostart=true [program:dovecot] -command=/usr/local/sbin/dovecot -F +command=/usr/sbin/dovecot -F +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 autorestart=true -[program:cron] -command=/usr/sbin/cron -f -autorestart=true +[eventlistener:processes] +command=/usr/local/sbin/stop-supervisor.sh +events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL diff --git a/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf b/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf new file mode 100644 index 0000000000..c028bcdbf0 --- /dev/null +++ b/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf @@ -0,0 +1,53 @@ +@version: 4.5 +@include "scl.conf" +options { + chain_hostnames(off); + flush_lines(0); + use_dns(no); + use_fqdn(no); + owner("root"); group("adm"); perm(0640); + stats(freq(0)); + keep_timestamp(no); + bad_hostname("^gconfd$"); +}; +source s_dgram { + unix-dgram("/dev/log"); + internal(); +}; +destination d_stdout { pipe("/dev/stdout"); }; +destination d_redis_ui_log { + redis( + host("`REDIS_SLAVEOF_IP`") + persist-name("redis1") + port(`REDIS_SLAVEOF_PORT`) + auth("`REDISPASS`") + command("LPUSH" "DOVECOT_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") + ); +}; +destination d_redis_f2b_channel { + redis( + host("`REDIS_SLAVEOF_IP`") + persist-name("redis2") + port(`REDIS_SLAVEOF_PORT`) + auth("`REDISPASS`") + command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") + ); +}; +filter f_mail { facility(mail); }; +filter f_replica { + not match("User has no mail_replica in userdb" value("MESSAGE")); + not match("Error: sync: Unknown user in remote" value("MESSAGE")); +}; +filter f_dovecot_auth_try { + not match("- trying the next passdb" value("MESSAGE")) and + not match("- trying the next userdb" value("MESSAGE")); +}; +log { + source(s_dgram); + filter(f_dovecot_auth_try); + filter(f_replica); + destination(d_stdout); + filter(f_mail); + destination(d_redis_ui_log); + destination(d_redis_f2b_channel); +}; diff --git a/data/Dockerfiles/dovecot/syslog-ng.conf b/data/Dockerfiles/dovecot/syslog-ng.conf index d788d3e09c..1918f4a23d 100644 --- a/data/Dockerfiles/dovecot/syslog-ng.conf +++ b/data/Dockerfiles/dovecot/syslog-ng.conf @@ -1,4 +1,4 @@ -@version: 3.8 +@version: 4.5 @include "scl.conf" options { chain_hostnames(off); @@ -6,11 +6,12 @@ options { use_dns(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); - stats_freq(0); + stats(freq(0)); + keep_timestamp(no); bad_hostname("^gconfd$"); }; -source s_src { - unix-stream("/dev/log"); +source s_dgram { + unix-dgram("/dev/log"); internal(); }; destination d_stdout { pipe("/dev/stdout"); }; @@ -19,6 +20,7 @@ destination d_redis_ui_log { host("redis-mailcow") persist-name("redis1") port(6379) + auth("`REDISPASS`") command("LPUSH" "DOVECOT_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") ); }; @@ -27,14 +29,23 @@ destination d_redis_f2b_channel { host("redis-mailcow") persist-name("redis2") port(6379) - command("PUBLISH" "F2B_CHANNEL" "$MESSAGE") + auth("`REDISPASS`") + command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; filter f_mail { facility(mail); }; -filter f_not_watchdog { not message("172\.22\.1\.248"); }; +filter f_replica { + not match("User has no mail_replica in userdb" value("MESSAGE")); + not match("Error: sync: Unknown user in remote" value("MESSAGE")); +}; +filter f_dovecot_auth_try { + not match("- trying the next passdb" value("MESSAGE")) and + not match("- trying the next userdb" value("MESSAGE")); +}; log { - source(s_src); - filter(f_not_watchdog); + source(s_dgram); + filter(f_dovecot_auth_try); + filter(f_replica); destination(d_stdout); filter(f_mail); destination(d_redis_ui_log); diff --git a/data/Dockerfiles/dovecot/trim_logs.sh b/data/Dockerfiles/dovecot/trim_logs.sh index 8489f27ae1..3b5e052782 100755 --- a/data/Dockerfiles/dovecot/trim_logs.sh +++ b/data/Dockerfiles/dovecot/trim_logs.sh @@ -1,7 +1,26 @@ #!/bin/bash -/usr/bin/redis-cli -h redis LTRIM ACME_LOG 0 LOG_LINES -/usr/bin/redis-cli -h redis LTRIM POSTFIX_MAILLOG 0 LOG_LINES -/usr/bin/redis-cli -h redis LTRIM DOVECOT_MAILLOG 0 LOG_LINES -/usr/bin/redis-cli -h redis LTRIM SOGO_LOG 0 LOG_LINES -/usr/bin/redis-cli -h redis LTRIM NETFILTER_LOG 0 LOG_LINES -/usr/bin/redis-cli -h redis LTRIM AUTODISCOVER_LOG 0 LOG_LINES +catch_non_zero() { + CMD=${1} + ${CMD} > /dev/null + EC=$? + if [ ${EC} -ne 0 ]; then + echo "Command ${CMD} failed to execute, exit code was ${EC}" + fi +} +source /source_env.sh +# Do not attempt to write to slave +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning" +else + REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning" +fi +catch_non_zero "${REDIS_CMDLINE} LTRIM ACME_LOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM POSTFIX_MAILLOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM DOVECOT_MAILLOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM SOGO_LOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM NETFILTER_LOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM AUTODISCOVER_LOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM API_LOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM RL_LOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM WATCHDOG_LOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM CRON_LOG 0 ${LOG_LINES}" diff --git a/data/Dockerfiles/netfilter/Dockerfile b/data/Dockerfiles/netfilter/Dockerfile index aab9b1d3ef..70cd49c1a1 100644 --- a/data/Dockerfiles/netfilter/Dockerfile +++ b/data/Dockerfiles/netfilter/Dockerfile @@ -1,13 +1,43 @@ -FROM alpine:3.8 -LABEL maintainer "Andre Peters " +FROM alpine:3.23 +LABEL maintainer = "The Infrastructure Company GmbH " + +WORKDIR /app + +ARG PIP_BREAK_SYSTEM_PACKAGES=1 ENV XTABLES_LIBDIR /usr/lib/xtables ENV PYTHON_IPTABLES_XTABLES_VERSION 12 ENV IPTABLES_LIBDIR /usr/lib -RUN apk add -U python2 python-dev py-pip gcc musl-dev iptables ip6tables tzdata \ - && pip2 install --upgrade python-iptables==0.13.0 redis ipaddress \ - && apk del python-dev py2-pip gcc +RUN apk add --virtual .build-deps \ + gcc \ + python3-dev \ + libffi-dev \ + openssl-dev \ +&& apk add -U python3 \ + iptables \ + iptables-dev \ + ip6tables \ + xtables-addons \ + nftables \ + tzdata \ + py3-pip \ + py3-nftables \ + musl-dev \ +&& pip3 install --ignore-installed --upgrade pip \ + jsonschema \ + python-iptables \ + redis \ + ipaddress \ + dnspython \ +&& apk del .build-deps + +# && pip3 install --upgrade pip python-iptables==0.13.0 redis ipaddress dnspython \ + +COPY modules /app/modules +COPY main.py /app/ +COPY ./docker-entrypoint.sh /app/ + +RUN chmod +x /app/docker-entrypoint.sh -COPY server.py / -CMD ["python2", "-u", "/server.py"] +CMD ["/bin/sh", "-c", "/app/docker-entrypoint.sh"] diff --git a/data/Dockerfiles/netfilter/docker-entrypoint.sh b/data/Dockerfiles/netfilter/docker-entrypoint.sh new file mode 100755 index 0000000000..98cab0a728 --- /dev/null +++ b/data/Dockerfiles/netfilter/docker-entrypoint.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +backend=nftables + +nft list table ip filter &>/dev/null +nftables_found=$? + +iptables -L -n &>/dev/null +iptables_found=$? + +if [ $nftables_found -lt $iptables_found ]; then + backend=nftables +fi + +if [ $nftables_found -gt $iptables_found ]; then + backend=iptables +fi + +if [ $nftables_found -eq 0 ] && [ $nftables_found -eq $iptables_found ]; then + nftables_lines=$(nft list ruleset | wc -l) + iptables_lines=$(iptables-save | wc -l) + if [ $nftables_lines -gt $iptables_lines ]; then + backend=nftables + else + backend=iptables + fi +fi + +exec python -u /app/main.py $backend diff --git a/data/Dockerfiles/netfilter/main.py b/data/Dockerfiles/netfilter/main.py new file mode 100644 index 0000000000..5b718a94b7 --- /dev/null +++ b/data/Dockerfiles/netfilter/main.py @@ -0,0 +1,548 @@ +#!/usr/bin/env python3 + +DEBUG = False + +import re +import os +import sys +import time +import atexit +import signal +import ipaddress +from collections import Counter +from random import randint +from threading import Thread +from threading import Lock +import redis +import json +import dns.resolver +import dns.exception +import uuid +from modules.Logger import Logger +from modules.IPTables import IPTables +from modules.NFTables import NFTables + +def logdebug(msg): + if DEBUG: + logger.logInfo("DEBUG: %s" % msg) + +# Globals +WHITELIST = [] +BLACKLIST = [] +bans = {} +quit_now = False +exit_code = 0 +lock = Lock() +chain_name = "MAILCOW" +r = None +pubsub = None +clear_before_quit = False + +def refreshF2boptions(): + global f2boptions + global quit_now + global exit_code + f2boptions = {} + + if not r.get('F2B_OPTIONS'): + f2boptions['ban_time'] = r.get('F2B_BAN_TIME') + f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME') + f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT') + f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') + f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') + f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') + f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') + else: + try: + f2boptions = json.loads(r.get('F2B_OPTIONS')) + except ValueError as e: + logger.logCrit( + 'Error loading F2B options: F2B_OPTIONS is not json. Exception: %s' % e) + quit_now = True + exit_code = 2 + + verifyF2boptions(f2boptions) + r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False)) + +def verifyF2boptions(f2boptions): + verifyF2boption(f2boptions, 'ban_time', 1800) + verifyF2boption(f2boptions, 'max_ban_time', 10000) + verifyF2boption(f2boptions, 'ban_time_increment', True) + verifyF2boption(f2boptions, 'max_attempts', 10) + verifyF2boption(f2boptions, 'retry_window', 600) + verifyF2boption(f2boptions, 'netban_ipv4', 32) + verifyF2boption(f2boptions, 'netban_ipv6', 128) + verifyF2boption(f2boptions, 'banlist_id', str(uuid.uuid4())) + verifyF2boption(f2boptions, 'manage_external', 0) + +def verifyF2boption(f2boptions, f2boption, f2bdefault): + f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault + +def refreshF2bregex(): + global f2bregex + global quit_now + global exit_code + if not r.get('F2B_REGEX'): + f2bregex = {} + f2bregex[1] = r'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)' + f2bregex[2] = r'Rspamd UI: Invalid password by ([0-9a-f\.:]+)' + f2bregex[3] = r'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed: (?!.*Connection lost to authentication server).+' + f2bregex[4] = r'warning: non-SMTP command from .*\[([0-9a-f\.:]+)]:.+' + f2bregex[5] = r'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+' + f2bregex[6] = r'\w+\([^,]+,([0-9a-f\.:]+),<[^>]+>\): Password mismatch \(SHA1 of given password: [a-f0-9]+\)' + f2bregex[7] = r'\w+\([^,]+,([0-9a-f\.:]+),<[^>]+>\): unknown user \(SHA1 of given password: [a-f0-9]+\)' + f2bregex[8] = r'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked' + f2bregex[9] = r'([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+' + r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False)) + else: + try: + f2bregex = {} + f2bregex = json.loads(r.get('F2B_REGEX')) + except ValueError: + logger.logCrit('Error loading F2B options: F2B_REGEX is not json') + quit_now = True + exit_code = 2 + +def get_ip(address): + ip = ipaddress.ip_address(address) + if type(ip) is ipaddress.IPv6Address and ip.ipv4_mapped: + ip = ip.ipv4_mapped + if ip.is_private or ip.is_loopback: + return False + + return ip + +def ban(address): + global f2boptions + global lock + logdebug("ban() called with address=%s" % address) + refreshF2boptions() + MAX_ATTEMPTS = int(f2boptions['max_attempts']) + RETRY_WINDOW = int(f2boptions['retry_window']) + NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4']) + NETBAN_IPV6 = '/' + str(f2boptions['netban_ipv6']) + + ip = get_ip(address) + if not ip: + logdebug("No valid IP -- skipping ban()") + return + address = str(ip) + self_network = ipaddress.ip_network(address) + + with lock: + temp_whitelist = set(WHITELIST) + logdebug("Checking if %s overlaps with any WHITELIST entries" % self_network) + if temp_whitelist: + for wl_key in temp_whitelist: + wl_net = ipaddress.ip_network(wl_key, False) + logdebug("Checking overlap between %s and %s" % (self_network, wl_net)) + if wl_net.overlaps(self_network): + logger.logInfo( + 'Address %s is allowlisted by rule %s' % (self_network, wl_net)) + return + + net = ipaddress.ip_network( + (address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False) + net = str(net) + logdebug("Ban net: %s" % net) + + if not net in bans: + bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0} + logdebug("Initing new ban counter for %s" % net) + + current_attempt = time.time() + logdebug("Current attempt ts=%s, previous: %s, retry_window: %s" % + (current_attempt, bans[net]['last_attempt'], RETRY_WINDOW)) + if current_attempt - bans[net]['last_attempt'] > RETRY_WINDOW: + bans[net]['attempts'] = 0 + logdebug("Ban counter for %s reset as window expired" % net) + + bans[net]['attempts'] += 1 + bans[net]['last_attempt'] = current_attempt + logdebug("%s attempts now %d" % (net, bans[net]['attempts'])) + + if bans[net]['attempts'] >= MAX_ATTEMPTS: + cur_time = int(round(time.time())) + NET_BAN_TIME = calcNetBanTime(bans[net]['ban_counter']) + logger.logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 )) + if type(ip) is ipaddress.IPv4Address and int(f2boptions['manage_external']) != 1: + with lock: + logdebug("Calling tables.banIPv4(%s)" % net) + tables.banIPv4(net) + elif int(f2boptions['manage_external']) != 1: + with lock: + logdebug("Calling tables.banIPv6(%s)" % net) + tables.banIPv6(net) + + logdebug("Updating F2B_ACTIVE_BANS[%s]=%d" % + (net, cur_time + NET_BAN_TIME)) + r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME) + else: + logger.logWarn('%d more attempts in the next %d seconds until %s is banned' % ( + MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)) + +def unban(net): + global lock + logdebug("Calling unban() with net=%s" % net) + if not net in bans: + logger.logInfo( + '%s is not banned, skipping unban and deleting from queue (if any)' % net) + r.hdel('F2B_QUEUE_UNBAN', '%s' % net) + return + logger.logInfo('Unbanning %s' % net) + if type(ipaddress.ip_network(net)) is ipaddress.IPv4Network: + with lock: + logdebug("Calling tables.unbanIPv4(%s)" % net) + tables.unbanIPv4(net) + else: + with lock: + logdebug("Calling tables.unbanIPv6(%s)" % net) + tables.unbanIPv6(net) + r.hdel('F2B_ACTIVE_BANS', '%s' % net) + r.hdel('F2B_QUEUE_UNBAN', '%s' % net) + if net in bans: + logdebug("Unban for %s, setting attempts=0, ban_counter+=1" % net) + bans[net]['attempts'] = 0 + bans[net]['ban_counter'] += 1 + +def permBan(net, unban=False): + global f2boptions + global lock + + is_unbanned = False + is_banned = False + if type(ipaddress.ip_network(net, strict=False)) is ipaddress.IPv4Network: + with lock: + if unban: + is_unbanned = tables.unbanIPv4(net) + elif int(f2boptions['manage_external']) != 1: + is_banned = tables.banIPv4(net) + else: + with lock: + if unban: + is_unbanned = tables.unbanIPv6(net) + elif int(f2boptions['manage_external']) != 1: + is_banned = tables.banIPv6(net) + + + if is_unbanned: + r.hdel('F2B_PERM_BANS', '%s' % net) + logger.logCrit('Removed host/network %s from denylist' % net) + elif is_banned: + r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time()))) + logger.logCrit('Added host/network %s to denylist' % net) + +def clear(): + global lock + logger.logInfo('Clearing all bans') + for net in bans.copy(): + logdebug("Unbanning net: %s" % net) + unban(net) + with lock: + logdebug("Clearing IPv4/IPv6 table") + tables.clearIPv4Table() + tables.clearIPv6Table() + try: + if r is not None: + r.delete('F2B_ACTIVE_BANS') + r.delete('F2B_PERM_BANS') + except Exception as ex: + logger.logWarn('Error clearing redis keys F2B_ACTIVE_BANS and F2B_PERM_BANS: %s' % ex) + +def watch(): + global pubsub + global quit_now + global exit_code + + logger.logInfo('Watching Redis channel F2B_CHANNEL') + pubsub.subscribe('F2B_CHANNEL') + + while not quit_now: + try: + for item in pubsub.listen(): + refreshF2bregex() + for rule_id, rule_regex in f2bregex.items(): + if item['data'] and item['type'] == 'message': + try: + result = re.search(rule_regex, item['data']) + except re.error: + result = False + if result: + addr = result.group(1) + ip = ipaddress.ip_address(addr) + if ip.is_private or ip.is_loopback: + continue + logger.logWarn('%s matched rule id %s (%s)' % (addr, rule_id, item['data'])) + ban(addr) + except Exception as ex: + logger.logWarn('Error reading log line from pubsub: %s' % ex) + pubsub = None + quit_now = True + exit_code = 2 + +def snat4(snat_target): + global lock + global quit_now + + while not quit_now: + time.sleep(10) + with lock: + tables.snat4(snat_target, os.getenv('IPV4_NETWORK', '172.22.1') + '.0/24') + +def snat6(snat_target): + global lock + global quit_now + + while not quit_now: + time.sleep(10) + with lock: + tables.snat6(snat_target, os.getenv('IPV6_NETWORK', 'fd4d:6169:6c63:6f77::/64')) + +def autopurge(): + global f2boptions + logdebug("autopurge thread started") + while not quit_now: + logdebug("autopurge tick") + time.sleep(10) + refreshF2boptions() + MAX_ATTEMPTS = int(f2boptions['max_attempts']) + QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN') + logdebug("QUEUE_UNBAN: %s" % QUEUE_UNBAN) + if QUEUE_UNBAN: + for net in QUEUE_UNBAN: + logdebug("Autopurge: unbanning queued net: %s" % net) + unban(str(net)) + # Only check expiry for actively banned IPs: + active_bans = r.hgetall('F2B_ACTIVE_BANS') + now = time.time() + for net_str, expire_str in active_bans.items(): + logdebug("Checking ban expiry for (actively banned): %s" % net_str) + # Defensive: always process if timer missing or expired + try: + expire = float(expire_str) + except Exception: + logdebug("Invalid expire time for %s; unbanning" % net_str) + unban(net_str) + continue + time_left = expire - now + logdebug("Time left for %s: %.1f seconds" % (net_str, time_left)) + if time_left <= 0: + logdebug("Ban expired for %s" % net_str) + unban(net_str) + +def mailcowChainOrder(): + global lock + global quit_now + global exit_code + while not quit_now: + time.sleep(10) + with lock: + quit_now, exit_code = tables.checkIPv4ChainOrder() + if quit_now: return + quit_now, exit_code = tables.checkIPv6ChainOrder() + +def calcNetBanTime(ban_counter): + global f2boptions + + BAN_TIME = int(f2boptions['ban_time']) + MAX_BAN_TIME = int(f2boptions['max_ban_time']) + BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment']) + NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** ban_counter + NET_BAN_TIME = max([BAN_TIME, min([NET_BAN_TIME, MAX_BAN_TIME])]) + return NET_BAN_TIME + +def isIpNetwork(address): + try: + ipaddress.ip_network(address, False) + except ValueError: + return False + return True + +def genNetworkList(list): + resolver = dns.resolver.Resolver() + hostnames = [] + networks = [] + for key in list: + if isIpNetwork(key): + networks.append(key) + else: + hostnames.append(key) + for hostname in hostnames: + hostname_ips = [] + for rdtype in ['A', 'AAAA']: + try: + answer = resolver.resolve(qname=hostname, rdtype=rdtype, lifetime=3) + except dns.exception.Timeout: + logger.logInfo('Hostname %s timedout on resolve' % hostname) + break + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + continue + except dns.exception.DNSException as dnsexception: + logger.logInfo('%s' % dnsexception) + continue + for rdata in answer: + hostname_ips.append(rdata.to_text()) + networks.extend(hostname_ips) + return set(networks) + +def whitelistUpdate(): + global lock + global quit_now + global WHITELIST + while not quit_now: + start_time = time.time() + list = r.hgetall('F2B_WHITELIST') + new_whitelist = [] + if list: + new_whitelist = genNetworkList(list) + with lock: + if Counter(new_whitelist) != Counter(WHITELIST): + WHITELIST = new_whitelist + logger.logInfo('Allowlist was changed, it has %s entries' % len(WHITELIST)) + time.sleep(60.0 - ((time.time() - start_time) % 60.0)) + +def blacklistUpdate(): + global quit_now + global BLACKLIST + while not quit_now: + start_time = time.time() + list = r.hgetall('F2B_BLACKLIST') + new_blacklist = [] + if list: + new_blacklist = genNetworkList(list) + if Counter(new_blacklist) != Counter(BLACKLIST): + addban = set(new_blacklist).difference(BLACKLIST) + delban = set(BLACKLIST).difference(new_blacklist) + BLACKLIST = new_blacklist + logger.logInfo('Denylist was changed, it has %s entries' % len(BLACKLIST)) + if addban: + for net in addban: + permBan(net=net) + if delban: + for net in delban: + permBan(net=net, unban=True) + time.sleep(60.0 - ((time.time() - start_time) % 60.0)) + +def sigterm_quit(signum, frame): + global clear_before_quit + logdebug("SIGTERM received, setting clear_before_quit to True and exiting") + clear_before_quit = True + sys.exit(exit_code) + +def before_quit(): + logdebug("before_quit called, clear_before_quit=%s" % clear_before_quit) + if clear_before_quit: + clear() + if pubsub is not None: + pubsub.unsubscribe() + +if __name__ == '__main__': + logger = Logger() + logdebug("Sys.argv: %s" % sys.argv) + atexit.register(before_quit) + signal.signal(signal.SIGTERM, sigterm_quit) + + backend = sys.argv[1] + logdebug("Backend: %s" % backend) + if backend == "nftables": + logger.logInfo('Using NFTables backend') + tables = NFTables(chain_name, logger) + else: + logger.logInfo('Using IPTables backend') + logger.logWarn( + "DEPRECATION: iptables-legacy is deprecated and will be removed in future releases. " + "Please switch to nftables on your host to ensure complete compatibility." + ) + time.sleep(5) + tables = IPTables(chain_name, logger) + + clear() + logger.logInfo("Initializing mailcow netfilter chain") + tables.initChainIPv4() + tables.initChainIPv6() + + if os.getenv("DISABLE_NETFILTER_ISOLATION_RULE", "").lower() in ("y", "yes"): + logger.logInfo(f"Skipping {chain_name} isolation") + else: + logger.logInfo(f"Setting {chain_name} isolation") + tables.create_mailcow_isolation_rule("br-mailcow", [3306, 6379, 8983, 12345], os.getenv("MAILCOW_REPLICA_IP")) + + # connect to redis + while True: + try: + redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '') + redis_slaveof_port = os.getenv('REDIS_SLAVEOF_PORT', '') + logdebug( + "Connecting redis (SLAVEOF_IP:%s, PORT:%s)" % (redis_slaveof_ip, redis_slaveof_port)) + if "".__eq__(redis_slaveof_ip): + r = redis.StrictRedis( + host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS']) + else: + r = redis.StrictRedis( + host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0, password=os.environ['REDISPASS']) + r.ping() + pubsub = r.pubsub() + except Exception as ex: + logdebug( + 'Redis connection failed: %s - trying again in 3 seconds' % (ex)) + time.sleep(3) + else: + break + logger.set_redis(r) + logdebug("Redis connection established, setting up F2B keys") + + if r.exists('F2B_LOG'): + logdebug("Renaming F2B_LOG to NETFILTER_LOG") + r.rename('F2B_LOG', 'NETFILTER_LOG') + r.delete('F2B_ACTIVE_BANS') + r.delete('F2B_PERM_BANS') + + refreshF2boptions() + + watch_thread = Thread(target=watch) + watch_thread.daemon = True + watch_thread.start() + + if os.getenv('SNAT_TO_SOURCE') and os.getenv('SNAT_TO_SOURCE') != 'n': + try: + snat_ip = os.getenv('SNAT_TO_SOURCE') + snat_ipo = ipaddress.ip_address(snat_ip) + if type(snat_ipo) is ipaddress.IPv4Address: + snat4_thread = Thread(target=snat4, args=(snat_ip,)) + snat4_thread.daemon = True + snat4_thread.start() + except ValueError: + print(os.getenv('SNAT_TO_SOURCE') + ' is not a valid IPv4 address') + + if os.getenv('SNAT6_TO_SOURCE') and os.getenv('SNAT6_TO_SOURCE') != 'n': + try: + snat_ip = os.getenv('SNAT6_TO_SOURCE') + snat_ipo = ipaddress.ip_address(snat_ip) + if type(snat_ipo) is ipaddress.IPv6Address: + snat6_thread = Thread(target=snat6,args=(snat_ip,)) + snat6_thread.daemon = True + snat6_thread.start() + except ValueError: + print(os.getenv('SNAT6_TO_SOURCE') + ' is not a valid IPv6 address') + + autopurge_thread = Thread(target=autopurge) + autopurge_thread.daemon = True + autopurge_thread.start() + + mailcowchainwatch_thread = Thread(target=mailcowChainOrder) + mailcowchainwatch_thread.daemon = True + mailcowchainwatch_thread.start() + + blacklistupdate_thread = Thread(target=blacklistUpdate) + blacklistupdate_thread.daemon = True + blacklistupdate_thread.start() + + whitelistupdate_thread = Thread(target=whitelistUpdate) + whitelistupdate_thread.daemon = True + whitelistupdate_thread.start() + + while not quit_now: + time.sleep(0.5) + + logdebug("Exiting with code %s" % exit_code) + sys.exit(exit_code) \ No newline at end of file diff --git a/data/Dockerfiles/netfilter/modules/IPTables.py b/data/Dockerfiles/netfilter/modules/IPTables.py new file mode 100644 index 0000000000..3d3d43974b --- /dev/null +++ b/data/Dockerfiles/netfilter/modules/IPTables.py @@ -0,0 +1,252 @@ +import iptc +import time +import os + +class IPTables: + def __init__(self, chain_name, logger): + self.chain_name = chain_name + self.logger = logger + + def initChainIPv4(self): + if not iptc.Chain(iptc.Table(iptc.Table.FILTER), self.chain_name) in iptc.Table(iptc.Table.FILTER).chains: + iptc.Table(iptc.Table.FILTER).create_chain(self.chain_name) + for c in ['FORWARD', 'INPUT']: + chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), c) + rule = iptc.Rule() + rule.src = '0.0.0.0/0' + rule.dst = '0.0.0.0/0' + target = iptc.Target(rule, self.chain_name) + rule.target = target + if rule not in chain.rules: + chain.insert_rule(rule) + + def initChainIPv6(self): + if not iptc.Chain(iptc.Table6(iptc.Table6.FILTER), self.chain_name) in iptc.Table6(iptc.Table6.FILTER).chains: + iptc.Table6(iptc.Table6.FILTER).create_chain(self.chain_name) + for c in ['FORWARD', 'INPUT']: + chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), c) + rule = iptc.Rule6() + rule.src = '::/0' + rule.dst = '::/0' + target = iptc.Target(rule, self.chain_name) + rule.target = target + if rule not in chain.rules: + chain.insert_rule(rule) + + def checkIPv4ChainOrder(self): + filter_table = iptc.Table(iptc.Table.FILTER) + filter_table.refresh() + return self.checkChainOrder(filter_table) + + def checkIPv6ChainOrder(self): + filter_table = iptc.Table6(iptc.Table6.FILTER) + filter_table.refresh() + return self.checkChainOrder(filter_table) + + def checkChainOrder(self, filter_table): + err = False + exit_code = None + + forward_chain = iptc.Chain(filter_table, 'FORWARD') + input_chain = iptc.Chain(filter_table, 'INPUT') + for chain in [forward_chain, input_chain]: + target_found = False + for position, item in enumerate(chain.rules): + if item.target.name == self.chain_name: + target_found = True + if position > 2: + self.logger.logCrit('Error in %s chain: %s target not found, restarting container' % (chain.name, self.chain_name)) + err = True + exit_code = 2 + if not target_found: + self.logger.logCrit('Error in %s chain: %s target not found, restarting container' % (chain.name, self.chain_name)) + err = True + exit_code = 2 + + return err, exit_code + + def clearIPv4Table(self): + self.clearTable(iptc.Table(iptc.Table.FILTER)) + + def clearIPv6Table(self): + self.clearTable(iptc.Table6(iptc.Table6.FILTER)) + + def clearTable(self, filter_table): + filter_table.autocommit = False + forward_chain = iptc.Chain(filter_table, "FORWARD") + input_chain = iptc.Chain(filter_table, "INPUT") + mailcow_chain = iptc.Chain(filter_table, self.chain_name) + if mailcow_chain in filter_table.chains: + for rule in mailcow_chain.rules: + mailcow_chain.delete_rule(rule) + for rule in forward_chain.rules: + if rule.target.name == self.chain_name: + forward_chain.delete_rule(rule) + for rule in input_chain.rules: + if rule.target.name == self.chain_name: + input_chain.delete_rule(rule) + filter_table.delete_chain(self.chain_name) + filter_table.commit() + filter_table.refresh() + filter_table.autocommit = True + + def banIPv4(self, source): + chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), self.chain_name) + rule = iptc.Rule() + rule.src = source + target = iptc.Target(rule, "REJECT") + rule.target = target + if rule in chain.rules: + return False + chain.insert_rule(rule) + return True + + def banIPv6(self, source): + chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), self.chain_name) + rule = iptc.Rule6() + rule.src = source + target = iptc.Target(rule, "REJECT") + rule.target = target + if rule in chain.rules: + return False + chain.insert_rule(rule) + return True + + def unbanIPv4(self, source): + chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), self.chain_name) + rule = iptc.Rule() + rule.src = source + target = iptc.Target(rule, "REJECT") + rule.target = target + if rule not in chain.rules: + return False + chain.delete_rule(rule) + return True + + def unbanIPv6(self, source): + chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), self.chain_name) + rule = iptc.Rule6() + rule.src = source + target = iptc.Target(rule, "REJECT") + rule.target = target + if rule not in chain.rules: + return False + chain.delete_rule(rule) + return True + + def snat4(self, snat_target, source): + try: + table = iptc.Table('nat') + table.refresh() + chain = iptc.Chain(table, 'POSTROUTING') + table.autocommit = False + new_rule = self.getSnat4Rule(snat_target, source) + + if not chain.rules: + # if there are no rules in the chain, insert the new rule directly + self.logger.logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}') + chain.insert_rule(new_rule) + else: + for position, rule in enumerate(chain.rules): + if not hasattr(rule.target, 'parameter'): + continue + match = all(( + new_rule.get_src() == rule.get_src(), + new_rule.get_dst() == rule.get_dst(), + new_rule.target.parameters == rule.target.parameters, + new_rule.target.name == rule.target.name + )) + if position == 0: + if not match: + self.logger.logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}') + chain.insert_rule(new_rule) + else: + if match: + self.logger.logInfo(f'Remove rule for source network {new_rule.src} to SNAT target {snat_target} from POSTROUTING chain at position {position}') + chain.delete_rule(rule) + + table.commit() + table.autocommit = True + return True + except: + self.logger.logCrit('Error running SNAT4, retrying...') + return False + + def snat6(self, snat_target, source): + try: + table = iptc.Table6('nat') + table.refresh() + chain = iptc.Chain(table, 'POSTROUTING') + table.autocommit = False + new_rule = self.getSnat6Rule(snat_target, source) + + if new_rule not in chain.rules: + self.logger.logInfo('Added POSTROUTING rule for source network %s to SNAT target %s' % (new_rule.src, snat_target)) + chain.insert_rule(new_rule) + else: + for position, item in enumerate(chain.rules): + if item == new_rule: + if position != 0: + chain.delete_rule(new_rule) + + table.commit() + table.autocommit = True + except: + self.logger.logCrit('Error running SNAT6, retrying...') + + + def getSnat4Rule(self, snat_target, source): + rule = iptc.Rule() + rule.src = source + rule.dst = '!' + rule.src + target = rule.create_target("SNAT") + target.to_source = snat_target + match = rule.create_match("comment") + match.comment = f'{int(round(time.time()))}' + return rule + + def getSnat6Rule(self, snat_target, source): + rule = iptc.Rule6() + rule.src = source + rule.dst = '!' + rule.src + target = rule.create_target("SNAT") + target.to_source = snat_target + return rule + + def create_mailcow_isolation_rule(self, _interface:str, _dports:list, _allow:str = ""): + try: + chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), self.chain_name) + + # insert mailcow isolation rule + rule = iptc.Rule() + rule.in_interface = f'!{_interface}' + rule.out_interface = _interface + rule.protocol = 'tcp' + rule.create_target("DROP") + match = rule.create_match("multiport") + match.dports = ','.join(map(str, _dports)) + + if rule in chain.rules: + chain.delete_rule(rule) + chain.insert_rule(rule, position=0) + + # insert mailcow isolation exception rule + if _allow != "": + rule = iptc.Rule() + rule.src = _allow + rule.in_interface = f'!{_interface}' + rule.out_interface = _interface + rule.protocol = 'tcp' + rule.create_target("ACCEPT") + match = rule.create_match("multiport") + match.dports = ','.join(map(str, _dports)) + + if rule in chain.rules: + chain.delete_rule(rule) + chain.insert_rule(rule, position=0) + + + return True + except Exception as e: + self.logger.logCrit(f"Error adding {self.chain_name} isolation: {e}") + return False \ No newline at end of file diff --git a/data/Dockerfiles/netfilter/modules/Logger.py b/data/Dockerfiles/netfilter/modules/Logger.py new file mode 100644 index 0000000000..b5114539b2 --- /dev/null +++ b/data/Dockerfiles/netfilter/modules/Logger.py @@ -0,0 +1,42 @@ +import time +import json +import datetime + +class Logger: + def __init__(self): + self.r = None + + def set_redis(self, redis): + self.r = redis + + def _format_timestamp(self): + # Local time with milliseconds + return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + def log(self, priority, message): + # build redis-friendly dict + tolog = { + 'time': int(round(time.time())), # keep raw timestamp for Redis + 'priority': priority, + 'message': message + } + + # print human-readable message with timestamp + ts = self._format_timestamp() + print(f"{ts} {priority.upper()}: {message}", flush=True) + + # also push JSON to Redis if connected + if self.r is not None: + try: + self.r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False)) + except Exception as ex: + print(f'{ts} WARN: Failed logging to redis: {ex}', flush=True) + + def logWarn(self, message): + self.log('warn', message) + + def logCrit(self, message): + self.log('crit', message) + + def logInfo(self, message): + self.log('info', message) \ No newline at end of file diff --git a/data/Dockerfiles/netfilter/modules/NFTables.py b/data/Dockerfiles/netfilter/modules/NFTables.py new file mode 100644 index 0000000000..7740fa59f8 --- /dev/null +++ b/data/Dockerfiles/netfilter/modules/NFTables.py @@ -0,0 +1,659 @@ +import nftables +import ipaddress +import os + +class NFTables: + def __init__(self, chain_name, logger): + self.chain_name = chain_name + self.logger = logger + + self.nft = nftables.Nftables() + self.nft.set_json_output(True) + self.nft.set_handle_output(True) + self.nft_chain_names = {'ip': {'filter': {'input': '', 'forward': ''}, 'nat': {'postrouting': ''} }, + 'ip6': {'filter': {'input': '', 'forward': ''}, 'nat': {'postrouting': ''} } } + + self.search_current_chains() + + def initChainIPv4(self): + self.insert_mailcow_chains("ip") + + def initChainIPv6(self): + self.insert_mailcow_chains("ip6") + + def checkIPv4ChainOrder(self): + return self.checkChainOrder("ip") + + def checkIPv6ChainOrder(self): + return self.checkChainOrder("ip6") + + def checkChainOrder(self, filter_table): + err = False + exit_code = None + + for chain in ['input', 'forward']: + chain_position = self.check_mailcow_chains(filter_table, chain) + if chain_position is None: continue + + if chain_position is False: + self.logger.logCrit(f'MAILCOW target not found in {filter_table} {chain} table, restarting container to fix it...') + err = True + exit_code = 2 + + if chain_position > 0: + chain_position += 1 + self.logger.logCrit(f'MAILCOW target is in position {chain_position} in the {filter_table} {chain} table, restarting container to fix it...') + err = True + exit_code = 2 + + return err, exit_code + + def clearIPv4Table(self): + self.clearTable("ip") + + def clearIPv6Table(self): + self.clearTable("ip6") + + def clearTable(self, _family): + is_empty_dict = True + json_command = self.get_base_dict() + chain_handle = self.get_chain_handle(_family, "filter", self.chain_name) + # if no handle, the chain doesn't exists + if chain_handle is not None: + is_empty_dict = False + # flush chain + mailcow_chain = {'family': _family, 'table': 'filter', 'name': self.chain_name} + flush_chain = {'flush': {'chain': mailcow_chain}} + json_command["nftables"].append(flush_chain) + + # remove rule in forward chain + # remove rule in input chain + chains_family = [self.nft_chain_names[_family]['filter']['input'], + self.nft_chain_names[_family]['filter']['forward'] ] + + for chain_base in chains_family: + if not chain_base: continue + + rules_handle = self.get_rules_handle(_family, "filter", chain_base) + if rules_handle is not None: + for r_handle in rules_handle: + is_empty_dict = False + mailcow_rule = {'family':_family, + 'table': 'filter', + 'chain': chain_base, + 'handle': r_handle } + delete_rules = {'delete': {'rule': mailcow_rule} } + json_command["nftables"].append(delete_rules) + + # remove chain + # after delete all rules referencing this chain + if chain_handle is not None: + mc_chain_handle = {'family':_family, + 'table': 'filter', + 'name': self.chain_name, + 'handle': chain_handle } + delete_chain = {'delete': {'chain': mc_chain_handle} } + json_command["nftables"].append(delete_chain) + + if is_empty_dict == False: + if self.nft_exec_dict(json_command): + self.logger.logInfo(f"Clear completed: {_family}") + + def banIPv4(self, source): + ban_dict = self.get_ban_ip_dict(source, "ip") + return self.nft_exec_dict(ban_dict) + + def banIPv6(self, source): + ban_dict = self.get_ban_ip_dict(source, "ip6") + return self.nft_exec_dict(ban_dict) + + def unbanIPv4(self, source): + unban_dict = self.get_unban_ip_dict(source, "ip") + if not unban_dict: + return False + return self.nft_exec_dict(unban_dict) + + def unbanIPv6(self, source): + unban_dict = self.get_unban_ip_dict(source, "ip6") + if not unban_dict: + return False + return self.nft_exec_dict(unban_dict) + + def snat4(self, snat_target, source): + self.snat_rule("ip", snat_target, source) + + def snat6(self, snat_target, source): + self.snat_rule("ip6", snat_target, source) + + + def nft_exec_dict(self, query: dict): + if not query: return False + + rc, output, error = self.nft.json_cmd(query) + if rc != 0: + #self.logger.logCrit(f"Nftables Error: {error}") + return False + + # Prevent returning False or empty string on commands that do not produce output + if rc == 0 and len(output) == 0: + return True + + return output + + def get_base_dict(self): + return {'nftables': [{ 'metainfo': { 'json_schema_version': 1} } ] } + + def search_current_chains(self): + nft_chain_priority = {'ip': {'filter': {'input': None, 'forward': None}, 'nat': {'postrouting': None} }, + 'ip6': {'filter': {'input': None, 'forward': None}, 'nat': {'postrouting': None} } } + + # Command: 'nft list chains' + _list = {'list' : {'chains': 'null'} } + command = self.get_base_dict() + command['nftables'].append(_list) + kernel_ruleset = self.nft_exec_dict(command) + if kernel_ruleset: + for _object in kernel_ruleset['nftables']: + chain = _object.get("chain") + if not chain: continue + + _family = chain['family'] + _table = chain['table'] + _hook = chain.get("hook") + _priority = chain.get("prio") + _name = chain['name'] + + if _family not in self.nft_chain_names: continue + if _table not in self.nft_chain_names[_family]: continue + if _hook not in self.nft_chain_names[_family][_table]: continue + if _priority is None: continue + + _saved_priority = nft_chain_priority[_family][_table][_hook] + if _saved_priority is None or _priority < _saved_priority: + # at this point, we know the chain has: + # hook and priority set + # and it has the lowest priority + nft_chain_priority[_family][_table][_hook] = _priority + self.nft_chain_names[_family][_table][_hook] = _name + + def search_for_chain(self, kernel_ruleset: dict, chain_name: str): + found = False + for _object in kernel_ruleset["nftables"]: + chain = _object.get("chain") + if not chain: + continue + ch_name = chain.get("name") + if ch_name == chain_name: + found = True + break + return found + + def get_chain_dict(self, _family: str, _name: str): + # nft (add | create) chain [] + _chain_opts = {'family': _family, 'table': 'filter', 'name': _name } + _add = {'add': {'chain': _chain_opts} } + final_chain = self.get_base_dict() + final_chain["nftables"].append(_add) + return final_chain + + def get_mailcow_jump_rule_dict(self, _family: str, _chain: str): + _jump_rule = self.get_base_dict() + _expr_opt=[] + _expr_counter = {'family': _family, 'table': 'filter', 'packets': 0, 'bytes': 0} + _counter_dict = {'counter': _expr_counter} + _expr_opt.append(_counter_dict) + + _jump_opts = {'jump': {'target': self.chain_name} } + + _expr_opt.append(_jump_opts) + + _rule_params = {'family': _family, + 'table': 'filter', + 'chain': _chain, + 'expr': _expr_opt, + 'comment': "mailcow" } + + _add_rule = {'insert': {'rule': _rule_params} } + + _jump_rule["nftables"].append(_add_rule) + + return _jump_rule + + def insert_mailcow_chains(self, _family: str): + nft_input_chain = self.nft_chain_names[_family]['filter']['input'] + nft_forward_chain = self.nft_chain_names[_family]['filter']['forward'] + # Command: 'nft list table filter' + _table_opts = {'family': _family, 'name': 'filter'} + _list = {'list': {'table': _table_opts} } + command = self.get_base_dict() + command['nftables'].append(_list) + kernel_ruleset = self.nft_exec_dict(command) + if kernel_ruleset: + # chain + if not self.search_for_chain(kernel_ruleset, self.chain_name): + cadena = self.get_chain_dict(_family, self.chain_name) + if self.nft_exec_dict(cadena): + self.logger.logInfo(f"MAILCOW {_family} chain created successfully.") + + input_jump_found, forward_jump_found = False, False + + for _object in kernel_ruleset["nftables"]: + if not _object.get("rule"): + continue + + rule = _object["rule"] + if nft_input_chain and rule["chain"] == nft_input_chain: + if rule.get("comment") and rule["comment"] == "mailcow": + input_jump_found = True + if nft_forward_chain and rule["chain"] == nft_forward_chain: + if rule.get("comment") and rule["comment"] == "mailcow": + forward_jump_found = True + + if not input_jump_found: + command = self.get_mailcow_jump_rule_dict(_family, nft_input_chain) + self.nft_exec_dict(command) + + if not forward_jump_found: + command = self.get_mailcow_jump_rule_dict(_family, nft_forward_chain) + self.nft_exec_dict(command) + + def delete_nat_rule(self, _family:str, _chain: str, _handle:str): + delete_command = self.get_base_dict() + _rule_opts = {'family': _family, + 'table': 'nat', + 'chain': _chain, + 'handle': _handle } + _delete = {'delete': {'rule': _rule_opts} } + delete_command["nftables"].append(_delete) + + return self.nft_exec_dict(delete_command) + + def delete_filter_rule(self, _family:str, _chain: str, _handle:str): + delete_command = self.get_base_dict() + _rule_opts = {'family': _family, + 'table': 'filter', + 'chain': _chain, + 'handle': _handle } + _delete = {'delete': {'rule': _rule_opts} } + delete_command["nftables"].append(_delete) + + return self.nft_exec_dict(delete_command) + + def snat_rule(self, _family: str, snat_target: str, source_address: str): + chain_name = self.nft_chain_names[_family]['nat']['postrouting'] + + # no postrouting chain, may occur if docker has ipv6 disabled. + if not chain_name: return + + # Command: nft list chain nat + _chain_opts = {'family': _family, 'table': 'nat', 'name': chain_name} + _list = {'list':{'chain': _chain_opts} } + command = self.get_base_dict() + command['nftables'].append(_list) + kernel_ruleset = self.nft_exec_dict(command) + if not kernel_ruleset: + return + + rule_position = 0 + rule_handle = None + rule_found = False + for _object in kernel_ruleset["nftables"]: + if not _object.get("rule"): + continue + + rule = _object["rule"] + if not rule.get("comment") or not rule["comment"] == "mailcow": + rule_position +=1 + continue + + rule_found = True + rule_handle = rule["handle"] + break + + dest_net = ipaddress.ip_network(source_address, strict=False) + target_net = ipaddress.ip_network(snat_target, strict=False) + + if rule_found: + saddr_ip = rule["expr"][0]["match"]["right"]["prefix"]["addr"] + saddr_len = int(rule["expr"][0]["match"]["right"]["prefix"]["len"]) + + daddr_ip = rule["expr"][1]["match"]["right"]["prefix"]["addr"] + daddr_len = int(rule["expr"][1]["match"]["right"]["prefix"]["len"]) + + target_ip = rule["expr"][3]["snat"]["addr"] + + saddr_net = ipaddress.ip_network(saddr_ip + '/' + str(saddr_len), strict=False) + daddr_net = ipaddress.ip_network(daddr_ip + '/' + str(daddr_len), strict=False) + current_target_net = ipaddress.ip_network(target_ip, strict=False) + + match = all(( + dest_net == saddr_net, + dest_net == daddr_net, + target_net == current_target_net + )) + try: + if rule_position == 0: + if not match: + # Position 0 , it is a mailcow rule , but it does not have the same parameters + if self.delete_nat_rule(_family, chain_name, rule_handle): + self.logger.logInfo(f'Remove rule for source network {saddr_net} to SNAT target {target_net} from {_family} nat {chain_name} chain, rule does not match configured parameters') + else: + # Position > 0 and is mailcow rule + if self.delete_nat_rule(_family, chain_name, rule_handle): + self.logger.logInfo(f'Remove rule for source network {saddr_net} to SNAT target {target_net} from {_family} nat {chain_name} chain, rule is at position {rule_position}') + except: + self.logger.logCrit(f"Error running SNAT on {_family}, retrying..." ) + else: + # rule not found + json_command = self.get_base_dict() + try: + snat_dict = {'snat': {'addr': str(target_net.network_address)} } + + expr_counter = {'family': _family, 'table': 'nat', 'packets': 0, 'bytes': 0} + counter_dict = {'counter': expr_counter} + + prefix_dict = {'prefix': {'addr': str(dest_net.network_address), 'len': int(dest_net.prefixlen)} } + payload_dict = {'payload': {'protocol': _family, 'field': "saddr"} } + match_dict1 = {'match': {'op': '==', 'left': payload_dict, 'right': prefix_dict} } + + payload_dict2 = {'payload': {'protocol': _family, 'field': "daddr"} } + match_dict2 = {'match': {'op': '!=', 'left': payload_dict2, 'right': prefix_dict } } + expr_list = [ + match_dict1, + match_dict2, + counter_dict, + snat_dict + ] + rule_fields = {'family': _family, + 'table': 'nat', + 'chain': chain_name, + 'comment': "mailcow", + 'expr': expr_list } + + insert_dict = {'insert': {'rule': rule_fields} } + json_command["nftables"].append(insert_dict) + if self.nft_exec_dict(json_command): + self.logger.logInfo(f'Added {_family} nat {chain_name} rule for source network {dest_net} to {target_net}') + except: + self.logger.logCrit(f"Error running SNAT on {_family}, retrying...") + + def get_chain_handle(self, _family: str, _table: str, chain_name: str): + chain_handle = None + # Command: 'nft list chains {family}' + _list = {'list': {'chains': {'family': _family} } } + command = self.get_base_dict() + command['nftables'].append(_list) + kernel_ruleset = self.nft_exec_dict(command) + if kernel_ruleset: + for _object in kernel_ruleset["nftables"]: + if not _object.get("chain"): + continue + chain = _object["chain"] + if chain["family"] == _family and chain["table"] == _table and chain["name"] == chain_name: + chain_handle = chain["handle"] + break + return chain_handle + + def get_rules_handle(self, _family: str, _table: str, chain_name: str, _comment_filter = "mailcow"): + rule_handle = [] + # Command: 'nft list chain {family} {table} {chain_name}' + _chain_opts = {'family': _family, 'table': _table, 'name': chain_name} + _list = {'list': {'chain': _chain_opts} } + command = self.get_base_dict() + command['nftables'].append(_list) + + kernel_ruleset = self.nft_exec_dict(command) + if kernel_ruleset: + for _object in kernel_ruleset["nftables"]: + if not _object.get("rule"): + continue + + rule = _object["rule"] + if rule["family"] == _family and rule["table"] == _table and rule["chain"] == chain_name: + if rule.get("comment") and rule["comment"] == _comment_filter: + rule_handle.append(rule["handle"]) + return rule_handle + + def get_ban_ip_dict(self, ipaddr: str, _family: str): + json_command = self.get_base_dict() + + expr_opt = [] + ipaddr_net = ipaddress.ip_network(ipaddr, strict=False) + right_dict = {'prefix': {'addr': str(ipaddr_net.network_address), 'len': int(ipaddr_net.prefixlen) } } + + left_dict = {'payload': {'protocol': _family, 'field': 'saddr'} } + match_dict = {'op': '==', 'left': left_dict, 'right': right_dict } + expr_opt.append({'match': match_dict}) + + counter_dict = {'counter': {'family': _family, 'table': "filter", 'packets': 0, 'bytes': 0} } + expr_opt.append(counter_dict) + + expr_opt.append({'drop': "null"}) + + rule_dict = {'family': _family, 'table': "filter", 'chain': self.chain_name, 'expr': expr_opt} + + base_dict = {'insert': {'rule': rule_dict} } + json_command["nftables"].append(base_dict) + + return json_command + + def get_unban_ip_dict(self, ipaddr:str, _family: str): + json_command = self.get_base_dict() + # Command: 'nft list chain {s_family} filter MAILCOW' + _chain_opts = {'family': _family, 'table': 'filter', 'name': self.chain_name} + _list = {'list': {'chain': _chain_opts} } + command = self.get_base_dict() + command['nftables'].append(_list) + kernel_ruleset = self.nft_exec_dict(command) + rule_handle = None + if kernel_ruleset: + for _object in kernel_ruleset["nftables"]: + if not _object.get("rule"): + continue + + rule = _object["rule"]["expr"][0]["match"] + if not "payload" in rule["left"]: + continue + left_opt = rule["left"]["payload"] + if not left_opt["protocol"] == _family: + continue + if not left_opt["field"] =="saddr": + continue + + # ip currently banned + rule_right = rule["right"] + if isinstance(rule_right, dict): + current_rule_ip = rule_right["prefix"]["addr"] + '/' + str(rule_right["prefix"]["len"]) + else: + current_rule_ip = rule_right + current_rule_net = ipaddress.ip_network(current_rule_ip) + + # ip to ban + candidate_net = ipaddress.ip_network(ipaddr, strict=False) + + if current_rule_net == candidate_net: + rule_handle = _object["rule"]["handle"] + break + + if rule_handle is not None: + mailcow_rule = {'family': _family, 'table': 'filter', 'chain': self.chain_name, 'handle': rule_handle} + delete_rule = {'delete': {'rule': mailcow_rule} } + json_command["nftables"].append(delete_rule) + else: + return False + + return json_command + + def check_mailcow_chains(self, family: str, chain: str): + position = 0 + rule_found = False + chain_name = self.nft_chain_names[family]['filter'][chain] + + if not chain_name: return None + + _chain_opts = {'family': family, 'table': 'filter', 'name': chain_name} + _list = {'list': {'chain': _chain_opts}} + command = self.get_base_dict() + command['nftables'].append(_list) + kernel_ruleset = self.nft_exec_dict(command) + if kernel_ruleset: + for _object in kernel_ruleset["nftables"]: + if not _object.get("rule"): + continue + rule = _object["rule"] + if rule.get("comment") and rule["comment"] == "mailcow": + rule_found = True + break + + position+=1 + + return position if rule_found else False + + def create_mailcow_isolation_rule(self, _interface:str, _dports:list, _allow:str = ""): + family = "ip" + table = "filter" + comment_filter_drop = "mailcow isolation" + comment_filter_allow = "mailcow isolation allow" + json_command = self.get_base_dict() + + # Delete old mailcow isolation rules + handles = self.get_rules_handle(family, table, self.chain_name, comment_filter_drop) + for handle in handles: + self.delete_filter_rule(family, self.chain_name, handle) + handles = self.get_rules_handle(family, table, self.chain_name, comment_filter_allow) + for handle in handles: + self.delete_filter_rule(family, self.chain_name, handle) + + # insert mailcow isolation rule + _match_dict_drop = [ + { + "match": { + "op": "!=", + "left": { + "meta": { + "key": "iifname" + } + }, + "right": _interface + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifname" + } + }, + "right": _interface + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "dport" + } + }, + "right": { + "set": _dports + } + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": None + } + ] + rule_drop = { "insert": { "rule": { + "family": family, + "table": table, + "chain": self.chain_name, + "comment": comment_filter_drop, + "expr": _match_dict_drop + }}} + json_command["nftables"].append(rule_drop) + + # insert mailcow isolation allow rule + if _allow != "": + _match_dict_allow = [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip", + "field": "saddr" + } + }, + "right": _allow + } + }, + { + "match": { + "op": "!=", + "left": { + "meta": { + "key": "iifname" + } + }, + "right": _interface + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifname" + } + }, + "right": _interface + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "dport" + } + }, + "right": { + "set": _dports + } + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "accept": None + } + ] + rule_allow = { "insert": { "rule": { + "family": family, + "table": table, + "chain": self.chain_name, + "comment": comment_filter_allow, + "expr": _match_dict_allow + }}} + json_command["nftables"].append(rule_allow) + + success = self.nft_exec_dict(json_command) + if success == False: + self.logger.logCrit(f"Error adding {self.chain_name} isolation") + return False + + return True \ No newline at end of file diff --git a/data/Dockerfiles/netfilter/modules/__init__.py b/data/Dockerfiles/netfilter/modules/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/Dockerfiles/netfilter/server.py b/data/Dockerfiles/netfilter/server.py deleted file mode 100644 index 9bf4a08413..0000000000 --- a/data/Dockerfiles/netfilter/server.py +++ /dev/null @@ -1,473 +0,0 @@ -#!/usr/bin/env python2 - -import re -import os -import time -import atexit -import signal -import ipaddress -from random import randint -from threading import Thread -from threading import Lock -import redis -import time -import json -import iptc - -while True: - try: - r = redis.StrictRedis(host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0) - r.ping() - except Exception as ex: - print '%s - trying again in 3 seconds' % (ex) - time.sleep(3) - else: - break - -pubsub = r.pubsub() - -RULES = {} -RULES[1] = 'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed' -RULES[2] = '-login: Disconnected \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),' -RULES[3] = '-login: Aborted login \(no auth .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' -RULES[4] = '-login: Aborted login \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' -RULES[5] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked' -RULES[6] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)' - -bans = {} -log = {} -quit_now = False -lock = Lock() - -def refreshF2boptions(): - global f2boptions - global quit_now - if not r.get('F2B_OPTIONS'): - f2boptions = {} - f2boptions['ban_time'] = int - f2boptions['max_attempts'] = int - f2boptions['retry_window'] = int - f2boptions['netban_ipv4'] = int - f2boptions['netban_ipv6'] = int - f2boptions['ban_time'] = r.get('F2B_BAN_TIME') or 1800 - f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') or 10 - f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') or 600 - f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') or 24 - f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') or 64 - r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False)) - else: - try: - f2boptions = {} - f2boptions = json.loads(r.get('F2B_OPTIONS')) - except ValueError, e: - print 'Error loading F2B options: F2B_OPTIONS is not json' - quit_now = True - -if r.exists('F2B_LOG'): - r.rename('F2B_LOG', 'NETFILTER_LOG') - -def mailcowChainOrder(): - global lock - global quit_now - while not quit_now: - time.sleep(10) - with lock: - filter4_table = iptc.Table(iptc.Table.FILTER) - filter6_table = iptc.Table6(iptc.Table6.FILTER) - filter4_table.refresh() - filter6_table.refresh() - for f in [filter4_table, filter6_table]: - forward_chain = iptc.Chain(f, 'FORWARD') - input_chain = iptc.Chain(f, 'INPUT') - for chain in [forward_chain, input_chain]: - target_found = False - for position, item in enumerate(chain.rules): - if item.target.name == 'MAILCOW': - target_found = True - if position != 0: - log['time'] = int(round(time.time())) - log['priority'] = 'crit' - log['message'] = 'Error in ' + chain.name + ' chain order, restarting container' - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - print log['message'] - quit_now = True - if not target_found: - log['time'] = int(round(time.time())) - log['priority'] = 'crit' - log['message'] = 'Error in ' + chain.name + ' chain: MAILCOW target not found, restarting container' - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - print log['message'] - quit_now = True - -def ban(address): - global lock - refreshF2boptions() - BAN_TIME = int(f2boptions['ban_time']) - MAX_ATTEMPTS = int(f2boptions['max_attempts']) - RETRY_WINDOW = int(f2boptions['retry_window']) - NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4']) - NETBAN_IPV6 = '/' + str(f2boptions['netban_ipv6']) - WHITELIST = r.hgetall('F2B_WHITELIST') - - ip = ipaddress.ip_address(address.decode('ascii')) - if type(ip) is ipaddress.IPv6Address and ip.ipv4_mapped: - ip = ip.ipv4_mapped - address = str(ip) - if ip.is_private or ip.is_loopback: - return - - self_network = ipaddress.ip_network(address.decode('ascii')) - if WHITELIST: - for wl_key in WHITELIST: - wl_net = ipaddress.ip_network(wl_key.decode('ascii'), False) - if wl_net.overlaps(self_network): - log['time'] = int(round(time.time())) - log['priority'] = 'info' - log['message'] = 'Address %s is whitelisted by rule %s' % (self_network, wl_net) - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - print 'Address %s is whitelisted by rule %s' % (self_network, wl_net) - return - - net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)).decode('ascii'), strict=False) - net = str(net) - - if not net in bans or time.time() - bans[net]['last_attempt'] > RETRY_WINDOW: - bans[net] = { 'attempts': 0 } - active_window = RETRY_WINDOW - else: - active_window = time.time() - bans[net]['last_attempt'] - - bans[net]['attempts'] += 1 - bans[net]['last_attempt'] = time.time() - - active_window = time.time() - bans[net]['last_attempt'] - - if bans[net]['attempts'] >= MAX_ATTEMPTS: - log['time'] = int(round(time.time())) - log['priority'] = 'crit' - log['message'] = 'Banning %s' % net - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - print 'Banning %s for %d minutes' % (net, BAN_TIME / 60) - if type(ip) is ipaddress.IPv4Address: - with lock: - chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') - rule = iptc.Rule() - rule.src = net - target = iptc.Target(rule, "REJECT") - rule.target = target - if rule not in chain.rules: - chain.insert_rule(rule) - else: - with lock: - chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW') - rule = iptc.Rule6() - rule.src = net - target = iptc.Target(rule, "REJECT") - rule.target = target - if rule not in chain.rules: - chain.insert_rule(rule) - r.hset('F2B_ACTIVE_BANS', '%s' % net, log['time'] + BAN_TIME) - else: - log['time'] = int(round(time.time())) - log['priority'] = 'warn' - log['message'] = '%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net) - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - print '%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net) - -def unban(net): - global lock - log['time'] = int(round(time.time())) - log['priority'] = 'info' - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - if not net in bans: - log['message'] = '%s is not banned, skipping unban and deleting from queue (if any)' % net - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - print '%s is not banned, skipping unban and deleting from queue (if any)' % net - r.hdel('F2B_QUEUE_UNBAN', '%s' % net) - return - log['message'] = 'Unbanning %s' % net - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - print 'Unbanning %s' % net - if type(ipaddress.ip_network(net.decode('ascii'))) is ipaddress.IPv4Network: - with lock: - chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') - rule = iptc.Rule() - rule.src = net - target = iptc.Target(rule, "REJECT") - rule.target = target - if rule in chain.rules: - chain.delete_rule(rule) - else: - with lock: - chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW') - rule = iptc.Rule6() - rule.src = net - target = iptc.Target(rule, "REJECT") - rule.target = target - if rule in chain.rules: - chain.delete_rule(rule) - r.hdel('F2B_ACTIVE_BANS', '%s' % net) - r.hdel('F2B_QUEUE_UNBAN', '%s' % net) - if net in bans: - del bans[net] - -def quit(signum, frame): - global quit_now - quit_now = True - -def clear(): - global lock - log['time'] = int(round(time.time())) - log['priority'] = 'info' - log['message'] = 'Clearing all bans' - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - print 'Clearing all bans' - for net in bans.copy(): - unban(net) - with lock: - filter4_table = iptc.Table(iptc.Table.FILTER) - filter6_table = iptc.Table6(iptc.Table6.FILTER) - for filter_table in [filter4_table, filter6_table]: - filter_table.autocommit = False - forward_chain = iptc.Chain(filter_table, "FORWARD") - input_chain = iptc.Chain(filter_table, "INPUT") - mailcow_chain = iptc.Chain(filter_table, "MAILCOW") - if mailcow_chain in filter_table.chains: - for rule in mailcow_chain.rules: - mailcow_chain.delete_rule(rule) - for rule in forward_chain.rules: - if rule.target.name == 'MAILCOW': - forward_chain.delete_rule(rule) - for rule in input_chain.rules: - if rule.target.name == 'MAILCOW': - input_chain.delete_rule(rule) - filter_table.delete_chain("MAILCOW") - filter_table.commit() - filter_table.refresh() - filter_table.autocommit = True - r.delete('F2B_ACTIVE_BANS') - r.delete('F2B_PERM_BANS') - pubsub.unsubscribe() - -def watch(): - log['time'] = int(round(time.time())) - log['priority'] = 'info' - log['message'] = 'Watching Redis channel F2B_CHANNEL' - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - pubsub.subscribe('F2B_CHANNEL') - print 'Subscribing to Redis channel F2B_CHANNEL' - - while not quit_now: - for item in pubsub.listen(): - for rule_id, rule_regex in RULES.iteritems(): - if item['data'] and item['type'] == 'message': - result = re.search(rule_regex, item['data']) - if result: - addr = result.group(1) - ip = ipaddress.ip_address(addr.decode('ascii')) - if ip.is_private or ip.is_loopback: - continue - print '%s matched rule id %d' % (addr, rule_id) - log['time'] = int(round(time.time())) - log['priority'] = 'warn' - log['message'] = '%s matched rule id %d' % (addr, rule_id) - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - ban(addr) - -def snat4(snat_target): - global lock - global quit_now - - def get_snat4_rule(): - rule = iptc.Rule() - rule.src = os.getenv('IPV4_NETWORK', '172.22.1') + '.0/24' - rule.dst = '!' + rule.src - target = rule.create_target("SNAT") - target.to_source = snat_target - return rule - - while not quit_now: - time.sleep(10) - with lock: - try: - table = iptc.Table('nat') - table.refresh() - chain = iptc.Chain(table, 'POSTROUTING') - table.autocommit = False - if get_snat4_rule() not in chain.rules: - log['time'] = int(round(time.time())) - log['priority'] = 'info' - log['message'] = 'Added POSTROUTING rule for source network ' + get_snat4_rule().src + ' to SNAT target ' + snat_target - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - print log['message'] - chain.insert_rule(get_snat4_rule()) - table.commit() - else: - for position, item in enumerate(chain.rules): - if item == get_snat4_rule(): - if position != 0: - chain.delete_rule(get_snat4_rule()) - table.commit() - table.autocommit = True - except: - print 'Error running SNAT4, retrying...' - -def snat6(snat_target): - global lock - global quit_now - - def get_snat6_rule(): - rule = iptc.Rule6() - rule.src = os.getenv('IPV6_NETWORK', 'fd4d:6169:6c63:6f77::/64') - rule.dst = '!' + rule.src - target = rule.create_target("SNAT") - target.to_source = snat_target - return rule - - while not quit_now: - time.sleep(10) - with lock: - try: - table = iptc.Table6('nat') - table.refresh() - chain = iptc.Chain(table, 'POSTROUTING') - table.autocommit = False - if get_snat6_rule() not in chain.rules: - log['time'] = int(round(time.time())) - log['priority'] = 'info' - log['message'] = 'Added POSTROUTING rule for source network ' + get_snat6_rule().src + ' to SNAT target ' + snat_target - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - print log['message'] - chain.insert_rule(get_snat6_rule()) - table.commit() - else: - for position, item in enumerate(chain.rules): - if item == get_snat6_rule(): - if position != 0: - chain.delete_rule(get_snat6_rule()) - table.commit() - table.autocommit = True - except: - print 'Error running SNAT6, retrying...' - -def autopurge(): - while not quit_now: - time.sleep(10) - refreshF2boptions() - BAN_TIME = f2boptions['ban_time'] - MAX_ATTEMPTS = f2boptions['max_attempts'] - QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN') - if QUEUE_UNBAN: - for net in QUEUE_UNBAN: - unban(str(net)) - for net in bans.copy(): - if bans[net]['attempts'] >= MAX_ATTEMPTS: - if time.time() - bans[net]['last_attempt'] > BAN_TIME: - unban(net) - -def initChain(): - # Is called before threads start, no locking - print "Initializing mailcow netfilter chain" - # IPv4 - if not iptc.Chain(iptc.Table(iptc.Table.FILTER), "MAILCOW") in iptc.Table(iptc.Table.FILTER).chains: - iptc.Table(iptc.Table.FILTER).create_chain("MAILCOW") - for c in ['FORWARD', 'INPUT']: - chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), c) - rule = iptc.Rule() - rule.src = '0.0.0.0/0' - rule.dst = '0.0.0.0/0' - target = iptc.Target(rule, "MAILCOW") - rule.target = target - if rule not in chain.rules: - chain.insert_rule(rule) - # IPv6 - if not iptc.Chain(iptc.Table6(iptc.Table6.FILTER), "MAILCOW") in iptc.Table6(iptc.Table6.FILTER).chains: - iptc.Table6(iptc.Table6.FILTER).create_chain("MAILCOW") - for c in ['FORWARD', 'INPUT']: - chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), c) - rule = iptc.Rule6() - rule.src = '::/0' - rule.dst = '::/0' - target = iptc.Target(rule, "MAILCOW") - rule.target = target - if rule not in chain.rules: - chain.insert_rule(rule) - # Apply blacklist - BLACKLIST = r.hgetall('F2B_BLACKLIST') - if BLACKLIST: - for bl_key in BLACKLIST: - if type(ipaddress.ip_network(bl_key.decode('ascii'), strict=False)) is ipaddress.IPv4Network: - chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') - rule = iptc.Rule() - rule.src = bl_key - target = iptc.Target(rule, "REJECT") - rule.target = target - if rule not in chain.rules: - log['time'] = int(round(time.time())) - log['priority'] = 'crit' - log['message'] = 'Blacklisting host/network %s' % bl_key - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - print log['message'] - chain.insert_rule(rule) - r.hset('F2B_PERM_BANS', '%s' % bl_key, int(round(time.time()))) - else: - chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW') - rule = iptc.Rule6() - rule.src = bl_key - target = iptc.Target(rule, "REJECT") - rule.target = target - if rule not in chain.rules: - log['time'] = int(round(time.time())) - log['priority'] = 'crit' - log['message'] = 'Blacklisting host/network %s' % bl_key - r.lpush('NETFILTER_LOG', json.dumps(log, ensure_ascii=False)) - print log['message'] - chain.insert_rule(rule) - r.hset('F2B_PERM_BANS', '%s' % bl_key, int(round(time.time()))) - -if __name__ == '__main__': - - # In case a previous session was killed without cleanup - clear() - # Reinit MAILCOW chain - initChain() - - watch_thread = Thread(target=watch) - watch_thread.daemon = True - watch_thread.start() - - if os.getenv('SNAT_TO_SOURCE') and os.getenv('SNAT_TO_SOURCE') is not 'n': - try: - snat_ip = os.getenv('SNAT_TO_SOURCE').decode('ascii') - snat_ipo = ipaddress.ip_address(snat_ip) - if type(snat_ipo) is ipaddress.IPv4Address: - snat4_thread = Thread(target=snat4,args=(snat_ip,)) - snat4_thread.daemon = True - snat4_thread.start() - except ValueError: - print os.getenv('SNAT_TO_SOURCE') + ' is not a valid IPv4 address' - - if os.getenv('SNAT6_TO_SOURCE') and os.getenv('SNAT6_TO_SOURCE') is not 'n': - try: - snat_ip = os.getenv('SNAT6_TO_SOURCE').decode('ascii') - snat_ipo = ipaddress.ip_address(snat_ip) - if type(snat_ipo) is ipaddress.IPv6Address: - snat6_thread = Thread(target=snat6,args=(snat_ip,)) - snat6_thread.daemon = True - snat6_thread.start() - except ValueError: - print os.getenv('SNAT6_TO_SOURCE') + ' is not a valid IPv6 address' - - autopurge_thread = Thread(target=autopurge) - autopurge_thread.daemon = True - autopurge_thread.start() - - mailcowchainwatch_thread = Thread(target=mailcowChainOrder) - mailcowchainwatch_thread.daemon = True - mailcowchainwatch_thread.start() - - signal.signal(signal.SIGTERM, quit) - atexit.register(clear) - - while not quit_now: - time.sleep(0.5) diff --git a/data/Dockerfiles/nginx/Dockerfile b/data/Dockerfiles/nginx/Dockerfile new file mode 100644 index 0000000000..d3280a33d4 --- /dev/null +++ b/data/Dockerfiles/nginx/Dockerfile @@ -0,0 +1,18 @@ +FROM nginx:1.30.4-alpine +LABEL maintainer "The Infrastructure Company GmbH " + +ENV PIP_BREAK_SYSTEM_PACKAGES=1 + +RUN apk add --no-cache nginx \ + python3 \ + py3-pip && \ + pip install --upgrade pip && \ + pip install Jinja2 + +RUN mkdir -p /etc/nginx/includes + +COPY ./bootstrap.py / +COPY ./docker-entrypoint.sh / + +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["nginx", "-g", "daemon off;"] diff --git a/data/Dockerfiles/nginx/bootstrap.py b/data/Dockerfiles/nginx/bootstrap.py new file mode 100644 index 0000000000..97ce7a4e12 --- /dev/null +++ b/data/Dockerfiles/nginx/bootstrap.py @@ -0,0 +1,100 @@ +import os +import subprocess +from jinja2 import Environment, FileSystemLoader + +def includes_conf(env, template_vars): + server_name = "server_name.active" + listen_plain = "listen_plain.active" + listen_ssl = "listen_ssl.active" + + server_name_config = f"server_name {template_vars['MAILCOW_HOSTNAME']} autodiscover.* autoconfig.* {' '.join(template_vars['ADDITIONAL_SERVER_NAMES'])};" + listen_plain_config = f"listen {template_vars['HTTP_PORT']};" + listen_ssl_config = f"listen {template_vars['HTTPS_PORT']};" + if template_vars['ENABLE_IPV6']: + listen_plain_config += f"\nlisten [::]:{template_vars['HTTP_PORT']};" + listen_ssl_config += f"\nlisten [::]:{template_vars['HTTPS_PORT']} ssl;" + listen_ssl_config += "\nhttp2 on;" + + with open(f"/etc/nginx/conf.d/{server_name}", "w") as f: + f.write(server_name_config) + + with open(f"/etc/nginx/conf.d/{listen_plain}", "w") as f: + f.write(listen_plain_config) + + with open(f"/etc/nginx/conf.d/{listen_ssl}", "w") as f: + f.write(listen_ssl_config) + +def sites_default_conf(env, template_vars): + config_name = "sites-default.conf" + template = env.get_template(f"{config_name}.j2") + config = template.render(template_vars) + + with open(f"/etc/nginx/includes/{config_name}", "w") as f: + f.write(config) + +def nginx_conf(env, template_vars): + config_name = "nginx.conf" + template = env.get_template(f"{config_name}.j2") + config = template.render(template_vars) + + with open(f"/etc/nginx/{config_name}", "w") as f: + f.write(config) + +def prepare_template_vars(): + ipv4_network = os.getenv("IPV4_NETWORK", "172.22.1") + additional_server_names = os.getenv("ADDITIONAL_SERVER_NAMES", "") + trusted_proxies = os.getenv("TRUSTED_PROXIES", "") + + template_vars = { + 'IPV4_NETWORK': ipv4_network, + 'TRUSTED_PROXIES': [item.strip() for item in trusted_proxies.split(",") if item.strip()], + 'SKIP_RSPAMD': os.getenv("SKIP_RSPAMD", "n").lower() in ("y", "yes"), + 'SKIP_SOGO': os.getenv("SKIP_SOGO", "n").lower() in ("y", "yes"), + 'NGINX_USE_PROXY_PROTOCOL': os.getenv("NGINX_USE_PROXY_PROTOCOL", "n").lower() in ("y", "yes"), + 'MAILCOW_HOSTNAME': os.getenv("MAILCOW_HOSTNAME", ""), + 'ADDITIONAL_SERVER_NAMES': [item.strip() for item in additional_server_names.split(",") if item.strip()], + 'HTTP_PORT': os.getenv("HTTP_PORT", "80"), + 'HTTPS_PORT': os.getenv("HTTPS_PORT", "443"), + 'SOGOHOST': os.getenv("SOGOHOST", ipv4_network + ".248"), + 'RSPAMDHOST': os.getenv("RSPAMDHOST", "rspamd-mailcow"), + 'PHPFPMHOST': os.getenv("PHPFPMHOST", "php-fpm-mailcow"), + 'ENABLE_IPV6': os.getenv("ENABLE_IPV6", "true").lower() != "false", + 'HTTP_REDIRECT': os.getenv("HTTP_REDIRECT", "n").lower() in ("y", "yes"), + } + + ssl_dir = '/etc/ssl/mail/' + template_vars['valid_cert_dirs'] = [] + for d in os.listdir(ssl_dir): + full_path = os.path.join(ssl_dir, d) + if not os.path.isdir(full_path): + continue + + cert_path = os.path.join(full_path, 'cert.pem') + key_path = os.path.join(full_path, 'key.pem') + domains_path = os.path.join(full_path, 'domains') + + if os.path.isfile(cert_path) and os.path.isfile(key_path) and os.path.isfile(domains_path): + with open(domains_path, 'r') as file: + domains = file.read().strip() + domains_list = domains.split() + if domains_list and template_vars["MAILCOW_HOSTNAME"] not in domains_list: + template_vars['valid_cert_dirs'].append({ + 'cert_path': full_path + '/', + 'domains': domains + }) + + return template_vars + +def main(): + env = Environment(loader=FileSystemLoader('./etc/nginx/conf.d/templates')) + + # Render config + print("Render config") + template_vars = prepare_template_vars() + sites_default_conf(env, template_vars) + nginx_conf(env, template_vars) + includes_conf(env, template_vars) + + +if __name__ == "__main__": + main() diff --git a/data/Dockerfiles/nginx/docker-entrypoint.sh b/data/Dockerfiles/nginx/docker-entrypoint.sh new file mode 100755 index 0000000000..45e327eded --- /dev/null +++ b/data/Dockerfiles/nginx/docker-entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +PHPFPMHOST=${PHPFPMHOST:-"php-fpm-mailcow"} +SOGOHOST=${SOGOHOST:-"$IPV4_NETWORK.248"} +RSPAMDHOST=${RSPAMDHOST:-"rspamd-mailcow"} + +until ping ${PHPFPMHOST} -c1 > /dev/null; do + echo "Waiting for PHP..." + sleep 1 +done +if ! printf "%s\n" "${SKIP_SOGO}" | grep -E '^([yY][eE][sS]|[yY])+$' >/dev/null; then + until ping ${SOGOHOST} -c1 > /dev/null; do + echo "Waiting for SOGo..." + sleep 1 + done +fi +if ! printf "%s\n" "${SKIP_RSPAMD}" | grep -E '^([yY][eE][sS]|[yY])+$' >/dev/null; then + until ping ${RSPAMDHOST} -c1 > /dev/null; do + echo "Waiting for Rspamd..." + sleep 1 + done +fi + +python3 /bootstrap.py + +exec "$@" diff --git a/data/Dockerfiles/olefy/Dockerfile b/data/Dockerfiles/olefy/Dockerfile new file mode 100644 index 0000000000..845b125f6b --- /dev/null +++ b/data/Dockerfiles/olefy/Dockerfile @@ -0,0 +1,23 @@ +FROM alpine:3.21 + +LABEL maintainer = "The Infrastructure Company GmbH " + +ARG PIP_BREAK_SYSTEM_PACKAGES=1 +WORKDIR /app + +#RUN addgroup -S olefy && adduser -S olefy -G olefy \ +RUN apk add --virtual .build-deps gcc musl-dev python3-dev libffi-dev openssl-dev cargo \ + && apk add --update --no-cache python3 py3-pip openssl tzdata libmagic \ + && pip3 install --upgrade pip \ + && pip3 install --upgrade asyncio python-magic \ + && pip3 install --upgrade https://github.com/decalage2/oletools/archive/master.zip \ + && apk del .build-deps +# && sed -i 's/template_injection_detected = True/template_injection_detected = False/g' /usr/lib/python3.9/site-packages/oletools/olevba.py + +ADD olefy.py /app/ + +RUN chown -R nobody:nobody /app /tmp + +USER nobody + +CMD ["python3", "-u", "/app/olefy.py"] diff --git a/data/Dockerfiles/olefy/olefy.py b/data/Dockerfiles/olefy/olefy.py new file mode 100644 index 0000000000..7c68809295 --- /dev/null +++ b/data/Dockerfiles/olefy/olefy.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright (c) 2020, Dennis Kalbhen +# Copyright (c) 2020, Carsten Rosenberg +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### +# +# olefy is a little helper socket to use oletools with rspamd. (https://rspamd.com) +# Please find updates and issues here: https://github.com/HeinleinSupport/olefy +# +### + +from subprocess import Popen, PIPE +import sys +import os +import logging +import asyncio +import time +import magic +import re + +skip_olefy = os.getenv('SKIP_OLEFY', '') + +if skip_olefy.lower() in ['yes', 'y']: + print("SKIP_OLEFY=y, skipping Olefy...") + time.sleep(365 * 24 * 60 * 60) + sys.exit(0) + +# merge variables from /etc/olefy.conf and the defaults +olefy_listen_addr_string = os.getenv('OLEFY_BINDADDRESS', '127.0.0.1,::1') +olefy_listen_port = int(os.getenv('OLEFY_BINDPORT', '10050')) +olefy_tmp_dir = os.getenv('OLEFY_TMPDIR', '/tmp') +olefy_python_path = os.getenv('OLEFY_PYTHON_PATH', '/usr/bin/python3') +olefy_olevba_path = os.getenv('OLEFY_OLEVBA_PATH', '/usr/local/bin/olevba3') +# 10:DEBUG, 20:INFO, 30:WARNING, 40:ERROR, 50:CRITICAL +olefy_loglvl = int(os.getenv('OLEFY_LOGLVL', 20)) +olefy_min_length = int(os.getenv('OLEFY_MINLENGTH', 500)) +olefy_del_tmp = int(os.getenv('OLEFY_DEL_TMP', 1)) +olefy_del_tmp_failed = int(os.getenv('OLEFY_DEL_TMP_FAILED', 1)) + +# internal used variables +request_time = '0000000000.000000' +olefy_protocol = 'OLEFY' +olefy_ping = 'PING' +olefy_protocol_sep = '\n\n' +olefy_headers = {} + +# init logging +logger = logging.getLogger('olefy') +logging.basicConfig(stream=sys.stdout, level=olefy_loglvl, format='olefy %(levelname)s %(funcName)s %(message)s') + +logger.debug('olefy listen address string: {} (type {})'.format(olefy_listen_addr_string, type(olefy_listen_addr_string))) + +if not olefy_listen_addr_string: + olefy_listen_addr = "" +else: + addr_re = re.compile('[\[" \]]') + olefy_listen_addr = addr_re.sub('', olefy_listen_addr_string.replace("'", "")).split(',') + +# log runtime variables +logger.info('olefy listen address: {} (type: {})'.format(olefy_listen_addr, type(olefy_listen_addr))) +logger.info('olefy listen port: {}'.format(olefy_listen_port)) +logger.info('olefy tmp dir: {}'.format(olefy_tmp_dir)) +logger.info('olefy python path: {}'.format(olefy_python_path)) +logger.info('olefy olvba path: {}'.format(olefy_olevba_path)) +logger.info('olefy log level: {}'.format(olefy_loglvl)) +logger.info('olefy min file length: {}'.format(olefy_min_length)) +logger.info('olefy delete tmp file: {}'.format(olefy_del_tmp)) +logger.info('olefy delete tmp file when failed: {}'.format(olefy_del_tmp_failed)) + +if not os.path.isfile(olefy_python_path): + logger.critical('python path not found: {}'.format(olefy_python_path)) + exit(1) +if not os.path.isfile(olefy_olevba_path): + logger.critical('olevba path not found: {}'.format(olefy_olevba_path)) + exit(1) + +# olefy protocol function +def protocol_split( olefy_line ): + header_lines = olefy_line.split('\n') + for line in header_lines: + if line == 'OLEFY/1.0': + olefy_headers['olefy'] = line + elif line != '': + kv = line.split(': ') + if kv[0] != '' and kv[1] != '': + olefy_headers[kv[0]] = kv[1] + logger.debug('olefy_headers: {}'.format(olefy_headers)) + +# calling oletools +def oletools( stream, tmp_file_name, lid ): + if olefy_min_length > stream.__len__(): + logger.error('{} {} bytes (Not Scanning! File smaller than {!r})'.format(lid, stream.__len__(), olefy_min_length)) + out = b'[ { "error": "File too small" } ]' + else: + tmp_file = open(tmp_file_name, 'wb') + tmp_file.write(stream) + tmp_file.close() + + file_magic = magic.Magic(mime=True, uncompress=True) + file_mime = file_magic.from_file(tmp_file_name) + logger.info('{} {} (libmagic output)'.format(lid, file_mime)) + + # do the olefy + cmd_tmp = Popen([olefy_python_path, olefy_olevba_path, '-a', '-j' , '-l', 'error', tmp_file_name], stdout=PIPE, stderr=PIPE) + out, err = cmd_tmp.communicate() + out = bytes(out.decode('utf-8', 'ignore').replace(' ', ' ').replace('\t', '').replace('\n', '').replace('XLMMacroDeobfuscator: pywin32 is not installed (only is required if you want to use MS Excel)', ''), encoding="utf-8") + failed = False + if out.__len__() < 30: + logger.error('{} olevba returned <30 chars - rc: {!r}, response: {!r}, error: {!r}'.format(lid,cmd_tmp.returncode, + out.decode('utf-8', 'ignore'), err.decode('utf-8', 'ignore'))) + out = b'[ { "error": "Unhandled error - too short olevba response" } ]' + failed = True + elif err.__len__() > 10 and cmd_tmp.returncode == 9: + logger.error("{} olevba stderr >10 chars - rc: {!r}, response: {!r}".format(lid, cmd_tmp.returncode, err.decode("utf-8", "ignore"))) + out = b'[ { "error": "Decrypt failed" } ]' + failed = True + elif err.__len__() > 10 and cmd_tmp.returncode > 9: + logger.error('{} olevba stderr >10 chars - rc: {!r}, response: {!r}'.format(lid, cmd_tmp.returncode, err.decode('utf-8', 'ignore'))) + out = b'[ { "error": "Unhandled oletools error" } ]' + failed = True + elif cmd_tmp.returncode != 0: + logger.error('{} olevba exited with code {!r}; err: {!r}'.format(lid, cmd_tmp.returncode, err.decode('utf-8', 'ignore'))) + failed = True + + if failed and olefy_del_tmp_failed == 0: + logger.debug('{} {} FAILED: not deleting tmp file'.format(lid, tmp_file_name)) + elif olefy_del_tmp == 1: + logger.debug('{} {} deleting tmp file'.format(lid, tmp_file_name)) + os.remove(tmp_file_name) + + logger.debug('{} response: {}'.format(lid, out.decode('utf-8', 'ignore'))) + return out + b'\t\n\n\t' + +# Asyncio data handling, default AIO-Functions +class AIO(asyncio.Protocol): + def __init__(self): + self.extra = bytearray() + + def connection_made(self, transport): + global request_time + peer = transport.get_extra_info('peername') + logger.debug('{} new connection was made'.format(peer)) + self.transport = transport + request_time = str(time.time()) + + def data_received(self, request, msgid=1): + peer = self.transport.get_extra_info('peername') + logger.debug('{} data received from new connection'.format(peer)) + self.extra.extend(request) + + def eof_received(self): + peer = self.transport.get_extra_info('peername') + olefy_protocol_err = False + proto_ck = self.extra[0:2000].decode('utf-8', 'ignore') + + headers = proto_ck[0:proto_ck.find(olefy_protocol_sep)] + + if olefy_protocol == headers[0:5]: + self.extra = bytearray(self.extra[len(headers)+2:len(self.extra)]) + protocol_split(headers) + else: + olefy_protocol_err = True + + if olefy_ping == headers[0:4]: + is_ping = True + else: + is_ping = False + rspamd_id = olefy_headers['Rspamd-ID'][:6] or '' + lid = 'Rspamd-ID' in olefy_headers and '<'+rspamd_id+'>' + tmp_file_name = olefy_tmp_dir+'/'+request_time+'.'+str(peer[1])+'.'+rspamd_id + logger.debug('{} {} choosen as tmp filename'.format(lid, tmp_file_name)) + + if not is_ping or olefy_loglvl == 10: + logger.info('{} {} bytes (stream size)'.format(lid, self.extra.__len__())) + + if olefy_ping == headers[0:4]: + logger.debug('{} PING request'.format(peer)) + out = b'PONG' + elif olefy_protocol_err == True or olefy_headers['olefy'] != 'OLEFY/1.0': + logger.error('{} Protocol ERROR: no OLEFY/1.0 found'.format(lid)) + out = b'[ { "error": "Protocol error" } ]' + elif 'Method' in olefy_headers: + if olefy_headers['Method'] == 'oletools': + out = oletools(self.extra, tmp_file_name, lid) + else: + logger.error('Protocol ERROR: Method header not found') + out = b'[ { "error": "Protocol error: Method header not found" } ]' + + self.transport.write(out) + if not is_ping or olefy_loglvl == 10: + logger.info('{} {} response send: {!r}'.format(lid, peer, out)) + self.transport.close() + + +# start the listeners +loop = asyncio.get_event_loop() +# each client connection will create a new protocol instance +coro = loop.create_server(AIO, olefy_listen_addr, olefy_listen_port) +server = loop.run_until_complete(coro) +for sockets in server.sockets: + logger.info('serving on {}'.format(sockets.getsockname())) + +# XXX serve requests until KeyboardInterrupt, not needed for production +try: + loop.run_forever() +except KeyboardInterrupt: + pass + +# graceful shutdown/reload +server.close() +loop.run_until_complete(server.wait_closed()) +loop.close() +logger.info('stopped serving') diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index 0ed81896de..ca252163ab 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -1,27 +1,45 @@ -FROM php:7.2-fpm-alpine3.7 -LABEL maintainer "Andre Peters " +FROM php:8.2-fpm-alpine3.21 -ENV APCU_PECL 5.1.12 -ENV IMAGICK_PECL 3.4.3 -ENV MAILPARSE_PECL 3.0.2 -ENV MEMCACHED_PECL 3.0.4 -ENV REDIS_PECL 4.1.1 +LABEL maintainer = "The Infrastructure Company GmbH " + +# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?.*)$ +ARG APCU_PECL_VERSION=5.1.28 +# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?.*)$ +ARG IMAGICK_PECL_VERSION=3.8.1 +# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?.*)$ +ARG MAILPARSE_PECL_VERSION=3.2.0 +# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?.*)$ +ARG MEMCACHED_PECL_VERSION=3.4.0 +# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?.*)$ +ARG REDIS_PECL_VERSION=6.3.0 +# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?.*)$ +ARG COMPOSER_VERSION=2.10.2 RUN apk add -U --no-cache autoconf \ + aspell-dev \ + aspell-libs \ bash \ c-client \ cyrus-sasl-dev \ freetype \ freetype-dev \ g++ \ + git \ + gettext \ gettext-dev \ + gmp-dev \ + gnupg \ icu-dev \ icu-libs \ imagemagick \ imagemagick-dev \ imap-dev \ + jq \ + libavif \ + libavif-dev \ libjpeg-turbo \ libjpeg-turbo-dev \ + libmemcached \ libmemcached-dev \ libpng \ libpng-dev \ @@ -31,7 +49,11 @@ RUN apk add -U --no-cache autoconf \ libtool \ libwebp-dev \ libxml2-dev \ + libxpm \ libxpm-dev \ + libzip \ + libzip-dev \ + linux-headers \ make \ mysql-client \ openldap-dev \ @@ -41,42 +63,52 @@ RUN apk add -U --no-cache autoconf \ samba-client \ zlib-dev \ tzdata \ - && pear install channel://pear.php.net/Net_IDNA2-0.2.0 \ - channel://pear.php.net/Auth_SASL-1.1.0 \ - Net_IMAP \ - Net_Sieve \ - NET_SMTP \ - Mail_mime \ - && pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} mailparse-${MAILPARSE_PECL} \ - && docker-php-ext-enable apcu imagick mailparse memcached redis \ + && pecl install APCu-${APCU_PECL_VERSION} \ + && pecl install imagick-${IMAGICK_PECL_VERSION} \ + && pecl install mailparse-${MAILPARSE_PECL_VERSION} \ + && pecl install memcached-${MEMCACHED_PECL_VERSION} \ + && pecl install redis-${REDIS_PECL_VERSION} \ + && docker-php-ext-enable apcu imagick memcached mailparse redis \ && pecl clear-cache \ && docker-php-ext-configure intl \ - && docker-php-ext-configure gd \ - --with-gd \ - --enable-gd-native-ttf \ - --with-freetype-dir=/usr/include/ \ - --with-png-dir=/usr/include/ \ - --with-jpeg-dir=/usr/include/ \ - && docker-php-ext-install -j 4 gd gettext intl ldap opcache pcntl pdo pdo_mysql soap sockets xmlrpc zip \ + && docker-php-ext-configure exif \ + && docker-php-ext-configure gd --with-freetype=/usr/include/ \ + --with-jpeg=/usr/include/ \ + --with-webp \ + --with-xpm \ + --with-avif \ + && docker-php-ext-install -j 4 exif gd gettext intl ldap opcache pcntl pdo pdo_mysql pspell soap sockets zip bcmath gmp \ && docker-php-ext-configure imap --with-imap --with-imap-ssl \ && docker-php-ext-install -j 4 imap \ + && curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \ + && mv composer.phar /usr/local/bin/composer \ + && chmod +x /usr/local/bin/composer \ && apk del --purge autoconf \ + aspell-dev \ cyrus-sasl-dev \ freetype-dev \ g++ \ + gettext-dev \ icu-dev \ imagemagick-dev \ imap-dev \ + libavif-dev \ libjpeg-turbo-dev \ + libmemcached-dev \ libpng-dev \ libressl-dev \ libwebp-dev \ libxml2-dev \ + libxpm-dev \ + libzip-dev \ + linux-headers \ make \ + openldap-dev \ pcre-dev \ zlib-dev COPY ./docker-entrypoint.sh / ENTRYPOINT ["/docker-entrypoint.sh"] + CMD ["php-fpm"] diff --git a/data/Dockerfiles/phpfpm/docker-entrypoint.sh b/data/Dockerfiles/phpfpm/docker-entrypoint.sh index 694d5cf6f0..d7fa155562 100755 --- a/data/Dockerfiles/phpfpm/docker-entrypoint.sh +++ b/data/Dockerfiles/phpfpm/docker-entrypoint.sh @@ -1,64 +1,225 @@ #!/bin/bash -set -e function array_by_comma { local IFS=","; echo "$*"; } # Wait for containers -while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do +while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do echo "Waiting for SQL..." sleep 2 done -until [[ $(redis-cli -h redis-mailcow PING) == "PONG" ]]; do +# Do not attempt to write to slave +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + REDIS_HOST=$REDIS_SLAVEOF_IP + REDIS_PORT=$REDIS_SLAVEOF_PORT +else + REDIS_HOST="redis" + REDIS_PORT="6379" +fi +REDIS_CMDLINE="redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT} -a ${REDISPASS} --no-auth-warning" + +until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do echo "Waiting for Redis..." sleep 2 done -# Trigger db init -echo "Running DB init..." -php -c /usr/local/etc/php -f /web/inc/init_db.inc.php - -# Migrate domain map -declare -a DOMAIN_ARR -redis-cli -h redis-mailcow DEL DOMAIN_MAP -while read line -do - DOMAIN_ARR+=("$line") -done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs) -while read line -do - DOMAIN_ARR+=("$line") -done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs) - -if [[ ! -z ${DOMAIN_ARR} ]]; then -for domain in "${DOMAIN_ARR[@]}"; do - redis-cli -h redis-mailcow HSET DOMAIN_MAP ${domain} 1 +# Set redis session store +echo -n ' +session.save_handler = redis +session.save_path = "tcp://'${REDIS_HOST}':'${REDIS_PORT}'?auth='${REDISPASS}'" +' > /usr/local/etc/php/conf.d/session_store.ini + +# Check mysql_upgrade (master and slave) +CONTAINER_ID= +until [[ ! -z "${CONTAINER_ID}" ]] && [[ "${CONTAINER_ID}" =~ ^[[:alnum:]]*$ ]]; do + CONTAINER_ID=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"mysql-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" 2> /dev/null) + echo "Could not get mysql-mailcow container id... trying again" + sleep 2 done +echo "MySQL @ ${CONTAINER_ID}" +SQL_LOOP_C=0 +SQL_CHANGED=0 +until [[ ${SQL_UPGRADE_STATUS} == 'success' ]]; do + if [ ${SQL_LOOP_C} -gt 4 ]; then + echo "Tried to upgrade MySQL and failed, giving up after ${SQL_LOOP_C} retries and starting container (oops, not good)" + break + fi + SQL_FULL_UPGRADE_RETURN=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_upgrade"}' --silent -H 'Content-type: application/json') + SQL_UPGRADE_STATUS=$(echo ${SQL_FULL_UPGRADE_RETURN} | jq -r .type) + SQL_LOOP_C=$((SQL_LOOP_C+1)) + echo "SQL upgrade iteration #${SQL_LOOP_C}" + if [[ ${SQL_UPGRADE_STATUS} == 'warning' ]]; then + SQL_CHANGED=1 + echo "MySQL applied an upgrade, debug output:" + echo ${SQL_FULL_UPGRADE_RETURN} + sleep 3 + while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do + echo "Waiting for SQL to return, please wait" + sleep 2 + done + continue + elif [[ ${SQL_UPGRADE_STATUS} == 'success' ]]; then + echo "MySQL is up-to-date - debug output:" + echo ${SQL_FULL_UPGRADE_RETURN} + else + echo "No valid reponse for mysql_upgrade was received, debug output:" + echo ${SQL_FULL_UPGRADE_RETURN} + fi +done + +# doing post-installation stuff, if SQL was upgraded (master and slave) +if [ ${SQL_CHANGED} -eq 1 ]; then + POSTFIX=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"postfix-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" 2> /dev/null) + if [[ -z "${POSTFIX}" ]] || ! [[ "${POSTFIX}" =~ ^[[:alnum:]]*$ ]]; then + echo "Could not determine Postfix container ID, skipping Postfix restart." + else + echo "Restarting Postfix" + curl -X POST --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${POSTFIX}/restart | jq -r '.msg' + echo "Sleeping 5 seconds..." + sleep 5 + fi fi -# Set API options if env vars are not empty +# Check mysql tz import (master and slave) +TZ_CHECK=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT CONVERT_TZ('2019-11-02 23:33:00','Europe/Berlin','UTC') AS time;" -BN 2> /dev/null) +if [[ -z ${TZ_CHECK} ]] || [[ "${TZ_CHECK}" == "NULL" ]]; then + SQL_FULL_TZINFO_IMPORT_RETURN=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_tzinfo_to_sql"}' --silent -H 'Content-type: application/json') + echo "MySQL mysql_tzinfo_to_sql - debug output:" + echo ${SQL_FULL_TZINFO_IMPORT_RETURN} +fi -if [[ ${API_ALLOW_FROM} != "invalid" ]] && \ - [[ ${API_KEY} != "invalid" ]] && \ - [[ ! -z ${API_KEY} ]] && \ - [[ ! -z ${API_ALLOW_FROM} ]]; then - IFS=',' read -r -a API_ALLOW_FROM_ARR <<< "${API_ALLOW_FROM}" - declare -a VALIDATED_API_ALLOW_FROM_ARR - REGEX_IP6='^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$' - REGEX_IP4='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' +if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + echo "We are master, preparing..." + # Set a default release format + if [[ -z $(${REDIS_CMDLINE} --raw GET Q_RELEASE_FORMAT) ]]; then + ${REDIS_CMDLINE} --raw SET Q_RELEASE_FORMAT raw + fi - for IP in "${API_ALLOW_FROM_ARR[@]}"; do - if [[ ${IP} =~ ${REGEX_IP6} ]] || [[ ${IP} =~ ${REGEX_IP4} ]]; then - VALIDATED_API_ALLOW_FROM_ARR+=("${IP}") - fi + # Set max age of q items - if unset + if [[ -z $(${REDIS_CMDLINE} --raw GET Q_MAX_AGE) ]]; then + ${REDIS_CMDLINE} --raw SET Q_MAX_AGE 365 + fi + + # Set default password policy - if unset + if [[ -z $(${REDIS_CMDLINE} --raw HGET PASSWD_POLICY length) ]]; then + ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY length 6 + ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY chars 0 + ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY special_chars 0 + ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY lowerupper 0 + ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY numbers 0 + fi + + # Trigger db init + echo "Running DB init..." + php -c /usr/local/etc/php -f /web/inc/init_db.inc.php + + # Recreating domain map + echo "Rebuilding domain map in Redis..." + declare -a DOMAIN_ARR + ${REDIS_CMDLINE} DEL DOMAIN_MAP > /dev/null + while read line + do + DOMAIN_ARR+=("$line") + done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs) + while read line + do + DOMAIN_ARR+=("$line") + done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs) + + if [[ ! -z ${DOMAIN_ARR} ]]; then + for domain in "${DOMAIN_ARR[@]}"; do + ${REDIS_CMDLINE} HSET DOMAIN_MAP ${domain} 1 > /dev/null done - VALIDATED_IPS=$(array_by_comma ${VALIDATED_API_ALLOW_FROM_ARR[*]}) - if [[ ! -z ${VALIDATED_IPS} ]]; then - mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF -DELETE FROM api; -INSERT INTO api (api_key, active, allow_from) VALUES ("${API_KEY}", "1", "${VALIDATED_IPS}"); + fi + + # Set API options if env vars are not empty + if [[ ${API_ALLOW_FROM} != "invalid" ]] && [[ ! -z ${API_ALLOW_FROM} ]]; then + IFS=',' read -r -a API_ALLOW_FROM_ARR <<< "${API_ALLOW_FROM}" + declare -a VALIDATED_API_ALLOW_FROM_ARR + REGEX_IP6='^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$' + REGEX_IP4='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/([0-9]|[1-2][0-9]|3[0-2]))?$' + for IP in "${API_ALLOW_FROM_ARR[@]}"; do + if [[ ${IP} =~ ${REGEX_IP6} ]] || [[ ${IP} =~ ${REGEX_IP4} ]]; then + VALIDATED_API_ALLOW_FROM_ARR+=("${IP}") + fi + done + VALIDATED_IPS=$(array_by_comma ${VALIDATED_API_ALLOW_FROM_ARR[*]}) + if [[ ! -z ${VALIDATED_IPS} ]]; then + if [[ ${API_KEY} != "invalid" ]] && [[ ! -z ${API_KEY} ]]; then + mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF +DELETE FROM api WHERE access = 'rw'; +INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY}", "1", "${VALIDATED_IPS}", "rw"); +EOF + fi + if [[ ${API_KEY_READ_ONLY} != "invalid" ]] && [[ ! -z ${API_KEY_READ_ONLY} ]]; then + mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF +DELETE FROM api WHERE access = 'ro'; +INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY_READ_ONLY}", "1", "${VALIDATED_IPS}", "ro"); EOF + fi + fi fi + + # Create events (master only, STATUS for event on slave will be SLAVESIDE_DISABLED) + mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF +DROP EVENT IF EXISTS clean_spamalias; +DELIMITER // +CREATE EVENT clean_spamalias +ON SCHEDULE EVERY 1 DAY DO +BEGIN + DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP() AND permanent = 0; +END; +// +DELIMITER ; +DROP EVENT IF EXISTS clean_oauth2; +DELIMITER // +CREATE EVENT clean_oauth2 +ON SCHEDULE EVERY 1 DAY DO +BEGIN + DELETE FROM oauth_refresh_tokens WHERE expires < NOW(); + DELETE FROM oauth_access_tokens WHERE expires < NOW(); + DELETE FROM oauth_authorization_codes WHERE expires < NOW(); +END; +// +DELIMITER ; +DROP EVENT IF EXISTS clean_sasl_log; +DELIMITER // +CREATE EVENT clean_sasl_log +ON SCHEDULE EVERY 1 DAY DO +BEGIN + DELETE sasl_log.* FROM sasl_log + LEFT JOIN ( + SELECT username, service, MAX(datetime) AS lastdate + FROM sasl_log + GROUP BY username, service + ) AS last ON sasl_log.username = last.username AND sasl_log.service = last.service + WHERE datetime < DATE_SUB(NOW(), INTERVAL 31 DAY) AND datetime < lastdate; + DELETE FROM sasl_log + WHERE username NOT IN (SELECT username FROM mailbox) AND + datetime < DATE_SUB(NOW(), INTERVAL 31 DAY); +END; +// +DELIMITER ; +EOF fi +# Create dummy for custom overrides of mailcow style +[[ ! -f /web/css/build/0081-custom-mailcow.css ]] && echo '/* Autogenerated by mailcow */' > /web/css/build/0081-custom-mailcow.css + +# Fix permissions for global filters +chown -R 82:82 /global_sieve/* + +# Fix permissions on twig cache folder +chown -R 82:82 /web/templates/cache +# Clear cache +find /web/templates/cache/* -not -name '.gitkeep' -delete + +# Run hooks +for file in /hooks/*; do + if [ -x "${file}" ]; then + echo "Running hook ${file}" + "${file}" + fi +done + exec "$@" diff --git a/data/Dockerfiles/postfix-tlspol/Dockerfile b/data/Dockerfiles/postfix-tlspol/Dockerfile new file mode 100644 index 0000000000..68f6eccedf --- /dev/null +++ b/data/Dockerfiles/postfix-tlspol/Dockerfile @@ -0,0 +1,50 @@ +FROM golang:1.25-bookworm AS builder +WORKDIR /src + +ENV CGO_ENABLED=0 \ + GO111MODULE=on \ + NOOPT=1 \ + VERSION=1.8.22 + +RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \ + cd /src/postfix-tlspol && \ + scripts/build.sh build-only + + +FROM debian:bookworm-slim +LABEL maintainer="The Infrastructure Company GmbH " + +ARG DEBIAN_FRONTEND=noninteractive +ENV LC_ALL=C + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + dirmngr \ + dnsutils \ + iputils-ping \ + sudo \ + supervisor \ + redis-tools \ + syslog-ng \ + syslog-ng-core \ + syslog-ng-mod-redis \ + tzdata \ + && rm -rf /var/lib/apt/lists/* \ + && touch /etc/default/locale + +COPY supervisord.conf /etc/supervisor/supervisord.conf +COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf +COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf +COPY postfix-tlspol.sh /opt/postfix-tlspol.sh +COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh +COPY docker-entrypoint.sh /docker-entrypoint.sh +COPY --from=builder /src/postfix-tlspol/build/postfix-tlspol /usr/local/bin/postfix-tlspol + +RUN chmod +x /opt/postfix-tlspol.sh \ + /usr/local/sbin/stop-supervisor.sh \ + /docker-entrypoint.sh +RUN rm -rf /tmp/* /var/tmp/* + +ENTRYPOINT ["/docker-entrypoint.sh"] + +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] \ No newline at end of file diff --git a/data/Dockerfiles/postfix-tlspol/docker-entrypoint.sh b/data/Dockerfiles/postfix-tlspol/docker-entrypoint.sh new file mode 100755 index 0000000000..8c4f2c43e5 --- /dev/null +++ b/data/Dockerfiles/postfix-tlspol/docker-entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf +fi + +exec "$@" \ No newline at end of file diff --git a/data/Dockerfiles/postfix-tlspol/postfix-tlspol.sh b/data/Dockerfiles/postfix-tlspol/postfix-tlspol.sh new file mode 100755 index 0000000000..407a08f6f1 --- /dev/null +++ b/data/Dockerfiles/postfix-tlspol/postfix-tlspol.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +LOGLVL=info + +if [ ${DEV_MODE} != "n" ]; then + echo -e "\e[31mEnabling debug mode\e[0m" + set -x + LOGLVL=debug +fi + +[[ ! -d /etc/postfix-tlspol ]] && mkdir -p /etc/postfix-tlspol +[[ ! -d /var/lib/postfix-tlspol ]] && mkdir -p /var/lib/postfix-tlspol + +until dig +short mailcow.email > /dev/null; do + echo "Waiting for DNS..." + sleep 1 +done + +# Do not attempt to write to slave +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + export REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning" +else + export REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning" +fi + +until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do + echo "Waiting for Redis..." + sleep 2 +done + +echo "Waiting for Postfix..." +until ping postfix -c1 > /dev/null; do + sleep 1 +done +echo "Postfix OK" + +cat < /etc/postfix-tlspol/config.yaml +server: + address: 0.0.0.0:8642 + + log-level: ${LOGLVL} + + prefetch: true + + cache-file: /var/lib/postfix-tlspol/cache.db + +dns: + # must support DNSSEC + address: 127.0.0.11:53 +EOF + +/usr/local/bin/postfix-tlspol -config /etc/postfix-tlspol/config.yaml \ No newline at end of file diff --git a/data/Dockerfiles/postfix-tlspol/stop-supervisor.sh b/data/Dockerfiles/postfix-tlspol/stop-supervisor.sh new file mode 100755 index 0000000000..5394490ceb --- /dev/null +++ b/data/Dockerfiles/postfix-tlspol/stop-supervisor.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +printf "READY\n"; + +while read line; do + echo "Processing Event: $line" >&2; + kill -3 $(cat "/var/run/supervisord.pid") +done < /dev/stdin diff --git a/data/Dockerfiles/postfix-tlspol/supervisord.conf b/data/Dockerfiles/postfix-tlspol/supervisord.conf new file mode 100644 index 0000000000..90cf785ad8 --- /dev/null +++ b/data/Dockerfiles/postfix-tlspol/supervisord.conf @@ -0,0 +1,25 @@ +[supervisord] +pidfile=/var/run/supervisord.pid +nodaemon=true +user=root + +[program:syslog-ng] +command=/usr/sbin/syslog-ng --foreground --no-caps +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autostart=true + +[program:postfix-tlspol] +startsecs=10 +autorestart=true +command=/opt/postfix-tlspol.sh +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[eventlistener:processes] +command=/usr/local/sbin/stop-supervisor.sh +events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL \ No newline at end of file diff --git a/data/Dockerfiles/postfix-tlspol/syslog-ng-redis_slave.conf b/data/Dockerfiles/postfix-tlspol/syslog-ng-redis_slave.conf new file mode 100644 index 0000000000..3862a3547a --- /dev/null +++ b/data/Dockerfiles/postfix-tlspol/syslog-ng-redis_slave.conf @@ -0,0 +1,45 @@ +@version: 3.38 +@include "scl.conf" +options { + chain_hostnames(off); + flush_lines(0); + use_dns(no); + dns_cache(no); + use_fqdn(no); + owner("root"); group("adm"); perm(0640); + stats_freq(0); + bad_hostname("^gconfd$"); +}; +source s_src { + unix-stream("/dev/log"); + internal(); +}; +destination d_stdout { pipe("/dev/stdout"); }; +destination d_redis_ui_log { + redis( + host("`REDIS_SLAVEOF_IP`") + persist-name("redis1") + port(`REDIS_SLAVEOF_PORT`) + auth("`REDISPASS`") + command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") + ); +}; +filter f_mail { facility(mail); }; +# start +# overriding warnings are still displayed when the entrypoint runs its initial check +# warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs +# Some other warnings are ignored +filter f_ignore { + not match("overriding earlier entry" value("MESSAGE")); + not match("TLS SNI from checks.mailcow.email" value("MESSAGE")); + not match("no SASL support" value("MESSAGE")); + not facility (local0, local1, local2, local3, local4, local5, local6, local7); +}; +# end +log { + source(s_src); + filter(f_ignore); + destination(d_stdout); + filter(f_mail); + destination(d_redis_ui_log); +}; diff --git a/data/Dockerfiles/postfix-tlspol/syslog-ng.conf b/data/Dockerfiles/postfix-tlspol/syslog-ng.conf new file mode 100644 index 0000000000..7126c12509 --- /dev/null +++ b/data/Dockerfiles/postfix-tlspol/syslog-ng.conf @@ -0,0 +1,45 @@ +@version: 3.38 +@include "scl.conf" +options { + chain_hostnames(off); + flush_lines(0); + use_dns(no); + dns_cache(no); + use_fqdn(no); + owner("root"); group("adm"); perm(0640); + stats_freq(0); + bad_hostname("^gconfd$"); +}; +source s_src { + unix-stream("/dev/log"); + internal(); +}; +destination d_stdout { pipe("/dev/stdout"); }; +destination d_redis_ui_log { + redis( + host("redis-mailcow") + persist-name("redis1") + port(6379) + auth("`REDISPASS`") + command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") + ); +}; +filter f_mail { facility(mail); }; +# start +# overriding warnings are still displayed when the entrypoint runs its initial check +# warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs +# Some other warnings are ignored +filter f_ignore { + not match("overriding earlier entry" value("MESSAGE")); + not match("TLS SNI from checks.mailcow.email" value("MESSAGE")); + not match("no SASL support" value("MESSAGE")); + not facility (local0, local1, local2, local3, local4, local5, local6, local7); +}; +# end +log { + source(s_src); + filter(f_ignore); + destination(d_stdout); + filter(f_mail); + destination(d_redis_ui_log); +}; diff --git a/data/Dockerfiles/postfix/Dockerfile b/data/Dockerfiles/postfix/Dockerfile index 9ad52fb294..cf06476922 100644 --- a/data/Dockerfiles/postfix/Dockerfile +++ b/data/Dockerfiles/postfix/Dockerfile @@ -1,56 +1,63 @@ -FROM ubuntu:bionic -LABEL maintainer "Andre Peters " +FROM debian:trixie-slim + +LABEL maintainer="The Infrastructure Company GmbH " ARG DEBIAN_FRONTEND=noninteractive -ENV LC_ALL C +ENV LC_ALL=C RUN dpkg-divert --local --rename --add /sbin/initctl \ && ln -sf /bin/true /sbin/initctl \ && dpkg-divert --local --rename --add /usr/bin/ischroot \ && ln -sf /bin/true /usr/bin/ischroot -RUN apt-get update && apt-get install -y --no-install-recommends \ +# Add groups and users before installing Postfix to not break compatibility +RUN groupadd -g 102 postfix \ + && groupadd -g 103 postdrop \ + && useradd -g postfix -u 101 -d /var/spool/postfix -s /usr/sbin/nologin postfix \ + && apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ dirmngr \ + dnsutils \ gnupg \ libsasl2-modules \ + mariadb-client \ perl \ postfix \ postfix-mysql \ postfix-pcre \ - python-gpg \ + redis-tools \ sasl2-bin \ sudo \ supervisor \ syslog-ng \ syslog-ng-core \ syslog-ng-mod-redis \ - tzdata \ + tzdata \ && rm -rf /var/lib/apt/lists/* \ && touch /etc/default/locale \ && printf '#!/bin/bash\n/usr/sbin/postconf -c /opt/postfix/conf "$@"' > /usr/local/sbin/postconf \ && chmod +x /usr/local/sbin/postconf -RUN addgroup --system --gid 600 zeyple \ - && adduser --system --home /var/lib/zeyple --no-create-home --uid 600 --gid 600 --disabled-login zeyple \ - && touch /var/log/zeyple.log \ - && chown zeyple: /var/log/zeyple.log \ - && mkdir -p /opt/mailman/var/data \ - && touch /opt/mailman/var/data/postfix_lmtp \ - && touch /opt/mailman/var/data/postfix_domains - -COPY zeyple.py /usr/local/bin/zeyple.py -COPY zeyple.conf /etc/zeyple.conf COPY supervisord.conf /etc/supervisor/supervisord.conf COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf +COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf COPY postfix.sh /opt/postfix.sh COPY rspamd-pipe-ham /usr/local/bin/rspamd-pipe-ham COPY rspamd-pipe-spam /usr/local/bin/rspamd-pipe-spam COPY whitelist_forwardinghosts.sh /usr/local/bin/whitelist_forwardinghosts.sh +COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh +COPY docker-entrypoint.sh /docker-entrypoint.sh + +RUN chmod +x /opt/postfix.sh \ + /usr/local/bin/rspamd-pipe-ham \ + /usr/local/bin/rspamd-pipe-spam \ + /usr/local/bin/whitelist_forwardinghosts.sh \ + /usr/local/sbin/stop-supervisor.sh +RUN rm -rf /tmp/* /var/tmp/* EXPOSE 588 -CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf +ENTRYPOINT ["/docker-entrypoint.sh"] -RUN rm -rf /tmp/* /var/tmp/* +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] diff --git a/data/Dockerfiles/postfix/docker-entrypoint.sh b/data/Dockerfiles/postfix/docker-entrypoint.sh new file mode 100755 index 0000000000..7b6c5d4aa0 --- /dev/null +++ b/data/Dockerfiles/postfix/docker-entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Run hooks +for file in /hooks/*; do + if [ -x "${file}" ]; then + echo "Running hook ${file}" + "${file}" + fi +done + +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf +fi + +# Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20) +if grep -qE '\!SSLv2|\!SSLv3|>=TLSv1(\.[0-1])?$' /opt/postfix/conf/main.cf /opt/postfix/conf/extra.cf; then + sed -i '/\[openssl_init\]/a ssl_conf = ssl_configuration' /etc/ssl/openssl.cnf + + echo "[ssl_configuration]" >> /etc/ssl/openssl.cnf + echo "system_default = tls_system_default" >> /etc/ssl/openssl.cnf + echo "[tls_system_default]" >> /etc/ssl/openssl.cnf + echo "MinProtocol = TLSv1" >> /etc/ssl/openssl.cnf + echo "CipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf +fi + +exec "$@" diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index 83ec94e08f..51927ea11f 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -4,14 +4,63 @@ trap "postfix stop" EXIT [[ ! -d /opt/postfix/conf/sql/ ]] && mkdir -p /opt/postfix/conf/sql/ +# Wait for MySQL to warm-up +while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do + echo "Waiting for database to come up..." + sleep 2 +done + +until dig +short mailcow.email > /dev/null; do + echo "Waiting for DNS..." + sleep 1 +done + cat < /etc/aliases +# Autogenerated by mailcow null: /dev/null +watchdog: /dev/null ham: "|/usr/local/bin/rspamd-pipe-ham" spam: "|/usr/local/bin/rspamd-pipe-spam" EOF newaliases; +# create sni configuration +if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + echo -n "" > /opt/postfix/conf/sni.map +else + echo -n "" > /opt/postfix/conf/sni.map; + for cert_dir in /etc/ssl/mail/*/ ; do + if [[ ! -f ${cert_dir}domains ]] || [[ ! -f ${cert_dir}cert.pem ]] || [[ ! -f ${cert_dir}key.pem ]]; then + continue; + fi + IFS=" " read -r -a domains <<< "$(cat "${cert_dir}domains")" + for domain in "${domains[@]}"; do + echo -n "${domain} ${cert_dir}key.pem ${cert_dir}cert.pem" >> /opt/postfix/conf/sni.map; + echo "" >> /opt/postfix/conf/sni.map; + done + done +fi +postmap -F hash:/opt/postfix/conf/sni.map; + +cat < /opt/postfix/conf/sql/mysql_relay_ne.cf +# Autogenerated by mailcow +user = ${DBUSER} +password = ${DBPASS} +hosts = unix:/var/run/mysqld/mysqld.sock +dbname = ${DBNAME} +query = SELECT IF(EXISTS(SELECT address, domain FROM alias + WHERE address = '%s' + AND domain IN ( + SELECT domain FROM domain + WHERE backupmx = '1' + AND relay_all_recipients = '1' + AND relay_unknown_only = '1') + + ), 'lmtp:inet:dovecot:24', NULL) AS 'transport' +EOF + cat < /opt/postfix/conf/sql/mysql_relay_recipient_maps.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock @@ -30,6 +79,7 @@ query = SELECT DISTINCT EOF cat < /opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock @@ -38,6 +88,7 @@ query = SELECT CONCAT(policy, ' ', parameters) AS tls_policy FROM tls_policy_ove EOF cat < /opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock @@ -50,11 +101,12 @@ query = SELECT IF(EXISTS( SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d' ) - ) AND json_extract(attributes, '$.tls_enforce_in') LIKE '%%1%%' AND mailbox.active = '1' + ) AND JSON_UNQUOTE(JSON_VALUE(attributes, '$.tls_enforce_in')) = '1' AND mailbox.active = '1' ), 'reject_plaintext_session', NULL) AS 'tls_enforce_in'; EOF cat < /opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock @@ -69,50 +121,111 @@ query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps WHERE alias_domain = '%d' ) ) - AND json_extract(attributes, '$.tls_enforce_out') LIKE '%%1%%' + AND JSON_UNQUOTE(JSON_VALUE(attributes, '$.tls_enforce_out')) = '1' AND mailbox.active = '1' ), 'smtp_enforced_tls:', 'smtp:') AS 'transport' UNION ALL - SELECT hostname AS transport FROM relayhosts + SELECT COALESCE( + (SELECT hostname FROM relayhosts + LEFT OUTER JOIN mailbox ON JSON_UNQUOTE(JSON_VALUE(mailbox.attributes, '$.relayhost')) = relayhosts.id + WHERE relayhosts.active = '1' + AND ( + mailbox.username IN (SELECT alias.goto from alias + JOIN mailbox ON mailbox.username = alias.goto + WHERE alias.active = '1' + AND alias.address = '%s' + AND alias.address NOT LIKE '@%%' + ) + ) + ), + (SELECT hostname FROM relayhosts LEFT OUTER JOIN domain ON domain.relayhost = relayhosts.id WHERE relayhosts.active = '1' - AND domain = '%d' - OR domain IN ( - SELECT target_domain FROM alias_domain - WHERE alias_domain = '%d' + AND (domain.domain = '%d' + OR domain.domain IN ( + SELECT target_domain FROM alias_domain + WHERE alias_domain = '%d' + ) ) - ) - AS transport_view; + ) + ) + ) AS transport_view; EOF -cat < /opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf +cat < /opt/postfix/conf/sql/mysql_transport_maps.cf +# Autogenerated by mailcow +user = ${DBUSER} +password = ${DBPASS} +hosts = unix:/var/run/mysqld/mysqld.sock +dbname = ${DBNAME} +query = SELECT CONCAT('smtp_via_transport_maps:', nexthop) AS transport FROM transports + WHERE active = '1' + AND destination = '%s'; +EOF + +cat < /opt/postfix/conf/sql/mysql_virtual_resource_maps.cf +# Autogenerated by mailcow +user = ${DBUSER} +password = ${DBPASS} +hosts = unix:/var/run/mysqld/mysqld.sock +dbname = ${DBNAME} +query = SELECT 'null@localhost' FROM mailbox + WHERE kind REGEXP 'location|thing|group' AND username = '%s'; +EOF + +cat < /opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM relayhosts WHERE id IN ( - SELECT relayhost FROM domain - WHERE CONCAT('@', domain) = '%s' - OR '%s' IN ( - SELECT CONCAT('@', alias_domain) FROM alias_domain + SELECT COALESCE( + (SELECT id FROM relayhosts + LEFT OUTER JOIN domain ON domain.relayhost = relayhosts.id + WHERE relayhosts.active = '1' + AND (domain.domain = '%d' + OR domain.domain IN ( + SELECT target_domain FROM alias_domain + WHERE alias_domain = '%d' + ) + ) + ), + (SELECT id FROM relayhosts + LEFT OUTER JOIN mailbox ON JSON_UNQUOTE(JSON_VALUE(mailbox.attributes, '$.relayhost')) = relayhosts.id + WHERE relayhosts.active = '1' + AND ( + mailbox.username IN ( + SELECT alias.goto from alias + JOIN mailbox ON mailbox.username = alias.goto + WHERE alias.active = '1' + AND alias.address = '%s' + AND alias.address NOT LIKE '@%%' + ) + ) ) + ) ) + AND active = '1' AND username != ''; EOF -cat < /opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf +cat < /opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} -query = SELECT goto FROM alias, alias_domain - WHERE alias_domain.alias_domain = '%d' - AND alias.address = CONCAT('@', alias_domain.target_domain) - AND alias.active = 1 AND alias_domain.active='1' +query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM transports + WHERE nexthop = '%s' + AND active = '1' + AND username != '' + LIMIT 1; EOF cat < /opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock @@ -120,21 +233,23 @@ dbname = ${DBNAME} query = SELECT username FROM mailbox, alias_domain WHERE alias_domain.alias_domain = '%d' AND mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) - AND mailbox.active = '1' + AND (mailbox.active = '1' OR mailbox.active = '2') AND alias_domain.active='1' EOF cat < /opt/postfix/conf/sql/mysql_virtual_alias_maps.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT goto FROM alias WHERE address='%s' - AND active='1'; + AND (active='1' OR active='2'); EOF cat < /opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock @@ -146,6 +261,7 @@ query = SELECT bcc_dest FROM bcc_maps EOF cat < /opt/postfix/conf/sql/mysql_sender_bcc_maps.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock @@ -157,6 +273,7 @@ query = SELECT bcc_dest FROM bcc_maps EOF cat < /opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock @@ -167,6 +284,7 @@ query = SELECT new_dest FROM recipient_maps EOF cat < /opt/postfix/conf/sql/mysql_virtual_domains_maps.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock @@ -180,14 +298,16 @@ query = SELECT alias_domain from alias_domain WHERE alias_domain='%s' AND active EOF cat < /opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} -query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1' +query = SELECT CONCAT(JSON_UNQUOTE(JSON_VALUE(attributes, '$.mailbox_format')), mailbox_path_prefix, '%d/%u/') FROM mailbox WHERE username='%s' AND (active = '1' OR active = '2') EOF cat < /opt/postfix/conf/sql/mysql_virtual_relay_domain_maps.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock @@ -196,14 +316,30 @@ query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '1' AND activ EOF cat < /opt/postfix/conf/sql/mysql_virtual_sender_acl.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} # First select queries domain and alias_domain to determine if domains are active. query = SELECT goto FROM alias - WHERE address='%s' + WHERE id IN ( + SELECT COALESCE ( + ( + SELECT id FROM alias + WHERE address='%s' + AND (active='1' OR active='2') + AND sender_allowed='1' + ), ( + SELECT id FROM alias + WHERE address='@%d' + AND (active='1' OR active='2') + AND sender_allowed='1' + ) + ) + ) AND active='1' + AND sender_allowed='1' AND (domain IN (SELECT domain FROM domain WHERE domain='%d' @@ -232,34 +368,158 @@ query = SELECT goto FROM alias SELECT username FROM mailbox, alias_domain WHERE alias_domain.alias_domain = '%d' AND mailbox.username = CONCAT('%u','@',alias_domain.target_domain) - AND mailbox.active ='1' - AND alias_domain.active='1' + AND (mailbox.active = '1' OR mailbox.active ='2') + AND alias_domain.active='1'; +EOF + +# MX based routing +cat < /opt/postfix/conf/sql/mysql_mbr_access_maps.cf +# Autogenerated by mailcow +user = ${DBUSER} +password = ${DBPASS} +hosts = unix:/var/run/mysqld/mysqld.sock +dbname = ${DBNAME} +query = SELECT CONCAT('FILTER smtp_via_transport_maps:', nexthop) as transport FROM transports + WHERE '%s' REGEXP destination + AND active='1' + AND is_mx_based='1'; EOF cat < /opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf +# Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT goto FROM spamalias WHERE address='%s' - AND validity >= UNIX_TIMESTAMP() + AND (validity >= UNIX_TIMESTAMP() OR permanent != 0) EOF -# Reset GPG key permissions -mkdir -p /var/lib/zeyple/keys -chmod 700 /var/lib/zeyple/keys -chown -R 600:600 /var/lib/zeyple/keys +if [ ! -f /opt/postfix/conf/dns_blocklists.cf ]; then + cat < /opt/postfix/conf/dns_blocklists.cf +# This file can be edited. +# Delete this file and restart postfix container to revert any changes. +postscreen_dnsbl_sites = wl.mailspike.net=127.0.0.[18;19;20]*-2 + hostkarma.junkemailfilter.com=127.0.0.1*-2 + list.dnswl.org=127.0.[0..255].0*-2 + list.dnswl.org=127.0.[0..255].1*-4 + list.dnswl.org=127.0.[0..255].2*-6 + list.dnswl.org=127.0.[0..255].3*-8 + bl.spamcop.net*2 + bl.suomispam.net*2 + hostkarma.junkemailfilter.com=127.0.0.2*3 + hostkarma.junkemailfilter.com=127.0.0.4*2 + hostkarma.junkemailfilter.com=127.0.1.2*1 + backscatter.spameatingmonkey.net*2 + bl.ipv6.spameatingmonkey.net*2 + bl.spameatingmonkey.net*2 + b.barracudacentral.org=127.0.0.2*7 + bl.mailspike.net=127.0.0.2*5 + bl.mailspike.net=127.0.0.[10;11;12]*4 +EOF +fi + +# Remove discontinued DNSBLs from existing dns_blocklists.cf +sed -i '/ix\.dnsbl\.manitu\.net\*2/d' /opt/postfix/conf/dns_blocklists.cf # Nixspam + +DNSBL_CONFIG=$(grep -v '^#' /opt/postfix/conf/dns_blocklists.cf | grep '\S') + +if [ ! -z "$DNSBL_CONFIG" ]; then + echo -e "\e[33mChecking if ASN for your IP is listed for Spamhaus Bad ASN List...\e[0m" + if [ -n "$SPAMHAUS_DQS_KEY" ]; then + echo -e "\e[32mDetected SPAMHAUS_DQS_KEY variable from mailcow.conf...\e[0m" + echo -e "\e[33mUsing DQS Blocklists from Spamhaus!\e[0m" + SPAMHAUS_DNSBL_CONFIG=$(cat < /opt/postfix/conf/dnsbl_reply.map +# Autogenerated by mailcow, using Spamhaus DQS reply domains +${SPAMHAUS_DQS_KEY}.sbl.dq.spamhaus.net sbl.spamhaus.org +${SPAMHAUS_DQS_KEY}.xbl.dq.spamhaus.net xbl.spamhaus.org +${SPAMHAUS_DQS_KEY}.pbl.dq.spamhaus.net pbl.spamhaus.org +${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net zen.spamhaus.org +${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net dbl.spamhaus.org +${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net zrd.spamhaus.org +EOF + ) + else + if [ -f "/opt/postfix/conf/dnsbl_reply.map" ]; then + rm /opt/postfix/conf/dnsbl_reply.map + fi + response=$(curl --connect-timeout 15 --max-time 30 -s -o /dev/null -w "%{http_code}" "https://asn-check.mailcow.email") + if [ "$response" -eq 503 ]; then + echo -e "\e[31mThe AS of your IP is listed as a banned AS from Spamhaus!\e[0m" + echo -e "\e[33mNo SPAMHAUS_DQS_KEY found... Skipping Spamhaus blocklists entirely!\e[0m" + SPAMHAUS_DNSBL_CONFIG="" + elif [ "$response" -eq 200 ]; then + echo -e "\e[32mThe AS of your IP is NOT listed as a banned AS from Spamhaus!\e[0m" + echo -e "\e[33mUsing the open Spamhaus blocklists.\e[0m" + SPAMHAUS_DNSBL_CONFIG=$(cat <> /opt/postfix/conf/main.cf +# Append postscreen dnsbl sites to main.cf +if [ ! -z "$DNSBL_CONFIG" ]; then + echo -e "${DNSBL_CONFIG}\n${SPAMHAUS_DNSBL_CONFIG}" >> /opt/postfix/conf/main.cf +fi +# Append user overrides +echo -e "\n# User Overrides" >> /opt/postfix/conf/main.cf +touch /opt/postfix/conf/extra.cf +sed -i '/\$myhostname/! { /myhostname/d }' /opt/postfix/conf/extra.cf +echo -e "myhostname = ${MAILCOW_HOSTNAME}\n$(cat /opt/postfix/conf/extra.cf)" > /opt/postfix/conf/extra.cf +cat /opt/postfix/conf/extra.cf >> /opt/postfix/conf/main.cf + +if [ ! -f /opt/postfix/conf/custom_transport.pcre ]; then + echo "Creating dummy custom_transport.pcre" + touch /opt/postfix/conf/custom_transport.pcre +fi + +if [[ ! -f /opt/postfix/conf/custom_postscreen_whitelist.cidr ]]; then + echo "Creating dummy custom_postscreen_whitelist.cidr" + cat < /opt/postfix/conf/custom_postscreen_whitelist.cidr +# Autogenerated by mailcow +# Rules are evaluated in the order as specified. +# Blacklist 192.168.* except 192.168.0.1. +# 192.168.0.1 permit +# 192.168.0.0/16 reject +EOF +fi # Fix Postfix permissions -chown -R root:postfix /opt/postfix/conf/sql/ -chmod 640 /opt/postfix/conf/sql/*.cf +chown -R root:postfix /opt/postfix/conf/sql/ /opt/postfix/conf/custom_transport.pcre +chmod 640 /opt/postfix/conf/sql/*.cf /opt/postfix/conf/custom_transport.pcre chgrp -R postdrop /var/spool/postfix/public chgrp -R postdrop /var/spool/postfix/maildrop postfix set-permissions +# Checking if there is a leftover of a crashed postfix container before starting a new one +if [ -e /var/spool/postfix/pid/master.pid ]; then + rm -rf /var/spool/postfix/pid/master.pid +fi + # Check Postfix configuration -postconf -c /opt/postfix/conf +postconf -c /opt/postfix/conf > /dev/null if [[ $? != 0 ]]; then echo "Postfix configuration error, refusing to start." diff --git a/data/Dockerfiles/postfix/stop-supervisor.sh b/data/Dockerfiles/postfix/stop-supervisor.sh new file mode 100755 index 0000000000..5394490ceb --- /dev/null +++ b/data/Dockerfiles/postfix/stop-supervisor.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +printf "READY\n"; + +while read line; do + echo "Processing Event: $line" >&2; + kill -3 $(cat "/var/run/supervisord.pid") +done < /dev/stdin diff --git a/data/Dockerfiles/postfix/supervisord.conf b/data/Dockerfiles/postfix/supervisord.conf index b5ae6b54f8..ba70f8edf8 100644 --- a/data/Dockerfiles/postfix/supervisord.conf +++ b/data/Dockerfiles/postfix/supervisord.conf @@ -1,4 +1,5 @@ [supervisord] +pidfile=/var/run/supervisord.pid nodaemon=true user=root @@ -12,4 +13,13 @@ autostart=true [program:postfix] command=/opt/postfix.sh +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 autorestart=true +startsecs=10 + +[eventlistener:processes] +command=/usr/local/sbin/stop-supervisor.sh +events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL diff --git a/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf b/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf new file mode 100644 index 0000000000..bbc9420fe4 --- /dev/null +++ b/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf @@ -0,0 +1,55 @@ +@version: 4.8 +@include "scl.conf" +options { + chain_hostnames(off); + flush_lines(0); + use_dns(no); + dns_cache(no); + use_fqdn(no); + owner("root"); group("adm"); perm(0640); + stats(freq(0)); + bad_hostname("^gconfd$"); +}; +source s_src { + unix-stream("/dev/log"); + internal(); +}; +destination d_stdout { pipe("/dev/stdout"); }; +destination d_redis_ui_log { + redis( + host("`REDIS_SLAVEOF_IP`") + persist-name("redis1") + port(`REDIS_SLAVEOF_PORT`) + auth("`REDISPASS`") + command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") + ); +}; +destination d_redis_f2b_channel { + redis( + host("`REDIS_SLAVEOF_IP`") + persist-name("redis2") + port(`REDIS_SLAVEOF_PORT`) + auth("`REDISPASS`") + command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") + ); +}; +filter f_mail { facility(mail); }; +# start +# overriding warnings are still displayed when the entrypoint runs its initial check +# warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs +# Some other warnings are ignored +filter f_ignore { + not match("overriding earlier entry" value("MESSAGE")); + not match("TLS SNI from checks.mailcow.email" value("MESSAGE")); + not match("no SASL support" value("MESSAGE")); + not facility (local0, local1, local2, local3, local4, local5, local6, local7); +}; +# end +log { + source(s_src); + filter(f_ignore); + destination(d_stdout); + filter(f_mail); + destination(d_redis_ui_log); + destination(d_redis_f2b_channel); +}; diff --git a/data/Dockerfiles/postfix/syslog-ng.conf b/data/Dockerfiles/postfix/syslog-ng.conf index 5d11a3b7c5..8f09d04a49 100644 --- a/data/Dockerfiles/postfix/syslog-ng.conf +++ b/data/Dockerfiles/postfix/syslog-ng.conf @@ -1,12 +1,13 @@ -@version: 3.13 +@version: 4.8 @include "scl.conf" options { chain_hostnames(off); flush_lines(0); use_dns(no); + dns_cache(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); - stats_freq(0); + stats(freq(0)); bad_hostname("^gconfd$"); }; source s_src { @@ -19,6 +20,7 @@ destination d_redis_ui_log { host("redis-mailcow") persist-name("redis1") port(6379) + auth("`REDISPASS`") command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") ); }; @@ -27,14 +29,25 @@ destination d_redis_f2b_channel { host("redis-mailcow") persist-name("redis2") port(6379) - command("PUBLISH" "F2B_CHANNEL" "$MESSAGE") + auth("`REDISPASS`") + command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; filter f_mail { facility(mail); }; -filter f_skip_local { not facility (local0, local1, local2, local3, local4, local5, local6, local7); }; +# start +# overriding warnings are still displayed when the entrypoint runs its initial check +# warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs +# Some other warnings are ignored +filter f_ignore { + not match("overriding earlier entry" value("MESSAGE")); + not match("TLS SNI from checks.mailcow.email" value("MESSAGE")); + not match("no SASL support" value("MESSAGE")); + not facility (local0, local1, local2, local3, local4, local5, local6, local7); +}; +# end log { source(s_src); - filter(f_skip_local); + filter(f_ignore); destination(d_stdout); filter(f_mail); destination(d_redis_ui_log); diff --git a/data/Dockerfiles/postfix/zeyple.conf b/data/Dockerfiles/postfix/zeyple.conf deleted file mode 100644 index cc176a0ea5..0000000000 --- a/data/Dockerfiles/postfix/zeyple.conf +++ /dev/null @@ -1,9 +0,0 @@ -[zeyple] -log_file = /dev/null - -[gpg] -home = /var/lib/zeyple/keys - -[relay] -host = localhost -port = 10026 diff --git a/data/Dockerfiles/postfix/zeyple.py b/data/Dockerfiles/postfix/zeyple.py deleted file mode 100755 index bb2188314b..0000000000 --- a/data/Dockerfiles/postfix/zeyple.py +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import sys -import os -import logging -import email -import email.mime.multipart -import email.mime.application -import email.encoders -import smtplib -import copy -from io import BytesIO - -try: - from configparser import SafeConfigParser # Python 3 -except ImportError: - from ConfigParser import SafeConfigParser # Python 2 - -import gpgme - -# Boiler plate to avoid dependency on six -# BBB: Python 2.7 support -PY3K = sys.version_info > (3, 0) - - -def message_from_binary(message): - if PY3K: - return email.message_from_bytes(message) - else: - return email.message_from_string(message) - - -def as_binary_string(email): - if PY3K: - return email.as_bytes() - else: - return email.as_string() - - -def encode_string(string): - if isinstance(string, bytes): - return string - else: - return string.encode('utf-8') - - -__title__ = 'Zeyple' -__version__ = '1.2.0' -__author__ = 'Cรฉdric Fรฉlizard' -__license__ = 'AGPLv3+' -__copyright__ = 'Copyright 2012-2016 Cรฉdric Fรฉlizard' - - -class Zeyple: - """Zeyple Encrypts Your Precious Log Emails""" - - def __init__(self, config_fname='zeyple.conf'): - self.config = self.load_configuration(config_fname) - - log_file = self.config.get('zeyple', 'log_file') - logging.basicConfig( - filename=log_file, level=logging.DEBUG, - format='%(asctime)s %(process)s %(levelname)s %(message)s' - ) - logging.info("Zeyple ready to encrypt outgoing emails") - - def load_configuration(self, filename): - """Reads and parses the config file""" - - config = SafeConfigParser() - config.read([ - os.path.join('/etc/', filename), - filename, - ]) - if not config.sections(): - raise IOError('Cannot open config file.') - return config - - @property - def gpg(self): - protocol = gpgme.PROTOCOL_OpenPGP - - if self.config.has_option('gpg', 'executable'): - executable = self.config.get('gpg', 'executable') - else: - executable = None # Default value - - home_dir = self.config.get('gpg', 'home') - - ctx = gpgme.Context() - ctx.set_engine_info(protocol, executable, home_dir) - ctx.armor = True - - return ctx - - def process_message(self, message_data, recipients): - """Encrypts the message with recipient keys""" - message_data = encode_string(message_data) - - in_message = message_from_binary(message_data) - logging.info( - "Processing outgoing message %s", in_message['Message-id']) - - if not recipients: - logging.warn("Cannot find any recipients, ignoring") - - sent_messages = [] - for recipient in recipients: - logging.info("Recipient: %s", recipient) - - key_id = self._user_key(recipient) - logging.info("Key ID: %s", key_id) - - if key_id: - out_message = self._encrypt_message(in_message, key_id) - - # Delete Content-Transfer-Encoding if present to default to - # "7bit" otherwise Thunderbird seems to hang in some cases. - del out_message["Content-Transfer-Encoding"] - else: - logging.warn("No keys found, message will be sent unencrypted") - out_message = copy.copy(in_message) - - self._add_zeyple_header(out_message) - self._send_message(out_message, recipient) - sent_messages.append(out_message) - - return sent_messages - - def _get_version_part(self): - ret = email.mime.application.MIMEApplication( - 'Version: 1\n', - 'pgp-encrypted', - email.encoders.encode_noop, - ) - ret.add_header( - 'Content-Description', - "PGP/MIME version identification", - ) - return ret - - def _get_encrypted_part(self, payload): - ret = email.mime.application.MIMEApplication( - payload, - 'octet-stream', - email.encoders.encode_noop, - name="encrypted.asc", - ) - ret.add_header('Content-Description', "OpenPGP encrypted message") - ret.add_header( - 'Content-Disposition', - 'inline', - filename='encrypted.asc', - ) - return ret - - def _encrypt_message(self, in_message, key_id): - if in_message.is_multipart(): - # get the body (after the first \n\n) - payload = in_message.as_string().split("\n\n", 1)[1].strip() - - # prepend the Content-Type including the boundary - content_type = "Content-Type: " + in_message["Content-Type"] - payload = content_type + "\n\n" + payload - - message = email.message.Message() - message.set_payload(payload) - - payload = message.get_payload() - - else: - payload = in_message.get_payload() - payload = encode_string(payload) - - quoted_printable = email.charset.Charset('ascii') - quoted_printable.body_encoding = email.charset.QP - - message = email.mime.nonmultipart.MIMENonMultipart( - 'text', 'plain', charset='utf-8' - ) - message.set_payload(payload, charset=quoted_printable) - - mixed = email.mime.multipart.MIMEMultipart( - 'mixed', - None, - [message], - ) - - # remove superfluous header - del mixed['MIME-Version'] - - payload = as_binary_string(mixed) - - encrypted_payload = self._encrypt_payload(payload, [key_id]) - - version = self._get_version_part() - encrypted = self._get_encrypted_part(encrypted_payload) - - out_message = copy.copy(in_message) - out_message.preamble = "This is an OpenPGP/MIME encrypted " \ - "message (RFC 4880 and 3156)" - - if 'Content-Type' not in out_message: - out_message['Content-Type'] = 'multipart/encrypted' - else: - out_message.replace_header( - 'Content-Type', - 'multipart/encrypted', - ) - - out_message.set_param('protocol', 'application/pgp-encrypted') - out_message.set_payload([version, encrypted]) - - return out_message - - def _encrypt_payload(self, payload, key_ids): - """Encrypts the payload with the given keys""" - payload = encode_string(payload) - - plaintext = BytesIO(payload) - ciphertext = BytesIO() - - self.gpg.armor = True - - recipient = [self.gpg.get_key(key_id) for key_id in key_ids] - - self.gpg.encrypt(recipient, gpgme.ENCRYPT_ALWAYS_TRUST, - plaintext, ciphertext) - - return ciphertext.getvalue() - - def _user_key(self, email): - """Returns the GPG key for the given email address""" - logging.info("Trying to encrypt for %s", email) - keys = [key for key in self.gpg.keylist(email)] - - if keys: - key = keys.pop() # NOTE: looks like keys[0] is the master key - key_id = key.subkeys[0].keyid - return key_id - - return None - - def _add_zeyple_header(self, message): - if self.config.has_option('zeyple', 'add_header') and \ - self.config.getboolean('zeyple', 'add_header'): - message.add_header( - 'X-Zeyple', - "processed by {0} v{1}".format(__title__, __version__) - ) - - def _send_message(self, message, recipient): - """Sends the given message through the SMTP relay""" - logging.info("Sending message %s", message['Message-id']) - - smtp = smtplib.SMTP(self.config.get('relay', 'host'), - self.config.get('relay', 'port')) - - smtp.sendmail(message['From'], recipient, message.as_string()) - smtp.quit() - - logging.info("Message %s sent", message['Message-id']) - - -if __name__ == '__main__': - recipients = sys.argv[1:] - - # BBB: Python 2.7 support - binary_stdin = sys.stdin.buffer if PY3K else sys.stdin - message = binary_stdin.read() - - zeyple = Zeyple() - zeyple.process_message(message, recipients) diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile index daf9760b0c..13e56357bb 100644 --- a/data/Dockerfiles/rspamd/Dockerfile +++ b/data/Dockerfiles/rspamd/Dockerfile @@ -1,28 +1,40 @@ -FROM ubuntu:xenial -LABEL maintainer "Andre Peters " +FROM debian:trixie-slim +LABEL maintainer="The Infrastructure Company GmbH " ARG DEBIAN_FRONTEND=noninteractive -ENV LC_ALL C +ARG RSPAMD_VER=rspamd_4.1.4-1~beb659b +ARG CODENAME=trixie +ENV LC_ALL=C -RUN apt-get update && apt-get install -y \ +RUN apt-get update && apt-get install -y --no-install-recommends \ tzdata \ - ca-certificates \ - gnupg2 \ - gnupg-curl \ - apt-transport-https \ - && apt-key adv --fetch-keys https://rspamd.com/apt/gpg.key \ - && echo "deb https://rspamd.com/apt-stable/ xenial main" > /etc/apt/sources.list.d/rspamd.list \ - && apt-get update && apt-get install -y rspamd \ - && rm -rf /var/lib/apt/lists/* \ - && echo '.include $LOCAL_CONFDIR/local.d/rspamd.conf.local' > /etc/rspamd/rspamd.conf.local \ - && apt-get autoremove --purge \ - && apt-get clean \ - && mkdir -p /run/rspamd \ - && chown _rspamd:_rspamd /run/rspamd + ca-certificates \ + gnupg2 \ + apt-transport-https \ + dnsutils \ + netcat-traditional \ + wget \ + redis-tools \ + procps \ + nano \ + lua-cjson \ + && arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \ + && wget -P /tmp https://rspamd.com/apt-stable/pool/main/r/rspamd/${RSPAMD_VER}~${CODENAME}_${arch}.deb\ + && apt install -y /tmp/${RSPAMD_VER}~${CODENAME}_${arch}.deb \ + && rm -rf /var/lib/apt/lists/* /tmp/*\ + && apt-get autoremove --purge \ + && apt-get clean \ + && mkdir -p /run/rspamd \ + && chown _rspamd:_rspamd /run/rspamd \ + && echo 'alias ll="ls -la --color"' >> ~/.bashrc \ + && sed -i 's/#analysis_keyword_table > 0/analysis_cat_table.macro_exist == "M"/g' /usr/share/rspamd/lualib/lua_scanners/oletools.lua COPY settings.conf /etc/rspamd/settings.conf +COPY set_worker_password.sh /set_worker_password.sh COPY docker-entrypoint.sh /docker-entrypoint.sh ENTRYPOINT ["/docker-entrypoint.sh"] +STOPSIGNAL SIGTERM + CMD ["/usr/bin/rspamd", "-f", "-u", "_rspamd", "-g", "_rspamd"] diff --git a/data/Dockerfiles/rspamd/docker-entrypoint.sh b/data/Dockerfiles/rspamd/docker-entrypoint.sh index 6288550d35..a764725cb7 100755 --- a/data/Dockerfiles/rspamd/docker-entrypoint.sh +++ b/data/Dockerfiles/rspamd/docker-entrypoint.sh @@ -1,9 +1,338 @@ #!/bin/bash -chown -R _rspamd:_rspamd /var/lib/rspamd /etc/rspamd/local.d /etc/rspamd/override.d /etc/rspamd/custom +until nc phpfpm 9001 -z; do + echo "Waiting for PHP on port 9001..." + sleep 3 +done + +until nc phpfpm 9002 -z; do + echo "Waiting for PHP on port 9002..." + sleep 3 +done + +mkdir -p /etc/rspamd/plugins.d \ + /etc/rspamd/custom + +touch /etc/rspamd/rspamd.conf.local \ + /etc/rspamd/rspamd.conf.override + chmod 755 /var/lib/rspamd -[[ ! -f /etc/rspamd/override.d/worker-controller-password.inc ]] && echo '# Placeholder' > /etc/rspamd/override.d/worker-controller-password.inc -chown _rspamd:_rspamd /etc/rspamd/override.d/worker-controller-password.inc -[[ ! -f /etc/rspamd/custom/sa-rules-heinlein ]] && echo '# to be auto-filled by dovecot-mailcow' > /etc/rspamd/custom/sa-rules-heinlein + + +[[ ! -f /etc/rspamd/override.d/worker-controller-password.inc ]] && echo '# Autogenerated by mailcow' > /etc/rspamd/override.d/worker-controller-password.inc + +echo ${IPV4_NETWORK}.0/24 > /etc/rspamd/custom/mailcow_networks.map +echo ${IPV6_NETWORK} >> /etc/rspamd/custom/mailcow_networks.map + +DOVECOT_V4= +DOVECOT_V6= +until [[ ! -z ${DOVECOT_V4} ]]; do + DOVECOT_V4=$(dig a dovecot +short) + DOVECOT_V6=$(dig aaaa dovecot +short) + [[ ! -z ${DOVECOT_V4} ]] && break; + echo "Waiting for Dovecot..." + sleep 3 +done +echo ${DOVECOT_V4}/32 > /etc/rspamd/custom/dovecot_trusted.map +if [[ ! -z ${DOVECOT_V6} ]]; then + echo ${DOVECOT_V6}/128 >> /etc/rspamd/custom/dovecot_trusted.map +fi + +RSPAMD_V4= +RSPAMD_V6= +until [[ ! -z ${RSPAMD_V4} ]]; do + RSPAMD_V4=$(dig a rspamd +short) + RSPAMD_V6=$(dig aaaa rspamd +short) + [[ ! -z ${RSPAMD_V4} ]] && break; + echo "Waiting for Rspamd..." + sleep 3 +done +echo ${RSPAMD_V4}/32 > /etc/rspamd/custom/rspamd_trusted.map +if [[ ! -z ${RSPAMD_V6} ]]; then + echo ${RSPAMD_V6}/128 >> /etc/rspamd/custom/rspamd_trusted.map +fi + +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + cat < /etc/rspamd/local.d/redis.conf +read_servers = "redis:6379"; +write_servers = "${REDIS_SLAVEOF_IP}:${REDIS_SLAVEOF_PORT}"; +password = "${REDISPASS}"; +timeout = 10; +EOF + until [[ $(redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do + echo "Waiting for Redis @redis-mailcow..." + sleep 2 + done + until [[ $(redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do + echo "Waiting for Redis @${REDIS_SLAVEOF_IP}..." + sleep 2 + done + redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning SLAVEOF ${REDIS_SLAVEOF_IP} ${REDIS_SLAVEOF_PORT} +else + cat < /etc/rspamd/local.d/redis.conf +servers = "redis:6379"; +password = "${REDISPASS}"; +timeout = 10; +EOF + until [[ $(redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do + echo "Waiting for Redis slave..." + sleep 2 + done + redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning SLAVEOF NO ONE +fi + +if [[ "${SKIP_OLEFY}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + if [[ -f /etc/rspamd/local.d/external_services.conf ]]; then + rm /etc/rspamd/local.d/external_services.conf + fi +else + if [[ ! -f /etc/rspamd/local.d/external_services.conf ]]; then + cat < /etc/rspamd/local.d/external_services.conf +oletools { + # default olefy settings + servers = "olefy:10055"; + # needs to be set explicitly for Rspamd < 1.9.5 + scan_mime_parts = true; + # mime-part regex matching in content-type or filename + # block all macros + extended = true; + max_size = 3145728; + timeout = 20.0; + retransmits = 1; +} +EOF + fi +fi + +# Provide additional lua modules +ln -s /usr/lib/$(uname -m)-linux-gnu/liblua5.1-cjson.so.0.0.0 /usr/lib/rspamd/cjson.so + +chown -R _rspamd:_rspamd /var/lib/rspamd \ + /etc/rspamd/local.d \ + /etc/rspamd/override.d \ + /etc/rspamd/rspamd.conf.local \ + /etc/rspamd/rspamd.conf.override \ + /etc/rspamd/plugins.d + +# Fix missing default global maps, if any +# These exists in mailcow UI and should not be removed +touch /etc/rspamd/custom/global_mime_from_blacklist.map \ + /etc/rspamd/custom/global_rcpt_blacklist.map \ + /etc/rspamd/custom/global_smtp_from_blacklist.map \ + /etc/rspamd/custom/global_mime_from_whitelist.map \ + /etc/rspamd/custom/global_rcpt_whitelist.map \ + /etc/rspamd/custom/global_smtp_from_whitelist.map \ + /etc/rspamd/custom/bad_languages.map \ + /etc/rspamd/custom/sa-rules \ + /etc/rspamd/custom/dovecot_trusted.map \ + /etc/rspamd/custom/rspamd_trusted.map \ + /etc/rspamd/custom/mailcow_networks.map \ + /etc/rspamd/custom/ip_wl.map \ + /etc/rspamd/custom/fishy_tlds.map \ + /etc/rspamd/custom/bad_words.map \ + /etc/rspamd/custom/bad_asn.map \ + /etc/rspamd/custom/bad_words_de.map \ + /etc/rspamd/custom/bulk_header.map \ + /etc/rspamd/custom/bad_header.map + +# www-data (82) group needs to write to these files +chown _rspamd:_rspamd /etc/rspamd/custom/ +chmod 0755 /etc/rspamd/custom/. +chown -R 82:82 /etc/rspamd/custom/* +chmod 644 -R /etc/rspamd/custom/* + +# Run hooks +for file in /hooks/*; do + if [ -x "${file}" ]; then + echo "Running hook ${file}" + "${file}" + fi +done + +# If DQS KEY is set in mailcow.conf add Spamhaus DQS RBLs +if [[ ! -z ${SPAMHAUS_DQS_KEY} ]]; then + cat < /etc/rspamd/custom/dqs-rbl.conf + # Autogenerated by mailcow. DO NOT TOUCH! + spamhaus { + rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net"; + from = false; + } + spamhaus_from { + from = true; + received = false; + rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net"; + returncodes { + SPAMHAUS_ZEN = [ "127.0.0.2", "127.0.0.3", "127.0.0.4", "127.0.0.5", "127.0.0.6", "127.0.0.7", "127.0.0.9", "127.0.0.10", "127.0.0.11" ]; + } + } + spamhaus_authbl_received { + # Check if the sender client is listed in AuthBL (AuthBL is *not* part of ZEN) + rbl = "${SPAMHAUS_DQS_KEY}.authbl.dq.spamhaus.net"; + from = false; + received = true; + ipv6 = true; + returncodes { + SH_AUTHBL_RECEIVED = "127.0.0.20" + } + } + spamhaus_dbl { + # Add checks on the HELO string + rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net"; + helo = true; + rdns = true; + dkim = true; + disable_monitoring = true; + returncodes { + RBL_DBL_SPAM = "127.0.1.2"; + RBL_DBL_PHISH = "127.0.1.4"; + RBL_DBL_MALWARE = "127.0.1.5"; + RBL_DBL_BOTNET = "127.0.1.6"; + RBL_DBL_ABUSED_SPAM = "127.0.1.102"; + RBL_DBL_ABUSED_PHISH = "127.0.1.104"; + RBL_DBL_ABUSED_MALWARE = "127.0.1.105"; + RBL_DBL_ABUSED_BOTNET = "127.0.1.106"; + RBL_DBL_DONT_QUERY_IPS = "127.0.1.255"; + } + } + spamhaus_dbl_fullurls { + ignore_defaults = true; + no_ip = true; + rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net"; + selector = 'urls:get_host' + disable_monitoring = true; + returncodes { + DBLABUSED_SPAM_FULLURLS = "127.0.1.102"; + DBLABUSED_PHISH_FULLURLS = "127.0.1.104"; + DBLABUSED_MALWARE_FULLURLS = "127.0.1.105"; + DBLABUSED_BOTNET_FULLURLS = "127.0.1.106"; + } + } + spamhaus_zrd { + # Add checks on the HELO string also for DQS + rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net"; + helo = true; + rdns = true; + dkim = true; + disable_monitoring = true; + returncodes { + RBL_ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"]; + RBL_ZRD_FRESH_DOMAIN = [ + "127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24" + ]; + RBL_ZRD_DONT_QUERY_IPS = "127.0.2.255"; + } + } + "SPAMHAUS_ZEN_URIBL" { + enabled = true; + rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net"; + resolve_ip = true; + checks = ['urls']; + replyto = true; + emails = true; + ipv4 = true; + ipv6 = true; + emails_domainonly = true; + returncodes { + URIBL_SBL = "127.0.0.2"; + URIBL_SBL_CSS = "127.0.0.3"; + URIBL_XBL = ["127.0.0.4", "127.0.0.5", "127.0.0.6", "127.0.0.7"]; + URIBL_PBL = ["127.0.0.10", "127.0.0.11"]; + URIBL_DROP = "127.0.0.9"; + } + } + SH_EMAIL_DBL { + ignore_defaults = true; + replyto = true; + emails_domainonly = true; + disable_monitoring = true; + rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net"; + returncodes = { + SH_EMAIL_DBL = [ + "127.0.1.2", + "127.0.1.4", + "127.0.1.5", + "127.0.1.6" + ]; + SH_EMAIL_DBL_ABUSED = [ + "127.0.1.102", + "127.0.1.104", + "127.0.1.105", + "127.0.1.106" + ]; + SH_EMAIL_DBL_DONT_QUERY_IPS = [ "127.0.1.255" ]; + } + } + SH_EMAIL_ZRD { + ignore_defaults = true; + replyto = true; + emails_domainonly = true; + disable_monitoring = true; + rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net"; + returncodes = { + SH_EMAIL_ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"]; + SH_EMAIL_ZRD_FRESH_DOMAIN = [ + "127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24" + ]; + SH_EMAIL_ZRD_DONT_QUERY_IPS = [ "127.0.2.255" ]; + } + } + "DBL" { + # override the defaults for DBL defined in modules.d/rbl.conf + rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net"; + disable_monitoring = true; + } + "ZRD" { + ignore_defaults = true; + rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net"; + no_ip = true; + dkim = true; + emails = true; + emails_domainonly = true; + urls = true; + returncodes = { + ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"]; + ZRD_FRESH_DOMAIN = ["127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"]; + } + } + spamhaus_sbl_url { + ignore_defaults = true + rbl = "${SPAMHAUS_DQS_KEY}.sbl.dq.spamhaus.net"; + checks = ['urls']; + disable_monitoring = true; + returncodes { + SPAMHAUS_SBL_URL = "127.0.0.2"; + } + } + + SH_HBL_EMAIL { + ignore_defaults = true; + rbl = "_email.${SPAMHAUS_DQS_KEY}.hbl.dq.spamhaus.net"; + emails_domainonly = false; + selector = "from('smtp').lower;from('mime').lower"; + ignore_whitelist = true; + checks = ['emails', 'replyto']; + hash = "sha1"; + returncodes = { + SH_HBL_EMAIL = [ + "127.0.3.2" + ]; + } + } + + spamhaus_dqs_hbl { + symbol = "HBL_FILE_UNKNOWN"; + rbl = "_file.${SPAMHAUS_DQS_KEY}.hbl.dq.spamhaus.net."; + selector = "attachments('rbase32', 'sha256')"; + ignore_whitelist = true; + ignore_defaults = true; + returncodes { + SH_HBL_FILE_MALICIOUS = "127.0.3.10"; + SH_HBL_FILE_SUSPICIOUS = "127.0.3.15"; + } + } +EOF +else + rm -rf /etc/rspamd/custom/dqs-rbl.conf +fi exec "$@" diff --git a/data/Dockerfiles/rspamd/sa_trivial_convert.lua b/data/Dockerfiles/rspamd/sa_trivial_convert.lua new file mode 100644 index 0000000000..4725dabd59 --- /dev/null +++ b/data/Dockerfiles/rspamd/sa_trivial_convert.lua @@ -0,0 +1,443 @@ +local fun = require "fun" +local rspamd_logger = require "rspamd_logger" +local util = require "rspamd_util" +local lua_util = require "lua_util" +local rspamd_regexp = require "rspamd_regexp" +local ucl = require "ucl" + +local complicated = {} +local rules = {} +local scores = {} + +local function words_to_re(words, start) + return table.concat(fun.totable(fun.drop_n(start, words)), " "); +end + +local function split(str, delim) + local result = {} + + if not delim then + delim = '[^%s]+' + end + + for token in string.gmatch(str, delim) do + table.insert(result, token) + end + + return result +end + +local function handle_header_def(hline, cur_rule) + --Now check for modifiers inside header's name + local hdrs = split(hline, '[^|]+') + local hdr_params = {} + local cur_param = {} + -- Check if an re is an ordinary re + local ordinary = true + + for _,h in ipairs(hdrs) do + if h == 'ALL' or h == 'ALL:raw' then + ordinary = false + else + local args = split(h, '[^:]+') + cur_param['strong'] = false + cur_param['raw'] = false + cur_param['header'] = args[1] + + if args[2] then + -- We have some ops that are required for the header, so it's not ordinary + ordinary = false + end + + fun.each(function(func) + if func == 'addr' then + cur_param['function'] = function(str) + local addr_parsed = util.parse_addr(str) + local ret = {} + if addr_parsed then + for _,elt in ipairs(addr_parsed) do + if elt['addr'] then + table.insert(ret, elt['addr']) + end + end + end + + return ret + end + elseif func == 'name' then + cur_param['function'] = function(str) + local addr_parsed = util.parse_addr(str) + local ret = {} + if addr_parsed then + for _,elt in ipairs(addr_parsed) do + if elt['name'] then + table.insert(ret, elt['name']) + end + end + end + + return ret + end + elseif func == 'raw' then + cur_param['raw'] = true + elseif func == 'case' then + cur_param['strong'] = true + else + rspamd_logger.warnx(rspamd_config, 'Function %1 is not supported in %2', + func, cur_rule['symbol']) + end + end, fun.tail(args)) + + -- Some header rules require splitting to check of multiple headers + if cur_param['header'] == 'MESSAGEID' then + -- Special case for spamassassin + ordinary = false + elseif cur_param['header'] == 'ToCc' then + ordinary = false + else + table.insert(hdr_params, cur_param) + end + end + + cur_rule['ordinary'] = ordinary and (not (#hdr_params > 1)) + cur_rule['header'] = hdr_params + end +end + +local function process_sa_conf(f) + local cur_rule = {} + local valid_rule = false + + local function insert_cur_rule() + if not rules[cur_rule.type] then + rules[cur_rule.type] = {} + end + + local target = rules[cur_rule.type] + + if cur_rule.type == 'header' then + if not cur_rule.header[1].header then + rspamd_logger.errx(rspamd_config, 'bad rule definition: %1', cur_rule) + return + end + if not target[cur_rule.header[1].header] then + target[cur_rule.header[1].header] = {} + end + target = target[cur_rule.header[1].header] + end + + if not cur_rule['symbol'] then + rspamd_logger.errx(rspamd_config, 'bad rule definition: %1', cur_rule) + return + end + target[cur_rule['symbol']] = cur_rule + cur_rule = {} + valid_rule = false + end + + local function parse_score(words) + if #words == 3 then + -- score rule + return tonumber(words[3]) + elseif #words == 6 then + -- score rule + -- we assume here that bayes and network are enabled and select + return tonumber(words[6]) + else + rspamd_logger.errx(rspamd_config, 'invalid score for %1', words[2]) + end + + return 0 + end + + local skip_to_endif = false + local if_nested = 0 + for l in f:lines() do + (function () + l = lua_util.rspamd_str_trim(l) + -- Replace bla=~/re/ with bla =~ /re/ (#2372) + l = l:gsub('([^%s])%s*([=!]~)%s*([^%s])', '%1 %2 %3') + + if string.len(l) == 0 or string.sub(l, 1, 1) == '#' then + return + end + + -- Unbalanced if/endif + if if_nested < 0 then if_nested = 0 end + if skip_to_endif then + if string.match(l, '^endif') then + if_nested = if_nested - 1 + + if if_nested == 0 then + skip_to_endif = false + end + elseif string.match(l, '^if') then + if_nested = if_nested + 1 + elseif string.match(l, '^else') then + -- Else counterpart for if + skip_to_endif = false + end + table.insert(complicated, l) + return + else + if string.match(l, '^ifplugin') then + skip_to_endif = true + if_nested = if_nested + 1 + table.insert(complicated, l) + elseif string.match(l, '^if !plugin%(') then + skip_to_endif = true + if_nested = if_nested + 1 + table.insert(complicated, l) + elseif string.match(l, '^if') then + -- Unknown if + skip_to_endif = true + if_nested = if_nested + 1 + table.insert(complicated, l) + elseif string.match(l, '^else') then + -- Else counterpart for if + skip_to_endif = true + table.insert(complicated, l) + elseif string.match(l, '^endif') then + if_nested = if_nested - 1 + table.insert(complicated, l) + end + end + + -- Skip comments + local words = fun.totable(fun.take_while( + function(w) return string.sub(w, 1, 1) ~= '#' end, + fun.filter(function(w) + return w ~= "" end, + fun.iter(split(l))))) + + if words[1] == "header" then + -- header SYMBOL Header ~= /regexp/ + if valid_rule then + insert_cur_rule() + end + if words[4] and (words[4] == '=~' or words[4] == '!~') then + cur_rule['type'] = 'header' + cur_rule['symbol'] = words[2] + + if words[4] == '!~' then + table.insert(complicated, l) + return + end + + cur_rule['re_expr'] = words_to_re(words, 4) + local unset_comp = string.find(cur_rule['re_expr'], '%s+%[if%-unset:') + if unset_comp then + table.insert(complicated, l) + return + end + + cur_rule['re'] = rspamd_regexp.create(cur_rule['re_expr']) + + if not cur_rule['re'] then + rspamd_logger.warnx(rspamd_config, "Cannot parse regexp '%1' for %2", + cur_rule['re_expr'], cur_rule['symbol']) + table.insert(complicated, l) + return + else + handle_header_def(words[3], cur_rule) + if not cur_rule['ordinary'] then + table.insert(complicated, l) + return + end + end + + valid_rule = true + else + table.insert(complicated, l) + return + end + elseif words[1] == "body" then + -- body SYMBOL /regexp/ + if valid_rule then + insert_cur_rule() + end + + cur_rule['symbol'] = words[2] + if words[3] and (string.sub(words[3], 1, 1) == '/' + or string.sub(words[3], 1, 1) == 'm') then + cur_rule['type'] = 'sabody' + cur_rule['re_expr'] = words_to_re(words, 2) + cur_rule['re'] = rspamd_regexp.create(cur_rule['re_expr']) + if cur_rule['re'] then + + valid_rule = true + end + else + -- might be function + table.insert(complicated, l) + return + end + elseif words[1] == "rawbody" then + -- body SYMBOL /regexp/ + if valid_rule then + insert_cur_rule() + end + + cur_rule['symbol'] = words[2] + if words[3] and (string.sub(words[3], 1, 1) == '/' + or string.sub(words[3], 1, 1) == 'm') then + cur_rule['type'] = 'sarawbody' + cur_rule['re_expr'] = words_to_re(words, 2) + cur_rule['re'] = rspamd_regexp.create(cur_rule['re_expr']) + if cur_rule['re'] then + valid_rule = true + end + else + table.insert(complicated, l) + return + end + elseif words[1] == "full" then + -- body SYMBOL /regexp/ + if valid_rule then + insert_cur_rule() + end + + cur_rule['symbol'] = words[2] + + if words[3] and (string.sub(words[3], 1, 1) == '/' + or string.sub(words[3], 1, 1) == 'm') then + cur_rule['type'] = 'message' + cur_rule['re_expr'] = words_to_re(words, 2) + cur_rule['re'] = rspamd_regexp.create(cur_rule['re_expr']) + cur_rule['raw'] = true + if cur_rule['re'] then + valid_rule = true + end + else + table.insert(complicated, l) + return + end + elseif words[1] == "uri" then + -- uri SYMBOL /regexp/ + if valid_rule then + insert_cur_rule() + end + cur_rule['type'] = 'uri' + cur_rule['symbol'] = words[2] + cur_rule['re_expr'] = words_to_re(words, 2) + cur_rule['re'] = rspamd_regexp.create(cur_rule['re_expr']) + if cur_rule['re'] and cur_rule['symbol'] then + valid_rule = true + else + table.insert(complicated, l) + return + end + elseif words[1] == "meta" then + -- meta SYMBOL expression + if valid_rule then + insert_cur_rule() + end + table.insert(complicated, l) + return + elseif words[1] == "describe" and valid_rule then + cur_rule['description'] = words_to_re(words, 2) + elseif words[1] == "score" then + scores[words[2]] = parse_score(words) + else + table.insert(complicated, l) + return + end + end)() + end + if valid_rule then + insert_cur_rule() + end +end + +for _,matched in ipairs(arg) do + local f = io.open(matched, "r") + if f then + rspamd_logger.messagex(rspamd_config, 'loading SA rules from %s', matched) + process_sa_conf(f) + else + rspamd_logger.errx(rspamd_config, "cannot open %1", matched) + end +end + +local multimap_conf = {} + +local function handle_rule(what, syms, hdr) + local mtype + local filter + local fname + local header + local sym = what:upper() + if what == 'sabody' then + mtype = 'content' + fname = 'body_re.map' + filter = 'oneline' + elseif what == 'sarawbody' then + fname = 'raw_body_re.map' + mtype = 'content' + filter = 'rawtext' + elseif what == 'full' then + fname = 'full_re.map' + mtype = 'content' + filter = 'full' + elseif what == 'uri' then + fname = 'uri_re.map' + mtype = 'url' + filter = 'full' + elseif what == 'header' then + fname = ('hdr_' .. hdr .. '_re.map'):lower() + mtype = 'header' + header = hdr + sym = sym .. '_' .. hdr:upper() + else + rspamd_logger.errx('unknown type: %s', what) + return + end + local conf = { + type = mtype, + filter = filter, + symbol = 'SA_MAP_AUTO_' .. sym, + regexp = true, + map = fname, + header = header, + symbols = {} + } + local re_file = io.open(fname, 'w') + + for k,r in pairs(syms) do + local score = 0.0 + if scores[k] then + score = scores[k] + end + re_file:write(string.format('/%s/ %s:%f\n', tostring(r.re), k, score)) + table.insert(conf.symbols, k) + end + + re_file:close() + + multimap_conf[sym:lower()] = conf + rspamd_logger.messagex('stored %s regexp in %s', sym:lower(), fname) +end + +for k,v in pairs(rules) do + if k == 'header' then + for h,r in pairs(v) do + handle_rule(k, r, h) + end + else + handle_rule(k, v) + end +end + +local out = ucl.to_format(multimap_conf, 'ucl') +local mmap_conf = io.open('auto_multimap.conf', 'w') +mmap_conf:write(out) +mmap_conf:close() +rspamd_logger.messagex('stored multimap conf in %s', 'auto_multimap.conf') + +local sa_remain = io.open('auto_sa.conf', 'w') +fun.each(function(l) + sa_remain:write(l) + sa_remain:write('\n') +end, fun.filter(function(l) return not string.match(l, '^%s+$') end, complicated)) +sa_remain:close() +rspamd_logger.messagex('stored sa remains conf in %s', 'auto_sa.conf') diff --git a/data/Dockerfiles/rspamd/set_worker_password.sh b/data/Dockerfiles/rspamd/set_worker_password.sh new file mode 100755 index 0000000000..7205e88811 --- /dev/null +++ b/data/Dockerfiles/rspamd/set_worker_password.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +password_file='/etc/rspamd/override.d/worker-controller-password.inc' +password_hash=`/usr/bin/rspamadm pw -e -p $1` + +echo 'enable_password = "'$password_hash'";' > $password_file + +if grep -q "$password_hash" "$password_file"; then + echo "OK" +else + echo "ERROR" +fi \ No newline at end of file diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index 9d3965a724..f65ec7ebd0 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -1,54 +1,179 @@ -FROM debian:stretch-slim -LABEL maintainer "Andre Peters " +# SOGo built from source to enable security patch application +# Repository: https://github.com/Alinto/sogo +# Version: SOGo-5.12.9 +# +# Applied security patches: +# - +# +# To add new patches, modify SOGO_SECURITY_PATCHES ARG below with space-separated commit hashes + +FROM debian:bookworm + +LABEL maintainer="The Infrastructure Company GmbH " ARG DEBIAN_FRONTEND=noninteractive -ENV LC_ALL C -ENV GOSU_VERSION 1.9 +ARG SOGO_VERSION=SOGo-5.12.10 +ARG SOPE_VERSION=SOPE-5.12.10 +# Security patches to apply (space-separated commit hashes) +ARG SOGO_SECURITY_PATCHES="" +# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?.*)$ +ARG GOSU_VERSION=1.19 +ENV LC_ALL=C -# Prerequisites +# Install dependencies, build SOPE and SOGo, then clean up (all in one layer to minimize image size) RUN apt-get update && apt-get install -y --no-install-recommends \ - apt-transport-https \ - ca-certificates \ - cron \ - gnupg \ - mysql-client \ - supervisor \ - syslog-ng \ - syslog-ng-core \ - syslog-ng-mod-redis \ - dirmngr \ - netcat \ - psmisc \ - wget \ + # Build dependencies + git \ + build-essential \ + gobjc \ + pkg-config \ + gnustep-make \ + gnustep-base-runtime \ + libgnustep-base-dev \ + libxml2-dev \ + libldap2-dev \ + libssl-dev \ + zlib1g-dev \ + libpq-dev \ + libmariadb-dev-compat \ + libmemcached-dev \ + libsodium-dev \ + libcurl4-openssl-dev \ + libzip-dev \ + libytnef0-dev \ + libwbxml2-dev \ + curl \ + ca-certificates \ + # Runtime dependencies + apt-transport-https \ + gettext \ + gnupg \ + mariadb-client \ + rsync \ + supervisor \ + syslog-ng \ + syslog-ng-core \ + syslog-ng-mod-redis \ + dirmngr \ + netcat-traditional \ + psmisc \ + wget \ patch \ - && rm -rf /var/lib/apt/lists/* \ - && dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \ - && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \ - && chmod +x /usr/local/bin/gosu \ - && gosu nobody true - -RUN mkdir /usr/share/doc/sogo \ - && touch /usr/share/doc/sogo/empty.sh \ - && apt-key adv --keyserver keyserver.ubuntu.com --recv-key 0x810273C4 \ - && echo "deb http://packages.inverse.ca/SOGo/nightly/4/debian/ stretch stretch" > /etc/apt/sources.list.d/sogo.list \ - && apt-get update && apt-get install -y --force-yes \ - sogo \ - sogo-activesync \ - && rm -rf /var/lib/apt/lists/* \ - && echo '* * * * * sogo /usr/sbin/sogo-ealarms-notify 2>/dev/null' > /etc/cron.d/sogo \ - && echo '* * * * * sogo /usr/sbin/sogo-tool expire-sessions 60' >> /etc/cron.d/sogo \ - && echo '0 0 * * * sogo /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/sieve.creds' >> /etc/cron.d/sogo \ - && touch /etc/default/locale - -COPY ./bootstrap-sogo.sh / + libobjc4 \ + libxml2 \ + libldap-2.5-0 \ + libssl3 \ + zlib1g \ + libmariadb3 \ + libmemcached11 \ + libsodium23 \ + libcurl4 \ + libzip4 \ + libytnef0 \ + libwbxml2-1 \ + # Download gosu + && dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \ + && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \ + && chmod +x /usr/local/bin/gosu \ + && gosu nobody true \ + # Build SOPE + && git clone --depth 1 --branch ${SOPE_VERSION} https://github.com/Alinto/sope.git /tmp/sope \ + && cd /tmp/sope \ + && rm -rf .git \ + && . /usr/share/GNUstep/Makefiles/GNUstep.sh \ + && ./configure --prefix=/usr --disable-debug --disable-strip \ + && make -j$(nproc) \ + && make install \ + && cd / \ + && rm -rf /tmp/sope \ + # Build SOGo with security patches + && git clone --depth 1 --branch ${SOGO_VERSION} https://github.com/Alinto/sogo.git /tmp/sogo \ + && cd /tmp/sogo \ + && git config user.email "builder@mailcow.local" \ + && git config user.name "SOGo Builder" \ + && for patch in ${SOGO_SECURITY_PATCHES}; do \ + echo "Applying security patch: ${patch}"; \ + git fetch origin ${patch} && git cherry-pick ${patch}; \ + done \ + && rm -rf .git \ + && . /usr/share/GNUstep/Makefiles/GNUstep.sh \ + && ./configure --disable-debug --disable-strip \ + && make -j$(nproc) \ + && make install \ + && cd /tmp/sogo/ActiveSync \ + && . /usr/share/GNUstep/Makefiles/GNUstep.sh \ + && make -j$(nproc) install \ + && cd / \ + && rm -rf /tmp/sogo \ + # Strip binaries + && strip --strip-unneeded /usr/local/sbin/sogod 2>/dev/null || true \ + && strip --strip-unneeded /usr/local/sbin/sogo-tool 2>/dev/null || true \ + && strip --strip-unneeded /usr/local/sbin/sogo-ealarms-notify 2>/dev/null || true \ + && strip --strip-unneeded /usr/local/sbin/sogo-slapd-sockd 2>/dev/null || true \ + # Remove build dependencies and clean up + && apt-get purge -y --auto-remove \ + git \ + build-essential \ + gobjc \ + gnustep-make \ + libgnustep-base-dev \ + libxml2-dev \ + libldap2-dev \ + libssl-dev \ + zlib1g-dev \ + libpq-dev \ + libmariadb-dev-compat \ + libmemcached-dev \ + libsodium-dev \ + libcurl4-openssl-dev \ + libzip-dev \ + libytnef0-dev \ + curl \ + && apt-get autoremove -y \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /usr/share/doc/* \ + && rm -rf /usr/share/man/* \ + && rm -rf /var/cache/debconf/* \ + && rm -rf /tmp/* \ + && rm -rf /root/.cache \ + && find /usr/local/lib -name '*.a' -delete \ + && find /usr/lib -name '*.a' -delete \ + && mkdir -p /usr/share/doc/sogo \ + && touch /usr/share/doc/sogo/empty.sh \ + && touch /etc/default/locale + +# Configure library paths +RUN echo "/usr/lib64" > /etc/ld.so.conf.d/sogo.conf \ + && echo "/usr/local/lib/sogo" >> /etc/ld.so.conf.d/sogo.conf \ + && echo "/usr/local/lib/GNUstep/Frameworks/SOGo.framework/Versions/5/sogo" >> /etc/ld.so.conf.d/sogo.conf \ + && ldconfig + +# Create sogo user and group +RUN groupadd -r -g 999 sogo \ + && useradd -r -u 999 -g sogo -d /var/lib/sogo -s /bin/bash -c "SOGo Daemon" sogo \ + && mkdir -p /var/lib/sogo /var/run/sogo /var/log/sogo /var/spool/sogo \ + && chown -R sogo:sogo /var/lib/sogo /var/run/sogo /var/log/sogo /var/spool/sogo + +# Create symlinks for SOGo binaries +RUN ln -s /usr/local/sbin/sogod /usr/sbin/sogod \ + && ln -s /usr/local/sbin/sogo-tool /usr/sbin/sogo-tool \ + && ln -s /usr/local/sbin/sogo-ealarms-notify /usr/sbin/sogo-ealarms-notify \ + && ln -s /usr/local/sbin/sogo-slapd-sockd /usr/sbin/sogo-slapd-sockd + +# Copy configuration files and scripts +COPY ./bootstrap-sogo.sh /bootstrap-sogo.sh COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf +COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf COPY supervisord.conf /etc/supervisor/supervisord.conf -COPY theme-blue.js /usr/lib/GNUstep/SOGo/WebServerResources/js/theme-blue.js -COPY theme-blue.css /usr/lib/GNUstep/SOGo/WebServerResources/css/theme-default.css -COPY sogo-full.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg COPY acl.diff /acl.diff -CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf +COPY navMailcowBtns.diff /navMailcowBtns.diff +COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh +COPY docker-entrypoint.sh / + +RUN chmod +x /bootstrap-sogo.sh \ + /usr/local/sbin/stop-supervisor.sh -VOLUME /usr/lib/GNUstep/SOGo/ +ENTRYPOINT ["/docker-entrypoint.sh"] -RUN rm -rf /tmp/* /var/tmp/* +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] diff --git a/data/Dockerfiles/sogo/acl.diff b/data/Dockerfiles/sogo/acl.diff index 5137003886..e6b5d05205 100644 --- a/data/Dockerfiles/sogo/acl.diff +++ b/data/Dockerfiles/sogo/acl.diff @@ -1,5 +1,5 @@ ---- /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox 2018-08-17 18:29:57.987504204 +0200 -+++ /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox 2018-08-17 18:29:35.918291298 +0200 +--- /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox 2018-08-17 18:29:57.987504204 +0200 ++++ /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox 2018-08-17 18:29:35.918291298 +0200 @@ -46,7 +46,7 @@ diff --git a/data/Dockerfiles/sogo/bootstrap-sogo.sh b/data/Dockerfiles/sogo/bootstrap-sogo.sh index b8dc895c88..1b2cd2ace9 100755 --- a/data/Dockerfiles/sogo/bootstrap-sogo.sh +++ b/data/Dockerfiles/sogo/bootstrap-sogo.sh @@ -1,7 +1,7 @@ #!/bin/bash # Wait for MySQL to warm-up -while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do +while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do echo "Waiting for database to come up..." sleep 2 done @@ -13,68 +13,26 @@ do sleep 3 done -# Recreate view - -mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view" - -while [[ ${VIEW_OK} != 'OK' ]]; do - mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF -CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, home, kind, multiple_bookings) AS -SELECT mailbox.username, mailbox.domain, mailbox.username, if(json_extract(attributes, '$.force_pw_update') LIKE '%0%', password, 'invalid'), mailbox.name, mailbox.username, IFNULL(GROUP_CONCAT(ga.aliases SEPARATOR ' '), ''), IFNULL(gda.ad_alias, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox -LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)') -LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username -WHERE mailbox.active = '1' -GROUP BY mailbox.username; -EOF - if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then - VIEW_OK=OK - else - echo "Will retry to setup SOGo view in 3s" - sleep 3 - fi -done - -# Wait for static view table if missing after update and update content - -while [[ ${STATIC_VIEW_OK} != 'OK' ]]; do - if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then - STATIC_VIEW_OK=OK - echo "Updating _sogo_static_view content..." - mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "REPLACE INTO _sogo_static_view SELECT * from sogo_view" - mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "DELETE FROM _sogo_static_view WHERE c_uid NOT IN (SELECT username FROM mailbox WHERE active = '1')" - else - echo "Waiting for database initialization..." - sleep 3 - fi -done - -# Recreate password update trigger - -mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password" - -while [[ ${TRIGGER_OK} != 'OK' ]]; do - mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF -DELIMITER - -CREATE TRIGGER sogo_update_password AFTER UPDATE ON _sogo_static_view -FOR EACH ROW -BEGIN -UPDATE mailbox SET password = NEW.c_password WHERE NEW.c_uid = username; -END; -- -DELIMITER ; -EOF - if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_NAME = 'sogo_update_password'") ]]; then - TRIGGER_OK=OK - else - echo "Will retry to setup SOGo password update trigger in 3s" - sleep 3 - fi +# Wait for updated schema +DBV_NOW=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN) +DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2) +while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do + echo "Waiting for schema update..." + DBV_NOW=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN) + DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2) + sleep 5 done +echo "DB schema is ${DBV_NOW}" +if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password" +fi -mkdir -p /var/lib/sogo/GNUstep/Defaults/ +# cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl +RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9) # Generate plist header with timezone data +mkdir -p /var/lib/sogo/GNUstep/Defaults/ cat < /var/lib/sogo/GNUstep/Defaults/sogod.plist @@ -82,6 +40,18 @@ cat < /var/lib/sogo/GNUstep/Defaults/sogod.plist OCSAclURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_acl + SOGoIMAPServer + imap://${IPV4_NETWORK}.250:143/?TLS=YES&tlsVerifyMode=none + SOGoSieveServer + sieve://${IPV4_NETWORK}.250:4190/?TLS=YES&tlsVerifyMode=none + SOGoSMTPServer + smtp://${IPV4_NETWORK}.253:588/?TLS=YES&tlsVerifyMode=none + SOGoTrustProxyAuthentication + YES + SOGoEncryptionKey + ${RAND_PASS} + OCSAdminURL + mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_admin OCSCacheFolderURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_cache_folder OCSEMailAlarmsFolderURL @@ -101,9 +71,9 @@ cat < /var/lib/sogo/GNUstep/Defaults/sogod.plist EOF # Generate multi-domain setup -while read line - do - echo " ${line} +while read -r line gal + do + echo " ${line} SOGoMailDomain ${line} @@ -114,6 +84,7 @@ while read line aliases ad_aliases + ext_acl KindFieldName kind @@ -126,23 +97,26 @@ while read line canAuthenticate YES displayName - GAL + GAL ${line} id ${line} isAddressBook - YES + ${gal} type sql userPasswordAlgorithm - ssha256 + ${MAILCOW_PASS_SCHEME} prependPasswordScheme YES viewURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/_sogo_static_view - - + " >> /var/lib/sogo/GNUstep/Defaults/sogod.plist + # Generate alternative LDAP authentication dict, when SQL authentication fails + # This will nevertheless read attributes from LDAP + /etc/sogo/plist_ldap.sh ${line} ${gal} >> /var/lib/sogo/GNUstep/Defaults/sogod.plist + echo " " >> /var/lib/sogo/GNUstep/Defaults/sogod.plist -done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain;" -B -N) +done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain, CASE gal WHEN '1' THEN 'YES' ELSE 'NO' END AS gal FROM domain;" -B -N) # Generate footer echo ' @@ -153,46 +127,35 @@ echo ' chown sogo:sogo -R /var/lib/sogo/ chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist -# Prevent theme switching -sed -i \ - -e 's/eaf5e9/E3F2FD/g' \ - -e 's/cbe5c8/BBDEFB/g' \ - -e 's/aad6a5/90CAF9/g' \ - -e 's/88c781/64B5F6/g' \ - -e 's/66b86a/42A5F5/g' \ - -e 's/56b04c/2196F3/g' \ - -e 's/4da143/1E88E5/g' \ - -e 's/388e3c/1976D2/g' \ - -e 's/367d2e/1565C0/g' \ - -e 's/225e1b/0D47A1/g' \ - -e 's/fafafa/82B1FF/g' \ - -e 's/69f0ae/448AFF/g' \ - -e 's/00e676/2979ff/g' \ - -e 's/00c853/2962ff/g' \ - /usr/lib/GNUstep/SOGo/WebServerResources/js/Common/Common.app.js \ - /usr/lib/GNUstep/SOGo/WebServerResources/js/Common.js +# Patch ACLs +#if [[ ${ACL_ANYONE} == 'allow' ]]; then +# #enable any or authenticated targets for ACL +# if patch -R -sfN --dry-run /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then +# patch -R /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff; +# fi +#else +# #disable any or authenticated targets for ACL +# if patch -sfN --dry-run /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then +# patch /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff; +# fi +#fi + +# Apply custom UI patch (reverse patch to ADD buttons) +if patch -R -sfN --dry-run /usr/local/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff > /dev/null; then + echo "Applying navMailcowBtns patch (reverse to add buttons)..." + patch -R /usr/local/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff; +else + echo "navMailcowBtns patch already applied or cannot be applied" +fi + +# Rename custom logo, if any +[[ -f /etc/sogo/sogo-full.svg ]] && mv /etc/sogo/sogo-full.svg /etc/sogo/custom-fulllogo.svg -sed -i \ - -e 's/default: "900"/default: "700"/g' \ - -e 's/default: "500"/default: "700"/g' \ - -e 's/"hue-1": "400"/"hue-1": "500"/g' \ - -e 's/"hue-1": "A100"/"hue-1": "500"/g' \ - -e 's/"hue-2": "800"/"hue-2": "700"/g' \ - -e 's/"hue-2": "300"/"hue-2": "700"/g' \ - -e 's/"hue-3": "A700"/"hue-3": "A200"/' \ - -e 's/default:"900"/default:"700"/g' \ - -e 's/default:"500"/default:"700"/g' \ - -e 's/"hue-1":"400"/"hue-1":"500"/g' \ - -e 's/"hue-1":"A100"/"hue-1":"500"/g' \ - -e 's/"hue-2":"800"/"hue-2":"700"/g' \ - -e 's/"hue-2":"300"/"hue-2":"700"/g' \ - -e 's/"hue-3":"A700"/"hue-3":"A200"/' \ - /usr/lib/GNUstep/SOGo/WebServerResources/js/Common/Common.app.js \ - /usr/lib/GNUstep/SOGo/WebServerResources/js/Common.js +# Rsync web content +echo "Syncing web content with named volume" +rsync -a /usr/local/lib/GNUstep/SOGo/. /sogo_web/ -# Patch ACLs (comment this out to enable any or authenticated targets for ACL) -if patch -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then - patch /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff; -fi +# Chown backup path +chown -R sogo:sogo /sogo_backup exec gosu sogo /usr/sbin/sogod diff --git a/data/Dockerfiles/sogo/docker-entrypoint.sh b/data/Dockerfiles/sogo/docker-entrypoint.sh new file mode 100755 index 0000000000..4e20e960ea --- /dev/null +++ b/data/Dockerfiles/sogo/docker-entrypoint.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +if [[ "${SKIP_SOGO}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + echo "SKIP_SOGO=y, skipping SOGo..." + sleep 365d + exit 0 +fi + +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf +fi + +echo "$TZ" > /etc/timezone + +# Run hooks +for file in /hooks/*; do + if [ -x "${file}" ]; then + echo "Running hook ${file}" + "${file}" + fi +done + +exec "$@" diff --git a/data/Dockerfiles/sogo/navMailcowBtns.diff b/data/Dockerfiles/sogo/navMailcowBtns.diff new file mode 100644 index 0000000000..2107b5b1fe --- /dev/null +++ b/data/Dockerfiles/sogo/navMailcowBtns.diff @@ -0,0 +1,15 @@ +60,65d58 +< var:ng-click="navButtonClick" +< ng-href="/user"> +< build +< mailcow +< +< ng-show="::activeUser.path.logoff.length" +85c78 +< ng-href="#"> +--- +> ng-href="{{::activeUser.path.logoff}}"> diff --git a/data/Dockerfiles/sogo/sogo-full.svg b/data/Dockerfiles/sogo/sogo-full.svg deleted file mode 100644 index 281ee7c669..0000000000 --- a/data/Dockerfiles/sogo/sogo-full.svg +++ /dev/null @@ -1,160 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/data/Dockerfiles/sogo/stop-supervisor.sh b/data/Dockerfiles/sogo/stop-supervisor.sh new file mode 100755 index 0000000000..5394490ceb --- /dev/null +++ b/data/Dockerfiles/sogo/stop-supervisor.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +printf "READY\n"; + +while read line; do + echo "Processing Event: $line" >&2; + kill -3 $(cat "/var/run/supervisord.pid") +done < /dev/stdin diff --git a/data/Dockerfiles/sogo/supervisord.conf b/data/Dockerfiles/sogo/supervisord.conf index 1c1422b3b8..4946d98ccc 100644 --- a/data/Dockerfiles/sogo/supervisord.conf +++ b/data/Dockerfiles/sogo/supervisord.conf @@ -11,18 +11,17 @@ stderr_logfile_maxbytes=0 autostart=true priority=1 -[program:cron] -command=/usr/sbin/cron -f -autorestart=true -priority=2 - [program:bootstrap-sogo] command=/bootstrap-sogo.sh stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 -priority=3 +priority=2 startretries=10 autorestart=true stopwaitsecs=120 + +[eventlistener:processes] +command=/usr/local/sbin/stop-supervisor.sh +events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL diff --git a/data/Dockerfiles/sogo/syslog-ng-redis_slave.conf b/data/Dockerfiles/sogo/syslog-ng-redis_slave.conf new file mode 100644 index 0000000000..675e4c67af --- /dev/null +++ b/data/Dockerfiles/sogo/syslog-ng-redis_slave.conf @@ -0,0 +1,47 @@ +@version: 3.38 +@include "scl.conf" +options { + chain_hostnames(off); + flush_lines(0); + use_dns(no); + use_fqdn(no); + owner("root"); group("adm"); perm(0640); + stats_freq(0); + bad_hostname("^gconfd$"); +}; +source s_src { + unix-stream("/dev/log"); + internal(); +}; +source s_sogo { + pipe("/dev/sogo_log" owner(sogo) group(sogo)); +}; +destination d_stdout { pipe("/dev/stdout"); }; +destination d_redis_ui_log { + redis( + host("`REDIS_SLAVEOF_IP`") + persist-name("redis1") + port(`REDIS_SLAVEOF_PORT`) + auth("`REDISPASS`") + command("LPUSH" "SOGO_LOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") + ); +}; +destination d_redis_f2b_channel { + redis( + host("`REDIS_SLAVEOF_IP`") + persist-name("redis2") + port(`REDIS_SLAVEOF_PORT`) + auth("`REDISPASS`") + command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") + ); +}; +log { + source(s_sogo); + destination(d_redis_ui_log); + destination(d_redis_f2b_channel); +}; +log { + source(s_sogo); + source(s_src); + destination(d_stdout); +}; diff --git a/data/Dockerfiles/sogo/syslog-ng.conf b/data/Dockerfiles/sogo/syslog-ng.conf index 264bca4496..8460f2f951 100644 --- a/data/Dockerfiles/sogo/syslog-ng.conf +++ b/data/Dockerfiles/sogo/syslog-ng.conf @@ -1,4 +1,4 @@ -@version: 3.8 +@version: 3.38 @include "scl.conf" options { chain_hostnames(off); @@ -22,6 +22,7 @@ destination d_redis_ui_log { host("redis-mailcow") persist-name("redis1") port(6379) + auth("`REDISPASS`") command("LPUSH" "SOGO_LOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") ); }; @@ -30,7 +31,8 @@ destination d_redis_f2b_channel { host("redis-mailcow") persist-name("redis2") port(6379) - command("PUBLISH" "F2B_CHANNEL" "$MESSAGE") + auth("`REDISPASS`") + command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; log { diff --git a/data/Dockerfiles/sogo/theme-blue.css b/data/Dockerfiles/sogo/theme-blue.css deleted file mode 100644 index 1282d40558..0000000000 --- a/data/Dockerfiles/sogo/theme-blue.css +++ /dev/null @@ -1,16 +0,0 @@ -md-autocomplete.md-default-theme input, md-autocomplete input{color:rgba(0,0,0,0.87)}.md-autocomplete-suggestions-container.md-default-theme li, .md-autocomplete-suggestions-container li{color:rgba(0,0,0,0.87)}md-bottom-sheet.md-default-theme.md-list md-list-item, md-bottom-sheet.md-list md-list-item{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-primary, .md-button.md-primary{color:rgb(25,118,210)}.md-button.md-default-theme.md-primary.md-fab, .md-button.md-primary.md-fab,.md-button.md-default-theme.md-primary.md-raised, .md-button.md-primary.md-raised{color:rgba(255,255,255,0.87);background-color:rgb(25,118,210)}.md-button.md-default-theme.md-primary.md-fab:not([disabled]) md-icon, .md-button.md-primary.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-primary.md-raised:not([disabled]) md-icon, .md-button.md-primary.md-raised:not([disabled]) md-icon{color:rgba(255,255,255,0.87)}.md-button.md-default-theme.md-primary.md-fab:not([disabled]).md-focused, .md-button.md-primary.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-primary.md-fab:not([disabled]):hover, .md-button.md-primary.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-primary.md-raised:not([disabled]).md-focused, .md-button.md-primary.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-primary.md-raised:not([disabled]):hover, .md-button.md-primary.md-raised:not([disabled]):hover{background-color:rgb(30,136,229)}.md-button.md-default-theme.md-primary:not([disabled]) md-icon, .md-button.md-primary:not([disabled]) md-icon{color:rgb(25,118,210)}._md a.md-default-theme:not(.md-button).md-primary, ._md a:not(.md-button).md-primary{color:rgb(25,118,210)}._md a.md-default-theme:not(.md-button).md-primary:hover, ._md a:not(.md-button).md-primary:hover{color:rgb(25,118,210)}md-card.md-default-theme .md-card-image, md-card .md-card-image{border-radius:2px 2px 0 0}md-card.md-default-theme md-card-header md-card-header-text .md-subhead, md-card md-card-header md-card-header-text .md-subhead,md-card.md-default-theme md-card-title md-card-title-text:not(:only-child) .md-subhead, md-card md-card-title md-card-title-text:not(:only-child) .md-subhead{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme .md-ink-ripple, md-checkbox .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme:not(.md-checked) .md-icon, md-checkbox:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme:not([disabled]).md-primary .md-ripple, md-checkbox:not([disabled]).md-primary .md-ripple{color:rgb(30,136,229)}md-checkbox.md-default-theme:not([disabled]).md-primary.md-checked .md-ripple, md-checkbox:not([disabled]).md-primary.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme:not([disabled]).md-primary .md-ink-ripple, md-checkbox:not([disabled]).md-primary .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme:not([disabled]).md-primary.md-checked .md-ink-ripple, md-checkbox:not([disabled]).md-primary.md-checked .md-ink-ripple{color:rgba(25,118,210,0.87)}md-checkbox.md-default-theme:not([disabled]).md-primary:not(.md-checked) .md-icon, md-checkbox:not([disabled]).md-primary:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme:not([disabled]).md-primary.md-checked .md-icon, md-checkbox:not([disabled]).md-primary.md-checked .md-icon{background-color:rgba(25,118,210,0.87)}md-checkbox.md-default-theme:not([disabled]).md-primary.md-checked.md-focused .md-container:before, md-checkbox:not([disabled]).md-primary.md-checked.md-focused .md-container:before{background-color:rgba(25,118,210,0.26)}md-checkbox.md-default-theme:not([disabled]).md-primary.md-checked .md-icon:after, md-checkbox:not([disabled]).md-primary.md-checked .md-icon:after{border-color:rgba(255,255,255,0.87)}md-checkbox.md-default-theme:not([disabled]).md-primary .md-indeterminate[disabled] .md-container, md-checkbox:not([disabled]).md-primary .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme[disabled]:not(.md-checked) .md-icon, md-checkbox[disabled]:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme[disabled] .md-icon:after, md-checkbox[disabled] .md-icon:after{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme[disabled] .md-label, md-checkbox[disabled] .md-label{color:rgba(0,0,0,0.38)}md-chips.md-default-theme .md-chips, md-chips .md-chips{box-shadow:0 1px rgba(0,0,0,0.12)}md-chips.md-default-theme .md-chips.md-focused, md-chips .md-chips.md-focused{box-shadow:0 2px rgb(25,118,210)}md-chips.md-default-theme .md-chips .md-chip-input-container input, md-chips .md-chips .md-chip-input-container input{color:rgba(0,0,0,0.87)}md-chips.md-default-theme .md-chips .md-chip-input-container input:-moz-placeholder, md-chips .md-chips .md-chip-input-container input:-moz-placeholder,md-chips.md-default-theme .md-chips .md-chip-input-container input::-moz-placeholder, md-chips .md-chips .md-chip-input-container input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme .md-chips .md-chip-input-container input:-ms-input-placeholder, md-chips .md-chips .md-chip-input-container input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme .md-chips .md-chip-input-container input::-webkit-input-placeholder, md-chips .md-chips .md-chip-input-container input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme md-chip.md-focused, md-chips md-chip.md-focused{background:rgb(25,118,210);color:rgba(255,255,255,0.87)}md-chips.md-default-theme md-chip.md-focused md-icon, md-chips md-chip.md-focused md-icon{color:rgba(255,255,255,0.87)}.md-default-theme .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator, .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator{border:1px solid rgb(33,150,243)}.md-default-theme .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled, .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled{color:rgba(33,150,243,0.6)}.md-default-theme .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator,.md-default-theme .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator{background:rgb(33,150,243);color:rgba(0,0,0,0.87);border-color:transparent}.md-default-theme .md-calendar-date-disabled, .md-calendar-date-disabled,.md-default-theme .md-calendar-month-label-disabled, .md-calendar-month-label-disabled{color:rgba(0,0,0,0.38)}.md-default-theme .md-calendar-month-label md-icon, .md-calendar-month-label md-icon,.md-default-theme .md-datepicker-input, .md-datepicker-input{color:rgba(0,0,0,0.87)}.md-default-theme .md-datepicker-input:-moz-placeholder, .md-datepicker-input:-moz-placeholder,.md-default-theme .md-datepicker-input::-moz-placeholder, .md-datepicker-input::-moz-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme .md-datepicker-input:-ms-input-placeholder, .md-datepicker-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme .md-datepicker-input::-webkit-input-placeholder, .md-datepicker-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme .md-datepicker-input-container, .md-datepicker-input-container{border-bottom-color:rgba(0,0,0,0.12)}.md-default-theme .md-datepicker-input-container.md-datepicker-focused, .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(25,118,210)}.md-default-theme .md-datepicker-triangle-button .md-datepicker-expand-triangle, .md-datepicker-triangle-button .md-datepicker-expand-triangle{border-top-color:rgba(0,0,0,0.54)}.md-default-theme .md-datepicker-open .md-datepicker-calendar-icon, .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(25,118,210)}md-dialog.md-default-theme.md-content-overflow .md-actions, md-dialog.md-content-overflow .md-actions,md-dialog.md-default-theme.md-content-overflow md-dialog-actions, md-dialog.md-content-overflow md-dialog-actions,md-divider.md-default-theme, md-divider{border-top-color:rgba(0,0,0,0.12)}.layout-gt-lg-row>md-divider.md-default-theme, .layout-gt-lg-row>md-divider,.layout-gt-md-row>md-divider.md-default-theme, .layout-gt-md-row>md-divider,.layout-gt-sm-row>md-divider.md-default-theme, .layout-gt-sm-row>md-divider,.layout-gt-xs-row>md-divider.md-default-theme, .layout-gt-xs-row>md-divider,.layout-lg-row>md-divider.md-default-theme, .layout-lg-row>md-divider,.layout-md-row>md-divider.md-default-theme, .layout-md-row>md-divider,.layout-row>md-divider.md-default-theme, .layout-row>md-divider,.layout-sm-row>md-divider.md-default-theme, .layout-sm-row>md-divider,.layout-xl-row>md-divider.md-default-theme, .layout-xl-row>md-divider,.layout-xs-row>md-divider.md-default-theme, .layout-xs-row>md-divider{border-right-color:rgba(0,0,0,0.12)}md-icon.md-default-theme, md-icon{color:rgba(0,0,0,0.54)}md-icon.md-default-theme.md-primary, md-icon.md-primary{color:rgb(25,118,210)}md-input-container.md-default-theme .md-input, md-input-container .md-input{color:rgba(0,0,0,0.87);border-color:rgba(0,0,0,0.12)}md-input-container.md-default-theme .md-input:-moz-placeholder, md-input-container .md-input:-moz-placeholder,md-input-container.md-default-theme .md-input::-moz-placeholder, md-input-container .md-input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme .md-input:-ms-input-placeholder, md-input-container .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme .md-input::-webkit-input-placeholder, md-input-container .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme>md-icon, md-input-container>md-icon{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme .md-placeholder, md-input-container .md-placeholder,md-input-container.md-default-theme label, md-input-container label{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme:not(.md-input-focused):not(.md-input-invalid) label.md-required:after, md-input-container:not(.md-input-focused):not(.md-input-invalid) label.md-required:after{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme .md-input-message-animation .md-char-counter, md-input-container .md-input-message-animation .md-char-counter,md-input-container.md-default-theme .md-input-messages-animation .md-char-counter, md-input-container .md-input-messages-animation .md-char-counter{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme.md-input-focused .md-input:-moz-placeholder, md-input-container.md-input-focused .md-input:-moz-placeholder,md-input-container.md-default-theme.md-input-focused .md-input::-moz-placeholder, md-input-container.md-input-focused .md-input::-moz-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-input-focused .md-input:-ms-input-placeholder, md-input-container.md-input-focused .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-input-focused .md-input::-webkit-input-placeholder, md-input-container.md-input-focused .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme:not(.md-input-invalid).md-input-has-value label, md-input-container:not(.md-input-invalid).md-input-has-value label{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused .md-input, md-input-container:not(.md-input-invalid).md-input-focused .md-input,md-input-container.md-default-theme:not(.md-input-invalid).md-input-resized .md-input, md-input-container:not(.md-input-invalid).md-input-resized .md-input{border-color:rgb(25,118,210)}md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused label, md-input-container:not(.md-input-invalid).md-input-focused label,md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused md-icon, md-input-container:not(.md-input-invalid).md-input-focused md-icon{color:rgb(25,118,210)}md-list.md-default-theme md-list-item.md-2-line .md-list-item-text h3, md-list md-list-item.md-2-line .md-list-item-text h3,md-list.md-default-theme md-list-item.md-2-line .md-list-item-text h4, md-list md-list-item.md-2-line .md-list-item-text h4,md-list.md-default-theme md-list-item.md-3-line .md-list-item-text h3, md-list md-list-item.md-3-line .md-list-item-text h3,md-list.md-default-theme md-list-item.md-3-line .md-list-item-text h4, md-list md-list-item.md-3-line .md-list-item-text h4{color:rgba(0,0,0,0.87)}md-list.md-default-theme md-list-item.md-2-line .md-list-item-text p, md-list md-list-item.md-2-line .md-list-item-text p,md-list.md-default-theme md-list-item.md-3-line .md-list-item-text p, md-list md-list-item.md-3-line .md-list-item-text p{color:rgba(0,0,0,0.54)}md-list.md-default-theme md-list-item>md-icon, md-list md-list-item>md-icon{color:rgba(0,0,0,0.54)}md-list.md-default-theme md-list-item>md-icon.md-highlight, md-list md-list-item>md-icon.md-highlight{color:rgb(25,118,210)}md-menu-content.md-default-theme md-menu-item, md-menu-content md-menu-item{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme md-menu-item md-icon, md-menu-content md-menu-item md-icon{color:rgba(0,0,0,0.54)}md-menu-content.md-default-theme md-menu-item .md-button[disabled], md-menu-content md-menu-item .md-button[disabled],md-menu-content.md-default-theme md-menu-item .md-button[disabled] md-icon, md-menu-content md-menu-item .md-button[disabled] md-icon{color:rgba(0,0,0,0.38)}md-menu-bar.md-default-theme>button.md-button, md-menu-bar>button.md-button{color:rgba(0,0,0,0.87);border-radius:2px}md-menu-bar.md-default-theme md-menu>button, md-menu-bar md-menu>button{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme .md-menu>.md-button:after, md-menu-content .md-menu>.md-button:after{color:rgba(0,0,0,0.54)}md-toolbar.md-default-theme.md-menu-toolbar md-toolbar-filler, md-toolbar.md-menu-toolbar md-toolbar-filler{background-color:rgb(25,118,210);color:rgba(255,255,255,0.87)}md-toolbar.md-default-theme.md-menu-toolbar md-toolbar-filler md-icon, md-toolbar.md-menu-toolbar md-toolbar-filler md-icon{color:rgba(255,255,255,0.87)}md-nav-bar.md-default-theme .md-button._md-nav-button.md-unselected, md-nav-bar .md-button._md-nav-button.md-unselected{color:rgba(0,0,0,0.54)}md-nav-bar.md-default-theme .md-button._md-nav-button[disabled], md-nav-bar .md-button._md-nav-button[disabled]{color:rgba(0,0,0,0.38)}md-nav-bar.md-default-theme.md-primary>.md-nav-bar, md-nav-bar.md-primary>.md-nav-bar{background-color:rgb(25,118,210)}md-nav-bar.md-default-theme.md-primary>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-primary>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-nav-bar.md-default-theme.md-primary>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-primary>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(255,255,255,0.87)}md-nav-bar.md-default-theme.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-toolbar>md-nav-bar.md-default-theme>.md-nav-bar, md-toolbar>md-nav-bar>.md-nav-bar{background-color:rgb(25,118,210)}md-toolbar>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button, md-toolbar>md-nav-bar>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-toolbar>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(255,255,255,0.87)}md-toolbar>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-progress-circular.md-default-theme path, md-progress-circular path{stroke:rgb(25,118,210)}md-progress-linear.md-default-theme .md-container, md-progress-linear .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme .md-bar, md-progress-linear .md-bar{background-color:rgb(25,118,210)}md-progress-linear.md-default-theme[md-mode=buffer].md-primary .md-bar1, md-progress-linear[md-mode=buffer].md-primary .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme[md-mode=buffer].md-primary .md-dashed:before, md-progress-linear[md-mode=buffer].md-primary .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme .md-off, md-radio-button .md-off{border-color:rgba(0,0,0,0.54)}md-radio-button.md-default-theme:not([disabled]).md-primary .md-on, md-radio-button:not([disabled]).md-primary .md-on,md-radio-button.md-default-theme:not([disabled]) .md-primary .md-on, md-radio-button:not([disabled]) .md-primary .md-on,md-radio-group.md-default-theme:not([disabled]).md-primary .md-on, md-radio-group:not([disabled]).md-primary .md-on,md-radio-group.md-default-theme:not([disabled]) .md-primary .md-on, md-radio-group:not([disabled]) .md-primary .md-on{background-color:rgba(25,118,210,0.87)}md-radio-button.md-default-theme:not([disabled]).md-primary.md-checked .md-off, md-radio-button:not([disabled]).md-primary.md-checked .md-off,md-radio-button.md-default-theme:not([disabled]) .md-primary.md-checked .md-off, md-radio-button:not([disabled]) .md-primary.md-checked .md-off,md-radio-button.md-default-theme:not([disabled]).md-primary .md-checked .md-off, md-radio-button:not([disabled]).md-primary .md-checked .md-off,md-radio-button.md-default-theme:not([disabled]) .md-primary .md-checked .md-off, md-radio-button:not([disabled]) .md-primary .md-checked .md-off,md-radio-group.md-default-theme:not([disabled]).md-primary.md-checked .md-off, md-radio-group:not([disabled]).md-primary.md-checked .md-off,md-radio-group.md-default-theme:not([disabled]) .md-primary.md-checked .md-off, md-radio-group:not([disabled]) .md-primary.md-checked .md-off,md-radio-group.md-default-theme:not([disabled]).md-primary .md-checked .md-off, md-radio-group:not([disabled]).md-primary .md-checked .md-off,md-radio-group.md-default-theme:not([disabled]) .md-primary .md-checked .md-off, md-radio-group:not([disabled]) .md-primary .md-checked .md-off{border-color:rgba(25,118,210,0.87)}md-radio-button.md-default-theme:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-button:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-button:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-button:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-button.md-default-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-button:not([disabled]) .md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-group:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-group:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-group:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-group:not([disabled]) .md-primary .md-checked .md-ink-ripple{color:rgba(25,118,210,0.87)}md-radio-button.md-default-theme:not([disabled]).md-primary .md-container .md-ripple, md-radio-button:not([disabled]).md-primary .md-container .md-ripple,md-radio-button.md-default-theme:not([disabled]) .md-primary .md-container .md-ripple, md-radio-button:not([disabled]) .md-primary .md-container .md-ripple,md-radio-group.md-default-theme:not([disabled]).md-primary .md-container .md-ripple, md-radio-group:not([disabled]).md-primary .md-container .md-ripple,md-radio-group.md-default-theme:not([disabled]) .md-primary .md-container .md-ripple, md-radio-group:not([disabled]) .md-primary .md-container .md-ripple{color:rgb(30,136,229)}md-radio-button.md-default-theme[disabled], md-radio-button[disabled],md-radio-group.md-default-theme[disabled], md-radio-group[disabled]{color:rgba(0,0,0,0.38)}md-radio-button.md-default-theme[disabled] .md-container .md-off, md-radio-button[disabled] .md-container .md-off,md-radio-button.md-default-theme[disabled] .md-container .md-on, md-radio-button[disabled] .md-container .md-on,md-radio-group.md-default-theme[disabled] .md-container .md-off, md-radio-group[disabled] .md-container .md-off,md-radio-group.md-default-theme[disabled] .md-container .md-on, md-radio-group[disabled] .md-container .md-on{border-color:rgba(0,0,0,0.38)}md-radio-group.md-default-theme .md-checked:not([disabled]).md-primary .md-ink-ripple, md-radio-group .md-checked:not([disabled]).md-primary .md-ink-ripple,md-radio-group.md-default-theme.md-primary .md-checked:not([disabled]) .md-ink-ripple, md-radio-group.md-primary .md-checked:not([disabled]) .md-ink-ripple{color:rgba(25,118,210,0.26)}md-radio-group.md-default-theme.md-focused:not(:empty) .md-checked.md-primary .md-container:before, md-radio-group.md-focused:not(:empty) .md-checked.md-primary .md-container:before,md-radio-group.md-default-theme.md-focused:not(:empty).md-primary .md-checked .md-container:before, md-radio-group.md-focused:not(:empty).md-primary .md-checked .md-container:before{background-color:rgba(25,118,210,0.26)}md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-default-theme .md-select-value span:first-child:after, md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select .md-select-value span:first-child:after{color:rgba(0,0,0,0.38)}md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme .md-select-value, md-input-container.md-input-focused:not(.md-input-has-value) md-select .md-select-value,md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme .md-select-value.md-select-placeholder, md-input-container.md-input-focused:not(.md-input-has-value) md-select .md-select-value.md-select-placeholder{color:rgb(25,118,210)}md-input-container.md-input-invalid md-select.md-default-theme.md-no-underline .md-select-value, md-input-container.md-input-invalid md-select.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme .md-select-value, md-select .md-select-value{border-bottom-color:rgba(0,0,0,0.12)}md-select.md-default-theme .md-select-value.md-select-placeholder, md-select .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme.md-no-underline .md-select-value, md-select.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.ng-invalid.ng-touched.md-no-underline .md-select-value, md-select.ng-invalid.ng-touched.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme:not([disabled]):focus .md-select-value, md-select:not([disabled]):focus .md-select-value{border-bottom-color:rgb(25,118,210);color:rgba(0,0,0,0.87)}md-select.md-default-theme:not([disabled]):focus .md-select-value.md-select-placeholder, md-select:not([disabled]):focus .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.87)}md-select.md-default-theme:not([disabled]):focus.md-no-underline .md-select-value, md-select:not([disabled]):focus.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme[disabled] .md-select-icon, md-select[disabled] .md-select-icon,md-select.md-default-theme[disabled] .md-select-value, md-select[disabled] .md-select-value,md-select.md-default-theme[disabled] .md-select-value.md-select-placeholder, md-select[disabled] .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme .md-select-icon, md-select .md-select-icon{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme md-content md-optgroup, md-select-menu md-content md-optgroup{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme md-content md-option, md-select-menu md-content md-option{color:rgba(0,0,0,0.87)}md-select-menu.md-default-theme md-content md-option[disabled] .md-text, md-select-menu md-content md-option[disabled] .md-text{color:rgba(0,0,0,0.38)}md-select-menu.md-default-theme md-content md-option[selected], md-select-menu md-content md-option[selected]{color:rgb(33,150,243)}md-select-menu.md-default-theme md-content md-option[selected]:focus, md-select-menu md-content md-option[selected]:focus{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme .md-ripple, .md-checkbox-enabled .md-ripple{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme .md-ink-ripple, .md-checkbox-enabled .md-ink-ripple{color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme[selected] .md-ink-ripple, .md-checkbox-enabled[selected] .md-ink-ripple{color:rgba(25,118,210,0.87)}.md-checkbox-enabled.md-default-theme:not(.md-checked) .md-icon, .md-checkbox-enabled:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme[selected] .md-icon, .md-checkbox-enabled[selected] .md-icon{background-color:rgba(25,118,210,0.87)}.md-checkbox-enabled.md-default-theme[selected].md-focused .md-container:before, .md-checkbox-enabled[selected].md-focused .md-container:before{background-color:rgba(25,118,210,0.26)}.md-checkbox-enabled.md-default-theme[selected] .md-icon:after, .md-checkbox-enabled[selected] .md-icon:after{border-color:rgba(255,255,255,0.87)}.md-checkbox-enabled.md-default-theme .md-indeterminate[disabled] .md-container, .md-checkbox-enabled .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}.md-checkbox-enabled.md-default-theme md-option .md-text, .md-checkbox-enabled md-option .md-text{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-primary .md-focus-ring, md-slider.md-primary .md-focus-ring{background-color:rgba(144,202,249,0.38)}md-slider.md-default-theme.md-primary .md-track.md-track-fill, md-slider.md-primary .md-track.md-track-fill{background-color:rgb(25,118,210)}md-slider.md-default-theme.md-primary .md-thumb:after, md-slider.md-primary .md-thumb:after{border-color:rgb(25,118,210);background-color:rgb(25,118,210)}md-slider.md-default-theme.md-primary .md-sign, md-slider.md-primary .md-sign{background-color:rgb(25,118,210)}md-slider.md-default-theme.md-primary .md-sign:after, md-slider.md-primary .md-sign:after{border-top-color:rgb(25,118,210)}md-slider.md-default-theme.md-primary[md-vertical] .md-sign:after, md-slider.md-primary[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(25,118,210)}md-slider.md-default-theme.md-primary .md-thumb-text, md-slider.md-primary .md-thumb-text{color:rgba(255,255,255,0.87)}md-slider.md-default-theme[disabled] .md-thumb:after, md-slider[disabled] .md-thumb:after{border-color:transparent}md-slider-container[disabled]>:first-child:not(md-slider),md-slider-container[disabled]>:last-child:not(md-slider){color:rgba(0,0,0,0.38)}.md-subheader.md-default-theme.md-primary, .md-subheader.md-primary{color:rgb(25,118,210)}md-switch.md-default-theme.md-checked.md-primary .md-ink-ripple, md-switch.md-checked.md-primary .md-ink-ripple{color:rgb(25,118,210)}md-switch.md-default-theme.md-checked.md-primary .md-thumb, md-switch.md-checked.md-primary .md-thumb{background-color:rgb(25,118,210)}md-switch.md-default-theme.md-checked.md-primary .md-bar, md-switch.md-checked.md-primary .md-bar{background-color:rgba(25,118,210,0.5)}md-switch.md-default-theme.md-checked.md-primary.md-focused .md-thumb:before, md-switch.md-checked.md-primary.md-focused .md-thumb:before{background-color:rgba(25,118,210,0.26)}md-tabs.md-default-theme .md-paginator md-icon, md-tabs .md-paginator md-icon{color:rgb(25,118,210)}md-tabs.md-default-theme .md-tab, md-tabs .md-tab{color:rgba(0,0,0,0.54)}md-tabs.md-default-theme .md-tab[disabled], md-tabs .md-tab[disabled],md-tabs.md-default-theme .md-tab[disabled] md-icon, md-tabs .md-tab[disabled] md-icon{color:rgba(0,0,0,0.38)}md-tabs.md-default-theme .md-tab.md-active, md-tabs .md-tab.md-active,md-tabs.md-default-theme .md-tab.md-active md-icon, md-tabs .md-tab.md-active md-icon,md-tabs.md-default-theme .md-tab.md-focused, md-tabs .md-tab.md-focused,md-tabs.md-default-theme .md-tab.md-focused md-icon, md-tabs .md-tab.md-focused md-icon{color:rgb(25,118,210)}md-tabs.md-default-theme .md-tab.md-focused, md-tabs .md-tab.md-focused{background:rgba(25,118,210,0.1)}md-tabs.md-default-theme.md-primary>md-tabs-wrapper, md-tabs.md-primary>md-tabs-wrapper{background-color:rgb(25,118,210)}md-tabs.md-default-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-tabs.md-default-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(255,255,255,0.87)}md-tabs.md-default-theme.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper, md-toolbar>md-tabs>md-tabs-wrapper{background-color:rgb(25,118,210)}md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(255,255,255,0.87)}md-toolbar>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toast.md-default-theme .md-toast-content .md-button.md-highlight.md-primary, md-toast .md-toast-content .md-button.md-highlight.md-primary{color:rgb(25,118,210)}md-toolbar.md-default-theme:not(.md-menu-toolbar), md-toolbar:not(.md-menu-toolbar){background-color:rgb(25,118,210);color:rgba(255,255,255,0.87)}md-toolbar.md-default-theme:not(.md-menu-toolbar) md-icon, md-toolbar:not(.md-menu-toolbar) md-icon{color:rgba(255,255,255,0.87);fill:rgba(255,255,255,0.87)}md-toolbar.md-default-theme:not(.md-menu-toolbar) .md-button[disabled] md-icon, md-toolbar:not(.md-menu-toolbar) .md-button[disabled] md-icon{color:rgba(255,255,255,0.26);fill:rgba(255,255,255,0.26)} -md-autocomplete.md-default-theme.md-hue-1 input, md-autocomplete.md-hue-1 input{color:rgba(0,0,0,0.87)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-1 li, .md-autocomplete-suggestions-container.md-hue-1 li{color:rgba(0,0,0,0.87)}md-bottom-sheet.md-default-theme.md-hue-1.md-list md-list-item, md-bottom-sheet.md-hue-1.md-list md-list-item{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1.md-primary, .md-button.md-hue-1.md-primary{color:rgb(33,150,243)}.md-button.md-default-theme.md-hue-1.md-primary.md-fab, .md-button.md-hue-1.md-primary.md-fab,.md-button.md-default-theme.md-hue-1.md-primary.md-raised, .md-button.md-hue-1.md-primary.md-raised{color:rgba(0,0,0,0.87);background-color:rgb(33,150,243)}.md-button.md-default-theme.md-hue-1.md-primary.md-fab:not([disabled]) md-icon, .md-button.md-hue-1.md-primary.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-1.md-primary.md-raised:not([disabled]) md-icon, .md-button.md-hue-1.md-primary.md-raised:not([disabled]) md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1.md-primary.md-fab:not([disabled]).md-focused, .md-button.md-hue-1.md-primary.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1.md-primary.md-fab:not([disabled]):hover, .md-button.md-hue-1.md-primary.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-1.md-primary.md-raised:not([disabled]).md-focused, .md-button.md-hue-1.md-primary.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1.md-primary.md-raised:not([disabled]):hover, .md-button.md-hue-1.md-primary.md-raised:not([disabled]):hover{background-color:rgb(30,136,229)}.md-button.md-default-theme.md-hue-1.md-primary:not([disabled]) md-icon, .md-button.md-hue-1.md-primary:not([disabled]) md-icon{color:rgb(33,150,243)}._md a.md-default-theme.md-hue-1:not(.md-button).md-primary, ._md a.md-hue-1:not(.md-button).md-primary{color:rgb(33,150,243)}._md a.md-default-theme.md-hue-1:not(.md-button).md-primary:hover, ._md a.md-hue-1:not(.md-button).md-primary:hover{color:rgb(25,118,210)}md-card.md-default-theme.md-hue-1 .md-card-image, md-card.md-hue-1 .md-card-image{border-radius:2px 2px 0 0}md-card.md-default-theme.md-hue-1 md-card-header md-card-header-text .md-subhead, md-card.md-hue-1 md-card-header md-card-header-text .md-subhead,md-card.md-default-theme.md-hue-1 md-card-title md-card-title-text:not(:only-child) .md-subhead, md-card.md-hue-1 md-card-title md-card-title-text:not(:only-child) .md-subhead{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-1 .md-ink-ripple, md-checkbox.md-hue-1 .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-1:not(.md-checked) .md-icon, md-checkbox.md-hue-1:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary .md-ripple, md-checkbox.md-hue-1:not([disabled]).md-primary .md-ripple{color:rgb(30,136,229)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-ripple, md-checkbox.md-hue-1:not([disabled]).md-primary.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary .md-ink-ripple, md-checkbox.md-hue-1:not([disabled]).md-primary .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-ink-ripple, md-checkbox.md-hue-1:not([disabled]).md-primary.md-checked .md-ink-ripple{color:rgba(33,150,243,0.87)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary:not(.md-checked) .md-icon, md-checkbox.md-hue-1:not([disabled]).md-primary:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-icon, md-checkbox.md-hue-1:not([disabled]).md-primary.md-checked .md-icon{background-color:rgba(33,150,243,0.87)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked.md-focused .md-container:before, md-checkbox.md-hue-1:not([disabled]).md-primary.md-checked.md-focused .md-container:before{background-color:rgba(33,150,243,0.26)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-icon:after, md-checkbox.md-hue-1:not([disabled]).md-primary.md-checked .md-icon:after{border-color:rgba(0,0,0,0.87)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-primary .md-indeterminate[disabled] .md-container, md-checkbox.md-hue-1:not([disabled]).md-primary .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-1[disabled]:not(.md-checked) .md-icon, md-checkbox.md-hue-1[disabled]:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-1[disabled] .md-icon:after, md-checkbox.md-hue-1[disabled] .md-icon:after{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-1[disabled] .md-label, md-checkbox.md-hue-1[disabled] .md-label{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-1 .md-chips, md-chips.md-hue-1 .md-chips{box-shadow:0 1px rgba(0,0,0,0.12)}md-chips.md-default-theme.md-hue-1 .md-chips.md-focused, md-chips.md-hue-1 .md-chips.md-focused{box-shadow:0 2px rgb(33,150,243)}md-chips.md-default-theme.md-hue-1 .md-chips .md-chip-input-container input, md-chips.md-hue-1 .md-chips .md-chip-input-container input{color:rgba(0,0,0,0.87)}md-chips.md-default-theme.md-hue-1 .md-chips .md-chip-input-container input:-moz-placeholder, md-chips.md-hue-1 .md-chips .md-chip-input-container input:-moz-placeholder,md-chips.md-default-theme.md-hue-1 .md-chips .md-chip-input-container input::-moz-placeholder, md-chips.md-hue-1 .md-chips .md-chip-input-container input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-1 .md-chips .md-chip-input-container input:-ms-input-placeholder, md-chips.md-hue-1 .md-chips .md-chip-input-container input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-1 .md-chips .md-chip-input-container input::-webkit-input-placeholder, md-chips.md-hue-1 .md-chips .md-chip-input-container input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-1 md-chip.md-focused, md-chips.md-hue-1 md-chip.md-focused{background:rgb(33,150,243);color:rgba(0,0,0,0.87)}md-chips.md-default-theme.md-hue-1 md-chip.md-focused md-icon, md-chips.md-hue-1 md-chip.md-focused md-icon{color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-1 .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator, .md-hue-1 .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator{border:1px solid rgb(33,150,243)}.md-default-theme.md-hue-1 .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled, .md-hue-1 .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled{color:rgba(33,150,243,0.6)}.md-default-theme.md-hue-1 .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-hue-1 .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator,.md-default-theme.md-hue-1 .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-hue-1 .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator{background:rgb(33,150,243);color:rgba(0,0,0,0.87);border-color:transparent}.md-default-theme.md-hue-1 .md-calendar-date-disabled, .md-hue-1 .md-calendar-date-disabled,.md-default-theme.md-hue-1 .md-calendar-month-label-disabled, .md-hue-1 .md-calendar-month-label-disabled{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-1 .md-calendar-month-label md-icon, .md-hue-1 .md-calendar-month-label md-icon,.md-default-theme.md-hue-1 .md-datepicker-input, .md-hue-1 .md-datepicker-input{color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-1 .md-datepicker-input:-moz-placeholder, .md-hue-1 .md-datepicker-input:-moz-placeholder,.md-default-theme.md-hue-1 .md-datepicker-input::-moz-placeholder, .md-hue-1 .md-datepicker-input::-moz-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-1 .md-datepicker-input:-ms-input-placeholder, .md-hue-1 .md-datepicker-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-1 .md-datepicker-input::-webkit-input-placeholder, .md-hue-1 .md-datepicker-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-1 .md-datepicker-input-container, .md-hue-1 .md-datepicker-input-container{border-bottom-color:rgba(0,0,0,0.12)}.md-default-theme.md-hue-1 .md-datepicker-input-container.md-datepicker-focused, .md-hue-1 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(33,150,243)}.md-default-theme.md-hue-1 .md-datepicker-triangle-button .md-datepicker-expand-triangle, .md-hue-1 .md-datepicker-triangle-button .md-datepicker-expand-triangle{border-top-color:rgba(0,0,0,0.54)}.md-default-theme.md-hue-1 .md-datepicker-open .md-datepicker-calendar-icon, .md-hue-1 .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(33,150,243)}md-dialog.md-default-theme.md-hue-1.md-content-overflow .md-actions, md-dialog.md-hue-1.md-content-overflow .md-actions,md-dialog.md-default-theme.md-hue-1.md-content-overflow md-dialog-actions, md-dialog.md-hue-1.md-content-overflow md-dialog-actions,md-divider.md-default-theme.md-hue-1, md-divider.md-hue-1{border-top-color:rgba(0,0,0,0.12)}.layout-gt-lg-row>md-divider.md-default-theme.md-hue-1, .layout-gt-lg-row>md-divider.md-hue-1,.layout-gt-md-row>md-divider.md-default-theme.md-hue-1, .layout-gt-md-row>md-divider.md-hue-1,.layout-gt-sm-row>md-divider.md-default-theme.md-hue-1, .layout-gt-sm-row>md-divider.md-hue-1,.layout-gt-xs-row>md-divider.md-default-theme.md-hue-1, .layout-gt-xs-row>md-divider.md-hue-1,.layout-lg-row>md-divider.md-default-theme.md-hue-1, .layout-lg-row>md-divider.md-hue-1,.layout-md-row>md-divider.md-default-theme.md-hue-1, .layout-md-row>md-divider.md-hue-1,.layout-row>md-divider.md-default-theme.md-hue-1, .layout-row>md-divider.md-hue-1,.layout-sm-row>md-divider.md-default-theme.md-hue-1, .layout-sm-row>md-divider.md-hue-1,.layout-xl-row>md-divider.md-default-theme.md-hue-1, .layout-xl-row>md-divider.md-hue-1,.layout-xs-row>md-divider.md-default-theme.md-hue-1, .layout-xs-row>md-divider.md-hue-1{border-right-color:rgba(0,0,0,0.12)}md-icon.md-default-theme.md-hue-1, md-icon.md-hue-1{color:rgba(0,0,0,0.54)}md-icon.md-default-theme.md-hue-1.md-primary, md-icon.md-hue-1.md-primary{color:rgb(33,150,243)}md-input-container.md-default-theme.md-hue-1 .md-input, md-input-container.md-hue-1 .md-input{color:rgba(0,0,0,0.87);border-color:rgba(0,0,0,0.12)}md-input-container.md-default-theme.md-hue-1 .md-input:-moz-placeholder, md-input-container.md-hue-1 .md-input:-moz-placeholder,md-input-container.md-default-theme.md-hue-1 .md-input::-moz-placeholder, md-input-container.md-hue-1 .md-input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-1 .md-input:-ms-input-placeholder, md-input-container.md-hue-1 .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-1 .md-input::-webkit-input-placeholder, md-input-container.md-hue-1 .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-1>md-icon, md-input-container.md-hue-1>md-icon{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme.md-hue-1 .md-placeholder, md-input-container.md-hue-1 .md-placeholder,md-input-container.md-default-theme.md-hue-1 label, md-input-container.md-hue-1 label{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-1:not(.md-input-focused):not(.md-input-invalid) label.md-required:after, md-input-container.md-hue-1:not(.md-input-focused):not(.md-input-invalid) label.md-required:after{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-1 .md-input-message-animation .md-char-counter, md-input-container.md-hue-1 .md-input-message-animation .md-char-counter,md-input-container.md-default-theme.md-hue-1 .md-input-messages-animation .md-char-counter, md-input-container.md-hue-1 .md-input-messages-animation .md-char-counter{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme.md-hue-1.md-input-focused .md-input:-moz-placeholder, md-input-container.md-hue-1.md-input-focused .md-input:-moz-placeholder,md-input-container.md-default-theme.md-hue-1.md-input-focused .md-input::-moz-placeholder, md-input-container.md-hue-1.md-input-focused .md-input::-moz-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-1.md-input-focused .md-input:-ms-input-placeholder, md-input-container.md-hue-1.md-input-focused .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-1.md-input-focused .md-input::-webkit-input-placeholder, md-input-container.md-hue-1.md-input-focused .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-has-value label, md-input-container.md-hue-1:not(.md-input-invalid).md-input-has-value label{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused .md-input, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused .md-input,md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-resized .md-input, md-input-container.md-hue-1:not(.md-input-invalid).md-input-resized .md-input{border-color:rgb(33,150,243)}md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused label, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused label,md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused md-icon, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused md-icon{color:rgb(33,150,243)}md-list.md-default-theme.md-hue-1 md-list-item.md-2-line .md-list-item-text h3, md-list.md-hue-1 md-list-item.md-2-line .md-list-item-text h3,md-list.md-default-theme.md-hue-1 md-list-item.md-2-line .md-list-item-text h4, md-list.md-hue-1 md-list-item.md-2-line .md-list-item-text h4,md-list.md-default-theme.md-hue-1 md-list-item.md-3-line .md-list-item-text h3, md-list.md-hue-1 md-list-item.md-3-line .md-list-item-text h3,md-list.md-default-theme.md-hue-1 md-list-item.md-3-line .md-list-item-text h4, md-list.md-hue-1 md-list-item.md-3-line .md-list-item-text h4{color:rgba(0,0,0,0.87)}md-list.md-default-theme.md-hue-1 md-list-item.md-2-line .md-list-item-text p, md-list.md-hue-1 md-list-item.md-2-line .md-list-item-text p,md-list.md-default-theme.md-hue-1 md-list-item.md-3-line .md-list-item-text p, md-list.md-hue-1 md-list-item.md-3-line .md-list-item-text p{color:rgba(0,0,0,0.54)}md-list.md-default-theme.md-hue-1 md-list-item>md-icon, md-list.md-hue-1 md-list-item>md-icon{color:rgba(0,0,0,0.54)}md-list.md-default-theme.md-hue-1 md-list-item>md-icon.md-highlight, md-list.md-hue-1 md-list-item>md-icon.md-highlight{color:rgb(33,150,243)}md-menu-content.md-default-theme.md-hue-1 md-menu-item, md-menu-content.md-hue-1 md-menu-item{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme.md-hue-1 md-menu-item md-icon, md-menu-content.md-hue-1 md-menu-item md-icon{color:rgba(0,0,0,0.54)}md-menu-content.md-default-theme.md-hue-1 md-menu-item .md-button[disabled], md-menu-content.md-hue-1 md-menu-item .md-button[disabled],md-menu-content.md-default-theme.md-hue-1 md-menu-item .md-button[disabled] md-icon, md-menu-content.md-hue-1 md-menu-item .md-button[disabled] md-icon{color:rgba(0,0,0,0.38)}md-menu-bar.md-default-theme.md-hue-1>button.md-button, md-menu-bar.md-hue-1>button.md-button{color:rgba(0,0,0,0.87);border-radius:2px}md-menu-bar.md-default-theme.md-hue-1 md-menu>button, md-menu-bar.md-hue-1 md-menu>button{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme.md-hue-1 .md-menu>.md-button:after, md-menu-content.md-hue-1 .md-menu>.md-button:after{color:rgba(0,0,0,0.54)}md-toolbar.md-default-theme.md-hue-1.md-menu-toolbar md-toolbar-filler, md-toolbar.md-hue-1.md-menu-toolbar md-toolbar-filler{background-color:rgb(33,150,243);color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-1.md-menu-toolbar md-toolbar-filler md-icon, md-toolbar.md-hue-1.md-menu-toolbar md-toolbar-filler md-icon{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-1 .md-button._md-nav-button.md-unselected, md-nav-bar.md-hue-1 .md-button._md-nav-button.md-unselected{color:rgba(0,0,0,0.54)}md-nav-bar.md-default-theme.md-hue-1 .md-button._md-nav-button[disabled], md-nav-bar.md-hue-1 .md-button._md-nav-button[disabled]{color:rgba(0,0,0,0.38)}md-nav-bar.md-default-theme.md-hue-1.md-primary>.md-nav-bar, md-nav-bar.md-hue-1.md-primary>.md-nav-bar{background-color:rgb(33,150,243)}md-nav-bar.md-default-theme.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-nav-bar.md-default-theme.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-1.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-toolbar>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar, md-toolbar>md-nav-bar.md-hue-1>.md-nav-bar{background-color:rgb(33,150,243)}md-toolbar>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button, md-toolbar>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-toolbar>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-toolbar>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-progress-circular.md-default-theme.md-hue-1 path, md-progress-circular.md-hue-1 path{stroke:rgb(33,150,243)}md-progress-linear.md-default-theme.md-hue-1 .md-container, md-progress-linear.md-hue-1 .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-1 .md-bar, md-progress-linear.md-hue-1 .md-bar{background-color:rgb(33,150,243)}md-progress-linear.md-default-theme.md-hue-1[md-mode=buffer].md-primary .md-bar1, md-progress-linear.md-hue-1[md-mode=buffer].md-primary .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-1[md-mode=buffer].md-primary .md-dashed:before, md-progress-linear.md-hue-1[md-mode=buffer].md-primary .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-1 .md-off, md-radio-button.md-hue-1 .md-off{border-color:rgba(0,0,0,0.54)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-primary .md-on, md-radio-button.md-hue-1:not([disabled]).md-primary .md-on,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-on, md-radio-button.md-hue-1:not([disabled]) .md-primary .md-on,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-primary .md-on, md-radio-group.md-hue-1:not([disabled]).md-primary .md-on,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-on, md-radio-group.md-hue-1:not([disabled]) .md-primary .md-on{background-color:rgba(33,150,243,0.87)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-off, md-radio-button.md-hue-1:not([disabled]).md-primary.md-checked .md-off,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-primary.md-checked .md-off, md-radio-button.md-hue-1:not([disabled]) .md-primary.md-checked .md-off,md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-primary .md-checked .md-off, md-radio-button.md-hue-1:not([disabled]).md-primary .md-checked .md-off,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-checked .md-off, md-radio-button.md-hue-1:not([disabled]) .md-primary .md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-off, md-radio-group.md-hue-1:not([disabled]).md-primary.md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-primary.md-checked .md-off, md-radio-group.md-hue-1:not([disabled]) .md-primary.md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-primary .md-checked .md-off, md-radio-group.md-hue-1:not([disabled]).md-primary .md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-checked .md-off, md-radio-group.md-hue-1:not([disabled]) .md-primary .md-checked .md-off{border-color:rgba(33,150,243,0.87)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]) .md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]) .md-primary .md-checked .md-ink-ripple{color:rgba(33,150,243,0.87)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-primary .md-container .md-ripple, md-radio-button.md-hue-1:not([disabled]).md-primary .md-container .md-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-container .md-ripple, md-radio-button.md-hue-1:not([disabled]) .md-primary .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-primary .md-container .md-ripple, md-radio-group.md-hue-1:not([disabled]).md-primary .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-primary .md-container .md-ripple, md-radio-group.md-hue-1:not([disabled]) .md-primary .md-container .md-ripple{color:rgb(30,136,229)}md-radio-button.md-default-theme.md-hue-1[disabled], md-radio-button.md-hue-1[disabled],md-radio-group.md-default-theme.md-hue-1[disabled], md-radio-group.md-hue-1[disabled]{color:rgba(0,0,0,0.38)}md-radio-button.md-default-theme.md-hue-1[disabled] .md-container .md-off, md-radio-button.md-hue-1[disabled] .md-container .md-off,md-radio-button.md-default-theme.md-hue-1[disabled] .md-container .md-on, md-radio-button.md-hue-1[disabled] .md-container .md-on,md-radio-group.md-default-theme.md-hue-1[disabled] .md-container .md-off, md-radio-group.md-hue-1[disabled] .md-container .md-off,md-radio-group.md-default-theme.md-hue-1[disabled] .md-container .md-on, md-radio-group.md-hue-1[disabled] .md-container .md-on{border-color:rgba(0,0,0,0.38)}md-radio-group.md-default-theme.md-hue-1 .md-checked:not([disabled]).md-primary .md-ink-ripple, md-radio-group.md-hue-1 .md-checked:not([disabled]).md-primary .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1.md-primary .md-checked:not([disabled]) .md-ink-ripple, md-radio-group.md-hue-1.md-primary .md-checked:not([disabled]) .md-ink-ripple{color:rgba(33,150,243,0.26)}md-radio-group.md-default-theme.md-hue-1.md-focused:not(:empty) .md-checked.md-primary .md-container:before, md-radio-group.md-hue-1.md-focused:not(:empty) .md-checked.md-primary .md-container:before,md-radio-group.md-default-theme.md-hue-1.md-focused:not(:empty).md-primary .md-checked .md-container:before, md-radio-group.md-hue-1.md-focused:not(:empty).md-primary .md-checked .md-container:before{background-color:rgba(33,150,243,0.26)}md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-default-theme.md-hue-1 .md-select-value span:first-child:after, md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-hue-1 .md-select-value span:first-child:after{color:rgba(0,0,0,0.38)}md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme.md-hue-1 .md-select-value, md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-hue-1 .md-select-value,md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme.md-hue-1 .md-select-value.md-select-placeholder, md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-hue-1 .md-select-value.md-select-placeholder{color:rgb(33,150,243)}md-input-container.md-input-invalid md-select.md-default-theme.md-hue-1.md-no-underline .md-select-value, md-input-container.md-input-invalid md-select.md-hue-1.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-1 .md-select-value, md-select.md-hue-1 .md-select-value{border-bottom-color:rgba(0,0,0,0.12)}md-select.md-default-theme.md-hue-1 .md-select-value.md-select-placeholder, md-select.md-hue-1 .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme.md-hue-1.md-no-underline .md-select-value, md-select.md-hue-1.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-1.ng-invalid.ng-touched.md-no-underline .md-select-value, md-select.md-hue-1.ng-invalid.ng-touched.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-1:not([disabled]):focus .md-select-value, md-select.md-hue-1:not([disabled]):focus .md-select-value{border-bottom-color:rgb(33,150,243);color:rgba(0,0,0,0.87)}md-select.md-default-theme.md-hue-1:not([disabled]):focus .md-select-value.md-select-placeholder, md-select.md-hue-1:not([disabled]):focus .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.87)}md-select.md-default-theme.md-hue-1:not([disabled]):focus.md-no-underline .md-select-value, md-select.md-hue-1:not([disabled]):focus.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-1[disabled] .md-select-icon, md-select.md-hue-1[disabled] .md-select-icon,md-select.md-default-theme.md-hue-1[disabled] .md-select-value, md-select.md-hue-1[disabled] .md-select-value,md-select.md-default-theme.md-hue-1[disabled] .md-select-value.md-select-placeholder, md-select.md-hue-1[disabled] .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme.md-hue-1 .md-select-icon, md-select.md-hue-1 .md-select-icon{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme.md-hue-1 md-content md-optgroup, md-select-menu.md-hue-1 md-content md-optgroup{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme.md-hue-1 md-content md-option, md-select-menu.md-hue-1 md-content md-option{color:rgba(0,0,0,0.87)}md-select-menu.md-default-theme.md-hue-1 md-content md-option[disabled] .md-text, md-select-menu.md-hue-1 md-content md-option[disabled] .md-text{color:rgba(0,0,0,0.38)}md-select-menu.md-default-theme.md-hue-1 md-content md-option[selected], md-select-menu.md-hue-1 md-content md-option[selected]{color:rgb(33,150,243)}md-select-menu.md-default-theme.md-hue-1 md-content md-option[selected]:focus, md-select-menu.md-hue-1 md-content md-option[selected]:focus{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme.md-hue-1 .md-ripple, .md-checkbox-enabled.md-hue-1 .md-ripple{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme.md-hue-1 .md-ink-ripple, .md-checkbox-enabled.md-hue-1 .md-ink-ripple{color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme.md-hue-1[selected] .md-ink-ripple, .md-checkbox-enabled.md-hue-1[selected] .md-ink-ripple{color:rgba(33,150,243,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-1:not(.md-checked) .md-icon, .md-checkbox-enabled.md-hue-1:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme.md-hue-1[selected] .md-icon, .md-checkbox-enabled.md-hue-1[selected] .md-icon{background-color:rgba(33,150,243,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-1[selected].md-focused .md-container:before, .md-checkbox-enabled.md-hue-1[selected].md-focused .md-container:before{background-color:rgba(33,150,243,0.26)}.md-checkbox-enabled.md-default-theme.md-hue-1[selected] .md-icon:after, .md-checkbox-enabled.md-hue-1[selected] .md-icon:after{border-color:rgba(0,0,0,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-1 .md-indeterminate[disabled] .md-container, .md-checkbox-enabled.md-hue-1 .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}.md-checkbox-enabled.md-default-theme.md-hue-1 md-option .md-text, .md-checkbox-enabled.md-hue-1 md-option .md-text{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-1.md-primary .md-focus-ring, md-slider.md-hue-1.md-primary .md-focus-ring{background-color:rgba(144,202,249,0.38)}md-slider.md-default-theme.md-hue-1.md-primary .md-track.md-track-fill, md-slider.md-hue-1.md-primary .md-track.md-track-fill{background-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-1.md-primary .md-thumb:after, md-slider.md-hue-1.md-primary .md-thumb:after{border-color:rgb(33,150,243);background-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-1.md-primary .md-sign, md-slider.md-hue-1.md-primary .md-sign{background-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-1.md-primary .md-sign:after, md-slider.md-hue-1.md-primary .md-sign:after{border-top-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-1.md-primary[md-vertical] .md-sign:after, md-slider.md-hue-1.md-primary[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-1.md-primary .md-thumb-text, md-slider.md-hue-1.md-primary .md-thumb-text{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-1[disabled] .md-thumb:after, md-slider.md-hue-1[disabled] .md-thumb:after{border-color:transparent}md-slider-container[disabled]>:first-child:not(md-slider),md-slider-container[disabled]>:last-child:not(md-slider){color:rgba(0,0,0,0.38)}.md-subheader.md-default-theme.md-hue-1.md-primary, .md-subheader.md-hue-1.md-primary{color:rgb(33,150,243)}md-switch.md-default-theme.md-hue-1.md-checked.md-primary .md-ink-ripple, md-switch.md-hue-1.md-checked.md-primary .md-ink-ripple{color:rgb(33,150,243)}md-switch.md-default-theme.md-hue-1.md-checked.md-primary .md-thumb, md-switch.md-hue-1.md-checked.md-primary .md-thumb{background-color:rgb(33,150,243)}md-switch.md-default-theme.md-hue-1.md-checked.md-primary .md-bar, md-switch.md-hue-1.md-checked.md-primary .md-bar{background-color:rgba(33,150,243,0.5)}md-switch.md-default-theme.md-hue-1.md-checked.md-primary.md-focused .md-thumb:before, md-switch.md-hue-1.md-checked.md-primary.md-focused .md-thumb:before{background-color:rgba(33,150,243,0.26)}md-tabs.md-default-theme.md-hue-1 .md-paginator md-icon, md-tabs.md-hue-1 .md-paginator md-icon{color:rgb(33,150,243)}md-tabs.md-default-theme.md-hue-1 .md-tab, md-tabs.md-hue-1 .md-tab{color:rgba(0,0,0,0.54)}md-tabs.md-default-theme.md-hue-1 .md-tab[disabled], md-tabs.md-hue-1 .md-tab[disabled],md-tabs.md-default-theme.md-hue-1 .md-tab[disabled] md-icon, md-tabs.md-hue-1 .md-tab[disabled] md-icon{color:rgba(0,0,0,0.38)}md-tabs.md-default-theme.md-hue-1 .md-tab.md-active, md-tabs.md-hue-1 .md-tab.md-active,md-tabs.md-default-theme.md-hue-1 .md-tab.md-active md-icon, md-tabs.md-hue-1 .md-tab.md-active md-icon,md-tabs.md-default-theme.md-hue-1 .md-tab.md-focused, md-tabs.md-hue-1 .md-tab.md-focused,md-tabs.md-default-theme.md-hue-1 .md-tab.md-focused md-icon, md-tabs.md-hue-1 .md-tab.md-focused md-icon{color:rgb(33,150,243)}md-tabs.md-default-theme.md-hue-1 .md-tab.md-focused, md-tabs.md-hue-1 .md-tab.md-focused{background:rgba(33,150,243,0.1)}md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper, md-tabs.md-hue-1.md-primary>md-tabs-wrapper{background-color:rgb(33,150,243)}md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-tabs.md-default-theme.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-1.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper, md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper{background-color:rgb(33,150,243)}md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-toolbar>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toast.md-default-theme.md-hue-1 .md-toast-content .md-button.md-highlight.md-primary, md-toast.md-hue-1 .md-toast-content .md-button.md-highlight.md-primary{color:rgb(33,150,243)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar), md-toolbar.md-hue-1:not(.md-menu-toolbar){background-color:rgb(33,150,243);color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar) md-icon, md-toolbar.md-hue-1:not(.md-menu-toolbar) md-icon{color:rgba(0,0,0,0.87);fill:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar) .md-button[disabled] md-icon, md-toolbar.md-hue-1:not(.md-menu-toolbar) .md-button[disabled] md-icon{color:rgba(0,0,0,0.26);fill:rgba(0,0,0,0.26)} -md-autocomplete.md-default-theme.md-hue-2 input, md-autocomplete.md-hue-2 input{color:rgba(0,0,0,0.87)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-2 li, .md-autocomplete-suggestions-container.md-hue-2 li{color:rgba(0,0,0,0.87)}md-bottom-sheet.md-default-theme.md-hue-2.md-list md-list-item, md-bottom-sheet.md-hue-2.md-list md-list-item{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-2.md-primary, .md-button.md-hue-2.md-primary{color:rgb(25,118,210)}.md-button.md-default-theme.md-hue-2.md-primary.md-fab, .md-button.md-hue-2.md-primary.md-fab,.md-button.md-default-theme.md-hue-2.md-primary.md-raised, .md-button.md-hue-2.md-primary.md-raised{color:rgba(255,255,255,0.87);background-color:rgb(25,118,210)}.md-button.md-default-theme.md-hue-2.md-primary.md-fab:not([disabled]) md-icon, .md-button.md-hue-2.md-primary.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-2.md-primary.md-raised:not([disabled]) md-icon, .md-button.md-hue-2.md-primary.md-raised:not([disabled]) md-icon{color:rgba(255,255,255,0.87)}.md-button.md-default-theme.md-hue-2.md-primary.md-fab:not([disabled]).md-focused, .md-button.md-hue-2.md-primary.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2.md-primary.md-fab:not([disabled]):hover, .md-button.md-hue-2.md-primary.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-2.md-primary.md-raised:not([disabled]).md-focused, .md-button.md-hue-2.md-primary.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2.md-primary.md-raised:not([disabled]):hover, .md-button.md-hue-2.md-primary.md-raised:not([disabled]):hover{background-color:rgb(30,136,229)}.md-button.md-default-theme.md-hue-2.md-primary:not([disabled]) md-icon, .md-button.md-hue-2.md-primary:not([disabled]) md-icon{color:rgb(25,118,210)}._md a.md-default-theme.md-hue-2:not(.md-button).md-primary, ._md a.md-hue-2:not(.md-button).md-primary{color:rgb(25,118,210)}._md a.md-default-theme.md-hue-2:not(.md-button).md-primary:hover, ._md a.md-hue-2:not(.md-button).md-primary:hover{color:rgb(25,118,210)}md-card.md-default-theme.md-hue-2 .md-card-image, md-card.md-hue-2 .md-card-image{border-radius:2px 2px 0 0}md-card.md-default-theme.md-hue-2 md-card-header md-card-header-text .md-subhead, md-card.md-hue-2 md-card-header md-card-header-text .md-subhead,md-card.md-default-theme.md-hue-2 md-card-title md-card-title-text:not(:only-child) .md-subhead, md-card.md-hue-2 md-card-title md-card-title-text:not(:only-child) .md-subhead{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-2 .md-ink-ripple, md-checkbox.md-hue-2 .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-2:not(.md-checked) .md-icon, md-checkbox.md-hue-2:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary .md-ripple, md-checkbox.md-hue-2:not([disabled]).md-primary .md-ripple{color:rgb(30,136,229)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-ripple, md-checkbox.md-hue-2:not([disabled]).md-primary.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary .md-ink-ripple, md-checkbox.md-hue-2:not([disabled]).md-primary .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-ink-ripple, md-checkbox.md-hue-2:not([disabled]).md-primary.md-checked .md-ink-ripple{color:rgba(25,118,210,0.87)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary:not(.md-checked) .md-icon, md-checkbox.md-hue-2:not([disabled]).md-primary:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-icon, md-checkbox.md-hue-2:not([disabled]).md-primary.md-checked .md-icon{background-color:rgba(25,118,210,0.87)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked.md-focused .md-container:before, md-checkbox.md-hue-2:not([disabled]).md-primary.md-checked.md-focused .md-container:before{background-color:rgba(25,118,210,0.26)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-icon:after, md-checkbox.md-hue-2:not([disabled]).md-primary.md-checked .md-icon:after{border-color:rgba(255,255,255,0.87)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-primary .md-indeterminate[disabled] .md-container, md-checkbox.md-hue-2:not([disabled]).md-primary .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-2[disabled]:not(.md-checked) .md-icon, md-checkbox.md-hue-2[disabled]:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-2[disabled] .md-icon:after, md-checkbox.md-hue-2[disabled] .md-icon:after{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-2[disabled] .md-label, md-checkbox.md-hue-2[disabled] .md-label{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-2 .md-chips, md-chips.md-hue-2 .md-chips{box-shadow:0 1px rgba(0,0,0,0.12)}md-chips.md-default-theme.md-hue-2 .md-chips.md-focused, md-chips.md-hue-2 .md-chips.md-focused{box-shadow:0 2px rgb(25,118,210)}md-chips.md-default-theme.md-hue-2 .md-chips .md-chip-input-container input, md-chips.md-hue-2 .md-chips .md-chip-input-container input{color:rgba(0,0,0,0.87)}md-chips.md-default-theme.md-hue-2 .md-chips .md-chip-input-container input:-moz-placeholder, md-chips.md-hue-2 .md-chips .md-chip-input-container input:-moz-placeholder,md-chips.md-default-theme.md-hue-2 .md-chips .md-chip-input-container input::-moz-placeholder, md-chips.md-hue-2 .md-chips .md-chip-input-container input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-2 .md-chips .md-chip-input-container input:-ms-input-placeholder, md-chips.md-hue-2 .md-chips .md-chip-input-container input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-2 .md-chips .md-chip-input-container input::-webkit-input-placeholder, md-chips.md-hue-2 .md-chips .md-chip-input-container input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-2 md-chip.md-focused, md-chips.md-hue-2 md-chip.md-focused{background:rgb(25,118,210);color:rgba(255,255,255,0.87)}md-chips.md-default-theme.md-hue-2 md-chip.md-focused md-icon, md-chips.md-hue-2 md-chip.md-focused md-icon{color:rgba(255,255,255,0.87)}.md-default-theme.md-hue-2 .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator, .md-hue-2 .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator{border:1px solid rgb(33,150,243)}.md-default-theme.md-hue-2 .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled, .md-hue-2 .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled{color:rgba(33,150,243,0.6)}.md-default-theme.md-hue-2 .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-hue-2 .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator,.md-default-theme.md-hue-2 .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-hue-2 .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator{background:rgb(33,150,243);color:rgba(0,0,0,0.87);border-color:transparent}.md-default-theme.md-hue-2 .md-calendar-date-disabled, .md-hue-2 .md-calendar-date-disabled,.md-default-theme.md-hue-2 .md-calendar-month-label-disabled, .md-hue-2 .md-calendar-month-label-disabled{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-2 .md-calendar-month-label md-icon, .md-hue-2 .md-calendar-month-label md-icon,.md-default-theme.md-hue-2 .md-datepicker-input, .md-hue-2 .md-datepicker-input{color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-2 .md-datepicker-input:-moz-placeholder, .md-hue-2 .md-datepicker-input:-moz-placeholder,.md-default-theme.md-hue-2 .md-datepicker-input::-moz-placeholder, .md-hue-2 .md-datepicker-input::-moz-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-2 .md-datepicker-input:-ms-input-placeholder, .md-hue-2 .md-datepicker-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-2 .md-datepicker-input::-webkit-input-placeholder, .md-hue-2 .md-datepicker-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-2 .md-datepicker-input-container, .md-hue-2 .md-datepicker-input-container{border-bottom-color:rgba(0,0,0,0.12)}.md-default-theme.md-hue-2 .md-datepicker-input-container.md-datepicker-focused, .md-hue-2 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(25,118,210)}.md-default-theme.md-hue-2 .md-datepicker-triangle-button .md-datepicker-expand-triangle, .md-hue-2 .md-datepicker-triangle-button .md-datepicker-expand-triangle{border-top-color:rgba(0,0,0,0.54)}.md-default-theme.md-hue-2 .md-datepicker-open .md-datepicker-calendar-icon, .md-hue-2 .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(25,118,210)}md-dialog.md-default-theme.md-hue-2.md-content-overflow .md-actions, md-dialog.md-hue-2.md-content-overflow .md-actions,md-dialog.md-default-theme.md-hue-2.md-content-overflow md-dialog-actions, md-dialog.md-hue-2.md-content-overflow md-dialog-actions,md-divider.md-default-theme.md-hue-2, md-divider.md-hue-2{border-top-color:rgba(0,0,0,0.12)}.layout-gt-lg-row>md-divider.md-default-theme.md-hue-2, .layout-gt-lg-row>md-divider.md-hue-2,.layout-gt-md-row>md-divider.md-default-theme.md-hue-2, .layout-gt-md-row>md-divider.md-hue-2,.layout-gt-sm-row>md-divider.md-default-theme.md-hue-2, .layout-gt-sm-row>md-divider.md-hue-2,.layout-gt-xs-row>md-divider.md-default-theme.md-hue-2, .layout-gt-xs-row>md-divider.md-hue-2,.layout-lg-row>md-divider.md-default-theme.md-hue-2, .layout-lg-row>md-divider.md-hue-2,.layout-md-row>md-divider.md-default-theme.md-hue-2, .layout-md-row>md-divider.md-hue-2,.layout-row>md-divider.md-default-theme.md-hue-2, .layout-row>md-divider.md-hue-2,.layout-sm-row>md-divider.md-default-theme.md-hue-2, .layout-sm-row>md-divider.md-hue-2,.layout-xl-row>md-divider.md-default-theme.md-hue-2, .layout-xl-row>md-divider.md-hue-2,.layout-xs-row>md-divider.md-default-theme.md-hue-2, .layout-xs-row>md-divider.md-hue-2{border-right-color:rgba(0,0,0,0.12)}md-icon.md-default-theme.md-hue-2, md-icon.md-hue-2{color:rgba(0,0,0,0.54)}md-icon.md-default-theme.md-hue-2.md-primary, md-icon.md-hue-2.md-primary{color:rgb(25,118,210)}md-input-container.md-default-theme.md-hue-2 .md-input, md-input-container.md-hue-2 .md-input{color:rgba(0,0,0,0.87);border-color:rgba(0,0,0,0.12)}md-input-container.md-default-theme.md-hue-2 .md-input:-moz-placeholder, md-input-container.md-hue-2 .md-input:-moz-placeholder,md-input-container.md-default-theme.md-hue-2 .md-input::-moz-placeholder, md-input-container.md-hue-2 .md-input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-2 .md-input:-ms-input-placeholder, md-input-container.md-hue-2 .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-2 .md-input::-webkit-input-placeholder, md-input-container.md-hue-2 .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-2>md-icon, md-input-container.md-hue-2>md-icon{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme.md-hue-2 .md-placeholder, md-input-container.md-hue-2 .md-placeholder,md-input-container.md-default-theme.md-hue-2 label, md-input-container.md-hue-2 label{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-2:not(.md-input-focused):not(.md-input-invalid) label.md-required:after, md-input-container.md-hue-2:not(.md-input-focused):not(.md-input-invalid) label.md-required:after{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-2 .md-input-message-animation .md-char-counter, md-input-container.md-hue-2 .md-input-message-animation .md-char-counter,md-input-container.md-default-theme.md-hue-2 .md-input-messages-animation .md-char-counter, md-input-container.md-hue-2 .md-input-messages-animation .md-char-counter{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme.md-hue-2.md-input-focused .md-input:-moz-placeholder, md-input-container.md-hue-2.md-input-focused .md-input:-moz-placeholder,md-input-container.md-default-theme.md-hue-2.md-input-focused .md-input::-moz-placeholder, md-input-container.md-hue-2.md-input-focused .md-input::-moz-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-2.md-input-focused .md-input:-ms-input-placeholder, md-input-container.md-hue-2.md-input-focused .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-2.md-input-focused .md-input::-webkit-input-placeholder, md-input-container.md-hue-2.md-input-focused .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-has-value label, md-input-container.md-hue-2:not(.md-input-invalid).md-input-has-value label{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused .md-input, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused .md-input,md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-resized .md-input, md-input-container.md-hue-2:not(.md-input-invalid).md-input-resized .md-input{border-color:rgb(25,118,210)}md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused label, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused label,md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused md-icon, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused md-icon{color:rgb(25,118,210)}md-list.md-default-theme.md-hue-2 md-list-item.md-2-line .md-list-item-text h3, md-list.md-hue-2 md-list-item.md-2-line .md-list-item-text h3,md-list.md-default-theme.md-hue-2 md-list-item.md-2-line .md-list-item-text h4, md-list.md-hue-2 md-list-item.md-2-line .md-list-item-text h4,md-list.md-default-theme.md-hue-2 md-list-item.md-3-line .md-list-item-text h3, md-list.md-hue-2 md-list-item.md-3-line .md-list-item-text h3,md-list.md-default-theme.md-hue-2 md-list-item.md-3-line .md-list-item-text h4, md-list.md-hue-2 md-list-item.md-3-line .md-list-item-text h4{color:rgba(0,0,0,0.87)}md-list.md-default-theme.md-hue-2 md-list-item.md-2-line .md-list-item-text p, md-list.md-hue-2 md-list-item.md-2-line .md-list-item-text p,md-list.md-default-theme.md-hue-2 md-list-item.md-3-line .md-list-item-text p, md-list.md-hue-2 md-list-item.md-3-line .md-list-item-text p{color:rgba(0,0,0,0.54)}md-list.md-default-theme.md-hue-2 md-list-item>md-icon, md-list.md-hue-2 md-list-item>md-icon{color:rgba(0,0,0,0.54)}md-list.md-default-theme.md-hue-2 md-list-item>md-icon.md-highlight, md-list.md-hue-2 md-list-item>md-icon.md-highlight{color:rgb(25,118,210)}md-menu-content.md-default-theme.md-hue-2 md-menu-item, md-menu-content.md-hue-2 md-menu-item{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme.md-hue-2 md-menu-item md-icon, md-menu-content.md-hue-2 md-menu-item md-icon{color:rgba(0,0,0,0.54)}md-menu-content.md-default-theme.md-hue-2 md-menu-item .md-button[disabled], md-menu-content.md-hue-2 md-menu-item .md-button[disabled],md-menu-content.md-default-theme.md-hue-2 md-menu-item .md-button[disabled] md-icon, md-menu-content.md-hue-2 md-menu-item .md-button[disabled] md-icon{color:rgba(0,0,0,0.38)}md-menu-bar.md-default-theme.md-hue-2>button.md-button, md-menu-bar.md-hue-2>button.md-button{color:rgba(0,0,0,0.87);border-radius:2px}md-menu-bar.md-default-theme.md-hue-2 md-menu>button, md-menu-bar.md-hue-2 md-menu>button{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme.md-hue-2 .md-menu>.md-button:after, md-menu-content.md-hue-2 .md-menu>.md-button:after{color:rgba(0,0,0,0.54)}md-toolbar.md-default-theme.md-hue-2.md-menu-toolbar md-toolbar-filler, md-toolbar.md-hue-2.md-menu-toolbar md-toolbar-filler{background-color:rgb(25,118,210);color:rgba(255,255,255,0.87)}md-toolbar.md-default-theme.md-hue-2.md-menu-toolbar md-toolbar-filler md-icon, md-toolbar.md-hue-2.md-menu-toolbar md-toolbar-filler md-icon{color:rgba(255,255,255,0.87)}md-nav-bar.md-default-theme.md-hue-2 .md-button._md-nav-button.md-unselected, md-nav-bar.md-hue-2 .md-button._md-nav-button.md-unselected{color:rgba(0,0,0,0.54)}md-nav-bar.md-default-theme.md-hue-2 .md-button._md-nav-button[disabled], md-nav-bar.md-hue-2 .md-button._md-nav-button[disabled]{color:rgba(0,0,0,0.38)}md-nav-bar.md-default-theme.md-hue-2.md-primary>.md-nav-bar, md-nav-bar.md-hue-2.md-primary>.md-nav-bar{background-color:rgb(25,118,210)}md-nav-bar.md-default-theme.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-nav-bar.md-default-theme.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(255,255,255,0.87)}md-nav-bar.md-default-theme.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-2.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-toolbar>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar, md-toolbar>md-nav-bar.md-hue-2>.md-nav-bar{background-color:rgb(25,118,210)}md-toolbar>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button, md-toolbar>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-toolbar>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(255,255,255,0.87)}md-toolbar>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-progress-circular.md-default-theme.md-hue-2 path, md-progress-circular.md-hue-2 path{stroke:rgb(25,118,210)}md-progress-linear.md-default-theme.md-hue-2 .md-container, md-progress-linear.md-hue-2 .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-2 .md-bar, md-progress-linear.md-hue-2 .md-bar{background-color:rgb(25,118,210)}md-progress-linear.md-default-theme.md-hue-2[md-mode=buffer].md-primary .md-bar1, md-progress-linear.md-hue-2[md-mode=buffer].md-primary .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-2[md-mode=buffer].md-primary .md-dashed:before, md-progress-linear.md-hue-2[md-mode=buffer].md-primary .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-2 .md-off, md-radio-button.md-hue-2 .md-off{border-color:rgba(0,0,0,0.54)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-primary .md-on, md-radio-button.md-hue-2:not([disabled]).md-primary .md-on,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-on, md-radio-button.md-hue-2:not([disabled]) .md-primary .md-on,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-primary .md-on, md-radio-group.md-hue-2:not([disabled]).md-primary .md-on,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-on, md-radio-group.md-hue-2:not([disabled]) .md-primary .md-on{background-color:rgba(25,118,210,0.87)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-off, md-radio-button.md-hue-2:not([disabled]).md-primary.md-checked .md-off,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-primary.md-checked .md-off, md-radio-button.md-hue-2:not([disabled]) .md-primary.md-checked .md-off,md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-primary .md-checked .md-off, md-radio-button.md-hue-2:not([disabled]).md-primary .md-checked .md-off,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-checked .md-off, md-radio-button.md-hue-2:not([disabled]) .md-primary .md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-off, md-radio-group.md-hue-2:not([disabled]).md-primary.md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-primary.md-checked .md-off, md-radio-group.md-hue-2:not([disabled]) .md-primary.md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-primary .md-checked .md-off, md-radio-group.md-hue-2:not([disabled]).md-primary .md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-checked .md-off, md-radio-group.md-hue-2:not([disabled]) .md-primary .md-checked .md-off{border-color:rgba(25,118,210,0.87)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]) .md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]) .md-primary .md-checked .md-ink-ripple{color:rgba(25,118,210,0.87)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-primary .md-container .md-ripple, md-radio-button.md-hue-2:not([disabled]).md-primary .md-container .md-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-container .md-ripple, md-radio-button.md-hue-2:not([disabled]) .md-primary .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-primary .md-container .md-ripple, md-radio-group.md-hue-2:not([disabled]).md-primary .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-primary .md-container .md-ripple, md-radio-group.md-hue-2:not([disabled]) .md-primary .md-container .md-ripple{color:rgb(30,136,229)}md-radio-button.md-default-theme.md-hue-2[disabled], md-radio-button.md-hue-2[disabled],md-radio-group.md-default-theme.md-hue-2[disabled], md-radio-group.md-hue-2[disabled]{color:rgba(0,0,0,0.38)}md-radio-button.md-default-theme.md-hue-2[disabled] .md-container .md-off, md-radio-button.md-hue-2[disabled] .md-container .md-off,md-radio-button.md-default-theme.md-hue-2[disabled] .md-container .md-on, md-radio-button.md-hue-2[disabled] .md-container .md-on,md-radio-group.md-default-theme.md-hue-2[disabled] .md-container .md-off, md-radio-group.md-hue-2[disabled] .md-container .md-off,md-radio-group.md-default-theme.md-hue-2[disabled] .md-container .md-on, md-radio-group.md-hue-2[disabled] .md-container .md-on{border-color:rgba(0,0,0,0.38)}md-radio-group.md-default-theme.md-hue-2 .md-checked:not([disabled]).md-primary .md-ink-ripple, md-radio-group.md-hue-2 .md-checked:not([disabled]).md-primary .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2.md-primary .md-checked:not([disabled]) .md-ink-ripple, md-radio-group.md-hue-2.md-primary .md-checked:not([disabled]) .md-ink-ripple{color:rgba(25,118,210,0.26)}md-radio-group.md-default-theme.md-hue-2.md-focused:not(:empty) .md-checked.md-primary .md-container:before, md-radio-group.md-hue-2.md-focused:not(:empty) .md-checked.md-primary .md-container:before,md-radio-group.md-default-theme.md-hue-2.md-focused:not(:empty).md-primary .md-checked .md-container:before, md-radio-group.md-hue-2.md-focused:not(:empty).md-primary .md-checked .md-container:before{background-color:rgba(25,118,210,0.26)}md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-default-theme.md-hue-2 .md-select-value span:first-child:after, md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-hue-2 .md-select-value span:first-child:after{color:rgba(0,0,0,0.38)}md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme.md-hue-2 .md-select-value, md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-hue-2 .md-select-value,md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme.md-hue-2 .md-select-value.md-select-placeholder, md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-hue-2 .md-select-value.md-select-placeholder{color:rgb(25,118,210)}md-input-container.md-input-invalid md-select.md-default-theme.md-hue-2.md-no-underline .md-select-value, md-input-container.md-input-invalid md-select.md-hue-2.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-2 .md-select-value, md-select.md-hue-2 .md-select-value{border-bottom-color:rgba(0,0,0,0.12)}md-select.md-default-theme.md-hue-2 .md-select-value.md-select-placeholder, md-select.md-hue-2 .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme.md-hue-2.md-no-underline .md-select-value, md-select.md-hue-2.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-2.ng-invalid.ng-touched.md-no-underline .md-select-value, md-select.md-hue-2.ng-invalid.ng-touched.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-2:not([disabled]):focus .md-select-value, md-select.md-hue-2:not([disabled]):focus .md-select-value{border-bottom-color:rgb(25,118,210);color:rgba(0,0,0,0.87)}md-select.md-default-theme.md-hue-2:not([disabled]):focus .md-select-value.md-select-placeholder, md-select.md-hue-2:not([disabled]):focus .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.87)}md-select.md-default-theme.md-hue-2:not([disabled]):focus.md-no-underline .md-select-value, md-select.md-hue-2:not([disabled]):focus.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-2[disabled] .md-select-icon, md-select.md-hue-2[disabled] .md-select-icon,md-select.md-default-theme.md-hue-2[disabled] .md-select-value, md-select.md-hue-2[disabled] .md-select-value,md-select.md-default-theme.md-hue-2[disabled] .md-select-value.md-select-placeholder, md-select.md-hue-2[disabled] .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme.md-hue-2 .md-select-icon, md-select.md-hue-2 .md-select-icon{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme.md-hue-2 md-content md-optgroup, md-select-menu.md-hue-2 md-content md-optgroup{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme.md-hue-2 md-content md-option, md-select-menu.md-hue-2 md-content md-option{color:rgba(0,0,0,0.87)}md-select-menu.md-default-theme.md-hue-2 md-content md-option[disabled] .md-text, md-select-menu.md-hue-2 md-content md-option[disabled] .md-text{color:rgba(0,0,0,0.38)}md-select-menu.md-default-theme.md-hue-2 md-content md-option[selected], md-select-menu.md-hue-2 md-content md-option[selected]{color:rgb(33,150,243)}md-select-menu.md-default-theme.md-hue-2 md-content md-option[selected]:focus, md-select-menu.md-hue-2 md-content md-option[selected]:focus{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme.md-hue-2 .md-ripple, .md-checkbox-enabled.md-hue-2 .md-ripple{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme.md-hue-2 .md-ink-ripple, .md-checkbox-enabled.md-hue-2 .md-ink-ripple{color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme.md-hue-2[selected] .md-ink-ripple, .md-checkbox-enabled.md-hue-2[selected] .md-ink-ripple{color:rgba(25,118,210,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-2:not(.md-checked) .md-icon, .md-checkbox-enabled.md-hue-2:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme.md-hue-2[selected] .md-icon, .md-checkbox-enabled.md-hue-2[selected] .md-icon{background-color:rgba(25,118,210,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-2[selected].md-focused .md-container:before, .md-checkbox-enabled.md-hue-2[selected].md-focused .md-container:before{background-color:rgba(25,118,210,0.26)}.md-checkbox-enabled.md-default-theme.md-hue-2[selected] .md-icon:after, .md-checkbox-enabled.md-hue-2[selected] .md-icon:after{border-color:rgba(255,255,255,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-2 .md-indeterminate[disabled] .md-container, .md-checkbox-enabled.md-hue-2 .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}.md-checkbox-enabled.md-default-theme.md-hue-2 md-option .md-text, .md-checkbox-enabled.md-hue-2 md-option .md-text{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-2.md-primary .md-focus-ring, md-slider.md-hue-2.md-primary .md-focus-ring{background-color:rgba(144,202,249,0.38)}md-slider.md-default-theme.md-hue-2.md-primary .md-track.md-track-fill, md-slider.md-hue-2.md-primary .md-track.md-track-fill{background-color:rgb(25,118,210)}md-slider.md-default-theme.md-hue-2.md-primary .md-thumb:after, md-slider.md-hue-2.md-primary .md-thumb:after{border-color:rgb(25,118,210);background-color:rgb(25,118,210)}md-slider.md-default-theme.md-hue-2.md-primary .md-sign, md-slider.md-hue-2.md-primary .md-sign{background-color:rgb(25,118,210)}md-slider.md-default-theme.md-hue-2.md-primary .md-sign:after, md-slider.md-hue-2.md-primary .md-sign:after{border-top-color:rgb(25,118,210)}md-slider.md-default-theme.md-hue-2.md-primary[md-vertical] .md-sign:after, md-slider.md-hue-2.md-primary[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(25,118,210)}md-slider.md-default-theme.md-hue-2.md-primary .md-thumb-text, md-slider.md-hue-2.md-primary .md-thumb-text{color:rgba(255,255,255,0.87)}md-slider.md-default-theme.md-hue-2[disabled] .md-thumb:after, md-slider.md-hue-2[disabled] .md-thumb:after{border-color:transparent}md-slider-container[disabled]>:first-child:not(md-slider),md-slider-container[disabled]>:last-child:not(md-slider){color:rgba(0,0,0,0.38)}.md-subheader.md-default-theme.md-hue-2.md-primary, .md-subheader.md-hue-2.md-primary{color:rgb(25,118,210)}md-switch.md-default-theme.md-hue-2.md-checked.md-primary .md-ink-ripple, md-switch.md-hue-2.md-checked.md-primary .md-ink-ripple{color:rgb(25,118,210)}md-switch.md-default-theme.md-hue-2.md-checked.md-primary .md-thumb, md-switch.md-hue-2.md-checked.md-primary .md-thumb{background-color:rgb(25,118,210)}md-switch.md-default-theme.md-hue-2.md-checked.md-primary .md-bar, md-switch.md-hue-2.md-checked.md-primary .md-bar{background-color:rgba(25,118,210,0.5)}md-switch.md-default-theme.md-hue-2.md-checked.md-primary.md-focused .md-thumb:before, md-switch.md-hue-2.md-checked.md-primary.md-focused .md-thumb:before{background-color:rgba(25,118,210,0.26)}md-tabs.md-default-theme.md-hue-2 .md-paginator md-icon, md-tabs.md-hue-2 .md-paginator md-icon{color:rgb(25,118,210)}md-tabs.md-default-theme.md-hue-2 .md-tab, md-tabs.md-hue-2 .md-tab{color:rgba(0,0,0,0.54)}md-tabs.md-default-theme.md-hue-2 .md-tab[disabled], md-tabs.md-hue-2 .md-tab[disabled],md-tabs.md-default-theme.md-hue-2 .md-tab[disabled] md-icon, md-tabs.md-hue-2 .md-tab[disabled] md-icon{color:rgba(0,0,0,0.38)}md-tabs.md-default-theme.md-hue-2 .md-tab.md-active, md-tabs.md-hue-2 .md-tab.md-active,md-tabs.md-default-theme.md-hue-2 .md-tab.md-active md-icon, md-tabs.md-hue-2 .md-tab.md-active md-icon,md-tabs.md-default-theme.md-hue-2 .md-tab.md-focused, md-tabs.md-hue-2 .md-tab.md-focused,md-tabs.md-default-theme.md-hue-2 .md-tab.md-focused md-icon, md-tabs.md-hue-2 .md-tab.md-focused md-icon{color:rgb(25,118,210)}md-tabs.md-default-theme.md-hue-2 .md-tab.md-focused, md-tabs.md-hue-2 .md-tab.md-focused{background:rgba(25,118,210,0.1)}md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper, md-tabs.md-hue-2.md-primary>md-tabs-wrapper{background-color:rgb(25,118,210)}md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(255,255,255,0.87)}md-tabs.md-default-theme.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-2.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper, md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper{background-color:rgb(25,118,210)}md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(255,255,255,0.87)}md-toolbar>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toast.md-default-theme.md-hue-2 .md-toast-content .md-button.md-highlight.md-primary, md-toast.md-hue-2 .md-toast-content .md-button.md-highlight.md-primary{color:rgb(25,118,210)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar), md-toolbar.md-hue-2:not(.md-menu-toolbar){background-color:rgb(25,118,210);color:rgba(255,255,255,0.87)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar) md-icon, md-toolbar.md-hue-2:not(.md-menu-toolbar) md-icon{color:rgba(255,255,255,0.87);fill:rgba(255,255,255,0.87)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar) .md-button[disabled] md-icon, md-toolbar.md-hue-2:not(.md-menu-toolbar) .md-button[disabled] md-icon{color:rgba(255,255,255,0.26);fill:rgba(255,255,255,0.26)} -md-autocomplete.md-default-theme.md-hue-3 input, md-autocomplete.md-hue-3 input{color:rgba(0,0,0,0.87)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-3 li, .md-autocomplete-suggestions-container.md-hue-3 li{color:rgba(0,0,0,0.87)}md-bottom-sheet.md-default-theme.md-hue-3.md-list md-list-item, md-bottom-sheet.md-hue-3.md-list md-list-item{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3.md-primary, .md-button.md-hue-3.md-primary{color:rgb(68,138,255)}.md-button.md-default-theme.md-hue-3.md-primary.md-fab, .md-button.md-hue-3.md-primary.md-fab,.md-button.md-default-theme.md-hue-3.md-primary.md-raised, .md-button.md-hue-3.md-primary.md-raised{color:rgba(0,0,0,0.87);background-color:rgb(68,138,255)}.md-button.md-default-theme.md-hue-3.md-primary.md-fab:not([disabled]) md-icon, .md-button.md-hue-3.md-primary.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-3.md-primary.md-raised:not([disabled]) md-icon, .md-button.md-hue-3.md-primary.md-raised:not([disabled]) md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3.md-primary.md-fab:not([disabled]).md-focused, .md-button.md-hue-3.md-primary.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3.md-primary.md-fab:not([disabled]):hover, .md-button.md-hue-3.md-primary.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-3.md-primary.md-raised:not([disabled]).md-focused, .md-button.md-hue-3.md-primary.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3.md-primary.md-raised:not([disabled]):hover, .md-button.md-hue-3.md-primary.md-raised:not([disabled]):hover{background-color:rgb(30,136,229)}.md-button.md-default-theme.md-hue-3.md-primary:not([disabled]) md-icon, .md-button.md-hue-3.md-primary:not([disabled]) md-icon{color:rgb(68,138,255)}._md a.md-default-theme.md-hue-3:not(.md-button).md-primary, ._md a.md-hue-3:not(.md-button).md-primary{color:rgb(68,138,255)}._md a.md-default-theme.md-hue-3:not(.md-button).md-primary:hover, ._md a.md-hue-3:not(.md-button).md-primary:hover{color:rgb(25,118,210)}md-card.md-default-theme.md-hue-3 .md-card-image, md-card.md-hue-3 .md-card-image{border-radius:2px 2px 0 0}md-card.md-default-theme.md-hue-3 md-card-header md-card-header-text .md-subhead, md-card.md-hue-3 md-card-header md-card-header-text .md-subhead,md-card.md-default-theme.md-hue-3 md-card-title md-card-title-text:not(:only-child) .md-subhead, md-card.md-hue-3 md-card-title md-card-title-text:not(:only-child) .md-subhead{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-3 .md-ink-ripple, md-checkbox.md-hue-3 .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-3:not(.md-checked) .md-icon, md-checkbox.md-hue-3:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary .md-ripple, md-checkbox.md-hue-3:not([disabled]).md-primary .md-ripple{color:rgb(30,136,229)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-ripple, md-checkbox.md-hue-3:not([disabled]).md-primary.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary .md-ink-ripple, md-checkbox.md-hue-3:not([disabled]).md-primary .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-ink-ripple, md-checkbox.md-hue-3:not([disabled]).md-primary.md-checked .md-ink-ripple{color:rgba(68,138,255,0.87)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary:not(.md-checked) .md-icon, md-checkbox.md-hue-3:not([disabled]).md-primary:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-icon, md-checkbox.md-hue-3:not([disabled]).md-primary.md-checked .md-icon{background-color:rgba(68,138,255,0.87)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked.md-focused .md-container:before, md-checkbox.md-hue-3:not([disabled]).md-primary.md-checked.md-focused .md-container:before{background-color:rgba(68,138,255,0.26)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-icon:after, md-checkbox.md-hue-3:not([disabled]).md-primary.md-checked .md-icon:after{border-color:rgba(0,0,0,0.87)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-primary .md-indeterminate[disabled] .md-container, md-checkbox.md-hue-3:not([disabled]).md-primary .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-3[disabled]:not(.md-checked) .md-icon, md-checkbox.md-hue-3[disabled]:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-3[disabled] .md-icon:after, md-checkbox.md-hue-3[disabled] .md-icon:after{border-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-3[disabled] .md-label, md-checkbox.md-hue-3[disabled] .md-label{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-3 .md-chips, md-chips.md-hue-3 .md-chips{box-shadow:0 1px rgba(0,0,0,0.12)}md-chips.md-default-theme.md-hue-3 .md-chips.md-focused, md-chips.md-hue-3 .md-chips.md-focused{box-shadow:0 2px rgb(68,138,255)}md-chips.md-default-theme.md-hue-3 .md-chips .md-chip-input-container input, md-chips.md-hue-3 .md-chips .md-chip-input-container input{color:rgba(0,0,0,0.87)}md-chips.md-default-theme.md-hue-3 .md-chips .md-chip-input-container input:-moz-placeholder, md-chips.md-hue-3 .md-chips .md-chip-input-container input:-moz-placeholder,md-chips.md-default-theme.md-hue-3 .md-chips .md-chip-input-container input::-moz-placeholder, md-chips.md-hue-3 .md-chips .md-chip-input-container input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-3 .md-chips .md-chip-input-container input:-ms-input-placeholder, md-chips.md-hue-3 .md-chips .md-chip-input-container input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-3 .md-chips .md-chip-input-container input::-webkit-input-placeholder, md-chips.md-hue-3 .md-chips .md-chip-input-container input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-chips.md-default-theme.md-hue-3 md-chip.md-focused, md-chips.md-hue-3 md-chip.md-focused{background:rgb(68,138,255);color:rgba(0,0,0,0.87)}md-chips.md-default-theme.md-hue-3 md-chip.md-focused md-icon, md-chips.md-hue-3 md-chip.md-focused md-icon{color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-3 .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator, .md-hue-3 .md-calendar-date.md-calendar-date-today .md-calendar-date-selection-indicator{border:1px solid rgb(33,150,243)}.md-default-theme.md-hue-3 .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled, .md-hue-3 .md-calendar-date.md-calendar-date-today.md-calendar-date-disabled{color:rgba(33,150,243,0.6)}.md-default-theme.md-hue-3 .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-hue-3 .md-calendar-date.md-calendar-selected-date .md-calendar-date-selection-indicator,.md-default-theme.md-hue-3 .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator, .md-hue-3 .md-calendar-date.md-focus.md-calendar-selected-date .md-calendar-date-selection-indicator{background:rgb(33,150,243);color:rgba(0,0,0,0.87);border-color:transparent}.md-default-theme.md-hue-3 .md-calendar-date-disabled, .md-hue-3 .md-calendar-date-disabled,.md-default-theme.md-hue-3 .md-calendar-month-label-disabled, .md-hue-3 .md-calendar-month-label-disabled{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-3 .md-calendar-month-label md-icon, .md-hue-3 .md-calendar-month-label md-icon,.md-default-theme.md-hue-3 .md-datepicker-input, .md-hue-3 .md-datepicker-input{color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-3 .md-datepicker-input:-moz-placeholder, .md-hue-3 .md-datepicker-input:-moz-placeholder,.md-default-theme.md-hue-3 .md-datepicker-input::-moz-placeholder, .md-hue-3 .md-datepicker-input::-moz-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-3 .md-datepicker-input:-ms-input-placeholder, .md-hue-3 .md-datepicker-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-3 .md-datepicker-input::-webkit-input-placeholder, .md-hue-3 .md-datepicker-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}.md-default-theme.md-hue-3 .md-datepicker-input-container, .md-hue-3 .md-datepicker-input-container{border-bottom-color:rgba(0,0,0,0.12)}.md-default-theme.md-hue-3 .md-datepicker-input-container.md-datepicker-focused, .md-hue-3 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(68,138,255)}.md-default-theme.md-hue-3 .md-datepicker-triangle-button .md-datepicker-expand-triangle, .md-hue-3 .md-datepicker-triangle-button .md-datepicker-expand-triangle{border-top-color:rgba(0,0,0,0.54)}.md-default-theme.md-hue-3 .md-datepicker-open .md-datepicker-calendar-icon, .md-hue-3 .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(68,138,255)}md-dialog.md-default-theme.md-hue-3.md-content-overflow .md-actions, md-dialog.md-hue-3.md-content-overflow .md-actions,md-dialog.md-default-theme.md-hue-3.md-content-overflow md-dialog-actions, md-dialog.md-hue-3.md-content-overflow md-dialog-actions,md-divider.md-default-theme.md-hue-3, md-divider.md-hue-3{border-top-color:rgba(0,0,0,0.12)}.layout-gt-lg-row>md-divider.md-default-theme.md-hue-3, .layout-gt-lg-row>md-divider.md-hue-3,.layout-gt-md-row>md-divider.md-default-theme.md-hue-3, .layout-gt-md-row>md-divider.md-hue-3,.layout-gt-sm-row>md-divider.md-default-theme.md-hue-3, .layout-gt-sm-row>md-divider.md-hue-3,.layout-gt-xs-row>md-divider.md-default-theme.md-hue-3, .layout-gt-xs-row>md-divider.md-hue-3,.layout-lg-row>md-divider.md-default-theme.md-hue-3, .layout-lg-row>md-divider.md-hue-3,.layout-md-row>md-divider.md-default-theme.md-hue-3, .layout-md-row>md-divider.md-hue-3,.layout-row>md-divider.md-default-theme.md-hue-3, .layout-row>md-divider.md-hue-3,.layout-sm-row>md-divider.md-default-theme.md-hue-3, .layout-sm-row>md-divider.md-hue-3,.layout-xl-row>md-divider.md-default-theme.md-hue-3, .layout-xl-row>md-divider.md-hue-3,.layout-xs-row>md-divider.md-default-theme.md-hue-3, .layout-xs-row>md-divider.md-hue-3{border-right-color:rgba(0,0,0,0.12)}md-icon.md-default-theme.md-hue-3, md-icon.md-hue-3{color:rgba(0,0,0,0.54)}md-icon.md-default-theme.md-hue-3.md-primary, md-icon.md-hue-3.md-primary{color:rgb(68,138,255)}md-input-container.md-default-theme.md-hue-3 .md-input, md-input-container.md-hue-3 .md-input{color:rgba(0,0,0,0.87);border-color:rgba(0,0,0,0.12)}md-input-container.md-default-theme.md-hue-3 .md-input:-moz-placeholder, md-input-container.md-hue-3 .md-input:-moz-placeholder,md-input-container.md-default-theme.md-hue-3 .md-input::-moz-placeholder, md-input-container.md-hue-3 .md-input::-moz-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-3 .md-input:-ms-input-placeholder, md-input-container.md-hue-3 .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-3 .md-input::-webkit-input-placeholder, md-input-container.md-hue-3 .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-3>md-icon, md-input-container.md-hue-3>md-icon{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme.md-hue-3 .md-placeholder, md-input-container.md-hue-3 .md-placeholder,md-input-container.md-default-theme.md-hue-3 label, md-input-container.md-hue-3 label{color:rgba(0,0,0,0.38)}md-input-container.md-default-theme.md-hue-3:not(.md-input-focused):not(.md-input-invalid) label.md-required:after, md-input-container.md-hue-3:not(.md-input-focused):not(.md-input-invalid) label.md-required:after{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-3 .md-input-message-animation .md-char-counter, md-input-container.md-hue-3 .md-input-message-animation .md-char-counter,md-input-container.md-default-theme.md-hue-3 .md-input-messages-animation .md-char-counter, md-input-container.md-hue-3 .md-input-messages-animation .md-char-counter{color:rgba(0,0,0,0.87)}md-input-container.md-default-theme.md-hue-3.md-input-focused .md-input:-moz-placeholder, md-input-container.md-hue-3.md-input-focused .md-input:-moz-placeholder,md-input-container.md-default-theme.md-hue-3.md-input-focused .md-input::-moz-placeholder, md-input-container.md-hue-3.md-input-focused .md-input::-moz-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-3.md-input-focused .md-input:-ms-input-placeholder, md-input-container.md-hue-3.md-input-focused .md-input:-ms-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-3.md-input-focused .md-input::-webkit-input-placeholder, md-input-container.md-hue-3.md-input-focused .md-input::-webkit-input-placeholder{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-has-value label, md-input-container.md-hue-3:not(.md-input-invalid).md-input-has-value label{color:rgba(0,0,0,0.54)}md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused .md-input, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused .md-input,md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-resized .md-input, md-input-container.md-hue-3:not(.md-input-invalid).md-input-resized .md-input{border-color:rgb(68,138,255)}md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused label, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused label,md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused md-icon, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused md-icon{color:rgb(68,138,255)}md-list.md-default-theme.md-hue-3 md-list-item.md-2-line .md-list-item-text h3, md-list.md-hue-3 md-list-item.md-2-line .md-list-item-text h3,md-list.md-default-theme.md-hue-3 md-list-item.md-2-line .md-list-item-text h4, md-list.md-hue-3 md-list-item.md-2-line .md-list-item-text h4,md-list.md-default-theme.md-hue-3 md-list-item.md-3-line .md-list-item-text h3, md-list.md-hue-3 md-list-item.md-3-line .md-list-item-text h3,md-list.md-default-theme.md-hue-3 md-list-item.md-3-line .md-list-item-text h4, md-list.md-hue-3 md-list-item.md-3-line .md-list-item-text h4{color:rgba(0,0,0,0.87)}md-list.md-default-theme.md-hue-3 md-list-item.md-2-line .md-list-item-text p, md-list.md-hue-3 md-list-item.md-2-line .md-list-item-text p,md-list.md-default-theme.md-hue-3 md-list-item.md-3-line .md-list-item-text p, md-list.md-hue-3 md-list-item.md-3-line .md-list-item-text p{color:rgba(0,0,0,0.54)}md-list.md-default-theme.md-hue-3 md-list-item>md-icon, md-list.md-hue-3 md-list-item>md-icon{color:rgba(0,0,0,0.54)}md-list.md-default-theme.md-hue-3 md-list-item>md-icon.md-highlight, md-list.md-hue-3 md-list-item>md-icon.md-highlight{color:rgb(68,138,255)}md-menu-content.md-default-theme.md-hue-3 md-menu-item, md-menu-content.md-hue-3 md-menu-item{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme.md-hue-3 md-menu-item md-icon, md-menu-content.md-hue-3 md-menu-item md-icon{color:rgba(0,0,0,0.54)}md-menu-content.md-default-theme.md-hue-3 md-menu-item .md-button[disabled], md-menu-content.md-hue-3 md-menu-item .md-button[disabled],md-menu-content.md-default-theme.md-hue-3 md-menu-item .md-button[disabled] md-icon, md-menu-content.md-hue-3 md-menu-item .md-button[disabled] md-icon{color:rgba(0,0,0,0.38)}md-menu-bar.md-default-theme.md-hue-3>button.md-button, md-menu-bar.md-hue-3>button.md-button{color:rgba(0,0,0,0.87);border-radius:2px}md-menu-bar.md-default-theme.md-hue-3 md-menu>button, md-menu-bar.md-hue-3 md-menu>button{color:rgba(0,0,0,0.87)}md-menu-content.md-default-theme.md-hue-3 .md-menu>.md-button:after, md-menu-content.md-hue-3 .md-menu>.md-button:after{color:rgba(0,0,0,0.54)}md-toolbar.md-default-theme.md-hue-3.md-menu-toolbar md-toolbar-filler, md-toolbar.md-hue-3.md-menu-toolbar md-toolbar-filler{background-color:rgb(68,138,255);color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-3.md-menu-toolbar md-toolbar-filler md-icon, md-toolbar.md-hue-3.md-menu-toolbar md-toolbar-filler md-icon{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-3 .md-button._md-nav-button.md-unselected, md-nav-bar.md-hue-3 .md-button._md-nav-button.md-unselected{color:rgba(0,0,0,0.54)}md-nav-bar.md-default-theme.md-hue-3 .md-button._md-nav-button[disabled], md-nav-bar.md-hue-3 .md-button._md-nav-button[disabled]{color:rgba(0,0,0,0.38)}md-nav-bar.md-default-theme.md-hue-3.md-primary>.md-nav-bar, md-nav-bar.md-hue-3.md-primary>.md-nav-bar{background-color:rgb(68,138,255)}md-nav-bar.md-default-theme.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-nav-bar.md-default-theme.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-3.md-primary>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-toolbar>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar, md-toolbar>md-nav-bar.md-hue-3>.md-nav-bar{background-color:rgb(68,138,255)}md-toolbar>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button, md-toolbar>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button{color:rgb(187,222,251)}md-toolbar>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-toolbar>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-progress-circular.md-default-theme.md-hue-3 path, md-progress-circular.md-hue-3 path{stroke:rgb(68,138,255)}md-progress-linear.md-default-theme.md-hue-3 .md-container, md-progress-linear.md-hue-3 .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-3 .md-bar, md-progress-linear.md-hue-3 .md-bar{background-color:rgb(68,138,255)}md-progress-linear.md-default-theme.md-hue-3[md-mode=buffer].md-primary .md-bar1, md-progress-linear.md-hue-3[md-mode=buffer].md-primary .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-3[md-mode=buffer].md-primary .md-dashed:before, md-progress-linear.md-hue-3[md-mode=buffer].md-primary .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-3 .md-off, md-radio-button.md-hue-3 .md-off{border-color:rgba(0,0,0,0.54)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-primary .md-on, md-radio-button.md-hue-3:not([disabled]).md-primary .md-on,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-on, md-radio-button.md-hue-3:not([disabled]) .md-primary .md-on,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-primary .md-on, md-radio-group.md-hue-3:not([disabled]).md-primary .md-on,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-on, md-radio-group.md-hue-3:not([disabled]) .md-primary .md-on{background-color:rgba(68,138,255,0.87)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-off, md-radio-button.md-hue-3:not([disabled]).md-primary.md-checked .md-off,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-primary.md-checked .md-off, md-radio-button.md-hue-3:not([disabled]) .md-primary.md-checked .md-off,md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-primary .md-checked .md-off, md-radio-button.md-hue-3:not([disabled]).md-primary .md-checked .md-off,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-checked .md-off, md-radio-button.md-hue-3:not([disabled]) .md-primary .md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-off, md-radio-group.md-hue-3:not([disabled]).md-primary.md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-primary.md-checked .md-off, md-radio-group.md-hue-3:not([disabled]) .md-primary.md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-primary .md-checked .md-off, md-radio-group.md-hue-3:not([disabled]).md-primary .md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-checked .md-off, md-radio-group.md-hue-3:not([disabled]) .md-primary .md-checked .md-off{border-color:rgba(68,138,255,0.87)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]) .md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-primary.md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]).md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-primary.md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]) .md-primary.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-primary .md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]).md-primary .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]) .md-primary .md-checked .md-ink-ripple{color:rgba(68,138,255,0.87)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-primary .md-container .md-ripple, md-radio-button.md-hue-3:not([disabled]).md-primary .md-container .md-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-container .md-ripple, md-radio-button.md-hue-3:not([disabled]) .md-primary .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-primary .md-container .md-ripple, md-radio-group.md-hue-3:not([disabled]).md-primary .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-primary .md-container .md-ripple, md-radio-group.md-hue-3:not([disabled]) .md-primary .md-container .md-ripple{color:rgb(30,136,229)}md-radio-button.md-default-theme.md-hue-3[disabled], md-radio-button.md-hue-3[disabled],md-radio-group.md-default-theme.md-hue-3[disabled], md-radio-group.md-hue-3[disabled]{color:rgba(0,0,0,0.38)}md-radio-button.md-default-theme.md-hue-3[disabled] .md-container .md-off, md-radio-button.md-hue-3[disabled] .md-container .md-off,md-radio-button.md-default-theme.md-hue-3[disabled] .md-container .md-on, md-radio-button.md-hue-3[disabled] .md-container .md-on,md-radio-group.md-default-theme.md-hue-3[disabled] .md-container .md-off, md-radio-group.md-hue-3[disabled] .md-container .md-off,md-radio-group.md-default-theme.md-hue-3[disabled] .md-container .md-on, md-radio-group.md-hue-3[disabled] .md-container .md-on{border-color:rgba(0,0,0,0.38)}md-radio-group.md-default-theme.md-hue-3 .md-checked:not([disabled]).md-primary .md-ink-ripple, md-radio-group.md-hue-3 .md-checked:not([disabled]).md-primary .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3.md-primary .md-checked:not([disabled]) .md-ink-ripple, md-radio-group.md-hue-3.md-primary .md-checked:not([disabled]) .md-ink-ripple{color:rgba(68,138,255,0.26)}md-radio-group.md-default-theme.md-hue-3.md-focused:not(:empty) .md-checked.md-primary .md-container:before, md-radio-group.md-hue-3.md-focused:not(:empty) .md-checked.md-primary .md-container:before,md-radio-group.md-default-theme.md-hue-3.md-focused:not(:empty).md-primary .md-checked .md-container:before, md-radio-group.md-hue-3.md-focused:not(:empty).md-primary .md-checked .md-container:before{background-color:rgba(68,138,255,0.26)}md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-default-theme.md-hue-3 .md-select-value span:first-child:after, md-input-container:not(.md-input-focused):not(.md-input-invalid) md-select.md-hue-3 .md-select-value span:first-child:after{color:rgba(0,0,0,0.38)}md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme.md-hue-3 .md-select-value, md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-hue-3 .md-select-value,md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-default-theme.md-hue-3 .md-select-value.md-select-placeholder, md-input-container.md-input-focused:not(.md-input-has-value) md-select.md-hue-3 .md-select-value.md-select-placeholder{color:rgb(68,138,255)}md-input-container.md-input-invalid md-select.md-default-theme.md-hue-3.md-no-underline .md-select-value, md-input-container.md-input-invalid md-select.md-hue-3.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-3 .md-select-value, md-select.md-hue-3 .md-select-value{border-bottom-color:rgba(0,0,0,0.12)}md-select.md-default-theme.md-hue-3 .md-select-value.md-select-placeholder, md-select.md-hue-3 .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme.md-hue-3.md-no-underline .md-select-value, md-select.md-hue-3.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-3.ng-invalid.ng-touched.md-no-underline .md-select-value, md-select.md-hue-3.ng-invalid.ng-touched.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-3:not([disabled]):focus .md-select-value, md-select.md-hue-3:not([disabled]):focus .md-select-value{border-bottom-color:rgb(68,138,255);color:rgba(0,0,0,0.87)}md-select.md-default-theme.md-hue-3:not([disabled]):focus .md-select-value.md-select-placeholder, md-select.md-hue-3:not([disabled]):focus .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.87)}md-select.md-default-theme.md-hue-3:not([disabled]):focus.md-no-underline .md-select-value, md-select.md-hue-3:not([disabled]):focus.md-no-underline .md-select-value{border-bottom-color:transparent!important}md-select.md-default-theme.md-hue-3[disabled] .md-select-icon, md-select.md-hue-3[disabled] .md-select-icon,md-select.md-default-theme.md-hue-3[disabled] .md-select-value, md-select.md-hue-3[disabled] .md-select-value,md-select.md-default-theme.md-hue-3[disabled] .md-select-value.md-select-placeholder, md-select.md-hue-3[disabled] .md-select-value.md-select-placeholder{color:rgba(0,0,0,0.38)}md-select.md-default-theme.md-hue-3 .md-select-icon, md-select.md-hue-3 .md-select-icon{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme.md-hue-3 md-content md-optgroup, md-select-menu.md-hue-3 md-content md-optgroup{color:rgba(0,0,0,0.54)}md-select-menu.md-default-theme.md-hue-3 md-content md-option, md-select-menu.md-hue-3 md-content md-option{color:rgba(0,0,0,0.87)}md-select-menu.md-default-theme.md-hue-3 md-content md-option[disabled] .md-text, md-select-menu.md-hue-3 md-content md-option[disabled] .md-text{color:rgba(0,0,0,0.38)}md-select-menu.md-default-theme.md-hue-3 md-content md-option[selected], md-select-menu.md-hue-3 md-content md-option[selected]{color:rgb(33,150,243)}md-select-menu.md-default-theme.md-hue-3 md-content md-option[selected]:focus, md-select-menu.md-hue-3 md-content md-option[selected]:focus{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme.md-hue-3 .md-ripple, .md-checkbox-enabled.md-hue-3 .md-ripple{color:rgb(30,136,229)}.md-checkbox-enabled.md-default-theme.md-hue-3 .md-ink-ripple, .md-checkbox-enabled.md-hue-3 .md-ink-ripple{color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme.md-hue-3[selected] .md-ink-ripple, .md-checkbox-enabled.md-hue-3[selected] .md-ink-ripple{color:rgba(68,138,255,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-3:not(.md-checked) .md-icon, .md-checkbox-enabled.md-hue-3:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}.md-checkbox-enabled.md-default-theme.md-hue-3[selected] .md-icon, .md-checkbox-enabled.md-hue-3[selected] .md-icon{background-color:rgba(68,138,255,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-3[selected].md-focused .md-container:before, .md-checkbox-enabled.md-hue-3[selected].md-focused .md-container:before{background-color:rgba(68,138,255,0.26)}.md-checkbox-enabled.md-default-theme.md-hue-3[selected] .md-icon:after, .md-checkbox-enabled.md-hue-3[selected] .md-icon:after{border-color:rgba(0,0,0,0.87)}.md-checkbox-enabled.md-default-theme.md-hue-3 .md-indeterminate[disabled] .md-container, .md-checkbox-enabled.md-hue-3 .md-indeterminate[disabled] .md-container{color:rgba(0,0,0,0.38)}.md-checkbox-enabled.md-default-theme.md-hue-3 md-option .md-text, .md-checkbox-enabled.md-hue-3 md-option .md-text{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-3.md-primary .md-focus-ring, md-slider.md-hue-3.md-primary .md-focus-ring{background-color:rgba(144,202,249,0.38)}md-slider.md-default-theme.md-hue-3.md-primary .md-track.md-track-fill, md-slider.md-hue-3.md-primary .md-track.md-track-fill{background-color:rgb(68,138,255)}md-slider.md-default-theme.md-hue-3.md-primary .md-thumb:after, md-slider.md-hue-3.md-primary .md-thumb:after{border-color:rgb(68,138,255);background-color:rgb(68,138,255)}md-slider.md-default-theme.md-hue-3.md-primary .md-sign, md-slider.md-hue-3.md-primary .md-sign{background-color:rgb(68,138,255)}md-slider.md-default-theme.md-hue-3.md-primary .md-sign:after, md-slider.md-hue-3.md-primary .md-sign:after{border-top-color:rgb(68,138,255)}md-slider.md-default-theme.md-hue-3.md-primary[md-vertical] .md-sign:after, md-slider.md-hue-3.md-primary[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(68,138,255)}md-slider.md-default-theme.md-hue-3.md-primary .md-thumb-text, md-slider.md-hue-3.md-primary .md-thumb-text{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-3[disabled] .md-thumb:after, md-slider.md-hue-3[disabled] .md-thumb:after{border-color:transparent}md-slider-container[disabled]>:first-child:not(md-slider),md-slider-container[disabled]>:last-child:not(md-slider){color:rgba(0,0,0,0.38)}.md-subheader.md-default-theme.md-hue-3.md-primary, .md-subheader.md-hue-3.md-primary{color:rgb(68,138,255)}md-switch.md-default-theme.md-hue-3.md-checked.md-primary .md-ink-ripple, md-switch.md-hue-3.md-checked.md-primary .md-ink-ripple{color:rgb(68,138,255)}md-switch.md-default-theme.md-hue-3.md-checked.md-primary .md-thumb, md-switch.md-hue-3.md-checked.md-primary .md-thumb{background-color:rgb(68,138,255)}md-switch.md-default-theme.md-hue-3.md-checked.md-primary .md-bar, md-switch.md-hue-3.md-checked.md-primary .md-bar{background-color:rgba(68,138,255,0.5)}md-switch.md-default-theme.md-hue-3.md-checked.md-primary.md-focused .md-thumb:before, md-switch.md-hue-3.md-checked.md-primary.md-focused .md-thumb:before{background-color:rgba(68,138,255,0.26)}md-tabs.md-default-theme.md-hue-3 .md-paginator md-icon, md-tabs.md-hue-3 .md-paginator md-icon{color:rgb(68,138,255)}md-tabs.md-default-theme.md-hue-3 .md-tab, md-tabs.md-hue-3 .md-tab{color:rgba(0,0,0,0.54)}md-tabs.md-default-theme.md-hue-3 .md-tab[disabled], md-tabs.md-hue-3 .md-tab[disabled],md-tabs.md-default-theme.md-hue-3 .md-tab[disabled] md-icon, md-tabs.md-hue-3 .md-tab[disabled] md-icon{color:rgba(0,0,0,0.38)}md-tabs.md-default-theme.md-hue-3 .md-tab.md-active, md-tabs.md-hue-3 .md-tab.md-active,md-tabs.md-default-theme.md-hue-3 .md-tab.md-active md-icon, md-tabs.md-hue-3 .md-tab.md-active md-icon,md-tabs.md-default-theme.md-hue-3 .md-tab.md-focused, md-tabs.md-hue-3 .md-tab.md-focused,md-tabs.md-default-theme.md-hue-3 .md-tab.md-focused md-icon, md-tabs.md-hue-3 .md-tab.md-focused md-icon{color:rgb(68,138,255)}md-tabs.md-default-theme.md-hue-3 .md-tab.md-focused, md-tabs.md-hue-3 .md-tab.md-focused{background:rgba(68,138,255,0.1)}md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper, md-tabs.md-hue-3.md-primary>md-tabs-wrapper{background-color:rgb(68,138,255)}md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-tabs.md-default-theme.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-3.md-primary>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper, md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper{background-color:rgb(68,138,255)}md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(187,222,251)}md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-toolbar>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toast.md-default-theme.md-hue-3 .md-toast-content .md-button.md-highlight.md-primary, md-toast.md-hue-3 .md-toast-content .md-button.md-highlight.md-primary{color:rgb(68,138,255)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar), md-toolbar.md-hue-3:not(.md-menu-toolbar){background-color:rgb(68,138,255);color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar) md-icon, md-toolbar.md-hue-3:not(.md-menu-toolbar) md-icon{color:rgba(0,0,0,0.87);fill:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar) .md-button[disabled] md-icon, md-toolbar.md-hue-3:not(.md-menu-toolbar) .md-button[disabled] md-icon{color:rgba(0,0,0,0.26);fill:rgba(0,0,0,0.26)} -.md-button.md-default-theme.md-fab md-icon, .md-button.md-fab md-icon{color:rgba(255,255,255,0.87)}.md-button.md-default-theme.md-fab, .md-button.md-fab{background-color:rgb(21,101,192);color:rgba(255,255,255,0.87)}.md-button.md-default-theme.md-fab:not([disabled]) .md-icon, .md-button.md-fab:not([disabled]) .md-icon{color:rgba(255,255,255,0.87)}.md-button.md-default-theme.md-fab:not([disabled]).md-focused, .md-button.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-fab:not([disabled]):hover, .md-button.md-fab:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-accent, .md-button.md-accent{color:rgb(21,101,192)}.md-button.md-default-theme.md-accent.md-fab, .md-button.md-accent.md-fab,.md-button.md-default-theme.md-accent.md-raised, .md-button.md-accent.md-raised{color:rgba(255,255,255,0.87);background-color:rgb(21,101,192)}.md-button.md-default-theme.md-accent.md-fab:not([disabled]) md-icon, .md-button.md-accent.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-accent.md-raised:not([disabled]) md-icon, .md-button.md-accent.md-raised:not([disabled]) md-icon{color:rgba(255,255,255,0.87)}.md-button.md-default-theme.md-accent.md-fab:not([disabled]).md-focused, .md-button.md-accent.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-accent.md-fab:not([disabled]):hover, .md-button.md-accent.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-accent.md-raised:not([disabled]).md-focused, .md-button.md-accent.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-accent.md-raised:not([disabled]):hover, .md-button.md-accent.md-raised:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-accent:not([disabled]) md-icon, .md-button.md-accent:not([disabled]) md-icon{color:rgb(21,101,192)}.md-button.md-default-theme.md-accent[disabled], .md-button.md-accent[disabled],.md-button.md-default-theme.md-fab[disabled], .md-button.md-fab[disabled],.md-button.md-default-theme.md-raised[disabled], .md-button.md-raised[disabled],.md-button.md-default-theme.md-warn[disabled], .md-button.md-warn[disabled],.md-button.md-default-theme[disabled], .md-button[disabled]{color:rgba(0,0,0,0.38);cursor:default}.md-button.md-default-theme.md-accent[disabled] md-icon, .md-button.md-accent[disabled] md-icon,.md-button.md-default-theme.md-fab[disabled] md-icon, .md-button.md-fab[disabled] md-icon,.md-button.md-default-theme.md-raised[disabled] md-icon, .md-button.md-raised[disabled] md-icon,.md-button.md-default-theme.md-warn[disabled] md-icon, .md-button.md-warn[disabled] md-icon,.md-button.md-default-theme[disabled] md-icon, .md-button[disabled] md-icon{color:rgba(0,0,0,0.38)}._md a.md-default-theme:not(.md-button).md-accent, ._md a:not(.md-button).md-accent{color:rgb(21,101,192)}._md a.md-default-theme:not(.md-button).md-accent:hover, ._md a:not(.md-button).md-accent:hover{color:rgb(41,98,255)}md-checkbox.md-default-theme .md-ripple, md-checkbox .md-ripple{color:rgb(41,98,255)}md-checkbox.md-default-theme.md-checked.md-focused .md-container:before, md-checkbox.md-checked.md-focused .md-container:before{background-color:rgba(21,101,192,0.26)}md-checkbox.md-default-theme.md-checked .md-ink-ripple, md-checkbox.md-checked .md-ink-ripple{color:rgba(21,101,192,0.87)}md-checkbox.md-default-theme.md-checked .md-icon, md-checkbox.md-checked .md-icon{background-color:rgba(21,101,192,0.87)}md-checkbox.md-default-theme.md-checked .md-icon:after, md-checkbox.md-checked .md-icon:after{border-color:rgba(255,255,255,0.87)}.md-accent .md-default-theme .md-datepicker-input-container.md-datepicker-focused, .md-accent .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(21,101,192)}.md-accent .md-default-theme .md-datepicker-open .md-datepicker-calendar-icon, .md-accent .md-datepicker-open .md-datepicker-calendar-icon,.md-default-theme .md-datepicker-open.md-accent .md-datepicker-calendar-icon, .md-datepicker-open.md-accent .md-datepicker-calendar-icon{color:rgb(21,101,192)}md-icon.md-default-theme.md-accent, md-icon.md-accent{color:rgb(21,101,192)}md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused.md-accent .md-input, md-input-container:not(.md-input-invalid).md-input-focused.md-accent .md-input{border-color:rgb(21,101,192)}md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused.md-accent label, md-input-container:not(.md-input-invalid).md-input-focused.md-accent label,md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused.md-accent md-icon, md-input-container:not(.md-input-invalid).md-input-focused.md-accent md-icon{color:rgb(21,101,192)}md-list.md-default-theme md-list-item>md-icon.md-highlight.md-accent, md-list md-list-item>md-icon.md-highlight.md-accent{color:rgb(21,101,192)}md-nav-bar.md-default-theme md-nav-ink-bar, md-nav-bar md-nav-ink-bar{color:rgb(21,101,192);background:rgb(21,101,192)}md-nav-bar.md-default-theme.md-accent>.md-nav-bar, md-nav-bar.md-accent>.md-nav-bar{background-color:rgb(21,101,192)}md-nav-bar.md-default-theme.md-accent>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-accent>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-nav-bar.md-default-theme.md-accent>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-accent>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(255,255,255,0.87)}md-nav-bar.md-default-theme.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-nav-bar.md-default-theme.md-accent>.md-nav-bar md-nav-ink-bar, md-nav-bar.md-accent>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-nav-bar.md-default-theme>.md-nav-bar, md-toolbar.md-accent>md-nav-bar>.md-nav-bar{background-color:rgb(21,101,192)}md-toolbar.md-accent>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-accent>md-nav-bar>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-toolbar.md-accent>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-accent>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-accent>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(255,255,255,0.87)}md-toolbar.md-accent>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-toolbar.md-accent>md-nav-bar.md-default-theme>.md-nav-bar md-nav-ink-bar, md-toolbar.md-accent>md-nav-bar>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-progress-circular.md-default-theme.md-accent path, md-progress-circular.md-accent path{stroke:rgb(21,101,192)}md-progress-linear.md-default-theme.md-accent .md-container, md-progress-linear.md-accent .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-accent .md-bar, md-progress-linear.md-accent .md-bar{background-color:rgb(21,101,192)}md-progress-linear.md-default-theme[md-mode=buffer].md-accent .md-bar1, md-progress-linear[md-mode=buffer].md-accent .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme[md-mode=buffer].md-accent .md-dashed:before, md-progress-linear[md-mode=buffer].md-accent .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme .md-on, md-radio-button .md-on{background-color:rgba(21,101,192,0.87)}md-radio-button.md-default-theme.md-checked .md-off, md-radio-button.md-checked .md-off{border-color:rgba(21,101,192,0.87)}md-radio-button.md-default-theme.md-checked .md-ink-ripple, md-radio-button.md-checked .md-ink-ripple{color:rgba(21,101,192,0.87)}md-radio-button.md-default-theme .md-container .md-ripple, md-radio-button .md-container .md-ripple{color:rgb(41,98,255)}md-radio-group.md-default-theme .md-checked .md-ink-ripple, md-radio-group .md-checked .md-ink-ripple{color:rgba(21,101,192,0.26)}md-radio-group.md-default-theme.md-focused:not(:empty) .md-checked .md-container:before, md-radio-group.md-focused:not(:empty) .md-checked .md-container:before{background-color:rgba(21,101,192,0.26)}md-select.md-default-theme:not([disabled]):focus.md-accent .md-select-value, md-select:not([disabled]):focus.md-accent .md-select-value{border-bottom-color:rgb(21,101,192)}md-select-menu.md-default-theme md-content md-option[selected].md-accent, md-select-menu md-content md-option[selected].md-accent{color:rgb(21,101,192)}md-select-menu.md-default-theme md-content md-option[selected].md-accent:focus, md-select-menu md-content md-option[selected].md-accent:focus{color:rgb(41,98,255)}md-slider.md-default-theme .md-focus-ring, md-slider .md-focus-ring{background-color:rgba(68,138,255,0.2)}md-slider.md-default-theme .md-track.md-track-fill, md-slider .md-track.md-track-fill{background-color:rgb(21,101,192)}md-slider.md-default-theme .md-thumb:after, md-slider .md-thumb:after{border-color:rgb(21,101,192);background-color:rgb(21,101,192)}md-slider.md-default-theme .md-sign, md-slider .md-sign{background-color:rgb(21,101,192)}md-slider.md-default-theme .md-sign:after, md-slider .md-sign:after{border-top-color:rgb(21,101,192)}md-slider.md-default-theme[md-vertical] .md-sign:after, md-slider[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(21,101,192)}md-slider.md-default-theme .md-thumb-text, md-slider .md-thumb-text{color:rgba(255,255,255,0.87)}.md-subheader.md-default-theme.md-accent, .md-subheader.md-accent{color:rgb(21,101,192)}md-switch.md-default-theme.md-checked .md-ink-ripple, md-switch.md-checked .md-ink-ripple{color:rgb(21,101,192)}md-switch.md-default-theme.md-checked .md-thumb, md-switch.md-checked .md-thumb{background-color:rgb(21,101,192)}md-switch.md-default-theme.md-checked .md-bar, md-switch.md-checked .md-bar{background-color:rgba(21,101,192,0.5)}md-switch.md-default-theme.md-checked.md-focused .md-thumb:before, md-switch.md-checked.md-focused .md-thumb:before{background-color:rgba(21,101,192,0.26)}md-tabs.md-default-theme md-ink-bar, md-tabs md-ink-bar{color:rgb(21,101,192);background:rgb(21,101,192)}md-tabs.md-default-theme .md-tab .md-ripple-container, md-tabs .md-tab .md-ripple-container{color:rgb(130,177,255)}md-tabs.md-default-theme.md-accent>md-tabs-wrapper, md-tabs.md-accent>md-tabs-wrapper{background-color:rgb(21,101,192)}md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(255,255,255,0.87)}md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-tabs.md-default-theme.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-tabs.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper, md-toolbar.md-accent>md-tabs>md-tabs-wrapper{background-color:rgb(21,101,192)}md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(255,255,255,0.87)}md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toolbar.md-accent>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-toolbar.md-accent>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toast.md-default-theme .md-toast-content .md-button.md-highlight, md-toast .md-toast-content .md-button.md-highlight{color:rgb(21,101,192)}md-toolbar.md-default-theme:not(.md-menu-toolbar).md-accent, md-toolbar:not(.md-menu-toolbar).md-accent{background-color:rgb(21,101,192);color:rgba(255,255,255,0.87)}md-toolbar.md-default-theme:not(.md-menu-toolbar).md-accent .md-ink-ripple, md-toolbar:not(.md-menu-toolbar).md-accent .md-ink-ripple{color:rgba(255,255,255,0.87)}md-toolbar.md-default-theme:not(.md-menu-toolbar).md-accent md-icon, md-toolbar:not(.md-menu-toolbar).md-accent md-icon{color:rgba(255,255,255,0.87);fill:rgba(255,255,255,0.87)}md-toolbar.md-default-theme:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon, md-toolbar:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon{color:rgba(255,255,255,0.26);fill:rgba(255,255,255,0.26)} -.md-button.md-default-theme.md-hue-1.md-fab md-icon, .md-button.md-hue-1.md-fab md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1.md-fab, .md-button.md-hue-1.md-fab{background-color:rgb(227,242,253);color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1.md-fab:not([disabled]) .md-icon, .md-button.md-hue-1.md-fab:not([disabled]) .md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1.md-fab:not([disabled]).md-focused, .md-button.md-hue-1.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1.md-fab:not([disabled]):hover, .md-button.md-hue-1.md-fab:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-1.md-accent, .md-button.md-hue-1.md-accent{color:rgb(227,242,253)}.md-button.md-default-theme.md-hue-1.md-accent.md-fab, .md-button.md-hue-1.md-accent.md-fab,.md-button.md-default-theme.md-hue-1.md-accent.md-raised, .md-button.md-hue-1.md-accent.md-raised{color:rgba(0,0,0,0.87);background-color:rgb(227,242,253)}.md-button.md-default-theme.md-hue-1.md-accent.md-fab:not([disabled]) md-icon, .md-button.md-hue-1.md-accent.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-1.md-accent.md-raised:not([disabled]) md-icon, .md-button.md-hue-1.md-accent.md-raised:not([disabled]) md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1.md-accent.md-fab:not([disabled]).md-focused, .md-button.md-hue-1.md-accent.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1.md-accent.md-fab:not([disabled]):hover, .md-button.md-hue-1.md-accent.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-1.md-accent.md-raised:not([disabled]).md-focused, .md-button.md-hue-1.md-accent.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1.md-accent.md-raised:not([disabled]):hover, .md-button.md-hue-1.md-accent.md-raised:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-1.md-accent:not([disabled]) md-icon, .md-button.md-hue-1.md-accent:not([disabled]) md-icon{color:rgb(227,242,253)}.md-button.md-default-theme.md-hue-1.md-accent[disabled], .md-button.md-hue-1.md-accent[disabled],.md-button.md-default-theme.md-hue-1.md-fab[disabled], .md-button.md-hue-1.md-fab[disabled],.md-button.md-default-theme.md-hue-1.md-raised[disabled], .md-button.md-hue-1.md-raised[disabled],.md-button.md-default-theme.md-hue-1.md-warn[disabled], .md-button.md-hue-1.md-warn[disabled],.md-button.md-default-theme.md-hue-1[disabled], .md-button.md-hue-1[disabled]{color:rgba(0,0,0,0.38);cursor:default}.md-button.md-default-theme.md-hue-1.md-accent[disabled] md-icon, .md-button.md-hue-1.md-accent[disabled] md-icon,.md-button.md-default-theme.md-hue-1.md-fab[disabled] md-icon, .md-button.md-hue-1.md-fab[disabled] md-icon,.md-button.md-default-theme.md-hue-1.md-raised[disabled] md-icon, .md-button.md-hue-1.md-raised[disabled] md-icon,.md-button.md-default-theme.md-hue-1.md-warn[disabled] md-icon, .md-button.md-hue-1.md-warn[disabled] md-icon,.md-button.md-default-theme.md-hue-1[disabled] md-icon, .md-button.md-hue-1[disabled] md-icon{color:rgba(0,0,0,0.38)}._md a.md-default-theme.md-hue-1:not(.md-button).md-accent, ._md a.md-hue-1:not(.md-button).md-accent{color:rgb(227,242,253)}._md a.md-default-theme.md-hue-1:not(.md-button).md-accent:hover, ._md a.md-hue-1:not(.md-button).md-accent:hover{color:rgb(41,98,255)}md-checkbox.md-default-theme.md-hue-1 .md-ripple, md-checkbox.md-hue-1 .md-ripple{color:rgb(41,98,255)}md-checkbox.md-default-theme.md-hue-1.md-checked.md-focused .md-container:before, md-checkbox.md-hue-1.md-checked.md-focused .md-container:before{background-color:rgba(227,242,253,0.26)}md-checkbox.md-default-theme.md-hue-1.md-checked .md-ink-ripple, md-checkbox.md-hue-1.md-checked .md-ink-ripple{color:rgba(227,242,253,0.87)}md-checkbox.md-default-theme.md-hue-1.md-checked .md-icon, md-checkbox.md-hue-1.md-checked .md-icon{background-color:rgba(227,242,253,0.87)}md-checkbox.md-default-theme.md-hue-1.md-checked .md-icon:after, md-checkbox.md-hue-1.md-checked .md-icon:after{border-color:rgba(0,0,0,0.87)}.md-accent .md-default-theme.md-hue-1 .md-datepicker-input-container.md-datepicker-focused, .md-accent .md-hue-1 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(227,242,253)}.md-accent .md-default-theme.md-hue-1 .md-datepicker-open .md-datepicker-calendar-icon, .md-accent .md-hue-1 .md-datepicker-open .md-datepicker-calendar-icon,.md-default-theme.md-hue-1 .md-datepicker-open.md-accent .md-datepicker-calendar-icon, .md-hue-1 .md-datepicker-open.md-accent .md-datepicker-calendar-icon{color:rgb(227,242,253)}md-icon.md-default-theme.md-hue-1.md-accent, md-icon.md-hue-1.md-accent{color:rgb(227,242,253)}md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused.md-accent .md-input, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused.md-accent .md-input{border-color:rgb(227,242,253)}md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused.md-accent label, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused.md-accent label,md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused.md-accent md-icon, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused.md-accent md-icon{color:rgb(227,242,253)}md-list.md-default-theme.md-hue-1 md-list-item>md-icon.md-highlight.md-accent, md-list.md-hue-1 md-list-item>md-icon.md-highlight.md-accent{color:rgb(227,242,253)}md-nav-bar.md-default-theme.md-hue-1 md-nav-ink-bar, md-nav-bar.md-hue-1 md-nav-ink-bar{color:rgb(227,242,253);background:rgb(227,242,253)}md-nav-bar.md-default-theme.md-hue-1.md-accent>.md-nav-bar, md-nav-bar.md-hue-1.md-accent>.md-nav-bar{background-color:rgb(227,242,253)}md-nav-bar.md-default-theme.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-nav-bar.md-default-theme.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-1.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-nav-bar.md-default-theme.md-hue-1.md-accent>.md-nav-bar md-nav-ink-bar, md-nav-bar.md-hue-1.md-accent>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar, md-toolbar.md-accent>md-nav-bar.md-hue-1>.md-nav-bar{background-color:rgb(227,242,253)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-accent>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-accent>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar md-nav-ink-bar, md-toolbar.md-accent>md-nav-bar.md-hue-1>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-progress-circular.md-default-theme.md-hue-1.md-accent path, md-progress-circular.md-hue-1.md-accent path{stroke:rgb(227,242,253)}md-progress-linear.md-default-theme.md-hue-1.md-accent .md-container, md-progress-linear.md-hue-1.md-accent .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-1.md-accent .md-bar, md-progress-linear.md-hue-1.md-accent .md-bar{background-color:rgb(227,242,253)}md-progress-linear.md-default-theme.md-hue-1[md-mode=buffer].md-accent .md-bar1, md-progress-linear.md-hue-1[md-mode=buffer].md-accent .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-1[md-mode=buffer].md-accent .md-dashed:before, md-progress-linear.md-hue-1[md-mode=buffer].md-accent .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-1 .md-on, md-radio-button.md-hue-1 .md-on{background-color:rgba(227,242,253,0.87)}md-radio-button.md-default-theme.md-hue-1.md-checked .md-off, md-radio-button.md-hue-1.md-checked .md-off{border-color:rgba(227,242,253,0.87)}md-radio-button.md-default-theme.md-hue-1.md-checked .md-ink-ripple, md-radio-button.md-hue-1.md-checked .md-ink-ripple{color:rgba(227,242,253,0.87)}md-radio-button.md-default-theme.md-hue-1 .md-container .md-ripple, md-radio-button.md-hue-1 .md-container .md-ripple{color:rgb(41,98,255)}md-radio-group.md-default-theme.md-hue-1 .md-checked .md-ink-ripple, md-radio-group.md-hue-1 .md-checked .md-ink-ripple{color:rgba(227,242,253,0.26)}md-radio-group.md-default-theme.md-hue-1.md-focused:not(:empty) .md-checked .md-container:before, md-radio-group.md-hue-1.md-focused:not(:empty) .md-checked .md-container:before{background-color:rgba(227,242,253,0.26)}md-select.md-default-theme.md-hue-1:not([disabled]):focus.md-accent .md-select-value, md-select.md-hue-1:not([disabled]):focus.md-accent .md-select-value{border-bottom-color:rgb(227,242,253)}md-select-menu.md-default-theme.md-hue-1 md-content md-option[selected].md-accent, md-select-menu.md-hue-1 md-content md-option[selected].md-accent{color:rgb(227,242,253)}md-select-menu.md-default-theme.md-hue-1 md-content md-option[selected].md-accent:focus, md-select-menu.md-hue-1 md-content md-option[selected].md-accent:focus{color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-1 .md-focus-ring, md-slider.md-hue-1 .md-focus-ring{background-color:rgba(68,138,255,0.2)}md-slider.md-default-theme.md-hue-1 .md-track.md-track-fill, md-slider.md-hue-1 .md-track.md-track-fill{background-color:rgb(227,242,253)}md-slider.md-default-theme.md-hue-1 .md-thumb:after, md-slider.md-hue-1 .md-thumb:after{border-color:rgb(227,242,253);background-color:rgb(227,242,253)}md-slider.md-default-theme.md-hue-1 .md-sign, md-slider.md-hue-1 .md-sign{background-color:rgb(227,242,253)}md-slider.md-default-theme.md-hue-1 .md-sign:after, md-slider.md-hue-1 .md-sign:after{border-top-color:rgb(227,242,253)}md-slider.md-default-theme.md-hue-1[md-vertical] .md-sign:after, md-slider.md-hue-1[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(227,242,253)}md-slider.md-default-theme.md-hue-1 .md-thumb-text, md-slider.md-hue-1 .md-thumb-text{color:rgba(0,0,0,0.87)}.md-subheader.md-default-theme.md-hue-1.md-accent, .md-subheader.md-hue-1.md-accent{color:rgb(227,242,253)}md-switch.md-default-theme.md-hue-1.md-checked .md-ink-ripple, md-switch.md-hue-1.md-checked .md-ink-ripple{color:rgb(227,242,253)}md-switch.md-default-theme.md-hue-1.md-checked .md-thumb, md-switch.md-hue-1.md-checked .md-thumb{background-color:rgb(227,242,253)}md-switch.md-default-theme.md-hue-1.md-checked .md-bar, md-switch.md-hue-1.md-checked .md-bar{background-color:rgba(227,242,253,0.5)}md-switch.md-default-theme.md-hue-1.md-checked.md-focused .md-thumb:before, md-switch.md-hue-1.md-checked.md-focused .md-thumb:before{background-color:rgba(227,242,253,0.26)}md-tabs.md-default-theme.md-hue-1 md-ink-bar, md-tabs.md-hue-1 md-ink-bar{color:rgb(227,242,253);background:rgb(227,242,253)}md-tabs.md-default-theme.md-hue-1 .md-tab .md-ripple-container, md-tabs.md-hue-1 .md-tab .md-ripple-container{color:rgb(130,177,255)}md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper, md-tabs.md-hue-1.md-accent>md-tabs-wrapper{background-color:rgb(227,242,253)}md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-tabs.md-default-theme.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-tabs.md-hue-1.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper{background-color:rgb(227,242,253)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-toolbar.md-accent>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toast.md-default-theme.md-hue-1 .md-toast-content .md-button.md-highlight, md-toast.md-hue-1 .md-toast-content .md-button.md-highlight{color:rgb(227,242,253)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar).md-accent, md-toolbar.md-hue-1:not(.md-menu-toolbar).md-accent{background-color:rgb(227,242,253);color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar).md-accent .md-ink-ripple, md-toolbar.md-hue-1:not(.md-menu-toolbar).md-accent .md-ink-ripple{color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar).md-accent md-icon, md-toolbar.md-hue-1:not(.md-menu-toolbar).md-accent md-icon{color:rgba(0,0,0,0.87);fill:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon, md-toolbar.md-hue-1:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon{color:rgba(0,0,0,0.26);fill:rgba(0,0,0,0.26)} -.md-button.md-default-theme.md-hue-2.md-fab md-icon, .md-button.md-hue-2.md-fab md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-2.md-fab, .md-button.md-hue-2.md-fab{background-color:rgb(33,150,243);color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-2.md-fab:not([disabled]) .md-icon, .md-button.md-hue-2.md-fab:not([disabled]) .md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-2.md-fab:not([disabled]).md-focused, .md-button.md-hue-2.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2.md-fab:not([disabled]):hover, .md-button.md-hue-2.md-fab:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-2.md-accent, .md-button.md-hue-2.md-accent{color:rgb(33,150,243)}.md-button.md-default-theme.md-hue-2.md-accent.md-fab, .md-button.md-hue-2.md-accent.md-fab,.md-button.md-default-theme.md-hue-2.md-accent.md-raised, .md-button.md-hue-2.md-accent.md-raised{color:rgba(0,0,0,0.87);background-color:rgb(33,150,243)}.md-button.md-default-theme.md-hue-2.md-accent.md-fab:not([disabled]) md-icon, .md-button.md-hue-2.md-accent.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-2.md-accent.md-raised:not([disabled]) md-icon, .md-button.md-hue-2.md-accent.md-raised:not([disabled]) md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-2.md-accent.md-fab:not([disabled]).md-focused, .md-button.md-hue-2.md-accent.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2.md-accent.md-fab:not([disabled]):hover, .md-button.md-hue-2.md-accent.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-2.md-accent.md-raised:not([disabled]).md-focused, .md-button.md-hue-2.md-accent.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2.md-accent.md-raised:not([disabled]):hover, .md-button.md-hue-2.md-accent.md-raised:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-2.md-accent:not([disabled]) md-icon, .md-button.md-hue-2.md-accent:not([disabled]) md-icon{color:rgb(33,150,243)}.md-button.md-default-theme.md-hue-2.md-accent[disabled], .md-button.md-hue-2.md-accent[disabled],.md-button.md-default-theme.md-hue-2.md-fab[disabled], .md-button.md-hue-2.md-fab[disabled],.md-button.md-default-theme.md-hue-2.md-raised[disabled], .md-button.md-hue-2.md-raised[disabled],.md-button.md-default-theme.md-hue-2.md-warn[disabled], .md-button.md-hue-2.md-warn[disabled],.md-button.md-default-theme.md-hue-2[disabled], .md-button.md-hue-2[disabled]{color:rgba(0,0,0,0.38);cursor:default}.md-button.md-default-theme.md-hue-2.md-accent[disabled] md-icon, .md-button.md-hue-2.md-accent[disabled] md-icon,.md-button.md-default-theme.md-hue-2.md-fab[disabled] md-icon, .md-button.md-hue-2.md-fab[disabled] md-icon,.md-button.md-default-theme.md-hue-2.md-raised[disabled] md-icon, .md-button.md-hue-2.md-raised[disabled] md-icon,.md-button.md-default-theme.md-hue-2.md-warn[disabled] md-icon, .md-button.md-hue-2.md-warn[disabled] md-icon,.md-button.md-default-theme.md-hue-2[disabled] md-icon, .md-button.md-hue-2[disabled] md-icon{color:rgba(0,0,0,0.38)}._md a.md-default-theme.md-hue-2:not(.md-button).md-accent, ._md a.md-hue-2:not(.md-button).md-accent{color:rgb(33,150,243)}._md a.md-default-theme.md-hue-2:not(.md-button).md-accent:hover, ._md a.md-hue-2:not(.md-button).md-accent:hover{color:rgb(41,98,255)}md-checkbox.md-default-theme.md-hue-2 .md-ripple, md-checkbox.md-hue-2 .md-ripple{color:rgb(41,98,255)}md-checkbox.md-default-theme.md-hue-2.md-checked.md-focused .md-container:before, md-checkbox.md-hue-2.md-checked.md-focused .md-container:before{background-color:rgba(33,150,243,0.26)}md-checkbox.md-default-theme.md-hue-2.md-checked .md-ink-ripple, md-checkbox.md-hue-2.md-checked .md-ink-ripple{color:rgba(33,150,243,0.87)}md-checkbox.md-default-theme.md-hue-2.md-checked .md-icon, md-checkbox.md-hue-2.md-checked .md-icon{background-color:rgba(33,150,243,0.87)}md-checkbox.md-default-theme.md-hue-2.md-checked .md-icon:after, md-checkbox.md-hue-2.md-checked .md-icon:after{border-color:rgba(0,0,0,0.87)}.md-accent .md-default-theme.md-hue-2 .md-datepicker-input-container.md-datepicker-focused, .md-accent .md-hue-2 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(33,150,243)}.md-accent .md-default-theme.md-hue-2 .md-datepicker-open .md-datepicker-calendar-icon, .md-accent .md-hue-2 .md-datepicker-open .md-datepicker-calendar-icon,.md-default-theme.md-hue-2 .md-datepicker-open.md-accent .md-datepicker-calendar-icon, .md-hue-2 .md-datepicker-open.md-accent .md-datepicker-calendar-icon{color:rgb(33,150,243)}md-icon.md-default-theme.md-hue-2.md-accent, md-icon.md-hue-2.md-accent{color:rgb(33,150,243)}md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused.md-accent .md-input, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused.md-accent .md-input{border-color:rgb(33,150,243)}md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused.md-accent label, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused.md-accent label,md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused.md-accent md-icon, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused.md-accent md-icon{color:rgb(33,150,243)}md-list.md-default-theme.md-hue-2 md-list-item>md-icon.md-highlight.md-accent, md-list.md-hue-2 md-list-item>md-icon.md-highlight.md-accent{color:rgb(33,150,243)}md-nav-bar.md-default-theme.md-hue-2 md-nav-ink-bar, md-nav-bar.md-hue-2 md-nav-ink-bar{color:rgb(33,150,243);background:rgb(33,150,243)}md-nav-bar.md-default-theme.md-hue-2.md-accent>.md-nav-bar, md-nav-bar.md-hue-2.md-accent>.md-nav-bar{background-color:rgb(33,150,243)}md-nav-bar.md-default-theme.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-nav-bar.md-default-theme.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-2.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-nav-bar.md-default-theme.md-hue-2.md-accent>.md-nav-bar md-nav-ink-bar, md-nav-bar.md-hue-2.md-accent>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar, md-toolbar.md-accent>md-nav-bar.md-hue-2>.md-nav-bar{background-color:rgb(33,150,243)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-accent>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-accent>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar md-nav-ink-bar, md-toolbar.md-accent>md-nav-bar.md-hue-2>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-progress-circular.md-default-theme.md-hue-2.md-accent path, md-progress-circular.md-hue-2.md-accent path{stroke:rgb(33,150,243)}md-progress-linear.md-default-theme.md-hue-2.md-accent .md-container, md-progress-linear.md-hue-2.md-accent .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-2.md-accent .md-bar, md-progress-linear.md-hue-2.md-accent .md-bar{background-color:rgb(33,150,243)}md-progress-linear.md-default-theme.md-hue-2[md-mode=buffer].md-accent .md-bar1, md-progress-linear.md-hue-2[md-mode=buffer].md-accent .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-2[md-mode=buffer].md-accent .md-dashed:before, md-progress-linear.md-hue-2[md-mode=buffer].md-accent .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-2 .md-on, md-radio-button.md-hue-2 .md-on{background-color:rgba(33,150,243,0.87)}md-radio-button.md-default-theme.md-hue-2.md-checked .md-off, md-radio-button.md-hue-2.md-checked .md-off{border-color:rgba(33,150,243,0.87)}md-radio-button.md-default-theme.md-hue-2.md-checked .md-ink-ripple, md-radio-button.md-hue-2.md-checked .md-ink-ripple{color:rgba(33,150,243,0.87)}md-radio-button.md-default-theme.md-hue-2 .md-container .md-ripple, md-radio-button.md-hue-2 .md-container .md-ripple{color:rgb(41,98,255)}md-radio-group.md-default-theme.md-hue-2 .md-checked .md-ink-ripple, md-radio-group.md-hue-2 .md-checked .md-ink-ripple{color:rgba(33,150,243,0.26)}md-radio-group.md-default-theme.md-hue-2.md-focused:not(:empty) .md-checked .md-container:before, md-radio-group.md-hue-2.md-focused:not(:empty) .md-checked .md-container:before{background-color:rgba(33,150,243,0.26)}md-select.md-default-theme.md-hue-2:not([disabled]):focus.md-accent .md-select-value, md-select.md-hue-2:not([disabled]):focus.md-accent .md-select-value{border-bottom-color:rgb(33,150,243)}md-select-menu.md-default-theme.md-hue-2 md-content md-option[selected].md-accent, md-select-menu.md-hue-2 md-content md-option[selected].md-accent{color:rgb(33,150,243)}md-select-menu.md-default-theme.md-hue-2 md-content md-option[selected].md-accent:focus, md-select-menu.md-hue-2 md-content md-option[selected].md-accent:focus{color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-2 .md-focus-ring, md-slider.md-hue-2 .md-focus-ring{background-color:rgba(68,138,255,0.2)}md-slider.md-default-theme.md-hue-2 .md-track.md-track-fill, md-slider.md-hue-2 .md-track.md-track-fill{background-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-2 .md-thumb:after, md-slider.md-hue-2 .md-thumb:after{border-color:rgb(33,150,243);background-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-2 .md-sign, md-slider.md-hue-2 .md-sign{background-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-2 .md-sign:after, md-slider.md-hue-2 .md-sign:after{border-top-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-2[md-vertical] .md-sign:after, md-slider.md-hue-2[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(33,150,243)}md-slider.md-default-theme.md-hue-2 .md-thumb-text, md-slider.md-hue-2 .md-thumb-text{color:rgba(0,0,0,0.87)}.md-subheader.md-default-theme.md-hue-2.md-accent, .md-subheader.md-hue-2.md-accent{color:rgb(33,150,243)}md-switch.md-default-theme.md-hue-2.md-checked .md-ink-ripple, md-switch.md-hue-2.md-checked .md-ink-ripple{color:rgb(33,150,243)}md-switch.md-default-theme.md-hue-2.md-checked .md-thumb, md-switch.md-hue-2.md-checked .md-thumb{background-color:rgb(33,150,243)}md-switch.md-default-theme.md-hue-2.md-checked .md-bar, md-switch.md-hue-2.md-checked .md-bar{background-color:rgba(33,150,243,0.5)}md-switch.md-default-theme.md-hue-2.md-checked.md-focused .md-thumb:before, md-switch.md-hue-2.md-checked.md-focused .md-thumb:before{background-color:rgba(33,150,243,0.26)}md-tabs.md-default-theme.md-hue-2 md-ink-bar, md-tabs.md-hue-2 md-ink-bar{color:rgb(33,150,243);background:rgb(33,150,243)}md-tabs.md-default-theme.md-hue-2 .md-tab .md-ripple-container, md-tabs.md-hue-2 .md-tab .md-ripple-container{color:rgb(130,177,255)}md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper, md-tabs.md-hue-2.md-accent>md-tabs-wrapper{background-color:rgb(33,150,243)}md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-tabs.md-default-theme.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-tabs.md-hue-2.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper{background-color:rgb(33,150,243)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-toolbar.md-accent>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toast.md-default-theme.md-hue-2 .md-toast-content .md-button.md-highlight, md-toast.md-hue-2 .md-toast-content .md-button.md-highlight{color:rgb(33,150,243)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar).md-accent, md-toolbar.md-hue-2:not(.md-menu-toolbar).md-accent{background-color:rgb(33,150,243);color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar).md-accent .md-ink-ripple, md-toolbar.md-hue-2:not(.md-menu-toolbar).md-accent .md-ink-ripple{color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar).md-accent md-icon, md-toolbar.md-hue-2:not(.md-menu-toolbar).md-accent md-icon{color:rgba(0,0,0,0.87);fill:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon, md-toolbar.md-hue-2:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon{color:rgba(0,0,0,0.26);fill:rgba(0,0,0,0.26)} -.md-button.md-default-theme.md-hue-3.md-fab md-icon, .md-button.md-hue-3.md-fab md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3.md-fab, .md-button.md-hue-3.md-fab{background-color:rgb(41,98,255);color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3.md-fab:not([disabled]) .md-icon, .md-button.md-hue-3.md-fab:not([disabled]) .md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3.md-fab:not([disabled]).md-focused, .md-button.md-hue-3.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3.md-fab:not([disabled]):hover, .md-button.md-hue-3.md-fab:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-3.md-accent, .md-button.md-hue-3.md-accent{color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-3.md-accent.md-fab, .md-button.md-hue-3.md-accent.md-fab,.md-button.md-default-theme.md-hue-3.md-accent.md-raised, .md-button.md-hue-3.md-accent.md-raised{color:rgba(0,0,0,0.87);background-color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-3.md-accent.md-fab:not([disabled]) md-icon, .md-button.md-hue-3.md-accent.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-3.md-accent.md-raised:not([disabled]) md-icon, .md-button.md-hue-3.md-accent.md-raised:not([disabled]) md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3.md-accent.md-fab:not([disabled]).md-focused, .md-button.md-hue-3.md-accent.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3.md-accent.md-fab:not([disabled]):hover, .md-button.md-hue-3.md-accent.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-3.md-accent.md-raised:not([disabled]).md-focused, .md-button.md-hue-3.md-accent.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3.md-accent.md-raised:not([disabled]):hover, .md-button.md-hue-3.md-accent.md-raised:not([disabled]):hover{background-color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-3.md-accent:not([disabled]) md-icon, .md-button.md-hue-3.md-accent:not([disabled]) md-icon{color:rgb(41,98,255)}.md-button.md-default-theme.md-hue-3.md-accent[disabled], .md-button.md-hue-3.md-accent[disabled],.md-button.md-default-theme.md-hue-3.md-fab[disabled], .md-button.md-hue-3.md-fab[disabled],.md-button.md-default-theme.md-hue-3.md-raised[disabled], .md-button.md-hue-3.md-raised[disabled],.md-button.md-default-theme.md-hue-3.md-warn[disabled], .md-button.md-hue-3.md-warn[disabled],.md-button.md-default-theme.md-hue-3[disabled], .md-button.md-hue-3[disabled]{color:rgba(0,0,0,0.38);cursor:default}.md-button.md-default-theme.md-hue-3.md-accent[disabled] md-icon, .md-button.md-hue-3.md-accent[disabled] md-icon,.md-button.md-default-theme.md-hue-3.md-fab[disabled] md-icon, .md-button.md-hue-3.md-fab[disabled] md-icon,.md-button.md-default-theme.md-hue-3.md-raised[disabled] md-icon, .md-button.md-hue-3.md-raised[disabled] md-icon,.md-button.md-default-theme.md-hue-3.md-warn[disabled] md-icon, .md-button.md-hue-3.md-warn[disabled] md-icon,.md-button.md-default-theme.md-hue-3[disabled] md-icon, .md-button.md-hue-3[disabled] md-icon{color:rgba(0,0,0,0.38)}._md a.md-default-theme.md-hue-3:not(.md-button).md-accent, ._md a.md-hue-3:not(.md-button).md-accent{color:rgb(41,98,255)}._md a.md-default-theme.md-hue-3:not(.md-button).md-accent:hover, ._md a.md-hue-3:not(.md-button).md-accent:hover{color:rgb(41,98,255)}md-checkbox.md-default-theme.md-hue-3 .md-ripple, md-checkbox.md-hue-3 .md-ripple{color:rgb(41,98,255)}md-checkbox.md-default-theme.md-hue-3.md-checked.md-focused .md-container:before, md-checkbox.md-hue-3.md-checked.md-focused .md-container:before{background-color:rgba(41,98,255,0.26)}md-checkbox.md-default-theme.md-hue-3.md-checked .md-ink-ripple, md-checkbox.md-hue-3.md-checked .md-ink-ripple{color:rgba(41,98,255,0.87)}md-checkbox.md-default-theme.md-hue-3.md-checked .md-icon, md-checkbox.md-hue-3.md-checked .md-icon{background-color:rgba(41,98,255,0.87)}md-checkbox.md-default-theme.md-hue-3.md-checked .md-icon:after, md-checkbox.md-hue-3.md-checked .md-icon:after{border-color:rgba(0,0,0,0.87)}.md-accent .md-default-theme.md-hue-3 .md-datepicker-input-container.md-datepicker-focused, .md-accent .md-hue-3 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(41,98,255)}.md-accent .md-default-theme.md-hue-3 .md-datepicker-open .md-datepicker-calendar-icon, .md-accent .md-hue-3 .md-datepicker-open .md-datepicker-calendar-icon,.md-default-theme.md-hue-3 .md-datepicker-open.md-accent .md-datepicker-calendar-icon, .md-hue-3 .md-datepicker-open.md-accent .md-datepicker-calendar-icon{color:rgb(41,98,255)}md-icon.md-default-theme.md-hue-3.md-accent, md-icon.md-hue-3.md-accent{color:rgb(41,98,255)}md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused.md-accent .md-input, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused.md-accent .md-input{border-color:rgb(41,98,255)}md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused.md-accent label, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused.md-accent label,md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused.md-accent md-icon, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused.md-accent md-icon{color:rgb(41,98,255)}md-list.md-default-theme.md-hue-3 md-list-item>md-icon.md-highlight.md-accent, md-list.md-hue-3 md-list-item>md-icon.md-highlight.md-accent{color:rgb(41,98,255)}md-nav-bar.md-default-theme.md-hue-3 md-nav-ink-bar, md-nav-bar.md-hue-3 md-nav-ink-bar{color:rgb(41,98,255);background:rgb(41,98,255)}md-nav-bar.md-default-theme.md-hue-3.md-accent>.md-nav-bar, md-nav-bar.md-hue-3.md-accent>.md-nav-bar{background-color:rgb(41,98,255)}md-nav-bar.md-default-theme.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-nav-bar.md-default-theme.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-3.md-accent>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-nav-bar.md-default-theme.md-hue-3.md-accent>.md-nav-bar md-nav-ink-bar, md-nav-bar.md-hue-3.md-accent>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar, md-toolbar.md-accent>md-nav-bar.md-hue-3>.md-nav-bar{background-color:rgb(41,98,255)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-accent>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button{color:rgb(130,177,255)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-accent>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-accent>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-accent>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar md-nav-ink-bar, md-toolbar.md-accent>md-nav-bar.md-hue-3>.md-nav-bar md-nav-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-progress-circular.md-default-theme.md-hue-3.md-accent path, md-progress-circular.md-hue-3.md-accent path{stroke:rgb(41,98,255)}md-progress-linear.md-default-theme.md-hue-3.md-accent .md-container, md-progress-linear.md-hue-3.md-accent .md-container{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-3.md-accent .md-bar, md-progress-linear.md-hue-3.md-accent .md-bar{background-color:rgb(41,98,255)}md-progress-linear.md-default-theme.md-hue-3[md-mode=buffer].md-accent .md-bar1, md-progress-linear.md-hue-3[md-mode=buffer].md-accent .md-bar1{background-color:rgb(187,222,251)}md-progress-linear.md-default-theme.md-hue-3[md-mode=buffer].md-accent .md-dashed:before, md-progress-linear.md-hue-3[md-mode=buffer].md-accent .md-dashed:before{background:radial-gradient(rgb(187,222,251) 0,rgb(187,222,251) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-3 .md-on, md-radio-button.md-hue-3 .md-on{background-color:rgba(41,98,255,0.87)}md-radio-button.md-default-theme.md-hue-3.md-checked .md-off, md-radio-button.md-hue-3.md-checked .md-off{border-color:rgba(41,98,255,0.87)}md-radio-button.md-default-theme.md-hue-3.md-checked .md-ink-ripple, md-radio-button.md-hue-3.md-checked .md-ink-ripple{color:rgba(41,98,255,0.87)}md-radio-button.md-default-theme.md-hue-3 .md-container .md-ripple, md-radio-button.md-hue-3 .md-container .md-ripple{color:rgb(41,98,255)}md-radio-group.md-default-theme.md-hue-3 .md-checked .md-ink-ripple, md-radio-group.md-hue-3 .md-checked .md-ink-ripple{color:rgba(41,98,255,0.26)}md-radio-group.md-default-theme.md-hue-3.md-focused:not(:empty) .md-checked .md-container:before, md-radio-group.md-hue-3.md-focused:not(:empty) .md-checked .md-container:before{background-color:rgba(41,98,255,0.26)}md-select.md-default-theme.md-hue-3:not([disabled]):focus.md-accent .md-select-value, md-select.md-hue-3:not([disabled]):focus.md-accent .md-select-value{border-bottom-color:rgb(41,98,255)}md-select-menu.md-default-theme.md-hue-3 md-content md-option[selected].md-accent, md-select-menu.md-hue-3 md-content md-option[selected].md-accent{color:rgb(41,98,255)}md-select-menu.md-default-theme.md-hue-3 md-content md-option[selected].md-accent:focus, md-select-menu.md-hue-3 md-content md-option[selected].md-accent:focus{color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-3 .md-focus-ring, md-slider.md-hue-3 .md-focus-ring{background-color:rgba(68,138,255,0.2)}md-slider.md-default-theme.md-hue-3 .md-track.md-track-fill, md-slider.md-hue-3 .md-track.md-track-fill{background-color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-3 .md-thumb:after, md-slider.md-hue-3 .md-thumb:after{border-color:rgb(41,98,255);background-color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-3 .md-sign, md-slider.md-hue-3 .md-sign{background-color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-3 .md-sign:after, md-slider.md-hue-3 .md-sign:after{border-top-color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-3[md-vertical] .md-sign:after, md-slider.md-hue-3[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(41,98,255)}md-slider.md-default-theme.md-hue-3 .md-thumb-text, md-slider.md-hue-3 .md-thumb-text{color:rgba(0,0,0,0.87)}.md-subheader.md-default-theme.md-hue-3.md-accent, .md-subheader.md-hue-3.md-accent{color:rgb(41,98,255)}md-switch.md-default-theme.md-hue-3.md-checked .md-ink-ripple, md-switch.md-hue-3.md-checked .md-ink-ripple{color:rgb(41,98,255)}md-switch.md-default-theme.md-hue-3.md-checked .md-thumb, md-switch.md-hue-3.md-checked .md-thumb{background-color:rgb(41,98,255)}md-switch.md-default-theme.md-hue-3.md-checked .md-bar, md-switch.md-hue-3.md-checked .md-bar{background-color:rgba(41,98,255,0.5)}md-switch.md-default-theme.md-hue-3.md-checked.md-focused .md-thumb:before, md-switch.md-hue-3.md-checked.md-focused .md-thumb:before{background-color:rgba(41,98,255,0.26)}md-tabs.md-default-theme.md-hue-3 md-ink-bar, md-tabs.md-hue-3 md-ink-bar{color:rgb(41,98,255);background:rgb(41,98,255)}md-tabs.md-default-theme.md-hue-3 .md-tab .md-ripple-container, md-tabs.md-hue-3 .md-tab .md-ripple-container{color:rgb(130,177,255)}md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper, md-tabs.md-hue-3.md-accent>md-tabs-wrapper{background-color:rgb(41,98,255)}md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-tabs.md-default-theme.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-tabs.md-hue-3.md-accent>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper{background-color:rgb(41,98,255)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(130,177,255)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-accent>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar, md-toolbar.md-accent>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-ink-bar{color:rgba(30,136,229,1);background:rgba(30,136,229,1)}md-toast.md-default-theme.md-hue-3 .md-toast-content .md-button.md-highlight, md-toast.md-hue-3 .md-toast-content .md-button.md-highlight{color:rgb(41,98,255)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar).md-accent, md-toolbar.md-hue-3:not(.md-menu-toolbar).md-accent{background-color:rgb(41,98,255);color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar).md-accent .md-ink-ripple, md-toolbar.md-hue-3:not(.md-menu-toolbar).md-accent .md-ink-ripple{color:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar).md-accent md-icon, md-toolbar.md-hue-3:not(.md-menu-toolbar).md-accent md-icon{color:rgba(0,0,0,0.87);fill:rgba(0,0,0,0.87)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon, md-toolbar.md-hue-3:not(.md-menu-toolbar).md-accent .md-button[disabled] md-icon{color:rgba(0,0,0,0.26);fill:rgba(0,0,0,0.26)} -.md-button.md-default-theme.md-warn, .md-button.md-warn{color:rgb(255,87,34)}.md-button.md-default-theme.md-warn.md-fab, .md-button.md-warn.md-fab,.md-button.md-default-theme.md-warn.md-raised, .md-button.md-warn.md-raised{color:rgb(255,255,255);background-color:rgb(255,87,34)}.md-button.md-default-theme.md-warn.md-fab:not([disabled]) md-icon, .md-button.md-warn.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-warn.md-raised:not([disabled]) md-icon, .md-button.md-warn.md-raised:not([disabled]) md-icon{color:rgb(255,255,255)}.md-button.md-default-theme.md-warn.md-fab:not([disabled]).md-focused, .md-button.md-warn.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-warn.md-fab:not([disabled]):hover, .md-button.md-warn.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-warn.md-raised:not([disabled]).md-focused, .md-button.md-warn.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-warn.md-raised:not([disabled]):hover, .md-button.md-warn.md-raised:not([disabled]):hover{background-color:rgb(244,81,30)}.md-button.md-default-theme.md-warn:not([disabled]) md-icon, .md-button.md-warn:not([disabled]) md-icon{color:rgb(255,87,34)}._md a.md-default-theme:not(.md-button).md-warn, ._md a:not(.md-button).md-warn{color:rgb(255,87,34)}._md a.md-default-theme:not(.md-button).md-warn:hover, ._md a:not(.md-button).md-warn:hover{color:rgb(230,74,25)}md-checkbox.md-default-theme:not([disabled]).md-warn .md-ripple, md-checkbox:not([disabled]).md-warn .md-ripple{color:rgb(244,81,30)}md-checkbox.md-default-theme:not([disabled]).md-warn .md-ink-ripple, md-checkbox:not([disabled]).md-warn .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme:not([disabled]).md-warn.md-checked .md-ink-ripple, md-checkbox:not([disabled]).md-warn.md-checked .md-ink-ripple{color:rgba(255,87,34,0.87)}md-checkbox.md-default-theme:not([disabled]).md-warn:not(.md-checked) .md-icon, md-checkbox:not([disabled]).md-warn:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme:not([disabled]).md-warn.md-checked .md-icon, md-checkbox:not([disabled]).md-warn.md-checked .md-icon{background-color:rgba(255,87,34,0.87)}md-checkbox.md-default-theme:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before, md-checkbox:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before{background-color:rgba(255,87,34,0.26)}md-checkbox.md-default-theme:not([disabled]).md-warn.md-checked .md-icon:after, md-checkbox:not([disabled]).md-warn.md-checked .md-icon:after{border-color:rgb(238,238,238)}.md-default-theme .md-datepicker-input-container.md-datepicker-invalid, .md-datepicker-input-container.md-datepicker-invalid,.md-warn .md-default-theme .md-datepicker-input-container.md-datepicker-focused, .md-warn .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(221,44,0)}.md-default-theme .md-datepicker-open.md-warn .md-datepicker-calendar-icon, .md-datepicker-open.md-warn .md-datepicker-calendar-icon,.md-warn .md-default-theme .md-datepicker-open .md-datepicker-calendar-icon, .md-warn .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(221,44,0)}md-icon.md-default-theme.md-warn, md-icon.md-warn{color:rgb(255,87,34)}md-input-container.md-default-theme label.md-required:after, md-input-container label.md-required:after{color:rgb(221,44,0)}md-input-container.md-default-theme .md-input-message-animation, md-input-container .md-input-message-animation,md-input-container.md-default-theme .md-input-messages-animation, md-input-container .md-input-messages-animation{color:rgb(221,44,0)}md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused.md-warn .md-input, md-input-container:not(.md-input-invalid).md-input-focused.md-warn .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused.md-warn label, md-input-container:not(.md-input-invalid).md-input-focused.md-warn label,md-input-container.md-default-theme:not(.md-input-invalid).md-input-focused.md-warn md-icon, md-input-container:not(.md-input-invalid).md-input-focused.md-warn md-icon{color:rgb(221,44,0)}md-input-container.md-default-theme.md-input-invalid .md-input, md-input-container.md-input-invalid .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme.md-input-invalid .md-char-counter, md-input-container.md-input-invalid .md-char-counter,md-input-container.md-default-theme.md-input-invalid .md-input-message-animation, md-input-container.md-input-invalid .md-input-message-animation,md-input-container.md-default-theme.md-input-invalid label, md-input-container.md-input-invalid label{color:rgb(221,44,0)}md-nav-bar.md-default-theme.md-warn>.md-nav-bar, md-nav-bar.md-warn>.md-nav-bar{background-color:rgb(255,87,34)}md-nav-bar.md-default-theme.md-warn>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-warn>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-nav-bar.md-default-theme.md-warn>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-warn>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgb(255,255,255)}md-nav-bar.md-default-theme.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-toolbar.md-warn>md-nav-bar.md-default-theme>.md-nav-bar, md-toolbar.md-warn>md-nav-bar>.md-nav-bar{background-color:rgb(255,87,34)}md-toolbar.md-warn>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-warn>md-nav-bar>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-toolbar.md-warn>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-warn>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-warn>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgb(255,255,255)}md-toolbar.md-warn>md-nav-bar.md-default-theme>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-progress-circular.md-default-theme.md-warn path, md-progress-circular.md-warn path{stroke:rgb(255,87,34)}md-progress-linear.md-default-theme.md-warn .md-container, md-progress-linear.md-warn .md-container{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme.md-warn .md-bar, md-progress-linear.md-warn .md-bar{background-color:rgb(255,87,34)}md-progress-linear.md-default-theme[md-mode=buffer].md-warn .md-bar1, md-progress-linear[md-mode=buffer].md-warn .md-bar1{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme[md-mode=buffer].md-warn .md-dashed:before, md-progress-linear[md-mode=buffer].md-warn .md-dashed:before{background:radial-gradient(rgb(255,204,188) 0,rgb(255,204,188) 16%,transparent 42%)}md-radio-button.md-default-theme:not([disabled]).md-warn .md-on, md-radio-button:not([disabled]).md-warn .md-on,md-radio-button.md-default-theme:not([disabled]) .md-warn .md-on, md-radio-button:not([disabled]) .md-warn .md-on,md-radio-group.md-default-theme:not([disabled]).md-warn .md-on, md-radio-group:not([disabled]).md-warn .md-on,md-radio-group.md-default-theme:not([disabled]) .md-warn .md-on, md-radio-group:not([disabled]) .md-warn .md-on{background-color:rgba(255,87,34,0.87)}md-radio-button.md-default-theme:not([disabled]).md-warn.md-checked .md-off, md-radio-button:not([disabled]).md-warn.md-checked .md-off,md-radio-button.md-default-theme:not([disabled]) .md-warn.md-checked .md-off, md-radio-button:not([disabled]) .md-warn.md-checked .md-off,md-radio-button.md-default-theme:not([disabled]).md-warn .md-checked .md-off, md-radio-button:not([disabled]).md-warn .md-checked .md-off,md-radio-button.md-default-theme:not([disabled]) .md-warn .md-checked .md-off, md-radio-button:not([disabled]) .md-warn .md-checked .md-off,md-radio-group.md-default-theme:not([disabled]).md-warn.md-checked .md-off, md-radio-group:not([disabled]).md-warn.md-checked .md-off,md-radio-group.md-default-theme:not([disabled]) .md-warn.md-checked .md-off, md-radio-group:not([disabled]) .md-warn.md-checked .md-off,md-radio-group.md-default-theme:not([disabled]).md-warn .md-checked .md-off, md-radio-group:not([disabled]).md-warn .md-checked .md-off,md-radio-group.md-default-theme:not([disabled]) .md-warn .md-checked .md-off, md-radio-group:not([disabled]) .md-warn .md-checked .md-off{border-color:rgba(255,87,34,0.87)}md-radio-button.md-default-theme:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-button:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-button:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-button:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-button.md-default-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-button:not([disabled]) .md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-group:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-group:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-group:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-group:not([disabled]) .md-warn .md-checked .md-ink-ripple{color:rgba(255,87,34,0.87)}md-radio-button.md-default-theme:not([disabled]).md-warn .md-container .md-ripple, md-radio-button:not([disabled]).md-warn .md-container .md-ripple,md-radio-button.md-default-theme:not([disabled]) .md-warn .md-container .md-ripple, md-radio-button:not([disabled]) .md-warn .md-container .md-ripple,md-radio-group.md-default-theme:not([disabled]).md-warn .md-container .md-ripple, md-radio-group:not([disabled]).md-warn .md-container .md-ripple,md-radio-group.md-default-theme:not([disabled]) .md-warn .md-container .md-ripple, md-radio-group:not([disabled]) .md-warn .md-container .md-ripple{color:rgb(244,81,30)}md-radio-group.md-default-theme.md-focused:not(:empty) .md-checked.md-warn .md-container:before, md-radio-group.md-focused:not(:empty) .md-checked.md-warn .md-container:before,md-radio-group.md-default-theme.md-focused:not(:empty).md-warn .md-checked .md-container:before, md-radio-group.md-focused:not(:empty).md-warn .md-checked .md-container:before{background-color:rgba(255,87,34,0.26)}md-input-container md-select.md-default-theme .md-select-value span:first-child:after, md-input-container md-select .md-select-value span:first-child:after{color:rgb(221,44,0)}md-input-container.md-input-invalid md-select.md-default-theme .md-select-value, md-input-container.md-input-invalid md-select .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme .md-select-value span:first-child:after, md-select .md-select-value span:first-child:after{color:rgb(221,44,0)}md-select.md-default-theme.ng-invalid.ng-touched .md-select-value, md-select.ng-invalid.ng-touched .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme:not([disabled]):focus.md-warn .md-select-value, md-select:not([disabled]):focus.md-warn .md-select-value{border-bottom-color:rgb(255,87,34)}md-slider.md-default-theme.md-warn .md-focus-ring, md-slider.md-warn .md-focus-ring{background-color:rgba(255,171,145,0.38)}md-slider.md-default-theme.md-warn .md-track.md-track-fill, md-slider.md-warn .md-track.md-track-fill{background-color:rgb(255,87,34)}md-slider.md-default-theme.md-warn .md-thumb:after, md-slider.md-warn .md-thumb:after{border-color:rgb(255,87,34);background-color:rgb(255,87,34)}md-slider.md-default-theme.md-warn .md-sign, md-slider.md-warn .md-sign{background-color:rgb(255,87,34)}md-slider.md-default-theme.md-warn .md-sign:after, md-slider.md-warn .md-sign:after{border-top-color:rgb(255,87,34)}md-slider.md-default-theme.md-warn[md-vertical] .md-sign:after, md-slider.md-warn[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(255,87,34)}md-slider.md-default-theme.md-warn .md-thumb-text, md-slider.md-warn .md-thumb-text{color:rgb(255,255,255)}.md-subheader.md-default-theme.md-warn, .md-subheader.md-warn{color:rgb(255,87,34)}md-switch.md-default-theme.md-checked.md-warn .md-ink-ripple, md-switch.md-checked.md-warn .md-ink-ripple{color:rgb(255,87,34)}md-switch.md-default-theme.md-checked.md-warn .md-thumb, md-switch.md-checked.md-warn .md-thumb{background-color:rgb(255,87,34)}md-switch.md-default-theme.md-checked.md-warn .md-bar, md-switch.md-checked.md-warn .md-bar{background-color:rgba(255,87,34,0.5)}md-switch.md-default-theme.md-checked.md-warn.md-focused .md-thumb:before, md-switch.md-checked.md-warn.md-focused .md-thumb:before{background-color:rgba(255,87,34,0.26)}md-tabs.md-default-theme.md-warn>md-tabs-wrapper, md-tabs.md-warn>md-tabs-wrapper{background-color:rgb(255,87,34)}md-tabs.md-default-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-tabs.md-default-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgb(255,255,255)}md-tabs.md-default-theme.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper, md-toolbar.md-warn>md-tabs>md-tabs-wrapper{background-color:rgb(255,87,34)}md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-warn>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-warn>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-warn>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-warn>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-warn>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgb(255,255,255)}md-toolbar.md-warn>md-tabs.md-default-theme>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toast.md-default-theme .md-toast-content .md-button.md-highlight.md-warn, md-toast .md-toast-content .md-button.md-highlight.md-warn{color:rgb(255,87,34)}md-toolbar.md-default-theme:not(.md-menu-toolbar).md-warn, md-toolbar:not(.md-menu-toolbar).md-warn{background-color:rgb(255,87,34);color:rgb(255,255,255)} -.md-button.md-default-theme.md-hue-1.md-warn, .md-button.md-hue-1.md-warn{color:rgb(255,138,101)}.md-button.md-default-theme.md-hue-1.md-warn.md-fab, .md-button.md-hue-1.md-warn.md-fab,.md-button.md-default-theme.md-hue-1.md-warn.md-raised, .md-button.md-hue-1.md-warn.md-raised{color:rgba(0,0,0,0.87);background-color:rgb(255,138,101)}.md-button.md-default-theme.md-hue-1.md-warn.md-fab:not([disabled]) md-icon, .md-button.md-hue-1.md-warn.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-1.md-warn.md-raised:not([disabled]) md-icon, .md-button.md-hue-1.md-warn.md-raised:not([disabled]) md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1.md-warn.md-fab:not([disabled]).md-focused, .md-button.md-hue-1.md-warn.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1.md-warn.md-fab:not([disabled]):hover, .md-button.md-hue-1.md-warn.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-1.md-warn.md-raised:not([disabled]).md-focused, .md-button.md-hue-1.md-warn.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1.md-warn.md-raised:not([disabled]):hover, .md-button.md-hue-1.md-warn.md-raised:not([disabled]):hover{background-color:rgb(244,81,30)}.md-button.md-default-theme.md-hue-1.md-warn:not([disabled]) md-icon, .md-button.md-hue-1.md-warn:not([disabled]) md-icon{color:rgb(255,138,101)}._md a.md-default-theme.md-hue-1:not(.md-button).md-warn, ._md a.md-hue-1:not(.md-button).md-warn{color:rgb(255,138,101)}._md a.md-default-theme.md-hue-1:not(.md-button).md-warn:hover, ._md a.md-hue-1:not(.md-button).md-warn:hover{color:rgb(230,74,25)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-warn .md-ripple, md-checkbox.md-hue-1:not([disabled]).md-warn .md-ripple{color:rgb(244,81,30)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-warn .md-ink-ripple, md-checkbox.md-hue-1:not([disabled]).md-warn .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked .md-ink-ripple, md-checkbox.md-hue-1:not([disabled]).md-warn.md-checked .md-ink-ripple{color:rgba(255,138,101,0.87)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-warn:not(.md-checked) .md-icon, md-checkbox.md-hue-1:not([disabled]).md-warn:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked .md-icon, md-checkbox.md-hue-1:not([disabled]).md-warn.md-checked .md-icon{background-color:rgba(255,138,101,0.87)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before, md-checkbox.md-hue-1:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before{background-color:rgba(255,138,101,0.26)}md-checkbox.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked .md-icon:after, md-checkbox.md-hue-1:not([disabled]).md-warn.md-checked .md-icon:after{border-color:rgb(238,238,238)}.md-default-theme.md-hue-1 .md-datepicker-input-container.md-datepicker-invalid, .md-hue-1 .md-datepicker-input-container.md-datepicker-invalid,.md-warn .md-default-theme.md-hue-1 .md-datepicker-input-container.md-datepicker-focused, .md-warn .md-hue-1 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(221,44,0)}.md-default-theme.md-hue-1 .md-datepicker-open.md-warn .md-datepicker-calendar-icon, .md-hue-1 .md-datepicker-open.md-warn .md-datepicker-calendar-icon,.md-warn .md-default-theme.md-hue-1 .md-datepicker-open .md-datepicker-calendar-icon, .md-warn .md-hue-1 .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(221,44,0)}md-icon.md-default-theme.md-hue-1.md-warn, md-icon.md-hue-1.md-warn{color:rgb(255,138,101)}md-input-container.md-default-theme.md-hue-1 label.md-required:after, md-input-container.md-hue-1 label.md-required:after{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-1 .md-input-message-animation, md-input-container.md-hue-1 .md-input-message-animation,md-input-container.md-default-theme.md-hue-1 .md-input-messages-animation, md-input-container.md-hue-1 .md-input-messages-animation{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused.md-warn .md-input, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused.md-warn .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused.md-warn label, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused.md-warn label,md-input-container.md-default-theme.md-hue-1:not(.md-input-invalid).md-input-focused.md-warn md-icon, md-input-container.md-hue-1:not(.md-input-invalid).md-input-focused.md-warn md-icon{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-1.md-input-invalid .md-input, md-input-container.md-hue-1.md-input-invalid .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-1.md-input-invalid .md-char-counter, md-input-container.md-hue-1.md-input-invalid .md-char-counter,md-input-container.md-default-theme.md-hue-1.md-input-invalid .md-input-message-animation, md-input-container.md-hue-1.md-input-invalid .md-input-message-animation,md-input-container.md-default-theme.md-hue-1.md-input-invalid label, md-input-container.md-hue-1.md-input-invalid label{color:rgb(221,44,0)}md-nav-bar.md-default-theme.md-hue-1.md-warn>.md-nav-bar, md-nav-bar.md-hue-1.md-warn>.md-nav-bar{background-color:rgb(255,138,101)}md-nav-bar.md-default-theme.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-nav-bar.md-default-theme.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-1.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar, md-toolbar.md-warn>md-nav-bar.md-hue-1>.md-nav-bar{background-color:rgb(255,138,101)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-warn>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-warn>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar.md-hue-1>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-progress-circular.md-default-theme.md-hue-1.md-warn path, md-progress-circular.md-hue-1.md-warn path{stroke:rgb(255,138,101)}md-progress-linear.md-default-theme.md-hue-1.md-warn .md-container, md-progress-linear.md-hue-1.md-warn .md-container{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme.md-hue-1.md-warn .md-bar, md-progress-linear.md-hue-1.md-warn .md-bar{background-color:rgb(255,138,101)}md-progress-linear.md-default-theme.md-hue-1[md-mode=buffer].md-warn .md-bar1, md-progress-linear.md-hue-1[md-mode=buffer].md-warn .md-bar1{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme.md-hue-1[md-mode=buffer].md-warn .md-dashed:before, md-progress-linear.md-hue-1[md-mode=buffer].md-warn .md-dashed:before{background:radial-gradient(rgb(255,204,188) 0,rgb(255,204,188) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-warn .md-on, md-radio-button.md-hue-1:not([disabled]).md-warn .md-on,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-on, md-radio-button.md-hue-1:not([disabled]) .md-warn .md-on,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-warn .md-on, md-radio-group.md-hue-1:not([disabled]).md-warn .md-on,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-on, md-radio-group.md-hue-1:not([disabled]) .md-warn .md-on{background-color:rgba(255,138,101,0.87)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked .md-off, md-radio-button.md-hue-1:not([disabled]).md-warn.md-checked .md-off,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-warn.md-checked .md-off, md-radio-button.md-hue-1:not([disabled]) .md-warn.md-checked .md-off,md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-warn .md-checked .md-off, md-radio-button.md-hue-1:not([disabled]).md-warn .md-checked .md-off,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-checked .md-off, md-radio-button.md-hue-1:not([disabled]) .md-warn .md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked .md-off, md-radio-group.md-hue-1:not([disabled]).md-warn.md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-warn.md-checked .md-off, md-radio-group.md-hue-1:not([disabled]) .md-warn.md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-warn .md-checked .md-off, md-radio-group.md-hue-1:not([disabled]).md-warn .md-checked .md-off,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-checked .md-off, md-radio-group.md-hue-1:not([disabled]) .md-warn .md-checked .md-off{border-color:rgba(255,138,101,0.87)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-button.md-hue-1:not([disabled]) .md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-group.md-hue-1:not([disabled]) .md-warn .md-checked .md-ink-ripple{color:rgba(255,138,101,0.87)}md-radio-button.md-default-theme.md-hue-1:not([disabled]).md-warn .md-container .md-ripple, md-radio-button.md-hue-1:not([disabled]).md-warn .md-container .md-ripple,md-radio-button.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-container .md-ripple, md-radio-button.md-hue-1:not([disabled]) .md-warn .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]).md-warn .md-container .md-ripple, md-radio-group.md-hue-1:not([disabled]).md-warn .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-1:not([disabled]) .md-warn .md-container .md-ripple, md-radio-group.md-hue-1:not([disabled]) .md-warn .md-container .md-ripple{color:rgb(244,81,30)}md-radio-group.md-default-theme.md-hue-1.md-focused:not(:empty) .md-checked.md-warn .md-container:before, md-radio-group.md-hue-1.md-focused:not(:empty) .md-checked.md-warn .md-container:before,md-radio-group.md-default-theme.md-hue-1.md-focused:not(:empty).md-warn .md-checked .md-container:before, md-radio-group.md-hue-1.md-focused:not(:empty).md-warn .md-checked .md-container:before{background-color:rgba(255,138,101,0.26)}md-input-container md-select.md-default-theme.md-hue-1 .md-select-value span:first-child:after, md-input-container md-select.md-hue-1 .md-select-value span:first-child:after{color:rgb(221,44,0)}md-input-container.md-input-invalid md-select.md-default-theme.md-hue-1 .md-select-value, md-input-container.md-input-invalid md-select.md-hue-1 .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme.md-hue-1 .md-select-value span:first-child:after, md-select.md-hue-1 .md-select-value span:first-child:after{color:rgb(221,44,0)}md-select.md-default-theme.md-hue-1.ng-invalid.ng-touched .md-select-value, md-select.md-hue-1.ng-invalid.ng-touched .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme.md-hue-1:not([disabled]):focus.md-warn .md-select-value, md-select.md-hue-1:not([disabled]):focus.md-warn .md-select-value{border-bottom-color:rgb(255,138,101)}md-slider.md-default-theme.md-hue-1.md-warn .md-focus-ring, md-slider.md-hue-1.md-warn .md-focus-ring{background-color:rgba(255,171,145,0.38)}md-slider.md-default-theme.md-hue-1.md-warn .md-track.md-track-fill, md-slider.md-hue-1.md-warn .md-track.md-track-fill{background-color:rgb(255,138,101)}md-slider.md-default-theme.md-hue-1.md-warn .md-thumb:after, md-slider.md-hue-1.md-warn .md-thumb:after{border-color:rgb(255,138,101);background-color:rgb(255,138,101)}md-slider.md-default-theme.md-hue-1.md-warn .md-sign, md-slider.md-hue-1.md-warn .md-sign{background-color:rgb(255,138,101)}md-slider.md-default-theme.md-hue-1.md-warn .md-sign:after, md-slider.md-hue-1.md-warn .md-sign:after{border-top-color:rgb(255,138,101)}md-slider.md-default-theme.md-hue-1.md-warn[md-vertical] .md-sign:after, md-slider.md-hue-1.md-warn[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(255,138,101)}md-slider.md-default-theme.md-hue-1.md-warn .md-thumb-text, md-slider.md-hue-1.md-warn .md-thumb-text{color:rgba(0,0,0,0.87)}.md-subheader.md-default-theme.md-hue-1.md-warn, .md-subheader.md-hue-1.md-warn{color:rgb(255,138,101)}md-switch.md-default-theme.md-hue-1.md-checked.md-warn .md-ink-ripple, md-switch.md-hue-1.md-checked.md-warn .md-ink-ripple{color:rgb(255,138,101)}md-switch.md-default-theme.md-hue-1.md-checked.md-warn .md-thumb, md-switch.md-hue-1.md-checked.md-warn .md-thumb{background-color:rgb(255,138,101)}md-switch.md-default-theme.md-hue-1.md-checked.md-warn .md-bar, md-switch.md-hue-1.md-checked.md-warn .md-bar{background-color:rgba(255,138,101,0.5)}md-switch.md-default-theme.md-hue-1.md-checked.md-warn.md-focused .md-thumb:before, md-switch.md-hue-1.md-checked.md-warn.md-focused .md-thumb:before{background-color:rgba(255,138,101,0.26)}md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper, md-tabs.md-hue-1.md-warn>md-tabs-wrapper{background-color:rgb(255,138,101)}md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-tabs.md-default-theme.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-1.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper, md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper{background-color:rgb(255,138,101)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs.md-hue-1>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toast.md-default-theme.md-hue-1 .md-toast-content .md-button.md-highlight.md-warn, md-toast.md-hue-1 .md-toast-content .md-button.md-highlight.md-warn{color:rgb(255,138,101)}md-toolbar.md-default-theme.md-hue-1:not(.md-menu-toolbar).md-warn, md-toolbar.md-hue-1:not(.md-menu-toolbar).md-warn{background-color:rgb(255,138,101);color:rgba(0,0,0,0.87)} -.md-button.md-default-theme.md-hue-2.md-warn, .md-button.md-hue-2.md-warn{color:rgb(216,67,21)}.md-button.md-default-theme.md-hue-2.md-warn.md-fab, .md-button.md-hue-2.md-warn.md-fab,.md-button.md-default-theme.md-hue-2.md-warn.md-raised, .md-button.md-hue-2.md-warn.md-raised{color:rgb(255,255,255);background-color:rgb(216,67,21)}.md-button.md-default-theme.md-hue-2.md-warn.md-fab:not([disabled]) md-icon, .md-button.md-hue-2.md-warn.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-2.md-warn.md-raised:not([disabled]) md-icon, .md-button.md-hue-2.md-warn.md-raised:not([disabled]) md-icon{color:rgb(255,255,255)}.md-button.md-default-theme.md-hue-2.md-warn.md-fab:not([disabled]).md-focused, .md-button.md-hue-2.md-warn.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2.md-warn.md-fab:not([disabled]):hover, .md-button.md-hue-2.md-warn.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-2.md-warn.md-raised:not([disabled]).md-focused, .md-button.md-hue-2.md-warn.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2.md-warn.md-raised:not([disabled]):hover, .md-button.md-hue-2.md-warn.md-raised:not([disabled]):hover{background-color:rgb(244,81,30)}.md-button.md-default-theme.md-hue-2.md-warn:not([disabled]) md-icon, .md-button.md-hue-2.md-warn:not([disabled]) md-icon{color:rgb(216,67,21)}._md a.md-default-theme.md-hue-2:not(.md-button).md-warn, ._md a.md-hue-2:not(.md-button).md-warn{color:rgb(216,67,21)}._md a.md-default-theme.md-hue-2:not(.md-button).md-warn:hover, ._md a.md-hue-2:not(.md-button).md-warn:hover{color:rgb(230,74,25)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-warn .md-ripple, md-checkbox.md-hue-2:not([disabled]).md-warn .md-ripple{color:rgb(244,81,30)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-warn .md-ink-ripple, md-checkbox.md-hue-2:not([disabled]).md-warn .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked .md-ink-ripple, md-checkbox.md-hue-2:not([disabled]).md-warn.md-checked .md-ink-ripple{color:rgba(216,67,21,0.87)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-warn:not(.md-checked) .md-icon, md-checkbox.md-hue-2:not([disabled]).md-warn:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked .md-icon, md-checkbox.md-hue-2:not([disabled]).md-warn.md-checked .md-icon{background-color:rgba(216,67,21,0.87)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before, md-checkbox.md-hue-2:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before{background-color:rgba(216,67,21,0.26)}md-checkbox.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked .md-icon:after, md-checkbox.md-hue-2:not([disabled]).md-warn.md-checked .md-icon:after{border-color:rgb(238,238,238)}.md-default-theme.md-hue-2 .md-datepicker-input-container.md-datepicker-invalid, .md-hue-2 .md-datepicker-input-container.md-datepicker-invalid,.md-warn .md-default-theme.md-hue-2 .md-datepicker-input-container.md-datepicker-focused, .md-warn .md-hue-2 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(221,44,0)}.md-default-theme.md-hue-2 .md-datepicker-open.md-warn .md-datepicker-calendar-icon, .md-hue-2 .md-datepicker-open.md-warn .md-datepicker-calendar-icon,.md-warn .md-default-theme.md-hue-2 .md-datepicker-open .md-datepicker-calendar-icon, .md-warn .md-hue-2 .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(221,44,0)}md-icon.md-default-theme.md-hue-2.md-warn, md-icon.md-hue-2.md-warn{color:rgb(216,67,21)}md-input-container.md-default-theme.md-hue-2 label.md-required:after, md-input-container.md-hue-2 label.md-required:after{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-2 .md-input-message-animation, md-input-container.md-hue-2 .md-input-message-animation,md-input-container.md-default-theme.md-hue-2 .md-input-messages-animation, md-input-container.md-hue-2 .md-input-messages-animation{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused.md-warn .md-input, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused.md-warn .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused.md-warn label, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused.md-warn label,md-input-container.md-default-theme.md-hue-2:not(.md-input-invalid).md-input-focused.md-warn md-icon, md-input-container.md-hue-2:not(.md-input-invalid).md-input-focused.md-warn md-icon{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-2.md-input-invalid .md-input, md-input-container.md-hue-2.md-input-invalid .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-2.md-input-invalid .md-char-counter, md-input-container.md-hue-2.md-input-invalid .md-char-counter,md-input-container.md-default-theme.md-hue-2.md-input-invalid .md-input-message-animation, md-input-container.md-hue-2.md-input-invalid .md-input-message-animation,md-input-container.md-default-theme.md-hue-2.md-input-invalid label, md-input-container.md-hue-2.md-input-invalid label{color:rgb(221,44,0)}md-nav-bar.md-default-theme.md-hue-2.md-warn>.md-nav-bar, md-nav-bar.md-hue-2.md-warn>.md-nav-bar{background-color:rgb(216,67,21)}md-nav-bar.md-default-theme.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-nav-bar.md-default-theme.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgb(255,255,255)}md-nav-bar.md-default-theme.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-2.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar, md-toolbar.md-warn>md-nav-bar.md-hue-2>.md-nav-bar{background-color:rgb(216,67,21)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-warn>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-warn>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgb(255,255,255)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar.md-hue-2>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(255,255,255,0.1)}md-progress-circular.md-default-theme.md-hue-2.md-warn path, md-progress-circular.md-hue-2.md-warn path{stroke:rgb(216,67,21)}md-progress-linear.md-default-theme.md-hue-2.md-warn .md-container, md-progress-linear.md-hue-2.md-warn .md-container{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme.md-hue-2.md-warn .md-bar, md-progress-linear.md-hue-2.md-warn .md-bar{background-color:rgb(216,67,21)}md-progress-linear.md-default-theme.md-hue-2[md-mode=buffer].md-warn .md-bar1, md-progress-linear.md-hue-2[md-mode=buffer].md-warn .md-bar1{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme.md-hue-2[md-mode=buffer].md-warn .md-dashed:before, md-progress-linear.md-hue-2[md-mode=buffer].md-warn .md-dashed:before{background:radial-gradient(rgb(255,204,188) 0,rgb(255,204,188) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-warn .md-on, md-radio-button.md-hue-2:not([disabled]).md-warn .md-on,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-on, md-radio-button.md-hue-2:not([disabled]) .md-warn .md-on,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-warn .md-on, md-radio-group.md-hue-2:not([disabled]).md-warn .md-on,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-on, md-radio-group.md-hue-2:not([disabled]) .md-warn .md-on{background-color:rgba(216,67,21,0.87)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked .md-off, md-radio-button.md-hue-2:not([disabled]).md-warn.md-checked .md-off,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-warn.md-checked .md-off, md-radio-button.md-hue-2:not([disabled]) .md-warn.md-checked .md-off,md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-warn .md-checked .md-off, md-radio-button.md-hue-2:not([disabled]).md-warn .md-checked .md-off,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-checked .md-off, md-radio-button.md-hue-2:not([disabled]) .md-warn .md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked .md-off, md-radio-group.md-hue-2:not([disabled]).md-warn.md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-warn.md-checked .md-off, md-radio-group.md-hue-2:not([disabled]) .md-warn.md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-warn .md-checked .md-off, md-radio-group.md-hue-2:not([disabled]).md-warn .md-checked .md-off,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-checked .md-off, md-radio-group.md-hue-2:not([disabled]) .md-warn .md-checked .md-off{border-color:rgba(216,67,21,0.87)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-button.md-hue-2:not([disabled]) .md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-group.md-hue-2:not([disabled]) .md-warn .md-checked .md-ink-ripple{color:rgba(216,67,21,0.87)}md-radio-button.md-default-theme.md-hue-2:not([disabled]).md-warn .md-container .md-ripple, md-radio-button.md-hue-2:not([disabled]).md-warn .md-container .md-ripple,md-radio-button.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-container .md-ripple, md-radio-button.md-hue-2:not([disabled]) .md-warn .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]).md-warn .md-container .md-ripple, md-radio-group.md-hue-2:not([disabled]).md-warn .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-2:not([disabled]) .md-warn .md-container .md-ripple, md-radio-group.md-hue-2:not([disabled]) .md-warn .md-container .md-ripple{color:rgb(244,81,30)}md-radio-group.md-default-theme.md-hue-2.md-focused:not(:empty) .md-checked.md-warn .md-container:before, md-radio-group.md-hue-2.md-focused:not(:empty) .md-checked.md-warn .md-container:before,md-radio-group.md-default-theme.md-hue-2.md-focused:not(:empty).md-warn .md-checked .md-container:before, md-radio-group.md-hue-2.md-focused:not(:empty).md-warn .md-checked .md-container:before{background-color:rgba(216,67,21,0.26)}md-input-container md-select.md-default-theme.md-hue-2 .md-select-value span:first-child:after, md-input-container md-select.md-hue-2 .md-select-value span:first-child:after{color:rgb(221,44,0)}md-input-container.md-input-invalid md-select.md-default-theme.md-hue-2 .md-select-value, md-input-container.md-input-invalid md-select.md-hue-2 .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme.md-hue-2 .md-select-value span:first-child:after, md-select.md-hue-2 .md-select-value span:first-child:after{color:rgb(221,44,0)}md-select.md-default-theme.md-hue-2.ng-invalid.ng-touched .md-select-value, md-select.md-hue-2.ng-invalid.ng-touched .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme.md-hue-2:not([disabled]):focus.md-warn .md-select-value, md-select.md-hue-2:not([disabled]):focus.md-warn .md-select-value{border-bottom-color:rgb(216,67,21)}md-slider.md-default-theme.md-hue-2.md-warn .md-focus-ring, md-slider.md-hue-2.md-warn .md-focus-ring{background-color:rgba(255,171,145,0.38)}md-slider.md-default-theme.md-hue-2.md-warn .md-track.md-track-fill, md-slider.md-hue-2.md-warn .md-track.md-track-fill{background-color:rgb(216,67,21)}md-slider.md-default-theme.md-hue-2.md-warn .md-thumb:after, md-slider.md-hue-2.md-warn .md-thumb:after{border-color:rgb(216,67,21);background-color:rgb(216,67,21)}md-slider.md-default-theme.md-hue-2.md-warn .md-sign, md-slider.md-hue-2.md-warn .md-sign{background-color:rgb(216,67,21)}md-slider.md-default-theme.md-hue-2.md-warn .md-sign:after, md-slider.md-hue-2.md-warn .md-sign:after{border-top-color:rgb(216,67,21)}md-slider.md-default-theme.md-hue-2.md-warn[md-vertical] .md-sign:after, md-slider.md-hue-2.md-warn[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(216,67,21)}md-slider.md-default-theme.md-hue-2.md-warn .md-thumb-text, md-slider.md-hue-2.md-warn .md-thumb-text{color:rgb(255,255,255)}.md-subheader.md-default-theme.md-hue-2.md-warn, .md-subheader.md-hue-2.md-warn{color:rgb(216,67,21)}md-switch.md-default-theme.md-hue-2.md-checked.md-warn .md-ink-ripple, md-switch.md-hue-2.md-checked.md-warn .md-ink-ripple{color:rgb(216,67,21)}md-switch.md-default-theme.md-hue-2.md-checked.md-warn .md-thumb, md-switch.md-hue-2.md-checked.md-warn .md-thumb{background-color:rgb(216,67,21)}md-switch.md-default-theme.md-hue-2.md-checked.md-warn .md-bar, md-switch.md-hue-2.md-checked.md-warn .md-bar{background-color:rgba(216,67,21,0.5)}md-switch.md-default-theme.md-hue-2.md-checked.md-warn.md-focused .md-thumb:before, md-switch.md-hue-2.md-checked.md-warn.md-focused .md-thumb:before{background-color:rgba(216,67,21,0.26)}md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper, md-tabs.md-hue-2.md-warn>md-tabs-wrapper{background-color:rgb(216,67,21)}md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgb(255,255,255)}md-tabs.md-default-theme.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-2.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper, md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper{background-color:rgb(216,67,21)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgb(255,255,255)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs.md-hue-2>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(255,255,255,0.1)}md-toast.md-default-theme.md-hue-2 .md-toast-content .md-button.md-highlight.md-warn, md-toast.md-hue-2 .md-toast-content .md-button.md-highlight.md-warn{color:rgb(216,67,21)}md-toolbar.md-default-theme.md-hue-2:not(.md-menu-toolbar).md-warn, md-toolbar.md-hue-2:not(.md-menu-toolbar).md-warn{background-color:rgb(216,67,21);color:rgb(255,255,255)} -.md-button.md-default-theme.md-hue-3.md-warn, .md-button.md-hue-3.md-warn{color:rgb(255,158,128)}.md-button.md-default-theme.md-hue-3.md-warn.md-fab, .md-button.md-hue-3.md-warn.md-fab,.md-button.md-default-theme.md-hue-3.md-warn.md-raised, .md-button.md-hue-3.md-warn.md-raised{color:rgba(0,0,0,0.87);background-color:rgb(255,158,128)}.md-button.md-default-theme.md-hue-3.md-warn.md-fab:not([disabled]) md-icon, .md-button.md-hue-3.md-warn.md-fab:not([disabled]) md-icon,.md-button.md-default-theme.md-hue-3.md-warn.md-raised:not([disabled]) md-icon, .md-button.md-hue-3.md-warn.md-raised:not([disabled]) md-icon{color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3.md-warn.md-fab:not([disabled]).md-focused, .md-button.md-hue-3.md-warn.md-fab:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3.md-warn.md-fab:not([disabled]):hover, .md-button.md-hue-3.md-warn.md-fab:not([disabled]):hover,.md-button.md-default-theme.md-hue-3.md-warn.md-raised:not([disabled]).md-focused, .md-button.md-hue-3.md-warn.md-raised:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3.md-warn.md-raised:not([disabled]):hover, .md-button.md-hue-3.md-warn.md-raised:not([disabled]):hover{background-color:rgb(244,81,30)}.md-button.md-default-theme.md-hue-3.md-warn:not([disabled]) md-icon, .md-button.md-hue-3.md-warn:not([disabled]) md-icon{color:rgb(255,158,128)}._md a.md-default-theme.md-hue-3:not(.md-button).md-warn, ._md a.md-hue-3:not(.md-button).md-warn{color:rgb(255,158,128)}._md a.md-default-theme.md-hue-3:not(.md-button).md-warn:hover, ._md a.md-hue-3:not(.md-button).md-warn:hover{color:rgb(230,74,25)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-warn .md-ripple, md-checkbox.md-hue-3:not([disabled]).md-warn .md-ripple{color:rgb(244,81,30)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-warn .md-ink-ripple, md-checkbox.md-hue-3:not([disabled]).md-warn .md-ink-ripple{color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked .md-ink-ripple, md-checkbox.md-hue-3:not([disabled]).md-warn.md-checked .md-ink-ripple{color:rgba(255,158,128,0.87)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-warn:not(.md-checked) .md-icon, md-checkbox.md-hue-3:not([disabled]).md-warn:not(.md-checked) .md-icon{border-color:rgba(0,0,0,0.54)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked .md-icon, md-checkbox.md-hue-3:not([disabled]).md-warn.md-checked .md-icon{background-color:rgba(255,158,128,0.87)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before, md-checkbox.md-hue-3:not([disabled]).md-warn.md-checked.md-focused:not([disabled]) .md-container:before{background-color:rgba(255,158,128,0.26)}md-checkbox.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked .md-icon:after, md-checkbox.md-hue-3:not([disabled]).md-warn.md-checked .md-icon:after{border-color:rgb(238,238,238)}.md-default-theme.md-hue-3 .md-datepicker-input-container.md-datepicker-invalid, .md-hue-3 .md-datepicker-input-container.md-datepicker-invalid,.md-warn .md-default-theme.md-hue-3 .md-datepicker-input-container.md-datepicker-focused, .md-warn .md-hue-3 .md-datepicker-input-container.md-datepicker-focused{border-bottom-color:rgb(221,44,0)}.md-default-theme.md-hue-3 .md-datepicker-open.md-warn .md-datepicker-calendar-icon, .md-hue-3 .md-datepicker-open.md-warn .md-datepicker-calendar-icon,.md-warn .md-default-theme.md-hue-3 .md-datepicker-open .md-datepicker-calendar-icon, .md-warn .md-hue-3 .md-datepicker-open .md-datepicker-calendar-icon{color:rgb(221,44,0)}md-icon.md-default-theme.md-hue-3.md-warn, md-icon.md-hue-3.md-warn{color:rgb(255,158,128)}md-input-container.md-default-theme.md-hue-3 label.md-required:after, md-input-container.md-hue-3 label.md-required:after{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-3 .md-input-message-animation, md-input-container.md-hue-3 .md-input-message-animation,md-input-container.md-default-theme.md-hue-3 .md-input-messages-animation, md-input-container.md-hue-3 .md-input-messages-animation{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused.md-warn .md-input, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused.md-warn .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused.md-warn label, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused.md-warn label,md-input-container.md-default-theme.md-hue-3:not(.md-input-invalid).md-input-focused.md-warn md-icon, md-input-container.md-hue-3:not(.md-input-invalid).md-input-focused.md-warn md-icon{color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-3.md-input-invalid .md-input, md-input-container.md-hue-3.md-input-invalid .md-input{border-color:rgb(221,44,0)}md-input-container.md-default-theme.md-hue-3.md-input-invalid .md-char-counter, md-input-container.md-hue-3.md-input-invalid .md-char-counter,md-input-container.md-default-theme.md-hue-3.md-input-invalid .md-input-message-animation, md-input-container.md-hue-3.md-input-invalid .md-input-message-animation,md-input-container.md-default-theme.md-hue-3.md-input-invalid label, md-input-container.md-hue-3.md-input-invalid label{color:rgb(221,44,0)}md-nav-bar.md-default-theme.md-hue-3.md-warn>.md-nav-bar, md-nav-bar.md-hue-3.md-warn>.md-nav-bar{background-color:rgb(255,158,128)}md-nav-bar.md-default-theme.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button, md-nav-bar.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-nav-bar.md-default-theme.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button.md-active, md-nav-bar.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button.md-active,md-nav-bar.md-default-theme.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused, md-nav-bar.md-hue-3.md-warn>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar, md-toolbar.md-warn>md-nav-bar.md-hue-3>.md-nav-bar{background-color:rgb(255,158,128)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button, md-toolbar.md-warn>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button{color:rgb(255,204,188)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-active, md-toolbar.md-warn>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-active,md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused{color:rgba(0,0,0,0.87)}md-toolbar.md-warn>md-nav-bar.md-default-theme.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused, md-toolbar.md-warn>md-nav-bar.md-hue-3>.md-nav-bar .md-button._md-nav-button.md-focused{background:rgba(0,0,0,0.1)}md-progress-circular.md-default-theme.md-hue-3.md-warn path, md-progress-circular.md-hue-3.md-warn path{stroke:rgb(255,158,128)}md-progress-linear.md-default-theme.md-hue-3.md-warn .md-container, md-progress-linear.md-hue-3.md-warn .md-container{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme.md-hue-3.md-warn .md-bar, md-progress-linear.md-hue-3.md-warn .md-bar{background-color:rgb(255,158,128)}md-progress-linear.md-default-theme.md-hue-3[md-mode=buffer].md-warn .md-bar1, md-progress-linear.md-hue-3[md-mode=buffer].md-warn .md-bar1{background-color:rgb(255,204,188)}md-progress-linear.md-default-theme.md-hue-3[md-mode=buffer].md-warn .md-dashed:before, md-progress-linear.md-hue-3[md-mode=buffer].md-warn .md-dashed:before{background:radial-gradient(rgb(255,204,188) 0,rgb(255,204,188) 16%,transparent 42%)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-warn .md-on, md-radio-button.md-hue-3:not([disabled]).md-warn .md-on,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-on, md-radio-button.md-hue-3:not([disabled]) .md-warn .md-on,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-warn .md-on, md-radio-group.md-hue-3:not([disabled]).md-warn .md-on,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-on, md-radio-group.md-hue-3:not([disabled]) .md-warn .md-on{background-color:rgba(255,158,128,0.87)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked .md-off, md-radio-button.md-hue-3:not([disabled]).md-warn.md-checked .md-off,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-warn.md-checked .md-off, md-radio-button.md-hue-3:not([disabled]) .md-warn.md-checked .md-off,md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-warn .md-checked .md-off, md-radio-button.md-hue-3:not([disabled]).md-warn .md-checked .md-off,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-checked .md-off, md-radio-button.md-hue-3:not([disabled]) .md-warn .md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked .md-off, md-radio-group.md-hue-3:not([disabled]).md-warn.md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-warn.md-checked .md-off, md-radio-group.md-hue-3:not([disabled]) .md-warn.md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-warn .md-checked .md-off, md-radio-group.md-hue-3:not([disabled]).md-warn .md-checked .md-off,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-checked .md-off, md-radio-group.md-hue-3:not([disabled]) .md-warn .md-checked .md-off{border-color:rgba(255,158,128,0.87)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-button.md-hue-3:not([disabled]) .md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-warn.md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]).md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-warn.md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]) .md-warn.md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-warn .md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]).md-warn .md-checked .md-ink-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-checked .md-ink-ripple, md-radio-group.md-hue-3:not([disabled]) .md-warn .md-checked .md-ink-ripple{color:rgba(255,158,128,0.87)}md-radio-button.md-default-theme.md-hue-3:not([disabled]).md-warn .md-container .md-ripple, md-radio-button.md-hue-3:not([disabled]).md-warn .md-container .md-ripple,md-radio-button.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-container .md-ripple, md-radio-button.md-hue-3:not([disabled]) .md-warn .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]).md-warn .md-container .md-ripple, md-radio-group.md-hue-3:not([disabled]).md-warn .md-container .md-ripple,md-radio-group.md-default-theme.md-hue-3:not([disabled]) .md-warn .md-container .md-ripple, md-radio-group.md-hue-3:not([disabled]) .md-warn .md-container .md-ripple{color:rgb(244,81,30)}md-radio-group.md-default-theme.md-hue-3.md-focused:not(:empty) .md-checked.md-warn .md-container:before, md-radio-group.md-hue-3.md-focused:not(:empty) .md-checked.md-warn .md-container:before,md-radio-group.md-default-theme.md-hue-3.md-focused:not(:empty).md-warn .md-checked .md-container:before, md-radio-group.md-hue-3.md-focused:not(:empty).md-warn .md-checked .md-container:before{background-color:rgba(255,158,128,0.26)}md-input-container md-select.md-default-theme.md-hue-3 .md-select-value span:first-child:after, md-input-container md-select.md-hue-3 .md-select-value span:first-child:after{color:rgb(221,44,0)}md-input-container.md-input-invalid md-select.md-default-theme.md-hue-3 .md-select-value, md-input-container.md-input-invalid md-select.md-hue-3 .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme.md-hue-3 .md-select-value span:first-child:after, md-select.md-hue-3 .md-select-value span:first-child:after{color:rgb(221,44,0)}md-select.md-default-theme.md-hue-3.ng-invalid.ng-touched .md-select-value, md-select.md-hue-3.ng-invalid.ng-touched .md-select-value{color:rgb(221,44,0)!important;border-bottom-color:rgb(221,44,0)!important}md-select.md-default-theme.md-hue-3:not([disabled]):focus.md-warn .md-select-value, md-select.md-hue-3:not([disabled]):focus.md-warn .md-select-value{border-bottom-color:rgb(255,158,128)}md-slider.md-default-theme.md-hue-3.md-warn .md-focus-ring, md-slider.md-hue-3.md-warn .md-focus-ring{background-color:rgba(255,171,145,0.38)}md-slider.md-default-theme.md-hue-3.md-warn .md-track.md-track-fill, md-slider.md-hue-3.md-warn .md-track.md-track-fill{background-color:rgb(255,158,128)}md-slider.md-default-theme.md-hue-3.md-warn .md-thumb:after, md-slider.md-hue-3.md-warn .md-thumb:after{border-color:rgb(255,158,128);background-color:rgb(255,158,128)}md-slider.md-default-theme.md-hue-3.md-warn .md-sign, md-slider.md-hue-3.md-warn .md-sign{background-color:rgb(255,158,128)}md-slider.md-default-theme.md-hue-3.md-warn .md-sign:after, md-slider.md-hue-3.md-warn .md-sign:after{border-top-color:rgb(255,158,128)}md-slider.md-default-theme.md-hue-3.md-warn[md-vertical] .md-sign:after, md-slider.md-hue-3.md-warn[md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(255,158,128)}md-slider.md-default-theme.md-hue-3.md-warn .md-thumb-text, md-slider.md-hue-3.md-warn .md-thumb-text{color:rgba(0,0,0,0.87)}.md-subheader.md-default-theme.md-hue-3.md-warn, .md-subheader.md-hue-3.md-warn{color:rgb(255,158,128)}md-switch.md-default-theme.md-hue-3.md-checked.md-warn .md-ink-ripple, md-switch.md-hue-3.md-checked.md-warn .md-ink-ripple{color:rgb(255,158,128)}md-switch.md-default-theme.md-hue-3.md-checked.md-warn .md-thumb, md-switch.md-hue-3.md-checked.md-warn .md-thumb{background-color:rgb(255,158,128)}md-switch.md-default-theme.md-hue-3.md-checked.md-warn .md-bar, md-switch.md-hue-3.md-checked.md-warn .md-bar{background-color:rgba(255,158,128,0.5)}md-switch.md-default-theme.md-hue-3.md-checked.md-warn.md-focused .md-thumb:before, md-switch.md-hue-3.md-checked.md-warn.md-focused .md-thumb:before{background-color:rgba(255,158,128,0.26)}md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper, md-tabs.md-hue-3.md-warn>md-tabs-wrapper{background-color:rgb(255,158,128)}md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-tabs.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-tabs.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-tabs.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-tabs.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-tabs.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-tabs.md-default-theme.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-tabs.md-hue-3.md-warn>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper, md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper{background-color:rgb(255,158,128)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]), md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]),md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon, md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]) md-icon{color:rgb(255,204,188)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active, md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon, md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-active md-icon,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused,md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon, md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused md-icon{color:rgba(0,0,0,0.87)}md-toolbar.md-warn>md-tabs.md-default-theme.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused, md-toolbar.md-warn>md-tabs.md-hue-3>md-tabs-wrapper>md-tabs-canvas>md-pagination-wrapper>md-tab-item:not([disabled]).md-focused{background:rgba(0,0,0,0.1)}md-toast.md-default-theme.md-hue-3 .md-toast-content .md-button.md-highlight.md-warn, md-toast.md-hue-3 .md-toast-content .md-button.md-highlight.md-warn{color:rgb(255,158,128)}md-toolbar.md-default-theme.md-hue-3:not(.md-menu-toolbar).md-warn, md-toolbar.md-hue-3:not(.md-menu-toolbar).md-warn{background-color:rgb(255,158,128);color:rgba(0,0,0,0.87)} -md-autocomplete.md-default-theme, md-autocomplete{background:rgb(245,245,245)}md-autocomplete.md-default-theme[disabled]:not([md-floating-label]), md-autocomplete[disabled]:not([md-floating-label]){background:rgb(238,238,238)}md-autocomplete.md-default-theme button md-icon path, md-autocomplete button md-icon path{fill:rgb(117,117,117)}md-autocomplete.md-default-theme button:after, md-autocomplete button:after{background:rgba(117,117,117,0.3)}.md-autocomplete-suggestions-container.md-default-theme, .md-autocomplete-suggestions-container{background:rgb(245,245,245)}.md-autocomplete-suggestions-container.md-default-theme li.selected, .md-autocomplete-suggestions-container li.selected,.md-autocomplete-suggestions-container.md-default-theme li:hover, .md-autocomplete-suggestions-container li:hover{background:rgba(158,158,158,0.18)}md-backdrop{background-color:rgba(33,33,33,0.0)}md-backdrop.md-opaque.md-default-theme, md-backdrop.md-opaque{background-color:rgba(33,33,33,1.0)}md-bottom-sheet.md-default-theme, md-bottom-sheet{background-color:rgb(250,250,250);border-top-color:rgb(224,224,224)}md-bottom-sheet.md-default-theme .md-subheader, md-bottom-sheet .md-subheader{background-color:rgb(250,250,250);color:rgba(0,0,0,0.87)}.md-button.md-default-theme:not([disabled]).md-focused, .md-button:not([disabled]).md-focused,.md-button.md-default-theme:not([disabled]):hover, .md-button:not([disabled]):hover{background-color:rgba(158,158,158,0.2)}.md-button.md-default-theme:not([disabled]).md-icon-button:hover, .md-button:not([disabled]).md-icon-button:hover{background-color:transparent}.md-button.md-default-theme.md-raised, .md-button.md-raised{color:rgb(33,33,33);background-color:rgb(250,250,250)}.md-button.md-default-theme.md-raised:not([disabled]) md-icon, .md-button.md-raised:not([disabled]) md-icon{color:rgb(33,33,33)}.md-button.md-default-theme.md-raised:not([disabled]):hover, .md-button.md-raised:not([disabled]):hover{background-color:rgb(250,250,250)}.md-button.md-default-theme.md-raised:not([disabled]).md-focused, .md-button.md-raised:not([disabled]).md-focused{background-color:rgb(238,238,238)}.md-button.md-default-theme.md-fab[disabled], .md-button.md-fab[disabled],.md-button.md-default-theme.md-raised[disabled], .md-button.md-raised[disabled]{background-color:rgba(0,0,0,0.12)}.md-button.md-default-theme[disabled], .md-button[disabled]{background-color:transparent}md-card.md-default-theme, md-card{color:rgba(0,0,0,0.87);background-color:rgb(245,245,245);border-radius:2px}md-card.md-default-theme md-card-header md-card-avatar md-icon, md-card md-card-header md-card-avatar md-icon{color:rgb(250,250,250);background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-checked .md-ripple, md-checkbox.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme[disabled].md-checked .md-icon, md-checkbox[disabled].md-checked .md-icon{background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme[disabled].md-checked .md-icon:after, md-checkbox[disabled].md-checked .md-icon:after{border-color:rgb(238,238,238)}md-chips.md-default-theme md-chip, md-chips md-chip{background:rgb(224,224,224);color:rgb(66,66,66)}md-chips.md-default-theme md-chip md-icon, md-chips md-chip md-icon{color:rgb(97,97,97)}md-chips.md-default-theme md-chip._md-chip-editing, md-chips md-chip._md-chip-editing{background:transparent;color:rgb(66,66,66)}md-chips.md-default-theme md-chip-remove .md-button md-icon path, md-chips md-chip-remove .md-button md-icon path{fill:rgb(158,158,158)}.md-contact-suggestion span.md-contact-email{color:rgb(189,189,189)}md-content.md-default-theme, md-content{color:rgba(0,0,0,0.87);background-color:rgb(250,250,250)}.md-default-theme .md-calendar, .md-calendar{background:rgb(245,245,245);color:rgba(0,0,0,0.87)}.md-default-theme .md-calendar tr:last-child td, .md-calendar tr:last-child td{border-bottom-color:rgb(238,238,238)}.md-default-theme .md-calendar-day-header, .md-calendar-day-header{background:rgba(158,158,158,0.32);color:rgba(0,0,0,0.87)}.md-calendar-date.md-focus .md-default-theme .md-calendar-date-selection-indicator, .md-calendar-date.md-focus .md-calendar-date-selection-indicator,.md-default-theme .md-calendar-date-selection-indicator:hover, .md-calendar-date-selection-indicator:hover{background:rgba(158,158,158,0.32)}.md-default-theme .md-datepicker-calendar-pane, .md-datepicker-calendar-pane{border-color:rgb(245,245,245)}.md-default-theme .md-datepicker-calendar, .md-datepicker-calendar{background:rgb(245,245,245)}.md-default-theme .md-datepicker-input-mask-opaque, .md-datepicker-input-mask-opaque{box-shadow:0 0 0 9999px rgb(245,245,245)}.md-default-theme .md-datepicker-open .md-datepicker-input-container, .md-datepicker-open .md-datepicker-input-container{background:rgb(245,245,245)}md-dialog.md-default-theme, md-dialog{border-radius:4px;background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}[disabled] md-input-container.md-default-theme .md-input, [disabled] md-input-container .md-input,md-input-container.md-default-theme .md-input[disabled], md-input-container .md-input[disabled]{border-bottom-color:transparent;color:rgba(0,0,0,0.38);background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-list.md-default-theme .md-proxy-focus.md-focused div.md-no-style, md-list .md-proxy-focus.md-focused div.md-no-style{background-color:rgb(245,245,245)}md-list.md-default-theme md-list-item .md-avatar-icon, md-list md-list-item .md-avatar-icon{background-color:rgba(0,0,0,0.38);color:rgb(250,250,250)}md-menu-content.md-default-theme, md-menu-content{background-color:rgb(245,245,245)}md-menu-content.md-default-theme md-menu-divider, md-menu-content md-menu-divider{background-color:rgba(0,0,0,0.12)}md-menu-bar.md-default-theme md-menu.md-open>button, md-menu-bar md-menu.md-open>button,md-menu-bar.md-default-theme md-menu>button:focus, md-menu-bar md-menu>button:focus{outline:none;background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme.md-open:not(.md-keyboard-mode) md-menu:hover>button, md-menu-bar.md-open:not(.md-keyboard-mode) md-menu:hover>button{background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme:not(.md-keyboard-mode):not(.md-open) md-menu button:focus, md-menu-bar:not(.md-keyboard-mode):not(.md-open) md-menu button:focus,md-menu-bar.md-default-theme:not(.md-keyboard-mode):not(.md-open) md-menu button:hover, md-menu-bar:not(.md-keyboard-mode):not(.md-open) md-menu button:hover{background:transparent}md-menu-content.md-default-theme .md-menu.md-open>.md-button, md-menu-content .md-menu.md-open>.md-button{background-color:rgba(158,158,158,0.18)}md-toolbar.md-default-theme.md-menu-toolbar, md-toolbar.md-menu-toolbar{background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme .md-nav-bar, md-nav-bar .md-nav-bar{background-color:transparent;border-color:rgba(0,0,0,0.12)}._md-panel-backdrop.md-default-theme, ._md-panel-backdrop{background-color:rgba(33,33,33,1.0)}md-select.md-default-theme[disabled] .md-select-value, md-select[disabled] .md-select-value{border-bottom-color:transparent;background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-select-menu.md-default-theme md-content, md-select-menu md-content{background-color:rgb(245,245,245)}md-select-menu.md-default-theme md-content md-option:not([disabled]):focus, md-select-menu md-content md-option:not([disabled]):focus,md-select-menu.md-default-theme md-content md-option:not([disabled]):hover, md-select-menu md-content md-option:not([disabled]):hover{background-color:rgba(158,158,158,0.18)}.md-checkbox-enabled.md-default-theme[selected] .md-ripple, .md-checkbox-enabled[selected] .md-ripple{color:rgb(117,117,117)}md-sidenav.md-default-theme, md-sidenav,md-sidenav.md-default-theme md-content, md-sidenav md-content{background-color:rgb(245,245,245)}md-slider.md-default-theme .md-track, md-slider .md-track{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme .md-track-ticks, md-slider .md-track-ticks{color:rgba(0,0,0,0.87)}md-slider.md-default-theme .md-disabled-thumb, md-slider .md-disabled-thumb{border-color:rgb(250,250,250);background-color:rgb(250,250,250)}md-slider.md-default-theme.md-min .md-thumb:after, md-slider.md-min .md-thumb:after{background-color:rgb(250,250,250);border-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-min .md-focus-ring, md-slider.md-min .md-focus-ring{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-min[md-discrete] .md-thumb:after, md-slider.md-min[md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.87);border-color:transparent}md-slider.md-default-theme.md-min[md-discrete] .md-sign, md-slider.md-min[md-discrete] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme.md-min[md-discrete] .md-sign:after, md-slider.md-min[md-discrete] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme.md-min[md-discrete][md-vertical] .md-sign:after, md-slider.md-min[md-discrete][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme[disabled]:not(.md-min) .md-thumb:after, md-slider[disabled]:not(.md-min) .md-thumb:after,md-slider.md-default-theme[disabled][md-discrete] .md-thumb:after, md-slider[disabled][md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.38);border-color:transparent}md-slider.md-default-theme[disabled][readonly] .md-sign, md-slider[disabled][readonly] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme[disabled][readonly] .md-sign:after, md-slider[disabled][readonly] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme[disabled][readonly][md-vertical] .md-sign:after, md-slider[disabled][readonly][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme[disabled][readonly] .md-disabled-thumb, md-slider[disabled][readonly] .md-disabled-thumb{border-color:transparent;background-color:transparent}.md-subheader.md-default-theme, .md-subheader{color:rgba(0,0,0,0.54);background-color:rgb(250,250,250)}md-switch.md-default-theme .md-ink-ripple, md-switch .md-ink-ripple{color:rgb(158,158,158)}md-switch.md-default-theme .md-thumb, md-switch .md-thumb{background-color:rgb(250,250,250)}md-switch.md-default-theme .md-bar, md-switch .md-bar{background-color:rgb(158,158,158)}md-switch.md-default-theme[disabled] .md-thumb, md-switch[disabled] .md-thumb{background-color:rgb(189,189,189)}md-switch.md-default-theme[disabled] .md-bar, md-switch[disabled] .md-bar{background-color:rgba(0,0,0,0.12)}md-tabs.md-default-theme md-tabs-wrapper, md-tabs md-tabs-wrapper{background-color:transparent;border-color:rgba(0,0,0,0.12)}md-toast.md-default-theme .md-toast-content, md-toast .md-toast-content{background-color:#323232;color:rgb(250,250,250)}md-toast.md-default-theme .md-toast-content .md-button, md-toast .md-toast-content .md-button{color:rgb(250,250,250)}.md-panel.md-tooltip.md-default-theme, .md-panel.md-tooltip{color:rgba(255,255,255,0.87);background-color:rgb(97,97,97)}body.md-default-theme, body,html.md-default-theme, html{color:rgba(0,0,0,0.87);background-color:rgb(250,250,250)} -md-autocomplete.md-default-theme.md-hue-1, md-autocomplete.md-hue-1{background:rgb(245,245,245)}md-autocomplete.md-default-theme.md-hue-1[disabled]:not([md-floating-label]), md-autocomplete.md-hue-1[disabled]:not([md-floating-label]){background:rgb(238,238,238)}md-autocomplete.md-default-theme.md-hue-1 button md-icon path, md-autocomplete.md-hue-1 button md-icon path{fill:rgb(117,117,117)}md-autocomplete.md-default-theme.md-hue-1 button:after, md-autocomplete.md-hue-1 button:after{background:rgba(117,117,117,0.3)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-1, .md-autocomplete-suggestions-container.md-hue-1{background:rgb(245,245,245)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-1 li.selected, .md-autocomplete-suggestions-container.md-hue-1 li.selected,.md-autocomplete-suggestions-container.md-default-theme.md-hue-1 li:hover, .md-autocomplete-suggestions-container.md-hue-1 li:hover{background:rgba(158,158,158,0.18)}md-backdrop{background-color:rgba(33,33,33,0.0)}md-backdrop.md-opaque.md-default-theme.md-hue-1, md-backdrop.md-opaque.md-hue-1{background-color:rgba(33,33,33,1.0)}md-bottom-sheet.md-default-theme.md-hue-1, md-bottom-sheet.md-hue-1{background-color:rgb(250,250,250);border-top-color:rgb(224,224,224)}md-bottom-sheet.md-default-theme.md-hue-1 .md-subheader, md-bottom-sheet.md-hue-1 .md-subheader{background-color:rgb(250,250,250);color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-1:not([disabled]).md-focused, .md-button.md-hue-1:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-1:not([disabled]):hover, .md-button.md-hue-1:not([disabled]):hover{background-color:rgba(158,158,158,0.2)}.md-button.md-default-theme.md-hue-1:not([disabled]).md-icon-button:hover, .md-button.md-hue-1:not([disabled]).md-icon-button:hover{background-color:transparent}.md-button.md-default-theme.md-hue-1.md-raised, .md-button.md-hue-1.md-raised{color:rgb(33,33,33);background-color:rgb(250,250,250)}.md-button.md-default-theme.md-hue-1.md-raised:not([disabled]) md-icon, .md-button.md-hue-1.md-raised:not([disabled]) md-icon{color:rgb(33,33,33)}.md-button.md-default-theme.md-hue-1.md-raised:not([disabled]):hover, .md-button.md-hue-1.md-raised:not([disabled]):hover{background-color:rgb(250,250,250)}.md-button.md-default-theme.md-hue-1.md-raised:not([disabled]).md-focused, .md-button.md-hue-1.md-raised:not([disabled]).md-focused{background-color:rgb(238,238,238)}.md-button.md-default-theme.md-hue-1.md-fab[disabled], .md-button.md-hue-1.md-fab[disabled],.md-button.md-default-theme.md-hue-1.md-raised[disabled], .md-button.md-hue-1.md-raised[disabled]{background-color:rgba(0,0,0,0.12)}.md-button.md-default-theme.md-hue-1[disabled], .md-button.md-hue-1[disabled]{background-color:transparent}md-card.md-default-theme.md-hue-1, md-card.md-hue-1{color:rgba(0,0,0,0.87);background-color:rgb(245,245,245);border-radius:2px}md-card.md-default-theme.md-hue-1 md-card-header md-card-avatar md-icon, md-card.md-hue-1 md-card-header md-card-avatar md-icon{color:rgb(245,245,245);background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-1.md-checked .md-ripple, md-checkbox.md-hue-1.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme.md-hue-1[disabled].md-checked .md-icon, md-checkbox.md-hue-1[disabled].md-checked .md-icon{background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-1[disabled].md-checked .md-icon:after, md-checkbox.md-hue-1[disabled].md-checked .md-icon:after{border-color:rgb(238,238,238)}md-chips.md-default-theme.md-hue-1 md-chip, md-chips.md-hue-1 md-chip{background:rgb(224,224,224);color:rgb(66,66,66)}md-chips.md-default-theme.md-hue-1 md-chip md-icon, md-chips.md-hue-1 md-chip md-icon{color:rgb(97,97,97)}md-chips.md-default-theme.md-hue-1 md-chip._md-chip-editing, md-chips.md-hue-1 md-chip._md-chip-editing{background:transparent;color:rgb(66,66,66)}md-chips.md-default-theme.md-hue-1 md-chip-remove .md-button md-icon path, md-chips.md-hue-1 md-chip-remove .md-button md-icon path{fill:rgb(158,158,158)}.md-contact-suggestion span.md-contact-email{color:rgb(189,189,189)}md-content.md-default-theme.md-hue-1, md-content.md-hue-1{color:rgba(0,0,0,0.87);background-color:rgb(250,250,250)}.md-default-theme.md-hue-1 .md-calendar, .md-hue-1 .md-calendar{background:rgb(245,245,245);color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-1 .md-calendar tr:last-child td, .md-hue-1 .md-calendar tr:last-child td{border-bottom-color:rgb(238,238,238)}.md-default-theme.md-hue-1 .md-calendar-day-header, .md-hue-1 .md-calendar-day-header{background:rgba(158,158,158,0.32);color:rgba(0,0,0,0.87)}.md-calendar-date.md-focus .md-default-theme.md-hue-1 .md-calendar-date-selection-indicator, .md-calendar-date.md-focus .md-hue-1 .md-calendar-date-selection-indicator,.md-default-theme.md-hue-1 .md-calendar-date-selection-indicator:hover, .md-hue-1 .md-calendar-date-selection-indicator:hover{background:rgba(158,158,158,0.32)}.md-default-theme.md-hue-1 .md-datepicker-calendar-pane, .md-hue-1 .md-datepicker-calendar-pane{border-color:rgb(245,245,245)}.md-default-theme.md-hue-1 .md-datepicker-calendar, .md-hue-1 .md-datepicker-calendar{background:rgb(245,245,245)}.md-default-theme.md-hue-1 .md-datepicker-input-mask-opaque, .md-hue-1 .md-datepicker-input-mask-opaque{box-shadow:0 0 0 9999px rgb(245,245,245)}.md-default-theme.md-hue-1 .md-datepicker-open .md-datepicker-input-container, .md-hue-1 .md-datepicker-open .md-datepicker-input-container{background:rgb(245,245,245)}md-dialog.md-default-theme.md-hue-1, md-dialog.md-hue-1{border-radius:4px;background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}[disabled] md-input-container.md-default-theme.md-hue-1 .md-input, [disabled] md-input-container.md-hue-1 .md-input,md-input-container.md-default-theme.md-hue-1 .md-input[disabled], md-input-container.md-hue-1 .md-input[disabled]{border-bottom-color:transparent;color:rgba(0,0,0,0.38);background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-list.md-default-theme.md-hue-1 .md-proxy-focus.md-focused div.md-no-style, md-list.md-hue-1 .md-proxy-focus.md-focused div.md-no-style{background-color:rgb(245,245,245)}md-list.md-default-theme.md-hue-1 md-list-item .md-avatar-icon, md-list.md-hue-1 md-list-item .md-avatar-icon{background-color:rgba(0,0,0,0.38);color:rgb(245,245,245)}md-menu-content.md-default-theme.md-hue-1, md-menu-content.md-hue-1{background-color:rgb(245,245,245)}md-menu-content.md-default-theme.md-hue-1 md-menu-divider, md-menu-content.md-hue-1 md-menu-divider{background-color:rgba(0,0,0,0.12)}md-menu-bar.md-default-theme.md-hue-1 md-menu.md-open>button, md-menu-bar.md-hue-1 md-menu.md-open>button,md-menu-bar.md-default-theme.md-hue-1 md-menu>button:focus, md-menu-bar.md-hue-1 md-menu>button:focus{outline:none;background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme.md-hue-1.md-open:not(.md-keyboard-mode) md-menu:hover>button, md-menu-bar.md-hue-1.md-open:not(.md-keyboard-mode) md-menu:hover>button{background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme.md-hue-1:not(.md-keyboard-mode):not(.md-open) md-menu button:focus, md-menu-bar.md-hue-1:not(.md-keyboard-mode):not(.md-open) md-menu button:focus,md-menu-bar.md-default-theme.md-hue-1:not(.md-keyboard-mode):not(.md-open) md-menu button:hover, md-menu-bar.md-hue-1:not(.md-keyboard-mode):not(.md-open) md-menu button:hover{background:transparent}md-menu-content.md-default-theme.md-hue-1 .md-menu.md-open>.md-button, md-menu-content.md-hue-1 .md-menu.md-open>.md-button{background-color:rgba(158,158,158,0.18)}md-toolbar.md-default-theme.md-hue-1.md-menu-toolbar, md-toolbar.md-hue-1.md-menu-toolbar{background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-1 .md-nav-bar, md-nav-bar.md-hue-1 .md-nav-bar{background-color:transparent;border-color:rgba(0,0,0,0.12)}._md-panel-backdrop.md-default-theme.md-hue-1, ._md-panel-backdrop.md-hue-1{background-color:rgba(33,33,33,1.0)}md-select.md-default-theme.md-hue-1[disabled] .md-select-value, md-select.md-hue-1[disabled] .md-select-value{border-bottom-color:transparent;background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-select-menu.md-default-theme.md-hue-1 md-content, md-select-menu.md-hue-1 md-content{background-color:rgb(245,245,245)}md-select-menu.md-default-theme.md-hue-1 md-content md-option:not([disabled]):focus, md-select-menu.md-hue-1 md-content md-option:not([disabled]):focus,md-select-menu.md-default-theme.md-hue-1 md-content md-option:not([disabled]):hover, md-select-menu.md-hue-1 md-content md-option:not([disabled]):hover{background-color:rgba(158,158,158,0.18)}.md-checkbox-enabled.md-default-theme.md-hue-1[selected] .md-ripple, .md-checkbox-enabled.md-hue-1[selected] .md-ripple{color:rgb(117,117,117)}md-sidenav.md-default-theme.md-hue-1, md-sidenav.md-hue-1,md-sidenav.md-default-theme.md-hue-1 md-content, md-sidenav.md-hue-1 md-content{background-color:rgb(245,245,245)}md-slider.md-default-theme.md-hue-1 .md-track, md-slider.md-hue-1 .md-track{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-1 .md-track-ticks, md-slider.md-hue-1 .md-track-ticks{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-1 .md-disabled-thumb, md-slider.md-hue-1 .md-disabled-thumb{border-color:rgb(245,245,245);background-color:rgb(245,245,245)}md-slider.md-default-theme.md-hue-1.md-min .md-thumb:after, md-slider.md-hue-1.md-min .md-thumb:after{background-color:rgb(245,245,245);border-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-1.md-min .md-focus-ring, md-slider.md-hue-1.md-min .md-focus-ring{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-1.md-min[md-discrete] .md-thumb:after, md-slider.md-hue-1.md-min[md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.87);border-color:transparent}md-slider.md-default-theme.md-hue-1.md-min[md-discrete] .md-sign, md-slider.md-hue-1.md-min[md-discrete] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-1.md-min[md-discrete] .md-sign:after, md-slider.md-hue-1.md-min[md-discrete] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-1.md-min[md-discrete][md-vertical] .md-sign:after, md-slider.md-hue-1.md-min[md-discrete][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-1[disabled]:not(.md-min) .md-thumb:after, md-slider.md-hue-1[disabled]:not(.md-min) .md-thumb:after,md-slider.md-default-theme.md-hue-1[disabled][md-discrete] .md-thumb:after, md-slider.md-hue-1[disabled][md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.38);border-color:transparent}md-slider.md-default-theme.md-hue-1[disabled][readonly] .md-sign, md-slider.md-hue-1[disabled][readonly] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-1[disabled][readonly] .md-sign:after, md-slider.md-hue-1[disabled][readonly] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-1[disabled][readonly][md-vertical] .md-sign:after, md-slider.md-hue-1[disabled][readonly][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-1[disabled][readonly] .md-disabled-thumb, md-slider.md-hue-1[disabled][readonly] .md-disabled-thumb{border-color:transparent;background-color:transparent}.md-subheader.md-default-theme.md-hue-1, .md-subheader.md-hue-1{color:rgba(0,0,0,0.54);background-color:rgb(250,250,250)}md-switch.md-default-theme.md-hue-1 .md-ink-ripple, md-switch.md-hue-1 .md-ink-ripple{color:rgb(158,158,158)}md-switch.md-default-theme.md-hue-1 .md-thumb, md-switch.md-hue-1 .md-thumb{background-color:rgb(250,250,250)}md-switch.md-default-theme.md-hue-1 .md-bar, md-switch.md-hue-1 .md-bar{background-color:rgb(158,158,158)}md-switch.md-default-theme.md-hue-1[disabled] .md-thumb, md-switch.md-hue-1[disabled] .md-thumb{background-color:rgb(189,189,189)}md-switch.md-default-theme.md-hue-1[disabled] .md-bar, md-switch.md-hue-1[disabled] .md-bar{background-color:rgba(0,0,0,0.12)}md-tabs.md-default-theme.md-hue-1 md-tabs-wrapper, md-tabs.md-hue-1 md-tabs-wrapper{background-color:transparent;border-color:rgba(0,0,0,0.12)}md-toast.md-default-theme.md-hue-1 .md-toast-content, md-toast.md-hue-1 .md-toast-content{background-color:#323232;color:rgb(250,250,250)}md-toast.md-default-theme.md-hue-1 .md-toast-content .md-button, md-toast.md-hue-1 .md-toast-content .md-button{color:rgb(250,250,250)}.md-panel.md-tooltip.md-default-theme.md-hue-1, .md-panel.md-tooltip.md-hue-1{color:rgba(255,255,255,0.87);background-color:rgb(97,97,97)}body.md-default-theme.md-hue-1, body.md-hue-1,html.md-default-theme.md-hue-1, html.md-hue-1{color:rgba(0,0,0,0.87);background-color:rgb(245,245,245)} -md-autocomplete.md-default-theme.md-hue-2, md-autocomplete.md-hue-2{background:rgb(245,245,245)}md-autocomplete.md-default-theme.md-hue-2[disabled]:not([md-floating-label]), md-autocomplete.md-hue-2[disabled]:not([md-floating-label]){background:rgb(238,238,238)}md-autocomplete.md-default-theme.md-hue-2 button md-icon path, md-autocomplete.md-hue-2 button md-icon path{fill:rgb(117,117,117)}md-autocomplete.md-default-theme.md-hue-2 button:after, md-autocomplete.md-hue-2 button:after{background:rgba(117,117,117,0.3)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-2, .md-autocomplete-suggestions-container.md-hue-2{background:rgb(245,245,245)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-2 li.selected, .md-autocomplete-suggestions-container.md-hue-2 li.selected,.md-autocomplete-suggestions-container.md-default-theme.md-hue-2 li:hover, .md-autocomplete-suggestions-container.md-hue-2 li:hover{background:rgba(158,158,158,0.18)}md-backdrop{background-color:rgba(33,33,33,0.0)}md-backdrop.md-opaque.md-default-theme.md-hue-2, md-backdrop.md-opaque.md-hue-2{background-color:rgba(33,33,33,1.0)}md-bottom-sheet.md-default-theme.md-hue-2, md-bottom-sheet.md-hue-2{background-color:rgb(250,250,250);border-top-color:rgb(224,224,224)}md-bottom-sheet.md-default-theme.md-hue-2 .md-subheader, md-bottom-sheet.md-hue-2 .md-subheader{background-color:rgb(250,250,250);color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-2:not([disabled]).md-focused, .md-button.md-hue-2:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-2:not([disabled]):hover, .md-button.md-hue-2:not([disabled]):hover{background-color:rgba(158,158,158,0.2)}.md-button.md-default-theme.md-hue-2:not([disabled]).md-icon-button:hover, .md-button.md-hue-2:not([disabled]).md-icon-button:hover{background-color:transparent}.md-button.md-default-theme.md-hue-2.md-raised, .md-button.md-hue-2.md-raised{color:rgb(33,33,33);background-color:rgb(250,250,250)}.md-button.md-default-theme.md-hue-2.md-raised:not([disabled]) md-icon, .md-button.md-hue-2.md-raised:not([disabled]) md-icon{color:rgb(33,33,33)}.md-button.md-default-theme.md-hue-2.md-raised:not([disabled]):hover, .md-button.md-hue-2.md-raised:not([disabled]):hover{background-color:rgb(250,250,250)}.md-button.md-default-theme.md-hue-2.md-raised:not([disabled]).md-focused, .md-button.md-hue-2.md-raised:not([disabled]).md-focused{background-color:rgb(238,238,238)}.md-button.md-default-theme.md-hue-2.md-fab[disabled], .md-button.md-hue-2.md-fab[disabled],.md-button.md-default-theme.md-hue-2.md-raised[disabled], .md-button.md-hue-2.md-raised[disabled]{background-color:rgba(0,0,0,0.12)}.md-button.md-default-theme.md-hue-2[disabled], .md-button.md-hue-2[disabled]{background-color:transparent}md-card.md-default-theme.md-hue-2, md-card.md-hue-2{color:rgba(0,0,0,0.87);background-color:rgb(245,245,245);border-radius:2px}md-card.md-default-theme.md-hue-2 md-card-header md-card-avatar md-icon, md-card.md-hue-2 md-card-header md-card-avatar md-icon{color:rgb(238,238,238);background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-2.md-checked .md-ripple, md-checkbox.md-hue-2.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme.md-hue-2[disabled].md-checked .md-icon, md-checkbox.md-hue-2[disabled].md-checked .md-icon{background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-2[disabled].md-checked .md-icon:after, md-checkbox.md-hue-2[disabled].md-checked .md-icon:after{border-color:rgb(238,238,238)}md-chips.md-default-theme.md-hue-2 md-chip, md-chips.md-hue-2 md-chip{background:rgb(224,224,224);color:rgb(66,66,66)}md-chips.md-default-theme.md-hue-2 md-chip md-icon, md-chips.md-hue-2 md-chip md-icon{color:rgb(97,97,97)}md-chips.md-default-theme.md-hue-2 md-chip._md-chip-editing, md-chips.md-hue-2 md-chip._md-chip-editing{background:transparent;color:rgb(66,66,66)}md-chips.md-default-theme.md-hue-2 md-chip-remove .md-button md-icon path, md-chips.md-hue-2 md-chip-remove .md-button md-icon path{fill:rgb(158,158,158)}.md-contact-suggestion span.md-contact-email{color:rgb(189,189,189)}md-content.md-default-theme.md-hue-2, md-content.md-hue-2{color:rgba(0,0,0,0.87);background-color:rgb(250,250,250)}.md-default-theme.md-hue-2 .md-calendar, .md-hue-2 .md-calendar{background:rgb(245,245,245);color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-2 .md-calendar tr:last-child td, .md-hue-2 .md-calendar tr:last-child td{border-bottom-color:rgb(238,238,238)}.md-default-theme.md-hue-2 .md-calendar-day-header, .md-hue-2 .md-calendar-day-header{background:rgba(158,158,158,0.32);color:rgba(0,0,0,0.87)}.md-calendar-date.md-focus .md-default-theme.md-hue-2 .md-calendar-date-selection-indicator, .md-calendar-date.md-focus .md-hue-2 .md-calendar-date-selection-indicator,.md-default-theme.md-hue-2 .md-calendar-date-selection-indicator:hover, .md-hue-2 .md-calendar-date-selection-indicator:hover{background:rgba(158,158,158,0.32)}.md-default-theme.md-hue-2 .md-datepicker-calendar-pane, .md-hue-2 .md-datepicker-calendar-pane{border-color:rgb(245,245,245)}.md-default-theme.md-hue-2 .md-datepicker-calendar, .md-hue-2 .md-datepicker-calendar{background:rgb(245,245,245)}.md-default-theme.md-hue-2 .md-datepicker-input-mask-opaque, .md-hue-2 .md-datepicker-input-mask-opaque{box-shadow:0 0 0 9999px rgb(245,245,245)}.md-default-theme.md-hue-2 .md-datepicker-open .md-datepicker-input-container, .md-hue-2 .md-datepicker-open .md-datepicker-input-container{background:rgb(245,245,245)}md-dialog.md-default-theme.md-hue-2, md-dialog.md-hue-2{border-radius:4px;background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}[disabled] md-input-container.md-default-theme.md-hue-2 .md-input, [disabled] md-input-container.md-hue-2 .md-input,md-input-container.md-default-theme.md-hue-2 .md-input[disabled], md-input-container.md-hue-2 .md-input[disabled]{border-bottom-color:transparent;color:rgba(0,0,0,0.38);background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-list.md-default-theme.md-hue-2 .md-proxy-focus.md-focused div.md-no-style, md-list.md-hue-2 .md-proxy-focus.md-focused div.md-no-style{background-color:rgb(245,245,245)}md-list.md-default-theme.md-hue-2 md-list-item .md-avatar-icon, md-list.md-hue-2 md-list-item .md-avatar-icon{background-color:rgba(0,0,0,0.38);color:rgb(238,238,238)}md-menu-content.md-default-theme.md-hue-2, md-menu-content.md-hue-2{background-color:rgb(245,245,245)}md-menu-content.md-default-theme.md-hue-2 md-menu-divider, md-menu-content.md-hue-2 md-menu-divider{background-color:rgba(0,0,0,0.12)}md-menu-bar.md-default-theme.md-hue-2 md-menu.md-open>button, md-menu-bar.md-hue-2 md-menu.md-open>button,md-menu-bar.md-default-theme.md-hue-2 md-menu>button:focus, md-menu-bar.md-hue-2 md-menu>button:focus{outline:none;background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme.md-hue-2.md-open:not(.md-keyboard-mode) md-menu:hover>button, md-menu-bar.md-hue-2.md-open:not(.md-keyboard-mode) md-menu:hover>button{background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme.md-hue-2:not(.md-keyboard-mode):not(.md-open) md-menu button:focus, md-menu-bar.md-hue-2:not(.md-keyboard-mode):not(.md-open) md-menu button:focus,md-menu-bar.md-default-theme.md-hue-2:not(.md-keyboard-mode):not(.md-open) md-menu button:hover, md-menu-bar.md-hue-2:not(.md-keyboard-mode):not(.md-open) md-menu button:hover{background:transparent}md-menu-content.md-default-theme.md-hue-2 .md-menu.md-open>.md-button, md-menu-content.md-hue-2 .md-menu.md-open>.md-button{background-color:rgba(158,158,158,0.18)}md-toolbar.md-default-theme.md-hue-2.md-menu-toolbar, md-toolbar.md-hue-2.md-menu-toolbar{background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-2 .md-nav-bar, md-nav-bar.md-hue-2 .md-nav-bar{background-color:transparent;border-color:rgba(0,0,0,0.12)}._md-panel-backdrop.md-default-theme.md-hue-2, ._md-panel-backdrop.md-hue-2{background-color:rgba(33,33,33,1.0)}md-select.md-default-theme.md-hue-2[disabled] .md-select-value, md-select.md-hue-2[disabled] .md-select-value{border-bottom-color:transparent;background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-select-menu.md-default-theme.md-hue-2 md-content, md-select-menu.md-hue-2 md-content{background-color:rgb(245,245,245)}md-select-menu.md-default-theme.md-hue-2 md-content md-option:not([disabled]):focus, md-select-menu.md-hue-2 md-content md-option:not([disabled]):focus,md-select-menu.md-default-theme.md-hue-2 md-content md-option:not([disabled]):hover, md-select-menu.md-hue-2 md-content md-option:not([disabled]):hover{background-color:rgba(158,158,158,0.18)}.md-checkbox-enabled.md-default-theme.md-hue-2[selected] .md-ripple, .md-checkbox-enabled.md-hue-2[selected] .md-ripple{color:rgb(117,117,117)}md-sidenav.md-default-theme.md-hue-2, md-sidenav.md-hue-2,md-sidenav.md-default-theme.md-hue-2 md-content, md-sidenav.md-hue-2 md-content{background-color:rgb(245,245,245)}md-slider.md-default-theme.md-hue-2 .md-track, md-slider.md-hue-2 .md-track{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-2 .md-track-ticks, md-slider.md-hue-2 .md-track-ticks{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-2 .md-disabled-thumb, md-slider.md-hue-2 .md-disabled-thumb{border-color:rgb(238,238,238);background-color:rgb(238,238,238)}md-slider.md-default-theme.md-hue-2.md-min .md-thumb:after, md-slider.md-hue-2.md-min .md-thumb:after{background-color:rgb(238,238,238);border-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-2.md-min .md-focus-ring, md-slider.md-hue-2.md-min .md-focus-ring{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-2.md-min[md-discrete] .md-thumb:after, md-slider.md-hue-2.md-min[md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.87);border-color:transparent}md-slider.md-default-theme.md-hue-2.md-min[md-discrete] .md-sign, md-slider.md-hue-2.md-min[md-discrete] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-2.md-min[md-discrete] .md-sign:after, md-slider.md-hue-2.md-min[md-discrete] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-2.md-min[md-discrete][md-vertical] .md-sign:after, md-slider.md-hue-2.md-min[md-discrete][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-2[disabled]:not(.md-min) .md-thumb:after, md-slider.md-hue-2[disabled]:not(.md-min) .md-thumb:after,md-slider.md-default-theme.md-hue-2[disabled][md-discrete] .md-thumb:after, md-slider.md-hue-2[disabled][md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.38);border-color:transparent}md-slider.md-default-theme.md-hue-2[disabled][readonly] .md-sign, md-slider.md-hue-2[disabled][readonly] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-2[disabled][readonly] .md-sign:after, md-slider.md-hue-2[disabled][readonly] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-2[disabled][readonly][md-vertical] .md-sign:after, md-slider.md-hue-2[disabled][readonly][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-2[disabled][readonly] .md-disabled-thumb, md-slider.md-hue-2[disabled][readonly] .md-disabled-thumb{border-color:transparent;background-color:transparent}.md-subheader.md-default-theme.md-hue-2, .md-subheader.md-hue-2{color:rgba(0,0,0,0.54);background-color:rgb(250,250,250)}md-switch.md-default-theme.md-hue-2 .md-ink-ripple, md-switch.md-hue-2 .md-ink-ripple{color:rgb(158,158,158)}md-switch.md-default-theme.md-hue-2 .md-thumb, md-switch.md-hue-2 .md-thumb{background-color:rgb(250,250,250)}md-switch.md-default-theme.md-hue-2 .md-bar, md-switch.md-hue-2 .md-bar{background-color:rgb(158,158,158)}md-switch.md-default-theme.md-hue-2[disabled] .md-thumb, md-switch.md-hue-2[disabled] .md-thumb{background-color:rgb(189,189,189)}md-switch.md-default-theme.md-hue-2[disabled] .md-bar, md-switch.md-hue-2[disabled] .md-bar{background-color:rgba(0,0,0,0.12)}md-tabs.md-default-theme.md-hue-2 md-tabs-wrapper, md-tabs.md-hue-2 md-tabs-wrapper{background-color:transparent;border-color:rgba(0,0,0,0.12)}md-toast.md-default-theme.md-hue-2 .md-toast-content, md-toast.md-hue-2 .md-toast-content{background-color:#323232;color:rgb(250,250,250)}md-toast.md-default-theme.md-hue-2 .md-toast-content .md-button, md-toast.md-hue-2 .md-toast-content .md-button{color:rgb(250,250,250)}.md-panel.md-tooltip.md-default-theme.md-hue-2, .md-panel.md-tooltip.md-hue-2{color:rgba(255,255,255,0.87);background-color:rgb(97,97,97)}body.md-default-theme.md-hue-2, body.md-hue-2,html.md-default-theme.md-hue-2, html.md-hue-2{color:rgba(0,0,0,0.87);background-color:rgb(238,238,238)} -md-autocomplete.md-default-theme.md-hue-3, md-autocomplete.md-hue-3{background:rgb(245,245,245)}md-autocomplete.md-default-theme.md-hue-3[disabled]:not([md-floating-label]), md-autocomplete.md-hue-3[disabled]:not([md-floating-label]){background:rgb(238,238,238)}md-autocomplete.md-default-theme.md-hue-3 button md-icon path, md-autocomplete.md-hue-3 button md-icon path{fill:rgb(117,117,117)}md-autocomplete.md-default-theme.md-hue-3 button:after, md-autocomplete.md-hue-3 button:after{background:rgba(117,117,117,0.3)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-3, .md-autocomplete-suggestions-container.md-hue-3{background:rgb(245,245,245)}.md-autocomplete-suggestions-container.md-default-theme.md-hue-3 li.selected, .md-autocomplete-suggestions-container.md-hue-3 li.selected,.md-autocomplete-suggestions-container.md-default-theme.md-hue-3 li:hover, .md-autocomplete-suggestions-container.md-hue-3 li:hover{background:rgba(158,158,158,0.18)}md-backdrop{background-color:rgba(33,33,33,0.0)}md-backdrop.md-opaque.md-default-theme.md-hue-3, md-backdrop.md-opaque.md-hue-3{background-color:rgba(33,33,33,1.0)}md-bottom-sheet.md-default-theme.md-hue-3, md-bottom-sheet.md-hue-3{background-color:rgb(250,250,250);border-top-color:rgb(224,224,224)}md-bottom-sheet.md-default-theme.md-hue-3 .md-subheader, md-bottom-sheet.md-hue-3 .md-subheader{background-color:rgb(250,250,250);color:rgba(0,0,0,0.87)}.md-button.md-default-theme.md-hue-3:not([disabled]).md-focused, .md-button.md-hue-3:not([disabled]).md-focused,.md-button.md-default-theme.md-hue-3:not([disabled]):hover, .md-button.md-hue-3:not([disabled]):hover{background-color:rgba(158,158,158,0.2)}.md-button.md-default-theme.md-hue-3:not([disabled]).md-icon-button:hover, .md-button.md-hue-3:not([disabled]).md-icon-button:hover{background-color:transparent}.md-button.md-default-theme.md-hue-3.md-raised, .md-button.md-hue-3.md-raised{color:rgb(33,33,33);background-color:rgb(250,250,250)}.md-button.md-default-theme.md-hue-3.md-raised:not([disabled]) md-icon, .md-button.md-hue-3.md-raised:not([disabled]) md-icon{color:rgb(33,33,33)}.md-button.md-default-theme.md-hue-3.md-raised:not([disabled]):hover, .md-button.md-hue-3.md-raised:not([disabled]):hover{background-color:rgb(250,250,250)}.md-button.md-default-theme.md-hue-3.md-raised:not([disabled]).md-focused, .md-button.md-hue-3.md-raised:not([disabled]).md-focused{background-color:rgb(238,238,238)}.md-button.md-default-theme.md-hue-3.md-fab[disabled], .md-button.md-hue-3.md-fab[disabled],.md-button.md-default-theme.md-hue-3.md-raised[disabled], .md-button.md-hue-3.md-raised[disabled]{background-color:rgba(0,0,0,0.12)}.md-button.md-default-theme.md-hue-3[disabled], .md-button.md-hue-3[disabled]{background-color:transparent}md-card.md-default-theme.md-hue-3, md-card.md-hue-3{color:rgba(0,0,0,0.87);background-color:rgb(245,245,245);border-radius:2px}md-card.md-default-theme.md-hue-3 md-card-header md-card-avatar md-icon, md-card.md-hue-3 md-card-header md-card-avatar md-icon{color:rgb(224,224,224);background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-3.md-checked .md-ripple, md-checkbox.md-hue-3.md-checked .md-ripple{color:rgb(117,117,117)}md-checkbox.md-default-theme.md-hue-3[disabled].md-checked .md-icon, md-checkbox.md-hue-3[disabled].md-checked .md-icon{background-color:rgba(0,0,0,0.38)}md-checkbox.md-default-theme.md-hue-3[disabled].md-checked .md-icon:after, md-checkbox.md-hue-3[disabled].md-checked .md-icon:after{border-color:rgb(238,238,238)}md-chips.md-default-theme.md-hue-3 md-chip, md-chips.md-hue-3 md-chip{background:rgb(224,224,224);color:rgb(66,66,66)}md-chips.md-default-theme.md-hue-3 md-chip md-icon, md-chips.md-hue-3 md-chip md-icon{color:rgb(97,97,97)}md-chips.md-default-theme.md-hue-3 md-chip._md-chip-editing, md-chips.md-hue-3 md-chip._md-chip-editing{background:transparent;color:rgb(66,66,66)}md-chips.md-default-theme.md-hue-3 md-chip-remove .md-button md-icon path, md-chips.md-hue-3 md-chip-remove .md-button md-icon path{fill:rgb(158,158,158)}.md-contact-suggestion span.md-contact-email{color:rgb(189,189,189)}md-content.md-default-theme.md-hue-3, md-content.md-hue-3{color:rgba(0,0,0,0.87);background-color:rgb(250,250,250)}.md-default-theme.md-hue-3 .md-calendar, .md-hue-3 .md-calendar{background:rgb(245,245,245);color:rgba(0,0,0,0.87)}.md-default-theme.md-hue-3 .md-calendar tr:last-child td, .md-hue-3 .md-calendar tr:last-child td{border-bottom-color:rgb(238,238,238)}.md-default-theme.md-hue-3 .md-calendar-day-header, .md-hue-3 .md-calendar-day-header{background:rgba(158,158,158,0.32);color:rgba(0,0,0,0.87)}.md-calendar-date.md-focus .md-default-theme.md-hue-3 .md-calendar-date-selection-indicator, .md-calendar-date.md-focus .md-hue-3 .md-calendar-date-selection-indicator,.md-default-theme.md-hue-3 .md-calendar-date-selection-indicator:hover, .md-hue-3 .md-calendar-date-selection-indicator:hover{background:rgba(158,158,158,0.32)}.md-default-theme.md-hue-3 .md-datepicker-calendar-pane, .md-hue-3 .md-datepicker-calendar-pane{border-color:rgb(245,245,245)}.md-default-theme.md-hue-3 .md-datepicker-calendar, .md-hue-3 .md-datepicker-calendar{background:rgb(245,245,245)}.md-default-theme.md-hue-3 .md-datepicker-input-mask-opaque, .md-hue-3 .md-datepicker-input-mask-opaque{box-shadow:0 0 0 9999px rgb(245,245,245)}.md-default-theme.md-hue-3 .md-datepicker-open .md-datepicker-input-container, .md-hue-3 .md-datepicker-open .md-datepicker-input-container{background:rgb(245,245,245)}md-dialog.md-default-theme.md-hue-3, md-dialog.md-hue-3{border-radius:4px;background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}[disabled] md-input-container.md-default-theme.md-hue-3 .md-input, [disabled] md-input-container.md-hue-3 .md-input,md-input-container.md-default-theme.md-hue-3 .md-input[disabled], md-input-container.md-hue-3 .md-input[disabled]{border-bottom-color:transparent;color:rgba(0,0,0,0.38);background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-list.md-default-theme.md-hue-3 .md-proxy-focus.md-focused div.md-no-style, md-list.md-hue-3 .md-proxy-focus.md-focused div.md-no-style{background-color:rgb(245,245,245)}md-list.md-default-theme.md-hue-3 md-list-item .md-avatar-icon, md-list.md-hue-3 md-list-item .md-avatar-icon{background-color:rgba(0,0,0,0.38);color:rgb(224,224,224)}md-menu-content.md-default-theme.md-hue-3, md-menu-content.md-hue-3{background-color:rgb(245,245,245)}md-menu-content.md-default-theme.md-hue-3 md-menu-divider, md-menu-content.md-hue-3 md-menu-divider{background-color:rgba(0,0,0,0.12)}md-menu-bar.md-default-theme.md-hue-3 md-menu.md-open>button, md-menu-bar.md-hue-3 md-menu.md-open>button,md-menu-bar.md-default-theme.md-hue-3 md-menu>button:focus, md-menu-bar.md-hue-3 md-menu>button:focus{outline:none;background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme.md-hue-3.md-open:not(.md-keyboard-mode) md-menu:hover>button, md-menu-bar.md-hue-3.md-open:not(.md-keyboard-mode) md-menu:hover>button{background-color:rgba(158,158,158,0.18)}md-menu-bar.md-default-theme.md-hue-3:not(.md-keyboard-mode):not(.md-open) md-menu button:focus, md-menu-bar.md-hue-3:not(.md-keyboard-mode):not(.md-open) md-menu button:focus,md-menu-bar.md-default-theme.md-hue-3:not(.md-keyboard-mode):not(.md-open) md-menu button:hover, md-menu-bar.md-hue-3:not(.md-keyboard-mode):not(.md-open) md-menu button:hover{background:transparent}md-menu-content.md-default-theme.md-hue-3 .md-menu.md-open>.md-button, md-menu-content.md-hue-3 .md-menu.md-open>.md-button{background-color:rgba(158,158,158,0.18)}md-toolbar.md-default-theme.md-hue-3.md-menu-toolbar, md-toolbar.md-hue-3.md-menu-toolbar{background-color:rgb(245,245,245);color:rgba(0,0,0,0.87)}md-nav-bar.md-default-theme.md-hue-3 .md-nav-bar, md-nav-bar.md-hue-3 .md-nav-bar{background-color:transparent;border-color:rgba(0,0,0,0.12)}._md-panel-backdrop.md-default-theme.md-hue-3, ._md-panel-backdrop.md-hue-3{background-color:rgba(33,33,33,1.0)}md-select.md-default-theme.md-hue-3[disabled] .md-select-value, md-select.md-hue-3[disabled] .md-select-value{border-bottom-color:transparent;background-image:linear-gradient(90deg,rgba(0,0,0,0.38) 0,rgba(0,0,0,0.38) 33%,transparent 0);background-image:-ms-linear-gradient(left,transparent 0,rgba(0,0,0,0.38) 100%)}md-select-menu.md-default-theme.md-hue-3 md-content, md-select-menu.md-hue-3 md-content{background-color:rgb(245,245,245)}md-select-menu.md-default-theme.md-hue-3 md-content md-option:not([disabled]):focus, md-select-menu.md-hue-3 md-content md-option:not([disabled]):focus,md-select-menu.md-default-theme.md-hue-3 md-content md-option:not([disabled]):hover, md-select-menu.md-hue-3 md-content md-option:not([disabled]):hover{background-color:rgba(158,158,158,0.18)}.md-checkbox-enabled.md-default-theme.md-hue-3[selected] .md-ripple, .md-checkbox-enabled.md-hue-3[selected] .md-ripple{color:rgb(117,117,117)}md-sidenav.md-default-theme.md-hue-3, md-sidenav.md-hue-3,md-sidenav.md-default-theme.md-hue-3 md-content, md-sidenav.md-hue-3 md-content{background-color:rgb(245,245,245)}md-slider.md-default-theme.md-hue-3 .md-track, md-slider.md-hue-3 .md-track{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-3 .md-track-ticks, md-slider.md-hue-3 .md-track-ticks{color:rgba(0,0,0,0.87)}md-slider.md-default-theme.md-hue-3 .md-disabled-thumb, md-slider.md-hue-3 .md-disabled-thumb{border-color:rgb(224,224,224);background-color:rgb(224,224,224)}md-slider.md-default-theme.md-hue-3.md-min .md-thumb:after, md-slider.md-hue-3.md-min .md-thumb:after{background-color:rgb(224,224,224);border-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-3.md-min .md-focus-ring, md-slider.md-hue-3.md-min .md-focus-ring{background-color:rgba(0,0,0,0.38)}md-slider.md-default-theme.md-hue-3.md-min[md-discrete] .md-thumb:after, md-slider.md-hue-3.md-min[md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.87);border-color:transparent}md-slider.md-default-theme.md-hue-3.md-min[md-discrete] .md-sign, md-slider.md-hue-3.md-min[md-discrete] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-3.md-min[md-discrete] .md-sign:after, md-slider.md-hue-3.md-min[md-discrete] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-3.md-min[md-discrete][md-vertical] .md-sign:after, md-slider.md-hue-3.md-min[md-discrete][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-3[disabled]:not(.md-min) .md-thumb:after, md-slider.md-hue-3[disabled]:not(.md-min) .md-thumb:after,md-slider.md-default-theme.md-hue-3[disabled][md-discrete] .md-thumb:after, md-slider.md-hue-3[disabled][md-discrete] .md-thumb:after{background-color:rgba(0,0,0,0.38);border-color:transparent}md-slider.md-default-theme.md-hue-3[disabled][readonly] .md-sign, md-slider.md-hue-3[disabled][readonly] .md-sign{background-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-3[disabled][readonly] .md-sign:after, md-slider.md-hue-3[disabled][readonly] .md-sign:after{border-top-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-3[disabled][readonly][md-vertical] .md-sign:after, md-slider.md-hue-3[disabled][readonly][md-vertical] .md-sign:after{border-top-color:transparent;border-left-color:rgb(189,189,189)}md-slider.md-default-theme.md-hue-3[disabled][readonly] .md-disabled-thumb, md-slider.md-hue-3[disabled][readonly] .md-disabled-thumb{border-color:transparent;background-color:transparent}.md-subheader.md-default-theme.md-hue-3, .md-subheader.md-hue-3{color:rgba(0,0,0,0.54);background-color:rgb(250,250,250)}md-switch.md-default-theme.md-hue-3 .md-ink-ripple, md-switch.md-hue-3 .md-ink-ripple{color:rgb(158,158,158)}md-switch.md-default-theme.md-hue-3 .md-thumb, md-switch.md-hue-3 .md-thumb{background-color:rgb(250,250,250)}md-switch.md-default-theme.md-hue-3 .md-bar, md-switch.md-hue-3 .md-bar{background-color:rgb(158,158,158)}md-switch.md-default-theme.md-hue-3[disabled] .md-thumb, md-switch.md-hue-3[disabled] .md-thumb{background-color:rgb(189,189,189)}md-switch.md-default-theme.md-hue-3[disabled] .md-bar, md-switch.md-hue-3[disabled] .md-bar{background-color:rgba(0,0,0,0.12)}md-tabs.md-default-theme.md-hue-3 md-tabs-wrapper, md-tabs.md-hue-3 md-tabs-wrapper{background-color:transparent;border-color:rgba(0,0,0,0.12)}md-toast.md-default-theme.md-hue-3 .md-toast-content, md-toast.md-hue-3 .md-toast-content{background-color:#323232;color:rgb(250,250,250)}md-toast.md-default-theme.md-hue-3 .md-toast-content .md-button, md-toast.md-hue-3 .md-toast-content .md-button{color:rgb(250,250,250)}.md-panel.md-tooltip.md-default-theme.md-hue-3, .md-panel.md-tooltip.md-hue-3{color:rgba(255,255,255,0.87);background-color:rgb(97,97,97)}body.md-default-theme.md-hue-3, body.md-hue-3,html.md-default-theme.md-hue-3, html.md-hue-3{color:rgba(0,0,0,0.87);background-color:rgb(224,224,224)} \ No newline at end of file diff --git a/data/Dockerfiles/sogo/theme-blue.js b/data/Dockerfiles/sogo/theme-blue.js deleted file mode 100644 index 865443f9f0..0000000000 --- a/data/Dockerfiles/sogo/theme-blue.js +++ /dev/null @@ -1,103 +0,0 @@ -/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ - -(function() { - 'use strict'; - - angular.module('SOGo.Common') - .config(configure) - - /** - * @ngInject - */ - configure.$inject = ['$mdThemingProvider']; - function configure($mdThemingProvider) { - - // Overwrite values to prevent flipping colors on login screen - $mdThemingProvider.definePalette('mailcow-blue', { - '50': 'E3F2FD', - '100': 'BBDEFB', - '200': '90CAF9', - '300': '64B5F6', - '400': '42A5F5', - '500': '2196F3', - '600': '1E88E5', - '700': '1976D2', - '800': '1565C0', - '900': '0D47A1', - '1000': '0D47A1', - 'A100': '82B1FF', - 'A200': '448AFF', - 'A400': '2979ff', - 'A700': '2962ff', - 'contrastDefaultColor': 'dark', - 'contrastLightColors': ['700', '800', '900'], - 'contrastDarkColors': undefined - }); - - $mdThemingProvider.definePalette('sogo-green', { - '50': 'E3F2FD', - '100': 'BBDEFB', - '200': '90CAF9', - '300': '64B5F6', - '400': '42A5F5', - '500': '2196F3', - '600': '1E88E5', - '700': '1976D2', - '800': '1565C0', - '900': '0D47A1', - '1000': '0D47A1', - 'A100': '82B1FF', - 'A200': '448AFF', - 'A400': '2979ff', - 'A700': '2962ff', - 'contrastDefaultColor': 'dark', - 'contrastLightColors': ['700', '800', '900'], - 'contrastDarkColors': undefined - }); - - $mdThemingProvider.definePalette('default', { - '50': 'E3F2FD', - '100': 'BBDEFB', - '200': '90CAF9', - '300': '64B5F6', - '400': '42A5F5', - '500': '2196F3', - '600': '1E88E5', - '700': '1976D2', - '800': '1565C0', - '900': '0D47A1', - '1000': '0D47A1', - 'A100': '82B1FF', - 'A200': '448AFF', - 'A400': '2979ff', - 'A700': '2962ff', - 'contrastDefaultColor': 'dark', - 'contrastLightColors': ['700', '800', '900'], - 'contrastDarkColors': undefined - }); - - $mdThemingProvider.theme('default') - .primaryPalette('mailcow-blue', { - 'default': '700', // top toolbar - 'hue-1': '500', - 'hue-2': '700', // sidebar toolbar - 'hue-3': 'A200' - }) - .accentPalette('mailcow-blue', { - 'default': '800', // fab buttons - 'hue-1': '50', // center list toolbar - 'hue-2': '500', - 'hue-3': 'A700' - }) - .backgroundPalette('grey', { - 'default': '50', // center list background - 'hue-1': '100', - 'hue-2': '200', - 'hue-3': '300' - }); - - $mdThemingProvider.setDefaultTheme('default'); - $mdThemingProvider.generateThemesOnDemand(false); - $mdThemingProvider.alwaysWatchTheme(true); - } -})(); diff --git a/data/Dockerfiles/unbound/Dockerfile b/data/Dockerfiles/unbound/Dockerfile index 8e056a935b..b1ec99e4fa 100644 --- a/data/Dockerfiles/unbound/Dockerfile +++ b/data/Dockerfiles/unbound/Dockerfile @@ -1,20 +1,39 @@ -FROM alpine:3.8 +FROM alpine:3.23 -LABEL maintainer "Andre Peters " +LABEL maintainer = "The Infrastructure Company GmbH " +# install unbound from alpine:edge to get security patches +RUN apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/main unbound + +# install other packages from regular alpine stable repo RUN apk add --update --no-cache \ curl \ - unbound \ + bind-tools \ + coreutils \ bash \ openssl \ drill \ + tzdata \ + syslog-ng \ + supervisor \ && curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache \ && chown root:unbound /etc/unbound \ - && adduser unbound tty \ + && adduser unbound tty \ && chmod 775 /etc/unbound EXPOSE 53/udp 53/tcp COPY docker-entrypoint.sh /docker-entrypoint.sh +# healthcheck (dig, ping) +COPY healthcheck.sh /healthcheck.sh +COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf +COPY supervisord.conf /etc/supervisor/supervisord.conf +COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh + +RUN chmod +x /healthcheck.sh +HEALTHCHECK --interval=30s --timeout=10s \ + CMD sh -c '[ -f /tmp/healthcheck_status ] && [ "$(cat /tmp/healthcheck_status)" -eq 0 ] || exit 1' + ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] diff --git a/data/Dockerfiles/unbound/docker-entrypoint.sh b/data/Dockerfiles/unbound/docker-entrypoint.sh index d179eacab3..bb9c1150b0 100755 --- a/data/Dockerfiles/unbound/docker-entrypoint.sh +++ b/data/Dockerfiles/unbound/docker-entrypoint.sh @@ -8,4 +8,13 @@ echo "Receiving anchor key..." echo "Receiving root hints..." curl -#o /etc/unbound/root.hints https://www.internic.net/domain/named.cache /usr/sbin/unbound-control-setup + +# Run hooks +for file in /hooks/*; do + if [ -x "${file}" ]; then + echo "Running hook ${file}" + "${file}" + fi +done + exec "$@" diff --git a/data/Dockerfiles/unbound/healthcheck.sh b/data/Dockerfiles/unbound/healthcheck.sh new file mode 100644 index 0000000000..7d91811270 --- /dev/null +++ b/data/Dockerfiles/unbound/healthcheck.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +STATUS_FILE="/tmp/healthcheck_status" +RUNS=0 + +# Declare log function for logfile to stdout +function log_to_stdout() { +echo "$(date +"%Y-%m-%d %H:%M:%S"): $1" +} + +# General Ping function to check general pingability +function check_ping() { +declare -a ipstoping=("1.1.1.1" "8.8.8.8" "9.9.9.9") +local fail_tolerance=1 +local failures=0 + +for ip in "${ipstoping[@]}" ; do + success=false + for ((i=1; i<=3; i++)); do + ping -q -c 3 -w 5 "$ip" > /dev/null + if [ $? -eq 0 ]; then + success=true + break + else + log_to_stdout "Healthcheck: Failed to ping $ip on attempt $i. Trying again..." + fi + done + + if [ "$success" = false ]; then + log_to_stdout "Healthcheck: Couldn't ping $ip after 3 attempts. Marking this IP as failed." + ((failures++)) + fi +done + +if [ $failures -gt $fail_tolerance ]; then + log_to_stdout "Healthcheck: Too many ping failures ($fail_tolerance failures allowed, you got $failures failures), marking Healthcheck as unhealthy..." + return 1 +fi + +return 0 + +} + +# General DNS Resolve Check against Unbound Resolver himself +function check_dns() { +declare -a domains=("fuzzy.mailcow.email" "github.com" "hub.docker.com") +local fail_tolerance=1 +local failures=0 + +for domain in "${domains[@]}" ; do + success=false + for ((i=1; i<=3; i++)); do + dig_output=$(dig +short +timeout=2 +tries=1 "$domain" @127.0.0.1 2>/dev/null) + dig_rc=$? + + if [ $dig_rc -ne 0 ] || [ -z "$dig_output" ]; then + log_to_stdout "Healthcheck: DNS Resolution Failed on attempt $i for $domain! Trying again..." + else + success=true + break + fi + done + + if [ "$success" = false ]; then + log_to_stdout "Healthcheck: DNS Resolution not possible after 3 attempts for $domain... Gave up!" + ((failures++)) + fi +done + +if [ $failures -gt $fail_tolerance ]; then + log_to_stdout "Healthcheck: Too many DNS failures ($fail_tolerance failures allowed, you got $failures failures), marking Healthcheck as unhealthy..." + return 1 +fi + +return 0 +} + +while true; do + + if [[ ${SKIP_UNBOUND_HEALTHCHECK} == "y" ]]; then + log_to_stdout "Healthcheck: ALL CHECKS WERE SKIPPED! Unbound is healthy!" + echo "0" > $STATUS_FILE + sleep 365d + fi + + # run checks, if check is not returning 0 (return value if check is ok), healthcheck will exit with 1 (marked in docker as unhealthy) + check_ping + PING_STATUS=$? + + check_dns + DNS_STATUS=$? + + if [ $PING_STATUS -ne 0 ] || [ $DNS_STATUS -ne 0 ]; then + echo "1" > $STATUS_FILE + + else + echo "0" > $STATUS_FILE + fi + + sleep 30 + +done \ No newline at end of file diff --git a/data/Dockerfiles/unbound/stop-supervisor.sh b/data/Dockerfiles/unbound/stop-supervisor.sh new file mode 100755 index 0000000000..acd402738a --- /dev/null +++ b/data/Dockerfiles/unbound/stop-supervisor.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +printf "READY\n"; + +while read line; do + echo "Processing Event: $line" >&2; + kill -3 $(cat "/var/run/supervisord.pid") +done < /dev/stdin + +rm -rf /tmp/healthcheck_status \ No newline at end of file diff --git a/data/Dockerfiles/unbound/supervisord.conf b/data/Dockerfiles/unbound/supervisord.conf new file mode 100644 index 0000000000..b47c8b11b3 --- /dev/null +++ b/data/Dockerfiles/unbound/supervisord.conf @@ -0,0 +1,32 @@ +[supervisord] +nodaemon=true +user=root +pidfile=/var/run/supervisord.pid + +[program:syslog-ng] +command=/usr/sbin/syslog-ng --foreground --no-caps +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autostart=true + +[program:unbound] +command=/usr/sbin/unbound +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=true + +[program:unbound-healthcheck] +command=/bin/bash /healthcheck.sh +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=true + +[eventlistener:processes] +command=/usr/local/sbin/stop-supervisor.sh +events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL diff --git a/data/Dockerfiles/unbound/syslog-ng.conf b/data/Dockerfiles/unbound/syslog-ng.conf new file mode 100644 index 0000000000..de858f9e07 --- /dev/null +++ b/data/Dockerfiles/unbound/syslog-ng.conf @@ -0,0 +1,21 @@ +@version: 4.5 +@include "scl.conf" +options { + chain_hostnames(off); + flush_lines(0); + use_dns(no); + use_fqdn(no); + owner("root"); group("adm"); perm(0640); + stats(freq(0)); + keep_timestamp(no); + bad_hostname("^gconfd$"); +}; +source s_dgram { + unix-dgram("/dev/log"); + internal(); +}; +destination d_stdout { pipe("/dev/stdout"); }; +log { + source(s_dgram); + destination(d_stdout); +}; diff --git a/data/Dockerfiles/watchdog/Dockerfile b/data/Dockerfiles/watchdog/Dockerfile index 46130e5f70..54854ea1aa 100644 --- a/data/Dockerfiles/watchdog/Dockerfile +++ b/data/Dockerfiles/watchdog/Dockerfile @@ -1,5 +1,6 @@ -FROM alpine:3.6 -LABEL maintainer "Andrรฉ Peters " +FROM alpine:3.23 + +LABEL maintainer = "The Infrastructure Company GmbH " # Installation RUN apk add --update \ @@ -7,16 +8,19 @@ RUN apk add --update \ nagios-plugins-tcp \ nagios-plugins-http \ nagios-plugins-ping \ + mariadb-client \ curl \ bash \ + coreutils \ jq \ fcgi \ + openssl \ nagios-plugins-mysql \ - nagios-plugins-dns \ nagios-plugins-disk \ bind-tools \ redis \ perl \ + perl-net-dns \ perl-io-socket-ssl \ perl-io-socket-inet6 \ perl-socket \ @@ -25,11 +29,14 @@ RUN apk add --update \ perl-term-readkey \ tini \ tzdata \ - && curl https://raw.githubusercontent.com/mludvig/smtp-cli/v3.9/smtp-cli -o /smtp-cli \ - && chmod +x smtp-cli + whois \ + && curl https://raw.githubusercontent.com/mludvig/smtp-cli/v3.10/smtp-cli -o /smtp-cli \ + && chmod +x smtp-cli \ + && mkdir /usr/lib/mailcow COPY watchdog.sh /watchdog.sh +COPY check_mysql_slavestatus.sh /usr/lib/nagios/plugins/check_mysql_slavestatus.sh +COPY check_dns.sh /usr/lib/mailcow/check_dns.sh +COPY client.cnf /etc/my.cnf.d/client.cnf -ENTRYPOINT ["/sbin/tini", "-g", "--"] -# Less verbose -CMD /watchdog.sh 2> /dev/null +CMD ["/watchdog.sh"] diff --git a/data/Dockerfiles/watchdog/check_dns.sh b/data/Dockerfiles/watchdog/check_dns.sh new file mode 100755 index 0000000000..ba430fe95b --- /dev/null +++ b/data/Dockerfiles/watchdog/check_dns.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +while getopts "H:s:" opt; do + case "$opt" in + H) HOST="$OPTARG" ;; + s) SERVER="$OPTARG" ;; + *) echo "Usage: $0 -H host -s server"; exit 3 ;; + esac +done + +if [ -z "$SERVER" ]; then + echo "No DNS Server provided" + exit 3 +fi + +if [ -z "$HOST" ]; then + echo "No host to test provided" + exit 3 +fi + +# run dig and measure the time it takes to run +START_TIME=$(perl -MTime::HiRes -e 'print Time::HiRes::time') +dig_output=$(dig +short +timeout=2 +tries=1 "$HOST" @"$SERVER" 2>/dev/null) +dig_rc=$? +END_TIME=$(perl -MTime::HiRes -e 'print Time::HiRes::time') +dig_output_ips=$(echo "$dig_output" | grep -E '^[0-9.]+$' | sort | paste -sd ',' -) +ELAPSED_TIME=$(perl -e "printf('%.3f', $END_TIME - $START_TIME)") + +# validate and perform nagios like output and exit codes +if [ $dig_rc -ne 0 ] || [ -z "$dig_output" ]; then + echo "Domain $HOST was not found by the server" + exit 2 +elif [ $dig_rc -eq 0 ]; then + echo "DNS OK: $ELAPSED_TIME seconds response time. $HOST returns $dig_output_ips" + exit 0 +else + echo "Unknown error" + exit 3 +fi diff --git a/data/Dockerfiles/watchdog/check_mysql_slavestatus.sh b/data/Dockerfiles/watchdog/check_mysql_slavestatus.sh new file mode 100755 index 0000000000..42eec59bb4 --- /dev/null +++ b/data/Dockerfiles/watchdog/check_mysql_slavestatus.sh @@ -0,0 +1,223 @@ +#!/bin/bash +######################################################################### +# Script: check_mysql_slavestatus.sh # +# Author: Claudio Kuenzler www.claudiokuenzler.com # +# Purpose: Monitor MySQL Replication status with Nagios # +# Description: Connects to given MySQL hosts and checks for running # +# SLAVE state and delivers additional info # +# Original: This script is a modified version of # +# check mysql slave sql running written by dhirajt # +# Thanks to: Victor Balada Diaz for his ideas added on 20080930 # +# Soren Klintrup for stuff added on 20081015 # +# Marc Feret for Slave_IO_Running check 20111227 # +# Peter Lecki for his mods added on 20120803 # +# Serge Victor for his mods added on 20131223 # +# Omri Bahumi for his fix added on 20131230 # +# Marc Falzon for his option mods added on 20190822 # +# Andreas Pfeiffer for adding socket option on 20190822 # +# History: # +# 2008041700 Original Script modified # +# 2008041701 Added additional info if status OK # +# 2008041702 Added usage of script with params -H -u -p # +# 2008041703 Added bindir variable for multiple platforms # +# 2008041704 Added help because mankind needs help # +# 2008093000 Using /bin/sh instead of /bin/bash # +# 2008093001 Added port for MySQL server # +# 2008093002 Added mysqldir if mysql binary is elsewhere # +# 2008101501 Changed bindir/mysqldir to use PATH # +# 2008101501 Use $() instead of `` to avoid forks # +# 2008101501 Use ${} for variables to prevent problems # +# 2008101501 Check if required commands exist # +# 2008101501 Check if mysql connection works # +# 2008101501 Exit with unknown status at script end # +# 2008101501 Also display help if no option is given # +# 2008101501 Add warning/critical check to delay # +# 2011062200 Add perfdata # +# 2011122700 Checking Slave_IO_Running # +# 2012080300 Changed to use only one mysql query # +# 2012080301 Added warn and crit delay as optional args # +# 2012080302 Added standard -h option for syntax help # +# 2012080303 Added check for mandatory options passed in # +# 2012080304 Added error output from mysql # +# 2012080305 Changed from 'cut' to 'awk' (eliminate ws) # +# 2012111600 Do not show password in error output # +# 2013042800 Changed PATH to use existing PATH, too # +# 2013050800 Bugfix in PATH export # +# 2013092700 Bugfix in PATH export # +# 2013092701 Bugfix in getopts # +# 2013101600 Rewrite of threshold logic and handling # +# 2013101601 Optical clean up # +# 2013101602 Rewrite help output # +# 2013101700 Handle Slave IO in 'Connecting' state # +# 2013101701 Minor changes in output, handling UNKNOWN situations now # +# 2013101702 Exit CRITICAL when Slave IO in Connecting state # +# 2013123000 Slave_SQL_Running also matched Slave_SQL_Running_State # +# 2015011600 Added 'moving' check to catch possible connection issues # +# 2015011900 Use its own threshold for replication moving check # +# 2019082200 Add support for mysql option file # +# 2019082201 Improve password security (remove from mysql cli) # +# 2019082202 Added socket parameter (-S) # +# 2019082203 Use default port 3306, makes -P optional # +# 2019082204 Fix moving subcheck, improve documentation # +######################################################################### +# Usage: ./check_mysql_slavestatus.sh (-o file|(-H dbhost [-P port]|-S socket) -u dbuser -p dbpass) [-s connection] [-w integer] [-c integer] [-m integer] +######################################################################### +help="\ncheck_mysql_slavestatus.sh (c) 2008-2019 GNU GPLv2 licence +Usage: $0 (-o file|(-H dbhost [-P port]|-S socket) -u username -p password) [-s connection] [-w integer] [-c integer] [-m]\n +Options:\n-o Path to option file containing connection settings (e.g. /home/nagios/.my.cnf). Note: If this option is used, -H, -u, -p parameters will become optional\n-H Hostname or IP of slave server\n-P MySQL Port of slave server (optional, defaults to 3306)\n-u Username of DB-user\n-p Password of DB-user\n-S database socket\n-s Connection name (optional, with multi-source replication)\n-w Replication delay in seconds for Warning status (optional)\n-c Replication delay in seconds for Critical status (optional)\n-m Threshold in seconds since when replication did not move (compares the slaves log position)\n +Attention: The DB-user you type in must have CLIENT REPLICATION rights on the DB-server. Example:\n\tGRANT REPLICATION CLIENT on *.* TO 'nagios'@'%' IDENTIFIED BY 'secret';" + +STATE_OK=0 # define the exit code if status is OK +STATE_WARNING=1 # define the exit code if status is Warning (not really used) +STATE_CRITICAL=2 # define the exit code if status is Critical +STATE_UNKNOWN=3 # define the exit code if status is Unknown +export PATH=$PATH:/usr/local/bin:/usr/bin:/bin # Set path +crit="No" # what is the answer of MySQL Slave_SQL_Running for a Critical status? +ok="Yes" # what is the answer of MySQL Slave_SQL_Running for an OK status? +port="-P 3306" # on which tcp port is the target MySQL slave listening? + +for cmd in mysql awk grep expr [ +do + if ! `which ${cmd} &>/dev/null` + then + echo "UNKNOWN: This script requires the command '${cmd}' but it does not exist; please check if command exists and PATH is correct" + exit ${STATE_UNKNOWN} + fi +done + +# Check for people who need help +######################################################################### +if [ "${1}" = "--help" -o "${#}" = "0" ]; + then + echo -e "${help}"; + exit 1; +fi + +# Important given variables for the DB-Connect +######################################################################### +while getopts "H:P:u:p:S:s:w:c:o:m:h" Input; +do + case ${Input} in + H) host="-h ${OPTARG}";slavetarget=${OPTARG};; + P) port="-P ${OPTARG}";; + u) user="-u ${OPTARG}";; + p) password="${OPTARG}"; export MYSQL_PWD="${OPTARG}";; + S) socket="-S ${OPTARG}";; + s) connection=\"${OPTARG}\";; + w) warn_delay=${OPTARG};; + c) crit_delay=${OPTARG};; + o) optfile="--defaults-extra-file=${OPTARG}";; + m) moving=${OPTARG};; + h) echo -e "${help}"; exit 1;; + \?) echo "Wrong option given. Check help (-h, --help) for usage." + exit 1 + ;; + esac +done + +# Check if we can write to tmp +######################################################################### +test -w /tmp && tmpfile="/tmp/mysql_slave_${slavetarget}_pos.txt" + +# Connect to the DB server and check for informations +######################################################################### +# Check whether all required arguments were passed in (either option file or full connection settings) +if [[ -z "${optfile}" && -z "${host}" && -z "${socket}" ]]; then + echo -e "Missing required parameter(s)"; exit ${STATE_UNKNOWN} +elif [[ -n "${host}" && (-z "${user}" || -z "${password}") ]]; then + echo -e "Missing required parameter(s)"; exit ${STATE_UNKNOWN} +elif [[ -n "${socket}" && (-z "${user}" || -z "${password}") ]]; then + echo -e "Missing required parameter(s)"; exit ${STATE_UNKNOWN} +fi + +# Connect to the DB server and store output in vars +if [[ -n $socket ]]; then + ConnectionResult=$(mariadb --skip-ssl ${optfile} ${socket} ${user} -e "show slave ${connection} status\G" 2>&1) +else + ConnectionResult=$(mariadb --skip-ssl ${optfile} ${host} ${port} ${user} -e "show slave ${connection} status\G" 2>&1) +fi + +if [ -z "`echo "${ConnectionResult}" |grep Slave_IO_State`" ]; then + echo -e "CRITICAL: Unable to connect to server" + exit ${STATE_CRITICAL} +fi +check=`echo "${ConnectionResult}" |grep Slave_SQL_Running: | awk '{print $2}'` +checkio=`echo "${ConnectionResult}" |grep Slave_IO_Running: | awk '{print $2}'` +masterinfo=`echo "${ConnectionResult}" |grep Master_Host: | awk '{print $2}'` +delayinfo=`echo "${ConnectionResult}" |grep Seconds_Behind_Master: | awk '{print $2}'` +readpos=`echo "${ConnectionResult}" |grep Read_Master_Log_Pos: | awk '{print $2}'` +execpos=`echo "${ConnectionResult}" |grep Exec_Master_Log_Pos: | awk '{print $2}'` + +# Output of different exit states +######################################################################### +if [ ${check} = "NULL" ]; then +echo "CRITICAL: Slave_SQL_Running is answering NULL"; exit ${STATE_CRITICAL}; +fi + +if [ ${check} = ${crit} ]; then +echo "CRITICAL: ${host}:${port} Slave_SQL_Running: ${check}"; exit ${STATE_CRITICAL}; +fi + +if [ ${checkio} = ${crit} ]; then +echo "CRITICAL: ${host} Slave_IO_Running: ${checkio}"; exit ${STATE_CRITICAL}; +fi + +if [ ${checkio} = "Connecting" ]; then +echo "CRITICAL: ${host} Slave_IO_Running: ${checkio}"; exit ${STATE_CRITICAL}; +fi + +if [ ${check} = ${ok} ] && [ ${checkio} = ${ok} ]; then + # Delay thresholds are set + if [[ -n ${warn_delay} ]] && [[ -n ${crit_delay} ]]; then + if ! [[ ${warn_delay} -gt 0 ]]; then echo "Warning threshold must be a valid integer greater than 0"; exit $STATE_UNKNOWN; fi + if ! [[ ${crit_delay} -gt 0 ]]; then echo "Warning threshold must be a valid integer greater than 0"; exit $STATE_UNKNOWN; fi + if [[ -z ${warn_delay} ]] || [[ -z ${crit_delay} ]]; then echo "Both warning and critical thresholds must be set"; exit $STATE_UNKNOWN; fi + if [[ ${warn_delay} -gt ${crit_delay} ]]; then echo "Warning threshold cannot be greater than critical"; exit $STATE_UNKNOWN; fi + + if [[ ${delayinfo} -ge ${crit_delay} ]] + then echo "CRITICAL: Slave is ${delayinfo} seconds behind Master | delay=${delayinfo}s"; exit ${STATE_CRITICAL} + elif [[ ${delayinfo} -ge ${warn_delay} ]] + then echo "WARNING: Slave is ${delayinfo} seconds behind Master | delay=${delayinfo}s"; exit ${STATE_WARNING} + else + # Everything looks OK here but now let us check if the replication is moving + if [[ -n ${moving} ]] && [[ -n ${tmpfile} ]] && [[ $readpos -eq $execpos ]] + then + #echo "Debug: Read pos is $readpos - Exec pos is $execpos" + # Check if tmp file exists + curtime=`date +%s` + if [[ -w $tmpfile ]] + then + tmpfiletime=`date +%s -r $tmpfile` + if [[ `expr $curtime - $tmpfiletime` -gt ${moving} ]] + then + exectmp=`cat $tmpfile` + #echo "Debug: Exec pos in tmpfile is $exectmp" + if [[ $exectmp -eq $execpos ]] + then + # The value read from the tmp file and from db are the same. Replication hasnt moved! + echo "WARNING: Slave replication has not moved in ${moving} seconds. Manual check required."; exit ${STATE_WARNING} + else + # Replication has moved since the tmp file was written. Delete tmp file and output OK. + rm $tmpfile + echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s"; exit ${STATE_OK}; + fi + else + echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s"; exit ${STATE_OK}; + fi + else + echo "$execpos" > $tmpfile + echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s"; exit ${STATE_OK}; + fi + else # Everything OK (no additional moving check) + echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s"; exit ${STATE_OK}; + fi + fi + else + # Without delay thresholds + echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s" + exit ${STATE_OK}; + fi +fi + +echo "UNKNOWN: should never reach this part (Slave_SQL_Running is ${check}, Slave_IO_Running is ${checkio})" +exit ${STATE_UNKNOWN} diff --git a/data/Dockerfiles/watchdog/client.cnf b/data/Dockerfiles/watchdog/client.cnf new file mode 100644 index 0000000000..f078f3cbe0 --- /dev/null +++ b/data/Dockerfiles/watchdog/client.cnf @@ -0,0 +1,3 @@ +[client] +ssl = false +ssl-verify-server-cert = false diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh index ab528a78cf..020f3f8388 100755 --- a/data/Dockerfiles/watchdog/watchdog.sh +++ b/data/Dockerfiles/watchdog/watchdog.sh @@ -1,10 +1,20 @@ #!/bin/bash +if [ "${DEV_MODE}" != "n" ]; then + echo -e "\e[31mEnabled Debug Mode\e[0m" + set -x +fi + trap "exit" INT TERM trap "kill 0" EXIT # Prepare BACKGROUND_TASKS=() +echo "Waiting for containers to settle..." +for i in {30..1}; do + echo "${i}" + sleep 1 +done if [[ "${USE_WATCHDOG}" =~ ^([nN][oO]|[nN])+$ ]]; then echo -e "$(date) - USE_WATCHDOG=n, skipping watchdog..." @@ -12,12 +22,64 @@ if [[ "${USE_WATCHDOG}" =~ ^([nN][oO]|[nN])+$ ]]; then exec $(readlink -f "$0") fi +if [[ "${WATCHDOG_VERBOSE}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + SMTP_VERBOSE="--verbose" + CURL_VERBOSE="--verbose" + set -xv +else + SMTP_VERBOSE="" + CURL_VERBOSE="" + exec 2>/dev/null +fi + # Checks pipe their corresponding container name in this pipe if [[ ! -p /tmp/com_pipe ]]; then mkfifo /tmp/com_pipe fi +# Wait for containers +while ! mariadb-admin status --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do + echo "Waiting for SQL..." + sleep 2 +done + +# Do not attempt to write to slave +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning" +else + REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning" +fi + +until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do + echo "Waiting for Redis..." + sleep 2 +done + +${REDIS_CMDLINE} DEL F2B_RES > /dev/null + # Common functions +get_ipv6(){ + local IPV6= + local IPV6_SRCS= + local TRY= + IPV6_SRCS[0]="ip6.mailcow.email" + IPV6_SRCS[1]="ip6.nevondo.com" + until [[ ! -z ${IPV6} ]] || [[ ${TRY} -ge 10 ]]; do + IPV6=$(curl --connect-timeout 3 -m 10 -L6s ${IPV6_SRCS[$RANDOM % ${#IPV6_SRCS[@]} ]} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") + [[ ! -z ${TRY} ]] && sleep 1 + TRY=$((TRY+1)) + done + echo ${IPV6} +} + +array_diff() { + # https://stackoverflow.com/questions/2312762, Alex Offshore + eval local ARR1=\(\"\${$2[@]}\"\) + eval local ARR2=\(\"\${$3[@]}\"\) + local IFS=$'\n' + mapfile -t $1 < <(comm -23 <(echo "${ARR1[*]}" | sort) <(echo "${ARR2[*]}" | sort)) +} + progress() { SERVICE=${1} TOTAL=${2} @@ -28,35 +90,102 @@ progress() { [[ ${CURRENT} -gt ${TOTAL} ]] && return [[ ${CURRENT} -lt 0 ]] && CURRENT=0 PERCENT=$(( 200 * ${CURRENT} / ${TOTAL} % 2 + 100 * ${CURRENT} / ${TOTAL} )) - redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"service\":\"${SERVICE}\",\"lvl\":\"${PERCENT}\",\"hpnow\":\"${CURRENT}\",\"hptotal\":\"${TOTAL}\",\"hpdiff\":\"${DIFF}\"}" > /dev/null + ${REDIS_CMDLINE} LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"service\":\"${SERVICE}\",\"lvl\":\"${PERCENT}\",\"hpnow\":\"${CURRENT}\",\"hptotal\":\"${TOTAL}\",\"hpdiff\":\"${DIFF}\"}" > /dev/null log_msg "${SERVICE} health level: ${PERCENT}% (${CURRENT}/${TOTAL}), health trend: ${DIFF}" no_redis + # Return 10 to indicate a dead service + [ ${CURRENT} -le 0 ] && return 10 } log_msg() { if [[ ${2} != "no_redis" ]]; then - redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \ - tr '%&;$"_[]{}-\r\n' ' ')\"}" > /dev/null + ${REDIS_CMDLINE} LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \ + tr '\r\n%&;$"_[]{}-' ' ')\"}" > /dev/null fi echo $(date) $(printf '%s\n' "${1}") } -function mail_error() { +function notify_error() { + # Check if one of the notification options is enabled + [[ -z ${WATCHDOG_NOTIFY_EMAIL} ]] && [[ -z ${WATCHDOG_NOTIFY_WEBHOOK} ]] && return 0 + THROTTLE= [[ -z ${1} ]] && return 1 - [[ -z ${2} ]] && return 2 - RCPT_DOMAIN=$(echo ${1} | awk -F @ {'print $NF'}) - RCPT_MX=$(dig +short ${RCPT_DOMAIN} mx | sort -n | awk '{print $2; exit}') - if [[ -z ${RCPT_MX} ]]; then - log_msg "Cannot determine MX for ${1}, skipping email notification..." - return 1 + # If exists, body will be the content of "/tmp/${1}", even if ${2} is set + [[ -z ${2} ]] && BODY="Service was restarted on $(date), please check your mailcow installation." || BODY="$(date) - ${2}" + # If exists, mail will be throttled by argument in seconds + [[ ! -z ${3} ]] && THROTTLE=${3} + if [[ ! -z ${THROTTLE} ]]; then + TTL_LEFT="$(${REDIS_CMDLINE} TTL THROTTLE_${1} 2> /dev/null)" + if [[ "${TTL_LEFT}" == "-2" ]]; then + # Delay key not found, setting a delay key now + ${REDIS_CMDLINE} SET THROTTLE_${1} 1 EX ${THROTTLE} + else + log_msg "Not sending notification email now, blocked for ${TTL_LEFT} seconds..." + return 1 + fi + fi + WATCHDOG_NOTIFY_EMAIL=$(echo "${WATCHDOG_NOTIFY_EMAIL}" | sed 's/"//;s|"$||') + # Some exceptions for subject and body formats + if [[ ${1} == "fail2ban" ]]; then + SUBJECT="${BODY}" + BODY="Please see netfilter-mailcow for more details and triggered rules." + else + SUBJECT="${WATCHDOG_SUBJECT}: ${1}" + fi + + # Send mail notification if enabled + if [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]]; then + IFS=',' read -r -a MAIL_RCPTS <<< "${WATCHDOG_NOTIFY_EMAIL}" + for rcpt in "${MAIL_RCPTS[@]}"; do + RCPT_DOMAIN= + RCPT_MX= + RCPT_DOMAIN=$(echo ${rcpt} | awk -F @ {'print $NF'}) + CHECK_FOR_VALID_MX=$(dig +short ${RCPT_DOMAIN} mx) + if [[ -z ${CHECK_FOR_VALID_MX} ]]; then + log_msg "Cannot determine MX for ${rcpt}, skipping email notification..." + return 1 + fi + [ -f "/tmp/${1}" ] && BODY="/tmp/${1}" + timeout 10s ./smtp-cli --missing-modules-ok \ + "${SMTP_VERBOSE}" \ + --charset=UTF-8 \ + --subject="${SUBJECT}" \ + --body-plain="${BODY}" \ + --add-header="X-Priority: 1" \ + --to=${rcpt} \ + --from="watchdog@${MAILCOW_HOSTNAME}" \ + --hello-host=${MAILCOW_HOSTNAME} \ + --ipv4 + if [[ $? -eq 1 ]]; then # exit code 1 is fine + log_msg "Sent notification email to ${rcpt}" + else + if [[ "${SMTP_VERBOSE}" == "" ]]; then + log_msg "Error while sending notification email to ${rcpt}. You can enable verbose logging by setting 'WATCHDOG_VERBOSE=y' in mailcow.conf." + else + log_msg "Error while sending notification email to ${rcpt}." + fi + fi + done + fi + + # Send webhook notification if enabled + if [[ ! -z ${WATCHDOG_NOTIFY_WEBHOOK} ]]; then + if [[ -z ${WATCHDOG_NOTIFY_WEBHOOK_BODY} ]]; then + log_msg "No webhook body set, skipping webhook notification..." + return 1 + fi + + # Escape subject and body (https://stackoverflow.com/a/2705678) + ESCAPED_SUBJECT=$(echo ${SUBJECT} | sed -e 's/[\/&]/\\&/g') + ESCAPED_BODY=$(echo ${BODY} | sed -e 's/[\/&]/\\&/g') + + # Replace subject and body placeholders + WEBHOOK_BODY=$(echo ${WATCHDOG_NOTIFY_WEBHOOK_BODY} | sed -e "s/\$SUBJECT\|\${SUBJECT}/$ESCAPED_SUBJECT/g" -e "s/\$BODY\|\${BODY}/$ESCAPED_BODY/g") + + # POST to webhook + curl -X POST -H "Content-Type: application/json" ${CURL_VERBOSE} -d "${WEBHOOK_BODY}" ${WATCHDOG_NOTIFY_WEBHOOK} + + log_msg "Sent notification using webhook" fi - ./smtp-cli --missing-modules-ok \ - --subject="Watchdog: ${2} service hit the error rate limit" \ - --body-plain="Service was restarted, please check your mailcow installation." \ - --to=${1} \ - --from="watchdog@${MAILCOW_HOSTNAME}" \ - --server="${RCPT_MX}" \ - --hello-host=${MAILCOW_HOSTNAME} - log_msg "Sent notification email to ${1}" } get_container_ip() { @@ -66,50 +195,157 @@ get_container_ip() { CONTAINER_IP= LOOP_C=1 until [[ ${CONTAINER_IP} =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] || [[ ${LOOP_C} -gt 5 ]]; do - sleep 0.5 - # get long container id for exact match - CONTAINER_ID=($(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring == \"${1}\") | .id")) - # returned id can have multiple elements (if scaled), shuffle for random test - CONTAINER_ID=($(printf "%s\n" "${CONTAINER_ID[@]}" | shuf)) - if [[ ! -z ${CONTAINER_ID} ]]; then - for matched_container in "${CONTAINER_ID[@]}"; do - CONTAINER_IPS=($(curl --silent --insecure https://dockerapi/containers/${matched_container}/json | jq -r '.NetworkSettings.Networks[].IPAddress')) - for ip_match in "${CONTAINER_IPS[@]}"; do - # grep will do nothing if one of these vars is empty - [[ -z ${ip_match} ]] && continue - [[ -z ${IPV4_NETWORK} ]] && continue - # only return ips that are part of our network - if ! grep -q ${IPV4_NETWORK} <(echo ${ip_match}); then - continue - else - CONTAINER_IP=${ip_match} - break - fi + if [ ${IP_BY_DOCKER_API} -eq 0 ]; then + CONTAINER_IP=$(dig a "${1}" +short) + else + sleep 0.5 + # get long container id for exact match + CONTAINER_ID=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring == \"${1}\") | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id")) + # returned id can have multiple elements (if scaled), shuffle for random test + CONTAINER_ID=($(printf "%s\n" "${CONTAINER_ID[@]}" | shuf)) + if [[ ! -z ${CONTAINER_ID} ]]; then + for matched_container in "${CONTAINER_ID[@]}"; do + CONTAINER_IPS=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${matched_container}/json | jq -r '.NetworkSettings.Networks[].IPAddress')) + for ip_match in "${CONTAINER_IPS[@]}"; do + # grep will do nothing if one of these vars is empty + [[ -z ${ip_match} ]] && continue + [[ -z ${IPV4_NETWORK} ]] && continue + # only return ips that are part of our network + if ! grep -q ${IPV4_NETWORK} <(echo ${ip_match}); then + continue + else + CONTAINER_IP=${ip_match} + break + fi + done + [[ ! -z ${CONTAINER_IP} ]] && break done - [[ ! -z ${CONTAINER_IP} ]] && break - done + fi fi LOOP_C=$((LOOP_C + 1)) done [[ ${LOOP_C} -gt 5 ]] && echo 240.0.0.0 || echo ${CONTAINER_IP} } +# One-time check +if grep -qi "$(echo ${IPV6_NETWORK} | cut -d: -f1-3)" <<< "$(ip a s)"; then + if [[ -z "$(get_ipv6)" ]]; then + notify_error "ipv6-config" "enable_ipv6 is true in docker-compose.yml, but an IPv6 link could not be established. Please verify your IPv6 connection." + fi +fi + +external_checks() { + err_count=0 + diff_c=0 + THRESHOLD=${EXTERNAL_CHECKS_THRESHOLD} + # Reduce error count by 2 after restarting an unhealthy container + GUID=$(mariadb --skip-ssl -u${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'GUID'" -BN) + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + err_c_cur=${err_count} + CHECK_REPONSE="$(curl --connect-timeout 3 -m 10 -4 -s https://checks.mailcow.email -X POST -dguid=${GUID} 2> /dev/null)" + if [[ ! -z "${CHECK_REPONSE}" ]] && [[ "$(echo ${CHECK_REPONSE} | jq -r .response)" == "critical" ]]; then + echo ${CHECK_REPONSE} | jq -r .out > /tmp/external_checks + err_count=$(( ${err_count} + 1 )) + fi + CHECK_REPONSE6="$(curl --connect-timeout 3 -m 10 -6 -s https://checks.mailcow.email -X POST -dguid=${GUID} 2> /dev/null)" + if [[ ! -z "${CHECK_REPONSE6}" ]] && [[ "$(echo ${CHECK_REPONSE6} | jq -r .response)" == "critical" ]]; then + echo ${CHECK_REPONSE} | jq -r .out > /tmp/external_checks + err_count=$(( ${err_count} + 1 )) + fi + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "External checks" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 60 + else + diff_c=0 + sleep $(( ( RANDOM % 20 ) + 1800 )) + fi + done + return 1 +} + nginx_checks() { err_count=0 diff_c=0 - THRESHOLD=16 + THRESHOLD=${NGINX_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/nginx-mailcow; echo "$(tail -50 /tmp/nginx-mailcow)" > /tmp/nginx-mailcow host_ip=$(get_container_ip nginx-mailcow) err_c_cur=${err_count} - /usr/lib/nagios/plugins/check_ping -4 -H ${host_ip} -w 2000,10% -c 4000,100% -p2 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u / -p 8081 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u / -p 8081 2>> /tmp/nginx-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Nginx" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} - diff_c=0 - sleep $(( ( RANDOM % 30 ) + 10 )) + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi + done + return 1 +} + +unbound_checks() { + err_count=0 + diff_c=0 + THRESHOLD=${UNBOUND_THRESHOLD} + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/unbound-mailcow; echo "$(tail -50 /tmp/unbound-mailcow)" > /tmp/unbound-mailcow + host_ip=$(get_container_ip unbound-mailcow) + err_c_cur=${err_count} + /usr/lib/mailcow/check_dns.sh -s ${host_ip} -H stackoverflow.com 2>> /tmp/unbound-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + DNSSEC=$(dig com +dnssec | egrep 'flags:.+ad') + if [[ -z ${DNSSEC} ]]; then + echo "DNSSEC failure" 2>> /tmp/unbound-mailcow 1>&2 + err_count=$(( ${err_count} + 1)) + else + echo "DNSSEC check succeeded" 2>> /tmp/unbound-mailcow 1>&2 + fi + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "Unbound" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi + done + return 1 +} + +redis_checks() { + # A check for the local redis container + err_count=0 + diff_c=0 + THRESHOLD=${REDIS_THRESHOLD} + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/redis-mailcow; echo "$(tail -50 /tmp/redis-mailcow)" > /tmp/redis-mailcow + host_ip=$(get_container_ip redis-mailcow) + err_c_cur=${err_count} + /usr/lib/nagios/plugins/check_tcp -4 -H redis-mailcow -p 6379 -E -s "AUTH ${REDISPASS}\nPING\n" -q "QUIT" -e "PONG" 2>> /tmp/redis-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "Redis" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi done return 1 } @@ -117,19 +353,48 @@ nginx_checks() { mysql_checks() { err_count=0 diff_c=0 - THRESHOLD=12 + THRESHOLD=${MYSQL_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do - host_ip=$(get_container_ip mysql-mailcow) + touch /tmp/mysql-mailcow; echo "$(tail -50 /tmp/mysql-mailcow)" > /tmp/mysql-mailcow err_c_cur=${err_count} - /usr/lib/nagios/plugins/check_mysql -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_mysql_query -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} -q "SELECT COUNT(*) FROM information_schema.tables" 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_mysql -f /etc/my.cnf.d/client.cnf -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_mysql_query -f /etc/my.cnf.d/client.cnf -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} -q "SELECT COUNT(*) FROM information_schema.tables" 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "MySQL/MariaDB" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} - diff_c=0 - sleep $(( ( RANDOM % 30 ) + 10 )) + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi + done + return 1 +} + +mysql_repl_checks() { + err_count=0 + diff_c=0 + THRESHOLD=${MYSQL_REPLICATION_THRESHOLD} + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/mysql_repl_checks; echo "$(tail -50 /tmp/mysql_repl_checks)" > /tmp/mysql_repl_checks + err_c_cur=${err_count} + /usr/lib/nagios/plugins/check_mysql_slavestatus.sh -o /etc/my.cnf.d/client.cnf -S /var/run/mysqld/mysqld.sock -u root -p ${DBROOT} 2>> /tmp/mysql_repl_checks 1>&2; err_count=$(( ${err_count} + $? )) + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "MySQL/MariaDB replication" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 60 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi done return 1 } @@ -137,18 +402,24 @@ mysql_checks() { sogo_checks() { err_count=0 diff_c=0 - THRESHOLD=20 + THRESHOLD=${SOGO_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/sogo-mailcow; echo "$(tail -50 /tmp/sogo-mailcow)" > /tmp/sogo-mailcow host_ip=$(get_container_ip sogo-mailcow) err_c_cur=${err_count} - /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u /SOGo.index/ -p 20000 -R "SOGo\.MainUI" 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u /SOGo.index/ -p 20000 2>> /tmp/sogo-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "SOGo" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} - diff_c=0 - sleep $(( ( RANDOM % 30 ) + 10 )) + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi done return 1 } @@ -156,19 +427,75 @@ sogo_checks() { postfix_checks() { err_count=0 diff_c=0 - THRESHOLD=16 + THRESHOLD=${POSTFIX_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do - host_ip=$(get_container_ip postfix-mailcow) + touch /tmp/postfix-mailcow; echo "$(tail -50 /tmp/postfix-mailcow)" > /tmp/postfix-mailcow + host_ip=$(get_container_ip postfix-mailcow) err_c_cur=${err_count} - /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -f "watchdog@invalid" -C "RCPT TO:null@localhost" -C DATA -C . -R 250 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -S 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -f "watchdog@invalid" -C "RCPT TO:watchdog@localhost" -C DATA -C . -R 250 2>> /tmp/postfix-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -S 2>> /tmp/postfix-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Postfix" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} - diff_c=0 - sleep $(( ( RANDOM % 30 ) + 10 )) + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi + done + return 1 +} + +postfix-tlspol_checks() { + err_count=0 + diff_c=0 + THRESHOLD=${POSTFIX_TLSPOL_THRESHOLD} + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/postfix-tlspol-mailcow; echo "$(tail -50 /tmp/postfix-tlspol-mailcow)" > /tmp/postfix-tlspol-mailcow + host_ip=$(get_container_ip postfix-tlspol-mailcow) + err_c_cur=${err_count} + /usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 8642 2>> /tmp/postfix-tlspol-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "Postfix TLS Policy companion" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi + done + return 1 +} + +clamd_checks() { + err_count=0 + diff_c=0 + THRESHOLD=${CLAMD_THRESHOLD} + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/clamd-mailcow; echo "$(tail -50 /tmp/clamd-mailcow)" > /tmp/clamd-mailcow + host_ip=$(get_container_ip clamd-mailcow) + err_c_cur=${err_count} + /usr/lib/nagios/plugins/check_clamd -4 -H ${host_ip} 2>> /tmp/clamd-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "Clamd" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 120 ) + 20 )) + fi done return 1 } @@ -176,22 +503,77 @@ postfix_checks() { dovecot_checks() { err_count=0 diff_c=0 - THRESHOLD=24 + THRESHOLD=${DOVECOT_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/dovecot-mailcow; echo "$(tail -50 /tmp/dovecot-mailcow)" > /tmp/dovecot-mailcow host_ip=$(get_container_ip dovecot-mailcow) err_c_cur=${err_count} - /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 24 -f "watchdog@invalid" -C "RCPT TO:" -L -R "User doesn't exist" 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_imap -4 -H ${host_ip} -p 993 -S -e "OK " 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_imap -4 -H ${host_ip} -p 143 -e "OK " 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 10001 -e "VERSION" 1>&2; err_count=$(( ${err_count} + $? )) - /usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 4190 -e "Dovecot ready" 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 24 -f "watchdog@invalid" -C "RCPT TO:" -L -R "User doesn't exist" 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_imap -4 -H ${host_ip} -p 993 -S -e "OK " 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_imap -4 -H ${host_ip} -p 143 -e "OK " 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 10001 -e "VERSION" 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 4190 -e "Dovecot ready" 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Dovecot" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} - diff_c=0 - sleep $(( ( RANDOM % 30 ) + 10 )) + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi + done + return 1 +} + +dovecot_repl_checks() { + err_count=0 + diff_c=0 + THRESHOLD=${DOVECOT_REPL_THRESHOLD} + D_REPL_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning -r GET DOVECOT_REPL_HEALTH) + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + err_c_cur=${err_count} + D_REPL_STATUS=$(redis-cli --raw -h redis -a ${REDISPASS} --no-auth-warning GET DOVECOT_REPL_HEALTH) + if [[ "${D_REPL_STATUS}" != "1" ]]; then + err_count=$(( ${err_count} + 1 )) + fi + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "Dovecot replication" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 60 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi + done + return 1 +} + +cert_checks() { + err_count=0 + diff_c=0 + THRESHOLD=7 + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/certcheck; echo "$(tail -50 /tmp/certcheck)" > /tmp/certcheck + host_ip_postfix=$(get_container_ip postfix) + host_ip_dovecot=$(get_container_ip dovecot) + err_c_cur=${err_count} + /usr/lib/nagios/plugins/check_smtp -H ${host_ip_postfix} -p 589 -4 -S -D 7 2>> /tmp/certcheck 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_imap -H ${host_ip_dovecot} -p 993 -4 -S -D 7 2>> /tmp/certcheck 1>&2; err_count=$(( ${err_count} + $? )) + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "Primary certificate expiry check" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + # Always sleep 5 minutes, mail notifications are limited + sleep 300 done return 1 } @@ -199,20 +581,157 @@ dovecot_checks() { phpfpm_checks() { err_count=0 diff_c=0 - THRESHOLD=10 + THRESHOLD=${PHPFPM_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/php-fpm-mailcow; echo "$(tail -50 /tmp/php-fpm-mailcow)" > /tmp/php-fpm-mailcow host_ip=$(get_container_ip php-fpm-mailcow) err_c_cur=${err_count} - nc -z ${host_ip} 9001 ; err_count=$(( ${err_count} + ($? * 2))) - nc -z ${host_ip} 9002 ; err_count=$(( ${err_count} + ($? * 2))) - /usr/lib/nagios/plugins/check_ping -4 -H ${host_ip} -w 2000,10% -c 4000,100% -p2 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_tcp -H ${host_ip} -p 9001 2>> /tmp/php-fpm-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + /usr/lib/nagios/plugins/check_tcp -H ${host_ip} -p 9002 2>> /tmp/php-fpm-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "PHP-FPM" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} - diff_c=0 - sleep $(( ( RANDOM % 30 ) + 10 )) + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi + done + return 1 +} + +ratelimit_checks() { + err_count=0 + diff_c=0 + THRESHOLD=${RATELIMIT_THRESHOLD} + RL_LOG_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning LRANGE RL_LOG 0 0 | jq .qid) + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + err_c_cur=${err_count} + RL_LOG_STATUS_PREV=${RL_LOG_STATUS} + RL_LOG_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning LRANGE RL_LOG 0 0 | jq .qid) + if [[ ${RL_LOG_STATUS_PREV} != ${RL_LOG_STATUS} ]]; then + err_count=$(( ${err_count} + 1 )) + echo 'Last 10 applied ratelimits (may overlap with previous reports).' > /tmp/ratelimit + echo 'Full ratelimit buckets can be emptied by deleting the ratelimit hash from within mailcow UI (see /debug -> Protocols -> Ratelimit):' >> /tmp/ratelimit + echo >> /tmp/ratelimit + redis-cli --raw -h redis -a ${REDISPASS} --no-auth-warning LRANGE RL_LOG 0 10 | jq . >> /tmp/ratelimit + fi + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "Ratelimit" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi + done + return 1 +} + +mailq_checks() { + err_count=0 + diff_c=0 + THRESHOLD=${MAILQ_THRESHOLD} + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/mail_queue_status; echo "$(tail -50 /tmp/mail_queue_status)" > /tmp/mail_queue_status + MAILQ_LOG_STATUS=$(find /var/spool/postfix/deferred -type f | wc -l) + echo "Mail queue contains ${MAILQ_LOG_STATUS} items (critical limit is ${MAILQ_CRIT}) at $(date)" >> /tmp/mail_queue_status + err_c_cur=${err_count} + if [ ${MAILQ_LOG_STATUS} -ge ${MAILQ_CRIT} ]; then + err_count=$(( ${err_count} + 1 )) + echo "Mail queue contains ${MAILQ_LOG_STATUS} items (critical limit is ${MAILQ_CRIT}) at $(date)" >> /tmp/mail_queue_status + fi + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "Mail queue" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 60 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi + done + return 1 +} + +fail2ban_checks() { + err_count=0 + diff_c=0 + THRESHOLD=${FAIL2BAN_THRESHOLD} + F2B_LOG_STATUS=($(${REDIS_CMDLINE} --raw HKEYS F2B_ACTIVE_BANS)) + F2B_RES= + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + err_c_cur=${err_count} + F2B_LOG_STATUS_PREV=(${F2B_LOG_STATUS[@]}) + F2B_LOG_STATUS=($(${REDIS_CMDLINE} --raw HKEYS F2B_ACTIVE_BANS)) + array_diff F2B_RES F2B_LOG_STATUS F2B_LOG_STATUS_PREV + if [[ ! -z "${F2B_RES}" ]]; then + err_count=$(( ${err_count} + 1 )) + echo -n "${F2B_RES[@]}" | tr -cd "[a-fA-F0-9.:/] " | timeout 3s ${REDIS_CMDLINE} -x SET F2B_RES > /dev/null + if [ $? -ne 0 ]; then + ${REDIS_CMDLINE} -x DEL F2B_RES + fi + fi + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "Fail2ban" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi + done + return 1 +} + +acme_checks() { + err_count=0 + diff_c=0 + THRESHOLD=${ACME_THRESHOLD} + ACME_LOG_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning GET ACME_FAIL_TIME) + if [[ -z "${ACME_LOG_STATUS}" ]]; then + ${REDIS_CMDLINE} SET ACME_FAIL_TIME 0 + ACME_LOG_STATUS=0 + fi + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + err_c_cur=${err_count} + ACME_LOG_STATUS_PREV=${ACME_LOG_STATUS} + ACME_LC=0 + until [[ ! -z ${ACME_LOG_STATUS} ]] || [ ${ACME_LC} -ge 3 ]; do + ACME_LOG_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning GET ACME_FAIL_TIME 2> /dev/null) + sleep 3 + ACME_LC=$((ACME_LC+1)) + done + if [[ ${ACME_LOG_STATUS_PREV} != ${ACME_LOG_STATUS} ]]; then + err_count=$(( ${err_count} + 1 )) + fi + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "ACME" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi done return 1 } @@ -220,111 +739,328 @@ phpfpm_checks() { rspamd_checks() { err_count=0 diff_c=0 - THRESHOLD=10 + THRESHOLD=${RSPAMD_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/rspamd-mailcow; echo "$(tail -50 /tmp/rspamd-mailcow)" > /tmp/rspamd-mailcow host_ip=$(get_container_ip rspamd-mailcow) err_c_cur=${err_count} - SCORE=$(/usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/scan -d ' -To: null@localhost + SCORE=$(echo 'To: null@localhost From: watchdog@localhost Empty -' | jq -rc .required_score) - if [[ ${SCORE} != "9999" ]]; then - echo "Rspamd settings check failed" 1>&2 +' | usr/bin/curl --max-time 10 -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/scan | jq -rc .default.required_score | sed 's/\..*//' ) + if [[ ${SCORE} -ne 9999 ]]; then + echo "Rspamd settings check failed, score returned: ${SCORE}" 2>> /tmp/rspamd-mailcow 1>&2 err_count=$(( ${err_count} + 1)) else - echo "Rspamd settings check succeeded" 1>&2 + echo "Rspamd settings check succeeded, score returned: ${SCORE}" 2>> /tmp/rspamd-mailcow 1>&2 + fi + # A dirty hack until a PING PONG event is implemented to worker proxy + # We expect an empty response, not a timeout + if [ "$(curl -s --max-time 10 ${host_ip}:9900 2> /dev/null ; echo $?)" == "28" ]; then + echo "Milter check failed" 2>> /tmp/rspamd-mailcow 1>&2; err_count=$(( ${err_count} + 1 )); + else + echo "Milter check succeeded" 2>> /tmp/rspamd-mailcow 1>&2 fi - /usr/lib/nagios/plugins/check_ping -4 -H ${host_ip} -w 2000,10% -c 4000,100% -p2 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Rspamd" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} - diff_c=0 - sleep $(( ( RANDOM % 30 ) + 10 )) + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi + done + return 1 +} + +olefy_checks() { + err_count=0 + diff_c=0 + THRESHOLD=${OLEFY_THRESHOLD} + # Reduce error count by 2 after restarting an unhealthy container + trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 + while [ ${err_count} -lt ${THRESHOLD} ]; do + touch /tmp/olefy-mailcow; echo "$(tail -50 /tmp/olefy-mailcow)" > /tmp/olefy-mailcow + host_ip=$(get_container_ip olefy-mailcow) + err_c_cur=${err_count} + /usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 10055 -s "PING\n" 2>> /tmp/olefy-mailcow 1>&2; err_count=$(( ${err_count} + $? )) + [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 + [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) + progress "Olefy" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 60 ) + 20 )) + fi done return 1 } +# Notify about start +if [[ ${WATCHDOG_NOTIFY_START} =~ ^([yY][eE][sS]|[yY])+$ ]]; then + notify_error "watchdog-mailcow" "Watchdog started monitoring mailcow." +fi + # Create watchdog agents + ( while true; do if ! nginx_checks; then log_msg "Nginx hit error limit" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "nginx-mailcow" echo nginx-mailcow > /tmp/com_pipe fi done ) & -BACKGROUND_TASKS+=($!) +PID=$! +echo "Spawned nginx_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) + +if [[ ${WATCHDOG_EXTERNAL_CHECKS} =~ ^([yY][eE][sS]|[yY])+$ ]]; then +( +while true; do + if ! external_checks; then + log_msg "External checks hit error limit" + echo external_checks > /tmp/com_pipe + fi +done +) & +PID=$! +echo "Spawned external_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) +fi + +if [[ ${WATCHDOG_MYSQL_REPLICATION_CHECKS} =~ ^([yY][eE][sS]|[yY])+$ ]]; then +( +while true; do + if ! mysql_repl_checks; then + log_msg "MySQL replication check hit error limit" + echo mysql_repl_checks > /tmp/com_pipe + fi +done +) & +PID=$! +echo "Spawned mysql_repl_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) +fi ( while true; do if ! mysql_checks; then log_msg "MySQL hit error limit" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "mysql-mailcow" echo mysql-mailcow > /tmp/com_pipe fi done ) & -BACKGROUND_TASKS+=($!) +PID=$! +echo "Spawned mysql_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) + +( +while true; do + if ! redis_checks; then + log_msg "Local Redis hit error limit" + echo redis-mailcow > /tmp/com_pipe + fi +done +) & +PID=$! +echo "Spawned redis_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) ( while true; do if ! phpfpm_checks; then log_msg "PHP-FPM hit error limit" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "php-fpm-mailcow" echo php-fpm-mailcow > /tmp/com_pipe fi done ) & -BACKGROUND_TASKS+=($!) +PID=$! +echo "Spawned phpfpm_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) +if [[ "${SKIP_SOGO}" =~ ^([nN][oO]|[nN])+$ ]]; then ( while true; do if ! sogo_checks; then log_msg "SOGo hit error limit" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "sogo-mailcow" echo sogo-mailcow > /tmp/com_pipe fi done ) & -BACKGROUND_TASKS+=($!) +PID=$! +echo "Spawned sogo_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) +fi + +if [ ${CHECK_UNBOUND} -eq 1 ]; then +( +while true; do + if ! unbound_checks; then + log_msg "Unbound hit error limit" + echo unbound-mailcow > /tmp/com_pipe + fi +done +) & +PID=$! +echo "Spawned unbound_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) +fi + +if [[ "${SKIP_CLAMD}" =~ ^([nN][oO]|[nN])+$ ]]; then +( +while true; do + if ! clamd_checks; then + log_msg "Clamd hit error limit" + echo clamd-mailcow > /tmp/com_pipe + fi +done +) & +PID=$! +echo "Spawned clamd_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) +fi ( while true; do if ! postfix_checks; then log_msg "Postfix hit error limit" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "postfix-mailcow" echo postfix-mailcow > /tmp/com_pipe fi done ) & -BACKGROUND_TASKS+=($!) +PID=$! +echo "Spawned postfix_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) + +( +while true; do + if ! mailq_checks; then + log_msg "Mail queue hit error limit" + echo mail_queue_status > /tmp/com_pipe + fi +done +) & +PID=$! +echo "Spawned mailq_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) + +( +while true; do + if ! postfix-tlspol_checks; then + log_msg "Postfix TLS Policy hit error limit" + echo postfix-tlspol-mailcow > /tmp/com_pipe + fi +done +) & +PID=$! +echo "Spawned postfix-tlspol_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) ( while true; do if ! dovecot_checks; then log_msg "Dovecot hit error limit" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "dovecot-mailcow" echo dovecot-mailcow > /tmp/com_pipe fi done ) & -BACKGROUND_TASKS+=($!) +PID=$! +echo "Spawned dovecot_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) + +( +while true; do + if ! dovecot_repl_checks; then + log_msg "Dovecot hit error limit" + echo dovecot_repl_checks > /tmp/com_pipe + fi +done +) & +PID=$! +echo "Spawned dovecot_repl_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) ( while true; do if ! rspamd_checks; then log_msg "Rspamd hit error limit" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "rspamd-mailcow" echo rspamd-mailcow > /tmp/com_pipe fi done ) & -BACKGROUND_TASKS+=($!) +PID=$! +echo "Spawned rspamd_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) + +( +while true; do + if ! ratelimit_checks; then + log_msg "Ratelimit hit error limit" + echo ratelimit > /tmp/com_pipe + fi +done +) & +PID=$! +echo "Spawned ratelimit_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) + +( +while true; do + if ! fail2ban_checks; then + log_msg "Fail2ban hit error limit" + echo fail2ban > /tmp/com_pipe + fi +done +) & +PID=$! +echo "Spawned fail2ban_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) + +( +while true; do + if ! cert_checks; then + log_msg "Cert check hit error limit" + echo certcheck > /tmp/com_pipe + fi +done +) & +PID=$! +echo "Spawned cert_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) + +if [[ "${SKIP_OLEFY}" =~ ^([nN][oO]|[nN])+$ ]]; then +( +while true; do + if ! olefy_checks; then + log_msg "Olefy hit error limit" + echo olefy-mailcow > /tmp/com_pipe + fi +done +) & +PID=$! +echo "Spawned olefy_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) +fi + +( +while true; do + if ! acme_checks; then + log_msg "ACME client hit error limit" + echo acme-mailcow > /tmp/com_pipe + fi +done +) & +PID=$! +echo "Spawned acme_checks with PID ${PID}" +BACKGROUND_TASKS+=(${PID}) # Monitor watchdog agents, stop script when agents fails and wait for respawn by Docker (restart:always:n) ( @@ -355,21 +1091,80 @@ while true; do done ) & -# Restart container when threshold limit reached +# Actions when threshold limit is reached while true; do CONTAINER_ID= + HAS_INITDB= read com_pipe_answer /dev/null)) + if [[ ! -z "${F2B_RES}" ]]; then + ${REDIS_CMDLINE} DEL F2B_RES > /dev/null + host= + for host in "${F2B_RES[@]}"; do + log_msg "Banned ${host}" + rm /tmp/fail2ban 2> /dev/null + timeout 2s whois "${host}" > /tmp/fail2ban + [[ ${WATCHDOG_NOTIFY_BAN} =~ ^([yY][eE][sS]|[yY])+$ ]] && notify_error "${com_pipe_answer}" "IP ban: ${host}" + done + fi + elif [[ ${com_pipe_answer} =~ .+-mailcow ]]; then kill -STOP ${BACKGROUND_TASKS[*]} - sleep 3 - CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | .id") + sleep 10 + CONTAINER_ID=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id") if [[ ! -z ${CONTAINER_ID} ]]; then - log_msg "Sending restart command to ${CONTAINER_ID}..." - curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/restart + if [[ "${com_pipe_answer}" == "php-fpm-mailcow" ]]; then + HAS_INITDB=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/top | jq '.msg.Processes[] | contains(["php -c /usr/local/etc/php -f /web/inc/init_db.inc.php"])' | grep true) + fi + S_RUNNING=$(($(date +%s) - $(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/json | jq .State.StartedAt | xargs -n1 date +%s -d))) + if [ ${S_RUNNING} -lt 360 ]; then + log_msg "Container is running for less than 360 seconds, skipping action..." + elif [[ ! -z ${HAS_INITDB} ]]; then + log_msg "Database is being initialized by php-fpm-mailcow, not restarting but delaying checks for a minute..." + sleep 60 + else + log_msg "Sending restart command to ${CONTAINER_ID}..." + curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/restart + notify_error "${com_pipe_answer}" + log_msg "Wait for restarted container to settle and continue watching..." + sleep 35 + fi fi - log_msg "Wait for restarted container to settle and continue watching..." - sleep 30s kill -CONT ${BACKGROUND_TASKS[*]} + sleep 1 kill -USR1 ${BACKGROUND_TASKS[*]} fi done + diff --git a/data/assets/nextcloud/nextcloud.conf b/data/assets/nextcloud/nextcloud.conf deleted file mode 100644 index 72f30240d3..0000000000 --- a/data/assets/nextcloud/nextcloud.conf +++ /dev/null @@ -1,112 +0,0 @@ -map $http_x_forwarded_proto $client_req_scheme_nc { - default $scheme; - https https; -} - -server { - include /etc/nginx/conf.d/listen_ssl.active; - include /etc/nginx/mime.types; - charset utf-8; - override_charset on; - - ssl on; - ssl_certificate /etc/ssl/mail/cert.pem; - ssl_certificate_key /etc/ssl/mail/key.pem; - ssl_protocols TLSv1.2; - ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; - ssl_prefer_server_ciphers on; - ssl_session_cache shared:SSL:50m; - ssl_session_timeout 1d; - ssl_session_tickets off; - add_header Strict-Transport-Security "max-age=15768000; includeSubDomains"; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Robots-Tag none; - add_header X-Download-Options noopen; - add_header X-Permitted-Cross-Domain-Policies none; - add_header X-Frame-Options "SAMEORIGIN"; - - server_name NC_SUBD; - - root /web/nextcloud/; - - location = /robots.txt { - allow all; - log_not_found off; - access_log off; - } - - location = /.well-known/carddav { - return 301 $client_req_scheme_nc://$host/remote.php/dav; - } - - location = /.well-known/caldav { - return 301 $client_req_scheme_nc://$host/remote.php/dav; - } - - location ^~ /.well-known/acme-challenge/ { - default_type "text/plain"; - root /web; - } - - gzip on; - gzip_vary on; - gzip_comp_level 4; - gzip_min_length 256; - gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; - gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; - set_real_ip_from fc00::/7; - set_real_ip_from 10.0.0.0/8; - set_real_ip_from 172.16.0.0/12; - set_real_ip_from 192.168.0.0/16; - real_ip_header X-Forwarded-For; - real_ip_recursive on; - - location / { - rewrite ^ /index.php$uri; - } - - location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ { - deny all; - } - - location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { - deny all; - } - - location ~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\.php(?:$|/) { - fastcgi_split_path_info ^(.+\.php)(/.*)$; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; - #Avoid sending the security headers twice - fastcgi_param modHeadersAvailable true; - fastcgi_param front_controller_active true; - fastcgi_pass phpfpm:9002; - fastcgi_intercept_errors on; - fastcgi_request_buffering off; - client_max_body_size 0; - fastcgi_read_timeout 1200; - } - - location ~ ^/(?:updater|ocs-provider)(?:$|/) { - try_files $uri/ =404; - index index.php; - } - - location ~ \.(?:css|js|woff|svg|gif)$ { - try_files $uri /index.php$uri$is_args$args; - add_header Cache-Control "public, max-age=15778463"; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Robots-Tag none; - add_header X-Download-Options noopen; - add_header X-Permitted-Cross-Domain-Policies none; - access_log off; - } - - location ~ \.(?:png|html|ttf|ico|jpg|jpeg)$ { - try_files $uri /index.php$uri$is_args$args; - access_log off; - } -} diff --git a/data/assets/nextcloud/occ b/data/assets/nextcloud/occ deleted file mode 100755 index 2ae0800128..0000000000 --- a/data/assets/nextcloud/occ +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ ${@} diff --git a/data/assets/nextcloud/site.nextcloud.custom b/data/assets/nextcloud/site.nextcloud.custom deleted file mode 100644 index 6ac2990207..0000000000 --- a/data/assets/nextcloud/site.nextcloud.custom +++ /dev/null @@ -1,44 +0,0 @@ - location ^~ /nextcloud { - location /nextcloud { - rewrite ^ /nextcloud/index.php$uri; - } - location ~ ^/nextcloud/(?:build|tests|config|lib|3rdparty|templates|data)/ { - deny all; - } - location ~ ^/nextcloud/(?:\.|autotest|occ|issue|indie|db_|console) { - deny all; - } - location ~ ^/nextcloud/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\.php(?:$|/) { - fastcgi_split_path_info ^(.+\.php)(/.*)$; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_param HTTPS on; - fastcgi_param modHeadersAvailable true; - fastcgi_param front_controller_active true; - fastcgi_pass phpfpm:9002; - fastcgi_intercept_errors on; - fastcgi_request_buffering off; - client_max_body_size 0; - fastcgi_read_timeout 1200; - } - location ~ ^/nextcloud/(?:updater|ocs-provider)(?:$|/) { - try_files $uri/ =404; - index index.php; - } - location ~ \.(?:css|js|woff|svg|gif)$ { - try_files $uri /nextcloud/index.php$uri$is_args$args; - add_header Cache-Control "public, max-age=15778463"; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Robots-Tag none; - add_header X-Download-Options noopen; - add_header X-Permitted-Cross-Domain-Policies none; - add_header X-Frame-Options "SAMEORIGIN"; - access_log off; - } - location ~ \.(?:png|html|ttf|ico|jpg|jpeg)$ { - try_files $uri /nextcloud/index.php$uri$is_args$args; - access_log off; - } - } diff --git a/data/assets/ssl-example/ca-key.pem b/data/assets/ssl-example/ca-key.pem deleted file mode 100644 index ce767c9c37..0000000000 --- a/data/assets/ssl-example/ca-key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEAr+geb+b9p5PgNMDUwtK3NxtmUJrHvnCSxkkLEBPFp/vVvEQV -eJQN3WNQVXjkqaAXQzWSkTz/URiKAKMSL/nx1+Es6HRXdR79GMc6kY1GBFMfhSwt -BzblNZ++WpD4JB3bL5prMrf2HiTqe617dlENoqK0kBEDGZq3En07aV+MIRuT8HSW -B4j1/St2DAKJYGCGVqPDgpFt7+aR8aXGkvoCMKpgiB32tNkmdDfzOdWmAdFS1KaV -RK+ap9WmwNdMH0BzCud9vhuJjdUZlhfmWuuT5XaGU+fftIlI191XYOEZHe44TLy1 -xWwXUhc+BmhhSj3k75lNvtAxydVrZIyoVxpLHwIDAQABAoIBAARMxkGyAc1Q3hAs -Dodco0Hjl5Ks1ekf01apfm28Lf63NzhM6cFyzQv2W4ZbWCuVUDxCWPzX4t3Wnbj6 -Q32MvI6sYG6mOWURhtpONG5OZ8G/Tmvw8oDUpLG03/BSzt4DJNJ7EdfBi3CdMmYn -jXcM8Cpjk8pZwBumHoeDLCqdPU2p1onq+iwMdQDKfq1ESBwEuXIO82x9y/O6zxNZ -HlWFDS5hLEt0UVWIV5aGK0/M/kzOCKwy7MMbJhHPYKy7Yvno2t+fJ6jiwswyEsCz -M2Z3DUWzgrT4DdWSV2Bns0X/xID5kH7EXm7GqsudbHHJNnr7LWuowFSOzAX47t+N -JgrRIaECgYEA1iFfpgO/awiNQLV1VpgA3pqzmFddEh74sFqILkSGd2fxH6BbgXJB -z+7Cu6jij8Odvf48suKsG9K0OwAehnR/ZgkrQmnKftf/pEZ+iLdsS/T8IZAXjQ4g -cLlYOOMuqIi8Ev1nT0/++IaSKro0CSDuuNtkqRCQ/ZMJtWzFxw3yZRcCgYEA0k1m -vaxvkSXqrYvFjDkPM5gUXcE4wx9t6Y4iwmRISLe6bJcfVfaT9oCz4o6p4dxZsoXw -SFSazFedqwQlzXMrrBd2rHYgg13N9LU9TggZXtSMOdye+dxk24P7GmJdWH5+S5vm -TqATs105AD95p7X8+H1TpW4liwW+eNjFqqO8HzkCgYBIuVj01z0BqwveOEK7wDA7 -aisoyDMR3nbz3it2G0vX7fNUnG+3jQiRCDQW7ArWbMd8KYaP8rAlWvBfQXEclSBX -lTGeArQFVHK8Zjy/Thx3x6KB+6AkBfI6lphB6daE4ruNb4bQxwh/e6TU4hyeJRMu -sUSErt2vYWrgtSqOqkvyzQKBgBKExFZRd+WVLCwqEbQ+VgtaCfkTibcM7nXRkVgC -0qasnxru19CPDQp43N5HZ8g+yhtBVh5YbOUvle+4Rsfnq2HVExsur8BBo2A4EXTs -m6dRGiQCPHGOKcd2wMbbAJNJWD+6M7aavAFgZSOTc1gEW4laJ+J6Z43tbI9hr05O -asNpAoGAFje1QFMYw8s8ZTYVrfwafgDB/BNnQRVnQwTZRXcabAT5O0Wc8hTDPOun -aBNJ3oKSwBvt7Snsn1G/oeH0aZ6cI8kkYE+dyQ5YqSZZUbJF6g771iNv4lMvxRBN -BT/ihKzXb/t47z6W8dL5N1BSZ0KrwIrEkqCeHBlAW+G/FArrbZU= ------END RSA PRIVATE KEY----- diff --git a/data/assets/ssl-example/ca.pem b/data/assets/ssl-example/ca.pem deleted file mode 100644 index 505f5698f0..0000000000 --- a/data/assets/ssl-example/ca.pem +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICzjCCAbigAwIBAgIRALtgn1eSIUIhoGv9nq94NYEwCwYJKoZIhvcNAQELMBIx -EDAOBgNVBAoTB21haWxjb3cwHhcNMTYxMjEzMTAxMTAwWhcNMTkxMTI4MTAxMTAw -WjASMRAwDgYDVQQKEwdtYWlsY293MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEAr+geb+b9p5PgNMDUwtK3NxtmUJrHvnCSxkkLEBPFp/vVvEQVeJQN3WNQ -VXjkqaAXQzWSkTz/URiKAKMSL/nx1+Es6HRXdR79GMc6kY1GBFMfhSwtBzblNZ++ -WpD4JB3bL5prMrf2HiTqe617dlENoqK0kBEDGZq3En07aV+MIRuT8HSWB4j1/St2 -DAKJYGCGVqPDgpFt7+aR8aXGkvoCMKpgiB32tNkmdDfzOdWmAdFS1KaVRK+ap9Wm -wNdMH0BzCud9vhuJjdUZlhfmWuuT5XaGU+fftIlI191XYOEZHe44TLy1xWwXUhc+ -BmhhSj3k75lNvtAxydVrZIyoVxpLHwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAKww -DwYDVR0TAQH/BAUwAwEB/zALBgkqhkiG9w0BAQsDggEBAF36qnigBDvvqRI0xksc -NkGFMB3JLKznOZ0DrWUx121/GHFaNNNeI7ECmyk2eRYKCEZNnxWa1/LJ7GWn7lRU -JD3OeWbhBUgA+HXoKl/jzXokuXMjYi/eFrgOofk2AqNDA5ioduS6A4vL8UDQc+74 -WSS8za4zoVR4GtxqDG+msRzTNVWXRcaaaWSrWMfZtQcEKIeQGDkcccvZ+mzlFUsH -G1xxKuOPGjAwrxda4x+FY/dYdPbRV8ua0RQmYUMnROv507QnGZ9FdzdrvisZ67xx -5BfxbApAyxDD/p7B4Zh1daga2LYGRwMJuwYjXlw9uNxJVQwcxg4nppWF1KZ4Fz0c -EjY= ------END CERTIFICATE----- diff --git a/data/assets/ssl-example/dhparams.pem b/data/assets/ssl-example/dhparams.pem index b245f051ef..9b182b7201 100644 --- a/data/assets/ssl-example/dhparams.pem +++ b/data/assets/ssl-example/dhparams.pem @@ -1,8 +1,8 @@ -----BEGIN DH PARAMETERS----- -MIIBCAKCAQEA9iHB0CRDhV8wfBgqnmvuJpl0fzL3qL75R4ZvQHlfMNLrxuIz2x9D -9zcDhPcBTVzV5Ay0AAkke4wP6r6wDQqXqBP4Y8IOkYAyLh3jM40jfHQzQt+5JdQl -ond3kiscBsFOch/vMfSLMu3lAb0YhPNTvrxhMz7LcVAWYl82swASupdiKR+MgaQr -XsugpmDKsHW60VmIM9B7K9Y+rNHwvMWkmISd0KxA8oOy1WJvsVEissMALZDE3c4w -2xHmO2lXxgEx3aez28736t4m/KW3g9Zr31a1M0KusmfY//fGkPk4NUrLBOS2xrgp -Y/rG1qSBdcVyerM0Ki93qCyHKYu4ene0OwIBAg== +MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== -----END DH PARAMETERS----- diff --git a/data/assets/templates/pw_reset_html.tpl b/data/assets/templates/pw_reset_html.tpl new file mode 100644 index 0000000000..481f8cbef7 --- /dev/null +++ b/data/assets/templates/pw_reset_html.tpl @@ -0,0 +1,29 @@ + + + + + + +Hello {{username2}},

+ +Somebody requested a new password for the {{hostname}} account associated with {{username}}.
+Date of the password reset request: {{date}}

+ +You can reset your password by clicking the link below:
+{{link}}

+ +The link will be valid for the next {{token_lifetime}} minutes.

+ +If you did not request a new password, please ignore this email.
+ + diff --git a/data/assets/templates/pw_reset_text.tpl b/data/assets/templates/pw_reset_text.tpl new file mode 100644 index 0000000000..fabe1e71a1 --- /dev/null +++ b/data/assets/templates/pw_reset_text.tpl @@ -0,0 +1,11 @@ +Hello {{username2}}, + +Somebody requested a new password for the {{hostname}} account associated with {{username}}. +Date of the password reset request: {{date}} + +You can reset your password by clicking the link below: +{{link}} + +The link will be valid for the next {{token_lifetime}} minutes. + +If you did not request a new password, please ignore this email. diff --git a/data/assets/templates/quarantine.tpl b/data/assets/templates/quarantine.tpl new file mode 100644 index 0000000000..8fa88c54b2 --- /dev/null +++ b/data/assets/templates/quarantine.tpl @@ -0,0 +1,69 @@ + + + + + + +

Hi {{username}}!
+ {% if counter == 1 %} + There is 1 new message waiting in quarantine:
+ {% else %} + There are {{counter}} new messages waiting in quarantine:
+ {% endif %} +

+ {% if quarantine_acl == 1 %}{% endif %} + {% for line in meta|reverse %} + + + + + {% if line.action == "reject" %} + + {% else %} + + {% endif %} + + {% if quarantine_acl == 1 %} + {% if line.action == "reject" %} + + {% else %} + + {% endif %} + {% endif %} + + {% endfor %} +
SubjectSenderScoreActionArrived onActions
{{ line.subject|e }}{{ line.sender|e }}{{ line.score }}RejectedSent to Junk folder{{ line.created }}Release to inbox | deleteSend copy to inbox | delete
+

+ + diff --git a/data/assets/templates/quota.tpl b/data/assets/templates/quota.tpl new file mode 100644 index 0000000000..b6ad644e5c --- /dev/null +++ b/data/assets/templates/quota.tpl @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + +
+ Hi {{username}}!

+ Your mailbox is now {{percent}}% full, please consider deleting old messages to still be able to receive new mails in the future.

+
{{percent}}%
+ + diff --git a/data/conf/acme/.gitkeep b/data/conf/acme/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/conf/clamav/clamd.conf b/data/conf/clamav/clamd.conf index f09510bd79..df1aa1e1c0 100644 --- a/data/conf/clamav/clamd.conf +++ b/data/conf/clamav/clamd.conf @@ -1,4 +1,5 @@ -LogFile /dev/console +#Debug true +#LogFile /dev/null LogTime yes LogClean yes ExtendedDetectionInfo yes @@ -23,9 +24,9 @@ DetectPUA yes #IncludePUA Spy #IncludePUA Scanner #IncludePUA RAT -AlgorithmicDetection yes +HeuristicAlerts yes ScanOLE2 yes -OLE2BlockMacros yes +AlertOLE2Macros no ScanPDF yes ScanSWF yes ScanXMLDOCS yes @@ -40,7 +41,7 @@ MaxScanSize 50M MaxFileSize 25M MaxRecursion 5 MaxFiles 200 -ScanOnAccess no Bytecode yes BytecodeSecurity TrustSigned BytecodeTimeout 1000 +ConcurrentDatabaseReload no diff --git a/data/conf/clamav/freshclam.conf b/data/conf/clamav/freshclam.conf index f4fff5820a..cfb497e977 100644 --- a/data/conf/clamav/freshclam.conf +++ b/data/conf/clamav/freshclam.conf @@ -1,10 +1,12 @@ -UpdateLogFile /var/log/clamav/freshclam.log +#UpdateLogFile /dev/console LogTime yes PidFile /run/clamav/freshclam.pid DatabaseOwner clamav -AllowSupplementaryGroups yes DNSDatabaseInfo current.cvd.clamav.net -DatabaseMirror database.clamav.net +DatabaseMirror db.uk.clamav.net +DatabaseMirror db.nl.clamav.net +DatabaseMirror db.fr.clamav.net +DatabaseMirror db.ch.clamav.net MaxAttempts 4 ScriptedUpdates yes Checks 6 diff --git a/data/conf/clamav/whitelist.ign2 b/data/conf/clamav/whitelist.ign2 deleted file mode 100644 index 70ef98b9f3..0000000000 --- a/data/conf/clamav/whitelist.ign2 +++ /dev/null @@ -1 +0,0 @@ -Example-Signature.Ignore-1 diff --git a/data/conf/dovecot/auth/mailcowauth.php b/data/conf/dovecot/auth/mailcowauth.php new file mode 100644 index 0000000000..d1c7381f63 --- /dev/null +++ b/data/conf/dovecot/auth/mailcowauth.php @@ -0,0 +1,122 @@ + false); +if(!isset($post['username']) || !isset($post['password']) || !isset($post['real_rip'])){ + error_log("MAILCOWAUTH: Bad Request"); + http_response_code(400); // Bad Request + echo json_encode($return); + exit(); +} + +require_once('../../../web/inc/vars.inc.php'); +if (file_exists('../../../web/inc/vars.local.inc.php')) { + include_once('../../../web/inc/vars.local.inc.php'); +} +require_once '../../../web/inc/lib/vendor/autoload.php'; + + +// Init Redis +$redis = new Redis(); +try { + if (!empty(getenv('REDIS_SLAVEOF_IP'))) { + $redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT')); + } + else { + $redis->connect('redis-mailcow', 6379); + } + $redis->auth(getenv("REDISPASS")); +} +catch (Exception $e) { + error_log("MAILCOWAUTH: " . $e . PHP_EOL); + http_response_code(500); // Internal Server Error + echo json_encode($return); + exit; +} + +// Init database +$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; +$opt = [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); +} +catch (PDOException $e) { + error_log("MAILCOWAUTH: " . $e . PHP_EOL); + http_response_code(500); // Internal Server Error + echo json_encode($return); + exit; +} + +// Load core functions first +require_once 'functions.inc.php'; +require_once 'functions.auth.inc.php'; +require_once 'sessions.inc.php'; +require_once 'functions.mailbox.inc.php'; +require_once 'functions.ratelimit.inc.php'; +require_once 'functions.acl.inc.php'; + + +$isSOGoRequest = $post['real_rip'] == getenv('IPV4_NETWORK') . '.248'; +$result = false; +if ($isSOGoRequest) { + // This is a SOGo Auth request. First check for SSO password. + $sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass"); + if ($sogo_sso_pass === $post['password']){ + error_log('MAILCOWAUTH: SOGo SSO auth for user ' . $post['username']); + set_sasl_log($post['username'], $post['real_rip'], "SOGO"); + $result = true; + } +} +if ($result === false){ + // If it's a SOGo Request, don't check for protocol access + if ($isSOGoRequest) { + $service = 'SOGO'; + $post['service'] = 'NONE'; + } else { + $service = $post['service']; + } + + $result = apppass_login($post['username'], $post['password'], array( + 'service' => $post['service'], + 'is_internal' => true, + 'remote_addr' => $post['real_rip'] + )); + if ($result) { + error_log('MAILCOWAUTH: App auth for user ' . $post['username'] . " with service " . $service . " from IP " . $post['real_rip']); + set_sasl_log($post['username'], $post['real_rip'], $service); + } +} +if ($result === false){ + // Init Identity Provider + $iam_provider = identity_provider('init'); + $iam_settings = identity_provider('get'); + $result = user_login($post['username'], $post['password'], array('is_internal' => true, 'service' => $post['service'])); + if ($result) { + error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']); + set_sasl_log($post['username'], $post['real_rip'], $post['service']); + } +} + +if ($result) { + http_response_code(200); // OK + $return['success'] = true; +} else { + error_log("MAILCOWAUTH: Login failed for user " . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']); + http_response_code(401); // Unauthorized +} + + +echo json_encode($return); +session_destroy(); +exit; diff --git a/data/conf/dovecot/auth/passwd-verify.lua b/data/conf/dovecot/auth/passwd-verify.lua new file mode 100644 index 0000000000..ea847932df --- /dev/null +++ b/data/conf/dovecot/auth/passwd-verify.lua @@ -0,0 +1,57 @@ +function auth_password_verify(request, password) + if request.domain == nil then + return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user" + end + + local json = require "cjson" + local ltn12 = require "ltn12" + local https = require "ssl.https" + https.TIMEOUT = 30 + + local req = { + username = request.user, + password = password, + real_rip = request.real_rip, + service = request.service + } + local req_json = json.encode(req) + local res = {} + + local b, c = https.request { + method = "POST", + url = "https://nginx:9082", + source = ltn12.source.string(req_json), + headers = { + ["content-type"] = "application/json", + ["content-length"] = tostring(#req_json) + }, + sink = ltn12.sink.table(res), + insecure = true + } + + -- Returning PASSDB_RESULT_PASSWORD_MISMATCH will reset the user's auth cache entry. + -- Returning PASSDB_RESULT_INTERNAL_FAILURE keeps the existing cache entry, + -- even if the TTL has expired. Useful to avoid cache eviction during backend issues. + if c ~= 200 and c ~= 401 then + dovecot.i_info("HTTP request failed with " .. c .. " for user " .. request.user) + return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Upstream error" + end + + local response_str = table.concat(res) + local is_response_valid, response_json = pcall(json.decode, response_str) + + if not is_response_valid then + dovecot.i_info("Invalid JSON received: " .. response_str) + return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Invalid response format" + end + + if response_json.success == true then + return dovecot.auth.PASSDB_RESULT_OK, "" + end + + return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate" +end + +function auth_passdb_lookup(req) + return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "" +end diff --git a/data/conf/dovecot/conf.d/fts.conf b/data/conf/dovecot/conf.d/fts.conf new file mode 100644 index 0000000000..e8a2f73f5a --- /dev/null +++ b/data/conf/dovecot/conf.d/fts.conf @@ -0,0 +1,37 @@ +# mailcow FTS Flatcurve Settings, change them as you like. +plugin { + fts_autoindex = yes + fts_autoindex_exclude = \Junk + fts_autoindex_exclude2 = \Trash + # Tweak this setting if you only want to ensure big and frequent folders are indexed, not all. + fts_autoindex_max_recent_msgs = 20 + fts = flatcurve + + # Maximum term length can be set via the 'maxlen' argument (maxlen is + # specified in bytes, not number of UTF-8 characters) + fts_tokenizer_email_address = maxlen=100 + fts_tokenizer_generic = algorithm=simple maxlen=30 + + # These are not flatcurve settings, but required for Dovecot FTS. See + # Dovecot FTS Configuration link above for further information. + fts_languages = en es de + fts_tokenizers = generic email-address + + # OPTIONAL: Recommended default FTS core configuration + fts_filters = normalizer-icu snowball stopwords + fts_filters_en = lowercase snowball english-possessive stopwords + + fts_index_timeout = 300s +} + +### THIS PART WILL BE CHANGED BY MODIFYING mailcow.conf AUTOMATICALLY DURING RUNTIME! ### + +service indexer-worker { + # Max amount of simultaniously running indexer jobs. + process_limit=1 + + # Max amount of RAM used by EACH indexer process. + vsz_limit=128 MB +} + +### THIS PART WILL BE CHANGED BY MODIFYING mailcow.conf AUTOMATICALLY DURING RUNTIME! ### \ No newline at end of file diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf index 02ce5dff43..05d9264fa8 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -1,9 +1,16 @@ # -------------------------------------------------------------------------- # Please create a file "extra.conf" for persistent overrides to dovecot.conf # -------------------------------------------------------------------------- +# LDAP example: +#passdb { +# args = /etc/dovecot/ldap/passdb.conf +# driver = ldap +#} + auth_mechanisms = plain login #mail_debug = yes #auth_debug = yes +#log_debug = category=fts-flatcurve # Activate Logging for Flatcurve FTS Searchings log_path = syslog disable_plaintext_auth = yes # Uncomment on NFS share @@ -14,21 +21,29 @@ disable_plaintext_auth = yes login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k" mail_home = /var/vmail/%d/%n mail_location = maildir:~/ -mail_plugins = quota acl zlib listescape mail_crypt mail_crypt_acl +mail_plugins = +!include_try /etc/dovecot/sni.conf +!include_try /etc/dovecot/sogo_trusted_ip.conf +!include_try /etc/dovecot/extra.conf +!include_try /etc/dovecot/shared_namespace.conf +!include_try /etc/dovecot/conf.d/fts.conf +# +default_client_limit = 10400 +default_vsz_limit = 1024 M diff --git a/data/conf/dovecot/dovecot.folders.conf b/data/conf/dovecot/dovecot.folders.conf new file mode 100644 index 0000000000..fa6872679f --- /dev/null +++ b/data/conf/dovecot/dovecot.folders.conf @@ -0,0 +1,308 @@ +namespace inbox { + inbox = yes + location = + separator = / + mailbox "Trash" { + auto = subscribe + special_use = \Trash + } + mailbox "Deleted Messages" { + special_use = \Trash + } + mailbox "Deleted Items" { + special_use = \Trash + } + mailbox "Rubbish" { + special_use = \Trash + } + mailbox "Gelรถschte Objekte" { + special_use = \Trash + } + mailbox "Gelรถschte Elemente" { + special_use = \Trash + } + mailbox "Papierkorb" { + special_use = \Trash + } + mailbox "Itens Excluidos" { + special_use = \Trash + } + mailbox "Itens Excluรญdos" { + special_use = \Trash + } + mailbox "Lixeira" { + special_use = \Trash + } + mailbox "Prullenbak" { + special_use = \Trash + } + mailbox "Odstrรกnenรฉ poloลพky" { + special_use = \Trash + } + mailbox "Koลก" { + special_use = \Trash + } + mailbox "Verwijderde items" { + special_use = \Trash + } + mailbox "ะฃะดะฐะปะตะฝะฝั‹ะต" { + special_use = \Trash + } + mailbox "ะฃะดะฐะปะตะฝะฝั‹ะต ัะปะตะผะตะฝั‚ั‹" { + special_use = \Trash + } + mailbox "ะšะพั€ะทะธะฝะฐ" { + special_use = \Trash + } + mailbox "ะ’ะธะดะฐะปะตะฝั–" { + special_use = \Trash + } + mailbox "ะ’ะธะดะฐะปะตะฝั– ะตะปะตะผะตะฝั‚ะธ" { + special_use = \Trash + } + mailbox "ะšะพัˆะธะบ" { + special_use = \Trash + } + mailbox "ๅบŸไปถ็ฎฑ" { + special_use = \Trash + } + mailbox "ๅทฒๅˆ ้™คๆถˆๆฏ" { + special_use = \Trash + } + mailbox "ๅทฒๅˆ ้™ค้‚ฎไปถ" { + special_use = \Trash + } + mailbox "Archive" { + auto = subscribe + special_use = \Archive + } + mailbox "Archiv" { + special_use = \Archive + } + mailbox "Archives" { + special_use = \Archive + } + mailbox "Arquivo" { + special_use = \Archive + } + mailbox "Arquivos" { + special_use = \Archive + } + mailbox "Archief" { + special_use = \Archive + } + mailbox "Archรญv" { + special_use = \Archive + } + mailbox "Archivovaลฅ" { + special_use = \Archive + } + mailbox "ๅฝ’ๆกฃ" { + special_use = \Archive + } + mailbox "ะั€ั…ะธะฒ" { + special_use = \Archive + } + mailbox "ะั€ั…ั–ะฒ" { + special_use = \Archive + } + mailbox "Sent" { + auto = subscribe + special_use = \Sent + } + mailbox "Sent Messages" { + special_use = \Sent + } + mailbox "Sent Items" { + special_use = \Sent + } + mailbox "ๅทฒๅ‘้€" { + special_use = \Sent + } + mailbox "ๅทฒๅ‘้€ๆถˆๆฏ" { + special_use = \Sent + } + mailbox "ๅทฒๅ‘้€้‚ฎไปถ" { + special_use = \Sent + } + mailbox "ะžั‚ะฟั€ะฐะฒะปะตะฝะฝั‹ะต" { + special_use = \Sent + } + mailbox "ะžั‚ะฟั€ะฐะฒะปะตะฝะฝั‹ะต ัะปะตะผะตะฝั‚ั‹" { + special_use = \Sent + } + mailbox "ะะฐะดั–ัะปะฐะฝั–" { + special_use = \Sent + } + mailbox "ะะฐะดั–ัะปะฐะฝั– ะตะปะตะผะตะฝั‚ะธ" { + special_use = \Sent + } + mailbox "Gesendet" { + special_use = \Sent + } + mailbox "Gesendete Objekte" { + special_use = \Sent + } + mailbox "Gesendete Elemente" { + special_use = \Sent + } + mailbox "Itens Enviados" { + special_use = \Sent + } + mailbox "Enviados" { + special_use = \Sent + } + mailbox "Verzonden items" { + special_use = \Sent + } + mailbox "Verzonden" { + special_use = \Sent + } + mailbox "Odoslanรก poลกta" { + special_use = \Sent + } + mailbox "Odoslanรฉ" { + special_use = \Sent + } + mailbox "Drafts" { + auto = subscribe + special_use = \Drafts + } + mailbox "Entwรผrfe" { + special_use = \Drafts + } + mailbox "Rascunhos" { + special_use = \Drafts + } + mailbox "Concepten" { + special_use = \Drafts + } + mailbox "Koncepty" { + special_use = \Drafts + } + mailbox "่‰็จฟ" { + special_use = \Drafts + } + mailbox "่‰็จฟ็ฎฑ" { + special_use = \Drafts + } + mailbox "ะงะตั€ะฝะพะฒะธะบะธ" { + special_use = \Drafts + } + mailbox "ะงะตั€ะฝะตั‚ะบะธ" { + special_use = \Drafts + } + mailbox "Junk" { + auto = subscribe + special_use = \Junk + } + mailbox "Junk-E-Mail" { + special_use = \Junk + } + mailbox "Junk E-Mail" { + special_use = \Junk + } + mailbox "Spam" { + special_use = \Junk + } + mailbox "Lixo Eletrรดnico" { + special_use = \Junk + } + mailbox "Nevyลพiadanรก poลกta" { + special_use = \Junk + } + mailbox "Infikovanรฉ poloลพky" { + special_use = \Junk + } + mailbox "Ongewenste e-mail" { + special_use = \Junk + } + mailbox "ๅžƒๅœพ" { + special_use = \Junk + } + mailbox "ๅžƒๅœพ็ฎฑ" { + special_use = \Junk + } + mailbox "ะะตะถะตะปะฐั‚ะตะปัŒะฝะฐั ะฟะพั‡ั‚ะฐ" { + special_use = \Junk + } + mailbox "ะกะฟะฐะผ" { + special_use = \Junk + } + mailbox "ะะตะฑะฐะถะฐะฝะฐ ะฟะพัˆั‚ะฐ" { + special_use = \Junk + } + mailbox "Koncepty" { + special_use = \Drafts + } + mailbox "Nevyลพรกdanรก poลกta" { + special_use = \Junk + } + mailbox "Odstranฤ›nรก poลกta" { + special_use = \Trash + } + mailbox "Odeslanรก poลกta" { + special_use = \Sent + } + mailbox "Skrรคp" { + special_use = \Trash + } + mailbox "Borttagna Meddelanden" { + special_use = \Trash + } + mailbox "Arkiv" { + special_use = \Archive + } + mailbox "Arkeverat" { + special_use = \Archive + } + mailbox "Skickat" { + special_use = \Sent + } + mailbox "Skickade Meddelanden" { + special_use = \Sent + } + mailbox "Utkast" { + special_use = \Drafts + } + mailbox "Skraldespand" { + special_use = \Trash + } + mailbox "Slettet mails" { + special_use = \Trash + } + mailbox "Arkiv" { + special_use = \Archive + } + mailbox "Arkiveret mails" { + special_use = \Archive + } + mailbox "Sendt" { + special_use = \Sent + } + mailbox "Sendte mails" { + special_use = \Sent + } + mailbox "Udkast" { + special_use = \Drafts + } + mailbox "Kladde" { + special_use = \Drafts + } + mailbox "ฮ ฯฯŒฯ‡ฮตฮนฯฮฑ" { + special_use = \Drafts + } + mailbox "ฮ‘ฯ€ฮตฯƒฯ„ฮฑฮปฮผฮญฮฝฮฑ" { + special_use = \Sent + } + mailbox "ฮšฮฌฮดฮฟฯ‚ ฮฑฯ€ฮฟฯฯฮนฮผฮฌฯ„ฯ‰ฮฝ" { + special_use = \Trash + } + mailbox "ฮ‘ฮฝฮตฯ€ฮนฮธฯฮผฮทฯ„ฮฑ" { + special_use = \Junk + } + mailbox "ฮ‘ฯฯ‡ฮตฮนฮฟฮธฮตฯ„ฮทฮผฮญฮฝฮฑ" { + special_use = \Archive + } + prefix = +} diff --git a/data/conf/dovecot/global_sieve_after b/data/conf/dovecot/global_sieve_after new file mode 100644 index 0000000000..cf12543ac0 --- /dev/null +++ b/data/conf/dovecot/global_sieve_after @@ -0,0 +1,30 @@ +# global_sieve_after script +# global_sieve_before -> user sieve_before (mailcow UI) -> user sieve_after (mailcow UI) -> global_sieve_after + +require "fileinto"; +require "mailbox"; +require "variables"; +require "subaddress"; +require "envelope"; +require "duplicate"; + +if header :contains "X-Spam-Flag" "YES" { + fileinto "Junk"; +} + +if allof ( + envelope :detail :matches "to" "*", + header :contains "X-Moo-Tag" "YES" + ) { + set :lower :upperfirst "tag" "${1}"; + if mailboxexists "INBOX/${1}" { + fileinto "INBOX/${1}"; + } else { + fileinto :create "INBOX/${tag}"; + } +} + +if duplicate { + discard; + stop; +} \ No newline at end of file diff --git a/data/conf/dovecot/global_sieve_before b/data/conf/dovecot/global_sieve_before new file mode 100644 index 0000000000..e71be5100c --- /dev/null +++ b/data/conf/dovecot/global_sieve_before @@ -0,0 +1,3 @@ +# global_sieve_before script +# global_sieve_before -> user sieve_before (mailcow UI) -> user sieve_after (mailcow UI) -> global_sieve_after + diff --git a/data/conf/dovecot/ldap/passdb.conf b/data/conf/dovecot/ldap/passdb.conf new file mode 100644 index 0000000000..12fc3c050e --- /dev/null +++ b/data/conf/dovecot/ldap/passdb.conf @@ -0,0 +1,9 @@ +#hosts = 1.2.3.4 +#dn = cn=admin,dc=example,dc=local +#dnpass = password +#ldap_version = 3 +#base = ou=People,dc=example,dc=local +#auth_bind = no +#pass_filter = (&(objectClass=posixAccount)(mail=%u)) +#pass_attrs = mail=user,userPassword=password +#default_pass_scheme = SSHA diff --git a/data/conf/dovecot/sieve_after b/data/conf/dovecot/sieve_after deleted file mode 100644 index 2e0cfe0856..0000000000 --- a/data/conf/dovecot/sieve_after +++ /dev/null @@ -1,21 +0,0 @@ -require "fileinto"; -require "mailbox"; -require "variables"; -require "subaddress"; -require "envelope"; - -if header :contains "X-Spam-Flag" "YES" { - fileinto "Junk"; -} - -if allof ( - envelope :detail :matches "to" "*", - header :contains "X-Moo-Tag" "YES" - ) { - set :lower :upperfirst "tag" "${1}"; - if mailboxexists "INBOX/${1}" { - fileinto "INBOX/${1}"; - } else { - fileinto :create "INBOX/${tag}"; - } -} diff --git a/data/conf/mysql/my.cnf b/data/conf/mysql/my.cnf index 772c8a625a..24d6123bbd 100644 --- a/data/conf/mysql/my.cnf +++ b/data/conf/mysql/my.cnf @@ -1,18 +1,32 @@ [mysqld] character-set-client-handshake = FALSE character-set-server = utf8mb4 -collation-server = utf8mb4_unicode_ci -innodb_file_per_table = TRUE -innodb_file_format = barracuda -innodb_large_prefix = TRUE +collation-server = utf8mb4_general_ci +#innodb_file_per_table = TRUE +#innodb_file_format = barracuda +#innodb_large_prefix = TRUE #sql_mode=IGNORE_SPACE,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION -max_allowed_packet=192M -max-connections=1500 -innodb-strict-mode=0 +max_allowed_packet = 192M +max-connections = 550 +key_buffer_size = 0 +read_buffer_size = 192K +sort_buffer_size = 2M +innodb_buffer_pool_size = 24M +read_rnd_buffer_size = 256K +tmp_table_size = 24M +performance_schema = 0 +innodb-strict-mode = 0 +thread_cache_size = 8 +query_cache_type = 0 +query_cache_size = 0 +max_heap_table_size = 48M +thread_stack = 256K skip-host-cache skip-name-resolve -log-warnings=0 -event_scheduler=1 +log-warnings = 0 +event_scheduler = 1 +interactive_timeout = 3610 +wait_timeout = 3610 [client] default-character-set = utf8mb4 diff --git a/data/conf/nginx/000-map-size.conf b/data/conf/nginx/000-map-size.conf deleted file mode 100644 index a834306edd..0000000000 --- a/data/conf/nginx/000-map-size.conf +++ /dev/null @@ -1,3 +0,0 @@ -map_hash_max_size 256; -map_hash_bucket_size 256; - diff --git a/data/conf/nginx/dynmaps.conf b/data/conf/nginx/dynmaps.conf deleted file mode 100644 index 99c0c6aaab..0000000000 --- a/data/conf/nginx/dynmaps.conf +++ /dev/null @@ -1,19 +0,0 @@ -server { - listen 8081; - listen [::]:8081; - index index.php index.html; - server_name _; - error_log /var/log/nginx/error.log; - access_log /var/log/nginx/access.log; - root /dynmaps; - - location ~ \.php$ { - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9001; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; - } -} diff --git a/data/conf/nginx/meta_exporter.conf b/data/conf/nginx/meta_exporter.conf deleted file mode 100644 index 74291b1474..0000000000 --- a/data/conf/nginx/meta_exporter.conf +++ /dev/null @@ -1,19 +0,0 @@ -server { - listen 9081; - index index.php index.html; - server_name _; - error_log /var/log/nginx/error.log; - access_log /var/log/nginx/access.log; - root /meta_exporter; - client_max_body_size 10M; - location ~ \.php$ { - client_max_body_size 10M; - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9001; - fastcgi_index pipe.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; - } -} diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf deleted file mode 100644 index 871413bd97..0000000000 --- a/data/conf/nginx/site.conf +++ /dev/null @@ -1,187 +0,0 @@ -server_tokens off; -proxy_cache_path /tmp levels=1:2 keys_zone=sogo:10m inactive=24h max_size=1g; -server_names_hash_bucket_size 64; - -map $http_x_forwarded_proto $client_req_scheme { - default $scheme; - https https; -} - -map $sent_http_content_type $expires { - default off; - text/html off; - text/css 1d; - application/javascript 1d; - application/json off; - image/png 1d; -} - -server { - include /etc/nginx/mime.types; - charset utf-8; - override_charset on; - - ssl_certificate /etc/ssl/mail/cert.pem; - ssl_certificate_key /etc/ssl/mail/key.pem; - ssl_protocols TLSv1.2; - ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; - ssl_prefer_server_ciphers on; - ssl_session_cache shared:SSL:50m; - ssl_session_timeout 1d; - ssl_session_tickets off; - - add_header Strict-Transport-Security "max-age=15768000; includeSubDomains"; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Robots-Tag none; - add_header X-Download-Options noopen; - add_header X-Permitted-Cross-Domain-Policies none; - - index index.php index.html; - - client_max_body_size 0; - - include /etc/nginx/conf.d/listen_plain.active; - include /etc/nginx/conf.d/listen_ssl.active; - include /etc/nginx/conf.d/server_name.active; - - error_log /var/log/nginx/error.log; - access_log /var/log/nginx/access.log; - absolute_redirect off; - root /web; - - location / { - try_files $uri $uri/ @strip-ext; - } - - location /edit { - rewrite ^/edit/(.*)/(.*) /edit.php?$1=$2; - } - - location @strip-ext { - rewrite ^(.*)$ $1.php last; - } - - location ~ ^/api/v1/(.*)$ { - try_files $uri $uri/ /json_api.php?query=$1; - } - - location ^~ /.well-known/acme-challenge/ { - allow all; - default_type "text/plain"; - } - - # If behind reverse proxy, forwards the correct IP - set_real_ip_from 10.0.0.0/8; - set_real_ip_from 172.16.0.0/12; - set_real_ip_from 192.168.0.0/16; - set_real_ip_from fc00::/7; - real_ip_header X-Forwarded-For; - real_ip_recursive on; - - rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent; - rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent; - - location ^~ /principals { - return 301 /SOGo/dav; - } - - location ~ \.php$ { - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9002; - fastcgi_index index.php; - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_read_timeout 1200; - } - - location /rspamd/ { - proxy_pass http://rspamd:11334/; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Real-IP $remote_addr; - proxy_redirect off; - expires $expires; - } - - location ~* ^/Autodiscover/Autodiscover.xml { - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9002; - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - try_files /autodiscover.php =404; - } - - location ~* ^/Autodiscover/Autodiscover.json { - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9002; - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - try_files /autodiscover-json.php =404; - } - - location ~ /(?:m|M)ail/(?:c|C)onfig-v1.1.xml { - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9002; - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - try_files /autoconfig.php =404; - } - - location ^~ /Microsoft-Server-ActiveSync { - include /etc/nginx/conf.d/sogo_eas.active; - proxy_connect_timeout 4000; - proxy_next_upstream timeout error; - proxy_send_timeout 4000; - proxy_read_timeout 4000; - proxy_buffer_size 8k; - proxy_buffers 16 64k; - proxy_temp_file_write_size 64k; - proxy_busy_buffers_size 64k; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header x-webobjects-server-protocol HTTP/1.0; - proxy_set_header x-webobjects-remote-host $remote_addr; - proxy_set_header x-webobjects-server-name $server_name; - proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host; - proxy_set_header x-webobjects-server-port $server_port; - client_body_buffer_size 128k; - client_max_body_size 0; - } - - location ^~ /SOGo { - include /etc/nginx/conf.d/sogo.active; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header x-webobjects-server-protocol HTTP/1.0; - proxy_set_header x-webobjects-remote-host $remote_addr; - proxy_set_header x-webobjects-server-name $server_name; - proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host; - proxy_set_header x-webobjects-server-port $server_port; - client_body_buffer_size 128k; - client_max_body_size 0; - break; - } - - location /SOGo.woa/WebServerResources/ { - alias /usr/lib/GNUstep/SOGo/WebServerResources/; - } - - location /.woa/WebServerResources/ { - alias /usr/lib/GNUstep/SOGo/WebServerResources/; - } - - location /SOGo/WebServerResources/ { - alias /usr/lib/GNUstep/SOGo/WebServerResources/; - } - - location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$) { - alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2; - } - - include /etc/nginx/conf.d/site.*.custom; -} diff --git a/data/conf/nginx/templates/listen_plain.template b/data/conf/nginx/templates/listen_plain.template deleted file mode 100644 index a044b22f25..0000000000 --- a/data/conf/nginx/templates/listen_plain.template +++ /dev/null @@ -1,2 +0,0 @@ -listen ${HTTP_PORT}; -listen [::]:${HTTP_PORT}; diff --git a/data/conf/nginx/templates/listen_ssl.template b/data/conf/nginx/templates/listen_ssl.template deleted file mode 100644 index 93ec80c6df..0000000000 --- a/data/conf/nginx/templates/listen_ssl.template +++ /dev/null @@ -1,2 +0,0 @@ -listen ${HTTPS_PORT} ssl http2; -listen [::]:${HTTPS_PORT} ssl http2; diff --git a/data/conf/nginx/templates/nginx.conf.j2 b/data/conf/nginx/templates/nginx.conf.j2 new file mode 100644 index 0000000000..861c7e5603 --- /dev/null +++ b/data/conf/nginx/templates/nginx.conf.j2 @@ -0,0 +1,227 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + server_tokens off; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + # map-size.conf: + map_hash_max_size 256; + map_hash_bucket_size 256; + + # site.conf: + proxy_cache_path /tmp levels=1:2 keys_zone=sogo:10m inactive=24h max_size=1g; + server_names_hash_max_size 512; + server_names_hash_bucket_size 128; + + map $http_x_forwarded_proto $client_req_scheme { + default $scheme; + https https; + } + + map $server_port $sogo_auth_internal { + 65510 "1"; + default ""; + } + + {% if HTTP_REDIRECT %} + # HTTP to HTTPS redirect + server { + root /web; + listen {{ HTTP_PORT }} default_server; + {% if ENABLE_IPV6 %} + listen [::]:{{ HTTP_PORT }} default_server; + {%endif%} + + server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* mta-sts.* {{ ADDITIONAL_SERVER_NAMES | join(' ') }}; + + if ( $request_uri ~* "%0A|%0D" ) { return 403; } + location ^~ /.well-known/acme-challenge/ { + allow all; + default_type "text/plain"; + } + location ^~ /.well-known/mta-sts.txt { + allow all; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ PHPFPMHOST }}:9002; + include /etc/nginx/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/mta-sts.php; + fastcgi_param PATH_INFO $fastcgi_path_info; + } + location / { + return 301 https://$host$uri$is_args$args; + } + } + {%endif%} + + # Default Server Name + server { + listen 127.0.0.1:65510; # sogo-auth verify internal + + {% if not HTTP_REDIRECT %} + listen {{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; + {%endif%} + listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; + + {% if ENABLE_IPV6 %} + {% if not HTTP_REDIRECT %} + listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; + {%endif%} + listen [::]:{{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; + {%endif%} + + http2 on; + + ssl_certificate /etc/ssl/mail/cert.pem; + ssl_certificate_key /etc/ssl/mail/key.pem; + + server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* mta-sts.*; + + include /etc/nginx/includes/sites-default.conf; + } + + # Additional Server Names + {% for SERVER_NAME in ADDITIONAL_SERVER_NAMES %} + server { + listen 127.0.0.1:65510; # sogo-auth verify internal + + {% if not HTTP_REDIRECT %} + listen {{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; + {%endif%} + listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; + + {% if ENABLE_IPV6 %} + {% if not HTTP_REDIRECT %} + listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; + {%endif%} + listen [::]:{{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; + {%endif%} + + http2 on; + + ssl_certificate /etc/ssl/mail/cert.pem; + ssl_certificate_key /etc/ssl/mail/key.pem; + + server_name {{ SERVER_NAME }}; + + include /etc/nginx/includes/sites-default.conf; + } + {% endfor %} + + # rspamd dynmaps: + server { + listen 8081; + {% if ENABLE_IPV6 %} + listen [::]:8081; + {%endif%} + index index.php index.html; + server_name _; + error_log /var/log/nginx/error.log; + access_log /var/log/nginx/access.log; + root /dynmaps; + + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ PHPFPMHOST }}:9001; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + } + } + + # rspamd meta_exporter: + server { + listen 9081; + index index.php index.html; + server_name _; + error_log /var/log/nginx/error.log; + access_log /var/log/nginx/access.log; + root /meta_exporter; + client_max_body_size 10M; + location ~ \.php$ { + client_max_body_size 10M; + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ PHPFPMHOST }}:9001; + fastcgi_index pipe.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + } + } + + server { + listen 9082 ssl http2; + + ssl_certificate /etc/ssl/mail/cert.pem; + ssl_certificate_key /etc/ssl/mail/key.pem; + + index mailcowauth.php; + server_name _; + error_log /var/log/nginx/error.log; + access_log /var/log/nginx/access.log; + root /mailcowauth; + client_max_body_size 10M; + location ~ \.php$ { + client_max_body_size 10M; + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass phpfpm:9001; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + } + } + + include /etc/nginx/conf.d/*.conf; + + {% for cert in valid_cert_dirs %} + server { + {% if not HTTP_REDIRECT %} + listen {{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; + {%endif%} + listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; + + {% if ENABLE_IPV6 %} + {% if not HTTP_REDIRECT %} + listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; + {%endif%} + listen [::]:{{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; + {%endif%} + + http2 on; + + ssl_certificate {{ cert.cert_path }}cert.pem; + ssl_certificate_key {{ cert.cert_path }}key.pem; + + server_name {{ cert.domains }}; + + include /etc/nginx/includes/sites-default.conf; + } + {% endfor %} +} diff --git a/data/conf/nginx/templates/server_name.template b/data/conf/nginx/templates/server_name.template deleted file mode 100644 index 261a1ecebe..0000000000 --- a/data/conf/nginx/templates/server_name.template +++ /dev/null @@ -1 +0,0 @@ -server_name ${MAILCOW_HOSTNAME} autodiscover.* autoconfig.*; diff --git a/data/conf/nginx/templates/sites-default.conf.j2 b/data/conf/nginx/templates/sites-default.conf.j2 new file mode 100644 index 0000000000..842c50a54c --- /dev/null +++ b/data/conf/nginx/templates/sites-default.conf.j2 @@ -0,0 +1,297 @@ +include /etc/nginx/mime.types; +charset utf-8; +override_charset on; + +server_tokens off; + +ssl_protocols TLSv1.2 TLSv1.3; +ssl_prefer_server_ciphers on; +ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; +ssl_ecdh_curve X25519:X448:secp384r1:secp256k1; +ssl_session_cache shared:SSL:50m; +ssl_session_timeout 1d; +ssl_session_tickets off; + +add_header Strict-Transport-Security "max-age=15768000;"; +add_header X-Content-Type-Options nosniff; +add_header X-Robots-Tag none; +add_header X-Download-Options noopen; +add_header X-Frame-Options "SAMEORIGIN" always; +add_header X-Permitted-Cross-Domain-Policies none; +add_header Referrer-Policy strict-origin; + +index index.php index.html; + +client_max_body_size 0; + +gzip on; +gzip_disable "msie6"; + +gzip_vary on; +gzip_proxied off; +gzip_comp_level 6; +gzip_buffers 16 8k; +gzip_http_version 1.1; +gzip_min_length 256; +gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon; + +location ~ ^/(fonts|js|css|img)/ { + expires max; + add_header Cache-Control public; +} + +error_log /var/log/nginx/error.log; +access_log /var/log/nginx/access.log; +fastcgi_hide_header X-Powered-By; +absolute_redirect off; +root /web; + +# If behind reverse proxy, forwards the correct IP +set_real_ip_from 10.0.0.0/8; +set_real_ip_from 172.16.0.0/12; +set_real_ip_from 192.168.0.0/16; +set_real_ip_from fc00::/7; +{% for TRUSTED_PROXY in TRUSTED_PROXIES %} +set_real_ip_from {{ TRUSTED_PROXY }}; +{% endfor %} +{% if not NGINX_USE_PROXY_PROTOCOL %} +real_ip_header X-Forwarded-For; +{% else %} +real_ip_header proxy_protocol; +{% endif %} +real_ip_recursive on; + + +location @strip-ext { + rewrite ^(.*)$ $1.php last; +} + +location ^~ /inc/lib/ { + deny all; + return 403; +} + +location ^~ /.well-known/acme-challenge/ { + allow all; + default_type "text/plain"; +} +location ^~ /.well-known/mta-sts.txt { + allow all; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ PHPFPMHOST }}:9002; + include /etc/nginx/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/mta-sts.php; + fastcgi_param PATH_INFO $fastcgi_path_info; +} + +rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent; +rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent; + + +location / { + try_files $uri $uri/ @strip-ext; +} + +location /qhandler { + rewrite ^/qhandler/(.*)/(.*) /qhandler.php?action=$1&hash=$2; +} + +location /edit { + rewrite ^/edit/(.*)/(.*) /edit.php?$1=$2; +} + +location ~ ^/api/v1/(.*)$ { + try_files $uri $uri/ /json_api.php?query=$1&$args; +} + +location ~ ^/cache/(.*)$ { + try_files $uri $uri/ /resource.php?file=$1; +} + +location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ PHPFPMHOST }}:9002; + fastcgi_index index.php; + include /etc/nginx/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param HTTP_X_REAL_IP $remote_addr; + # trusted internal-auth marker; empty for external clients (see nginx.conf map) + fastcgi_param SOGO_AUTH_INTERNAL $sogo_auth_internal; + fastcgi_read_timeout 3600; + fastcgi_send_timeout 3600; +} + +location ~* ^/Autodiscover/Autodiscover.xml { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ PHPFPMHOST }}:9002; + include /etc/nginx/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + try_files /autodiscover.php =404; +} + +location ~* ^/Autodiscover/Autodiscover.json { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ PHPFPMHOST }}:9002; + include /etc/nginx/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + try_files /autodiscover-json.php =404; +} + +location ~ /(?:m|M)ail/(?:c|C)onfig-v1.1.xml { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass {{ PHPFPMHOST }}:9002; + include /etc/nginx/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + try_files /autoconfig.php =404; +} + +{% if not SKIP_RSPAMD %} +location /rspamd/ { + location /rspamd/auth { + # proxy_pass is not inherited + proxy_pass http://{{ RSPAMDHOST }}:11334/auth; + proxy_intercept_errors on; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For {% if not NGINX_USE_PROXY_PROTOCOL %}$proxy_add_x_forwarded_for{% else %}$proxy_protocol_addr{%endif%}; + proxy_set_header X-Real-IP {% if not NGINX_USE_PROXY_PROTOCOL %}$remote_addr{% else %}$proxy_protocol_addr{%endif%}; + proxy_redirect off; + error_page 401 /_rspamderror.php; + } + + proxy_pass http://{{ RSPAMDHOST }}:11334/; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For {% if not NGINX_USE_PROXY_PROTOCOL %}$proxy_add_x_forwarded_for{% else %}$proxy_protocol_addr{%endif%}; + proxy_set_header X-Real-IP {% if not NGINX_USE_PROXY_PROTOCOL %}$remote_addr{% else %}$proxy_protocol_addr{%endif%}; + proxy_redirect off; +} +{% endif %} + +{% if not SKIP_SOGO %} +location ^~ /principals { + return 301 /SOGo/dav; +} + +location /sogo-auth-verify { + internal; + proxy_set_header X-Original-URI $request_uri; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $http_host; + proxy_set_header Content-Length ""; + proxy_pass http://127.0.0.1:65510/sogo-auth; + proxy_pass_request_body off; +} + +location ^~ /Microsoft-Server-ActiveSync { + auth_request /sogo-auth-verify; + auth_request_set $user $upstream_http_x_user; + auth_request_set $auth $upstream_http_x_auth; + auth_request_set $auth_type $upstream_http_x_auth_type; + proxy_set_header x-webobjects-remote-user "$user"; + proxy_set_header Authorization "$auth"; + proxy_set_header x-webobjects-auth-type "$auth_type"; + + proxy_pass http://{{ SOGOHOST }}:20000/SOGo/Microsoft-Server-ActiveSync; + + proxy_set_header X-Forwarded-For {% if not NGINX_USE_PROXY_PROTOCOL %}$proxy_add_x_forwarded_for{% else %}$proxy_protocol_addr{%endif%}; + proxy_set_header X-Real-IP {% if not NGINX_USE_PROXY_PROTOCOL %}$remote_addr{% else %}$proxy_protocol_addr{%endif%}; + proxy_connect_timeout 75; + proxy_send_timeout 3600; + proxy_read_timeout 3600; + proxy_buffer_size 128k; + proxy_buffers 64 512k; + proxy_busy_buffers_size 512k; + proxy_set_header Host $http_host; + client_body_buffer_size 512k; + client_max_body_size 0; +} + +location ^~ /SOGo { + location ~* ^/SOGo/so/.*\.(xml|js|html|xhtml)$ { + auth_request /sogo-auth-verify; + auth_request_set $user $upstream_http_x_user; + auth_request_set $auth $upstream_http_x_auth; + auth_request_set $auth_type $upstream_http_x_auth_type; + proxy_set_header x-webobjects-remote-user "$user"; + proxy_set_header Authorization "$auth"; + proxy_set_header x-webobjects-auth-type "$auth_type"; + + proxy_pass http://{{ SOGOHOST }}:20000; + + proxy_set_header X-Forwarded-For {% if not NGINX_USE_PROXY_PROTOCOL %}$proxy_add_x_forwarded_for{% else %}$proxy_protocol_addr{%endif%}; + proxy_set_header X-Real-IP {% if not NGINX_USE_PROXY_PROTOCOL %}$remote_addr{% else %}$proxy_protocol_addr{%endif%}; + proxy_set_header Host $http_host; + proxy_set_header x-webobjects-server-protocol HTTP/1.0; + proxy_set_header x-webobjects-remote-host $remote_addr; + proxy_set_header x-webobjects-server-name $server_name; + proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host; + proxy_set_header x-webobjects-server-port $server_port; + proxy_hide_header Content-Type; + add_header Content-Type text/plain; + break; + } + auth_request /sogo-auth-verify; + auth_request_set $user $upstream_http_x_user; + auth_request_set $auth $upstream_http_x_auth; + auth_request_set $auth_type $upstream_http_x_auth_type; + proxy_set_header x-webobjects-remote-user "$user"; + proxy_set_header Authorization "$auth"; + proxy_set_header x-webobjects-auth-type "$auth_type"; + + proxy_pass http://{{ SOGOHOST }}:20000; + + proxy_set_header X-Forwarded-For {% if not NGINX_USE_PROXY_PROTOCOL %}$proxy_add_x_forwarded_for{% else %}$proxy_protocol_addr{%endif%}; + proxy_set_header X-Real-IP {% if not NGINX_USE_PROXY_PROTOCOL %}$remote_addr{% else %}$proxy_protocol_addr{%endif%}; + proxy_set_header Host $http_host; + proxy_set_header x-webobjects-server-protocol HTTP/1.0; + proxy_set_header x-webobjects-remote-host $remote_addr; + proxy_set_header x-webobjects-server-name $server_name; + proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host; + proxy_set_header x-webobjects-server-port $server_port; + proxy_buffer_size 128k; + proxy_buffers 64 512k; + proxy_busy_buffers_size 512k; + proxy_send_timeout 3600; + proxy_read_timeout 3600; + client_body_buffer_size 128k; + client_max_body_size 0; + break; +} + +location ~* /sogo$ { + return 301 $client_req_scheme://$http_host/SOGo; +} + +location /SOGo.woa/WebServerResources/ { + alias /usr/local/lib/GNUstep/SOGo/WebServerResources/; +} + +location /.woa/WebServerResources/ { + alias /usr/local/lib/GNUstep/SOGo/WebServerResources/; +} + +location /SOGo/WebServerResources/ { + alias /usr/local/lib/GNUstep/SOGo/WebServerResources/; +} + +location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$) { + alias /usr/local/lib/GNUstep/SOGo/$1.SOGo/Resources/$2; +} +{% endif %} + + +include /etc/nginx/conf.d/site.*.custom; + +error_page 502 @awaitingupstream; + +location @awaitingupstream { + rewrite ^(.*)$ /_status.502.html break; +} + +location ~* \.php$ { + return 404; +} +location ~* \.twig$ { + return 404; +} diff --git a/data/conf/nginx/templates/sogo.template b/data/conf/nginx/templates/sogo.template deleted file mode 100644 index 2c084389f2..0000000000 --- a/data/conf/nginx/templates/sogo.template +++ /dev/null @@ -1 +0,0 @@ -proxy_pass http://${IPV4_NETWORK}.248:20000; diff --git a/data/conf/nginx/templates/sogo_eas.template b/data/conf/nginx/templates/sogo_eas.template deleted file mode 100644 index 3cea9f9883..0000000000 --- a/data/conf/nginx/templates/sogo_eas.template +++ /dev/null @@ -1 +0,0 @@ -proxy_pass http://${IPV4_NETWORK}.248:20000/SOGo/Microsoft-Server-ActiveSync; diff --git a/data/conf/phpfpm/crons/keycloak-sync.php b/data/conf/phpfpm/crons/keycloak-sync.php new file mode 100644 index 0000000000..f09a47d790 --- /dev/null +++ b/data/conf/phpfpm/crons/keycloak-sync.php @@ -0,0 +1,231 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); +} +catch (PDOException $e) { + logMsg("err", $e->getMessage()); + session_destroy(); + exit; +} + +// Init Redis +$redis = new Redis(); +try { + if (!empty(getenv('REDIS_SLAVEOF_IP'))) { + $redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT')); + } + else { + $redis->connect('redis-mailcow', 6379); + } + $redis->auth(getenv("REDISPASS")); +} +catch (Exception $e) { + echo "Exiting: " . $e->getMessage(); + session_destroy(); + exit; +} + +function logMsg($priority, $message, $task = "Keycloak Sync") { + global $redis; + + $finalMsg = array( + "time" => time(), + "priority" => $priority, + "task" => $task, + "message" => $message + ); + $redis->lPush('CRON_LOG', json_encode($finalMsg)); +} + +// Load core functions first +require_once __DIR__ . '/../web/inc/functions.inc.php'; +require_once __DIR__ . '/../web/inc/functions.auth.inc.php'; +require_once __DIR__ . '/../web/inc/sessions.inc.php'; +require_once __DIR__ . '/../web/inc/functions.mailbox.inc.php'; +require_once __DIR__ . '/../web/inc/functions.ratelimit.inc.php'; +require_once __DIR__ . '/../web/inc/functions.acl.inc.php'; + +$_SESSION['mailcow_cc_username'] = "admin"; +$_SESSION['mailcow_cc_role'] = "admin"; +$_SESSION['acl']['tls_policy'] = "1"; +$_SESSION['acl']['quarantine_notification'] = "1"; +$_SESSION['acl']['quarantine_category'] = "1"; +$_SESSION['acl']['ratelimit'] = "1"; +$_SESSION['acl']['sogo_access'] = "1"; +$_SESSION['acl']['protocol_access'] = "1"; +$_SESSION['acl']['mailbox_relayhost'] = "1"; +$_SESSION['acl']['unlimited_quota'] = "1"; + +$iam_settings = identity_provider('get'); +if ($iam_settings['authsource'] != "keycloak" || (intval($iam_settings['periodic_sync']) != 1 && intval($iam_settings['import_users']) != 1)) { + session_destroy(); + exit; +} + +// Set pagination variables +$start = 0; +$max = 100; + +// lock sync if already running +$lock_file = '/tmp/iam-sync.lock'; +if (file_exists($lock_file)) { + $lock_file_parts = explode("\n", file_get_contents($lock_file)); + $pid = $lock_file_parts[0]; + if (count($lock_file_parts) > 1){ + $last_execution = $lock_file_parts[1]; + $elapsed_time = (time() - $last_execution) / 60; + if ($elapsed_time < intval($iam_settings['sync_interval'])) { + logMsg("warning", "Sync not ready (".number_format((float)$elapsed_time, 2, '.', '')."min / ".$iam_settings['sync_interval']."min)"); + session_destroy(); + exit; + } + } + + if (posix_kill($pid, 0)) { + logMsg("warning", "Sync is already running"); + session_destroy(); + exit; + } else { + unlink($lock_file); + } +} +$lock_file_handle = fopen($lock_file, 'w'); +fwrite($lock_file_handle, getmypid()); +fclose($lock_file_handle); + +// Init Keycloak Provider +$iam_provider = identity_provider('init'); + +// Loop until all users have been retrieved +while (true) { + // Get admin access token + $admin_token = identity_provider("get-keycloak-admin-token"); + + // Make the API request to retrieve the users + $url = "{$iam_settings['server_url']}/admin/realms/{$iam_settings['realm']}/users?first=$start&max=$max"; + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Content-Type: application/json", + "Authorization: Bearer " . $admin_token + ]); + $response = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($code != 200){ + logMsg("err", "Received HTTP {$code}"); + session_destroy(); + exit; + } + try { + $response = json_decode($response, true); + } catch (Exception $e) { + logMsg("err", $e->getMessage()); + break; + } + if (!is_array($response)){ + logMsg("err", "Received malformed response from keycloak api"); + break; + } + if (count($response) == 0) { + break; + } + + // Process the batch of users + foreach ($response as $user) { + if (empty($user['email'])){ + logMsg("warning", "No email address in keycloak found for user " . $user['name']); + continue; + } + + // try get mailbox user + $stmt = $pdo->prepare("SELECT + mailbox.*, + domain.active AS d_active + FROM `mailbox` + INNER JOIN domain on mailbox.domain = domain.domain + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `username` = :user"); + $stmt->execute(array(':user' => $user['email'])); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + // check if matching attribute mapping exists + $user_template = $user['attributes']['mailcow_template'][0]; + $mapper_key = array_search($user_template, $iam_settings['mappers']); + + $_SESSION['access_all_exception'] = '1'; + if (!$row && intval($iam_settings['import_users']) == 1){ + if ($mapper_key === false){ + if (!empty($iam_settings['default_template'])) { + $mbox_template = $iam_settings['default_template']; + logMsg("warning", "Using default template for user " . $user['email']); + } else { + logMsg("warning", "No matching attribute mapping found for user " . $user['email']); + continue; + } + } else { + $mbox_template = $iam_settings['templates'][$mapper_key]; + } + // mailbox user does not exist, create... + logMsg("info", "Creating user " . $user['email']); + $create_res = mailbox('add', 'mailbox_from_template', array( + 'domain' => explode('@', $user['email'])[1], + 'local_part' => explode('@', $user['email'])[0], + 'name' => $user['firstName'] . " " . $user['lastName'], + 'authsource' => 'keycloak', + 'template' => $mbox_template + )); + if (!$create_res){ + logMsg("err", "Could not create user " . $user['email']); + continue; + } + } else if ($row && intval($iam_settings['periodic_sync']) == 1 && $row['authsource'] == "keycloak") { + if ($mapper_key === false){ + logMsg("warning", "No matching attribute mapping found for user " . $user['email']); + continue; + } + $mbox_template = $iam_settings['templates'][$mapper_key]; + // mailbox user does exist, sync attribtues... + logMsg("info", "Syncing attributes for user " . $user['email']); + mailbox('edit', 'mailbox_from_template', array( + 'username' => $user['email'], + 'name' => $user['firstName'] . " " . $user['lastName'], + 'template' => $mbox_template + )); + } else { + // skip mailbox user + logMsg("info", "Skipping user " . $user['email']); + } + $_SESSION['access_all_exception'] = '0'; + + sleep(0.025); + } + + // Update the pagination variables for the next batch + $start += $max; + sleep(1); +} + +logMsg("info", "DONE!"); +// add last execution time to lock file +$lock_file_handle = fopen($lock_file, 'w'); +fwrite($lock_file_handle, getmypid() . "\n" . time()); +fclose($lock_file_handle); +session_destroy(); diff --git a/data/conf/phpfpm/crons/ldap-sync.php b/data/conf/phpfpm/crons/ldap-sync.php new file mode 100644 index 0000000000..32026c071e --- /dev/null +++ b/data/conf/phpfpm/crons/ldap-sync.php @@ -0,0 +1,198 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); +} +catch (PDOException $e) { + logMsg("err", $e->getMessage()); + session_destroy(); + exit; +} + +// Init Redis +$redis = new Redis(); +try { + if (!empty(getenv('REDIS_SLAVEOF_IP'))) { + $redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT')); + } + else { + $redis->connect('redis-mailcow', 6379); + } + $redis->auth(getenv("REDISPASS")); +} +catch (Exception $e) { + echo "Exiting: " . $e->getMessage(); + session_destroy(); + exit; +} + +function logMsg($priority, $message, $task = "LDAP Sync") { + global $redis; + + $finalMsg = array( + "time" => time(), + "priority" => $priority, + "task" => $task, + "message" => $message + ); + $redis->lPush('CRON_LOG', json_encode($finalMsg)); +} + +// Load core functions first +require_once __DIR__ . '/../web/inc/functions.inc.php'; +require_once __DIR__ . '/../web/inc/functions.auth.inc.php'; +require_once __DIR__ . '/../web/inc/sessions.inc.php'; +require_once __DIR__ . '/../web/inc/functions.mailbox.inc.php'; +require_once __DIR__ . '/../web/inc/functions.ratelimit.inc.php'; +require_once __DIR__ . '/../web/inc/functions.acl.inc.php'; + +$_SESSION['mailcow_cc_username'] = "admin"; +$_SESSION['mailcow_cc_role'] = "admin"; +$_SESSION['acl']['tls_policy'] = "1"; +$_SESSION['acl']['quarantine_notification'] = "1"; +$_SESSION['acl']['quarantine_category'] = "1"; +$_SESSION['acl']['ratelimit'] = "1"; +$_SESSION['acl']['sogo_access'] = "1"; +$_SESSION['acl']['protocol_access'] = "1"; +$_SESSION['acl']['mailbox_relayhost'] = "1"; +$_SESSION['acl']['unlimited_quota'] = "1"; + +$iam_settings = identity_provider('get'); +if ($iam_settings['authsource'] != "ldap" || (intval($iam_settings['periodic_sync']) != 1 && intval($iam_settings['import_users']) != 1)) { + session_destroy(); + exit; +} + +// Set pagination variables +$start = 0; +$max = 100; + +// lock sync if already running +$lock_file = '/tmp/iam-sync.lock'; +if (file_exists($lock_file)) { + $lock_file_parts = explode("\n", file_get_contents($lock_file)); + $pid = $lock_file_parts[0]; + if (count($lock_file_parts) > 1){ + $last_execution = $lock_file_parts[1]; + $elapsed_time = (time() - $last_execution) / 60; + if ($elapsed_time < intval($iam_settings['sync_interval'])) { + logMsg("warning", "Sync not ready (".number_format((float)$elapsed_time, 2, '.', '')."min / ".$iam_settings['sync_interval']."min)"); + session_destroy(); + exit; + } + } + + if (posix_kill($pid, 0)) { + logMsg("warning", "Sync is already running"); + session_destroy(); + exit; + } else { + unlink($lock_file); + } +} +$lock_file_handle = fopen($lock_file, 'w'); +fwrite($lock_file_handle, getmypid()); +fclose($lock_file_handle); + +// Init Provider +$iam_provider = identity_provider('init'); + +// Get ldap users +$ldap_query = $iam_provider->query(); +if (!empty($iam_settings['filter'])) { + $ldap_query = $ldap_query->rawFilter($iam_settings['filter']); +} +$response = $ldap_query->where($iam_settings['username_field'], "*") + ->where($iam_settings['attribute_field'], "*") + ->select([$iam_settings['username_field'], $iam_settings['attribute_field'], 'displayname']) + ->paginate($max); + +// Process the users +foreach ($response as $user) { + // try get mailbox user + $stmt = $pdo->prepare("SELECT + mailbox.*, + domain.active AS d_active + FROM `mailbox` + INNER JOIN domain on mailbox.domain = domain.domain + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `username` = :user"); + $stmt->execute(array(':user' => $user[$iam_settings['username_field']][0])); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + // check if matching attribute mapping exists + $user_template = $user[$iam_settings['attribute_field']][0]; + $mapper_key = array_search($user_template, $iam_settings['mappers']); + + if (empty($user[$iam_settings['username_field']][0])){ + logMsg("warning", "Skipping user " . $user['displayname'][0] . " due to empty LDAP ". $iam_settings['username_field'] . " property."); + continue; + } + + $_SESSION['access_all_exception'] = '1'; + if (!$row && intval($iam_settings['import_users']) == 1){ + if ($mapper_key === false){ + if (!empty($iam_settings['default_template'])) { + $mbox_template = $iam_settings['default_template']; + } else { + logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]); + continue; + } + } else { + $mbox_template = $iam_settings['templates'][$mapper_key]; + } + // mailbox user does not exist, create... + logMsg("info", "Creating user " . $user[$iam_settings['username_field']][0]); + $create_res = mailbox('add', 'mailbox_from_template', array( + 'domain' => explode('@', $user[$iam_settings['username_field']][0])[1], + 'local_part' => explode('@', $user[$iam_settings['username_field']][0])[0], + 'name' => $user['displayname'][0], + 'authsource' => 'ldap', + 'template' => $mbox_template + )); + if (!$create_res){ + logMsg("err", "Could not create user " . $user[$iam_settings['username_field']][0]); + continue; + } + } else if ($row && intval($iam_settings['periodic_sync']) == 1 && $row['authsource'] == "ldap") { + if ($mapper_key === false){ + logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]); + continue; + } + $mbox_template = $iam_settings['templates'][$mapper_key]; + // mailbox user does exist, sync attribtues... + logMsg("info", "Syncing attributes for user " . $user[$iam_settings['username_field']][0]); + mailbox('edit', 'mailbox_from_template', array( + 'username' => $user[$iam_settings['username_field']][0], + 'name' => $user['displayname'][0], + 'template' => $mbox_template + )); + } else { + // skip mailbox user + logMsg("info", "Skipping user " . $user[$iam_settings['username_field']][0]); + } + $_SESSION['access_all_exception'] = '0'; + + sleep(0.025); +} + +logMsg("info", "DONE!"); +// add last execution time to lock file +$lock_file_handle = fopen($lock_file, 'w'); +fwrite($lock_file_handle, getmypid() . "\n" . time()); +fclose($lock_file_handle); +session_destroy(); diff --git a/data/conf/phpfpm/php-conf.d/opcache-recommended.ini b/data/conf/phpfpm/php-conf.d/opcache-recommended.ini index 104f2425c9..452b454840 100644 --- a/data/conf/phpfpm/php-conf.d/opcache-recommended.ini +++ b/data/conf/phpfpm/php-conf.d/opcache-recommended.ini @@ -1,7 +1,16 @@ +; NOTE: Restart phpfpm on ANY manual changes to PHP files! + +; opcache opcache.enable=1 opcache.enable_cli=1 -opcache.interned_strings_buffer=8 +opcache.interned_strings_buffer=16 opcache.max_accelerated_files=10000 opcache.memory_consumption=128 opcache.save_comments=1 -opcache.revalidate_freq=1 +opcache.validate_timestamps=0 + +; JIT +; Disabled for now due to some PHP segmentation faults observed +; in certain environments. Possibly some PHP or PHP extension bug. +opcache.jit=disable +opcache.jit_buffer_size=0 diff --git a/data/conf/phpfpm/php-conf.d/other.ini b/data/conf/phpfpm/php-conf.d/other.ini index 4a90901d74..02f59a9c07 100644 --- a/data/conf/phpfpm/php-conf.d/other.ini +++ b/data/conf/phpfpm/php-conf.d/other.ini @@ -1,4 +1,3 @@ -session.save_handler = redis -session.save_path = "tcp://redis:6379" -max_execution_time = 1200 -max_input_time = 1200 +max_execution_time = 3600 +max_input_time = 3600 +memory_limit = 512M diff --git a/data/conf/phpfpm/php-fpm.d/pools.conf b/data/conf/phpfpm/php-fpm.d/pools.conf index c0db1e0413..605e686c10 100644 --- a/data/conf/phpfpm/php-fpm.d/pools.conf +++ b/data/conf/phpfpm/php-fpm.d/pools.conf @@ -11,17 +11,19 @@ access.log = /proc/self/fd/2 clear_env = no catch_workers_output = yes php_admin_value[memory_limit] = 256M +php_admin_value[disable_functions] = show_source, highlight_file, apache_child_terminate, apache_get_modules, apache_note, apache_setenv, virtual, dl, disk_total_space, posix_getpwnam, posix_getpwuid, posix_mkfifo, posix_mknod, posix_setpgid, posix_setsid, posix_setuid, posix_uname, proc_nice, openlog, syslog, pfsockopen, system, shell_exec, passthru, popen, proc_open, exec, ini_alter, pcntl_exec, proc_close, proc_get_status, proc_terminate, symlink [web-worker] user = www-data group = www-data pm = dynamic pm.max_children = 50 -pm.start_servers = 2 -pm.min_spare_servers = 2 -pm.max_spare_servers = 4 +pm.start_servers = 10 +pm.min_spare_servers = 10 +pm.max_spare_servers = 15 listen = [::]:9002 access.log = /proc/self/fd/2 clear_env = no catch_workers_output = yes php_admin_value[memory_limit] = 512M +php_admin_value[disable_functions] = show_source, highlight_file, apache_child_terminate, apache_get_modules, apache_note, apache_setenv, virtual, dl, disk_total_space, posix_getpwnam, posix_getpwuid, posix_mkfifo, posix_mknod, posix_setpgid, posix_setsid, posix_setuid, posix_uname, proc_nice, openlog, syslog, pfsockopen, system, shell_exec, passthru, popen, proc_open, exec, ini_alter, pcntl_exec, proc_close, proc_get_status, proc_terminate, symlink diff --git a/data/conf/phpfpm/sogo-sso/.gitkeep b/data/conf/phpfpm/sogo-sso/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/conf/postfix/allow_mailcow_local.regexp b/data/conf/postfix/allow_mailcow_local.regexp new file mode 100644 index 0000000000..0da4593ce7 --- /dev/null +++ b/data/conf/postfix/allow_mailcow_local.regexp @@ -0,0 +1 @@ +/^(.+)@mailcow.local/ OK diff --git a/data/conf/postfix/anonymize_headers.pcre b/data/conf/postfix/anonymize_headers.pcre new file mode 100644 index 0000000000..1a59d53a8b --- /dev/null +++ b/data/conf/postfix/anonymize_headers.pcre @@ -0,0 +1,20 @@ +if /^\s*Received:.*Authenticated sender.*\(Postcow\)/ +#/^Received: from .*? \([\w-.]* \[.*?\]\)\s+\(Authenticated sender: (.+)\)\s+by.+\(Postcow\) with (E?SMTPS?A?) id ([A-F0-9]+).+;.*?/ +/^Received: from .*? \([\w\-.]* \[.*?\]\)(.*|\n.*)\(Authenticated sender: (.+)\)\s+by.+\(Postcow\) with (.*)/ + REPLACE Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with $3 +endif +if /^\s*Received: from.* \(.*dovecot-mailcow.*mailcow-network.*\).*\(Postcow\)/ +/^Received: from.* (.*|\n.*)\((.+) (.+)\)\s+by (.+) \(Postcow\) with (.*)/ + REPLACE Received: from sieve (sieve $3) by $4 (Postcow) with $5 +endif +if /^\s*Received: from.* \(.*rspamd-mailcow.*mailcow-network.*\).*\(Postcow\)/ +/^Received: from.* (.*|\n.*)\((.+) (.+)\)\s+by (.+) \(Postcow\) with (.*)/ + REPLACE Received: from rspamd (rspamd $3) by $4 (Postcow) with $5 +endif +/^\s*X-Enigmail/ IGNORE +# Not removing Mailer by default, might be signed +#/^\s*X-Mailer/ IGNORE +/^\s*X-Originating-IP/ IGNORE +/^\s*X-Forward/ IGNORE +# Not removing UA by default, might be signed +#/^\s*User-Agent/ IGNORE diff --git a/data/conf/postfix/local_transport b/data/conf/postfix/local_transport new file mode 100644 index 0000000000..6dd2101110 --- /dev/null +++ b/data/conf/postfix/local_transport @@ -0,0 +1,2 @@ +/watchdog@localhost$/ watchdog_discard: +/localhost$/ local: diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index c5feeebb01..080be499af 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -1,16 +1,22 @@ +# -------------------------------------------------------------------------- +# Please create a file "extra.cf" for persistent overrides to main.cf +# -------------------------------------------------------------------------- biff = no append_dot_mydomain = no smtpd_tls_cert_file = /etc/ssl/mail/cert.pem smtpd_tls_key_file = /etc/ssl/mail/key.pem -smtpd_use_tls=yes +tls_server_sni_maps = hash:/opt/postfix/conf/sni.map smtpd_tls_received_header = yes -smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache -smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination +smtpd_relay_restrictions = permit_mynetworks, + permit_sasl_authenticated, + defer_unauth_destination +smtpd_forbid_bare_newline = yes +# alias maps are auto-generated in postfix.sh on startup alias_maps = hash:/etc/aliases alias_database = hash:/etc/aliases relayhost = -mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 [fe80::]/10 [fc00::]/7 +mynetworks_style = subnet mailbox_size_limit = 0 recipient_delimiter = + inet_interfaces = all @@ -19,20 +25,23 @@ bounce_queue_lifetime = 1d broken_sasl_auth_clients = yes disable_vrfy_command = yes maximal_backoff_time = 1800s -maximal_queue_lifetime = 1d +maximal_queue_lifetime = 5d +delay_warning_time = 4h message_size_limit = 104857600 -milter_default_action = accept +milter_default_action = tempfail milter_protocol = 6 minimal_backoff_time = 300s plaintext_reject_code = 550 -postscreen_access_list = permit_mynetworks, cidr:/opt/postfix/conf/postscreen_access.cidr, tcp:127.0.0.1:10027 +postscreen_access_list = permit_mynetworks, + cidr:/opt/postfix/conf/custom_postscreen_whitelist.cidr, + cidr:/opt/postfix/conf/postscreen_access.cidr, + tcp:127.0.0.1:10027 postscreen_bare_newline_enable = no -postscreen_blacklist_action = drop +postscreen_denylist_action = drop postscreen_cache_cleanup_interval = 24h postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache postscreen_dnsbl_action = enforce -postscreen_dnsbl_sites = b.barracudacentral.org=127.0.0.2*7 dnsbl.inps.de=127.0.0.2*7 bl.mailspike.net=127.0.0.2*5 bl.mailspike.net=127.0.0.[10;11;12]*4 dnsbl.sorbs.net=127.0.0.10*8 dnsbl.sorbs.net=127.0.0.5*6 dnsbl.sorbs.net=127.0.0.7*3 dnsbl.sorbs.net=127.0.0.8*2 dnsbl.sorbs.net=127.0.0.6*2 dnsbl.sorbs.net=127.0.0.9*2 zen.spamhaus.org=127.0.0.[10;11]*8 zen.spamhaus.org=127.0.0.[4..7]*6 zen.spamhaus.org=127.0.0.3*4 zen.spamhaus.org=127.0.0.2*3 hostkarma.junkemailfilter.com=127.0.0.2*3 hostkarma.junkemailfilter.com=127.0.0.4*1 hostkarma.junkemailfilter.com=127.0.1.2*1 wl.mailspike.net=127.0.0.[18;19;20]*-2 hostkarma.junkemailfilter.com=127.0.0.1*-2 -postscreen_dnsbl_threshold = 8 +postscreen_dnsbl_threshold = 6 postscreen_dnsbl_ttl = 5m postscreen_greet_action = enforce postscreen_greet_banner = $smtpd_banner @@ -40,14 +49,11 @@ postscreen_greet_ttl = 2d postscreen_greet_wait = 3s postscreen_non_smtp_command_enable = no postscreen_pipelining_enable = no -proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf, - proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf, - proxy:mysql:/opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf, - proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf, +proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf, + proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, - proxy:mysql:/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf, - proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf, - proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf, + $sender_dependent_default_transport_maps, + $smtp_tls_policy_maps, $local_recipient_maps, $mydestination, $virtual_alias_maps, @@ -58,11 +64,14 @@ proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf, $relay_domains, $canonical_maps, $sender_canonical_maps, + $sender_bcc_maps, + $recipient_bcc_maps, $recipient_canonical_maps, $relocated_maps, $transport_maps, $mynetworks, - $smtpd_sender_login_maps + $smtpd_sender_login_maps, + $smtp_sasl_password_maps queue_run_delay = 300s relay_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_relay_domain_maps.cf relay_recipient_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_relay_recipient_maps.cf @@ -76,40 +85,55 @@ smtp_tls_security_level = dane smtpd_data_restrictions = reject_unauth_pipelining, permit smtpd_delay_reject = yes smtpd_error_sleep_time = 10s +smtpd_forbid_bare_newline = yes smtpd_hard_error_limit = ${stress?1}${stress:5} smtpd_helo_required = yes smtpd_proxy_timeout = 600s -smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, check_recipient_access proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, reject_invalid_helo_hostname, reject_unknown_reverse_client_hostname, reject_unauth_destination +smtpd_recipient_restrictions = check_recipient_mx_access proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf, + permit_sasl_authenticated, + permit_mynetworks, + check_recipient_access proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, + reject_invalid_helo_hostname, + reject_unauth_destination smtpd_sasl_auth_enable = yes smtpd_sasl_authenticated_header = yes smtpd_sasl_path = inet:dovecot:10001 smtpd_sasl_type = dovecot smtpd_sender_login_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf -smtpd_sender_restrictions = reject_authenticated_sender_login_mismatch, permit_mynetworks, permit_sasl_authenticated, reject_unlisted_sender, reject_unknown_sender_domain +smtpd_sender_restrictions = reject_authenticated_sender_login_mismatch, + permit_mynetworks, + permit_sasl_authenticated, + reject_unlisted_sender, + reject_unknown_sender_domain smtpd_soft_error_limit = 3 smtpd_tls_auth_only = yes -smtpd_tls_dh1024_param_file = /etc/ssl/mail/dhparams.pem -smtpd_tls_eecdh_grade = auto smtpd_tls_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL, DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA smtpd_tls_loglevel = 1 -smtp_tls_mandatory_protocols = !SSLv2, !SSLv3 -smtp_tls_protocols = !SSLv2, !SSLv3 -lmtp_tls_mandatory_protocols = !SSLv2, !SSLv3 -lmtp_tls_protocols = !SSLv2, !SSLv2, !SSLv3 -smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3 -smtpd_tls_protocols = !SSLv2, !SSLv3 -smtpd_tls_security_level = may -tls_ssl_options = NO_COMPRESSION + +# Mandatory protocols and ciphers are used when a connections is enforced to use TLS +# Does _not_ apply to enforced incoming TLS settings per mailbox +smtp_tls_mandatory_protocols = >=TLSv1.2 +lmtp_tls_mandatory_protocols = >=TLSv1.2 +smtpd_tls_mandatory_protocols = >=TLSv1.2 smtpd_tls_mandatory_ciphers = high + +smtp_tls_protocols = >=TLSv1.2 +lmtp_tls_protocols = >=TLSv1.2 +smtpd_tls_protocols = >=TLSv1.2 + +smtpd_tls_security_level = may +tls_preempt_cipherlist = yes +tls_ssl_options = NO_COMPRESSION, NO_RENEGOTIATION virtual_alias_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_maps.cf, + proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_resource_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf, - proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf, - proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf + proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf virtual_gid_maps = static:5000 virtual_mailbox_base = /var/vmail/ virtual_mailbox_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_domains_maps.cf -recipient_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf -sender_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf +# -- moved to rspamd on 2021-06-01 +#recipient_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf +#sender_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf recipient_canonical_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf recipient_canonical_classes = envelope_recipient virtual_mailbox_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf @@ -120,12 +144,31 @@ smtpd_milters = inet:rspamd:9900 non_smtpd_milters = inet:rspamd:9900 milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen} mydestination = localhost.localdomain, localhost -#content_filter=zeyple -# Prefere IPv4, useful for v4-only envs -smtp_address_preference = ipv4 +smtp_address_preference = any smtp_sender_dependent_authentication = yes smtp_sasl_auth_enable = yes -smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf -smtp_sasl_security_options = +smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf +smtp_sasl_security_options = smtp_sasl_mechanism_filter = plain, login -smtp_tls_policy_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf +smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:postfix-tlspol:8642:QUERY +smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre +mail_name = Postcow +# local_transport map catches local destinations and prevents routing local dests when the next map would route "*" +# Use custom_transport.pcre for custom transports +transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre, + pcre:/opt/postfix/conf/local_transport, + proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf, + proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf +smtp_sasl_auth_soft_bounce = no +postscreen_discard_ehlo_keywords = chunking, silent-discard, smtputf8, dsn +smtpd_discard_ehlo_keywords = chunking, silent-discard, smtputf8 +compatibility_level = 3.7 +# Define protocols for SMTPS and submission service +submission_smtpd_tls_mandatory_protocols = >=TLSv1.2 +smtps_smtpd_tls_mandatory_protocols = >=TLSv1.2 +parent_domain_matches_subdomains = debug_peer_list,fast_flush_domains,mynetworks,qmqpd_authorized_clients +# This Option is added to correctly set the X-Original-To Header when mails are send to lmtp (dovecot) +lmtp_destination_recipient_limit=1 + +# DO NOT EDIT ANYTHING BELOW # +# Overrides # diff --git a/data/conf/postfix/master.cf b/data/conf/postfix/master.cf index 4911271e6c..fb49f2df2c 100644 --- a/data/conf/postfix/master.cf +++ b/data/conf/postfix/master.cf @@ -1,27 +1,86 @@ +# inter-mx with postscreen on 25/tcp smtp inet n - n - 1 postscreen +10025 inet n - n - 1 postscreen + -o postscreen_upstream_proxy_protocol=haproxy + -o syslog_name=haproxy smtpd pass - - n - - smtpd - -o smtpd_helo_restrictions=permit_mynetworks,reject_non_fqdn_helo_hostname + -o smtpd_sasl_auth_enable=no + -o smtpd_sender_restrictions=permit_mynetworks,reject_unlisted_sender,reject_unknown_sender_domain + +# smtpd tls-wrapped (smtps) on 465/tcp +# TLS protocol can be modified by setting smtps_smtpd_tls_mandatory_protocols in extra.cf smtps inet n - n - - smtpd -o smtpd_tls_wrappermode=yes -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject + -o smtpd_tls_mandatory_protocols=$smtps_smtpd_tls_mandatory_protocols + -o tls_preempt_cipherlist=yes + -o cleanup_service_name=smtp_sender_cleanup + -o syslog_name=postfix/smtps +10465 inet n - n - - smtpd + -o smtpd_upstream_proxy_protocol=haproxy + -o smtpd_tls_wrappermode=yes + -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject + -o smtpd_tls_mandatory_protocols=$smtps_smtpd_tls_mandatory_protocols + -o tls_preempt_cipherlist=yes + -o cleanup_service_name=smtp_sender_cleanup + -o syslog_name=postfix/smtps-haproxy + +# smtpd with starttls on 587/tcp +# TLS protocol can be modified by setting submission_smtpd_tls_mandatory_protocols in extra.cf submission inet n - n - - smtpd -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject - -o smtpd_enforce_tls=yes -o smtpd_tls_security_level=encrypt + -o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols -o tls_preempt_cipherlist=yes + -o cleanup_service_name=smtp_sender_cleanup + -o syslog_name=postfix/submission +10587 inet n - n - - smtpd + -o smtpd_upstream_proxy_protocol=haproxy + -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject + -o smtpd_tls_security_level=encrypt + -o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols + -o tls_preempt_cipherlist=yes + -o cleanup_service_name=smtp_sender_cleanup + -o syslog_name=postfix/submission-haproxy + +# used by SOGo +# smtpd_sender_restrictions should match main.cf, but with check_sasl_access prepended for login-as-mailbox-user function 588 inet n - n - - smtpd -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_tls_auth_only=no + -o smtpd_sender_restrictions=check_sasl_access,regexp:/opt/postfix/conf/allow_mailcow_local.regexp,reject_authenticated_sender_login_mismatch,permit_mynetworks,permit_sasl_authenticated,reject_unlisted_sender,reject_unknown_sender_domain + -o cleanup_service_name=smtp_sender_cleanup + -o syslog_name=postfix/sogo + +# used to reinject quarantine mails 590 inet n - n - - smtpd + -o smtpd_helo_restrictions= -o smtpd_client_restrictions=permit_mynetworks,reject -o smtpd_tls_auth_only=no -o smtpd_milters= -o non_smtpd_milters= + -o syslog_name=postfix/quarantine + +# used to send bcc mails +591 inet n - n - - smtpd + -o smtpd_helo_restrictions= + -o smtpd_client_restrictions=permit_mynetworks,reject + -o smtpd_tls_auth_only=no + -o smtpd_milters= + -o non_smtpd_milters= + -o syslog_name=postfix/bcc + +# enforced smtp connector smtp_enforced_tls unix - - n - - smtp -o smtp_tls_security_level=encrypt -o syslog_name=enforced-tls-smtp -o smtp_delivery_status_filter=pcre:/opt/postfix/conf/smtp_dsn_filter +# smtp connector used, when a transport map matched +# this helps to have different sasl maps than we have with sender dependent transport maps +smtp_via_transport_maps unix - - n - - smtp + -o smtp_sasl_password_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf + tlsproxy unix - - n - 0 tlsproxy dnsblog unix - - n - 0 dnsblog pickup fifo n - n 60 1 pickup @@ -44,31 +103,22 @@ retry unix - - n - - error discard unix - - n - - discard local unix - n n - - local virtual unix - n n - - virtual -lmtp unix - - n - - lmtp +lmtp unix - - n - - lmtp flags=O anvil unix - - n - 1 anvil scache unix - - n - 1 scache maildrop unix - n n - - pipe flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient} -# start zeyple -zeyple unix - n n - - pipe - user=zeyple argv=/usr/local/bin/zeyple.py ${recipient} -127.0.0.1:10026 inet n - n - 10 smtpd - -o content_filter= - -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters - -o smtpd_helo_restrictions= - -o smtpd_client_restrictions= - -o smtpd_sender_restrictions= - -o smtpd_recipient_restrictions=permit_mynetworks,reject - -o mynetworks=127.0.0.0/8 - -o smtpd_authorized_xforward_hosts=127.0.0.0/8 -# end zeyple +# used to anonymize sender IP +smtp_sender_cleanup unix n - y - 0 cleanup + -o header_checks=$smtp_header_checks # start whitelist_fwd 127.0.0.1:10027 inet n n n - 0 spawn user=nobody argv=/usr/local/bin/whitelist_forwardinghosts.sh # end whitelist_fwd # start watchdog-specific +# logs to local7 (hidden) 589 inet n - n - - smtpd -o smtpd_client_restrictions=permit_mynetworks,reject -o syslog_name=watchdog @@ -91,5 +141,4 @@ watchdog_rewrite unix - - n - - trivial-rewrit watchdog_discard unix - - n - - discard -o syslog_facility=local7 -o syslog_name=watchdog - # end watchdog-specific diff --git a/data/conf/postfix/postscreen_access.cidr b/data/conf/postfix/postscreen_access.cidr index c76ec681e3..3801fe31e1 100644 --- a/data/conf/postfix/postscreen_access.cidr +++ b/data/conf/postfix/postscreen_access.cidr @@ -1,654 +1,2261 @@ -# Generated by Postwhite v1.30 on Thu Dec 8 21:11:27 CET 2016 +# Whitelist generated by Postwhite v3.4 on Sat Aug 1 00:35:20 UTC 2026 # https://github.com/stevejenkins/postwhite/ -# 651 total rules -2a00:1450:4000::/36 permit -2a01:111:f400::/48 permit -2a04:35c0::/29 permit -2c0f:fb50:4000::/36 permit -5.135.24.0/24 permit -8.20.114.31 permit -8.25.194.0/23 permit -8.25.196.0/23 permit -12.130.86.238 permit -13.111.0.0/19 permit -17.36.0.0/16 permit -17.41.0.0/16 permit -17.110.0.0/15 permit -17.120.0.0/16 permit -17.133.0.0/16 permit -17.139.0.0/16 permit -17.142.0.0/15 permit -17.151.1.0/24 permit -17.158.0.0/15 permit -17.162.0.0/15 permit -17.164.0.0/16 permit -17.171.37.0/24 permit -17.172.0.0/16 permit -23.21.83.90 permit -23.23.237.213 permit -23.96.52.53 permit -23.100.122.175 permit -23.103.128.0/19 permit -23.103.131.7 permit -23.103.191.0/24 permit -23.103.198.0/23 permit -23.103.200.0/21 permit -23.103.208.0/21 permit -23.103.224.0/19 permit -23.253.182.0/24 permit -23.253.182.103 permit -23.253.183.0/24 permit -23.253.183.145 permit -23.253.183.146 permit -23.253.183.147 permit -23.253.183.148 permit -23.253.183.150 permit -27.126.146.0/24 permit -37.59.69.128/25 permit -37.59.249.0/24 permit -37.188.97.188/32 permit -40.92.0.0/14 permit -40.96.32.50 permit -40.97.113.34 permit -40.97.113.210 permit -40.97.153.146 permit -40.97.155.26 permit -40.97.156.114 permit -40.97.160.2 permit -40.97.164.146 permit -40.97.166.138 permit -40.97.170.154 permit -40.107.0.0/17 permit -40.107.128.0/18 permit -41.74.192.0/22 permit -41.74.196.0/22 permit -41.74.200.0/22 permit -41.74.201.0/24 permit -41.74.204.0/22 permit -41.74.205.0/24 permit -46.19.168.0/23 permit -50.18.45.249 permit -50.18.121.236 permit -50.18.121.248 permit -50.18.123.221 permit -50.18.124.70 permit -50.18.125.97 permit -50.18.125.237 permit -50.18.126.162 permit -50.22.164.201 permit -50.23.218.192/27 permit -50.31.32.0/19 permit -50.31.36.197 permit -50.31.36.199 permit -50.31.36.205 permit -50.31.36.208 permit -50.31.36.213 permit -50.31.60.1 permit -50.31.156.96/27 permit -50.207.218.237 permit -51.4.71.62 permit -52.0.20.102 permit -52.95.48.152/29 permit -52.95.49.88/29 permit -52.205.61.79 permit -54.172.97.247 permit -54.173.229.38 permit -54.214.39.184 permit -54.240.0.0/18 permit -54.241.16.209 permit -54.243.205.80 permit -54.244.242.0/24 permit -62.17.146.128/26 permit -63.80.14.0/23 permit -63.111.28.137 permit -63.128.21.0/24 permit -64.4.22.64/26 permit -64.18.0.0/20 permit -64.20.241.45 permit -64.34.47.128/27 permit -64.34.57.192/26 permit -64.79.155.0/24 permit -64.79.155.192 permit -64.127.115.252 permit -64.132.88.0/23 permit -64.132.92.0/24 permit -64.135.77.0/24 permit -64.135.83.0/24 permit -64.233.160.0/19 permit -65.39.215.0/24 permit -65.54.51.64/26 permit -65.54.61.64/26 permit -65.54.121.120/29 permit -65.54.121.124/31 permit -65.54.190.0/24 permit -65.54.241.0/24 permit -65.55.33.64/28 permit -65.55.34.0/24 permit -65.55.42.224/28 permit -65.55.52.224/27 permit -65.55.77.28 permit -65.55.78.128/25 permit -65.55.81.48/28 permit -65.55.81.54/31 permit -65.55.85.12 permit -65.55.88.0/24 permit -65.55.90.0/24 permit -65.55.94.0/25 permit -65.55.111.0/24 permit -65.55.113.64/26 permit -65.55.116.0/25 permit -65.55.126.0/25 permit -65.55.169.0/24 permit -65.55.174.0/25 permit -65.55.178.128/27 permit -65.55.234.192/26 permit -65.110.161.77 permit -65.212.180.36 permit -65.242.92.0/24 permit -65.242.92.15 permit -66.77.16.201/32 permit -66.102.0.0/20 permit -66.135.215.0/24 permit -66.135.222.1 permit -66.211.168.230/31 permit -66.211.184.0/23 permit -66.220.144.128/25 permit -66.220.155.0/24 permit -66.220.155.128/25 permit -66.220.157.0/25 permit -66.231.80.0/20 permit -66.249.80.0/20 permit -67.23.31.6 permit -67.72.99.26 permit -67.221.168.65 permit -67.228.2.24/30 permit -67.228.21.184/29 permit -67.228.37.4/30 permit -67.228.50.32/27 permit -67.228.50.54/31 permit -67.231.145.42 permit -67.231.153.30 permit -68.232.192.0/20 permit -69.63.178.128/25 permit -69.63.179.25 permit -69.63.184.0/25 permit -69.65.42.195 permit -69.65.49.192/29 permit -69.162.98.0/24 permit -69.171.232.0/24 permit -69.171.232.128/25 permit -69.171.244.0/24 permit -70.37.151.128/25 permit -70.42.149.35 permit -72.3.185.0/24 permit -72.3.237.64/28 permit -72.5.230.111/32 permit -72.14.192.0/18 permit -72.21.192.0/19 permit -72.21.212.0/25 permit -72.21.217.142/32 permit -72.32.154.0/24 permit -72.32.217.0/24 permit -72.32.243.0/24 permit -72.249.147.250/32 permit -74.63.63.115 permit -74.63.63.121 permit -74.63.194.126 permit -74.63.234.75 permit -74.63.236.0/24 permit -74.86.113.28/30 permit -74.86.129.240/30 permit -74.86.131.208/30 permit -74.86.132.208/30 permit -74.86.160.160/30 permit -74.86.164.188/30 permit -74.86.171.192/30 permit -74.86.195.28/30 permit -74.86.207.36/30 permit -74.86.226.216/30 permit -74.86.236.240/30 permit -74.86.241.250/31 permit -74.112.64.26 permit -74.112.67.243 permit -74.112.170.21/32 permit -74.125.0.0/16 permit -74.201.84.0/24 permit -74.201.152.59/32 permit -74.201.154.0/24 permit -74.201.155.25/32 permit -74.201.155.26/32 permit -74.201.155.27/32 permit -74.201.155.28/32 permit -74.201.155.79/32 permit -74.202.227.52/32 permit -74.208.4.192/26 permit -74.208.5.64/26 permit -74.208.122.0/26 permit -74.209.250.0/24 permit -74.209.250.84 permit -75.126.200.128/27 permit -75.126.253.0/24 permit -75.126.253.48 permit -80.231.25.0/24 permit -80.231.219.0/24 permit -81.223.46.0/27 permit -82.165.159.0/24 permit -85.222.130.192/26 permit -85.222.138.192/26 permit -86.61.88.25 permit -87.238.80.0/21 permit -87.253.232.0/21 permit -91.194.248.0/23 permit -91.198.22.0/24 permit -91.211.240.0/24 permit -91.211.242.0/24 permit -91.211.243.0/24 permit -91.220.42.0/24 permit -94.236.119.0/26 permit -94.245.112.0/27 permit -94.245.112.10/31 permit -94.245.120.64/26 permit -96.43.144.0/20 permit -96.43.144.64/28 permit -96.43.144.64/31 permit -96.43.147.64/28 permit -96.43.148.64/28 permit -96.43.148.64/31 permit -96.43.151.64/28 permit -96.43.152.64/27 permit -96.43.153.64/27 permit -96.46.150.192/27 permit -101.53.164.192/26 permit -103.11.200.0/22 permit -103.13.69.0/24 permit -103.28.42.0/24 permit -103.237.104.0/22 permit -104.40.211.35 permit -104.43.195.251 permit -104.47.0.0/17 permit -104.130.96.0/28 permit -104.130.122.0/23 permit -104.245.209.192/26 permit -106.50.16.0/28 permit -107.0.11.224/27 permit -108.174.0.0/24 permit -108.174.0.215 permit -108.174.3.0/24 permit -108.174.6.0/24 permit -108.175.18.45 permit -108.175.30.45 permit -108.177.8.0/21 permit -108.177.96.0/19 permit -111.221.23.128/25 permit -111.221.26.0/27 permit -111.221.66.0/25 permit -111.221.69.128/25 permit -111.221.112.0/21 permit -124.47.150.0/24 permit -124.47.189.0/24 permit -129.41.77.70 permit -129.41.169.249 permit -131.107.0.0/16 permit -131.107.1.18 permit -131.107.1.19 permit -131.107.1.20 permit -131.107.1.37 permit -131.107.1.44 permit -131.107.1.48 permit -131.107.1.56 permit -131.253.30.0/24 permit -131.253.121.20 permit -134.170.113.0/26 permit -134.170.140.0/24 permit -134.170.141.64/26 permit -134.170.143.0/24 permit -134.170.174.0/24 permit -136.146.128.64/27 permit -136.146.208.16/28 permit -136.146.210.16/28 permit -136.147.46.192/26 permit -136.147.62.192/26 permit -136.147.128.0/20 permit -136.147.176.0/20 permit -146.88.28.0/24 permit -146.101.78.0/24 permit -147.243.1.47 permit -147.243.1.48 permit -147.243.1.153 permit -147.243.128.24 permit -147.243.128.26 permit -151.101.37.140 permit -157.55.0.192/26 permit -157.55.1.128/26 permit -157.55.2.0/25 permit -157.55.9.128/25 permit -157.55.11.0/25 permit -157.55.49.0/25 permit -157.55.61.0/24 permit -157.55.157.128/25 permit -157.55.158.0/23 permit -157.55.225.0/25 permit -157.55.234.0/24 permit -157.56.24.0/25 permit -157.56.110.0/23 permit -157.56.112.0/24 permit -157.56.120.128/26 permit -157.56.172.28 permit -157.56.232.0/21 permit -157.56.240.0/20 permit -157.56.248.0/21 permit -157.151.208.65 permit -162.88.4.0/24 permit -162.88.36.0/24 permit -162.248.185.121 permit -163.47.180.0/22 permit -165.254.167.152/30 permit -165.254.167.156/31 permit -165.254.167.162/31 permit -165.254.168.66/31 permit -165.254.168.68/31 permit -165.254.168.70/31 permit -165.254.168.72/31 permit -166.78.68.0/22 permit -166.78.68.221 permit -166.78.69.146 permit -166.78.69.169 permit -166.78.69.170 permit -166.78.71.131 permit -167.89.0.0/17 permit -167.89.16.30 permit -167.89.16.183 permit -167.89.16.245 permit -167.89.25.84 permit -167.89.32.5 permit -167.89.32.50 permit -167.89.46.159 permit -167.89.46.185 permit -167.89.60.95 permit -167.89.62.118 permit -167.89.64.9 permit -167.89.65.0 permit -167.89.65.53 permit -167.89.65.100 permit -167.89.74.233 permit -167.89.75.33 permit -167.89.75.126 permit -167.89.75.136 permit -167.89.75.164 permit -167.89.101.2 permit -167.89.101.192/28 permit -167.220.67.238 permit -172.217.0.0/19 permit -173.0.84.224/28 permit -173.0.94.244/30 permit -173.193.132.0/23 permit -173.193.132.134/31 permit -173.193.210.32/27 permit -173.194.0.0/16 permit -173.203.79.182 permit -173.203.81.39 permit -173.224.160.128/25 permit -173.224.161.128/25 permit -173.228.155.0/24 permit -174.36.80.208/28 permit -174.36.84.8/29 permit -174.36.84.16/29 permit -174.36.84.32/29 permit -174.36.84.144/29 permit -174.36.84.240/29 permit -174.36.85.248/30 permit -174.36.92.96/27 permit -174.36.114.128/30 permit -174.36.114.140/30 permit -174.36.114.148/30 permit -174.36.114.152/29 permit -174.37.67.28/30 permit -174.37.226.64/27 permit -174.129.194.241 permit -174.129.203.189 permit -174.137.46.0/24 permit -176.32.105.0/24 permit -176.32.127.0/24 permit -178.32.48.128 permit -178.33.111.144 permit -178.33.137.208/28 permit -178.33.221.0/24 permit -178.236.10.128/26 permit -178.249.98.16/29 permit -178.249.202.16/29 permit -180.189.28.0/24 permit -182.50.76.0/22 permit -182.50.78.64/28 permit -184.173.105.0/24 permit -184.173.153.0/24 permit -185.4.120.0/24 permit -185.4.122.0/24 permit -185.12.80.0/22 permit -185.28.196.0/22 permit -185.90.20.0/22 permit -188.172.128.0/20 permit -191.239.213.197 permit -192.28.128.0/18 permit -192.30.252.0/22 permit -192.64.236.0/24 permit -192.64.237.0/24 permit -192.64.238.0/24 permit -192.161.144.0/20 permit -192.230.81.86 permit -192.237.158.0/23 permit -192.237.159.42 permit -192.237.159.43 permit -192.254.112.0/20 permit -192.254.112.60 permit -192.254.112.98/31 permit -192.254.113.10 permit -192.254.113.101 permit -192.254.114.176 permit -192.254.115.72 permit -192.254.118.63 permit -193.28.178.0/25 permit -194.64.234.128/27 permit -194.64.234.129 permit -194.154.193.192/27 permit -195.54.172.0/23 permit -195.130.217.0/24 permit -198.2.128.0/18 permit -198.2.128.0/24 permit -198.2.132.0/22 permit -198.2.136.0/23 permit -198.2.177.0/24 permit -198.2.178.0/24 permit -198.2.179.0/24 permit -198.2.180.0/24 permit -198.2.186.0/23 permit -198.21.0.0/21 permit -198.21.3.166 permit -198.21.4.224 permit -198.37.144.0/20 permit -198.37.145.250 permit -198.37.149.128 permit -198.37.151.26 permit -198.61.254.0/23 permit -198.61.254.231 permit -198.178.234.57 permit -198.245.80.0/20 permit -199.15.176.173 permit -199.15.212.0/22 permit -199.15.214.169/32 permit -199.16.156.0/22 permit -199.19.0.0/21 permit -199.59.148.0/22 permit -199.83.132.86 permit -199.101.161.130 permit -199.101.162.0/25 permit -199.122.120.0/21 permit -199.127.232.0/22 permit -199.187.117.209 permit -199.187.117.233 permit -199.187.117.234/31 permit -199.187.117.236/31 permit -199.187.118.201 permit -199.187.118.202/31 permit -199.187.118.204 permit -199.187.118.209 permit -199.201.64.23 permit -199.201.65.23 permit -199.255.192.0/22 permit -202.129.242.0/23 permit -202.177.148.100 permit -202.177.148.110 permit -203.32.4.25 permit -203.55.21.0/24 permit -203.62.195.0/24 permit -203.81.17.0/24 permit -203.122.32.250 permit -203.145.57.160/27 permit -204.13.11.48/29 permit -204.13.11.48/30 permit -204.13.248.0/22 permit -204.14.232.0/21 permit -204.14.232.64/28 permit -204.14.234.64/28 permit -204.14.238.0/27 permit -204.29.186.0/23 permit -204.75.142.0/24 permit -204.92.114.187 permit -204.92.114.203 permit -204.92.114.204/31 permit -204.153.121.0/24 permit -205.139.110.0/23 permit -205.201.128.0/20 permit -205.201.131.128/25 permit -205.201.132.14 permit -205.201.134.128/25 permit -205.201.136.0/23 permit -205.201.137.229 permit -205.201.139.0/24 permit -205.201.140.14 permit -205.207.104.0/22 permit -205.217.25.132 permit -205.217.25.135 permit -205.251.233.32/32 permit -205.251.233.36/32 permit -206.25.247.143 permit -206.25.247.155 permit -206.165.246.80/29 permit -206.191.224.0/19 permit -206.246.157.1 permit -207.46.4.128/25 permit -207.46.22.35 permit -207.46.22.98 permit -207.46.22.101 permit -207.46.50.72 permit -207.46.50.82 permit -207.46.50.192/26 permit -207.46.50.224 permit -207.46.51.64/26 permit -207.46.52.71 permit -207.46.52.79 permit -207.46.58.128/25 permit -207.46.100.0/24 permit -207.46.101.128/26 permit -207.46.116.128/29 permit -207.46.117.0/24 permit -207.46.132.128/27 permit -207.46.163.0/24 permit -207.46.198.0/25 permit -207.46.200.0/27 permit -207.67.38.0/24 permit -207.67.98.192/27 permit -207.68.176.0/26 permit -207.68.176.96/27 permit -207.82.80.0/24 permit -207.126.144.0/20 permit -207.171.160.0/19 permit -207.211.30.0/24 permit -207.211.31.0/25 permit -207.211.41.113 permit -207.218.90.0/24 permit -207.250.68.0/24 permit -208.40.232.70 permit -208.43.21.28/30 permit -208.43.21.64/29 permit -208.43.21.72/30 permit -208.43.239.136/30 permit -208.64.132.0/22 permit -208.66.139.0/25 permit -208.74.204.0/22 permit -208.74.204.9 permit -208.75.120.0/22 permit -208.75.122.246 permit -208.76.56.0/21 permit -208.78.68.0/22 permit -208.82.236.96/28 permit -208.82.237.96/28 permit -208.82.238.96/28 permit -208.85.50.137 permit -208.89.13.233 permit -208.89.13.234/31 permit -208.89.13.236/31 permit -208.89.14.201 permit -208.89.14.202/31 permit -208.89.14.204 permit -208.89.14.209 permit -208.117.48.0/20 permit -208.185.229.45 permit -208.201.241.163 permit -209.43.22.0/28 permit -209.46.117.168 permit -209.46.117.179 permit -209.61.151.0/24 permit -209.67.98.46 permit -209.67.98.59 permit -209.85.128.0/17 permit -212.4.136.0/26 permit -212.123.28.40/32 permit -212.227.15.0/24 permit -212.227.17.0/27 permit -212.227.126.128/25 permit -213.165.64.0/23 permit -213.167.75.0/24 permit -213.167.81.0/24 permit -213.199.128.139 permit -213.199.128.145 permit -213.199.138.181 permit -213.199.138.191 permit -213.199.154.0/24 permit -213.199.161.128/27 permit -213.199.177.0/26 permit -213.199.180.0/24 permit -216.17.150.242 permit -216.17.150.251 permit -216.32.180.0/23 permit -216.46.168.197 permit -216.46.168.222 permit -216.58.192.0/19 permit -216.99.5.67 permit -216.99.5.68 permit -216.113.160.0/24 permit -216.113.172.0/25 permit -216.113.175.0/24 permit -216.136.162.65 permit -216.136.162.120/29 permit -216.136.168.80/28 permit -216.146.32.0/20 permit -216.198.0.0/18 permit -216.203.30.55 permit -216.203.33.178/31 permit -216.205.24.0/24 permit -216.229.156.0/25 permit -216.239.32.0/19 permit -217.72.207.0/27 permit -217.77.141.52 permit -217.77.141.59 permit -217.175.193.0/24 permit -217.175.194.0/23 permit -217.175.196.0/24 permit -2001:4860:4000::/36 permit -2404:6800:4000::/36 permit -2607:f8b0:4000::/36 permit -2620:109:c003:104::/64 permit -2620:109:c006:104::/64 permit -2620:109:c00d:104::/64 permit -2620:119:50c0:207::/64 permit -2800:3f0:4000::/36 permit +# 2256 total rules +2a00:1450:4000::/36 permit +2a00:1450:4864::/56 permit +2a01:111:f400::/48 permit +2a01:111:f403:2800::/53 permit +2a01:111:f403:8000::/51 permit +2a01:111:f403::/49 permit +2a01:111:f403:c000::/51 permit +2a01:111:f403:d000::/53 permit +2a01:111:f403:f000::/52 permit +2a01:238:20a:202:5370::1 permit +2a01:238:20a:202:5372::1 permit +2a01:238:20a:202:5373::1 permit +2a01:238:400:101:53::1 permit +2a01:238:400:102:53::1 permit +2a01:238:400:103:53::1 permit +2a01:238:400:301:53::1 permit +2a01:238:400:302:53::1 permit +2a01:238:400:303:53::1 permit +2a01:238:400:470:53::1 permit +2a01:238:400:471:53::1 permit +2a01:238:400:472:53::1 permit +2a01:b747:3000:200::/56 permit +2a01:b747:3001:200::/56 permit +2a01:b747:3002:200::/56 permit +2a01:b747:3003:200::/56 permit +2a01:b747:3004:200::/56 permit +2a01:b747:3005:200::/56 permit +2a01:b747:3006:200::/56 permit +2a01:b747:3007::/56 permit +2a02:a60:0:5::/64 permit +2a0f:f640::/56 permit +2c0f:fb50:4000::/36 permit +2c0f:fb50:4864::/56 permit +2.207.151.32/27 permit +2.207.151.53 permit +2.207.217.30 permit +2.207.223.160/27 permit +3.64.237.68 permit +3.65.3.180 permit +3.70.123.177 permit +3.72.182.33 permit +3.74.81.189 permit +3.74.125.228 permit +3.75.33.185 permit +3.93.157.0/24 permit +3.94.40.108 permit +3.121.107.214 permit +3.129.120.190 permit +3.210.190.0/24 permit +3.211.80.218 permit +3.216.221.67 permit +3.221.209.22 permit +8.20.114.31 permit +8.25.194.0/23 permit +8.25.196.0/23 permit +8.36.116.0/24 permit +8.39.54.0/23 permit +8.39.54.240/29 permit +8.39.54.250/31 permit +8.39.144.0/24 permit +8.40.222.0/23 permit +8.40.222.240/29 permit +8.40.222.250/31 permit +8.47.10.9 permit +12.130.86.238 permit +13.108.16.0/20 permit +13.110.208.0/21 permit +13.110.209.0/24 permit +13.110.216.0/22 permit +13.110.224.0/20 permit +13.111.0.0/16 permit +13.111.191.0/24 permit +13.216.7.111 permit +13.216.54.180 permit +13.247.164.219 permit +15.200.21.50 permit +15.200.44.248 permit +15.200.201.185 permit +17.41.0.0/16 permit +17.57.155.0/24 permit +17.57.156.0/24 permit +17.58.0.0/16 permit +17.142.0.0/15 permit +18.97.0.8/30 permit +18.97.1.184/29 permit +18.97.2.64/26 permit +18.156.89.250 permit +18.156.205.64 permit +18.157.70.148 permit +18.157.114.255 permit +18.157.243.190 permit +18.158.153.154 permit +18.194.95.56 permit +18.197.217.180 permit +18.198.96.88 permit +18.199.210.3 permit +18.207.52.234 permit +18.208.124.128/25 permit +18.216.232.154 permit +18.235.27.253 permit +18.236.40.242 permit +20.51.6.32/30 permit +20.52.52.2 permit +20.52.128.133 permit +20.59.80.4/30 permit +20.63.210.192/28 permit +20.69.8.108/30 permit +20.83.222.104/30 permit +20.88.157.184/30 permit +20.94.180.64/28 permit +20.97.34.220/30 permit +20.98.148.156/30 permit +20.98.194.68/30 permit +20.105.209.76/30 permit +20.107.239.64/30 permit +20.118.139.208/30 permit +20.141.10.196 permit +20.185.214.0/27 permit +20.185.214.32/27 permit +20.185.214.64/27 permit +23.103.224.0/19 permit +23.249.208.0/20 permit +23.251.224.0/19 permit +23.253.141.0/24 permit +23.253.182.0/23 permit +23.253.182.103 permit +23.253.183.145 permit +23.253.183.146 permit +23.253.183.147 permit +23.253.183.148 permit +23.253.183.150 permit +24.110.64.0/18 permit +27.123.204.128/30 permit +27.123.204.132/31 permit +27.123.204.148/30 permit +27.123.204.152 permit +27.123.204.168/30 permit +27.123.204.172 permit +27.123.204.188/30 permit +27.123.204.192 permit +27.123.206.50/31 permit +27.123.206.56/29 permit +27.123.206.76/30 permit +27.123.206.80/28 permit +31.47.251.17 permit +31.186.239.0/24 permit +34.2.64.0/22 permit +34.2.68.0/23 permit +34.2.70.0/23 permit +34.2.71.64/26 permit +34.2.72.0/22 permit +34.2.75.0/26 permit +34.2.78.0/23 permit +34.2.80.0/23 permit +34.2.82.0/23 permit +34.2.84.0/24 permit +34.2.84.64/26 permit +34.2.85.0/24 permit +34.2.85.64/26 permit +34.2.86.0/23 permit +34.2.88.0/23 permit +34.2.90.0/23 permit +34.2.92.0/23 permit +34.2.94.0/23 permit +34.70.158.162 permit +34.74.74.140 permit +34.83.159.189 permit +34.141.160.224 permit +34.193.58.168 permit +34.195.217.107 permit +34.197.10.50 permit +34.197.254.9 permit +34.198.94.229 permit +34.198.218.121 permit +34.212.163.75 permit +34.215.104.144 permit +34.218.115.239 permit +34.225.212.172 permit +34.241.242.183 permit +35.83.148.184 permit +35.155.198.111 permit +35.158.23.94 permit +35.161.32.253 permit +35.162.73.231 permit +35.167.93.243 permit +35.174.145.124 permit +35.176.132.251 permit +35.205.92.9 permit +35.228.216.85 permit +35.242.169.159 permit +37.188.97.188 permit +37.218.248.47 permit +37.218.249.47 permit +37.218.251.62 permit +39.156.163.64/29 permit +40.92.0.0/15 permit +40.92.0.0/16 permit +40.107.0.0/16 permit +40.112.65.63 permit +40.233.64.216 permit +40.233.83.78 permit +40.233.88.28 permit +43.239.212.33 permit +44.206.138.57 permit +44.210.169.44 permit +44.217.45.156 permit +44.236.56.93 permit +44.238.220.251 permit +44.245.243.92 permit +44.246.1.125 permit +44.246.68.102 permit +44.246.77.92 permit +45.14.148.0/22 permit +45.143.132.0/24 permit +45.143.133.0/24 permit +45.143.134.0/24 permit +45.143.135.0/24 permit +46.226.48.0/21 permit +46.228.36.37 permit +46.228.36.38/31 permit +46.228.36.212/30 permit +46.228.36.216/31 permit +46.228.37.1 permit +46.228.37.2 permit +46.228.37.3 permit +46.228.37.4 permit +46.228.37.5 permit +46.228.37.6/31 permit +46.228.37.8/31 permit +46.228.37.10/31 permit +46.228.37.12/30 permit +46.228.37.16/30 permit +46.228.37.20/30 permit +46.228.37.24/30 permit +46.228.37.28/31 permit +46.228.37.30/31 permit +46.228.37.32/29 permit +46.228.37.40/29 permit +46.228.37.48/31 permit +46.228.37.50/31 permit +46.228.37.52/30 permit +46.228.37.56/31 permit +46.228.38.33 permit +46.228.38.34/31 permit +46.228.38.36/31 permit +46.228.38.38 permit +46.228.38.53 permit +46.228.38.54/31 permit +46.228.38.56/31 permit +46.228.38.58 permit +46.228.38.101 permit +46.228.38.102/31 permit +46.228.38.128/28 permit +46.228.38.144/29 permit +46.228.38.152/31 permit +46.228.38.154 permit +46.228.39.64/27 permit +46.228.39.96/30 permit +46.228.39.100/30 permit +46.228.39.104/29 permit +46.228.39.112/31 permit +46.243.88.174 permit +46.243.88.175 permit +46.243.88.176 permit +46.243.88.177 permit +46.243.95.179 permit +46.243.95.180 permit +50.16.246.183 permit +50.18.45.249 permit +50.18.121.236 permit +50.18.121.248 permit +50.18.123.221 permit +50.18.124.70 permit +50.18.125.97 permit +50.18.125.237 permit +50.18.126.162 permit +50.31.32.0/19 permit +50.31.36.205 permit +50.56.130.220 permit +50.56.130.221 permit +50.56.130.222 permit +50.112.246.219 permit +51.83.17.38 permit +52.1.14.157 permit +52.5.230.59 permit +52.6.74.205 permit +52.12.53.23 permit +52.13.214.179 permit +52.26.1.71 permit +52.27.5.72 permit +52.27.28.47 permit +52.28.63.81 permit +52.28.197.132 permit +52.34.181.151 permit +52.35.192.45 permit +52.36.138.31 permit +52.37.142.146 permit +52.42.203.116 permit +52.50.24.208 permit +52.57.120.243 permit +52.58.216.183 permit +52.59.143.3 permit +52.60.41.5 permit +52.60.115.116 permit +52.61.91.9 permit +52.71.0.205 permit +52.94.124.0/28 permit +52.95.48.152/29 permit +52.95.49.88/29 permit +52.100.0.0/15 permit +52.102.0.0/16 permit +52.103.0.0/17 permit +52.119.213.144/28 permit +52.185.106.240/28 permit +52.200.59.0/24 permit +52.207.191.216 permit +52.222.62.51 permit +52.222.73.83 permit +52.222.73.120 permit +52.222.75.85 permit +52.222.89.228 permit +52.234.172.96/28 permit +52.235.253.128 permit +52.236.28.240/28 permit +54.90.148.255 permit +54.165.19.38 permit +54.174.52.0/24 permit +54.174.57.0/24 permit +54.174.59.0/24 permit +54.174.60.0/23 permit +54.174.63.0/24 permit +54.186.193.102 permit +54.191.223.56 permit +54.211.126.101 permit +54.213.20.246 permit +54.214.39.184 permit +54.240.0.0/18 permit +54.240.64.0/18 permit +54.241.16.209 permit +54.244.54.130 permit +54.244.242.0/24 permit +54.255.61.23 permit +56.124.6.228 permit +57.103.64.0/18 permit +57.129.93.249 permit +62.13.128.0/24 permit +62.13.129.128/25 permit +62.13.136.0/21 permit +62.13.144.0/21 permit +62.13.152.0/21 permit +62.17.146.128/26 permit +62.201.172.0/27 permit +62.201.172.32/27 permit +62.253.227.114 permit +63.80.14.0/23 permit +63.128.21.0/24 permit +63.143.57.128/25 permit +63.143.59.128/25 permit +63.176.194.123 permit +63.178.132.221 permit +63.178.143.178 permit +64.18.0.0/20 permit +64.20.241.45 permit +64.69.212.0/24 permit +64.71.149.160/28 permit +64.79.155.0/24 permit +64.79.155.192 permit +64.79.155.193 permit +64.79.155.205 permit +64.79.155.206 permit +64.127.115.252 permit +64.132.88.0/23 permit +64.132.92.0/24 permit +64.181.194.190 permit +64.181.213.254 permit +64.207.219.7 permit +64.207.219.8 permit +64.207.219.9 permit +64.207.219.10 permit +64.207.219.11 permit +64.207.219.12 permit +64.207.219.13 permit +64.207.219.14 permit +64.207.219.15 permit +64.207.219.71 permit +64.207.219.72 permit +64.207.219.73 permit +64.207.219.74 permit +64.207.219.75 permit +64.207.219.76 permit +64.207.219.77 permit +64.207.219.78 permit +64.207.219.79 permit +64.207.219.135 permit +64.207.219.136 permit +64.207.219.137 permit +64.207.219.138 permit +64.207.219.139 permit +64.207.219.140 permit +64.207.219.141 permit +64.207.219.142 permit +64.207.219.143 permit +64.233.160.0/19 permit +65.52.80.137 permit +65.55.29.77 permit +65.55.42.224/28 permit +65.110.161.77 permit +65.123.29.213 permit +65.123.29.220 permit +65.154.166.0/24 permit +65.212.180.36 permit +66.102.0.0/20 permit +66.119.150.192/26 permit +66.163.184.0/24 permit +66.163.185.0/24 permit +66.163.186.0/24 permit +66.163.187.0/24 permit +66.163.188.0/24 permit +66.163.189.0/24 permit +66.163.190.0/24 permit +66.163.191.0/24 permit +66.170.126.97 permit +66.196.80.112/28 permit +66.196.80.144/29 permit +66.196.80.193 permit +66.196.81.104/29 permit +66.196.81.112/29 permit +66.196.81.120 permit +66.196.81.125 permit +66.196.81.126/31 permit +66.196.81.128/28 permit +66.196.81.144/29 permit +66.196.81.152/31 permit +66.196.81.154 permit +66.196.81.225 permit +66.196.81.226/31 permit +66.196.81.228/30 permit +66.196.81.232/31 permit +66.196.81.234 permit +66.211.170.88/29 permit +66.211.184.0/23 permit +66.218.74.64/30 permit +66.218.74.68/31 permit +66.218.75.112/30 permit +66.218.75.116/31 permit +66.218.75.144/31 permit +66.218.75.146 permit +66.218.75.152 permit +66.218.75.192/30 permit +66.218.75.196/31 permit +66.218.75.203 permit +66.218.75.212/30 permit +66.218.75.216/31 permit +66.218.75.223 permit +66.218.75.232/30 permit +66.218.75.236/31 permit +66.218.75.243 permit +66.218.75.252/31 permit +66.218.75.254 permit +66.220.144.128/25 permit +66.220.144.178 permit +66.220.144.179 permit +66.220.155.0/24 permit +66.220.155.178 permit +66.220.155.179 permit +66.220.157.0/25 permit +66.231.80.0/20 permit +66.240.227.0/24 permit +66.249.80.0/20 permit +67.23.31.6 permit +67.72.99.26 permit +67.195.22.113 permit +67.195.22.116/30 permit +67.195.23.144/30 permit +67.195.23.148 permit +67.195.60.45 permit +67.195.60.46/31 permit +67.195.60.48/31 permit +67.195.60.50 permit +67.195.60.146 permit +67.195.60.155 permit +67.195.60.156 permit +67.195.87.64 permit +67.195.87.81 permit +67.195.87.82/31 permit +67.195.87.84/31 permit +67.195.87.86 permit +67.195.87.128 permit +67.195.87.145 permit +67.195.87.146/31 permit +67.195.87.192 permit +67.195.87.209 permit +67.195.87.210/31 permit +67.195.124.137 permit +67.195.124.139 permit +67.195.124.141 permit +67.195.124.143 permit +67.195.124.145 permit +67.195.124.147 permit +67.219.240.0/20 permit +67.221.168.65 permit +67.228.2.24/30 permit +67.228.21.184/29 permit +67.228.37.4/30 permit +67.231.145.42 permit +67.231.153.30 permit +68.142.230.64/31 permit +68.142.230.69 permit +68.142.230.70/31 permit +68.142.230.72/30 permit +68.142.230.76/31 permit +68.142.230.78 permit +68.232.140.138 permit +68.232.157.143 permit +68.232.192.0/20 permit +69.63.178.128/25 permit +69.63.181.0/24 permit +69.63.184.0/25 permit +69.65.42.195 permit +69.65.49.192/29 permit +69.72.32.0/20 permit +69.72.40.93 permit +69.72.40.94/31 permit +69.72.40.96/30 permit +69.72.47.205 permit +69.147.84.227 permit +69.162.98.0/24 permit +69.169.224.0/20 permit +69.171.232.0/24 permit +69.171.232.180 permit +69.171.232.181 permit +69.171.244.0/23 permit +70.42.149.35 permit +72.3.185.0/24 permit +72.14.192.0/18 permit +72.21.192.0/19 permit +72.21.217.142 permit +72.30.234.152/29 permit +72.30.236.160/30 permit +72.30.236.164/31 permit +72.30.236.180/30 permit +72.30.236.184/31 permit +72.30.236.208/30 permit +72.30.236.212/31 permit +72.30.236.224/30 permit +72.30.236.228/31 permit +72.30.236.244/30 permit +72.30.236.248/31 permit +72.30.237.32/30 permit +72.30.237.36/31 permit +72.30.237.52/30 permit +72.30.237.56/31 permit +72.30.237.80/31 permit +72.30.237.96/30 permit +72.30.237.100/31 permit +72.30.237.116/30 permit +72.30.237.120/31 permit +72.30.237.144/30 permit +72.30.237.148/31 permit +72.30.237.160/30 permit +72.30.237.164/31 permit +72.30.237.180/30 permit +72.30.237.184/31 permit +72.30.237.204/30 permit +72.30.238.116/30 permit +72.30.238.120/31 permit +72.30.238.128 permit +72.30.238.133 permit +72.30.238.168/30 permit +72.30.238.172/31 permit +72.30.238.188/30 permit +72.30.238.192 permit +72.30.238.197 permit +72.30.238.224/31 permit +72.30.238.240/30 permit +72.30.238.244/31 permit +72.30.239.5 permit +72.30.239.37 permit +72.30.239.38/31 permit +72.30.239.40/31 permit +72.30.239.42 permit +72.30.239.57 permit +72.30.239.64 permit +72.30.239.69 permit +72.30.239.89 permit +72.30.239.90/31 permit +72.30.239.92/31 permit +72.30.239.94 permit +72.30.239.128 permit +72.30.239.198 permit +72.30.239.224/30 permit +72.30.239.228/31 permit +72.30.239.244/30 permit +72.30.239.248/31 permit +72.32.154.0/24 permit +72.32.217.0/24 permit +72.32.243.0/24 permit +72.52.72.32/28 permit +74.6.128.0/24 permit +74.6.129.0/24 permit +74.6.130.0/24 permit +74.6.131.0/24 permit +74.6.132.0/24 permit +74.6.133.0/24 permit +74.6.134.0/24 permit +74.6.135.0/24 permit +74.63.212.0/24 permit +74.63.234.75 permit +74.63.236.0/24 permit +74.86.113.28/30 permit +74.86.129.240/30 permit +74.86.131.208/30 permit +74.86.132.208/30 permit +74.86.160.160/30 permit +74.86.164.188/30 permit +74.86.171.192/30 permit +74.86.195.28/30 permit +74.86.207.36/30 permit +74.86.226.216/30 permit +74.86.236.240/30 permit +74.86.241.250/31 permit +74.112.67.243 permit +74.125.0.0/16 permit +74.208.4.200 permit +74.208.4.201 permit +74.208.4.220 permit +74.208.4.221 permit +74.209.250.0/24 permit +76.223.128.0/19 permit +76.223.176.0/20 permit +77.238.176.0/24 permit +77.238.177.0/24 permit +77.238.178.0/24 permit +77.238.179.0/24 permit +77.238.189.21 permit +77.238.189.23 permit +77.238.189.35 permit +77.238.189.39 permit +77.238.189.58/31 permit +77.238.189.60/30 permit +77.238.189.64/29 permit +77.238.189.76/31 permit +77.238.189.128/30 permit +77.238.189.132/31 permit +77.238.189.137 permit +77.238.189.138/31 permit +77.238.189.140/31 permit +77.238.189.142 permit +77.238.189.146/31 permit +77.238.189.148/30 permit +79.135.106.0/24 permit +79.135.107.0/24 permit +80.225.160.128/25 permit +81.169.146.243 permit +81.169.146.245 permit +81.169.146.246 permit +81.223.46.0/27 permit +82.165.159.2 permit +82.165.159.3 permit +82.165.159.4 permit +82.165.159.12 permit +82.165.159.13 permit +82.165.159.14 permit +82.165.159.34 permit +82.165.159.35 permit +82.165.159.40 permit +82.165.159.41 permit +82.165.159.42 permit +82.165.159.45 permit +82.165.159.130 permit +82.165.159.131 permit +84.8.68.0/25 permit +84.8.192.128/25 permit +84.8.224.128/25 permit +85.9.206.169 permit +85.9.210.45 permit +85.158.136.0/21 permit +85.215.255.39 permit +85.215.255.40 permit +85.215.255.41 permit +85.215.255.45 permit +85.215.255.46 permit +85.215.255.47 permit +85.215.255.48 permit +85.215.255.49 permit +86.61.88.25 permit +87.238.80.0/21 permit +87.248.103.12 permit +87.248.103.21 permit +87.248.103.23 permit +87.248.103.27 permit +87.248.103.29 permit +87.248.103.36 permit +87.248.103.42/31 permit +87.248.103.44 permit +87.248.103.64 permit +87.248.103.76 permit +87.248.103.83 permit +87.248.103.92/31 permit +87.248.103.94 permit +87.248.103.107 permit +87.248.103.113 permit +87.248.103.122 permit +87.248.110.0/24 permit +87.248.117.30 permit +87.248.117.65 permit +87.248.117.67 permit +87.248.117.70 permit +87.248.117.73 permit +87.248.117.74 permit +87.248.117.86 permit +87.248.117.97 permit +87.248.117.98 permit +87.248.117.104 permit +87.248.117.119 permit +87.248.117.124 permit +87.248.117.185 permit +87.248.117.196 permit +87.248.117.198 permit +87.248.117.201 permit +87.248.117.202 permit +87.248.117.205 permit +87.253.232.0/21 permit +89.22.108.0/24 permit +91.198.2.177 permit +91.198.2.217 permit +91.198.2.222 permit +91.211.240.0/22 permit +94.236.119.0/26 permit +95.131.104.0/21 permit +95.217.114.154 permit +96.43.144.0/20 permit +96.43.144.64/28 permit +96.43.144.64/31 permit +96.43.148.64/28 permit +96.43.148.64/31 permit +96.43.151.64/28 permit +98.97.248.0/21 permit +98.136.44.181 permit +98.136.44.182/31 permit +98.136.44.184 permit +98.136.164.36/31 permit +98.136.164.64/29 permit +98.136.164.72/30 permit +98.136.164.76/31 permit +98.136.164.78 permit +98.136.172.32/30 permit +98.136.172.36/31 permit +98.136.185.29 permit +98.136.185.42/31 permit +98.136.185.46 permit +98.136.185.115 permit +98.136.214.28/30 permit +98.136.214.97 permit +98.136.214.98/31 permit +98.136.214.100 permit +98.136.214.116/30 permit +98.136.214.120/31 permit +98.136.214.157 permit +98.136.214.158/31 permit +98.136.214.160/31 permit +98.136.214.162 permit +98.136.214.190/31 permit +98.136.214.192/30 permit +98.136.214.238/31 permit +98.136.214.255 permit +98.136.215.0/31 permit +98.136.215.2 permit +98.136.215.3 permit +98.136.215.4/31 permit +98.136.215.6 permit +98.136.215.114/31 permit +98.136.215.116/31 permit +98.136.215.118 permit +98.136.215.136/30 permit +98.136.215.148/30 permit +98.136.215.152/31 permit +98.136.215.179 permit +98.136.215.180/30 permit +98.136.215.184 permit +98.136.215.208/30 permit +98.136.215.212/31 permit +98.136.217.1 permit +98.136.217.2 permit +98.136.217.3 permit +98.136.217.4/30 permit +98.136.217.8/31 permit +98.136.217.10/31 permit +98.136.217.12/30 permit +98.136.217.16/30 permit +98.136.217.20/30 permit +98.136.218.39 permit +98.136.218.40/29 permit +98.136.218.48/28 permit +98.136.218.67 permit +98.136.218.68/30 permit +98.136.218.72/30 permit +98.137.12.48/30 permit +98.137.12.52/31 permit +98.137.12.54 permit +98.137.12.177 permit +98.137.12.178/31 permit +98.137.12.180/31 permit +98.137.12.182 permit +98.137.12.192/27 permit +98.137.12.224/28 permit +98.137.12.240/29 permit +98.137.12.248/30 permit +98.137.12.252/31 permit +98.137.12.254 permit +98.137.13.81 permit +98.137.13.82 permit +98.137.13.87 permit +98.137.13.88 permit +98.137.13.91 permit +98.137.13.92 permit +98.137.13.97 permit +98.137.13.98 permit +98.137.13.101 permit +98.137.13.102 permit +98.137.13.107 permit +98.137.13.108 permit +98.137.13.111 permit +98.137.13.112 permit +98.137.13.117 permit +98.137.13.118 permit +98.137.13.121 permit +98.137.13.122 permit +98.137.13.127 permit +98.137.13.128 permit +98.137.13.131 permit +98.137.13.132 permit +98.137.13.137 permit +98.137.13.138 permit +98.137.64.0/24 permit +98.137.65.0/24 permit +98.137.66.0/24 permit +98.137.67.0/24 permit +98.137.68.0/24 permit +98.137.69.0/24 permit +98.137.70.0/24 permit +98.137.71.0/24 permit +98.137.176.58/31 permit +98.137.177.247 permit +98.138.31.128/30 permit +98.138.31.132/31 permit +98.138.31.192/30 permit +98.138.31.196/31 permit +98.138.31.202/31 permit +98.138.31.204/30 permit +98.138.31.232/30 permit +98.138.31.236/31 permit +98.138.82.208/30 permit +98.138.82.212/31 permit +98.138.83.176/31 permit +98.138.83.179 permit +98.138.83.180/31 permit +98.138.84.37 permit +98.138.84.38/31 permit +98.138.84.40/29 permit +98.138.85.128/30 permit +98.138.85.132/31 permit +98.138.85.148/30 permit +98.138.85.152/31 permit +98.138.85.168/30 permit +98.138.85.172/31 permit +98.138.85.188/30 permit +98.138.85.192/31 permit +98.138.85.208/30 permit +98.138.85.212/31 permit +98.138.85.228/30 permit +98.138.85.232/31 permit +98.138.85.248/30 permit +98.138.85.252/31 permit +98.138.86.69 permit +98.138.86.156/31 permit +98.138.86.192/30 permit +98.138.86.196/31 permit +98.138.87.1 permit +98.138.87.2/31 permit +98.138.87.4/31 permit +98.138.87.6 permit +98.138.87.7 permit +98.138.87.8/31 permit +98.138.87.10/31 permit +98.138.87.12 permit +98.138.87.16/30 permit +98.138.87.64/30 permit +98.138.87.68/31 permit +98.138.87.73 permit +98.138.87.74/31 permit +98.138.87.76/31 permit +98.138.87.78 permit +98.138.87.144/30 permit +98.138.87.148/31 permit +98.138.87.192/30 permit +98.138.87.196/31 permit +98.138.88.105 permit +98.138.88.106 permit +98.138.88.128/30 permit +98.138.88.132/31 permit +98.138.88.148/30 permit +98.138.88.152/31 permit +98.138.88.232/29 permit +98.138.89.160/28 permit +98.138.89.192/29 permit +98.138.89.232/31 permit +98.138.89.234 permit +98.138.89.240 permit +98.138.89.244/31 permit +98.138.89.246 permit +98.138.89.248/30 permit +98.138.89.252/31 permit +98.138.89.254 permit +98.138.90.64/28 permit +98.138.90.80/29 permit +98.138.90.88/30 permit +98.138.90.92/31 permit +98.138.90.95 permit +98.138.90.96/30 permit +98.138.90.100 permit +98.138.90.104/30 permit +98.138.90.108/31 permit +98.138.90.113 permit +98.138.90.114/31 permit +98.138.90.116/31 permit +98.138.90.118 permit +98.138.90.122/31 permit +98.138.90.124/30 permit +98.138.90.131 permit +98.138.90.132/30 permit +98.138.90.136 permit +98.138.91.1 permit +98.138.91.2/31 permit +98.138.91.4/31 permit +98.138.91.6 permit +98.138.100.220/30 permit +98.138.100.224/30 permit +98.138.100.228/31 permit +98.138.101.160/28 permit +98.138.101.176/29 permit +98.138.104.96/30 permit +98.138.104.100 permit +98.138.104.112/30 permit +98.138.104.116 permit +98.138.120.36/30 permit +98.138.120.48/28 permit +98.138.197.46/31 permit +98.138.197.48/30 permit +98.138.197.169 permit +98.138.197.176 permit +98.138.197.180 permit +98.138.198.52/31 permit +98.138.198.54 permit +98.138.198.56 permit +98.138.198.61 permit +98.138.198.190/31 permit +98.138.198.239 permit +98.138.199.6 permit +98.138.199.9 permit +98.138.199.48 permit +98.138.199.83 permit +98.138.199.84 permit +98.138.199.86/31 permit +98.138.199.94 permit +98.138.199.116 permit +98.138.199.208/30 permit +98.138.199.212/31 permit +98.138.199.218/31 permit +98.138.199.220/30 permit +98.138.199.236 permit +98.138.206.67 permit +98.138.206.73 permit +98.138.206.143 permit +98.138.206.167 permit +98.138.206.169 permit +98.138.206.173 permit +98.138.207.9 permit +98.138.207.10/31 permit +98.138.207.12/31 permit +98.138.207.14 permit +98.138.210.39 permit +98.138.210.64/30 permit +98.138.210.68/31 permit +98.138.210.74/31 permit +98.138.210.76/30 permit +98.138.210.84 permit +98.138.210.86 permit +98.138.210.89 permit +98.138.210.93 permit +98.138.210.94 permit +98.138.210.96 permit +98.138.210.98 permit +98.138.210.101 permit +98.138.210.111 permit +98.138.210.115 permit +98.138.210.122 permit +98.138.210.136 permit +98.138.210.146 permit +98.138.210.148 permit +98.138.210.155 permit +98.138.210.158/31 permit +98.138.210.166 permit +98.138.210.169 permit +98.138.210.175 permit +98.138.210.177 permit +98.138.210.181 permit +98.138.210.182/31 permit +98.138.210.187 permit +98.138.210.189 permit +98.138.210.201 permit +98.138.210.203 permit +98.138.210.210 permit +98.138.210.240/30 permit +98.138.210.244/31 permit +98.138.211.122/31 permit +98.138.211.124/30 permit +98.138.211.128/30 permit +98.138.211.132/31 permit +98.138.211.148/30 permit +98.138.211.152/31 permit +98.138.211.216/30 permit +98.138.211.220/31 permit +98.138.213.128/30 permit +98.138.213.132/31 permit +98.138.213.138/31 permit +98.138.213.140/30 permit +98.138.213.148/30 permit +98.138.213.152/31 permit +98.138.213.158/31 permit +98.138.213.160/30 permit +98.138.213.168/30 permit +98.138.213.172/31 permit +98.138.213.237 permit +98.138.213.238/31 permit +98.138.213.240/31 permit +98.138.213.242 permit +98.138.215.12/30 permit +98.138.215.16/28 permit +98.138.217.216/30 permit +98.138.217.220/31 permit +98.138.226.30/31 permit +98.138.226.56/29 permit +98.138.226.64/30 permit +98.138.226.68/31 permit +98.138.226.74/31 permit +98.138.226.76/30 permit +98.138.226.84/30 permit +98.138.226.88/31 permit +98.138.226.124/30 permit +98.138.226.128/30 permit +98.138.226.132/31 permit +98.138.226.160/29 permit +98.138.226.168/31 permit +98.138.226.240/30 permit +98.138.226.244 permit +98.138.227.32/30 permit +98.138.227.36/31 permit +98.138.227.56/30 permit +98.138.227.60/31 permit +98.138.227.80/30 permit +98.138.227.84/31 permit +98.138.227.104/30 permit +98.138.227.108/31 permit +98.138.227.128/30 permit +98.138.227.132/31 permit +98.138.229.24/29 permit +98.138.229.32/31 permit +98.138.229.122/31 permit +98.138.229.138/31 permit +98.138.229.154/31 permit +98.138.229.170/31 permit +98.139.164.96/30 permit +98.139.164.100/30 permit +98.139.164.104/29 permit +98.139.164.112/30 permit +98.139.172.112/30 permit +98.139.172.116/31 permit +98.139.175.65 permit +98.139.175.66/31 permit +98.139.175.68/30 permit +98.139.175.72/29 permit +98.139.175.80/29 permit +98.139.175.88/30 permit +98.139.175.92/31 permit +98.139.175.94 permit +98.139.210.112/30 permit +98.139.210.128/30 permit +98.139.210.132/31 permit +98.139.210.138/31 permit +98.139.210.140/30 permit +98.139.210.148/30 permit +98.139.210.152/31 permit +98.139.210.170/31 permit +98.139.210.172/30 permit +98.139.210.180/30 permit +98.139.210.184/31 permit +98.139.210.190/31 permit +98.139.210.192/30 permit +98.139.210.196/31 permit +98.139.210.202/31 permit +98.139.210.204/30 permit +98.139.211.160/30 permit +98.139.211.192/28 permit +98.139.212.160/28 permit +98.139.212.176/29 permit +98.139.212.184/30 permit +98.139.212.188/31 permit +98.139.212.192/27 permit +98.139.212.224/28 permit +98.139.212.240/29 permit +98.139.212.248/30 permit +98.139.213.8/31 permit +98.139.213.10/31 permit +98.139.213.12/30 permit +98.139.214.155 permit +98.139.214.156/30 permit +98.139.214.221 permit +98.139.215.228/31 permit +98.139.215.230 permit +98.139.215.248/30 permit +98.139.215.252/31 permit +98.139.215.254 permit +98.139.218.53 permit +98.139.218.61 permit +98.139.218.63 permit +98.139.219.128 permit +98.139.219.138 permit +98.139.219.159 permit +98.139.219.177 permit +98.139.219.184 permit +98.139.219.193 permit +98.139.219.194 permit +98.139.219.196 permit +98.139.219.203 permit +98.139.219.209 permit +98.139.219.215 permit +98.139.219.216/31 permit +98.139.219.231 permit +98.139.220.43 permit +98.139.220.50 permit +98.139.220.57 permit +98.139.220.63 permit +98.139.220.71 permit +98.139.220.73 permit +98.139.220.85 permit +98.139.220.96 permit +98.139.220.100 permit +98.139.220.103 permit +98.139.220.104 permit +98.139.220.106/31 permit +98.139.220.110/31 permit +98.139.220.112/31 permit +98.139.220.144 permit +98.139.220.152 permit +98.139.220.157 permit +98.139.220.158 permit +98.139.220.163 permit +98.139.220.164 permit +98.139.220.170 permit +98.139.220.182 permit +98.139.220.187 permit +98.139.220.189 permit +98.139.220.193 permit +98.139.220.198 permit +98.139.220.203 permit +98.139.220.208/31 permit +98.139.220.212 permit +98.139.220.214/31 permit +98.139.220.219 permit +98.139.220.223 permit +98.139.220.232 permit +98.139.220.238 permit +98.139.220.243 permit +98.139.220.245 permit +98.139.220.253 permit +98.139.221.43 permit +98.139.221.60/30 permit +98.139.221.156/30 permit +98.139.221.232/30 permit +98.139.221.236/31 permit +98.139.221.250 permit +98.139.244.47 permit +98.139.244.49 permit +98.139.244.50/31 permit +98.139.244.52/30 permit +98.139.244.57 permit +98.139.244.58/31 permit +98.139.244.60/31 permit +98.139.244.62 permit +98.139.244.132 permit +98.139.244.141 permit +98.139.244.142/31 permit +98.139.244.144/31 permit +98.139.244.146 permit +98.139.244.152 permit +98.139.244.161 permit +98.139.244.162/31 permit +98.139.244.164/31 permit +98.139.244.166 permit +98.139.244.172 permit +98.139.244.181 permit +98.139.244.182/31 permit +98.139.244.184/31 permit +98.139.244.186 permit +98.139.244.192 permit +98.139.244.201 permit +98.139.244.202/31 permit +98.139.244.204/31 permit +98.139.244.206 permit +98.139.244.212 permit +98.139.244.221 permit +98.139.244.222/31 permit +98.139.244.224/31 permit +98.139.244.226 permit +98.139.244.232 permit +98.139.245.176/30 permit +98.139.245.180/31 permit +98.139.245.208/30 permit +98.139.245.212/31 permit +99.78.197.208/28 permit +103.9.96.0/22 permit +103.28.42.0/24 permit +103.84.217.15 permit +103.84.217.238 permit +103.89.75.238 permit +103.151.192.0/23 permit +103.168.172.128/27 permit +103.237.104.0/22 permit +104.43.243.237 permit +104.44.112.128/25 permit +104.47.0.0/17 permit +104.130.96.0/28 permit +104.130.122.0/23 permit +106.10.144.64/27 permit +106.10.144.100/31 permit +106.10.144.103 permit +106.10.144.112/28 permit +106.10.144.128 permit +106.10.144.133 permit +106.10.144.138 permit +106.10.144.143 permit +106.10.144.148 permit +106.10.144.153 permit +106.10.144.158 permit +106.10.144.163 permit +106.10.144.168 permit +106.10.145.33 permit +106.10.145.34/31 permit +106.10.145.160/31 permit +106.10.145.162 permit +106.10.145.192/30 permit +106.10.145.196/31 permit +106.10.145.224/30 permit +106.10.145.228/31 permit +106.10.146.48/30 permit +106.10.146.52/31 permit +106.10.146.224/30 permit +106.10.146.228/31 permit +106.10.148.48/30 permit +106.10.148.52/31 permit +106.10.148.68/30 permit +106.10.148.80/28 permit +106.10.148.100/30 permit +106.10.148.124/31 permit +106.10.148.252/31 permit +106.10.148.254 permit +106.10.149.28/31 permit +106.10.149.30 permit +106.10.149.160/30 permit +106.10.149.164/31 permit +106.10.150.23 permit +106.10.150.24/30 permit +106.10.150.28/31 permit +106.10.150.32/30 permit +106.10.150.36/31 permit +106.10.150.52/30 permit +106.10.150.56/31 permit +106.10.150.72/30 permit +106.10.150.76/31 permit +106.10.150.92/30 permit +106.10.150.96/31 permit +106.10.150.112/30 permit +106.10.150.116/31 permit +106.10.150.132/30 permit +106.10.150.136/31 permit +106.10.150.172/30 permit +106.10.151.15 permit +106.10.151.16/31 permit +106.10.151.18 permit +106.10.151.19 permit +106.10.151.20 permit +106.10.151.21 permit +106.10.151.22/31 permit +106.10.151.24/31 permit +106.10.151.26/31 permit +106.10.151.28/30 permit +106.10.151.122/31 permit +106.10.151.138/31 permit +106.10.151.154/31 permit +106.10.151.170/31 permit +106.10.151.186/31 permit +106.10.151.202/31 permit +106.10.151.218/31 permit +106.10.151.234/31 permit +106.10.151.239 permit +106.10.151.250/31 permit +106.10.151.252/31 permit +106.10.151.254 permit +106.10.167.72 permit +106.10.167.128/27 permit +106.10.167.160/28 permit +106.10.167.176/31 permit +106.10.167.244/31 permit +106.10.167.246 permit +106.10.169.16/30 permit +106.10.169.20 permit +106.10.169.21 permit +106.10.169.208/31 permit +106.10.169.210/31 permit +106.10.169.233 permit +106.10.169.234/31 permit +106.10.169.236/31 permit +106.10.169.238 permit +106.10.169.253 permit +106.10.169.254 permit +106.10.174.118/31 permit +106.10.174.120/30 permit +106.10.174.154/31 permit +106.10.174.156/30 permit +106.10.176.32/29 permit +106.10.176.48 permit +106.10.176.112 permit +106.10.176.128/29 permit +106.10.176.136 permit +106.10.176.255 permit +106.10.177.0 permit +106.10.177.108/30 permit +106.10.177.128/30 permit +106.10.177.184/30 permit +106.10.177.188/31 permit +106.10.177.217 permit +106.10.177.218/31 permit +106.10.177.220/31 permit +106.10.177.222 permit +106.10.196.43 permit +106.10.196.44/30 permit +106.10.196.48 permit +106.10.240.0/24 permit +106.10.241.0/24 permit +106.10.242.0/24 permit +106.10.243.0/24 permit +106.10.244.0/24 permit +106.39.212.64/29 permit +106.50.16.0/28 permit +107.20.18.111 permit +107.20.210.250 permit +107.22.191.150 permit +108.174.0.0/24 permit +108.174.0.215 permit +108.174.3.0/24 permit +108.174.3.215 permit +108.174.6.0/24 permit +108.174.6.215 permit +108.175.18.45 permit +108.175.30.45 permit +108.179.144.0/20 permit +109.224.244.0/24 permit +109.237.142.0/24 permit +112.19.199.64/29 permit +112.19.242.64/29 permit +116.214.12.47 permit +116.214.12.48/31 permit +116.214.12.56/31 permit +116.214.12.58 permit +116.214.12.73 permit +116.214.12.74/31 permit +116.214.12.76/31 permit +116.214.12.78 permit +116.214.12.93 permit +116.214.12.94/31 permit +116.214.12.96/31 permit +116.214.12.98 permit +117.120.16.0/21 permit +119.42.242.52/31 permit +119.42.242.156 permit +121.244.91.31 permit +121.244.91.48 permit +121.244.91.52 permit +122.15.156.182 permit +123.126.78.64/29 permit +124.108.96.24/31 permit +124.108.96.28/31 permit +124.108.96.70/31 permit +124.108.96.72/31 permit +128.17.0.0/20 permit +128.17.64.0/20 permit +128.17.128.0/20 permit +128.17.192.0/20 permit +128.127.70.0/26 permit +128.245.0.0/20 permit +128.245.64.0/20 permit +128.245.176.0/20 permit +128.245.240.0/24 permit +128.245.241.0/24 permit +128.245.242.0/24 permit +128.245.242.16 permit +128.245.242.17 permit +128.245.242.18 permit +128.245.243.0/24 permit +128.245.244.0/24 permit +128.245.245.0/24 permit +128.245.246.0/24 permit +128.245.247.0/24 permit +128.245.248.0/21 permit +129.41.77.70 permit +129.41.169.249 permit +129.77.16.0/20 permit +129.80.5.164 permit +129.80.64.36 permit +129.80.67.121 permit +129.80.145.156 permit +129.145.74.12 permit +129.146.88.28 permit +129.146.147.105 permit +129.146.236.58 permit +129.148.135.0/25 permit +129.148.148.0/25 permit +129.148.164.0/25 permit +129.148.180.0/25 permit +129.148.215.0/25 permit +129.149.6.0/25 permit +129.149.22.0/25 permit +129.149.38.0/25 permit +129.149.52.0/25 permit +129.149.68.0/25 permit +129.149.84.0/25 permit +129.149.100.0/25 permit +129.149.118.0/25 permit +129.149.126.0/25 permit +129.151.67.221 permit +129.153.62.216 permit +129.153.104.71 permit +129.153.168.146 permit +129.153.190.200 permit +129.153.194.228 permit +129.154.255.129 permit +129.158.56.255 permit +129.158.62.153 permit +129.159.22.159 permit +129.159.87.137 permit +129.213.195.191 permit +130.35.116.0/25 permit +130.61.9.72 permit +130.162.39.83 permit +130.248.172.0/24 permit +130.248.173.0/24 permit +131.186.12.0/25 permit +131.253.30.0/24 permit +131.253.121.0/26 permit +132.145.13.209 permit +132.226.26.225 permit +132.226.49.32 permit +132.226.56.24 permit +134.98.248.128/25 permit +134.128.64.0/19 permit +134.128.96.0/19 permit +134.170.27.8 permit +134.170.113.0/26 permit +134.170.141.64/26 permit +134.170.143.0/24 permit +134.170.174.0/24 permit +135.84.80.0/24 permit +135.84.81.0/24 permit +135.84.82.0/24 permit +135.84.83.0/24 permit +135.84.216.0/22 permit +136.143.160.0/24 permit +136.143.161.0/24 permit +136.143.162.0/24 permit +136.143.176.0/24 permit +136.143.177.0/24 permit +136.143.178.49 permit +136.143.182.0/23 permit +136.143.184.0/24 permit +136.143.188.0/24 permit +136.143.190.0/23 permit +136.146.128.0/20 permit +136.147.128.0/20 permit +136.147.135.0/24 permit +136.147.176.0/20 permit +136.147.176.0/24 permit +136.147.182.0/24 permit +136.147.224.0/20 permit +136.179.50.206 permit +136.248.224.128/25 permit +136.248.232.128/25 permit +138.1.108.0/25 permit +138.1.170.0/24 permit +139.60.152.0/22 permit +139.138.35.44 permit +139.138.46.121 permit +139.138.46.176 permit +139.138.46.219 permit +139.138.57.55 permit +139.138.58.119 permit +139.167.79.86 permit +139.177.108.0/25 permit +139.180.17.0/24 permit +140.238.148.191 permit +141.148.55.217 permit +141.148.91.244 permit +141.148.159.229 permit +141.193.32.0/23 permit +141.193.184.32/27 permit +141.193.184.64/26 permit +141.193.184.128/25 permit +141.193.185.32/27 permit +141.193.185.64/26 permit +141.193.185.128/25 permit +143.47.120.152 permit +143.55.224.0/21 permit +143.55.232.0/22 permit +143.55.236.0/22 permit +143.244.80.0/20 permit +144.24.6.140 permit +144.34.8.247 permit +144.34.9.247 permit +144.34.32.247 permit +144.34.33.247 permit +144.178.36.0/24 permit +144.178.38.0/24 permit +145.253.228.160/29 permit +145.253.239.128/29 permit +146.20.14.104 permit +146.20.14.105 permit +146.20.14.106 permit +146.20.14.107 permit +146.20.112.0/26 permit +146.20.113.0/24 permit +146.20.191.0/24 permit +146.20.215.0/24 permit +146.20.215.182 permit +146.88.28.0/24 permit +146.148.116.76 permit +147.154.32.0/25 permit +147.154.63.0/24 permit +147.154.126.0/24 permit +147.154.191.0/24 permit +147.243.1.47 permit +147.243.1.48 permit +147.243.1.153 permit +147.243.128.24 permit +147.243.128.26 permit +148.105.0.0/16 permit +148.105.8.0/21 permit +148.116.32.128/25 permit +149.72.0.0/16 permit +149.72.234.184 permit +149.72.248.236 permit +149.97.173.180 permit +149.118.160.128/25 permit +150.136.21.199 permit +150.171.109.72 permit +150.230.98.160 permit +151.145.38.14 permit +152.67.105.195 permit +152.69.200.236 permit +152.70.155.126 permit +155.248.135.128/25 permit +155.248.140.0/25 permit +155.248.148.0/25 permit +155.248.208.51 permit +155.248.220.138 permit +155.248.234.149 permit +155.248.237.141 permit +157.56.120.128/26 permit +157.58.30.128/25 permit +157.58.196.96/29 permit +157.58.249.3 permit +157.137.32.128/25 permit +157.137.96.128/25 permit +157.151.208.65 permit +157.255.1.64/29 permit +158.101.211.207 permit +158.247.16.0/20 permit +158.247.100.0/25 permit +159.13.4.0/25 permit +159.92.154.0/24 permit +159.92.155.0/24 permit +159.92.157.0/24 permit +159.92.157.16 permit +159.92.157.17 permit +159.92.157.18 permit +159.92.158.0/24 permit +159.92.159.0/24 permit +159.92.160.0/24 permit +159.92.161.0/24 permit +159.92.162.0/24 permit +159.92.163.0/24 permit +159.92.164.0/22 permit +159.92.168.0/21 permit +159.112.240.0/20 permit +159.112.242.162 permit +159.135.132.128/25 permit +159.135.140.80/29 permit +159.135.224.0/20 permit +159.135.228.10 permit +159.183.0.0/16 permit +159.183.14.233 permit +159.183.68.71 permit +159.183.79.38 permit +159.183.121.182 permit +159.183.129.172 permit +160.1.62.192 permit +161.38.192.0/20 permit +161.38.204.0/22 permit +161.71.32.0/19 permit +161.71.64.0/20 permit +162.88.4.0/23 permit +162.88.8.0/24 permit +162.88.24.0/23 permit +162.88.24.0/24 permit +162.88.25.0/24 permit +162.88.36.0/24 permit +162.247.216.0/22 permit +163.47.180.0/22 permit +163.114.130.16 permit +163.114.132.120 permit +163.114.134.16 permit +163.114.135.16 permit +163.116.128.0/17 permit +163.192.116.87 permit +163.192.125.176 permit +163.192.196.146 permit +163.192.204.161 permit +164.152.23.32 permit +164.152.25.241 permit +164.177.132.168/30 permit +165.1.100.0/25 permit +165.173.128.0/24 permit +165.173.180.0/24 permit +165.173.180.250/31 permit +165.173.182.0/24 permit +165.173.182.250/31 permit +165.173.189.205 permit +166.78.68.0/22 permit +166.78.68.221 permit +166.78.69.169 permit +166.78.69.170 permit +166.78.71.131 permit +167.89.0.0/17 permit +167.89.46.159 permit +167.89.54.103 permit +167.89.60.95 permit +167.89.64.9 permit +167.89.65.0 permit +167.89.65.53 permit +167.89.65.100 permit +167.89.74.233 permit +167.89.75.33 permit +167.89.75.126 permit +167.89.75.136 permit +167.89.75.164 permit +167.220.67.232/29 permit +168.107.248.128/25 permit +168.110.160.128/25 permit +168.110.248.128/25 permit +168.129.184.128/25 permit +168.129.248.128/25 permit +168.138.5.36 permit +168.138.73.51 permit +168.138.77.31 permit +168.138.237.153 permit +168.245.0.0/17 permit +168.245.12.252 permit +168.245.46.9 permit +168.245.127.231 permit +169.148.129.0/24 permit +169.148.131.0/24 permit +169.148.138.0/24 permit +169.148.142.10 permit +169.148.142.33 permit +169.148.144.0/25 permit +169.148.144.10 permit +169.148.146.0/23 permit +169.148.174.10 permit +169.148.175.3 permit +169.148.179.3 permit +169.148.188.0/24 permit +169.148.188.182 permit +170.9.232.254 permit +170.10.128.0/24 permit +170.10.129.0/24 permit +170.10.132.56/29 permit +170.10.132.64/29 permit +170.10.133.0/24 permit +173.0.84.0/29 permit +173.0.84.224/27 permit +173.0.94.244/30 permit +173.194.0.0/16 permit +173.203.79.182 permit +173.203.81.39 permit +173.224.161.128/25 permit +173.224.165.0/26 permit +173.224.166.48/28 permit +174.36.84.8/29 permit +174.36.84.16/29 permit +174.36.84.32/29 permit +174.36.84.144/29 permit +174.36.84.240/29 permit +174.36.85.248/30 permit +174.36.114.128/30 permit +174.36.114.140/30 permit +174.36.114.148/30 permit +174.36.114.152/29 permit +174.37.67.28/30 permit +175.41.215.51 permit +176.32.105.0/24 permit +176.32.127.0/24 permit +178.236.10.128/26 permit +182.50.76.0/22 permit +182.50.78.64/28 permit +183.240.219.64/29 permit +185.4.120.0/22 permit +185.11.255.144 permit +185.12.80.0/22 permit +185.28.196.0/22 permit +185.58.84.93 permit +185.70.40.0/24 permit +185.70.41.0/24 permit +185.70.43.0/24 permit +185.80.93.204 permit +185.80.93.227 permit +185.80.95.31 permit +185.90.20.0/22 permit +185.138.56.128/25 permit +185.189.236.0/22 permit +185.211.120.0/22 permit +185.233.188.68 permit +185.233.188.75 permit +185.233.188.84 permit +185.233.188.160 permit +185.233.188.176 permit +185.233.188.247 permit +185.233.189.44 permit +185.233.189.98 permit +185.233.189.122 permit +185.233.189.228 permit +185.250.236.0/22 permit +185.250.239.148 permit +185.250.239.168 permit +185.250.239.190 permit +188.125.68.132 permit +188.125.68.152/31 permit +188.125.68.156 permit +188.125.68.163 permit +188.125.68.172/31 permit +188.125.68.176 permit +188.125.68.179 permit +188.125.68.184 permit +188.125.68.186 permit +188.125.68.192 permit +188.125.69.105 permit +188.125.69.110 permit +188.125.69.112 permit +188.125.69.120 permit +188.125.69.126 permit +188.125.69.131 permit +188.125.69.132/31 permit +188.125.69.137 permit +188.125.69.139 permit +188.125.69.144/30 permit +188.125.69.148/31 permit +188.125.83.88/30 permit +188.125.83.92/31 permit +188.125.83.96/30 permit +188.125.83.100/31 permit +188.125.83.116/30 permit +188.125.83.120/31 permit +188.125.83.140/30 permit +188.125.83.144/31 permit +188.125.83.160/30 permit +188.125.83.164/31 permit +188.125.84.32/30 permit +188.125.84.36 permit +188.125.84.122/31 permit +188.125.84.124/30 permit +188.125.84.244/30 permit +188.125.84.248/31 permit +188.125.85.116/30 permit +188.125.85.120/31 permit +188.125.85.130/31 permit +188.125.85.132/30 permit +188.125.85.202/31 permit +188.125.85.204/31 permit +188.125.85.206 permit +188.125.85.233 permit +188.125.85.234/31 permit +188.125.85.236/31 permit +188.125.85.238 permit +188.165.51.139 permit +188.172.128.0/20 permit +192.0.64.0/18 permit +192.18.139.154 permit +192.18.145.36 permit +192.18.152.58 permit +192.22.32.128/25 permit +192.22.96.128/25 permit +192.22.160.128/25 permit +192.22.224.128/25 permit +192.28.128.0/18 permit +192.29.24.0/25 permit +192.29.44.0/25 permit +192.29.72.0/25 permit +192.29.88.0/25 permit +192.29.103.128/25 permit +192.29.134.0/25 permit +192.29.151.128/25 permit +192.29.172.0/25 permit +192.29.178.0/25 permit +192.29.200.0/25 permit +192.29.216.0/25 permit +192.29.232.0/25 permit +192.29.248.0/25 permit +192.30.252.0/22 permit +192.161.144.0/20 permit +192.162.87.0/24 permit +192.237.158.0/23 permit +192.237.159.42 permit +192.237.159.43 permit +192.254.112.0/20 permit +193.109.254.0/23 permit +193.122.128.100 permit +193.123.56.63 permit +193.142.157.15 permit +193.142.157.125 permit +193.142.157.158 permit +193.142.157.191 permit +193.142.157.198 permit +194.19.134.0/25 permit +194.25.134.16/28 permit +194.25.134.80/28 permit +194.64.234.129 permit +194.97.196.0/24 permit +194.97.196.3 permit +194.97.196.4 permit +194.97.196.11 permit +194.97.196.12 permit +194.97.204.0/24 permit +194.97.204.3 permit +194.97.204.4 permit +194.97.204.11 permit +194.97.204.12 permit +194.97.212.0/24 permit +194.97.212.3 permit +194.97.212.4 permit +194.97.212.11 permit +194.97.212.12 permit +194.106.220.0/23 permit +194.113.24.0/22 permit +194.113.42.0/26 permit +194.154.193.192/27 permit +195.4.92.0/23 permit +195.54.172.0/23 permit +195.234.109.226 permit +195.245.230.0/23 permit +198.2.128.0/18 permit +198.21.0.0/21 permit +198.37.144.0/20 permit +198.37.152.186 permit +198.61.254.0/23 permit +198.61.254.21 permit +198.61.254.231 permit +198.178.234.57 permit +198.202.211.1 permit +198.244.48.0/20 permit +198.244.56.107 permit +198.244.56.108 permit +198.244.56.109 permit +198.244.56.111 permit +198.244.56.112 permit +198.244.56.113 permit +198.244.56.114 permit +198.244.56.115 permit +198.244.59.30 permit +198.244.59.33 permit +198.244.59.35 permit +198.244.60.0/22 permit +198.245.80.0/20 permit +198.245.81.0/24 permit +199.15.212.0/22 permit +199.15.213.187 permit +199.15.226.37 permit +199.16.156.0/22 permit +199.33.145.1 permit +199.33.145.32 permit +199.34.22.36 permit +199.59.148.0/22 permit +199.67.80.2 permit +199.67.80.20 permit +199.67.82.2 permit +199.67.82.20 permit +199.67.84.0/24 permit +199.67.86.0/24 permit +199.67.88.0/24 permit +199.67.90.0/24 permit +199.101.161.130 permit +199.101.162.0/25 permit +199.122.120.0/21 permit +199.122.123.0/24 permit +199.127.232.0/22 permit +199.255.192.0/22 permit +202.12.124.128/27 permit +202.129.242.0/23 permit +202.165.102.47 permit +202.177.148.100 permit +202.177.148.110 permit +203.32.4.25 permit +203.55.21.0/24 permit +203.81.17.0/24 permit +203.122.32.250 permit +203.145.57.160/27 permit +203.188.194.32 permit +203.188.194.151 permit +203.188.194.203 permit +203.188.194.204 permit +203.188.194.213 permit +203.188.194.251 permit +203.188.195.240/30 permit +203.188.195.244/31 permit +203.188.197.193 permit +203.188.197.194/31 permit +203.188.197.196/30 permit +203.188.197.209 permit +203.188.197.210/31 permit +203.188.197.212/30 permit +203.188.197.216/29 permit +203.188.197.232/29 permit +203.188.197.240/29 permit +203.188.200.56/31 permit +203.188.200.58 permit +203.188.200.60/30 permit +203.188.200.83 permit +203.188.200.100/31 permit +203.188.200.102 permit +203.188.200.108/31 permit +203.188.200.160/31 permit +203.188.200.208/30 permit +203.188.200.212/31 permit +203.188.201.12/30 permit +203.209.230.75 permit +203.209.230.76/31 permit +204.11.168.0/21 permit +204.13.11.48/29 permit +204.14.232.0/21 permit +204.14.232.64/28 permit +204.14.234.64/28 permit +204.75.142.0/24 permit +204.92.114.187 permit +204.92.114.203 permit +204.92.114.204/31 permit +204.141.32.0/23 permit +204.141.42.0/23 permit +204.216.164.202 permit +204.220.160.0/21 permit +204.220.168.0/21 permit +204.220.176.0/20 permit +204.220.181.105 permit +204.232.168.0/24 permit +205.139.110.0/24 permit +205.201.128.0/20 permit +205.201.137.229 permit +205.207.104.0/22 permit +205.220.167.17 permit +205.220.167.98 permit +205.220.179.17 permit +205.220.179.98 permit +205.251.233.32 permit +205.251.233.36 permit +206.25.247.143 permit +206.25.247.155 permit +206.55.144.0/20 permit +206.165.246.80/29 permit +206.191.224.0/19 permit +206.246.157.1 permit +207.46.22.35 permit +207.46.50.72 permit +207.46.50.82 permit +207.46.52.71 permit +207.46.52.79 permit +207.67.38.0/24 permit +207.67.98.192/27 permit +207.97.204.96/29 permit +207.126.144.0/20 permit +207.171.160.0/19 permit +207.211.30.64/26 permit +207.211.30.128/25 permit +207.211.31.0/25 permit +207.211.41.113 permit +207.211.132.0/25 permit +207.218.90.0/24 permit +207.218.90.122 permit +207.250.68.0/24 permit +208.40.232.70 permit +208.43.21.28/30 permit +208.43.21.64/29 permit +208.43.21.72/30 permit +208.56.9.224 permit +208.56.13.196 permit +208.64.132.0/22 permit +208.71.40.63 permit +208.71.40.64/31 permit +208.71.40.174/31 permit +208.71.40.185 permit +208.71.40.186/31 permit +208.71.40.202/31 permit +208.71.40.204/31 permit +208.71.40.219 permit +208.71.41.21 permit +208.71.41.22/31 permit +208.71.41.24/31 permit +208.71.41.42/31 permit +208.71.41.64/30 permit +208.71.41.68/31 permit +208.71.41.171 permit +208.71.41.172/31 permit +208.71.41.188/30 permit +208.71.41.192/31 permit +208.71.42.190/31 permit +208.71.42.192/28 permit +208.71.42.208/30 permit +208.71.42.212/31 permit +208.71.42.214 permit +208.72.249.240/29 permit +208.75.120.0/22 permit +208.76.62.0/23 permit +208.76.62.0/24 permit +208.76.63.0/24 permit +208.82.237.96/29 permit +208.82.237.104/31 permit +208.82.238.96/29 permit +208.82.238.104/31 permit +208.85.50.137 permit +208.117.48.0/20 permit +208.185.229.45 permit +208.201.241.163 permit +209.43.22.0/28 permit +209.46.117.168 permit +209.46.117.179 permit +209.61.151.0/24 permit +209.61.151.236 permit +209.61.151.249 permit +209.61.151.251 permit +209.67.98.46 permit +209.67.98.59 permit +209.85.128.0/17 permit +212.82.96.32/27 permit +212.82.96.64/29 permit +212.82.98.32/29 permit +212.82.98.64/27 permit +212.82.98.96/30 permit +212.82.98.100/30 permit +212.82.98.104/29 permit +212.82.98.112/29 permit +212.82.98.120/30 permit +212.82.98.144/28 permit +212.82.105.240/30 permit +212.82.108.112/29 permit +212.82.108.120/30 permit +212.82.108.124/31 permit +212.82.108.126 permit +212.82.108.132/30 permit +212.82.108.136/30 permit +212.82.108.208/30 permit +212.82.108.212/31 permit +212.82.108.224/30 permit +212.82.108.232/29 permit +212.82.108.240/29 permit +212.82.108.248/30 permit +212.82.108.252/31 permit +212.82.108.254 permit +212.82.109.128/31 permit +212.82.109.132 permit +212.82.111.131 permit +212.82.111.133 permit +212.82.111.135 permit +212.82.111.137 permit +212.82.111.139 permit +212.82.111.141 permit +212.82.111.225 permit +212.82.111.226/31 permit +212.82.111.228/31 permit +212.82.111.230 permit +212.123.28.40 permit +212.227.15.3 permit +212.227.15.4 permit +212.227.15.5 permit +212.227.15.6 permit +212.227.15.7 permit +212.227.15.8 permit +212.227.15.14 permit +212.227.15.15 permit +212.227.15.18 permit +212.227.15.19 permit +212.227.15.25 permit +212.227.15.26 permit +212.227.15.29 permit +212.227.15.44 permit +212.227.15.45 permit +212.227.15.46 permit +212.227.15.47 permit +212.227.15.50 permit +212.227.15.52 permit +212.227.15.53 permit +212.227.15.54 permit +212.227.15.55 permit +212.227.17.1 permit +212.227.17.2 permit +212.227.17.7 permit +212.227.17.11 permit +212.227.17.12 permit +212.227.17.16 permit +212.227.17.17 permit +212.227.17.18 permit +212.227.17.19 permit +212.227.17.20 permit +212.227.17.21 permit +212.227.17.22 permit +212.227.17.26 permit +212.227.17.27 permit +212.227.17.28 permit +212.227.17.29 permit +212.227.126.206 permit +212.227.126.207 permit +212.227.126.208 permit +212.227.126.209 permit +212.227.126.220 permit +212.227.126.221 permit +212.227.126.222 permit +212.227.126.223 permit +212.227.126.224 permit +212.227.126.225 permit +212.227.126.226 permit +212.227.126.227 permit +213.199.128.139 permit +213.199.128.145 permit +213.199.138.181 permit +213.199.138.191 permit +216.17.150.242 permit +216.17.150.251 permit +216.24.224.0/20 permit +216.27.86.152/31 permit +216.39.60.154/31 permit +216.39.60.156/30 permit +216.39.60.160/30 permit +216.39.60.164 permit +216.39.60.192/28 permit +216.39.60.208/29 permit +216.39.60.216 permit +216.39.60.218 permit +216.39.60.230/31 permit +216.39.60.232/29 permit +216.39.60.240/29 permit +216.39.60.248/30 permit +216.39.60.252/31 permit +216.39.60.254 permit +216.39.61.170 permit +216.39.61.175 permit +216.39.61.238/31 permit +216.39.62.32/28 permit +216.39.62.48/29 permit +216.39.62.56/30 permit +216.39.62.60/31 permit +216.39.62.136/29 permit +216.39.62.144/31 permit +216.66.217.240/29 permit +216.71.138.33 permit +216.71.152.207 permit +216.71.154.29 permit +216.71.155.89 permit +216.74.162.13 permit +216.74.162.14 permit +216.82.240.0/20 permit +216.98.158.0/24 permit +216.99.5.67 permit +216.99.5.68 permit +216.109.114.32/27 permit +216.109.114.64/29 permit +216.113.162.65 permit +216.113.163.65 permit +216.128.126.97 permit +216.136.162.65 permit +216.136.162.120/29 permit +216.136.168.80/28 permit +216.139.64.0/19 permit +216.145.221.0/24 permit +216.146.32.0/23 permit +216.146.32.0/24 permit +216.146.33.0/24 permit +216.198.0.0/18 permit +216.203.30.55 permit +216.203.33.178/31 permit +216.205.24.0/24 permit +216.221.160.0/19 permit +216.239.32.0/19 permit +217.72.192.77 permit +217.72.192.78 permit +217.77.141.52 permit +217.77.141.59 permit +217.175.194.0/24 permit +222.73.195.64/29 permit +223.165.113.0/24 permit +223.165.115.0/24 permit +223.165.118.0/23 permit +223.165.120.0/23 permit +2001:0868:0100:0600::/64 permit +2001:4860:4000::/36 permit +2001:4860:4864::/56 permit +2001:748:100:40::2:0/112 permit +2001:748:400:1300::3 permit +2001:748:400:1300::4 permit +2001:748:400:1301::0/64 permit +2001:748:400:1301::3 permit +2001:748:400:1301::4 permit +2001:748:400:2300::3 permit +2001:748:400:2300::4 permit +2001:748:400:2301::0/64 permit +2001:748:400:2301::3 permit +2001:748:400:2301::4 permit +2001:748:400:3300::3 permit +2001:748:400:3300::4 permit +2001:748:400:3301::0/64 permit +2001:748:400:3301::3 permit +2001:748:400:3301::4 permit +2404:6800:4000::/36 permit +2404:6800:4864::/56 permit +2603:1061:14:75::1 permit +2607:13c0:0001:0000:0000:0000:0000:7000/116 permit +2607:13c0:0002:0000:0000:0000:0000:1000/116 permit +2607:13c0:0004:0000:0000:0000:0000:0000/116 permit +2607:f8b0:4000::/36 permit +2607:f8b0:4864::/56 permit +2620:109:c003:104::/64 permit +2620:109:c003:104::215 permit +2620:109:c006:104::/64 permit +2620:109:c006:104::215 permit +2620:109:c00d:104::/64 permit +2620:10d:c090:400::8:1 permit +2620:10d:c091:400::8:1 permit +2620:10d:c09b:400::8:1 permit +2620:10d:c09c:400::8:1 permit +2620:119:50c0:207::/64 permit +2620:119:50c0:207::215 permit +2800:3f0:4000::/36 permit +2800:3f0:4864::/56 permit +49.12.4.251 permit # checks.mailcow.email +2a01:4f8:c17:7906::10 permit # checks.mailcow.email diff --git a/data/conf/redis/redis-conf.sh b/data/conf/redis/redis-conf.sh new file mode 100755 index 0000000000..7e2672a310 --- /dev/null +++ b/data/conf/redis/redis-conf.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +cat < /redis.conf +requirepass $REDISPASS +user quota_notify on nopass ~QW_* -@all +get +hget +ping +EOF + +if [ -n "$REDISMASTERPASS" ]; then + echo "masterauth $REDISMASTERPASS" >> /redis.conf +fi + +exec redis-server /redis.conf diff --git a/data/conf/rspamd/custom/bad_asn.map b/data/conf/rspamd/custom/bad_asn.map index ffd9af0f5c..a8d49cf39b 100644 --- a/data/conf/rspamd/custom/bad_asn.map +++ b/data/conf/rspamd/custom/bad_asn.map @@ -1,30 +1,31 @@ # High spam networks, disabled by default -# ASN:SCORE DESC +# ASN SCORE DESC # Remove comment to enable score -#201942:5 #Soltia Consulting SL - ipinfo.io -#16276:2 #OVH -#12876:2 #ONLINE S.A.S -#31034:5 #ARUBA-ASN, IT -#12874:5 #FASTWEB, IT -#30823:3 #PKV spam -#42831:5 #UK Dedicated Servers Ltd -#29119:5 #Aire Networks del Mediterraneo S.L.U. -#13335:5 #Cloudflare -#28753:5 #Leaseweb -#61272:5 #Informacines sistemos ir technologijos -#53755:5 #Input Output Flood LLC -#29422:5 #FICIX Helsinki -#62255:4 #Asmunda New Media Ltd -#14061:4 #Digitalocean -#55293:4 #A2 Hosting -#63018:4 #US Dedicated -#197518:2 #RACKMARKT -#44493:2 -#46606:2 -#49505:2 -#21100:2 -#197695:2 -#198068:2 -#43146:2 -#49100:4 -#39364:4 +#12874 5 #Fastweb SpA, Italy +#12876 2 #ONLINE S.A.S, France +#13335 5 #Cloudflare Inc., United States +#14061 4 #DigitalOcean LLC, United States +#16276 2 #OVH SAS, France +#21100 2 #ITL LLC, Ukraine +#28753 5 #Leaseweb Deutschland GmbH, Germany +#29119 5 #ServiHosting Networks S.L., Spain +#29422 5 #Telia Inmics-Nebula Oy, Finland +#30823 3 #combahton GmbH, Germany +#31034 5 #Aruba S.p.A, Italy +#39364 4 #Hormoz IT & Network Waves Connection Co. (PJS), Iran +#42831 5 #UK Dedicated Servers Limited, United Kingdom +#43146 2 #Domain names registrar REG.RU Ltd, Russia +#44493 2 #Chelyabinsk-Signal LLC, Russia +#46606 2 #Unified Layer, United States +#49100 4 #Pishgaman Toseeh Ertebatat Company (Private Joint Stock), Iran +#49505 2 #OOO Network of data-centers Selectel, Russia +#53755 5 #Input Output Flood LLC, United States +#55293 4 #A2 Hosting Inc., United States +#61272 5 #Informacines sistemos ir technologijos - UAB, Lithuania +#62255 4 #Asmunda New Media Ltd., Seychelles +#63018 4 #Dedicated.com, United States +#197518 2 #Rackmarkt SL, Spain +#197695 2 #Domain names registrar REG.RU Ltd, Russia +#198068 2 #P.A.G.M. OU, Estonia +#201942 5 #Soltia Consulting SL, Spain +#213373 4 #IP Connect Inc \ No newline at end of file diff --git a/data/conf/rspamd/custom/bad_header.map b/data/conf/rspamd/custom/bad_header.map new file mode 100644 index 0000000000..839c3c372a --- /dev/null +++ b/data/conf/rspamd/custom/bad_header.map @@ -0,0 +1,2 @@ +/Thread-Topic:\s[a-zA-Z]{3}\s[a-zA-Z]{2}[\s\r\n]{0,1}[^a-zA-Z0-9][\r\n]/i +/Thread-Topic:\s[a-zA-Z]{3}\s[a-zA-Z]{2}\s[a-zA-Z]{1}\s[a-zA-Z]{5}[\s\r\n]{0,1}[^a-zA-Z0-9][\r\n]/i diff --git a/data/conf/rspamd/custom/bad_languages.map b/data/conf/rspamd/custom/bad_languages.map new file mode 100644 index 0000000000..cf9ce3e142 --- /dev/null +++ b/data/conf/rspamd/custom/bad_languages.map @@ -0,0 +1 @@ +# Regex! /de/ will also match /de_at/ etc. diff --git a/data/conf/rspamd/custom/bad_words.map b/data/conf/rspamd/custom/bad_words.map new file mode 100644 index 0000000000..0d9af8be17 --- /dev/null +++ b/data/conf/rspamd/custom/bad_words.map @@ -0,0 +1,29 @@ +/\serotic\s/i +/\serection\s/i +/\ssexy\s/i +/\sass\s/i +/\sviagra\s/i +/\stits\s/i +/\stitty\s/i +/\stitties\s/i +/\scum\s/i +/\ssperm\s/i +/\sslut\s/i +/\sporn\s/i +/\scock\s/i +/\spharma\s/i +/\spharmacy\s/i +/\sseo\s/i +/\sjackpot\s/i +/\slottery\s/i +/bitcoin/i +/trojaner/i +/malware/i +/\sscooter\s/i +/testost/i +/web\sdevelopment/i +/\slottery\s/i +/\ssex\s/i +/\svagina\s/i +/\spenis\s/i +/\smarketing\s/i \ No newline at end of file diff --git a/data/conf/rspamd/custom/bad_words_de.map b/data/conf/rspamd/custom/bad_words_de.map new file mode 100644 index 0000000000..ccdd586279 --- /dev/null +++ b/data/conf/rspamd/custom/bad_words_de.map @@ -0,0 +1,17 @@ +/\slotto\s/i +/pillenversand/i +/\skredithilfe\s/i +/\skapital\s/i +/\skrankenversicherung\s/i +/pรคdophil/i +/paedophil/i +/freiberufler/i +/unternehmer/i +/masturbieren/i +/\sescooter\s/i +/\se-scooter\s/i +/testost/i +/\spotenz\s/i +/potenzmittel/i +/rezeptfrei/i +/apotheke/i \ No newline at end of file diff --git a/data/conf/rspamd/custom/bulk_header.map b/data/conf/rspamd/custom/bulk_header.map new file mode 100644 index 0000000000..69a20af872 --- /dev/null +++ b/data/conf/rspamd/custom/bulk_header.map @@ -0,0 +1,19 @@ +/X-EMV-Platform; .*/i +/.*nur-1-click.*/i +/.*episerver.*/i +/.*supergewinne.*/i +/List-Unsubscribe.*nbps\.eu/i +/.*regiofinder.*/i +/.*EmailSocket.*/i +/List-Unsubscribe:.*respread.*/i +/.*greenflamingo.*/i +/.*senderemailglobal.*/i +/.*promio\.net.*/i +/.*promio\.de.*/i +/.*mailer-service\.com.*/i +/.*mailer-service\.de.*/i +/.*dynamic-lht.*/i +/.*light-house-traffic.*/i +/.*newsletterplus.*/i +/.*X-Chpo.*/i +/.*List-Unsubscribe:.*@nl\..*/i diff --git a/data/conf/rspamd/custom/fishy_tlds.map b/data/conf/rspamd/custom/fishy_tlds.map new file mode 100644 index 0000000000..f19f70da5f --- /dev/null +++ b/data/conf/rspamd/custom/fishy_tlds.map @@ -0,0 +1,64 @@ +/.+\.accountant$/i +/.+\.art$/i +/.+\.asia$/i +/.+\.bid$/i +/.+\.biz$/i +/.+\.care$/i +/.+\.cf$/i +/.+\.click$/i +/.+\.cloud$/i +/.+\.co$/i +/.+\.construction$/i +/.+\.country$/i +/.+\.cricket$/i +/.+\.date$/i +/.+\.desi$/i +/.+\.download$/i +/.+\.estate$/i +/.+\.faith$/i +/.+\.fit$/i +/.+\.flights$/i +/.+\.ga$/i +/.+\.gdn$/i +/.+\.gq$/i +/.+\.guru$/i +/.+\.icu$/i +/.+\.id$/i +/.+\.in.net$/i +/.+\.ir$/i +/.+\.jetzt$/i +/.+\.kim$/i +/.+\.life$/i +/.+\.link$/i +/.+\.loan$/i +/.+\.mk$/i +/.+\.ml$/i +/.+\.ninja$/i +/.+\.online$/i +/.+\.ooo$/i +/.+\.party$/i +/.+\.pro$/i +/.+\.ps$/i +/.+\.pw$/i +/.+\.racing$/i +/.+\.review$/i +/.+\.rocks$/i +/.+\.ryukyu$/i +/.+\.science$/i +/.+\.site$/i +/.+\.space$/i +/.+\.stream$/i +/.+\.sucks$/i +/.+\.tk$/i +/.+\.top$/i +/.+\.topica\.com$/i +/.+\.town$/i +/.+\.trade$/i +/.+\.uno$/i +/.+\.vip$/i +/.+\.webcam$/i +/.+\.website$/i +/.+\.win$/i +/.+\.work$/i +/.+\.world$/i +/.+\.xyz$/i diff --git a/data/conf/rspamd/custom/global_mime_from_blacklist.map b/data/conf/rspamd/custom/global_mime_from_blacklist.map new file mode 100644 index 0000000000..3c872889ce --- /dev/null +++ b/data/conf/rspamd/custom/global_mime_from_blacklist.map @@ -0,0 +1 @@ +# /.+example\.com/i diff --git a/data/conf/rspamd/custom/global_mime_from_whitelist.map b/data/conf/rspamd/custom/global_mime_from_whitelist.map new file mode 100644 index 0000000000..3c872889ce --- /dev/null +++ b/data/conf/rspamd/custom/global_mime_from_whitelist.map @@ -0,0 +1 @@ +# /.+example\.com/i diff --git a/data/conf/rspamd/custom/global_rcpt_blacklist.map b/data/conf/rspamd/custom/global_rcpt_blacklist.map new file mode 100644 index 0000000000..3c872889ce --- /dev/null +++ b/data/conf/rspamd/custom/global_rcpt_blacklist.map @@ -0,0 +1 @@ +# /.+example\.com/i diff --git a/data/conf/rspamd/custom/global_rcpt_whitelist.map b/data/conf/rspamd/custom/global_rcpt_whitelist.map new file mode 100644 index 0000000000..3c872889ce --- /dev/null +++ b/data/conf/rspamd/custom/global_rcpt_whitelist.map @@ -0,0 +1 @@ +# /.+example\.com/i diff --git a/data/conf/rspamd/custom/global_smtp_from_blacklist.map b/data/conf/rspamd/custom/global_smtp_from_blacklist.map new file mode 100644 index 0000000000..3c872889ce --- /dev/null +++ b/data/conf/rspamd/custom/global_smtp_from_blacklist.map @@ -0,0 +1 @@ +# /.+example\.com/i diff --git a/data/conf/rspamd/custom/global_smtp_from_whitelist.map b/data/conf/rspamd/custom/global_smtp_from_whitelist.map new file mode 100644 index 0000000000..3c872889ce --- /dev/null +++ b/data/conf/rspamd/custom/global_smtp_from_whitelist.map @@ -0,0 +1 @@ +# /.+example\.com/i diff --git a/data/conf/rspamd/custom/ip_wl.map b/data/conf/rspamd/custom/ip_wl.map new file mode 100644 index 0000000000..c8bb552999 --- /dev/null +++ b/data/conf/rspamd/custom/ip_wl.map @@ -0,0 +1,4 @@ +# IP whitelist +# 127.0.0.1 +# 1.2.3.4 +# ... diff --git a/data/conf/rspamd/custom/monitoring_nolog.map b/data/conf/rspamd/custom/monitoring_nolog.map new file mode 100644 index 0000000000..0e00de7cda --- /dev/null +++ b/data/conf/rspamd/custom/monitoring_nolog.map @@ -0,0 +1,7 @@ +# Skip logging for these addresses +/monitoring-system@everycloudtech\.us/i +/monitor@tools\.mailflowmonitoring\.com/i +/watchdog@localhost/i +/supertool@mxtoolbox\.com/i +/test@mxtoolboxsmtpdiag\.com/i +/open-relay-check@mailcow\.email/i diff --git a/data/conf/rspamd/dynmaps/aliasexp.php b/data/conf/rspamd/dynmaps/aliasexp.php new file mode 100644 index 0000000000..814c0d38c2 --- /dev/null +++ b/data/conf/rspamd/dynmaps/aliasexp.php @@ -0,0 +1,175 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); +} +catch (PDOException $e) { + error_log("ALIASEXP: " . $e . PHP_EOL); + http_response_code(501); + exit; +} + +// Init Redis +$redis = new Redis(); +$redis->connect('redis-mailcow', 6379); +$redis->auth(getenv("REDISPASS")); + +function parse_email($email) { + if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; + $a = strrpos($email, '@'); + return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); +} +if (!function_exists('getallheaders')) { + function getallheaders() { + if (!is_array($_SERVER)) { + return array(); + } + $headers = array(); + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) == 'HTTP_') { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + return $headers; + } +} + +// Read headers +$headers = getallheaders(); +// Get rcpt +$rcpt = $headers['Rcpt']; +// Remove tag +$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); +// Parse email address +$parsed_rcpt = parse_email($rcpt); +// Create array of final mailboxes +$rcpt_final_mailboxes = array(); + +// Skip if not a mailcow handled domain +try { + if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) { + exit; + } +} +catch (RedisException $e) { + error_log("ALIASEXP: " . $e . PHP_EOL); + http_response_code(504); + exit; +} + +// Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases +// +// rcpt +// | +// mailbox <-- goto ---> alias1, alias2, mailbox2 +// | | +// mailbox3 | +// | +// alias3 ---> mailbox4 +// +try { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); + $stmt->execute(array( + ':rcpt' => $rcpt + )); + $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + if (empty($gotos)) { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); + $stmt->execute(array( + ':rcpt' => '@' . $parsed_rcpt['domain'] + )); + $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + } + if (empty($gotos)) { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'"); + $stmt->execute(array(':rcpt' => $parsed_rcpt['domain'])); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; + if ($goto_branch) { + $gotos = $parsed_rcpt['local'] . '@' . $goto_branch; + } + } + $gotos_array = explode(',', $gotos); + + $loop_c = 0; + + while (count($gotos_array) != 0 && $loop_c <= 20) { + + // Loop through all found gotos + foreach ($gotos_array as $index => &$goto) { + error_log("ALIAS EXPANDER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL); + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');"); + $stmt->execute(array(':goto' => $goto)); + $username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; + if (!empty($username)) { + error_log("ALIAS EXPANDER: http pipe: mailbox found: " . $username . PHP_EOL); + // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate + if (!in_array($username, $rcpt_final_mailboxes)) { + $rcpt_final_mailboxes[] = $username; + } + } + else { + $parsed_goto = parse_email($goto); + if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { + error_log("ALIAS EXPANDER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL); + } + else { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); + $stmt->execute(array(':goto' => $goto)); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + if ($goto_branch) { + error_log("ALIAS EXPANDER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); + $goto_branch_array = explode(',', $goto_branch); + } else { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'"); + $stmt->execute(array(':domain' => $parsed_goto['domain'])); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; + if ($goto_branch) { + error_log("ALIAS EXPANDER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL); + $goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch); + } + } + } + } + // goto item was processed, unset + unset($gotos_array[$index]); + } + + // Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array + if (!empty($goto_branch_array)) { + $gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array)); + unset($goto_branch_array); + } + + // Reindex array + $gotos_array = array_values($gotos_array); + + // Force exit if loop cannot be solved + // Postfix does not allow for alias loops, so this should never happen. + $loop_c++; + error_log("ALIAS EXPANDER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL); + } +} +catch (PDOException $e) { + error_log("ALIAS EXPANDER: " . $e->getMessage() . PHP_EOL); + http_response_code(502); + exit; +} + +// Does also return the mailbox name if question == answer (query == mailbox) +if (count($rcpt_final_mailboxes) == 1) { + error_log("ALIASEXP: direct alias " . $rcpt . " expanded to " . $rcpt_final_mailboxes[0] . PHP_EOL); + echo trim($rcpt_final_mailboxes[0]); +} diff --git a/data/conf/rspamd/dynmaps/bcc.php b/data/conf/rspamd/dynmaps/bcc.php new file mode 100644 index 0000000000..3145feeb04 --- /dev/null +++ b/data/conf/rspamd/dynmaps/bcc.php @@ -0,0 +1,88 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); +} +catch (PDOException $e) { + error_log("BCC MAP SQL ERROR: " . $e . PHP_EOL); + http_response_code(501); + exit; +} + +function parse_email($email) { + if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; + $a = strrpos($email, '@'); + return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); +} +if (!function_exists('getallheaders')) { + function getallheaders() { + if (!is_array($_SERVER)) { + return array(); + } + $headers = array(); + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) == 'HTTP_') { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + return $headers; + } +} + +// Read headers +$headers = getallheaders(); +// Get rcpt +$rcpt = $headers['Rcpt']; +// Get from +$from = $headers['From']; +// Remove tags +$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); +$from = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $from); + +try { + if (!empty($rcpt)) { + $stmt = $pdo->prepare("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = 'rcpt' AND `local_dest` = :local_dest AND `active` = '1'"); + $stmt->execute(array( + ':local_dest' => $rcpt + )); + $bcc_dest = $stmt->fetch(PDO::FETCH_ASSOC)['bcc_dest']; + if (!empty($bcc_dest) && filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { + error_log("BCC MAP: returning ". $bcc_dest . " for " . $rcpt . PHP_EOL); + http_response_code(201); + echo trim($bcc_dest); + exit; + } + } + if (!empty($from)) { + $stmt = $pdo->prepare("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = 'sender' AND `local_dest` = :local_dest AND `active` = '1'"); + $stmt->execute(array( + ':local_dest' => $from + )); + $bcc_dest = $stmt->fetch(PDO::FETCH_ASSOC)['bcc_dest']; + if (!empty($bcc_dest) && filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { + error_log("BCC MAP: returning ". $bcc_dest . " for " . $from . PHP_EOL); + http_response_code(201); + echo trim($bcc_dest); + exit; + } + } +} +catch (PDOException $e) { + error_log("BCC MAP SQL ERROR: " . $e->getMessage() . PHP_EOL); + http_response_code(502); + exit; +} + diff --git a/data/conf/rspamd/dynmaps/footer.php b/data/conf/rspamd/dynmaps/footer.php new file mode 100644 index 0000000000..545c45ec1c --- /dev/null +++ b/data/conf/rspamd/dynmaps/footer.php @@ -0,0 +1,113 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); +} +catch (PDOException $e) { + error_log("FOOTER: " . $e . PHP_EOL); + http_response_code(501); + exit; +} + +if (!function_exists('getallheaders')) { + function getallheaders() { + if (!is_array($_SERVER)) { + return array(); + } + $headers = array(); + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) == 'HTTP_') { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + return $headers; + } +} + +// Read headers +$headers = getallheaders(); +// Get Domain +$domain = $headers['Domain']; +// Get Username +$username = $headers['Username']; +// Get From +$from = $headers['From']; +// define empty footer +$empty_footer = json_encode(array( + 'html' => '', + 'plain' => '', + 'skip_replies' => 0, + 'vars' => array() +)); + +error_log("FOOTER: checking for domain " . $domain . ", user " . $username . " and address " . $from . PHP_EOL); + +try { + // try get $target_domain if $domain is an alias_domain + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` + WHERE `alias_domain` = :alias_domain"); + $stmt->execute(array( + ':alias_domain' => $domain + )); + $alias_domain = $stmt->fetch(PDO::FETCH_ASSOC); + if (!$alias_domain) { + $target_domain = $domain; + } else { + $target_domain = $alias_domain['target_domain']; + } + + // get footer associated with the domain + $stmt = $pdo->prepare("SELECT `plain`, `html`, `mbox_exclude`, `alias_domain_exclude`, `skip_replies` FROM `domain_wide_footer` + WHERE `domain` = :domain"); + $stmt->execute(array( + ':domain' => $target_domain + )); + $footer = $stmt->fetch(PDO::FETCH_ASSOC); + + // check if the sender is excluded + if (in_array($from, json_decode($footer['mbox_exclude']))){ + $footer = false; + } + if (in_array($domain, json_decode($footer['alias_domain_exclude']))){ + $footer = false; + } + if (empty($footer)){ + echo $empty_footer; + exit; + } + error_log("FOOTER: " . json_encode($footer) . PHP_EOL); + + // footer will be applied + // get custom mailbox attributes to insert into the footer + $stmt = $pdo->prepare("SELECT `custom_attributes` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $custom_attributes = $stmt->fetch(PDO::FETCH_ASSOC)['custom_attributes']; + if (empty($custom_attributes)){ + $custom_attributes = (object)array(); + } +} +catch (Exception $e) { + error_log("FOOTER: " . $e->getMessage() . PHP_EOL); + http_response_code(502); + exit; +} + + +// return footer +$footer["vars"] = $custom_attributes; +echo json_encode($footer); diff --git a/data/conf/rspamd/dynmaps/forwardinghosts.php b/data/conf/rspamd/dynmaps/forwardinghosts.php index 10285b715e..2186d7f26e 100644 --- a/data/conf/rspamd/dynmaps/forwardinghosts.php +++ b/data/conf/rspamd/dynmaps/forwardinghosts.php @@ -4,6 +4,7 @@ $redis = new Redis(); $redis->connect('redis-mailcow', 6379); +$redis->auth(getenv("REDISPASS")); function in_net($addr, $net) { $net = explode('/', $net); diff --git a/data/conf/rspamd/dynmaps/sasl_logs.php b/data/conf/rspamd/dynmaps/sasl_logs.php new file mode 100644 index 0000000000..2d4cbe6584 --- /dev/null +++ b/data/conf/rspamd/dynmaps/sasl_logs.php @@ -0,0 +1,2 @@ +prepare("SELECT GREATEST(COALESCE(MAX(UNIX_TIMESTAMP(UPDATE_TIME)), 1), COALESCE(MAX(UNIX_TIMESTAMP(CREATE_TIME)), 1)) AS `db_update_time` FROM `information_schema`.`tables` + WHERE (`TABLE_NAME` = 'filterconf' OR `TABLE_NAME` = 'settingsmap' OR `TABLE_NAME` = 'sogo_quick_contact' OR `TABLE_NAME` = 'alias') + AND TABLE_SCHEMA = :dbname;"); +$stmt->execute(array( + ':dbname' => $database_name +)); +$db_update_time = $stmt->fetch(PDO::FETCH_ASSOC)['db_update_time']; +if (empty($db_update_time)) { + $db_update_time = 1572048000; +} +if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $db_update_time)) { + header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 304); + exit; +} else { + header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 200); +} + function parse_email($email) { - if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; $a = strrpos($email, '@'); return array('local' => substr($email, 0, $a), 'domain' => substr($email, $a)); } +function normalize_email($email) { + $email = strtolower(str_replace('/', '\/', $email)); + $gm = "@gmail.com"; + if (substr_compare($email, $gm, -strlen($gm)) == 0) { + $email = explode('@', $email); + $email[0] = str_replace('.', '', $email[0]); + $email = implode('@', $email); + } + $gm_alt = "@googlemail.com"; + if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) { + $email = explode('@', $email); + $email[0] = str_replace('.', '', $email[0]); + $email[1] = str_replace('@', '', $gm); + $email = implode('@', $email); + } + if (str_contains($email, "+")) { + $email = explode('@', $email); + $user = explode('+', $email[0]); + $email[0] = $user[0]; + $email = implode('@', $email); + } + return $email; +} + +function wl_by_sogo() { + global $pdo; + $rcpt = array(); + $stmt = $pdo->query("SELECT DISTINCT(`sogo_folder_info`.`c_path2`) AS `user`, GROUP_CONCAT(`sogo_quick_contact`.`c_mail`) AS `contacts` FROM `sogo_folder_info` + INNER JOIN `sogo_quick_contact` ON `sogo_quick_contact`.`c_folder_id` = `sogo_folder_info`.`c_folder_id` + GROUP BY `c_path2`"); + $sogo_contacts = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($sogo_contacts)) { + foreach (explode(',', $row['contacts']) as $contact) { + if (!filter_var($contact, FILTER_VALIDATE_EMAIL)) { + continue; + } + // Explicit from, no mime_from, no regex - envelope must match + // mailcow white and blacklists also cover mime_from + $rcpt[$row['user']][] = normalize_email($contact); + } + } + return $rcpt; +} + function ucl_rcpts($object, $type) { global $pdo; + $rcpt = array(); if ($type == 'mailbox') { // Standard aliases $stmt = $pdo->prepare("SELECT `address` FROM `alias` @@ -48,10 +111,10 @@ function ucl_rcpts($object, $type) { if (!empty($local) && !empty($domain)) { $rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i'; } - $rcpt[] = '/^' . str_replace('/', '\/', $row['address']) . '$/i'; + $rcpt[] = str_replace('/', '\/', $row['address']); } // Aliases by alias domains - $stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox` + $stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox` LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain` WHERE `mailbox`.`username` = :object"); $stmt->execute(array( @@ -66,7 +129,7 @@ function ucl_rcpts($object, $type) { if (!empty($local) && !empty($domain)) { $rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i'; } - $rcpt[] = '/^' . str_replace('/', '\/', $row['alias']) . '$/i'; + $rcpt[] = str_replace('/', '\/', $row['alias']); } } } @@ -82,23 +145,23 @@ function ucl_rcpts($object, $type) { $rcpt[] = '/.*@' . $row['alias_domain'] . '/i'; } } - if (!empty($rcpt)) { - return $rcpt; - } - return false; + return $rcpt; } ?> settings { watchdog { priority = 10; - rcpt = "/null@localhost/i"; - from = "/watchdog@localhost/i"; + rcpt_mime = "/null@localhost/i"; + from_mime = "/watchdog@localhost/i"; apply "default" { + symbols_disabled = ["HISTORY_SAVE", "ARC", "ARC_SIGNED", "DKIM", "DKIM_SIGNED", "CLAM_VIRUS"]; + want_spam = yes; actions { reject = 9999.0; greylist = 9998.0; "add header" = 9997.0; } + } } ; prepare("SELECT `option`, `value` FROM `filterconf` + $stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf` WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel') AND `object`= :object"); $stmt->execute(array(':object' => $row['object'])); @@ -138,6 +201,40 @@ function ucl_rcpts($object, $type) { $contacts) { + $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $user); +?> + whitelist_sogo_ { + + from = ; + + priority = 4; + + rcpt = ; + + apply "default" { + SOGO_CONTACT = -99.0; + } + symbols [ + "SOGO_CONTACT" + ] + } + whitelist_ { prepare("SELECT GROUP_CONCAT(REPLACE(CONCAT('^', `value`, '$'), '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf` + $list_items = array(); + $stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `object`= :object AND `option` = 'whitelist_from'"); $stmt->execute(array(':object' => $row['object'])); - $grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN); - $value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0]))); + $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($list_items as $item) { ?> - from = "/()/i"; + from = "//i"; priority = 5; @@ -186,19 +285,13 @@ function ucl_rcpts($object, $type) { "MAILCOW_WHITE" ] } - whitelist_header_ { + whitelist_mime_ { prepare("SELECT GROUP_CONCAT(REPLACE(CONCAT('\<', `value`, '\>'), '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf` - WHERE `object`= :object - AND `option` = 'whitelist_from'"); - $stmt->execute(array(':object' => $row['object'])); - $grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN); - $value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0]))); + foreach ($list_items as $item) { ?> - header = { - "From" = "/()/i"; - } + from_mime = "//i"; priority = 5; @@ -241,15 +334,17 @@ function ucl_rcpts($object, $type) { ?> blacklist_ { prepare("SELECT GROUP_CONCAT(REPLACE(CONCAT('^', `value`, '$'), '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf` + $list_items = array(); + $stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `object`= :object AND `option` = 'blacklist_from'"); $stmt->execute(array(':object' => $row['object'])); - $grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN); - $value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0]))); + $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($list_items as $item) { ?> - from = "/()/i"; + from = "//i"; priority = 5; @@ -280,17 +375,11 @@ function ucl_rcpts($object, $type) { } blacklist_header_ { prepare("SELECT GROUP_CONCAT(REPLACE(CONCAT('\<', `value`, '\>'), '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf` - WHERE `object`= :object - AND `option` = 'blacklist_from'"); - $stmt->execute(array(':object' => $row['object'])); - $grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN); - $value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0]))); + foreach ($list_items as $item) { ?> - header = { - "From" = "/()/i"; - } + from_mime = "//i"; priority = 5; @@ -327,23 +416,39 @@ function ucl_rcpts($object, $type) { */ ?> - traps { + ham_trap { rcpt = ; + priority = 9; + apply "default" { + symbols_enabled = ["HISTORY_SAVE"]; + } + symbols [ + "HAM_TRAP" + ] + } + + spam_trap { + rcpt = ; priority = 9; - want_spam = yes; + apply "default" { + symbols_enabled = ["HISTORY_SAVE"]; + } + symbols [ + "SPAM_TRAP" + ] } - + } + + +query("SELECT `id`, `address`, `domain` FROM `alias` WHERE `active` = '1' AND `internal` = '1'"); +$aliases = $stmt->fetchAll(PDO::FETCH_ASSOC); +while ($alias = array_shift($aliases)) { + // build allowed_domains regex and add target domain and alias domains + $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `active` = '1' AND `target_domain` = :target_domain"); + $stmt->execute(array(':target_domain' => $alias['domain'])); + $allowed_domains = $stmt->fetchAll(PDO::FETCH_ASSOC); + $allowed_domains = array_map(function($item) { + return str_replace('.', '\.', $item['alias_domain']); + }, $allowed_domains); + $allowed_domains[] = str_replace('.', '\.', $alias['domain']); + $allowed_domains = implode('|', $allowed_domains); +?> + internal_alias_ { + priority = 10; + rcpt = ""; + from = "/^((?!.*@()).)*$/"; + apply "default" { + MAILCOW_INTERNAL_ALIAS = 9999.0; + } + symbols [ + "MAILCOW_INTERNAL_ALIAS" + ] + } + + } diff --git a/data/conf/rspamd/dynmaps/vars.inc.php b/data/conf/rspamd/dynmaps/vars.inc.php index d47e907985..79566b06fe 100644 --- a/data/conf/rspamd/dynmaps/vars.inc.php +++ b/data/conf/rspamd/dynmaps/vars.inc.php @@ -1,3 +1,6 @@ diff --git a/data/conf/rspamd/local.d/actions.conf b/data/conf/rspamd/local.d/actions.conf new file mode 100644 index 0000000000..3de63a54f4 --- /dev/null +++ b/data/conf/rspamd/local.d/actions.conf @@ -0,0 +1,3 @@ +reject = 15; +add_header = 8; +greylist = 7; diff --git a/data/conf/rspamd/local.d/antivirus.conf b/data/conf/rspamd/local.d/antivirus.conf index 157c3cb9ce..c8d31d1e3e 100644 --- a/data/conf/rspamd/local.d/antivirus.conf +++ b/data/conf/rspamd/local.d/antivirus.conf @@ -1,7 +1,11 @@ clamav { - attachments_only = true; + # Scan whole message + scan_mime_parts = false; + #scan_text_mime = true; + #scan_image_mime = true; symbol = "CLAM_VIRUS"; type = "clamav"; log_clean = true; servers = "clamd:3310"; + max_size = 20971520; } diff --git a/data/conf/rspamd/local.d/arc.conf b/data/conf/rspamd/local.d/arc.conf index e8d958710b..a857fc449d 100644 --- a/data/conf/rspamd/local.d/arc.conf +++ b/data/conf/rspamd/local.d/arc.conf @@ -1,25 +1,25 @@ # If false, messages with empty envelope from are not signed -allow_envfrom_empty = false; +allow_envfrom_empty = true; # If true, envelope/header domain mismatch is ignored -allow_hdrfrom_mismatch = false; +allow_hdrfrom_mismatch = true; # If true, multiple from headers are allowed (but only first is used) -allow_hdrfrom_multiple = true; +allow_hdrfrom_multiple = false; # If true, username does not need to contain matching domain allow_username_mismatch = false; # If false, messages from authenticated users are not selected for signing -auth_only = true; +sign_authenticated = false; # Default path to key, can include '$domain' and '$selector' variables path = "/data/dkim/keys/$domain.dkim"; # Default selector to use selector = "dkim"; # If false, messages from local networks are not selected for signing -sign_local = true; +sign_local = false; # Symbol to add when message is signed symbol = "ARC_SIGNED"; # Whether to fallback to global config try_fallback = true; # Domain to use for DKIM signing: can be "header" or "envelope" -use_domain = "envelope"; +use_domain = "recipient"; # Whether to normalise domains to eSLD use_esld = false; # Whether to get keys from Redis @@ -28,3 +28,5 @@ use_redis = true; key_prefix = "DKIM_PRIV_KEYS"; # Selector map selector_prefix = "DKIM_SELECTORS"; +sign_inbound = true; +use_domain_sign_inbound = "recipient"; diff --git a/data/conf/rspamd/local.d/composites.conf b/data/conf/rspamd/local.d/composites.conf index dd47be354d..4ce042aabd 100644 --- a/data/conf/rspamd/local.d/composites.conf +++ b/data/conf/rspamd/local.d/composites.conf @@ -1,12 +1,110 @@ MX_IMPLICIT { - expression = "MX_GOOD and MX_MISSING"; + expression = "MX_GOOD & MX_MISSING"; score = -0.01; } VIRUS_FOUND { expression = "CLAM_VIRUS & !MAILCOW_WHITE"; - score = 2000; + score = 2000.0; } -SPF_FAIL_NO_DKIM { - expression = "R_SPF_FAIL & R_DKIM_NA & !MAILCOW_WHITE"; - score = 10; +# Bad policy from free mail providers +FREEMAIL_POLICY_FAILURE { + expression = "FREEMAIL_FROM & !DMARC_POLICY_ALLOW & !MAILLIST & !WHITELISTED_FWD_HOST & -g+:policies"; + score = 16.0; +} +# Applies to freemail with undisclosed recipients +FREEMAIL_TO_UNDISC_RCPT { + expression = "FREEMAIL_FROM & ( MISSING_TO | R_UNDISC_RCPT | TO_EQ_FROM )"; + score = 5.0; +} +# Bad policy from non-whitelisted senders +# Remove SOGO_CONTACT symbol for fwd hosts and senders with broken policy +SOGO_CONTACT_EXCLUDE { + expression = "(-WHITELISTED_FWD_HOST | -g+:policies) & ^SOGO_CONTACT & !DMARC_POLICY_ALLOW"; +} +# Remove MAILCOW_WHITE symbol for senders with broken policy recieved not from fwd hosts +MAILCOW_WHITE_EXCLUDE { + expression = "^MAILCOW_WHITE & (-DMARC_POLICY_REJECT | -DMARC_POLICY_QUARANTINE | -R_SPF_PERMFAIL) & !WHITELISTED_FWD_HOST"; +} +# Spoofed header from and broken policy (excluding sieve host, rspamd host, whitelisted senders, authenticated senders and forward hosts) +SPOOFED_UNAUTH { + expression = "!MAILCOW_AUTH & !MAILCOW_WHITE & !RSPAMD_HOST & !SIEVE_HOST & MAILCOW_DOMAIN_HEADER_FROM & !WHITELISTED_FWD_HOST & -g+:policies"; + score = 50.0; +} +# Only apply to inbound unauthed and not whitelisted +OLEFY_MACRO { + expression = "!MAILCOW_AUTH & !MAILCOW_WHITE & OLETOOLS"; + score = 20.0; + policy = "remove_weight"; +} +# Applies to a content filter map +BAD_WORD_BAD_TLD { + expression = "FISHY_TLD & ( BAD_WORDS | BAD_WORDS_DE )"; + score = 10.0; +} +# Forged with bad policies and not fwd host, keep bad policy symbols +FORGED_W_BAD_POLICY { + expression = "( -g+:policies | -R_SPF_NA) & ( ~FROM_NEQ_ENVFROM | ~FORGED_SENDER ) & !WHITELISTED_FWD_HOST & !DMARC_POLICY_ALLOW"; + score = 3.0; +} +# Keep negative (good) scores for rbl, policies and hfilter, disable neural group +WL_FWD_HOST { + expression = "-WHITELISTED_FWD_HOST & (^g+:rbl | ^g+:policies | ^g+:hfilter | ^g:neural)"; +} +# Exclude X-Spam like flags from scoring from fwd and sieve hosts +UPSTREAM_CHECKS_EXCLUDE_FWD_HOST { + expression = "(-SIEVE_HOST | -WHITELISTED_FWD_HOST) & (^UNITEDINTERNET_SPAM | ^SPAM_FLAG | ^KLMS_SPAM | ^AOL_SPAM | ^MICROSOFT_SPAM)"; +} +# Remove fuzzy group from bounces +BOUNCE_FUZZY { + expression = "-BOUNCE & ^g+:fuzzy"; +} +# Remove bayes ham if fuzzy denied +FUZZY_HAM_MISMATCH { + expression = "( -FUZZY_DENIED | -MAILCOW_FUZZY_DENIED | -LOCAL_FUZZY_DENIED ) & ( ^BAYES_HAM | ^NEURAL_HAM_LONG | ^NEURAL_HAM_SHORT )"; +} +# Remove bayes spam if local fuzzy white +FUZZY_SPAM_MISMATCH { + expression = "( -LOCAL_FUZZY_WHITE ) & ( ^BAYES_SPAM | ^NEURAL_SPAM_LONG | ^NEURAL_SPAM_SHORT )"; +} +WL_FWD_HOST { + expression = "-WHITELISTED_FWD_HOST & (^g+:rbl | ^g+:policies | ^g+:hfilter | ^g:neural)"; +} +ENCRYPTED_CHAT { + expression = "CHAT_VERSION_HEADER & ENCRYPTED_PGP"; +} + +CLAMD_SPAM_FOUND { + expression = "CLAM_SECI_SPAM & !MAILCOW_WHITE"; + description = "Probably Spam, Securite Spam Flag set through ClamAV"; + score = 5; +} + +CLAMD_BAD_PDF { + expression = "CLAM_SECI_PDF & !MAILCOW_WHITE"; + description = "Bad PDF Found, Securite bad PDF Flag set through ClamAV"; + score = 8; +} + +CLAMD_BAD_JPG { + expression = "CLAM_SECI_JPG & !MAILCOW_WHITE"; + description = "Bad JPG Found, Securite bad JPG Flag set through ClamAV"; + score = 8; +} + +CLAMD_ASCII_MALWARE { + expression = "CLAM_SECI_ASCII & !MAILCOW_WHITE"; + description = "ASCII malware found, Securite ASCII malware Flag set through ClamAV"; + score = 8; +} + +CLAMD_HTML_MALWARE { + expression = "CLAM_SECI_HTML & !MAILCOW_WHITE"; + description = "HTML malware found, Securite HTML malware Flag set through ClamAV"; + score = 8; +} + +CLAMD_JS_MALWARE { + expression = "CLAM_SECI_JS & !MAILCOW_WHITE"; + description = "JS malware found, Securite JS malware Flag set through ClamAV"; + score = 8; } diff --git a/data/conf/rspamd/local.d/dkim_signing.conf b/data/conf/rspamd/local.d/dkim_signing.conf index b87f89c8be..4fac27fd91 100644 --- a/data/conf/rspamd/local.d/dkim_signing.conf +++ b/data/conf/rspamd/local.d/dkim_signing.conf @@ -1,13 +1,13 @@ # If false, messages with empty envelope from are not signed -allow_envfrom_empty = false; +allow_envfrom_empty = true; # If true, envelope/header domain mismatch is ignored -allow_hdrfrom_mismatch = false; +allow_hdrfrom_mismatch = true; # If true, multiple from headers are allowed (but only first is used) allow_hdrfrom_multiple = true; # If true, username does not need to contain matching domain allow_username_mismatch = true; # If false, messages from authenticated users are not selected for signing -auth_only = true; +sign_authenticated = true; # Default path to key, can include '$domain' and '$selector' variables path = "/data/dkim/keys/$domain.dkim"; # Default selector to use @@ -28,3 +28,8 @@ use_redis = true; key_prefix = "DKIM_PRIV_KEYS"; # Selector map selector_prefix = "DKIM_SELECTORS"; +# Sieve is in sign_networks only +# forwards are arc signed, rejects are dkim signed +sign_networks = "/etc/rspamd/custom/dovecot_trusted.map"; +use_domain_sign_networks = "header"; +sign_headers = "from:sender:reply-to:subject:date:message-id:to:cc:mime-version:content-type:content-transfer-encoding:content-language:resent-to:resent-cc:resent-from:resent-sender:resent-message-id:in-reply-to:references:list-id:list-help:list-owner:list-unsubscribe:list-subscribe:list-post:list-unsubscribe-post:disposition-notification-to:disposition-notification-options:original-recipient:openpgp:autocrypt"; diff --git a/data/conf/rspamd/local.d/fuzzy_check.conf b/data/conf/rspamd/local.d/fuzzy_check.conf index 775315e382..855e8d0e4a 100644 --- a/data/conf/rspamd/local.d/fuzzy_check.conf +++ b/data/conf/rspamd/local.d/fuzzy_check.conf @@ -27,4 +27,28 @@ rule "local" { } } - +rule "mailcow" { + # Fuzzy storage server list + servers = "fuzzy.mailcow.email:11445"; + # Default symbol for unknown flags + symbol = "MAILCOW_FUZZY_UNKNOWN"; + # Additional mime types to store/check + mime_types = ["application/*"]; + # Hash weight threshold for all maps + max_score = 100.0; + # Whether we can learn this storage + read_only = yes; + # Ignore unknown flags + skip_unknown = yes; + # Hash generation algorithm + algorithm = "mumhash"; + # Encrypt connection + encryption_key = "oa7xjgdr9u7w3hq1xbttas6brgau8qc17yi7ur5huaeq6paq8h4y"; + # Map flags to symbols + fuzzy_map = { + MAILCOW_FUZZY_DENIED { + max_score = 10.0; + flag = 11; + } + } +} diff --git a/data/conf/rspamd/local.d/fuzzy_group.conf b/data/conf/rspamd/local.d/fuzzy_group.conf index 6b3390c6a4..561ac4e11a 100644 --- a/data/conf/rspamd/local.d/fuzzy_group.conf +++ b/data/conf/rspamd/local.d/fuzzy_group.conf @@ -1,11 +1,17 @@ symbols = { "LOCAL_FUZZY_UNKNOWN" { - weight = 2.0; + weight = 0.1; } "LOCAL_FUZZY_DENIED" { - weight = 10.0; + weight = 15.0; + } + "MAILCOW_FUZZY_UNKNOWN" { + weight = 0.1; + } + "MAILCOW_FUZZY_DENIED" { + weight = 7.0; } "LOCAL_FUZZY_WHITE" { - weight = -3.4; + weight = -10.0; } } diff --git a/data/conf/rspamd/local.d/greylist.conf b/data/conf/rspamd/local.d/greylist.conf index ee0220d7b4..c43c907fb1 100644 --- a/data/conf/rspamd/local.d/greylist.conf +++ b/data/conf/rspamd/local.d/greylist.conf @@ -1 +1,4 @@ whitelisted_ip = "http://nginx:8081/forwardinghosts.php"; +ipv4_mask = 24; +ipv6_mask = 64; +message = "Greylisted, please try again later"; diff --git a/data/conf/rspamd/local.d/groups.conf b/data/conf/rspamd/local.d/groups.conf new file mode 100644 index 0000000000..f77d8a463f --- /dev/null +++ b/data/conf/rspamd/local.d/groups.conf @@ -0,0 +1,59 @@ +symbols { + "MAILCOW_AUTH" { + description = "mailcow authenticated"; + score = -20.0; + } + "CTYPE_MIXED_BOGUS" { + score = 0.0; + } + "BAD_REP_POLICIES" { + score = 2.0; + } + "BAD_HEADER" { + score = 10.0; + } + "BULK_HEADER" { + score = 4.0; + } + "ENCRYPTED_CHAT" { + score = -20.0; + } + "SOGO_CONTACT" { + score = -99.0; + } +} + +group "MX" { + "MX_INVALID" { + score = 0.5; + description = "No connectable MX"; + one_shot = true; + } + "MX_MISSING" { + score = 2.0; + description = "No MX record"; + one_shot = true; + } + "MX_GOOD" { + score = -0.01; + description = "MX was ok"; + one_shot = true; + } +} + +group "reputation" { + symbols = { + "IP_REPUTATION_HAM" { + weight = 1.0; + } + "IP_REPUTATION_SPAM" { + weight = 4.0; + } + "SENDER_REP_HAM" { + weight = 1.0; + } + "SENDER_REP_SPAM" { + weight = 2.0; + } + } +} diff --git a/data/conf/rspamd/local.d/headers_group.conf b/data/conf/rspamd/local.d/headers_group.conf new file mode 100644 index 0000000000..1df92b55a6 --- /dev/null +++ b/data/conf/rspamd/local.d/headers_group.conf @@ -0,0 +1,7 @@ +symbols = { + "R_MIXED_CHARSET" { + weight = 1.0; + description = "Mixed characters in a message"; + one_shot = true; + } +} diff --git a/data/conf/rspamd/local.d/hfilter_group.conf b/data/conf/rspamd/local.d/hfilter_group.conf new file mode 100644 index 0000000000..3c908c50e0 --- /dev/null +++ b/data/conf/rspamd/local.d/hfilter_group.conf @@ -0,0 +1,5 @@ +symbols = { + "HFILTER_HOSTNAME_UNKNOWN" { + score = 8.5; + } +} diff --git a/data/conf/rspamd/local.d/history_redis.conf b/data/conf/rspamd/local.d/history_redis.conf new file mode 100644 index 0000000000..68a59b0c24 --- /dev/null +++ b/data/conf/rspamd/local.d/history_redis.conf @@ -0,0 +1 @@ +nrows = 1000; diff --git a/data/conf/rspamd/local.d/ip_score.conf b/data/conf/rspamd/local.d/ip_score.conf deleted file mode 100644 index 1ea2c3cdfa..0000000000 --- a/data/conf/rspamd/local.d/ip_score.conf +++ /dev/null @@ -1,24 +0,0 @@ -# how each action is treated in scoring -actions { - reject = 1.0; - "add header" = 0.25; - "rewrite subject" = 0.25; - "no action" = 1.0; -} -# how each component is evaluated -scores { - asn = 0.5; - country = 0.1; - ipnet = 0.8; - ip = 1.0; -} -asn_prefix = "a:"; -country_prefix = "c:"; -hash = "ip_score"; -ipnet_prefix = "n:"; -lower_bound = 10; -metric = "default"; -max_score = 10; -min_score = -5; -score_divisor = 10; -symbol = "IP_SCORE"; diff --git a/data/conf/rspamd/local.d/metadata_exporter.conf b/data/conf/rspamd/local.d/metadata_exporter.conf index f1600708b5..cc85faacd3 100644 --- a/data/conf/rspamd/local.d/metadata_exporter.conf +++ b/data/conf/rspamd/local.d/metadata_exporter.conf @@ -1,10 +1,70 @@ rules { - QUARANTINE { - backend = "http"; - url = "http://nginx:9081/pipe.php"; - selector = "is_reject"; - formatter = "default"; - meta_headers = true; - } + QUARANTINE { + backend = "http"; + url = "http://nginx:9081/pipe.php"; + selector = "reject_no_global_bl"; + formatter = "multipart"; + } + RLINFO { + backend = "http"; + url = "http://nginx:9081/pipe_rl.php"; + selector = "ratelimited"; + formatter = "json"; + } + PUSHOVERMAIL { + backend = "http"; + url = "http://nginx:9081/pushover.php"; + selector = "mailcow_rcpt"; + formatter = "multipart"; + } +} + +custom_select { + mailcow_rcpt = <= 1 then -- Allow single zero compression too + table.insert(zero_runs, {start = run_start, length = run_length}) + end + in_run = false + end + end + end + + -- Don't forget the last run + if in_run and run_length >= 1 then + table.insert(zero_runs, {start = run_start, length = run_length}) + end + + -- Generate variant for each zero run compression + for _, run in ipairs(zero_runs) do + local parts = {} + + -- Before compression + for i = 1, run.start - 1 do + table.insert(parts, string.format('%x', segments[i])) + end + + -- The compression + if run.start == 1 then + table.insert(parts, '') + table.insert(parts, '') + elseif run.start + run.length - 1 == 8 then + table.insert(parts, '') + table.insert(parts, '') + else + table.insert(parts, '') + end + + -- After compression + for i = run.start + run.length, 8 do + table.insert(parts, string.format('%x', segments[i])) + end + + local compressed = table.concat(parts, ':'):gsub('::+', '::') + add_variant(compressed) + end + + return variants + end + local from_ip_string = tostring(ip) - ip_check_table = {from_ip_string} + local ip_check_table = {} + + -- Add all variants of the exact IP + for _, variant in ipairs(get_ipv6_variants(from_ip_string)) do + table.insert(ip_check_table, variant) + end local maxbits = 128 local minbits = 32 @@ -36,10 +318,18 @@ rspamd_config:register_symbol({ maxbits = 32 minbits = 8 end + + -- Add all CIDR notations with variants for i=maxbits,minbits,-1 do - local nip = ip:apply_mask(i):to_string() .. "/" .. i - table.insert(ip_check_table, nip) + local masked_ip = ip:apply_mask(i) + local cidr_base = masked_ip:to_string() + + for _, variant in ipairs(get_ipv6_variants(cidr_base)) do + local cidr = variant .. "/" .. i + table.insert(ip_check_table, cidr) + end end + local function keep_spam_cb(err, data) if err then rspamd_logger.infox(rspamd_config, "keep_spam query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err) @@ -47,12 +337,15 @@ rspamd_config:register_symbol({ else for k,v in pairs(data) do if (v and v ~= userdata and v == '1') then - rspamd_logger.infox(rspamd_config, "found ip in keep_spam map, setting pre-result", v) - task:set_pre_result('accept', 'IP matched with forward hosts') + rspamd_logger.infox(rspamd_config, "found ip %s (checked as: %s) in keep_spam map, setting pre-result accept", from_ip_string, ip_check_table[k]) + task:set_pre_result('accept', 'ip matched with forward hosts', 'keep_spam') + task:set_flag('no_stat') + return end end end end + table.insert(ip_check_table, 1, 'KEEP_SPAM') local redis_ret_user = rspamd_redis_make_request(task, redis_params, -- connect params @@ -69,48 +362,324 @@ rspamd_config:register_symbol({ priority = 19 }) +rspamd_config:register_symbol({ + name = 'TLS_HEADER', + type = 'postfilter', + callback = function(task) + local rspamd_logger = require "rspamd_logger" + local tls_tag = task:get_request_header('TLS-Version') + if type(tls_tag) == 'nil' then + task:set_milter_reply({ + add_headers = {['X-Last-TLS-Session-Version'] = 'None'} + }) + else + task:set_milter_reply({ + add_headers = {['X-Last-TLS-Session-Version'] = tostring(tls_tag)} + }) + end + end, + priority = 12 +}) + rspamd_config:register_symbol({ name = 'TAG_MOO', type = 'postfilter', + flags = 'ignore_passthrough', callback = function(task) local util = require("rspamd_util") local rspamd_logger = require "rspamd_logger" - + local redis_params = rspamd_parse_redis_server('taghandler') + local rspamd_http = require "rspamd_http" + local rcpts = task:get_recipients('smtp') + local lua_util = require "lua_util" local tagged_rcpt = task:get_symbol("TAGGED_RCPT") + + local function remove_moo_tag() + local moo_tag_header = task:get_header('X-Moo-Tag', false) + if moo_tag_header then + task:set_milter_reply({ + remove_headers = {['X-Moo-Tag'] = 0}, + }) + end + return true + end + + -- Check if we have exactly one recipient + if not (rcpts and #rcpts == 1) then + rspamd_logger.infox("TAG_MOO: not exactly one rcpt (%s), removing moo tag", rcpts and #rcpts or 0) + remove_moo_tag() + return + end + + local rcpt_addr = rcpts[1]['addr'] + local rcpt_user = rcpts[1]['user'] + local rcpt_domain = rcpts[1]['domain'] + + -- Check if recipient has a tag (contains '+') + local tag = nil + if tagged_rcpt ~= nil then + tag = tagged_rcpt + rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag) + end + + if not tag then + rspamd_logger.infox("TAG_MOO: no tag found in recipient %s, removing moo tag", rcpt_addr) + remove_moo_tag() + return + end + + -- Optional: Check if domain is a mailcow domain + -- When KEEP_SPAM is active, RCPT_MAILCOW_DOMAIN might not be set + -- If the mail is being delivered, we can assume it's valid local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN") + if not mailcow_domain then + rspamd_logger.infox("TAG_MOO: RCPT_MAILCOW_DOMAIN not set (possibly due to pre-result), proceeding anyway for domain %s", rcpt_domain) + end - if tagged_rcpt and tagged_rcpt[1].options and mailcow_domain then - local tag = tagged_rcpt[1].options[1] - rspamd_logger.infox("found tag: %s", tag) - local action = task:get_metric_action('default') - rspamd_logger.infox("metric action now: %s", action) + local action = task:get_metric_action('default') + rspamd_logger.infox("TAG_MOO: metric action: %s", action) - if action ~= 'no action' and action ~= 'greylist' then - rspamd_logger.infox("skipping tag handler for action: %s", action) - task:set_metric_action('default', action) - return true + -- Check if we have a pre-result (e.g., from KEEP_SPAM or POSTMASTER_HANDLER) + local allow_processing = false + + if task.has_pre_result then + local has_pre, pre_action = task:has_pre_result() + if has_pre then + rspamd_logger.infox("TAG_MOO: pre-result detected: %s", tostring(pre_action)) + if pre_action == 'accept' then + allow_processing = true + rspamd_logger.infox("TAG_MOO: pre-result is accept, will process") + end end + end + + -- Allow processing for mild actions or when we have pre-result accept + if not allow_processing and action ~= 'no action' and action ~= 'greylist' then + rspamd_logger.infox("TAG_MOO: skipping tag handler for action: %s", action) + remove_moo_tag() + return true + end - local wants_subject_tag = task:get_symbol("RCPT_WANTS_SUBJECT_TAG") - local wants_subfolder_tag = task:get_symbol("RCPT_WANTS_SUBFOLDER_TAG") + rspamd_logger.infox("TAG_MOO: processing allowed") - if wants_subject_tag then - rspamd_logger.infox("user wants subject modified for tagged mail") - local sbj = task:get_header('Subject') - new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?=' - task:set_milter_reply({ - remove_headers = {['Subject'] = 1}, - add_headers = {['Subject'] = new_sbj} + local function http_callback(err_message, code, body, headers) + if body ~= nil and body ~= "" then + rspamd_logger.infox(rspamd_config, "TAG_MOO: expanding rcpt to \"%s\"", body) + + local function tag_callback_subject(err, data) + if err or type(data) ~= 'string' or data == '' then + rspamd_logger.infox(rspamd_config, "TAG_MOO: subject tag handler rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying subfolder tag handler...", body, data, err) + + local function tag_callback_subfolder(err, data) + if err or type(data) ~= 'string' or data == '' then + rspamd_logger.infox(rspamd_config, "TAG_MOO: subfolder tag handler for rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\")", body, data, err) + remove_moo_tag() + else + rspamd_logger.infox("TAG_MOO: User wants subfolder tag, adding X-Moo-Tag header") + task:set_milter_reply({ + add_headers = {['X-Moo-Tag'] = 'YES'} + }) + end + end + + local redis_ret_subfolder = rspamd_redis_make_request(task, + redis_params, -- connect params + body, -- hash key + false, -- is write + tag_callback_subfolder, --callback + 'HGET', -- command + {'RCPT_WANTS_SUBFOLDER_TAG', body} -- arguments + ) + if not redis_ret_subfolder then + rspamd_logger.infox(rspamd_config, "TAG_MOO: cannot make request to load tag handler for rcpt") + remove_moo_tag() + end + + else + rspamd_logger.infox("TAG_MOO: user wants subject modified for tagged mail") + local sbj = task:get_header('Subject') or '' + local tag_value = tag[1] and tag[1].options and tag[1].options[1] or '' + new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag_value .. '] ' .. sbj)) .. '?=' + task:set_milter_reply({ + remove_headers = { + ['Subject'] = 1, + ['X-Moo-Tag'] = 0 + }, + add_headers = {['Subject'] = new_sbj} + }) + end + end + + local redis_ret_subject = rspamd_redis_make_request(task, + redis_params, -- connect params + body, -- hash key + false, -- is write + tag_callback_subject, --callback + 'HGET', -- command + {'RCPT_WANTS_SUBJECT_TAG', body} -- arguments + ) + if not redis_ret_subject then + rspamd_logger.infox(rspamd_config, "TAG_MOO: cannot make request to load tag handler for rcpt") + remove_moo_tag() + end + else + rspamd_logger.infox("TAG_MOO: alias expansion returned empty body") + remove_moo_tag() + end + end + + local rcpt_split = rspamd_str_split(rcpt_addr, '@') + if #rcpt_split == 2 then + if rcpt_split[1]:match('^postmaster') then + rspamd_logger.infox(rspamd_config, "TAG_MOO: not expanding postmaster alias") + remove_moo_tag() + else + rspamd_logger.infox("TAG_MOO: requesting alias expansion for %s", rcpt_addr) + rspamd_http.request({ + task=task, + url='http://nginx:8081/aliasexp.php', + body='', + callback=http_callback, + headers={Rcpt=rcpt_addr}, }) - elseif wants_subfolder_tag then - rspamd_logger.infox("Add X-Moo-Tag header") - task:set_milter_reply({ - add_headers = {['X-Moo-Tag'] = 'YES'} + end + else + rspamd_logger.infox("TAG_MOO: invalid rcpt format") + remove_moo_tag() + end + end, + priority = 19 +}) + +rspamd_config:register_symbol({ + name = 'BCC', + type = 'postfilter', + flags = 'ignore_passthrough', + callback = function(task) + local util = require("rspamd_util") + local rspamd_http = require "rspamd_http" + local rspamd_logger = require "rspamd_logger" + + local from_table = {} + local rcpt_table = {} + + if task:has_symbol('ENCRYPTED_CHAT') then + return -- stop + end + + local send_mail = function(task, bcc_dest) + local lua_smtp = require "lua_smtp" + local function sendmail_cb(ret, err) + if not ret then + rspamd_logger.errx(task, 'BCC SMTP ERROR: %s', err) + else + rspamd_logger.infox(rspamd_config, "BCC SMTP SUCCESS TO %s", bcc_dest) + end + end + if not bcc_dest then + return -- stop + end + -- dot stuff content before sending + local email_content = tostring(task:get_content()) + email_content = string.gsub(email_content, "\r\n%.", "\r\n..") + -- send mail + local from_smtp = task:get_from('smtp') + local from_addr = (from_smtp and from_smtp[1] and from_smtp[1].addr) or 'mailer-daemon@localhost' + lua_smtp.sendmail({ + task = task, + host = os.getenv("IPV4_NETWORK") .. '.253', + port = 591, + from = from_addr, + recipients = bcc_dest, + helo = 'bcc', + timeout = 20, + }, email_content, sendmail_cb) + end + + -- determine from + local from = task:get_from('smtp') + if from then + for _, a in ipairs(from) do + table.insert(from_table, a['addr']) -- add this rcpt to table + table.insert(from_table, '@' .. a['domain']) -- add this rcpts domain to table + end + else + return -- stop + end + + -- determine rcpts + local rcpts = task:get_recipients('smtp') + if rcpts then + for _, a in ipairs(rcpts) do + table.insert(rcpt_table, a['addr']) -- add this rcpt to table + table.insert(rcpt_table, '@' .. a['domain']) -- add this rcpts domain to table + end + else + return -- stop + end + + local action = task:get_metric_action('default') + rspamd_logger.infox("BCC: metric action: %s", action) + + -- Check for pre-result accept (e.g., from KEEP_SPAM) + local allow_bcc = false + if task.has_pre_result then + local has_pre, pre_action = task:has_pre_result() + if has_pre and pre_action == 'accept' then + allow_bcc = true + rspamd_logger.infox("BCC: pre-result accept detected, will send BCC") + end + end + + -- Allow BCC for mild actions or when we have pre-result accept + if not allow_bcc and action ~= 'no action' and action ~= 'add header' and action ~= 'rewrite subject' then + rspamd_logger.infox("BCC: skipping for action: %s", action) + return + end + + local function rcpt_callback(err_message, code, body, headers) + if err_message == nil and code == 201 and body ~= nil then + rspamd_logger.infox("BCC: sending BCC to %s for rcpt match", body) + send_mail(task, body) + end + end + + local function from_callback(err_message, code, body, headers) + if err_message == nil and code == 201 and body ~= nil then + rspamd_logger.infox("BCC: sending BCC to %s for from match", body) + send_mail(task, body) + end + end + + if rcpt_table then + for _,e in ipairs(rcpt_table) do + rspamd_logger.infox(rspamd_config, "BCC: checking bcc for rcpt address %s", e) + rspamd_http.request({ + task=task, + url='http://nginx:8081/bcc.php', + body='', + callback=rcpt_callback, + headers={Rcpt=e} }) end end + + if from_table then + for _,e in ipairs(from_table) do + rspamd_logger.infox(rspamd_config, "BCC: checking bcc for from address %s", e) + rspamd_http.request({ + task=task, + url='http://nginx:8081/bcc.php', + body='', + callback=from_callback, + headers={From=e} + }) + end + end + + -- Don't return true to avoid symbol being logged end, - priority = 11 + priority = 20 }) rspamd_config:register_symbol({ @@ -121,12 +690,18 @@ rspamd_config:register_symbol({ local redis_params = rspamd_parse_redis_server('dyn_rl') local rspamd_logger = require "rspamd_logger" local envfrom = task:get_from(1) + local envrcpt = task:get_recipients(1) or {} local uname = task:get_user() if not envfrom or not uname then return false end + local uname = uname:lower() + if #envrcpt == 1 and envrcpt[1].addr:lower() == uname then + return false + end + local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case local function redis_cb_user(err, data) @@ -174,6 +749,7 @@ rspamd_config:register_symbol({ end return true end, + flags = 'empty', priority = 20 }) @@ -182,9 +758,190 @@ rspamd_config:register_symbol({ type = 'postfilter', callback = function(task) local from = task:get_header('From') - if from and (string.find(from, 'monitoring-system@everycloudtech.us', 1, true) or from == 'watchdog@localhost') then + if from and (monitoring_hosts:get_key(from) or from == "watchdog@localhost") then task:set_flag('no_log') task:set_flag('no_stat') end end }) + +rspamd_config:register_symbol({ + name = 'MOO_FOOTER', + type = 'prefilter', + callback = function(task) + local cjson = require "cjson" + local lua_mime = require "lua_mime" + local lua_util = require "lua_util" + local rspamd_logger = require "rspamd_logger" + local rspamd_http = require "rspamd_http" + local envfrom = task:get_from(1) + local uname = task:get_user() + if not envfrom or not uname then + return false + end + local uname = uname:lower() + local env_from_domain = envfrom[1].domain:lower() + local env_from_addr = envfrom[1].addr:lower() + + -- determine newline type + local function newline(task) + local t = task:get_newlines_type() + + if t == 'cr' then + return '\r' + elseif t == 'lf' then + return '\n' + end + + return '\r\n' + end + -- retrieve footer + local function footer_cb(err_message, code, data, headers) + if err or type(data) ~= 'string' then + rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err) + else + + -- parse json string + local footer = cjson.decode(data) + if not footer then + rspamd_logger.infox(rspamd_config, "parsing domain wide footer for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err) + else + if footer and type(footer) == "table" and (footer.html and footer.html ~= "" or footer.plain and footer.plain ~= "") then + rspamd_logger.infox(rspamd_config, "found domain wide footer for user %s: html=%s, plain=%s, vars=%s", uname, footer.html, footer.plain, footer.vars) + + if footer.skip_replies ~= 0 then + in_reply_to = task:get_header_raw('in-reply-to') + if in_reply_to then + rspamd_logger.infox(rspamd_config, "mail is a reply - skip footer") + return + end + end + + local envfrom_mime = task:get_from(2) + local from_name = "" + if envfrom_mime and envfrom_mime[1].name then + from_name = envfrom_mime[1].name + elseif envfrom and envfrom[1].name then + from_name = envfrom[1].name + end + + -- default replacements + local replacements = { + auth_user = uname, + from_user = envfrom[1].user, + from_name = from_name, + from_addr = envfrom[1].addr, + from_domain = envfrom[1].domain:lower() + } + -- add custom mailbox attributes + if footer.vars and type(footer.vars) == "string" then + local footer_vars = cjson.decode(footer.vars) + + if type(footer_vars) == "table" then + for key, value in pairs(footer_vars) do + replacements[key] = value + end + end + end + if footer.html and footer.html ~= "" then + footer.html = lua_util.jinja_template(footer.html, replacements, true) + end + if footer.plain and footer.plain ~= "" then + footer.plain = lua_util.jinja_template(footer.plain, replacements, true) + end + + -- add footer + local out = {} + local rewrite = lua_mime.add_text_footer(task, footer.html, footer.plain) or {} + + local seen_cte + local newline_s = newline(task) + + local function rewrite_ct_cb(name, hdr) + if rewrite.need_rewrite_ct then + if name:lower() == 'content-type' then + -- include boundary if present + local boundary_part = rewrite.new_ct.boundary and + string.format('; boundary="%s"', rewrite.new_ct.boundary) or '' + local nct = string.format('%s: %s/%s; charset=utf-8%s', + 'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype, boundary_part) + out[#out + 1] = nct + -- update Content-Type header (include boundary if present) + task:set_milter_reply({ + remove_headers = {['Content-Type'] = 0}, + }) + task:set_milter_reply({ + add_headers = {['Content-Type'] = string.format('%s/%s; charset=utf-8%s', + rewrite.new_ct.type, rewrite.new_ct.subtype, boundary_part)} + }) + return + elseif name:lower() == 'content-transfer-encoding' then + out[#out + 1] = string.format('%s: %s', + 'Content-Transfer-Encoding', 'quoted-printable') + -- update Content-Transfer-Encoding header + task:set_milter_reply({ + remove_headers = {['Content-Transfer-Encoding'] = 0}, + }) + task:set_milter_reply({ + add_headers = {['Content-Transfer-Encoding'] = 'quoted-printable'} + }) + seen_cte = true + return + end + end + out[#out + 1] = hdr.raw:gsub('\r?\n?$', '') + end + + task:headers_foreach(rewrite_ct_cb, {full = true}) + + if not seen_cte and rewrite.need_rewrite_ct then + out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable') + end + + -- End of headers + out[#out + 1] = newline_s + + if rewrite.out then + for _,o in ipairs(rewrite.out) do + out[#out + 1] = o + end + else + out[#out + 1] = task:get_rawbody() + end + local out_parts = {} + for _,o in ipairs(out) do + if type(o) ~= 'table' then + out_parts[#out_parts + 1] = o + out_parts[#out_parts + 1] = newline_s + else + local removePrefix = "--\x0D\x0AContent-Type" + if string.lower(string.sub(tostring(o[1]), 1, string.len(removePrefix))) == string.lower(removePrefix) then + o[1] = string.sub(tostring(o[1]), string.len("--\x0D\x0A") + 1) + end + out_parts[#out_parts + 1] = o[1] + if o[2] then + out_parts[#out_parts + 1] = newline_s + end + end + end + task:set_message(out_parts) + else + rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\")", uname, data) + end + end + end + end + + -- fetch footer + rspamd_http.request({ + task=task, + url='http://nginx:8081/footer.php', + body='', + callback=footer_cb, + headers={Domain=env_from_domain,Username=uname,From=env_from_addr}, + }) + + return true + end, + priority = 1 +}) diff --git a/data/conf/rspamd/meta_exporter/pipe.php b/data/conf/rspamd/meta_exporter/pipe.php index 1e37ab5062..28b06d3d62 100644 --- a/data/conf/rspamd/meta_exporter/pipe.php +++ b/data/conf/rspamd/meta_exporter/pipe.php @@ -17,13 +17,14 @@ $pdo = new PDO($dsn, $database_user, $database_pass, $opt); } catch (PDOException $e) { - error_log("QUARANTINE: " . $e); + error_log("QUARANTINE: " . $e . PHP_EOL); http_response_code(501); exit; } // Init Redis $redis = new Redis(); $redis->connect('redis-mailcow', 6379); +$redis->auth(getenv("REDISPASS")); // Functions function parse_email($email) { @@ -31,40 +32,46 @@ function parse_email($email) { $a = strrpos($email, '@'); return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); } -if (!function_exists('getallheaders')) { - function getallheaders() { - if (!is_array($_SERVER)) { - return array(); - } - $headers = array(); - foreach ($_SERVER as $name => $value) { - if (substr($name, 0, 5) == 'HTTP_') { - $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; - } - } - return $headers; - } +// rspamd metadata_exporter (multipart formatter): +// - $_POST['metadata'] JSON with the rspamd metadata +// - $_FILES['message'] raw RFC822 message +if (empty($_POST['metadata']) || !isset($_FILES['message']) || $_FILES['message']['error'] !== UPLOAD_ERR_OK) { + error_log("QUARANTINE: missing multipart parts from rspamd" . PHP_EOL); + http_response_code(400); + exit; +} + +$meta = json_decode($_POST['metadata'], true); +if (!is_array($meta)) { + error_log("QUARANTINE: cannot decode metadata JSON" . PHP_EOL); + http_response_code(400); + exit; } -$raw_data_content = file_get_contents('php://input'); +$raw_data_content = file_get_contents($_FILES['message']['tmp_name']); $raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8"); -$headers = getallheaders(); +$raw_size = (int)$_FILES['message']['size']; -$qid = $headers['X-Rspamd-Qid']; -$score = $headers['X-Rspamd-Score']; -$rcpts = $headers['X-Rspamd-Rcpt']; -$user = $headers['X-Rspamd-User']; -$ip = $headers['X-Rspamd-Ip']; -$action = $headers['X-Rspamd-Action']; -$sender = $headers['X-Rspamd-From']; -$symbols = $headers['X-Rspamd-Symbols']; +$qid = $meta['qid'] ?? 'unknown'; +$subject = $meta['subject'] ?? ''; +$score = $meta['score'] ?? 0; +$rcpts = $meta['rcpt'] ?? array(); +$user = $meta['user'] ?? 'unknown'; +$ip = $meta['ip'] ?? 'unknown'; +$action = $meta['action'] ?? 'no action'; +$sender = $meta['from'] ?? ''; +$symbols = json_encode($meta['symbols'] ?? array()); +$fuzzy = json_encode(is_array($meta['fuzzy'] ?? null) ? $meta['fuzzy'] : array()); -$raw_size = (int)$_SERVER['CONTENT_LENGTH']; +if (empty($sender)) { + error_log("QUARANTINE: Unknown sender, assuming empty-env-from@localhost" . PHP_EOL); + $sender = 'empty-env-from@localhost'; +} try { $max_size = (int)$redis->Get('Q_MAX_SIZE'); if (($max_size * 1048576) < $raw_size) { - error_log(sprintf("QUARANTINE: Message too large: %d b exceeds %d b", $raw_size, ($max_size * 1048576))); + error_log(sprintf("QUARANTINE: Message too large: %d b exceeds %d b", $raw_size, ($max_size * 1048576)) . PHP_EOL); http_response_code(505); exit; } @@ -74,7 +81,7 @@ function getallheaders() { $retention_size = (int)$redis->Get('Q_RETENTION_SIZE'); } catch (RedisException $e) { - error_log("QUARANTINE: " . $e); + error_log("QUARANTINE: " . $e . PHP_EOL); http_response_code(504); exit; } @@ -82,10 +89,13 @@ function getallheaders() { $rcpt_final_mailboxes = array(); // Loop through all rcpts -foreach (json_decode($rcpts, true) as $rcpt) { +foreach ($rcpts as $rcpt) { + // Remove tag + $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); + // Break rcpt into local part and domain part $parsed_rcpt = parse_email($rcpt); - + // Skip if not a mailcow handled domain try { if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) { @@ -93,14 +103,14 @@ function getallheaders() { } } catch (RedisException $e) { - error_log("QUARANTINE: " . $e); + error_log("QUARANTINE: " . $e . PHP_EOL); http_response_code(504); exit; } // Skip if domain is excluded if (in_array($parsed_rcpt['domain'], $exclude_domains)) { - error_log(sprintf("QUARANTINE: Skipped domain %s", $parsed_rcpt['domain'])); + error_log(sprintf("QUARANTINE: Skipped domain %s", $parsed_rcpt['domain']) . PHP_EOL); continue; } @@ -127,6 +137,14 @@ function getallheaders() { )); $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; } + if (empty($gotos)) { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'"); + $stmt->execute(array(':rcpt' => $parsed_rcpt['domain'])); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; + if ($goto_branch) { + $gotos = $parsed_rcpt['local'] . '@' . $goto_branch; + } + } $gotos_array = explode(',', $gotos); $loop_c = 0; @@ -135,12 +153,12 @@ function getallheaders() { // Loop through all found gotos foreach ($gotos_array as $index => &$goto) { - error_log("QUARANTINE: quarantine pipe: query " . $goto . " as username from mailbox"); - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND `active`= '1';"); + error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL); + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');"); $stmt->execute(array(':goto' => $goto)); $username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; if (!empty($username)) { - error_log("QUARANTINE: quarantine pipe: mailbox found: " . $username); + error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL); // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate if (!in_array($username, $rcpt_final_mailboxes)) { $rcpt_final_mailboxes[] = $username; @@ -149,14 +167,24 @@ function getallheaders() { else { $parsed_goto = parse_email($goto); if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { - error_log("QUARANTINE:" . $goto . " is not a mailcow handled mailbox or alias address"); + error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL); } else { $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); $stmt->execute(array(':goto' => $goto)); $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - error_log("QUARANTINE: quarantine pipe: goto address " . $goto . " is a alias branch for " . $goto_branch); - $goto_branch_array = explode(',', $goto_branch); + if ($goto_branch) { + error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); + $goto_branch_array = explode(',', $goto_branch); + } else { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'"); + $stmt->execute(array(':domain' => $parsed_goto['domain'])); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; + if ($goto_branch) { + error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL); + $goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch); + } + } } } // goto item was processed, unset @@ -175,32 +203,37 @@ function getallheaders() { // Force exit if loop cannot be solved // Postfix does not allow for alias loops, so this should never happen. $loop_c++; - error_log("QUARANTINE: quarantine pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array)); + error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL); } } catch (PDOException $e) { - error_log("QUARANTINE: " . $e->getMessage()); + error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL); http_response_code(502); exit; } } -foreach ($rcpt_final_mailboxes as $rcpt) { - error_log("QUARANTINE: quarantine pipe: processing quarantine message for rcpt " . $rcpt); +foreach ($rcpt_final_mailboxes as $rcpt_final) { + error_log("QUARANTINE: quarantine pipe: processing quarantine message for rcpt " . $rcpt_final . PHP_EOL); try { - $stmt = $pdo->prepare("INSERT INTO `quarantine` (`qid`, `score`, `sender`, `rcpt`, `symbols`, `user`, `ip`, `msg`, `action`) - VALUES (:qid, :score, :sender, :rcpt, :symbols, :user, :ip, :msg, :action)"); + $stmt = $pdo->prepare("INSERT INTO `quarantine` (`qid`, `subject`, `score`, `sender`, `rcpt`, `symbols`, `user`, `ip`, `msg`, `action`, `fuzzy_hashes`) + VALUES (:qid, :subject, :score, :sender, :rcpt, :symbols, :user, :ip, :msg, :action, :fuzzy_hashes)"); $stmt->execute(array( ':qid' => $qid, + ':subject' => $subject, ':score' => $score, ':sender' => $sender, - ':rcpt' => $rcpt, + ':rcpt' => $rcpt_final, ':symbols' => $symbols, ':user' => $user, ':ip' => $ip, ':msg' => $raw_data, - ':action' => $action + ':action' => $action, + ':fuzzy_hashes' => $fuzzy )); + $lastId = $pdo->lastInsertId(); + $stmt_update = $pdo->prepare("UPDATE `quarantine` SET `qhash` = SHA2(CONCAT(`id`, `qid`), 256) WHERE `id` = :id"); + $stmt_update->execute(array(':id' => $lastId)); $stmt = $pdo->prepare('DELETE FROM `quarantine` WHERE `rcpt` = :rcpt AND `id` NOT IN ( SELECT `id` FROM ( @@ -209,16 +242,16 @@ function getallheaders() { WHERE `rcpt` = :rcpt2 ORDER BY id DESC LIMIT :retention_size - ) x + ) x );'); $stmt->execute(array( - ':rcpt' => $rcpt, - ':rcpt2' => $rcpt, + ':rcpt' => $rcpt_final, + ':rcpt2' => $rcpt_final, ':retention_size' => $retention_size )); } catch (PDOException $e) { - error_log("QUARANTINE: " . $e->getMessage()); + error_log("QUARANTINE: " . $e->getMessage() . PHP_EOL); http_response_code(503); exit; } diff --git a/data/conf/rspamd/meta_exporter/pipe_rl.php b/data/conf/rspamd/meta_exporter/pipe_rl.php new file mode 100644 index 0000000000..f9a21caf79 --- /dev/null +++ b/data/conf/rspamd/meta_exporter/pipe_rl.php @@ -0,0 +1,49 @@ +connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT')); + } + else { + $redis->connect('redis-mailcow', 6379); + } + $redis->auth(getenv("REDISPASS")); +} +catch (Exception $e) { + exit; +} + +$raw_data_content = file_get_contents('php://input'); +$raw_data_decoded = json_decode($raw_data_content, true); + +$data['time'] = time(); +$data['rcpt'] = implode(', ', $raw_data_decoded['rcpt']); +$data['from'] = $raw_data_decoded['from']; +$data['user'] = $raw_data_decoded['user']; +$symbol_rl_key = array_search('RATELIMITED', array_column($raw_data_decoded['symbols'], 'name')); +$data['rl_info'] = implode($raw_data_decoded['symbols'][$symbol_rl_key]['options']); +preg_match('/(.+)\((.+)\)/i', $data['rl_info'], $rl_matches); +if (!empty($rl_matches[1]) && !empty($rl_matches[2])) { + $data['rl_name'] = $rl_matches[1]; + $data['rl_hash'] = $rl_matches[2]; +} +else { + $data['rl_name'] = 'err'; + $data['rl_hash'] = 'err'; +} +$data['qid'] = $raw_data_decoded['qid']; +$data['ip'] = $raw_data_decoded['ip']; +$data['message_id'] = $raw_data_decoded['message_id']; +$data['header_subject'] = implode(' ', $raw_data_decoded['header_subject']); +$data['header_from'] = implode(', ', $raw_data_decoded['header_from']); + +$redis->lpush('RL_LOG', json_encode($data)); +exit; + diff --git a/data/conf/rspamd/meta_exporter/pushover.php b/data/conf/rspamd/meta_exporter/pushover.php new file mode 100644 index 0000000000..188d0a57bc --- /dev/null +++ b/data/conf/rspamd/meta_exporter/pushover.php @@ -0,0 +1,272 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); +} +catch (PDOException $e) { + error_log("NOTIFY: " . $e . PHP_EOL); + http_response_code(501); + exit; +} +// Init Redis +$redis = new Redis(); +$redis->connect('redis-mailcow', 6379); +$redis->auth(getenv("REDISPASS")); + +// Functions +function parse_email($email) { + if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; + $a = strrpos($email, '@'); + return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); +} +// rspamd metadata_exporter (multipart formatter): metadata JSON arrives as $_POST['metadata']. +if (empty($_POST['metadata'])) { + error_log("NOTIFY: missing metadata part from rspamd" . PHP_EOL); + http_response_code(400); + exit; +} + +$meta = json_decode($_POST['metadata'], true); +if (!is_array($meta)) { + error_log("NOTIFY: cannot decode metadata JSON" . PHP_EOL); + http_response_code(400); + exit; +} + +$qid = $meta['qid'] ?? 'unknown'; +$rcpts = $meta['rcpt'] ?? array(); +$sender = $meta['from'] ?? ''; +$ip = $meta['ip'] ?? 'unknown'; +$subject = $meta['subject'] ?? ''; +$messageid= $meta['message_id'] ?? ''; +$priority = 0; + +$symbols_array = $meta['symbols'] ?? array(); +if (is_array($symbols_array)) { + foreach ($symbols_array as $symbol) { + if (($symbol['name'] ?? null) == 'HAS_X_PRIO_ONE') { + $priority = 1; + break; + } + } +} + +$sender_address = $meta['header_from'][0] ?? ''; +$sender_name = '-'; +if (preg_match('/(?.*?)<(?
.*?)>/i', $sender_address, $matches)) { + $sender_address = $matches['address']; + $sender_name = trim($matches['name'], '"\' '); +} + +$to_address = $meta['header_to'][0] ?? ''; +$to_name = '-'; +if (preg_match('/(?.*?)<(?
.*?)>/i', $to_address, $matches)) { + $to_address = $matches['address']; + $to_name = trim($matches['name'], '"\' '); +} + +$rcpt_final_mailboxes = array(); + +// Loop through all rcpts +foreach ($rcpts as $rcpt) { + // Remove tag + $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); + + // Break rcpt into local part and domain part + $parsed_rcpt = parse_email($rcpt); + + // Skip if not a mailcow handled domain + try { + if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) { + continue; + } + } + catch (RedisException $e) { + error_log("NOTIFY: " . $e . PHP_EOL); + http_response_code(504); + exit; + } + + // Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases + // + // rcpt + // | + // mailbox <-- goto ---> alias1, alias2, mailbox2 + // | | + // mailbox3 | + // | + // alias3 ---> mailbox4 + // + try { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); + $stmt->execute(array( + ':rcpt' => $rcpt + )); + $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + if (empty($gotos)) { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); + $stmt->execute(array( + ':rcpt' => '@' . $parsed_rcpt['domain'] + )); + $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + } + if (empty($gotos)) { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'"); + $stmt->execute(array(':rcpt' => $parsed_rcpt['domain'])); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; + if ($goto_branch) { + $gotos = $parsed_rcpt['local'] . '@' . $goto_branch; + } + } + $gotos_array = explode(',', $gotos); + + $loop_c = 0; + + while (count($gotos_array) != 0 && $loop_c <= 20) { + + // Loop through all found gotos + foreach ($gotos_array as $index => &$goto) { + error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL); + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');"); + $stmt->execute(array(':goto' => $goto)); + $username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; + if (!empty($username)) { + error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL); + // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate + if (!in_array($username, $rcpt_final_mailboxes)) { + $rcpt_final_mailboxes[] = $username; + } + } + else { + $parsed_goto = parse_email($goto); + if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { + error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL); + } + else { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); + $stmt->execute(array(':goto' => $goto)); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + if ($goto_branch) { + error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); + $goto_branch_array = explode(',', $goto_branch); + } else { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'"); + $stmt->execute(array(':domain' => $parsed_goto['domain'])); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; + if ($goto_branch) { + error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL); + $goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch); + } + } + } + } + // goto item was processed, unset + unset($gotos_array[$index]); + } + + // Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array + if (!empty($goto_branch_array)) { + $gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array)); + unset($goto_branch_array); + } + + // Reindex array + $gotos_array = array_values($gotos_array); + + // Force exit if loop cannot be solved + // Postfix does not allow for alias loops, so this should never happen. + $loop_c++; + error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL); + } + } + catch (PDOException $e) { + error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL); + http_response_code(502); + exit; + } +} + + +foreach ($rcpt_final_mailboxes as $rcpt_final) { + error_log("NOTIFY: pushover pipe: processing pushover message for rcpt " . $rcpt_final . PHP_EOL); + $stmt = $pdo->prepare("SELECT * FROM `pushover` + WHERE `username` = :username AND `active` = '1'"); + $stmt->execute(array( + ':username' => $rcpt_final + )); + $api_data = $stmt->fetch(PDO::FETCH_ASSOC); + if (isset($api_data['key']) && isset($api_data['token'])) { + $title = (!empty($api_data['title'])) ? $api_data['title'] : 'Mail'; + $text = (!empty($api_data['text'])) ? $api_data['text'] : 'You\'ve got mail ๐Ÿ“ง'; + $attributes = json_decode($api_data['attributes'], true); + $senders = explode(',', $api_data['senders']); + $senders = array_filter($senders); + $senders_regex = $api_data['senders_regex']; + $sender_validated = false; + if (empty($senders) && empty($senders_regex)) { + $sender_validated = true; + } + else { + if (!empty($senders)) { + if (in_array($sender, $senders)) { + $sender_validated = true; + } + } + if (!empty($senders_regex) && $sender_validated !== true) { + if (preg_match($senders_regex, $sender)) { + $sender_validated = true; + } + } + } + if ($sender_validated === false) { + error_log("NOTIFY: pushover pipe: skipping unwanted sender " . $sender); + continue; + } + if ($attributes['only_x_prio'] == "1" && $priority == 0) { + error_log("NOTIFY: pushover pipe: mail has no X-Priority: 1 header, skipping"); + continue; + } + $post_fields = array( + "token" => $api_data['token'], + "user" => $api_data['key'], + "title" => sprintf("%s", str_replace( + array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}'), + array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid), $title) + ), + "priority" => $priority, + "message" => sprintf("%s", str_replace( + array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}', '\n'), + array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid, PHP_EOL), $text) + ), + "sound" => $attributes['sound'] ?? "pushover" + ); + if ($attributes['evaluate_x_prio'] == "1" && $priority == 1) { + $post_fields['expire'] = 600; + $post_fields['retry'] = 120; + $post_fields['priority'] = 2; + } + curl_setopt_array($ch = curl_init(), array( + CURLOPT_URL => "https://api.pushover.net/1/messages.json", + CURLOPT_POSTFIELDS => $post_fields, + CURLOPT_SAFE_UPLOAD => true, + CURLOPT_RETURNTRANSFER => true, + )); + $result = curl_exec($ch); + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + error_log("NOTIFY: result: " . $httpcode . PHP_EOL); + } +} diff --git a/data/conf/rspamd/meta_exporter/vars.inc.php b/data/conf/rspamd/meta_exporter/vars.inc.php index d47e907985..79566b06fe 100644 --- a/data/conf/rspamd/meta_exporter/vars.inc.php +++ b/data/conf/rspamd/meta_exporter/vars.inc.php @@ -1,3 +1,6 @@ diff --git a/data/conf/rspamd/override.d/logging.inc b/data/conf/rspamd/override.d/logging.inc index 23a9f3cf64..64d4064db3 100644 --- a/data/conf/rspamd/override.d/logging.inc +++ b/data/conf/rspamd/override.d/logging.inc @@ -1,3 +1,4 @@ +level = "silent"; type = "console"; systemd = false; .include "$CONFDIR/logging.inc" diff --git a/data/conf/rspamd/override.d/ratelimit.conf b/data/conf/rspamd/override.d/ratelimit.conf index 3150d0e539..33791499d5 100644 --- a/data/conf/rspamd/override.d/ratelimit.conf +++ b/data/conf/rspamd/override.d/ratelimit.conf @@ -1,11 +1,4 @@ -rates { - # Format: "1 / 1h" or "20 / 1m" etc. - global ratelimits are disabled by default - to = "100 / 1s"; - to_ip = "100 / 1s"; - to_ip_from = "100 / 1s"; - bounce_to = "100 / 1s"; - bounce_to_ip = "100 / 1s"; -} whitelisted_rcpts = "postmaster,mailer-daemon"; -max_rcpt = 5; +max_rcpt = 25; custom_keywords = "/etc/rspamd/lua/ratelimit.lua"; +info_symbol = "RATELIMITED"; diff --git a/data/conf/rspamd/override.d/worker-controller-password.inc b/data/conf/rspamd/override.d/worker-controller-password.inc deleted file mode 100644 index 9a5984d131..0000000000 --- a/data/conf/rspamd/override.d/worker-controller-password.inc +++ /dev/null @@ -1 +0,0 @@ -# Placeholder diff --git a/data/conf/rspamd/override.d/worker-controller.inc b/data/conf/rspamd/override.d/worker-controller.inc index 4d4ffe6f93..8c929b19fd 100644 --- a/data/conf/rspamd/override.d/worker-controller.inc +++ b/data/conf/rspamd/override.d/worker-controller.inc @@ -4,4 +4,4 @@ secure_ip = "127.0.0.1"; secure_ip = "::1"; bind_socket = "/var/lib/rspamd/rspamd.sock mode=0666 owner=nobody"; .include(try=true; priority=10) "$CONFDIR/override.d/worker-controller-password.inc" -.include(try=true; priority=20) "$CONFDIR/override.d/worker-controller.custom.inc" +.include(try=true; priority=30) "$CONFDIR/override.d/worker-controller.custom.inc" diff --git a/data/conf/rspamd/override.d/worker-fuzzy.inc b/data/conf/rspamd/override.d/worker-fuzzy.inc new file mode 100644 index 0000000000..291e6150f7 --- /dev/null +++ b/data/conf/rspamd/override.d/worker-fuzzy.inc @@ -0,0 +1,12 @@ +# Socket to listen on (UDP and TCP from rspamd 1.3) +bind_socket = "*:11445"; +allow_update = ["127.0.0.1", "::1"]; +# Number of processes to serve this storage (useful for read scaling) +count = 1; +# Backend ("sqlite" or "redis" - default "sqlite") +backend = "redis"; +# Hashes storage time (3 months) +expire = 90d; +# Synchronize updates to the storage each minute +sync = 1min; + diff --git a/data/conf/rspamd/override.d/worker-normal.inc b/data/conf/rspamd/override.d/worker-normal.inc index a7ab4baf11..d2067571b7 100644 --- a/data/conf/rspamd/override.d/worker-normal.inc +++ b/data/conf/rspamd/override.d/worker-normal.inc @@ -1,3 +1,4 @@ bind_socket = "*:11333"; -task_timeout = 12s; -.include(try=true; priority=20) "$CONFDIR/override.d/worker-normal.custom.inc" +task_timeout = 25s; +count = 1; +.include(try=true; priority=30) "$CONFDIR/override.d/worker-normal.custom.inc" diff --git a/data/conf/rspamd/override.d/worker-proxy.inc b/data/conf/rspamd/override.d/worker-proxy.inc index 0df926a76d..9eb47750d5 100644 --- a/data/conf/rspamd/override.d/worker-proxy.inc +++ b/data/conf/rspamd/override.d/worker-proxy.inc @@ -1,8 +1,9 @@ bind_socket = "rspamd:9900"; milter = true; -upstream { +upstream "local" { name = "localhost"; default = true; hosts = "rspamd:11333" } -.include(try=true; priority=20) "$CONFDIR/override.d/worker-proxy.custom.inc" +reject_message = "This message does not meet our delivery requirements"; +.include(try=true; priority=30) "$CONFDIR/override.d/worker-proxy.custom.inc" diff --git a/data/conf/rspamd/plugins.d/README.md b/data/conf/rspamd/plugins.d/README.md new file mode 100644 index 0000000000..1516cf2d1c --- /dev/null +++ b/data/conf/rspamd/plugins.d/README.md @@ -0,0 +1 @@ +This is where you should copy any rspamd custom module diff --git a/data/conf/rspamd/rspamd.conf.local b/data/conf/rspamd/rspamd.conf.local new file mode 100644 index 0000000000..9f2f8f1de7 --- /dev/null +++ b/data/conf/rspamd/rspamd.conf.local @@ -0,0 +1 @@ +# rspamd.conf.local diff --git a/data/conf/rspamd/rspamd.conf.override b/data/conf/rspamd/rspamd.conf.override new file mode 100644 index 0000000000..d033e8e24a --- /dev/null +++ b/data/conf/rspamd/rspamd.conf.override @@ -0,0 +1,2 @@ +# rspamd.conf.override + diff --git a/data/conf/sogo/custom-favicon.ico b/data/conf/sogo/custom-favicon.ico new file mode 100644 index 0000000000..4d5cb322fc Binary files /dev/null and b/data/conf/sogo/custom-favicon.ico differ diff --git a/data/conf/sogo/custom-fulllogo.png b/data/conf/sogo/custom-fulllogo.png new file mode 100644 index 0000000000..f5d3a75562 Binary files /dev/null and b/data/conf/sogo/custom-fulllogo.png differ diff --git a/data/conf/sogo/custom-fulllogo.svg b/data/conf/sogo/custom-fulllogo.svg new file mode 100644 index 0000000000..98ff2fc3b8 --- /dev/null +++ b/data/conf/sogo/custom-fulllogo.svg @@ -0,0 +1,44 @@ + + + +]> + + + diff --git a/data/conf/sogo/custom-shortlogo.svg b/data/conf/sogo/custom-shortlogo.svg new file mode 100644 index 0000000000..b5adf534c4 --- /dev/null +++ b/data/conf/sogo/custom-shortlogo.svg @@ -0,0 +1,16 @@ + + + +]> + + + diff --git a/data/conf/sogo/custom-sogo.js b/data/conf/sogo/custom-sogo.js new file mode 100644 index 0000000000..d3b90b0856 --- /dev/null +++ b/data/conf/sogo/custom-sogo.js @@ -0,0 +1,26 @@ +// redirect to mailcow login form +document.addEventListener('DOMContentLoaded', function () { + var loginForm = document.forms.namedItem("loginForm"); + if (loginForm) { + window.location.href = '/user'; + } +}); +// logout function +function mc_logout() { + fetch("/", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: "logout=1" + }).then(() => window.location.href = '/'); +} + +// Custom SOGo JS + +// Change the visible font-size in the editor, this does not change the font of a html message by default +CKEDITOR.addCss("body {font-size: 16px !important}"); + +// Enable scayt by default +//CKEDITOR.config.scayt_autoStartup = true; + diff --git a/data/conf/sogo/custom-theme.js b/data/conf/sogo/custom-theme.js new file mode 100644 index 0000000000..0df50677c0 --- /dev/null +++ b/data/conf/sogo/custom-theme.js @@ -0,0 +1,36 @@ +/* EXAMPLE - EXAMPLE - EXAMPLE - EXAMPLE - EXAMPLE - EXAMPLE - EXAMPLE +(function() { + 'use strict'; + angular.module('SOGo.Common') + .config(configure) + + configure.$inject = ['$mdThemingProvider']; + function configure($mdThemingProvider) { + var greyMap = $mdThemingProvider.extendPalette('grey', { + '200': 'F5F5F5', + '300': 'E5E5E5', + '1000': '4C566A' + }); + var greenCow = $mdThemingProvider.extendPalette('green', { + '600': 'E5E5E5' + }); + $mdThemingProvider.definePalette('frost-grey', greyMap); + $mdThemingProvider.definePalette('green-cow', greenCow); + $mdThemingProvider.theme('default') + .primaryPalette('green-cow', { + 'default': '400', + 'hue-1': '400', + 'hue-2': '600', + 'hue-3': 'A700' + }) + .accentPalette('green', { + 'default': '600', + 'hue-1': '300', + 'hue-2': '300', + 'hue-3': 'A700' + }) + .backgroundPalette('frost-grey'); + $mdThemingProvider.generateThemesOnDemand(false); + } +})(); + */ \ No newline at end of file diff --git a/data/conf/sogo/plist_ldap.sh b/data/conf/sogo/plist_ldap.sh new file mode 100755 index 0000000000..1911cd18c3 --- /dev/null +++ b/data/conf/sogo/plist_ldap.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +domain="$1" +gal_status="$2" + +echo " + " diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index b5979c826d..6854569f51 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -11,10 +11,26 @@ SOGoDraftsFolderName = "Drafts"; SOGoJunkFolderName= "Junk"; SOGoMailDomain = "sogo.local"; - SOGoEnableEMailAlarms = NO; + SOGoEnableEMailAlarms = YES; + SOGoMailHideInlineAttachments = YES; SOGoFoldersSendEMailNotifications = YES; SOGoForwardEnabled = YES; - SOGoUIAdditionalJSFiles = (js/theme-blue.js); + + // Added with SOGo 5.12 - Allows users to cleanup there maildirectories by deleting mails oder than X + SOGoEnableMailCleaning = YES; + + // Fixes "MODIFICATION_FAILED" error (HTTP 412) in Clients when accepting invitations from external services + SOGoDisableOrganizerEventCheck = YES; + + // Option to set Users as admin to globally manage calendar permissions etc. Disabled by default + // SOGoSuperUsernames = ("moo@example.com"); + + SOGoUIAdditionalJSFiles = ( + js/theme.js, + js/custom-sogo.js + ); + + SOGoEnablePublicAccess = YES; // Multi-domain setup // Domains are isolated, you can define visibility options here. @@ -25,14 +41,13 @@ // (domain3.tld, domain2.tld) // ); - SOGoIMAPServer = "imap://dovecot:143/?tls=YES"; - SOGoSieveServer = "sieve://dovecot:4190/?tls=YES"; - SOGoSMTPServer = "postfix:588"; + // self-signed is not trusted anymore WOPort = "0.0.0.0:20000"; SOGoMemcachedHost = "memcached"; SOGoLanguage = English; SOGoMailAuxiliaryUserAccountsEnabled = YES; + // SOGoCreateIdentitiesDisabled = NO; SOGoMailCustomFromEnabled = YES; SOGoMailingMechanism = smtp; SOGoSMTPAuthenticationType = plain; @@ -49,8 +64,8 @@ // This should do the trick for Outlook 2016 SOGoMaximumSyncResponseSize = 512; - WOWatchDogRequestTimeout = 20; - WOListenQueueSize = 300; + WOWatchDogRequestTimeout = 30; + WOListenQueueSize = 16; WONoDetach = YES; SOGoIMAPAclConformsToIMAPExt = Yes; @@ -58,7 +73,7 @@ SOGoFirstDayOfWeek = "1"; SOGoSieveFolderEncoding = "UTF-8"; - SOGoPasswordChangeEnabled = YES; + SOGoPasswordChangeEnabled = NO; SOGoSentFolderName = "Sent"; SOGoMailShowSubscribedFoldersOnly = NO; NGImap4ConnectionStringSeparator = "/"; @@ -71,11 +86,21 @@ SOGoMaximumFailedLoginInterval = 900; SOGoFailedLoginBlockInterval = 900; + // Enable SOGo URL Description for GDPR compliance, this may cause some issues with calendars and contacts. Also uncomment the encryption key below to use it. + //SOGoURLEncryptionEnabled = NO; + + // Set a 16 character encryption key for SOGo URL Description, change this to your own value + //SOGoURLPathEncryptionKey = "SOGoSuperSecret0"; + + GCSChannelCollectionTimer = 60; + GCSChannelExpireAge = 60; + MySQL4Encoding = "utf8mb4"; //SOGoDebugRequests = YES; //SoDebugBaseURL = YES; //ImapDebugEnabled = YES; //SOGoEASDebugEnabled = YES; + SOGoEASSearchInBody = YES; //LDAPDebugEnabled = YES; //PGDebugEnabled = YES; //MySQL4DebugEnabled = YES; diff --git a/data/conf/unbound/unbound.conf b/data/conf/unbound/unbound.conf index af725f0fc1..27110c0496 100644 --- a/data/conf/unbound/unbound.conf +++ b/data/conf/unbound/unbound.conf @@ -24,17 +24,22 @@ server: #private-address: 169.254.0.0/16 #private-address: fc00::/7 #private-address: fe80::/10 + # cache-min-ttl needs to be less or equal to cache-max-negative-ttl + cache-min-ttl: 5 + cache-max-negative-ttl: 60 root-hints: "/etc/unbound/root.hints" hide-identity: yes hide-version: yes max-udp-size: 4096 msg-buffer-size: 65552 + unwanted-reply-threshold: 10000 + ipsecmod-enabled: no remote-control: - control-enable: yes - control-interface: 127.0.0.1 - control-port: 8953 - server-key-file: "/etc/unbound/unbound_server.key" - server-cert-file: "/etc/unbound/unbound_server.pem" - control-key-file: "/etc/unbound/unbound_control.key" - control-cert-file: "/etc/unbound/unbound_control.pem" + control-enable: yes + control-interface: 127.0.0.1 + control-port: 8953 + server-key-file: "/etc/unbound/unbound_server.key" + server-cert-file: "/etc/unbound/unbound_server.pem" + control-key-file: "/etc/unbound/unbound_control.key" + control-cert-file: "/etc/unbound/unbound_control.pem" diff --git a/data/hooks/README.md b/data/hooks/README.md new file mode 100644 index 0000000000..d0996a18fa --- /dev/null +++ b/data/hooks/README.md @@ -0,0 +1,3 @@ +Place executable scripts in each directory to run a hook at the end of a docker-entrypoint.sh script. + +Only images with a docker-entrypoint are covered. diff --git a/data/hooks/dovecot/.gitkeep b/data/hooks/dovecot/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/hooks/phpfpm/.gitkeep b/data/hooks/phpfpm/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/hooks/postfix/.gitkeep b/data/hooks/postfix/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/hooks/rspamd/.gitkeep b/data/hooks/rspamd/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/hooks/sogo/.gitkeep b/data/hooks/sogo/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/hooks/unbound/.gitkeep b/data/hooks/unbound/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/web/_rspamderror.php b/data/web/_rspamderror.php new file mode 100644 index 0000000000..4976e0b5e4 --- /dev/null +++ b/data/web/_rspamderror.php @@ -0,0 +1,18 @@ +connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT')); + } + else { + $redis->connect('redis-mailcow', 6379); + } + $redis->auth(getenv("REDISPASS")); +} +catch (Exception $e) { + exit; +} +header('Content-Type: application/json'); +echo '{"error":"Unauthorized"}'; +error_log("Rspamd UI: Invalid password by " . $_SERVER['REMOTE_ADDR']); +$redis->publish("F2B_CHANNEL", "Rspamd UI: Invalid password by " . $_SERVER['REMOTE_ADDR']); diff --git a/data/web/_status.502.html b/data/web/_status.502.html new file mode 100644 index 0000000000..c8a9b70220 --- /dev/null +++ b/data/web/_status.502.html @@ -0,0 +1,25 @@ + + + + Preparing + + + +

What is happening?

+

Please do not stop the stack while we are initializing the database or do other preparations.

+

What is happening? - Nginx cannot connect to an upstream server or other services are not ready yet.
+ This is fine if mailcow was just + installed or updated and can take a few minutes to complete.
+ Please check the logs or contact support if the error persists.

+

Quick debugging

+

Check Nginx and PHP logs:

+
docker compose logs --tail=200 php-fpm-mailcow nginx-mailcow
+

Make sure your SQL credentials in mailcow.conf (a link to .env) do fit your initialized SQL volume. If you see an access denied, you might have the wrong mailcow.conf:

+
source mailcow.conf ; docker compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME}
+

In case of a previous failed installation, create a backup of your existing data, followed by removing all volumes and starting over (NEVER do this with a production system, it will remove ALL data):

+
BACKUP_LOCATION=/tmp/ ./helper-scripts/backup_and_restore.sh backup all
+
docker compose down --volumes ; docker compose up -d
+

Make sure your timezone is correct. Use "America/New_York" for example, do not use spaces. Check here for a list.

+
Click to learn more about getting support. + + diff --git a/data/web/admin.php b/data/web/admin.php deleted file mode 100644 index a4d7b697f4..0000000000 --- a/data/web/admin.php +++ /dev/null @@ -1,882 +0,0 @@ - -
- - -
-
-
-
-
-
-
-
-
-
- - - - -
-
- - - - -
-
:
-
-

-
- -
- -
- - [] -
-
- -
-
-
-
-
-
:
-
- -
-
- - API (experimental, work in progress) - - -
-
-
- -
- -
-
-
- -
- -
-
-
-
- -
-
-
-
-
- - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- - - - -
-
-
-
- -
-
-

Rspamd UI

-
-
-
-
-
-
-
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
- -
-
-
-
-
- Rspamd UI -
-
-
-
-
- -
-
- -
- -
-
-
-
-
- - -
-
- -
-
-
-

Domain: -

-

Selector ''

-

bit

-

-
-
-
-

โ†ช Private key

-
-
-
- -
-
-
-

Domain:

-
-
-
-
-
- -
-
-
-

โ†ณ Alias-Domain: -

-

Selector ''

-

bit

-

-
-
-
-

โ†ช Private key

-
-
-
- -
-
-
-

โ†ณ Alias-Domain:

-
-
-
-
-
- -
-
-
-

Domain: -

-

Selector ''

-

bit

-

-
-
-
-

โ†ช Private key

-
-
-
- - - -
-
- - - โ†ช -
-
- - -
-
- -
- -
- - - - -
-
-
- - -
-
- - -
-
- - -
- -
-
- - - - -
-
-
- -
- -
-
-
- -
- -
-
- -
-
- -
-
- - -
-
-
-

-
-
-
- - -

-
-
- - -
-
- -
- -
-
-
- - -
-
-
- -
-
- - -
-
- - -
-
- - -
-
- -
- / - -
-
-
- -
- / - -
-
-

-
- - -
-
- - -
-
- - -
-
-
-

- - - -

() - - - [] - [whitelist] - [blacklist] - - - -

- -

- -

- -
-
- - -
-
Relayhosts
-
-

-
-
-
-
-
- - - -
-
- -

-
-
- - -
-
- - -
-
- - -
- -
-
-
- - -
-
-
- -
-
-
-
- - -
-
-
-
- - -
-
-
-
-
- -
- -
-
-
- - -
-
Rspamd settings map
-
- Active settings map - -
- -
-
-
-
- - - - (ID #) - - -
-
-
-
- -
-

-
- -
-

-
- -
- - -
- - -
-
- - -
-
- -
- - - -
- -
-
-
- -
-
- - -
-
-
- -

-
-

- - -

-
- -
-
-
- mailcow logo -
- x px - - -
-
-
-
-

-
-
-
- - -

-
- - - - - - - $val): - ?> - - - - - - - - - - - - - -

- - -

-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
-
-
-
-
-
- -
-
-
-
-
-
- -
- -
-
-
- -
- -
-
- -
- -
- - -
-
- - -
-
-
- -
- -
-
-
-
- -
-
-
-
- -
-
-
-
-
- -
-
- - - - - diff --git a/data/web/admin/dashboard.php b/data/web/admin/dashboard.php new file mode 100644 index 0000000000..2867770bc5 --- /dev/null +++ b/data/web/admin/dashboard.php @@ -0,0 +1,81 @@ +Get('LICENSE_STATUS_CACHE')) { + $_SESSION['gal'] = json_decode($license_cache, true); +} + +$js_minifier->add('/web/js/site/dashboard.js'); + +// vmail df +$exec_fields = array('cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail'); +$vmail_df = explode(',', (string)json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true)); + +// containers +$containers_info = (array) docker('info'); +if ($clamd_status === false) unset($containers_info['clamd-mailcow']); +if ($olefy_status === false) unset($containers_info['olefy-mailcow']); +ksort($containers_info); +$containers = array(); +foreach ($containers_info as $container => $container_info) { + if (!isset($container_info['State']) || !is_array($container_info['State']) || !isset($container_info['State']['StartedAt'])){ + continue; + } + date_default_timezone_set('UTC'); + $StartedAt = date_parse($container_info['State']['StartedAt']); + if ($StartedAt['hour'] !== false) { + $date = new \DateTime(); + $date->setTimestamp(mktime( + $StartedAt['hour'], + $StartedAt['minute'], + $StartedAt['second'], + $StartedAt['month'], + $StartedAt['day'], + $StartedAt['year'])); + try { + $user_tz = new DateTimeZone(getenv('TZ')); + $date->setTimezone($user_tz); + $container_info['State']['StartedAtHR'] = $date->format('r'); + } catch(Exception $e) { + $container_info['State']['StartedAtHR'] = '?'; + } + } + else { + $container_info['State']['StartedAtHR'] = '?'; + } + $containers[$container] = $container_info; +} + +// get mailcow data +$hostname = getenv('MAILCOW_HOSTNAME'); +$timezone = getenv('TZ'); + +$template = 'dashboard.twig'; +$template_data = [ + 'log_lines' => getenv('LOG_LINES'), + 'vmail_df' => $vmail_df, + 'hostname' => $hostname, + 'timezone' => $timezone, + 'gal' => @$_SESSION['gal'], + 'license_guid' => license('guid'), + 'clamd_status' => $clamd_status, + 'olefy_status' => $olefy_status, + 'containers' => $containers, + 'ip_check' => customize('get', 'ip_check'), + 'lang_admin' => json_encode($lang['admin']), + 'lang_debug' => json_encode($lang['debug']), + 'lang_datatables' => json_encode($lang['datatables']), +]; + +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; + + diff --git a/data/web/admin/index.php b/data/web/admin/index.php new file mode 100644 index 0000000000..9e0bf1bd71 --- /dev/null +++ b/data/web/admin/index.php @@ -0,0 +1,33 @@ + @$_SESSION['ldelay'], + 'custom_login' => customize('get', 'custom_login'), +]; + +$js_minifier->add('/web/js/site/index.js'); +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; diff --git a/data/web/admin/mailbox.php b/data/web/admin/mailbox.php new file mode 100644 index 0000000000..7b07d6fa9d --- /dev/null +++ b/data/web/admin/mailbox.php @@ -0,0 +1,47 @@ +add('/web/js/site/mailbox.js'); +$js_minifier->add('/web/js/presets/sieveMailbox.js'); +$js_minifier->add('/web/js/site/pwgen.js'); + +$role = "admin"; +$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? 'true' : 'false'; +$allow_admin_email_login = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"])) ? 'true' : 'false'; + +// domains +$domains = mailbox('get', 'domains'); + +// mailboxes +$mailboxes = []; +foreach ($domains as $domain) { + foreach (mailbox('get', 'mailboxes', $domain) as $mailbox) { + $mailboxes[] = $mailbox; + } +} + +$template = 'mailbox.twig'; +$template_data = [ + 'acl' => $_SESSION['acl'], + 'acl_json' => json_encode($_SESSION['acl']), + 'role' => $role, + 'is_dual' => $is_dual, + 'allow_admin_email_login' => $allow_admin_email_login, + 'global_filters' => mailbox('get', 'global_filter_details'), + 'domains' => $domains, + 'mailboxes' => $mailboxes, + 'lang_mailbox' => json_encode($lang['mailbox']), + 'lang_rl' => json_encode($lang['ratelimit']), + 'lang_edit' => json_encode($lang['edit']), + 'lang_datatables' => json_encode($lang['datatables']), +]; + +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; diff --git a/data/web/admin/queue.php b/data/web/admin/queue.php new file mode 100644 index 0000000000..66682c7ae2 --- /dev/null +++ b/data/web/admin/queue.php @@ -0,0 +1,23 @@ +add('/web/js/site/queue.js'); +$_SESSION['return_to'] = $_SERVER['REQUEST_URI']; + +$role = "admin"; + +$template = 'queue.twig'; +$template_data = [ + 'acl' => $_SESSION['acl'], + 'acl_json' => json_encode($_SESSION['acl']), + 'role' => $role, + 'lang_admin' => json_encode($lang['admin']), + 'lang_queue' => json_encode($lang['queue']), + 'lang_datatables' => json_encode($lang['datatables']) +]; + +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; diff --git a/data/web/admin/system.php b/data/web/admin/system.php new file mode 100644 index 0000000000..4db40c753e --- /dev/null +++ b/data/web/admin/system.php @@ -0,0 +1,128 @@ + "get_friendly_names")); + +$js_minifier->add('/web/js/site/admin.js'); +$js_minifier->add('/web/js/presets/rspamd.js'); +$js_minifier->add('/web/js/site/pwgen.js'); + +// all domains +$domains = mailbox('get', 'domains'); +$all_domains = array_merge($domains, mailbox('get', 'alias_domains')); + +// mailboxes +$mailboxes = []; +foreach ($all_domains as $domain) { + foreach (mailbox('get', 'mailboxes', $domain) as $mailbox) { + $mailboxes[] = $mailbox; + } +} +$mailboxes = array_filter($mailboxes); + +// DKIM domains +$dkim_domains = []; +$dkim_domains_with_keys = []; +foreach($domains as $domain) { + $dkim_domains[$domain] = ['dkim' => null, 'alias_domains' => []]; + if (!empty($dkim = dkim('details', $domain))) { + $dkim_domains_with_keys[] = $domain; + if ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] !== true) { + $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.'); + } + $dkim_domains[$domain]['dkim'] = $dkim; + } + + // get alias domains + foreach (mailbox('get', 'alias_domains', $domain) as $alias_domain) { + $dkim_domains[$domain]['alias_domains'][$alias_domain] = ['dkim' => null]; + if (!empty($dkim = dkim('details', $alias_domain))) { + $dkim_domains_with_keys[] = $alias_domain; + if ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] !== true) { + $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.'); + } + $dkim_domains[$domain]['alias_domains'][$alias_domain]['dkim'] = $dkim; + } + } +} +$dkim_blind_domains = []; +foreach(dkim('blind') as $blind) { + $dkim_blind_domains[$blind] = ['dkim' => null]; + if (!empty($dkim = dkim('details', $blind))) { + $dkim_domains_with_keys[] = $blind; + if ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] !== true) { + $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.'); + } + $dkim_blind_domains[$blind]['dkim'] = $dkim; + } +} + +// rsettings +$rsettings = array_map(function ($rsetting){ + $rsetting['details'] = rsettings('details', $rsetting['id']); + return $rsetting; +}, rsettings('get')); + +// rspamd regex maps +$rspamd_regex_maps = []; +foreach ($RSPAMD_MAPS['regex'] as $rspamd_regex_desc => $rspamd_regex_map) { + $rspamd_regex_maps[$rspamd_regex_desc] = [ + 'map' => $rspamd_regex_map, + 'data' => file_get_contents('/rspamd_custom_maps/' . $rspamd_regex_map) + ]; +} + +// cors settings +$cors_settings = cors('get'); +$cors_settings['allowed_origins'] = str_replace(", ", "\n", $cors_settings['allowed_origins']); +$cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']); + +$f2b_data = fail2ban('get'); +// mbox templates +$mbox_templates = mailbox('get', 'mailbox_templates'); + +$template = 'admin.twig'; +$template_data = [ + 'tfa_data' => $tfa_data, + 'tfa_id' => @$_SESSION['tfa_id'], + 'fido2_cid' => @$_SESSION['fido2_cid'], + 'fido2_data' => $fido2_data, + 'api' => [ + 'ro' => admin_api('ro', 'get'), + 'rw' => admin_api('rw', 'get'), + ], + 'dkim_domains' => $dkim_domains, + 'dkim_domains_with_keys' => $dkim_domains_with_keys, + 'dkim_blind_domains' => $dkim_blind_domains, + 'domains' => $domains, + 'all_domains' => $all_domains, + 'mailboxes' => $mailboxes, + 'f2b_data' => $f2b_data, + 'f2b_banlist_url' => getBaseUrl() . "/f2b-banlist?id=" . $f2b_data['banlist_id'], + 'q_data' => quarantine('settings'), + 'qn_data' => quota_notification('get'), + 'pw_reset_data' => reset_password('get_notification'), + 'rsettings_map' => file_get_contents('http://nginx:8081/settings.php'), + 'rsettings' => $rsettings, + 'rspamd_regex_maps' => $rspamd_regex_maps, + 'logo_specs' => customize('get', 'main_logo_specs'), + 'logo_dark_specs' => customize('get', 'main_logo_dark_specs'), + 'ip_check' => customize('get', 'ip_check'), + 'custom_login' => customize('get', 'custom_login'), + 'password_complexity' => password_complexity('get'), + 'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'], + 'cors_settings' => $cors_settings, + 'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on', + 'iam_settings' => $iam_settings, + 'mbox_templates' => $mbox_templates, + 'lang_admin' => json_encode($lang['admin']), + 'lang_datatables' => json_encode($lang['datatables']) +]; + +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; diff --git a/data/web/api/favicon-16x16.png b/data/web/api/favicon-16x16.png new file mode 100644 index 0000000000..8b194e617a Binary files /dev/null and b/data/web/api/favicon-16x16.png differ diff --git a/data/web/api/favicon-32x32.png b/data/web/api/favicon-32x32.png new file mode 100644 index 0000000000..249737fe44 Binary files /dev/null and b/data/web/api/favicon-32x32.png differ diff --git a/data/web/api/index.css b/data/web/api/index.css new file mode 100644 index 0000000000..f2376fdaa8 --- /dev/null +++ b/data/web/api/index.css @@ -0,0 +1,16 @@ +html { + box-sizing: border-box; + overflow: -moz-scrollbars-vertical; + overflow-y: scroll; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +body { + margin: 0; + background: #fafafa; +} diff --git a/data/web/api/index.html b/data/web/api/index.html new file mode 100644 index 0000000000..84ae62d3da --- /dev/null +++ b/data/web/api/index.html @@ -0,0 +1,19 @@ + + + + + + Swagger UI + + + + + + + +
+ + + + + diff --git a/data/web/api/oauth2-redirect.html b/data/web/api/oauth2-redirect.html new file mode 100644 index 0000000000..5640917181 --- /dev/null +++ b/data/web/api/oauth2-redirect.html @@ -0,0 +1,79 @@ + + + + Swagger UI: OAuth2 Redirect + + + + + diff --git a/data/web/api/openapi.yaml b/data/web/api/openapi.yaml new file mode 100644 index 0000000000..c865619038 --- /dev/null +++ b/data/web/api/openapi.yaml @@ -0,0 +1,6206 @@ +openapi: 3.1.0 +info: + description: >- + mailcow is complete e-mailing solution with advanced antispam, antivirus, + nice UI and API. + + + In order to use this API you have to create a API key and add your IP + address to the whitelist of allowed IPs this can be done by logging into the + Mailcow UI using your admin account, then go to Configuration > Access > + Edit administrator details > API. There you will find a collapsed API menu. + + + There are two types of API keys + - The read only key can only be used for all get endpoints + - The read write key can be used for all endpoints + + title: mailcow API + version: "1.0.0" + +servers: + - url: / + +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + responses: + Unauthorized: + description: Unauthorized + content: + application/json: + schema: + type: object + properties: + type: + type: string + example: error + msg: + type: string + example: authentication failed + required: + - type + - msg + +security: + - ApiKeyAuth: [] + +paths: + /api/v1/add/alias: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - add + - alias + - active: "1" + address: alias@domain.tld + goto: destination@domain.tld + - null + msg: + - alias_added + - alias@domain.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Aliases + description: >- + You may create your own mailbox alias using this action. It takes a JSON + object containing a domain informations. + + Only one `goto*` option can be used, for ex. if you want learn as spam, + then send just `goto_spam = 1` in request body. + operationId: Create alias + requestBody: + content: + application/json: + schema: + example: + active: "1" + address: alias@domain.tld + goto: destination@domain.tld + properties: + active: + description: is alias active or not + type: boolean + address: + description: 'alias address, for catchall use "@domain.tld"' + type: string + goto: + description: "destination address, comma separated" + type: string + goto_ham: + description: learn as ham + type: boolean + goto_null: + description: silently ignore + type: boolean + goto_spam: + description: learn as spam + type: boolean + sogo_visible: + description: toggle visibility as selectable sender in SOGo + type: boolean + type: object + summary: Create alias + /api/v1/add/time_limited_alias: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - add + - time_limited_alias + - address: info@domain.tld + domain: domain.tld + - null + msg: + - mailbox_modified + - info@domain.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Aliases + description: >- + You may create a time limited alias using this action. It takes a JSON + object containing a domain and mailbox informations. + Mailcow will generate a random alias. + operationId: Create time limited alias + requestBody: + content: + application/json: + schema: + example: + username: info@domain.tld + domain: domain.tld + description: my time limited alias + validity: 8760 + permanent: false + properties: + username: + description: 'the mailbox an alias should be created for' + type: string + domain: + description: "the domain" + type: string + description: + description: "a description for the alias, defaults to an empty string" + type: string + validity: + description: "how many hours the alias stays valid, 1 to 87600, defaults to 8760 (one year)" + type: integer + permanent: + description: "keep the alias after it expired, defaults to false" + type: boolean + type: object + summary: Create time limited alias + /api/v1/add/app-passwd: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - app_passwd + - add + - active: "1" + username: info@domain.tld + app_name: wordpress + app_passwd: keyleudecticidechothistishownsan31 + app_passwd2: keyleudecticidechothistishownsan31 + protocols: + - imap_access + - dav_access + - smtp_access + - eas_access + - pop3_access + - sieve_access + msg: app_passwd_added + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - App Passwords + description: >- + Using this endpoint you can create a new app password for a specific + mailbox. + operationId: Create App Password + requestBody: + content: + application/json: + schema: + example: + active: "1" + username: info@domain.tld + app_name: wordpress + app_passwd: keyleudecticidechothistishownsan31 + app_passwd2: keyleudecticidechothistishownsan31 + protocols: + - imap_access + - dav_access + - smtp_access + - eas_access + - pop3_access + - sieve_access + properties: + active: + description: is alias active or not + type: boolean + username: + description: mailbox for which the app password should be created + type: string + app_name: + description: name of your app password + type: string + app_passwd: + description: your app password + type: string + app_passwd2: + description: your app password + type: string + type: object + summary: Create App Password + /api/v1/add/bcc: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - bcc + - add + - active: "1" + bcc_dest: bcc@awesomecow.tld + local_dest: mailcow.tld + type: sender + - null + msg: bcc_saved + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Address Rewriting + description: >- + Using this endpoint you can create a BCC map to forward all mails via a + bcc for a given domain. + operationId: Create BCC Map + requestBody: + content: + application/json: + schema: + example: + active: "1" + bcc_dest: bcc@awesomecow.tld + local_dest: mailcow.tld + type: sender + properties: + active: + description: 1 for a active user account 0 for a disabled user account + type: number + bcc_dest: + description: the email address where all mails should be send to + type: string + local_dest: + description: the domain which emails should be forwarded + type: string + type: + description: the type of bcc map can be `sender` or `rcpt` + enum: [sender, rcpt] + type: string + type: object + summary: Create BCC Map + /api/v1/add/dkim: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - dkim + - add + - dkim_selector: dkim + domains: hanspeterlol.de + key_size: "2048" + msg: + - dkim_added + - hanspeterlol.de + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - DKIM + description: Using this endpoint you can generate new DKIM keys. + operationId: Generate DKIM Key + requestBody: + content: + application/json: + schema: + example: + dkim_selector: dkim + domains: mailcow.tld + key_size: "2048" + properties: + dkim_selector: + description: the DKIM selector default dkim + type: string + domains: + description: a list of domains for which a dkim key should be generated + type: string + key_size: + description: the key size (1024, 2048, 3072 or 4096) + type: number + type: object + summary: Generate DKIM Key + /api/v1/add/dkim_duplicate: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - dkim + - duplicate + - from_domain: mailcow.tld + to_domain: awesomecow.tld + msg: + - dkim_duplicated + - mailcow.tld + - awesomecow.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - DKIM + description: Using this endpoint you can duplicate the DKIM Key of one domain. + operationId: Duplicate DKIM Key + requestBody: + content: + application/json: + schema: + example: + from_domain: mailcow.tld + to_domain: awesomecow.tld + properties: + fron_domain: + description: the domain where the dkim key should be copied from + type: string + to_domain: + description: the domain where the dkim key should be copied to + type: string + type: object + summary: Duplicate DKIM Key + /api/v1/add/domain: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - ratelimit + - edit + - domain + - object: domain.tld + rl_frame: s + rl_value: "10" + msg: + - rl_saved + - domain.tld + type: success + - log: + - mailbox + - add + - domain + - active: "1" + aliases: "400" + restart_sogo: "1" + backupmx: "0" + defquota: "3072" + description: some decsription + domain: domain.tld + mailboxes: "10" + maxquota: "10240" + quota: "10240" + relay_all_recipients: "0" + rl_frame: s + rl_value: "10" + tags: ["tag1", "tag2"] + - null + msg: + - domain_added + - domain.tld + type: success + schema: + type: array + items: + type: object + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + description: OK + headers: {} + tags: + - Domains + description: >- + You may create your own domain using this action. It takes a JSON object + containing a domain informations. + operationId: Create domain + requestBody: + content: + application/json: + schema: + example: + active: "1" + aliases: "400" + backupmx: "0" + defquota: "3072" + description: some decsription + domain: domain.tld + mailboxes: "10" + maxquota: "10240" + quota: "10240" + relay_all_recipients: "0" + rl_frame: s + rl_value: "10" + restart_sogo: "10" + tags: ["tag1", "tag2"] + properties: + active: + description: is domain active or not + type: boolean + aliases: + description: limit count of aliases associated with this domain + type: number + backupmx: + description: relay domain or not + type: boolean + defquota: + description: predefined mailbox quota in `add mailbox` form + type: number + description: + description: Description of domain + type: string + domain: + description: Fully qualified domain name + type: string + gal: + description: >- + is domain global address list active or not, it enables + shared contacts accross domain in SOGo webmail + type: boolean + mailboxes: + description: limit count of mailboxes associated with this domain + type: number + maxquota: + description: maximum quota per mailbox + type: number + quota: + description: maximum quota for this domain (for all mailboxes in sum) + type: number + restart_sogo: + description: restart SOGo to activate the domain in SOGo + type: number + relay_all_recipients: + description: >- + if not, them you have to create "dummy" mailbox for each + address to relay + type: boolean + relay_unknown_only: + description: Relay non-existing mailboxes only. Existing mailboxes will be delivered locally. + type: boolean + rl_frame: + enum: + - s + - m + - h + - d + type: string + rl_value: + description: rate limit value + type: number + tags: + description: tags for this Domain + type: array + items: + type: string + type: object + summary: Create domain + /api/v1/add/domain-admin: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - domain_admin + - add + - active: "1" + domains: mailcow.tld + password: "*" + password2: "*" + username: testadmin + msg: + - domain_admin_added + - testadmin + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Domain admin + description: >- + Using this endpoint you can create a new Domain Admin user. This user + has full control over a domain, and can create new mailboxes and + aliases. + operationId: Create Domain Admin user + requestBody: + content: + application/json: + schema: + example: + active: "1" + domains: mailcow.tld + password: supersecurepw + password2: supersecurepw + username: testadmin + properties: + active: + description: 1 for a active user account 0 for a disabled user account + type: number + domains: + description: the domains the user should be a admin of + type: string + password: + description: domain admin user password + type: string + password2: + description: domain admin user password + type: string + username: + description: the username for the admin user + type: string + type: object + summary: Create Domain Admin user + /api/v1/add/sso/domain-admin: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + token: "591F6D-5C3DD2-7455CD-DAF1C1-AA4FCC" + description: OK + headers: { } + tags: + - Single Sign-On + description: >- + Using this endpoint you can issue a token for Domain Admin user. This token can be used for + autologin Domain Admin user by using query_string var sso_token={token}. Token expiration time is 30s + operationId: Issue Domain Admin SSO token + requestBody: + content: + application/json: + schema: + example: + username: testadmin + properties: + username: + description: the username for the admin user + type: object + type: object + summary: Issue Domain Admin SSO token + /api/v1/edit/da-acl: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - type: success + log: + - acl + - edit + - testadmin + - username: + - testadmin + da_acl: + - syncjobs + - quarantine + - login_as + - sogo_access + - app_passwds + - bcc_maps + - pushover + - filters + - ratelimit + - spam_policy + - extend_sender_acl + - unlimited_quota + - protocol_access + - smtp_ip_access + - alias_domains + - domain_desc + msg: + - acl_saved + - testadmin + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Domain admin + description: >- + Using this endpoint you can edit the ACLs of a Domain Admin user. This user + has full control over a domain, and can create new mailboxes and + aliases. + operationId: Edit Domain Admin ACL + requestBody: + content: + application/json: + schema: + example: + items: + - testadmin + attr: + da_acl: + - syncjobs + - quarantine + - login_as + - sogo_access + - app_passwds + - bcc_maps + - pushover + - filters + - ratelimit + - spam_policy + - extend_sender_acl + - unlimited_quota + - protocol_access + - smtp_ip_access + - alias_domains + - domain_desc + properties: + items: + description: contains the domain admin username you want to edit + type: object + attr: + properties: + da_acl: + description: contains the list of acl names that are active for this user + type: object + type: object + summary: Edit Domain Admin ACL + /api/v1/edit/domain-admin: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - type: success + log: + - domain_admin + - edit + - username: testadmin + active: ["0","1"] + username_new: testadmin + domains: ["domain.tld"] + password: "*" + password2: "*" + msg: + - domain_admin_modified + - testadmin + schema: + properties: + type: + enum: + - success + - danger + - error + type: string + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: object + description: OK + headers: {} + tags: + - Domain admin + description: >- + Using this endpoint you can edit a existing Domain Admin user. This user + has full control over a domain, and can create new mailboxes and + aliases. + operationId: Edit Domain Admin user + requestBody: + content: + application/json: + schema: + example: + items: + - testadmin + attr: + active: + - '0' + - '1' + username_new: testadmin + domains: ["domain.tld"] + password: supersecurepassword + password2: supersecurepassword + properties: + attr: + properties: + active: + description: is the domain admin active or not + type: boolean + username_new: + description: the username of the domain admin, change this to change the username + type: string + domains: + description: a list of all domains managed by this domain admin + type: array + items: + type: string + password: + description: the new domain admin user password + type: string + password2: + description: the new domain admin user password for confirmation + type: string + type: object + items: + description: contains the domain admin username you want to edit + type: object + summary: Edit Domain Admin user + /api/v1/add/domain-policy: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - policy + - add + - domain + - domain: domain.tld + object_from: "*@baddomain.tld" + object_list: bl + msg: + - domain_modified + - domain.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Domain antispam policies + description: >- + You may create your own domain policy using this action. It takes a JSON + object containing a domain informations. + operationId: Create domain policy + requestBody: + content: + application/json: + schema: + example: + domain: domain.tld + object_from: "*@baddomain.tld" + object_list: bl + properties: + domain: + description: domain name to which policy is associated to + type: string + object_from: + description: exact address or use wildcard to match whole domain + type: string + object_list: + enum: + - wl + - bl + type: string + type: object + summary: Create domain policy + /api/v1/add/fwdhost: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - fwdhost + - add + - filter_spam: "0" + hostname: hosted.mailcow.de + msg: + - forwarding_host_added + - "5.1.76.202, 2a00:f820:417::202" + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Fordwarding Hosts + description: >- + Add a new Forwarding host to mailcow. You can chose to enable or disable + spam filtering of incoming emails by specifing `filter_spam` 0 = + inactive, 1 = active. + operationId: Add Forward Host + requestBody: + content: + application/json: + schema: + example: + filter_spam: "0" + hostname: hosted.mailcow.de + properties: + filter_spam: + description: "1 to enable spam filter, 0 to disable spam filter" + type: number + hostname: + description: contains the hostname you want to add + type: string + type: object + summary: Add Forward Host + /api/v1/add/mailbox: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - add + - mailbox + - active: "1" + domain: domain.tld + local_part: info + name: Full name + password: "*" + password2: "*" + quota: "3072" + force_pw_update: "1" + force_tfa: "1" + tls_enforce_in: "1" + tls_enforce_out: "1" + tags: ["tag1", "tag2"] + - null + msg: + - mailbox_added + - info@domain.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Mailboxes + description: >- + You may create your own mailbox using this action. It takes a JSON + object containing a domain informations. + operationId: Create mailbox + requestBody: + content: + application/json: + schema: + example: + active: "1" + domain: domain.tld + local_part: info + name: Full name + authsource: mailcow + password: atedismonsin + password2: atedismonsin + quota: "3072" + force_pw_update: "1" + force_tfa: "1" + tls_enforce_in: "1" + tls_enforce_out: "1" + tags: ["tag1", "tag2"] + properties: + active: + description: is mailbox active or not + type: boolean + domain: + description: domain name + type: string + local_part: + description: left part of email address + type: string + name: + description: Full name of the mailbox user + type: string + authsource: + description: Specifies the authentication source for the mailbox. + type: string + enum: [mailcow, ldap, keycloak, generic-oidc] + default: mailcow + password2: + description: mailbox password for confirmation + type: string + password: + description: mailbox password when using `mailcow` as the authentication source. + type: string + quota: + description: mailbox quota + type: number + force_pw_update: + description: forces the user to update its password on first login + type: boolean + force_tfa: + description: force 2FA enrollment at login + type: boolean + tls_enforce_in: + description: force inbound email tls encryption + type: boolean + tls_enforce_out: + description: force oubound tmail tls encryption + type: boolean + type: object + summary: Create mailbox + + /api/v1/add/oauth2-client: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - oauth2 + - add + - client + - redirect_uri: "https://mailcow.tld" + msg: Added client access + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - oAuth Clients + description: Using this endpoint you can create a oAuth clients. + operationId: Create oAuth Client + requestBody: + content: + application/json: + schema: + example: + redirect_uri: "https://mailcow.tld" + properties: + redirect_uri: + description: the uri where you should be redirected after oAuth + type: string + type: object + summary: Create oAuth Client + /api/v1/add/recipient_map: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - recipient_map + - add + - active: "1" + recipient_map_new: target@mailcow.tld + recipient_map_old: recipient@mailcow.tld + - null + msg: + - recipient_map_entry_saved + - recipient@mailcow.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Address Rewriting + description: >- + Using this endpoint you can create a recipient map to forward all mails + from one email address to another. + operationId: Create Recipient Map + requestBody: + content: + application/json: + schema: + example: + active: "1" + recipient_map_new: target@mailcow.tld + recipient_map_old: recipient@mailcow.tld + properties: + active: + description: 1 for a active user account 0 for a disabled user account + type: number + recipient_map_new: + description: the email address that should receive the forwarded emails + type: string + recipient_map_old: + description: the email address which emails should be forwarded + type: string + type: object + summary: Create Recipient Map + /api/v1/add/relayhost: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - relayhost + - add + - hostname: "mailcow.tld:25" + password: supersecurepassword + username: testuser + msg: + - relayhost_added + - "" + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Routing + description: Using this endpoint you can create Sender-Dependent Transports. + operationId: Create Sender-Dependent Transports + requestBody: + content: + application/json: + schema: + example: + hostname: "mailcow.tld:25" + password: supersecurepassword + username: testuser + properties: + hostname: + description: the hostname of the smtp server with port + type: string + password: + description: the password for the smtp user + type: string + username: + description: the username used to authenticate + type: string + type: object + summary: Create Sender-Dependent Transports + /api/v1/add/resource: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - add + - resource + - active: "1" + description: test + domain: mailcow.tld + kind: location + multiple_bookings: "0" + multiple_bookings_custom: "" + multiple_bookings_select: "0" + - null + msg: + - resource_added + - mailcow.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Resources + description: Using this endpoint you can create Resources. + operationId: Create Resources + requestBody: + content: + application/json: + schema: + example: + active: "1" + description: test + domain: mailcow.tld + kind: location + multiple_bookings: "0" + multiple_bookings_custom: "" + multiple_bookings_select: "0" + properties: + active: + description: 1 for a active transport map 0 for a disabled transport map + type: number + description: + description: a description of the resource + type: string + domain: + description: the domain for which the resource should be + type: string + kind: + description: the kind of recouse + enum: + - location + - group + - thing + type: string + multiple_bookings: + enum: + - "-1" + - "1" + - custom + type: string + multiple_bookings_custom: + description: always empty + type: number + multiple_bookings_select: + enum: + - "-1" + - "1" + - custom + type: string + type: object + summary: Create Resources + /api/v1/add/syncjob: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - add + - syncjob + - active: "1" + automap: "1" + custom_params: "" + delete1: "0" + delete2: "0" + delete2duplicates: "1" + enc1: SSL + exclude: (?i)spam|(?i)junk + host1: imap.server.tld + maxage: "0" + maxbytespersecond: "0" + mins_interval: "20" + password1: supersecret + port1: 993 + skipcrossduplicates: "0" + subfolder2: External + subscribeall: "1" + timeout1: "600" + timeout2: "600" + user1: username + username: mailbox@domain.tld + - null + msg: + - mailbox_modified + - mailbox@domain.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Sync jobs + description: >- + You can create new sync job using this action. It takes a JSON object + containing a domain informations. + operationId: Create sync job + summary: Create sync job + requestBody: + content: + application/json: + schema: + example: + username: lisa@mailcow.tld + host1: mail.mailcow.tld + port1: "143" + user1: demo@mailcow.tld + password1: supersecretpw + enc1: TLS + mins_interval: "20" + subfolder2: "/SyncIntoSubfolder" + maxage: "0" + maxbytespersecond: "0" + timeout1: "600" + timeout2: "600" + exclude: "(?i)spam|(?i)junk" + custom_params: "--dry" + delete2duplicates: "1" + delete1: "1" + delete2: "0" + automap: "1" + skipcrossduplicates: "0" + subscribeall: "0" + active: "1" + properties: + parameters: + description: your local mailcow mailbox + type: string + host1: + description: the smtp server where mails should be synced from + type: string + port1: + description: the smtp port of the target mail server + type: string + user1: + description: the username of the mailbox + type: string + password: + description: the password of the mailbox + type: string + enc1: + description: the encryption method used to connect to the mailserver + type: string + mins_internal: + description: the interval in which messages should be syned + type: number + subfolder2: + description: sync into subfolder on destination (empty = do not use subfolder) + type: string + maxage: + description: only sync messages up to this age in days + type: number + maxbytespersecond: + description: max speed transfer limit for the sync + type: number + timeout1: + description: timeout for connection to remote host + type: number + timeout2: + description: timeout for connection to local host + type: number + exclude: + description: exclude objects (regex) + type: string + custom_params: + description: custom parameters + type: string + delete2duplicates: + description: delete duplicates on destination (--delete2duplicates) + type: boolean + delete1: + description: delete from source when completed (--delete1) + type: boolean + delete2: + description: delete messages on destination that are not on source (--delete2) + type: boolean + automap: + description: try to automap folders ("Sent items", "Sent" => "Sent" etc.) (--automap) + type: boolean + skipcrossduplicates: + description: skip duplicate messages across folders (first come, first serve) (--skipcrossduplicates) + type: boolean + subscribeall: + description: subscribe all folders (--subscribeall) + type: boolean + active: + description: enables or disables the sync job + type: boolean + type: object + /api/v1/add/tls-policy-map: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - tls_policy_maps + - add + - parameters: "" + active: "1" + dest: mailcow.tld + policy: encrypt + - null + msg: + - tls_policy_map_entry_saved + - mailcow.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Outgoing TLS Policy Map Overrides + description: Using this endpoint you can create a TLS policy map override. + operationId: Create TLS Policy Map + requestBody: + content: + application/json: + schema: + example: + parameters: "" + active: "1" + dest: mailcow.tld + policy: encrypt + properties: + parameters: + description: >- + custom parameters you find out more about them + [here](http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps) + type: string + active: + description: 1 for a active user account 0 for a disabled user account + type: number + dest: + description: the target domain or email address + type: string + policy: + description: the policy + enum: + - none + - may + - encrypt + - dane + - "'dane" + - fingerprint + - verify + - secure + type: string + type: object + summary: Create TLS Policy Map + /api/v1/add/transport: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - transport + - add + - active: "1" + destination: example2.org + nexthop: "host:25" + password: supersecurepw + username: testuser + msg: + - relayhost_added + - "" + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Routing + description: Using this endpoint you can create Sender-Dependent Transports. + operationId: Create Transport Maps + requestBody: + content: + application/json: + schema: + example: + active: "1" + destination: example.org + nexthop: "host:25" + password: supersecurepw + username: testuser + properties: + active: + description: 1 for a active transport map 0 for a disabled transport map + type: number + destination: + type: string + nexthop: + type: string + password: + description: the password for the smtp user + type: string + username: + description: the username used to authenticate + type: string + type: object + summary: Create Transport Maps + /api/v1/delete/alias: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - delete + - alias + - id: + - "6" + - "9" + - null + msg: + - alias_removed + - alias@domain.tld + type: success + - log: + - mailbox + - delete + - alias + - id: + - "6" + - "9" + - null + msg: + - alias_removed + - alias2@domain.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Aliases + description: You can delete one or more aliases. + operationId: Delete alias + requestBody: + content: + application/json: + schema: + items: + example: "6" + type: string + type: array + summary: Delete alias + /api/v1/delete/app-passwd: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - app_passwd + - delete + - id: + - "2" + msg: + - app_passwd_removed + - "2" + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - App Passwords + description: Using this endpoint you can delete a single app password. + operationId: Delete App Password + requestBody: + content: + application/json: + schema: + example: + - "1" + properties: + items: + description: contains list of app passwords you want to delete + type: object + type: object + summary: Delete App Password + /api/v1/delete/bcc: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - bcc + - delete + - id: + - "4" + - null + msg: + - bcc_deleted + - "4" + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Address Rewriting + description: >- + Using this endpoint you can delete a BCC map, for this you have to know + its ID. You can get the ID using the GET method. + operationId: Delete BCC Map + requestBody: + content: + application/json: + schema: + example: + - "3" + properties: + items: + description: contains list of bcc maps you want to delete + type: object + type: object + summary: Delete BCC Map + /api/v1/delete/dkim: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - dkim + - delete + - domains: + - mailcow.tld + msg: + - dkim_removed + - mailcow.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - DKIM + description: Using this endpoint a existing DKIM Key can be deleted + operationId: Delete DKIM Key + requestBody: + content: + application/json: + schema: + items: + example: + - mailcow.tld + type: string + type: array + summary: Delete DKIM Key + /api/v1/delete/domain: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - delete + - domain + - domain: + - domain.tld + - domain2.tld + - null + msg: + - domain_removed + - domain.tld + type: success + - log: + - mailbox + - delete + - domain + - domain: + - domain.tld + - domain2.tld + - null + msg: + - domain_removed + - domain2.tld + type: success + schema: + type: array + items: + type: object + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + description: OK + headers: {} + tags: + - Domains + description: You can delete one or more domains. + operationId: Delete domain + requestBody: + content: + application/json: + schema: + type: object + example: + - domain.tld + - domain2.tld + properties: + items: + type: array + items: + type: string + summary: Delete domain + /api/v1/delete/domain-admin: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - domain_admin + - delete + - username: + - testadmin + msg: + - domain_admin_removed + - testadmin + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Domain admin + description: Using this endpoint a existing Domain Admin user can be deleted. + operationId: Delete Domain Admin + requestBody: + content: + application/json: + schema: + example: + - testadmin + properties: + items: + description: contains list of usernames of the users you want to delete + type: object + type: object + summary: Delete Domain Admin + /api/v1/delete/domain-policy: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - policy + - delete + - domain + - prefid: + - "1" + - "2" + msg: + - item_deleted + - "1" + type: success + - log: + - policy + - delete + - domain + - prefid: + - "1" + - "2" + msg: + - item_deleted + - "2" + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Domain antispam policies + description: You can delete one o more domain policies. + operationId: Delete domain policy + requestBody: + content: + application/json: + schema: + example: + - "1" + - "2" + properties: + items: + description: contains list of domain policys you want to delete + type: object + type: object + summary: Delete domain policy + /api/v1/delete/fwdhost: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - fwdhost + - delete + - forwardinghost: + - 5.1.76.202 + - "2a00:f820:417::202" + msg: + - forwarding_host_removed + - 5.1.76.202 + type: success + - log: + - fwdhost + - delete + - forwardinghost: + - 5.1.76.202 + - "2a00:f820:417::202" + msg: + - forwarding_host_removed + - "2a00:f820:417::202" + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Fordwarding Hosts + description: >- + Using this endpoint you can delete a forwarding host, in order to do so + you need to know the IP of the host. + operationId: Delete Forward Host + requestBody: + content: + application/json: + schema: + example: + - 5.1.76.202 + - "2a00:f820:417::202" + properties: + ip: + description: contains the ip of the fowarding host you want to delete + type: string + type: object + summary: Delete Forward Host + /api/v1/delete/mailbox: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - delete + - mailbox + - username: + - info@domain.tld + - sales@domain.tld + - null + msg: + - mailbox_removed + - info@domain.tld + type: success + - log: + - mailbox + - delete + - mailbox + - username: + - info@domain.tld + - sales@domain.tld + - null + msg: + - mailbox_removed + - sales@domain.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Mailboxes + description: You can delete one or more mailboxes. + operationId: Delete mailbox + requestBody: + content: + application/json: + schema: + example: + - info@domain.tld + - sales@domain.tld + properties: + items: + description: contains list of mailboxes you want to delete + type: object + type: object + summary: Delete mailbox + /api/v1/delete/mailq: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + msg: Task completed + type: success + description: OK + headers: {} + tags: + - Queue Manager + description: >- + Using this API you can delete the current mail queue. This will delete + all mails in it. + + This API uses the command: `postsuper -d` + operationId: Delete Queue + requestBody: + content: + application/json: + schema: + example: + action: super_delete + properties: + action: + description: use super_delete to delete the mail queue + type: string + type: object + summary: Delete Queue + /api/v1/delete/oauth2-client: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - oauth2 + - delete + - client + - id: + - "1" + msg: + - items_deleted + - "1" + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - oAuth Clients + description: >- + Using this endpoint you can delete a oAuth client, for this you have to + know its ID. You can get the ID using the GET method. + operationId: Delete oAuth Client + requestBody: + content: + application/json: + schema: + example: + - "3" + properties: + items: + description: contains list of oAuth clients you want to delete + type: object + type: object + summary: Delete oAuth Client + /api/v1/delete/qitem: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - quarantine + - delete + - id: + - "33" + msg: + - item_deleted + - "33" + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Quarantine + description: >- + Using this endpoint you can delete a email from quarantine, for this you + have to know its ID. You can get the ID using the GET method. + operationId: Delete mails in Quarantine + requestBody: + content: + application/json: + schema: + example: + - "33" + properties: + items: + description: contains list of emails you want to delete + type: object + type: object + summary: Delete mails in Quarantine + /api/v1/edit/qitem: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + release: + value: + - log: + - quarantine + - edit + - id: + - "33" + action: release + msg: + - item_released + - "33" + type: success + learnham: + value: + - log: + - quarantine + - edit + - id: + - "34" + action: learnham + msg: + - item_learned + - "34" + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Quarantine + description: >- + Using this endpoint you can perform actions on quarantine items. It is possible to release + emails from quarantine into to the inbox, or learn them as ham to improve Rspamd filtering. + You must provide the quarantine item IDs. You can get the IDs using the GET method. + operationId: Edit mails in Quarantine + requestBody: + content: + application/json: + schema: + example: + items: + - "33" + - "34" + attr: + action: release + properties: + items: + description: contains list of quarantine item IDs to release or learn as ham + type: object + attr: + description: attributes for the action + type: object + properties: + action: + type: string + enum: + - release + - learnham + description: "release - return email to inbox; learnham - learn as ham to improve filtering" + type: object + summary: Edit mails in Quarantine + /api/v1/delete/recipient_map: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - recipient_map + - delete + - id: + - "1" + - null + msg: + - recipient_map_entry_deleted + - "1" + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Address Rewriting + description: >- + Using this endpoint you can delete a recipient map, for this you have to + know its ID. You can get the ID using the GET method. + operationId: Delete Recipient Map + requestBody: + content: + application/json: + schema: + example: + - "1" + properties: + items: + description: contains list of recipient maps you want to delete + type: object + type: object + summary: Delete Recipient Map + /api/v1/delete/relayhost: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - relayhost + - delete + - id: + - "1" + msg: + - relayhost_removed + - "1" + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Routing + description: >- + Using this endpoint you can delete a Sender-Dependent Transport, for + this you have to know its ID. You can get the ID using the GET method. + operationId: Delete Sender-Dependent Transports + requestBody: + content: + application/json: + schema: + example: + - "1" + properties: + items: + description: >- + contains list of Sender-Dependent Transport you want to + delete + type: object + type: object + summary: Delete Sender-Dependent Transports + /api/v1/delete/resource: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - delete + - resource + - name: + - test@mailcow.tld + - null + msg: + - resource_removed + - test@mailcow.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Resources + description: >- + Using this endpoint you can delete a Resources, for this you have to + know its ID. You can get the ID using the GET method. + operationId: Delete Resources + requestBody: + content: + application/json: + schema: + example: + - test@mailcow.tld + properties: + items: + description: contains list of Resources you want to delete + type: object + type: object + summary: Delete Resources + /api/v1/delete/syncjob: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + log: + - entity + - action + - object + msg: + - message + - entity name + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Sync jobs + description: You can delete one or more sync jobs. + operationId: Delete sync job + requestBody: + content: + application/json: + schema: + example: + - "6" + - "9" + properties: + items: + description: contains list of aliases you want to delete + type: object + type: object + summary: Delete sync job + /api/v1/delete/tls-policy-map: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - tls_policy_maps + - delete + - id: + - "1" + - null + msg: + - tls_policy_map_entry_deleted + - "1" + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Outgoing TLS Policy Map Overrides + description: >- + Using this endpoint you can delete a TLS Policy Map, for this you have + to know its ID. You can get the ID using the GET method. + operationId: Delete TLS Policy Map + requestBody: + content: + application/json: + schema: + example: + - "3" + properties: + items: + description: contains list of tls policy maps you want to delete + type: object + type: object + summary: Delete TLS Policy Map + /api/v1/delete/transport: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - transport + - delete + - id: + - "1" + msg: + - relayhost_removed + - "1" + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Routing + description: >- + Using this endpoint you can delete a Transport Maps, for this you have + to know its ID. You can get the ID using the GET method. + operationId: Delete Transport Maps + requestBody: + content: + application/json: + schema: + example: + - "1" + properties: + items: + description: contains list of transport maps you want to delete + type: object + type: object + summary: Delete Transport Maps + "/api/v1/delete/mailbox/tag/{mailbox}": + post: + parameters: + - description: name of mailbox + in: path + name: mailbox + example: info@domain.tld + required: true + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - delete + - tags_mailbox + - tags: + - tag1 + - tag2 + mailbox: info@domain.tld + - null + msg: + - mailbox_modified + - info@domain.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Mailboxes + description: You can delete one or more mailbox tags. + operationId: Delete mailbox tags + requestBody: + content: + application/json: + schema: + example: + - tag1 + - tag2 + properties: + items: + description: contains list of mailboxes you want to delete + type: object + type: object + summary: Delete mailbox tags + "/api/v1/delete/domain/tag/{domain}": + post: + parameters: + - description: name of domain + in: path + name: domain + example: domain.tld + required: true + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - delete + - tags_domain + - tags: + - tag1 + - tag2 + domain: domain.tld + - null + msg: + - domain_modified + - domain.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Domains + description: You can delete one or more domain tags. + operationId: Delete domain tags + requestBody: + content: + application/json: + schema: + example: + - tag1 + - tag2 + properties: + items: + description: contains list of domains you want to delete + type: object + type: object + summary: Delete domain tags + /api/v1/edit/alias: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - edit + - alias + - active: "1" + address: alias@domain.tld + goto: destination@domain.tld + id: + - "6" + private_comment: private comment + public_comment: public comment + - null + msg: + - alias_modified + - alias@domain.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Aliases + description: >- + You can update one or more aliases per request. You can also send just + attributes you want to change + operationId: Update alias + requestBody: + content: + application/json: + schema: + example: + attr: + active: "1" + address: alias@domain.tld + goto: destination@domain.tld + private_comment: private comment + public_comment: public comment + items: ["6"] + properties: + attr: + properties: + active: + description: is alias active or not + type: boolean + address: + description: 'alias address, for catchall use "@domain.tld"' + type: string + goto: + description: "destination address, comma separated" + type: string + goto_ham: + description: learn as ham + type: boolean + goto_null: + description: silently ignore + type: boolean + goto_spam: + description: learn as spam + type: boolean + private_comment: + type: string + public_comment: + type: string + sogo_visible: + description: toggle visibility as selectable sender in SOGo + type: boolean + type: object + items: + description: contains list of aliases you want update + type: object + type: object + summary: Update alias + /api/v1/edit/domain: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + schema: + type: array + items: + type: object + properties: + log: + type: array + description: contains request object + items: {} + msg: + type: array + items: {} + type: + enum: + - success + - danger + - error + type: string + description: OK + headers: {} + tags: + - Domains + description: >- + You can update one or more domains per request. You can also send just + attributes you want to change. + + Example: You can add domain names to items list and in attr object just + include `"active": "0"` to deactivate domains. + operationId: Update domain + requestBody: + content: + application/json: + schema: + example: + attr: + active: "1" + aliases: "400" + backupmx: "1" + defquota: "3072" + description: domain description + gal: "1" + mailboxes: "10" + maxquota: "10240" + quota: "10240" + relay_all_recipients: "0" + relayhost: "2" + tags: ["tag3", "tag4"] + items: domain.tld + properties: + attr: + properties: + active: + description: is domain active or not + type: boolean + aliases: + description: limit count of aliases associated with this domain + type: number + backupmx: + description: relay domain or not + type: boolean + defquota: + description: predefined mailbox quota in `add mailbox` form + type: number + description: + description: Description of domain + type: string + gal: + description: >- + is domain global address list active or not, it enables + shared contacts accross domain in SOGo webmail + type: boolean + mailboxes: + description: limit count of mailboxes associated with this domain + type: number + maxquota: + description: maximum quota per mailbox + type: number + quota: + description: maximum quota for this domain (for all mailboxes in sum) + type: number + relay_all_recipients: + description: >- + if not, them you have to create "dummy" mailbox for each + address to relay + type: boolean + relay_unknown_only: + description: Relay non-existing mailboxes only. Existing mailboxes will be delivered locally. + type: boolean + relayhost: + description: id of relayhost + type: number + rl_frame: + enum: + - s + - m + - h + - d + type: string + rl_value: + description: rate limit value + type: number + tags: + description: tags for this Domain + type: array + items: + type: string + type: object + items: + description: contains list of domain names you want update + type: array + items: + type: string + type: object + summary: Update domain + /api/v1/edit/domain/footer: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - edit + - domain_wide_footer + - domains: + - mailcow.tld + html: "
foo {= foo =}" + plain: "- + You can update the footer of one or more domains per request. + operationId: Update domain wide footer + requestBody: + content: + application/json: + schema: + example: + attr: + html: "
foo {= foo =}" + plain: "foo {= foo =}" + mbox_exclude: + - moo@mailcow.tld + items: mailcow.tld + properties: + attr: + properties: + html: + description: Footer text in HTML format + type: string + plain: + description: Footer text in PLAIN text format + type: string + mbox_exclude: + description: Array of mailboxes to exclude from domain wide footer + type: object + type: object + items: + description: contains a list of domain names where you want to update the footer + type: array + items: + type: string + type: object + summary: Update domain wide footer + /api/v1/edit/fail2ban: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + "*/*": + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Fail2Ban + description: >- + Using this endpoint you can edit the Fail2Ban config and black or + whitelist new ips. + operationId: Edit Fail2Ban + requestBody: + content: + application/json: + schema: + example: + attr: + ban_time: "86400" + ban_time_increment: "1" + blacklist: "10.100.6.5/32,10.100.8.4/32" + max_attempts: "5" + max_ban_time: "86400" + netban_ipv4: "24" + netban_ipv6: "64" + retry_window: "600" + whitelist: mailcow.tld + items: none + properties: + attr: + description: array containing the fail2ban settings + properties: + backlist: + description: the backlisted ips or hostnames separated by comma + type: string + ban_time: + description: the time an ip should be banned + type: number + ban_time_increment: + description: if the time of the ban should increase each time + type: boolean + max_attempts: + description: the maximum numbe of wrong logins before a ip is banned + type: number + max_ban_time: + description: the maximum time an ip should be banned + type: number + netban_ipv4: + description: the networks mask to ban for ipv4 + type: number + netban_ipv6: + description: the networks mask to ban for ipv6 + type: number + retry_window: + description: >- + the maximum time in which a ip as to login with false + credentials to be banned + type: number + whitelist: + description: whitelisted ips or hostnames sepereated by comma + type: string + type: object + items: + description: has to be none + type: object + summary: Edit Fail2Ban + /api/v1/edit/mailbox: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - edit + - mailbox + - active: "1" + force_pw_update: "0" + force_tfa: "0" + name: Full name + password: "*" + password2: "*" + quota: "3072" + sender_acl: + - default + - info@domain2.tld + - domain3.tld + - "*" + sogo_access: "1" + username: + - info@domain.tld + tags: ["tag3", "tag4"] + - null + msg: + - mailbox_modified + - info@domain.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Mailboxes + description: >- + You can update one or more mailboxes per request. You can also send just + attributes you want to change + operationId: Update mailbox + requestBody: + content: + application/json: + schema: + example: + attr: + active: "1" + force_pw_update: "0" + force_tfa: "0" + name: Full name + authsource: mailcow + password: "" + password2: "" + quota: "3072" + sender_acl: + - default + - info@domain2.tld + - domain3.tld + - "*" + sogo_access: "1" + tags: ["tag3", "tag4"] + items: + - info@domain.tld + properties: + attr: + properties: + active: + description: is mailbox active or not + type: boolean + force_pw_update: + description: force user to change password on next login + type: boolean + force_tfa: + description: force 2FA enrollment at login + type: boolean + name: + description: Full name of the mailbox user + type: string + authsource: + description: Specifies the authentication source for the mailbox. + type: string + enum: [mailcow, ldap, keycloak, generic-oidc] + password2: + description: new mailbox password for confirmation + type: string + password: + description: new mailbox password when using `mailcow` as the authentication source. + type: string + quota: + description: mailbox quota + type: number + sender_acl: + description: list of allowed send from addresses + type: object + sogo_access: + description: is access to SOGo webmail active or not + type: boolean + type: object + items: + description: contains list of mailboxes you want update + type: object + type: object + summary: Update mailbox + /api/v1/edit/mailbox/custom-attribute: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - mailbox + - edit + - mailbox_custom_attribute + - mailboxes: + - moo@mailcow.tld + attribute: + - role + - foo + value: + - cow + - bar + - null + msg: + - mailbox_modified + - moo@mailcow.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Mailboxes + description: >- + You can update custom attributes of one or more mailboxes per request. + operationId: Update mailbox custom attributes + requestBody: + content: + application/json: + schema: + example: + attr: + attribute: + - role + - foo + value: + - cow + - bar + items: + - moo@mailcow.tld + properties: + attr: + properties: + attribute: + description: Array of attribute keys + type: object + value: + description: Array of attribute values + type: object + type: object + items: + description: contains list of mailboxes you want update + type: object + type: object + summary: Update mailbox custom attributes + /api/v1/edit/mailq: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + msg: Task completed + type: success + description: OK + headers: {} + tags: + - Queue Manager + description: >- + Using this API you can flush the current mail queue. This will try to + deliver all mails currently in it. + + This API uses the command: `postqueue -f` + operationId: Flush Queue + requestBody: + content: + application/json: + schema: + example: + action: flush + properties: + action: + description: use flush to flush the mail queue + type: string + type: object + summary: Flush Queue + /api/v1/edit/pushover: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - pushover + - edit + - active: "0" + evaluate_x_prio: "0" + key: 21e8918e1jksdjcpis712 + only_x_prio: "0" + sound: "pushover" + senders: "" + senders_regex: "" + text: "" + title: Mail + token: 9023e2ohcwed27d1idu2 + username: + - info@domain.tld + msg: pushover_settings_edited + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Mailboxes + description: >- + Using this endpoint it is possible to update the pushover settings for + mailboxes + operationId: Update Pushover settings + requestBody: + content: + application/json: + schema: + example: + attr: + active: "0" + evaluate_x_prio: "0" + key: 21e8918e1jksdjcpis712 + only_x_prio: "0" + sound: "pushover" + senders: "" + senders_regex: "" + text: "" + title: Mail + token: 9023e2ohcwed27d1idu2 + items: info@domain.tld + properties: + attr: + properties: + active: + description: Enables pushover 1 disable pushover 0 + type: number + evaluate_x_prio: + description: Send the Push with High priority + type: number + key: + description: Pushover key + type: string + only_x_prio: + description: Only send push for prio mails + type: number + sound: + description: Set notification sound + type: string + senders: + description: Only send push for emails from these senders + type: string + senders_regex: + description: Regex to match senders for which a push will be send + type: string + text: + description: Custom push noficiation text + type: string + title: + description: Push title + type: string + token: + description: Pushover token + type: string + type: object + items: + description: contains list of mailboxes you want to delete + type: object + type: object + summary: Update Pushover settings + /api/v1/edit/quarantine_notification: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + description: OK + headers: {} + tags: + - Mailboxes + description: You can update one or more mailboxes per request. + operationId: Quarantine Notifications + requestBody: + content: + application/json: + schema: + example: + attr: + quarantine_notification: hourly + items: + anyOf: + - mailbox1@domain.tld + - mailbox2@domain.tld + properties: + attr: + properties: + quarantine_notification: + description: recurrence + enum: + - hourly + - daily + - weekly + - never + type: string + type: object + items: + description: >- + contains list of mailboxes you want set qurantine + notifications + type: object + type: object + summary: Quarantine Notifications + /api/v1/edit/syncjob: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + log: + - entity + - action + - object + msg: + - message + - entity name + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Sync jobs + description: >- + You can update one or more sync jobs per request. You can also send just + attributes you want to change. + operationId: Update sync job + requestBody: + content: + application/json: + schema: + example: + attr: + active: "1" + automap: "1" + custom_params: "" + delete1: "0" + delete2: "0" + delete2duplicates: "1" + enc1: SSL + exclude: (?i)spam|(?i)junk + host1: imap.server.tld + maxage: "0" + maxbytespersecond: "0" + mins_interval: "20" + password1: supersecret + port1: "993" + skipcrossduplicates: "0" + subfolder2: External + subscribeall: "1" + timeout1: "600" + timeout2: "600" + user1: username + items: "1" + properties: + attr: + properties: + active: + description: Is sync job active + type: boolean + automap: + description: >- + Try to automap folders ("Sent items", "Sent" => "Sent" + etc.) + type: boolean + custom_params: + description: Custom parameters passed to imapsync command + type: string + delete1: + description: Delete from source when completed + type: boolean + delete2: + description: Delete messages on destination that are not on source + type: boolean + delete2duplicates: + description: Delete duplicates on destination + type: boolean + enc1: + description: Encryption + enum: + - TLS + - SSL + - PLAIN + type: string + exclude: + description: Exclude objects (regex) + type: string + host1: + description: Hostname + type: string + maxage: + description: >- + Maximum age of messages in days that will be polled from + remote (0 = ignore age) + type: number + maxbytespersecond: + description: Max. bytes per second (0 = unlimited) + type: number + mins_interval: + description: Interval (min) + type: number + password1: + description: Password + type: string + port1: + description: Port + type: string + skipcrossduplicates: + description: >- + Skip duplicate messages across folders (first come, + first serve) + type: boolean + subfolder2: + description: >- + Sync into subfolder on destination (empty = do not use + subfolder) + type: string + subscribeall: + description: Subscribe all folders + type: boolean + timeout1: + description: Timeout for connection to remote host + type: number + timeout2: + description: Timeout for connection to local host + type: number + user1: + description: Username + type: string + type: object + items: + description: contains list of aliases you want update + type: object + type: object + summary: Update sync job + /api/v1/edit/user-acl: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - log: + - acl + - edit + - user + - user_acl: + - spam_alias + - tls_policy + - spam_score + - spam_policy + - delimiter_action + - syncjobs + - eas_reset + - quarantine + - sogo_profile_reset + - quarantine_attachments + - quarantine_notification + - app_passwds + - pushover + username: + - info@domain.tld + msg: + - acl_saved + - info@domain.tld + type: success + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Mailboxes + description: Using this endpoints its possible to update the ACL's for mailboxes + operationId: Update mailbox ACL + requestBody: + content: + application/json: + schema: + example: + attr: + user_acl: + - spam_alias + - tls_policy + - spam_score + - spam_policy + - delimiter_action + - syncjobs + - eas_reset + - quarantine + - sogo_profile_reset + - quarantine_attachments + - quarantine_notification + - app_passwds + - pushover + items: info@domain.tld + properties: + attr: + properties: + user_acl: + description: contains a list of active user acls + type: object + type: object + items: + description: contains list of mailboxes you want to delete + type: object + type: object + summary: Update mailbox ACL + "/api/v1/get/alias/{id}": + get: + parameters: + - description: id of entry you want to get + example: all + in: path + name: id + required: true + schema: + enum: + - all + - "1" + - "2" + - "5" + - "10" + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - active: "1" + address: alias@domain.tld + created: "2019-04-04 19:29:49" + domain: domain.tld + goto: destination@domain.tld + id: 6 + in_primary_domain: "" + is_catch_all: 0 + modified: null + private_comment: null + public_comment: null + - active: "1" + address: "@domain.tld" + created: "2019-04-27 13:42:39" + domain: domain.tld + goto: destination@domain.tld + id: 10 + in_primary_domain: "" + is_catch_all: 1 + modified: null + private_comment: null + public_comment: null + description: OK + headers: {} + tags: + - Aliases + description: You can list mailbox aliases existing in system. + operationId: Get aliases + summary: Get aliases + "/api/v1/get/time_limited_aliases/{mailbox}": + get: + parameters: + - description: mailbox you want to get aliasses from + example: domain.tld + in: path + schema: + type: string + name: mailbox + required: true + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - address: alias@domain.tld + goto: destination@domain.tld + validity: 1668251246 + created: "2021-11-12 12:07:26" + modified: null + description: OK + headers: {} + tags: + - Aliases + description: You can list time limited mailbox aliases existing in system. + operationId: Get time limited aliases + summary: Get time limited aliases + "/api/v1/get/app-passwd/all/{mailbox}": + get: + parameters: + - description: mailbox of entry you want to get + example: hello@mailcow.email + in: path + name: mailbox + required: true + schema: + enum: + - hello@mailcow.email + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - active: "1" + created: "2019-12-21 16:04:55" + domain: mailcow.email + id: 2 + mailbox: hello@mailcow.email + modified: null + name: emclient + description: OK + headers: {} + tags: + - App Passwords + description: >- + Using this endpoint you can get all app passwords from a specific + mailbox. + operationId: Get App Password + summary: Get App Password + "/api/v1/get/bcc/{id}": + get: + parameters: + - description: id of entry you want to get + example: all + in: path + name: id + required: true + schema: + enum: + - all + - "1" + - "2" + - "5" + - "10" + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - active: "1" + bcc_dest: bcc@awesomecow.tld + created: "2019-10-02 21:44:34" + domain: mailcow.tld + id: 3 + local_dest: "@mailcow.tld" + modified: null + type: sender + description: OK + headers: {} + tags: + - Address Rewriting + description: Using this endpoint you can get all BCC maps. + operationId: Get BCC Map + summary: Get BCC Map + "/api/v1/get/dkim/{domain}": + get: + parameters: + - description: name of domain + in: path + name: domain + required: true + schema: + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + dkim_selector: dkim + dkim_txt: >- + v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA21tUSjyasQy/hJmVjPnlRGfzx6TPhYj8mXY9DVOzSAE64Gddw/GnE/GcCR6WXNT23u9q4zPnz1IPoNt5kFOps8vg/iNqrcH++494noaZuYyFPPFnebkfryO4EvEyxC/c66qts+gnOUml+M8uv5WObBJld2gG12jLwFM0263J/N6J8LuUsaXOB2uCIfx8Nf4zjuJ6Ieez2uyHNK5dXjDLfKA4mTr+EEK6W6e34M4KN1liWM6r9Oy5S1FlLrD42VpURxxBZtBiEtaJPEKSQuk6GQz8ihu7W20Yr53tyCdaORu8dhxXVUWVf+GjuuMEdAmQCjYkarXdYCrt56Psw703kwIDAQAB + length: "2048" + privkey: "" + pubkey: >- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA21tUSjyasQy/hJmVjPnlRGfzx6TPhYj8mXY9DVOzSAE64Gddw/GnE/GcCR6WXNT23u9q4zPnz1IPoNt5kFOps8vg/iNqrcH++494noaZuYyFPPFnebkfryO4EvEyxC/c66qts+gnOUml+M8uv5WObBJld2gG12jLwFM0263J/N6J8LuUsaXOB2uCIfx8Nf4zjuJ6Ieez2uyHNK5dXjDLfKA4mTr+EEK6W6e34M4KN1liWM6r9Oy5S1FlLrD42VpURxxBZtBiEtaJPEKSQuk6GQz8ihu7W20Yr53tyCdaORu8dhxXVUWVf+GjuuMEdAmQCjYkarXdYCrt56Psw703kwIDAQAB + description: OK + headers: {} + tags: + - DKIM + description: >- + Using this endpoint you can get the DKIM public key for a specific + domain. + operationId: Get DKIM Key + summary: Get DKIM Key + /api/v1/get/domain-admin/all: + get: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - active: "1" + created: "2019-10-02 10:29:41" + selected_domains: + - mailcow.tld + tfa_active: "0" + unselected_domains: + - awesomemailcow.de + - mailcowisgreat.de + username: testadmin + description: OK + headers: {} + tags: + - Domain admin + description: "" + operationId: Get Domain Admins + summary: Get Domain Admins + "/api/v1/get/domain/{id}": + get: + parameters: + - description: id of entry you want to get + example: all + in: path + name: id + required: true + schema: + enum: + - all + - mailcow.tld + type: string + - description: comma seperated list of tags to filter by + example: "tag1,tag2" + in: query + name: tags + required: false + schema: + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - active: "1" + aliases_in_domain: 0 + aliases_left: 400 + backupmx: "0" + bytes_total: "5076666944" + def_new_mailbox_quota: 3221225472 + def_quota_for_mbox: 3221225472 + description: Some description + domain_name: domain.tld + gal: "0" + max_new_mailbox_quota: 10737418240 + max_num_aliases_for_domain: 400 + max_num_mboxes_for_domain: 10 + max_quota_for_domain: 10737418240 + max_quota_for_mbox: 10737418240 + mboxes_in_domain: 0 + mboxes_left: 10 + msgs_total: "172440" + quota_used_in_domain: "0" + relay_all_recipients: "0" + relayhost: "0" + rl: false + tags: ["tag1", "tag2"] + - active: "1" + aliases_in_domain: 0 + aliases_left: 400 + backupmx: "1" + bytes_total: "5076666944" + def_new_mailbox_quota: 3221225472 + def_quota_for_mbox: 3221225472 + description: domain description + domain_name: domain2.tld + gal: "0" + max_new_mailbox_quota: 10737418240 + max_num_aliases_for_domain: 400 + max_num_mboxes_for_domain: 10 + max_quota_for_domain: 10737418240 + max_quota_for_mbox: 10737418240 + mboxes_in_domain: 0 + mboxes_left: 10 + msgs_total: "172440" + quota_used_in_domain: "0" + relay_all_recipients: "0" + relayhost: "0" + rl: false + tags: ["tag3", "tag4"] + description: OK + headers: {} + tags: + - Domains + description: You can list all domains existing in system. + operationId: Get domains + summary: Get domains + /api/v1/get/fail2ban: + get: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + ban_time: 604800 + ban_time_increment: 1 + blacklist: |- + 45.82.153.37/32 + 92.118.38.52/32 + max_attempts: 1 + max_ban_time: 604800 + netban_ipv4: 32 + netban_ipv6: 128 + perm_bans: + - 45.82.153.37/32 + - 92.118.38.52/32 + retry_window: 7200 + whitelist: 1.1.1.1 + description: OK + headers: {} + tags: + - Fail2Ban + description: Gets the current Fail2Ban configuration. + operationId: Get Fail2Ban Config + summary: Get Fail2Ban Config + /api/v1/get/fwdhost/all: + get: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - host: 5.1.76.202 + keep_spam: "yes" + source: hosted.mailcow.de + - host: "2a00:f820:417::202" + keep_spam: "yes" + source: hosted.mailcow.de + description: OK + headers: {} + tags: + - Fordwarding Hosts + description: You can list all Forwarding Hosts in your mailcow. + operationId: Get Forwarding Hosts + summary: Get Forwarding Hosts + "/api/v1/get/logs/acme/{count}": + get: + parameters: + - description: Number of logs to return + in: path + name: count + required: true + schema: + type: number + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - message: >- + Certificate validation done, neither changed nor due for + renewal, sleeping for another day. + time: "1569927728" + description: OK + headers: {} + tags: + - Logs + description: >- + This Api endpoint lists all ACME logs from issued Lets Enctypts + certificates. + + Tip: You can limit how many logs you want to get by using `/` at + the end of the api url. + operationId: Get ACME logs + summary: Get ACME logs + "/api/v1/get/logs/api/{count}": + get: + parameters: + - description: Number of logs to return + in: path + name: count + required: true + schema: + type: number + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - data: "" + method: GET + remote: 1.1.1.1 + time: 1569939001 + uri: /api/v1/get/logs/api/2 + description: OK + headers: {} + tags: + - Logs + description: >- + This Api endpoint lists all Api logs. + + Tip: You can limit how many logs you want to get by using `/` at + the end of the api url. + operationId: Get Api logs + summary: Get Api logs + "/api/v1/get/logs/autodiscover/{count}": + get: + parameters: + - description: Number of logs to return + in: path + name: count + required: true + schema: + type: number + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - service: activesync + time: 1569684212 + ua: >- + Microsoft Office/16.0 (Windows NT 6.2; MAPICPL + 16.0.11328; Pro) + user: awesome@mailcow.de + description: OK + headers: {} + tags: + - Logs + description: >- + This Api endpoint lists all Autodiscover logs. + + Tip: You can limit how many logs you want to get by using `/` at + the end of the api url. + operationId: Get Autodiscover logs + summary: Get Autodiscover logs + "/api/v1/get/logs/dovecot/{count}": + get: + parameters: + - description: Number of logs to return + in: path + name: count + required: true + schema: + type: number + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - message: >- + managesieve-login: Disconnected (no auth attempts in 0 + secs): user=<>, rip=172.22.1.3, lip=172.22.1.250 + priority: info + program: dovecot + time: "1569938740" + description: OK + headers: {} + tags: + - Logs + description: >- + This Api endpoint lists all Dovecot logs. + + Tip: You can limit how many logs you want to get by using `/` at + the end of the api url. + operationId: Get Dovecot logs + summary: Get Dovecot logs + "/api/v1/get/logs/netfilter/{count}": + get: + parameters: + - description: Number of logs to return + in: path + name: count + required: true + schema: + type: number + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - message: "Whitelist was changed, it has 1 entries" + priority: info + time: 1569754911 + - message: Add host/network 1.1.1.1/32 to blacklist + priority: crit + time: 1569754911 + description: OK + headers: {} + tags: + - Logs + description: >- + This Api endpoint lists all Netfilter logs. + + Tip: You can limit how many logs you want to get by using `/` at + the end of the api url. + operationId: Get Netfilter logs + summary: Get Netfilter logs + "/api/v1/get/logs/postfix/{count}": + get: + parameters: + - description: Number of logs to return + in: path + name: count + required: true + schema: + type: number + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - message: "EF1711500458: removed" + priority: info + program: postfix/qmgr + time: "1569937433" + description: OK + headers: {} + tags: + - Logs + description: >- + This Api endpoint lists all Postfix logs. + + Tip: You can limit how many logs you want to get by using `/` at + the end of the api url. + operationId: Get Postfix logs + summary: Get Postfix logs + "/api/v1/get/logs/ratelimited/{count}": + get: + parameters: + - description: Number of logs to return + in: path + name: count + required: true + schema: + type: number + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - from: awesome@mailcow.email + header_from: '"Awesome" ' + header_subject: Mailcow is amazing + ip: 172.22.1.248 + message_id: 6a-5d892500-7-240abd80@90879116 + qid: E3CF91500458 + rcpt: hello@mailcow.email + rl_hash: RLsdz3tuabozgd4oacbdh8kc78 + rl_info: mailcow(RLsdz3tuabozgd4oacbdh8kc78) + rl_name: mailcow + time: 1569269003 + user: awesome@mailcow.email + description: OK + headers: {} + tags: + - Logs + description: >- + This Api endpoint lists all Ratelimit logs. + + Tip: You can limit how many logs you want to get by using `/` at + the end of the api url. + operationId: Get Ratelimit logs + summary: Get Ratelimit logs + "/api/v1/get/logs/rspamd-history/{count}": + get: + parameters: + - description: Number of logs to return + in: path + name: count + required: true + schema: + type: number + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + description: OK + headers: {} + tags: + - Logs + description: >- + This Api endpoint lists all Rspamd logs. + + Tip: You can limit how many logs you want to get by using `/` at + the end of the api url. + operationId: Get Rspamd logs + summary: Get Rspamd logs + "/api/v1/get/logs/sogo/{count}": + get: + parameters: + - description: Number of logs to return + in: path + name: count + required: true + schema: + type: number + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - message: >- + [109]: + mailcowdockerized_watchdog-mailcow_1.mailcowdockerized_mailcow-network + "GET /SOGo.index/ HTTP/1.1" 200 2531/0 0.005 - - 0 + priority: notice + program: sogod + time: "1569938874" + description: OK + headers: {} + tags: + - Logs + description: >- + This Api endpoint lists all SOGo logs. + + Tip: You can limit how many logs you want to get by using `/` at + the end of the api url. + operationId: Get SOGo logs + summary: Get SOGo logs + "/api/v1/get/logs/watchdog/{count}": + get: + parameters: + - description: Number of logs to return + in: path + name: count + required: true + schema: + type: number + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - hpdiff: "0" + hpnow: "1" + hptotal: "1" + lvl: "100" + service: Fail2ban + time: "1569938958" + - hpdiff: "0" + hpnow: "5" + hptotal: "5" + lvl: "100" + service: Rspamd + time: "1569938956" + description: OK + headers: {} + tags: + - Logs + description: >- + This Api endpoint lists all Watchdog logs. + + Tip: You can limit how many logs you want to get by using `/` at + the end of the api url. + operationId: Get Watchdog logs + summary: Get Watchdog logs + "/api/v1/get/mailbox/{id}": + get: + parameters: + - description: id of entry you want to get + example: all + in: path + name: id + required: true + schema: + enum: + - all + - user@domain.tld + type: string + - description: comma seperated list of tags to filter by + example: "tag1,tag2" + in: query + name: tags + required: false + schema: + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - active: "1" + attributes: + force_pw_update: "0" + force_tfa: "0" + mailbox_format: "maildir:" + quarantine_notification: never + sogo_access: "1" + tls_enforce_in: "0" + tls_enforce_out: "0" + domain: doman3.tld + is_relayed: 0 + local_part: info + max_new_quota: 10737418240 + messages: 0 + name: Full name + percent_class: success + percent_in_use: 0 + quota: 3221225472 + quota_used: 0 + rl: false + sender_acl: ["otherbox@doman3.tld", "@aliasdomain.tld"] + spam_aliases: 0 + username: info@doman3.tld + tags: ["tag1", "tag2"] + description: OK + headers: {} + tags: + - Mailboxes + description: You can list all mailboxes existing in system. + operationId: Get mailboxes + summary: Get mailboxes + /api/v1/get/mailq/all: + get: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - arrival_time: 1570091234 + message_size: 1848 + queue_id: B98C6260CA1 + queue_name: incoming + recipients: + - recipient@awesomecow.tld + sender: sender@mailcow.tld + description: OK + headers: {} + tags: + - Queue Manager + description: Get the current mail queue and everything it contains. + operationId: Get Queue + summary: Get Queue + "/api/v1/get/oauth2-client/{id}": + get: + parameters: + - description: id of entry you want to get + example: all + in: path + name: id + required: true + schema: + enum: + - all + - "1" + - "2" + - "5" + - "10" + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - client_id: 17c76aaa88c0 + client_secret: 73fc668a88147e32a31ff80c + grant_types: null + id: 1 + redirect_uri: "https://mailcow.tld" + scope: profile + user_id: null + description: OK + headers: {} + tags: + - oAuth Clients + description: Using this endpoint you can get all oAuth clients. + operationId: Get oAuth Clients + summary: Get oAuth Clients + "/api/v1/get/policy_bl_domain/{domain}": + get: + parameters: + - description: name of domain + in: path + name: domain + required: true + schema: + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - object: domain.tld + prefid: 2 + value: "*@baddomain.tld" + description: OK + headers: {} + tags: + - Domain antispam policies + description: You can list all blacklist policies per domain. + operationId: List blacklist domain policy + summary: List blacklist domain policy + "/api/v1/get/policy_wl_domain/{domain}": + get: + parameters: + - description: name of domain + in: path + name: domain + required: true + schema: + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - object: domain.tld + prefid: 1 + value: "*@gooddomain.tld" + description: OK + headers: {} + tags: + - Domain antispam policies + description: You can list all whitelist policies per domain. + operationId: List whitelist domain policy + summary: List whitelist domain policy + /api/v1/get/quarantine/all: + get: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + created: 1572688831 + id: 33 + notified: 1 + qid: 8224615004C1 + rcpt: admin@domain.tld + score: 15.48 + sender: bounces@send.domain.tld + subject: mailcow is awesome + virus_flag: 0 + description: OK + headers: {} + tags: + - Quarantine + description: Get all mails that are currently in Quarantine. + operationId: Get mails in Quarantine + summary: Get mails in Quarantine + "/api/v1/get/recipient_map/{id}": + get: + parameters: + - description: id of entry you want to get + example: all + in: path + name: id + required: true + schema: + enum: + - all + - "1" + - "2" + - "5" + - "10" + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - active: "1" + created: "2019-10-02 22:06:29" + id: 3 + modified: null + recipient_map_new: target@mailcow.tld + recipient_map_old: recipient@mailcow.tld + description: OK + headers: {} + tags: + - Address Rewriting + description: Using this endpoint you can get all recipient maps. + operationId: Get Recipient Map + summary: Get Recipient Map + "/api/v1/get/relayhost/{id}": + get: + parameters: + - description: id of entry you want to get + example: all + in: path + name: id + required: true + schema: + enum: + - all + - "1" + - "2" + - "5" + - "10" + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - active: "1" + hostname: "mailcow.tld:25" + id: 1 + password: supersecurepassword + password_short: tes... + used_by_domains: "" + username: testuser + description: OK + headers: {} + tags: + - Routing + description: Using this endpoint you can get all Sender-Dependent Transports. + operationId: Get Sender-Dependent Transports + summary: Get Sender-Dependent Transports + /api/v1/get/resource/all: + get: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - active: "1" + description: test + domain: mailcow.tld + kind: location + local_part: test + multiple_bookings: 0 + name: test@mailcow.tld + description: OK + headers: {} + tags: + - Resources + description: Using this endpoint you can get all Resources. + operationId: Get Resources + summary: Get Resources + "/api/v1/get/rl-mbox/{mailbox}": + get: + parameters: + - description: name of mailbox or all + in: path + name: mailbox + required: true + schema: + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - frame: s + mailbox: leon@mailcow.tld + value: "5" + - frame: s + mailbox: lisa@mailcow.tld + value: "3" + description: OK + headers: {} + tags: + - Ratelimits + description: >- + Using this endpoint you can get the ratelimits for a certain mailbox. + You can use all for all mailboxes. + operationId: Get mailbox ratelimits + summary: Get mailbox ratelimits + "/api/v1/get/rl-domain/{domain}": + get: + parameters: + - description: name of domain or all + in: path + name: domain + required: true + schema: + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - frame: s + domain: domain.tld + value: "5" + - frame: s + mailbox: domain2.tld + value: "3" + description: OK + headers: {} + tags: + - Ratelimits + description: >- + Using this endpoint you can get the ratelimits for a certain domains. + You can use all for all domain. + operationId: Get domain ratelimits + summary: Get domain ratelimits + /api/v1/edit/rl-mbox/: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - type: success + log: + - ratelimit + - edit + - mailbox + - object: + - info@domain.tld + rl_value: "10" + rl_frame: h + msg: + - rl_saved + - info@domain.tld + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Ratelimits + description: >- + Using this endpoint you can edit the ratelimits for a certain mailbox. + operationId: Edit mailbox ratelimits + requestBody: + content: + application/json: + schema: + example: + attr: + rl_value: "10" + rl_frame: "h" + items: + - info@domain.tld + properties: + attr: + properties: + rl_frame: + description: contains the frame for the ratelimit h,s,m + type: string + rl_value: + description: contains the rate for the ratelimit 10,20,50,1 + type: number + type: object + items: + description: contains list of mailboxes you want to edit the ratelimit of + type: object + type: object + summary: Edit mailbox ratelimits + /api/v1/edit/rl-domain/: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - type: success + - log: + - ratelimit + - edit + - domain + - object: + - domain.tld + rl_value: "50" + rl_frame: "h" + msg: + - rl_saved + - domain.tld + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Ratelimits + description: >- + Using this endpoint you can edit the ratelimits for a certain domains. + operationId: Edit domain ratelimits + requestBody: + content: + application/json: + schema: + example: + attr: + rl_value: "10" + rl_frame: "h" + items: + - domain.tld + properties: + attr: + properties: + rl_frame: + description: contains the frame for the ratelimit h,s,m + type: string + rl_value: + description: contains the rate for the ratelimit 10,20,50,1 + type: number + type: object + items: + description: contains list of domains you want to edit the ratelimit of + type: object + type: object + summary: Edit domain ratelimits + /api/v1/get/status/containers: + get: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + acme-mailcow: + container: acme-mailcow + image: "mailcow/acme:1.63" + started_at: "2019-12-22T21:00:08.270660275Z" + state: running + type: info + clamd-mailcow: + container: clamd-mailcow + image: "mailcow/clamd:1.35" + started_at: "2019-12-22T21:00:01.622856172Z" + state: running + type: info + dockerapi-mailcow: + container: dockerapi-mailcow + image: "mailcow/dockerapi:1.36" + started_at: "2019-12-22T20:59:59.984797808Z" + state: running + type: info + dovecot-mailcow: + container: dovecot-mailcow + image: "mailcow/dovecot:1.104" + started_at: "2019-12-22T21:00:08.988680259Z" + state: running + type: info + ipv6nat-mailcow: + container: ipv6nat-mailcow + image: robbertkl/ipv6nat + started_at: "2019-12-22T21:06:37.273225445Z" + state: running + type: info + memcached-mailcow: + container: memcached-mailcow + image: "memcached:alpine" + started_at: "2019-12-22T20:59:58.0907785Z" + state: running + type: info + mysql-mailcow: + container: mysql-mailcow + image: "mariadb:10.3" + started_at: "2019-12-22T21:00:02.201937528Z" + state: running + type: info + netfilter-mailcow: + container: netfilter-mailcow + image: "mailcow/netfilter:1.31" + started_at: "2019-12-22T21:00:09.851559297Z" + state: running + type: info + nginx-mailcow: + container: nginx-mailcow + image: "nginx:mainline-alpine" + started_at: "2019-12-22T21:00:12.9843038Z" + state: running + type: info + olefy-mailcow: + container: olefy-mailcow + image: "mailcow/olefy:1.2" + started_at: "2019-12-22T20:59:59.676259274Z" + state: running + type: info + php-fpm-mailcow: + container: php-fpm-mailcow + image: "mailcow/phpfpm:1.55" + started_at: "2019-12-22T21:00:00.955808957Z" + state: running + type: info + postfix-mailcow: + container: postfix-mailcow + image: "mailcow/postfix:1.44" + started_at: "2019-12-22T21:00:07.186717617Z" + state: running + type: info + redis-mailcow: + container: redis-mailcow + image: "redis:5-alpine" + started_at: "2019-12-22T20:59:56.827166834Z" + state: running + type: info + rspamd-mailcow: + container: rspamd-mailcow + image: "mailcow/rspamd:1.56" + started_at: "2019-12-22T21:00:12.456075355Z" + state: running + type: info + sogo-mailcow: + container: sogo-mailcow + image: "mailcow/sogo:1.65" + started_at: "2019-12-22T20:59:58.382274592Z" + state: running + type: info + unbound-mailcow: + container: unbound-mailcow + image: "mailcow/unbound:1.10" + started_at: "2019-12-22T20:59:58.760595825Z" + state: running + type: info + watchdog-mailcow: + container: watchdog-mailcow + image: "mailcow/watchdog:1.65" + started_at: "2019-12-22T20:59:56.028660382Z" + state: running + type: info + description: OK + headers: {} + tags: + - Status + description: >- + Using this endpoint you can get the status of all containers and when + hey where started and a few other details. + operationId: Get container status + summary: Get container status + /api/v1/get/status/vmail: + get: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + disk: /dev/mapper/mail--vg-root + total: 41G + type: info + used: 11G + used_percent: 28% + description: OK + headers: {} + tags: + - Status + description: >- + Using this endpoint you can get the status of the vmail and the amount + of used storage. + operationId: Get vmail status + summary: Get vmail status + /api/v1/get/status/version: + get: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + version: "2022-04" + description: OK + headers: {} + tags: + - Status + description: >- + Using this endpoint you can get the current running release of this + instance. + operationId: Get version status + summary: Get version status + /api/v1/get/syncjobs/all/no_log: + get: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - active: "1" + authmd51: 0 + authmech1: PLAIN + automap: 1 + created: "2019-05-22 11:37:25" + custom_params: "" + delete1: 0 + delete2: 0 + delete2duplicates: 1 + domain2: "" + enc1: TLS + exclude: (?i)spam|(?i)junk + host1: imap.server.tld + id: 1 + is_running: 0 + last_run: "2019-05-22 11:40:02" + log: "" + maxage: 0 + maxbytespersecond: "0" + mins_interval: "20" + modified: "2019-05-22 11:40:02" + port1: 993 + regextrans2: "" + skipcrossduplicates: 0 + subfolder2: External + subscribeall: 1 + timeout1: 600 + timeout2: 600 + user1: username + user2: mailbox@domain.tld + description: OK + headers: {} + tags: + - Sync jobs + description: You can list all syn jobs existing in system. + operationId: Get sync jobs + summary: Get sync jobs + "/api/v1/get/tls-policy-map/{id}": + get: + parameters: + - description: id of entry you want to get + example: all + in: path + name: id + required: true + schema: + enum: + - all + - "1" + - "2" + - "5" + - "10" + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - parameters: "" + active: "1" + created: "2019-10-03 08:42:12" + dest: mailcow.tld + id: 1 + modified: null + policy: encrypt + description: OK + headers: {} + tags: + - Outgoing TLS Policy Map Overrides + description: Using this endpoint you can get all TLS policy map override maps. + operationId: Get TLS Policy Map + summary: Get TLS Policy Map + "/api/v1/get/transport/{id}": + get: + parameters: + - description: id of entry you want to get + example: all + in: path + name: id + required: true + schema: + enum: + - all + - "1" + - "2" + - "5" + - "10" + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - active: "1" + destination: example.org + id: 1 + lookup_mx: "0" + nexthop: "host:25" + password: supersecurepw + password_short: sup... + username: testuser + description: OK + headers: {} + tags: + - Routing + description: Using this endpoint you can get all Transport Maps. + operationId: Get Transport Maps + summary: Get Transport Maps + /api/v1/edit/spam-score/: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - type: success + log: + - mailbox + - edit + - spam_score + - username: + - info@domain.tld + spam_score: "8,15" + msg: + - mailbox_modified + - info@domain.tld + schema: + properties: + log: + description: contains request object + items: {} + type: array + msg: + items: {} + type: array + type: + enum: + - success + - danger + - error + type: string + type: object + description: OK + headers: {} + tags: + - Mailboxes + description: >- + Using this endpoint you can edit the spam filter score for a certain mailbox. + operationId: Edit mailbox spam filter score + requestBody: + content: + application/json: + schema: + example: + - items: + - info@domain.tld + attr: + spam_score: "8,15" + summary: Edit mailbox spam filter score + "/api/v1/get/mailbox/all/{domain}": + get: + parameters: + - description: name of domain + in: path + name: domain + required: true + schema: + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - active: "1" + attributes: + force_pw_update: "0" + force_tfa: "0" + mailbox_format: "maildir:" + quarantine_notification: never + sogo_access: "1" + tls_enforce_in: "0" + tls_enforce_out: "0" + custom_attributes: {} + domain: domain3.tld + is_relayed: 0 + local_part: info + max_new_quota: 10737418240 + messages: 0 + name: Full name + percent_class: success + percent_in_use: 0 + quota: 3221225472 + quota_used: 0 + rl: false + sender_acl: ["otherbox@domain3.tld", "@aliasdomain.tld"] + spam_aliases: 0 + username: info@domain3.tld + tags: ["tag1", "tag2"] + description: OK + headers: {} + tags: + - Mailboxes + description: You can list all mailboxes existing in system for a specific domain. + operationId: Get mailboxes of a domain + summary: Get mailboxes of a domain + /api/v1/edit/cors: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - type: "success" + log: ["cors", "edit", {"allowed_origins": ["*", "https://mail.mailcow.tld"], "allowed_methods": ["POST", "GET", "DELETE", "PUT"]}] + msg: "cors_headers_edited" + description: OK + headers: { } + tags: + - Cross-Origin Resource Sharing (CORS) + description: >- + This endpoint allows you to manage Cross-Origin Resource Sharing (CORS) settings for the API. + CORS is a security feature implemented by web browsers to prevent unauthorized cross-origin requests. + By editing the CORS settings, you can specify which domains and which methods are permitted to access the API resources from outside the mailcow domain. + operationId: Edit Cross-Origin Resource Sharing (CORS) settings + requestBody: + content: + application/json: + schema: + example: + attr: + allowed_origins: ["*", "https://mail.mailcow.tld"] + allowed_methods: ["POST", "GET", "DELETE", "PUT"] + properties: + attr: + type: object + properties: + allowed_origins: + type: array + items: + type: string + allowed_methods: + type: array + items: + type: string + summary: Edit Cross-Origin Resource Sharing (CORS) settings + "/api/v1/get/spam-score/{mailbox}": + get: + parameters: + - description: name of mailbox or empty for current user - admin user will retrieve the global spam filter score + in: path + name: mailbox + required: true + schema: + type: string + - description: e.g. api-key-string + example: api-key-string + in: header + name: X-API-Key + required: false + schema: + type: string + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + spam_score: "8,15" + description: OK + headers: {} + tags: + - Mailboxes + description: >- + Using this endpoint you can get the global spam filter score or the spam filter score of a certain mailbox. + operationId: Get mailbox or global spam filter score + summary: Get mailbox or global spam filter score + /api/v1/edit/identity-provider: + post: + responses: + "401": + $ref: "#/components/responses/Unauthorized" + "200": + content: + application/json: + examples: + response: + value: + - type: "success" + log: + - "identity_provider" + - "edit" + - authsource: "keycloak" + server_url: "https://auth.mailcow.tld" + realm: "mailcow" + client_id: "mailcow_client" + client_secret: "*" + redirect_url: "https://mail.mailcow.tld" + redirect_url_extra: ["https://extramail.mailcow.tld"] + version: "26.1.3" + default_template: "Default" + mappers: + - "small_mbox" + - "medium_mbox" + templates: + - "small" + - "medium" + ignore_ssl_error: true + mailpassword_flow: true + periodic_sync: true + import_users: true + sync_interval: 30 + msg: + - "object_modified" + - "" + description: OK + headers: { } + tags: + - Identity Provider + description: >- + Configure an external Identity Provider to use as user authentication + operationId: Edit external Identity Provider settings + requestBody: + content: + application/json: + schema: + properties: + items: + type: array + default: ["identity-provider"] + attr: + type: object + properties: + authsource: + description: Specifies the type of the Identity Provider + type: string + enum: [ldap, keycloak, generic-oidc] + server_url: + description: The base URL of your Keycloak server. Required if `authsource` is keycloak. + type: string + realm: + description: The Keycloak realm where the mailcow client is configured. Required if `authsource` is keycloak. + type: string + client_id: + description: The Client ID assigned to mailcow Client in OIDC Provider. Required if `authsource` is keycloak or generic-oidc. + type: string + client_secret: + description: The Client Secret assigned to mailcow Client in OIDC Provider. Required if `authsource` is keycloak or generic-oidc. + type: string + redirect_url: + description: The redirect URL that OIDC Provider will use after authentication. Required if `authsource` is keycloak or generic-oidc. + type: string + redirect_url_extra: + description: Additional redirect URLs that OIDC Provider can use after authentication if valid. + type: array + version: + description: Specifies the Keycloak version. Required if `authsource` is keycloak. + type: string + default_template: + description: (Optional) If no matching Attribute Mapping exists for a User, the default template will be used for creating the mailbox, but not for updating the mailbox. + type: string + mappers: + description: (Optional) Attribute values used to match a mailbox template. Each element corresponds to the respective index in the templates array (i.e., the first element matches the first element of templates, the second matches the second, and so on). + type: array + templates: + description: (Optional) Defines the mailbox templates to be assigned. Each element corresponds to the respective index in the `mappers` array. + type: array + ignore_ssl_error: + description: If enabled, SSL certificate validation is bypassed + type: boolean + default: false + mailpassword_flow: + description: If enabled, mailcow will attempt to validate user credentials using the Keycloak Admin REST API instead of relying solely on the Authorization Code Flow. + type: boolean + default: false + periodic_sync: + description: If enabled, mailcow periodically performs a full sync of all users from Keycloak or LDAP. + type: boolean + default: false + import_users: + description: If enabled, new users are automatically imported from Keycloak or LDAP into mailcow. + type: boolean + default: false + sync_interval: + description: Defines the time interval (in minutes) for periodic synchronization and user imports. + type: number + default: 15 + host: + description: The address of your LDAP server. You can provide a single hostname or a comma-separated list of hosts for fallback in case the primary server is unreachable. Required if `authsource` is ldap. + type: string + port: + description: The port used to connect to the LDAP server. Required if `authsource` is ldap. + type: string + use_ssl: + description: enable LDAPS connection. If Port is set to 389 it will be overriden to 636. + type: boolean + default: false + use_tls: + description: enable TLS connection. TLS is recommended over SSL. SSL Ports cannot be used. + type: boolean + default: false + basedn: + description: The Distinguished Name (DN) from which searches will be performed. Required if `authsource` is ldap. + type: string + username_field: + description: The LDAP attribute used to identify users during authentication. Required if `authsource` is ldap. + type: string + default: mail + filter: + description: An optional LDAP search filter to refine which users can authenticate. + type: string + attribute_field: + description: Specifies an LDAP attribute that holds a specific value which can be mapped to a mailbox template using the Attribute Mapping section. Required if `authsource` is ldap. + type: string + binddn: + description: The Distinguished Name (DN) of the LDAP user that will be used to authenticate and perform LDAP searches. This account should have sufficient permissions to read the required attributes. Required if `authsource` is ldap. + type: string + bindpass: + description: The password for the Bind DN user. It is required for authentication when connecting to the LDAP server. Required if `authsource` is ldap. + type: string + authorize_url: + description: The OIDC provider's authorization server URL. Required if `authsource` is generic-oidc. + type: string + token_url: + description: The OIDC provider's token server URL. Required if `authsource` is generic-oidc. + type: string + userinfo_url: + description: The OIDC provider's user info server URL. Required if `authsource` is generic-oidc. + type: string + client_scopes: + description: Specifies the OIDC scopes requested during authentication. + type: string + default: "openid profile email mailcow_template" + examples: + keycloak: + value: + items: + - "identity-provider" + attr: + authsource: "keycloak" + server_url: "https://auth.mailcow.tld" + realm: "mailcow" + client_id: "mailcow_client" + client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf" + redirect_url: "https://mail.mailcow.tld" + redirect_url_extra: ["https://extramail.mailcow.tld"] + version: "26.1.3" + default_template: "Default" + mappers: ["small_mbox", "medium_mbox"] + templates: ["small", "medium"] + ignore_ssl_error: true + mailpassword_flow: true + periodic_sync: true + import_users: true + sync_interval: 30 + ldap: + value: + items: + - "identity-provider" + attr: + authsource: "ldap" + host: "127.0.0.1" + port: "389" + use_ssl: false + use_tls: false + ignore_ssl_error: false + basedn: "DC=mailcow,DC=local" + username_field: "mail" + filter: "(memberOf:1.2.840.113556.1.4.1941:=DC=mailcow,DC=local)" + attribute_field: "othermailbox" + binddn: "CN=LDAP Read Only,CN=Users,DC=mailcow,DC=local" + bindpass: "moohoo" + default_template: "Default" + mappers: ["small_mbox", "medium_mbox"] + templates: ["small", "medium"] + periodic_sync: true + import_users: true + sync_interval: 30 + generic-oidc: + value: + items: + - "identity-provider" + attr: + authsource: "generic-oidc" + authorize_url: "https://auth.mailcow.tld/application/o/authorize/" + token_url: "https://auth.mailcow.tld/application/o/token/" + userinfo_url: "https://auth.mailcow.tld/application/o/userinfo/" + client_id: "mailcow_client" + client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf" + redirect_url: "https://mail.mailcow.tld" + redirect_url_extra: ["https://extramail.mailcow.tld"] + client_scopes: "openid profile email mailcow_template" + default_template: "Default" + mappers: ["small_mbox", "medium_mbox"] + templates: ["small", "medium"] + ignore_ssl_error: true + summary: Edit external Identity Provider + +tags: + - name: Domains + description: You can create antispam whitelist and blacklist policies + - name: Domain antispam policies + description: You can edit the Domain Antispam policies + - name: Mailboxes + description: You can manage mailboxes + - name: Aliases + description: You can manage aliases + - name: Sync jobs + description: Using Syncjobs you can sync your mails with other email servers + - name: Fordwarding Hosts + description: Forwarding Hosts enable you to send mail using a relay + - name: Logs + description: Get all mailcow system logs + - name: Queue Manager + description: Manage the postfix mail queue + - name: Quarantine + description: Check what emails went to quarantine + - name: Fail2Ban + description: Manage the Netfilter fail2ban options + - name: DKIM + description: Manage DKIM keys + - name: Domain admin + description: Create or udpdate domain admin users + - name: Single Sign-On + description: Issue tokens for users + - name: Address Rewriting + description: Create BCC maps or recipient maps + - name: Outgoing TLS Policy Map Overrides + description: Force global TLS policys + - name: oAuth Clients + description: Use mailcow as a oAuth server + - name: Routing + description: Define your own email routes + - name: Resources + description: Manage ressources + - name: App Passwords + description: Create mailbox app passwords + - name: Status + description: Get the status of your cow + - name: Ratelimits + description: Edit domain ratelimits + - name: Cross-Origin Resource Sharing (CORS) + description: Manage Cross-Origin Resource Sharing (CORS) settings + - name: Identity Provider + description: Manage external Identity Provider settings diff --git a/data/web/api/swagger-initializer.js b/data/web/api/swagger-initializer.js new file mode 100644 index 0000000000..13187cb9e3 --- /dev/null +++ b/data/web/api/swagger-initializer.js @@ -0,0 +1,18 @@ +window.onload = function() { + // Begin Swagger UI call region + window.ui = SwaggerUIBundle({ + urls: [{url: "/api/openapi.yaml", name: "mailcow API"}], + dom_id: '#swagger-ui', + deepLinking: true, + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + layout: "StandaloneLayout" + }); + // End Swagger UI call region + +}; diff --git a/data/web/api/swagger-ui-bundle.js b/data/web/api/swagger-ui-bundle.js new file mode 100644 index 0000000000..b628d7fef5 --- /dev/null +++ b/data/web/api/swagger-ui-bundle.js @@ -0,0 +1,3 @@ +/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIBundle=t():e.SwaggerUIBundle=t()}(this,(()=>(()=>{var e={17967:(e,t)=>{"use strict";t.N=void 0;var n=/^([^\w]*)(javascript|data|vbscript)/im,r=/&#(\w+)(^\w|;)?/g,o=/&(newline|tab);/gi,s=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,i=/^.+(:|:)/gim,a=[".","/"];t.N=function(e){var t,l=(t=e||"",t.replace(r,(function(e,t){return String.fromCharCode(t)}))).replace(o,"").replace(s,"").trim();if(!l)return"about:blank";if(function(e){return a.indexOf(e[0])>-1}(l))return l;var c=l.match(i);if(!c)return l;var u=c[0];return n.test(u)?"about:blank":l}},53795:(e,t,n)=>{"use strict";n.d(t,{Z:()=>P});var r=n(23101),o=n.n(r),s=n(61125),i=n.n(s),a=n(11882),l=n.n(a),c=n(97606),u=n.n(c),p=n(67294),h=n(43393);function f(e){return f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},f(e)}function d(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=function(e,t){return function(n){if("string"==typeof n)return(0,h.is)(t[n],e[n]);if(Array.isArray(n))return(0,h.is)(x(t,n),x(e,n));throw new TypeError("Invalid key: expected Array or string: "+n)}}(t,n),o=e||Object.keys(function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{};return!S(this.updateOnProps,this.props,e,"updateOnProps")||!S(this.updateOnStates,this.state,t,"updateOnStates")}}],r&&d(n.prototype,r),o&&d(n,o),t}(p.Component);var j=n(23930),O=n.n(j),k=n(45697),A=n.n(k);const C=e=>{const t=e.replace(/~1/g,"/").replace(/~0/g,"~");try{return decodeURIComponent(t)}catch{return t}};class P extends _{constructor(){super(...arguments),i()(this,"getModelName",(e=>-1!==l()(e).call(e,"#/definitions/")?C(e.replace(/^.*#\/definitions\//,"")):-1!==l()(e).call(e,"#/components/schemas/")?C(e.replace(/^.*#\/components\/schemas\//,"")):void 0)),i()(this,"getRefSchema",(e=>{let{specSelectors:t}=this.props;return t.findDefinition(e)}))}render(){let{getComponent:e,getConfigs:t,specSelectors:r,schema:s,required:i,name:a,isRef:l,specPath:c,displayName:u,includeReadOnly:h,includeWriteOnly:f}=this.props;const d=e("ObjectModel"),m=e("ArrayModel"),g=e("PrimitiveModel");let y="object",v=s&&s.get("$$ref");if(!a&&v&&(a=this.getModelName(v)),!s&&v&&(s=this.getRefSchema(a)),!s)return p.createElement("span",{className:"model model-title"},p.createElement("span",{className:"model-title__text"},u||a),p.createElement("img",{src:n(2517),height:"20px",width:"20px"}));const b=r.isOAS3()&&s.get("deprecated");switch(l=void 0!==l?l:!!v,y=s&&s.get("type")||y,y){case"object":return p.createElement(d,o()({className:"object"},this.props,{specPath:c,getConfigs:t,schema:s,name:a,deprecated:b,isRef:l,includeReadOnly:h,includeWriteOnly:f}));case"array":return p.createElement(m,o()({className:"array"},this.props,{getConfigs:t,schema:s,name:a,deprecated:b,required:i,includeReadOnly:h,includeWriteOnly:f}));default:return p.createElement(g,o()({},this.props,{getComponent:e,getConfigs:t,schema:s,name:a,deprecated:b,required:i}))}}}i()(P,"propTypes",{schema:u()(O()).isRequired,getComponent:A().func.isRequired,getConfigs:A().func.isRequired,specSelectors:A().object.isRequired,name:A().string,displayName:A().string,isRef:A().bool,required:A().bool,expandDepth:A().number,depth:A().number,specPath:O().list.isRequired,includeReadOnly:A().bool,includeWriteOnly:A().bool})},5623:(e,t,n)=>{"use strict";n.d(t,{Z:()=>h});var r=n(61125),o=n.n(r),s=n(28222),i=n.n(s),a=n(67294),l=n(84564),c=n.n(l),u=n(90242),p=n(27504);class h extends a.Component{constructor(e,t){super(e,t),o()(this,"getDefinitionUrl",(()=>{let{specSelectors:e}=this.props;return new(c())(e.url(),p.Z.location).toString()}));let{getConfigs:n}=e,{validatorUrl:r}=n();this.state={url:this.getDefinitionUrl(),validatorUrl:void 0===r?"https://validator.swagger.io/validator":r}}UNSAFE_componentWillReceiveProps(e){let{getConfigs:t}=e,{validatorUrl:n}=t();this.setState({url:this.getDefinitionUrl(),validatorUrl:void 0===n?"https://validator.swagger.io/validator":n})}render(){let{getConfigs:e}=this.props,{spec:t}=e(),n=(0,u.Nm)(this.state.validatorUrl);return"object"==typeof t&&i()(t).length?null:this.state.url&&(0,u.hW)(this.state.validatorUrl)&&(0,u.hW)(this.state.url)?a.createElement("span",{className:"float-right"},a.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:`${n}/debug?url=${encodeURIComponent(this.state.url)}`},a.createElement(f,{src:`${n}?url=${encodeURIComponent(this.state.url)}`,alt:"Online validator badge"}))):null}}class f extends a.Component{constructor(e){super(e),this.state={loaded:!1,error:!1}}componentDidMount(){const e=new Image;e.onload=()=>{this.setState({loaded:!0})},e.onerror=()=>{this.setState({error:!0})},e.src=this.props.src}UNSAFE_componentWillReceiveProps(e){if(e.src!==this.props.src){const t=new Image;t.onload=()=>{this.setState({loaded:!0})},t.onerror=()=>{this.setState({error:!0})},t.src=e.src}}render(){return this.state.error?a.createElement("img",{alt:"Error"}):this.state.loaded?a.createElement("img",{src:this.props.src,alt:this.props.alt}):null}}},4599:(e,t,n)=>{"use strict";n.d(t,{Z:()=>ye,s:()=>ve});var r=n(67294),o=n(89927);function s(e,t){if(Array.prototype.indexOf)return e.indexOf(t);for(var n=0,r=e.length;n=0;n--)!0===t(e[n])&&e.splice(n,1)}function a(e){throw new Error("Unhandled case for value: '".concat(e,"'"))}var l=function(){function e(e){void 0===e&&(e={}),this.tagName="",this.attrs={},this.innerHTML="",this.whitespaceRegex=/\s+/,this.tagName=e.tagName||"",this.attrs=e.attrs||{},this.innerHTML=e.innerHtml||e.innerHTML||""}return e.prototype.setTagName=function(e){return this.tagName=e,this},e.prototype.getTagName=function(){return this.tagName||""},e.prototype.setAttr=function(e,t){return this.getAttrs()[e]=t,this},e.prototype.getAttr=function(e){return this.getAttrs()[e]},e.prototype.setAttrs=function(e){return Object.assign(this.getAttrs(),e),this},e.prototype.getAttrs=function(){return this.attrs||(this.attrs={})},e.prototype.setClass=function(e){return this.setAttr("class",e)},e.prototype.addClass=function(e){for(var t,n=this.getClass(),r=this.whitespaceRegex,o=n?n.split(r):[],i=e.split(r);t=i.shift();)-1===s(o,t)&&o.push(t);return this.getAttrs().class=o.join(" "),this},e.prototype.removeClass=function(e){for(var t,n=this.getClass(),r=this.whitespaceRegex,o=n?n.split(r):[],i=e.split(r);o.length&&(t=i.shift());){var a=s(o,t);-1!==a&&o.splice(a,1)}return this.getAttrs().class=o.join(" "),this},e.prototype.getClass=function(){return this.getAttrs().class||""},e.prototype.hasClass=function(e){return-1!==(" "+this.getClass()+" ").indexOf(" "+e+" ")},e.prototype.setInnerHTML=function(e){return this.innerHTML=e,this},e.prototype.setInnerHtml=function(e){return this.setInnerHTML(e)},e.prototype.getInnerHTML=function(){return this.innerHTML||""},e.prototype.getInnerHtml=function(){return this.getInnerHTML()},e.prototype.toAnchorString=function(){var e=this.getTagName(),t=this.buildAttrsStr();return["<",e,t=t?" "+t:"",">",this.getInnerHtml(),""].join("")},e.prototype.buildAttrsStr=function(){if(!this.attrs)return"";var e=this.getAttrs(),t=[];for(var n in e)e.hasOwnProperty(n)&&t.push(n+'="'+e[n]+'"');return t.join(" ")},e}();var c=function(){function e(e){void 0===e&&(e={}),this.newWindow=!1,this.truncate={},this.className="",this.newWindow=e.newWindow||!1,this.truncate=e.truncate||{},this.className=e.className||""}return e.prototype.build=function(e){return new l({tagName:"a",attrs:this.createAttrs(e),innerHtml:this.processAnchorText(e.getAnchorText())})},e.prototype.createAttrs=function(e){var t={href:e.getAnchorHref()},n=this.createCssClass(e);return n&&(t.class=n),this.newWindow&&(t.target="_blank",t.rel="noopener noreferrer"),this.truncate&&this.truncate.length&&this.truncate.length=a)return l.host.length==t?(l.host.substr(0,t-o)+n).substr(0,a+r):i(u,a).substr(0,a+r);var p="";if(l.path&&(p+="/"+l.path),l.query&&(p+="?"+l.query),p){if((u+p).length>=a)return(u+p).length==t?(u+p).substr(0,t):(u+i(p,a-u.length)).substr(0,a+r);u+=p}if(l.fragment){var h="#"+l.fragment;if((u+h).length>=a)return(u+h).length==t?(u+h).substr(0,t):(u+i(h,a-u.length)).substr(0,a+r);u+=h}if(l.scheme&&l.host){var f=l.scheme+"://";if((u+f).length0&&(d=u.substr(-1*Math.floor(a/2))),(u.substr(0,Math.ceil(a/2))+n+d).substr(0,a+r)}(e,n):"middle"===r?function(e,t,n){if(e.length<=t)return e;var r,o;null==n?(n="…",r=8,o=3):(r=n.length,o=n.length);var s=t-o,i="";return s>0&&(i=e.substr(-1*Math.floor(s/2))),(e.substr(0,Math.ceil(s/2))+n+i).substr(0,s+r)}(e,n):function(e,t,n){return function(e,t,n){var r;return e.length>t&&(null==n?(n="…",r=3):r=n.length,e=e.substring(0,t-r)+n),e}(e,t,n)}(e,n)},e}(),u=function(){function e(e){this.__jsduckDummyDocProp=null,this.matchedText="",this.offset=0,this.tagBuilder=e.tagBuilder,this.matchedText=e.matchedText,this.offset=e.offset}return e.prototype.getMatchedText=function(){return this.matchedText},e.prototype.setOffset=function(e){this.offset=e},e.prototype.getOffset=function(){return this.offset},e.prototype.getCssClassSuffixes=function(){return[this.getType()]},e.prototype.buildTag=function(){return this.tagBuilder.build(this)},e}(),p=function(e,t){return p=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},p(e,t)};function h(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}p(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var f=function(){return f=Object.assign||function(e){for(var t,n=1,r=arguments.length;n-1},e.isValidUriScheme=function(e){var t=e.match(this.uriSchemeRegex),n=t&&t[0].toLowerCase();return"javascript:"!==n&&"vbscript:"!==n},e.urlMatchDoesNotHaveProtocolOrDot=function(e,t){return!(!e||t&&this.hasFullProtocolRegex.test(t)||-1!==e.indexOf("."))},e.urlMatchDoesNotHaveAtLeastOneWordChar=function(e,t){return!(!e||!t)&&(!this.hasFullProtocolRegex.test(t)&&!this.hasWordCharAfterProtocolRegex.test(e))},e.hasFullProtocolRegex=/^[A-Za-z][-.+A-Za-z0-9]*:\/\//,e.uriSchemeRegex=/^[A-Za-z][-.+A-Za-z0-9]*:/,e.hasWordCharAfterProtocolRegex=new RegExp(":[^\\s]*?["+k+"]"),e.ipRegex=/[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?(:[0-9]*)?\/?$/,e}(),V=(d=new RegExp("[/?#](?:["+N+"\\-+&@#/%=~_()|'$*\\[\\]{}?!:,.;^โœ“]*["+N+"\\-+&@#/%=~_()|'$*\\[\\]{}โœ“])?"),new RegExp(["(?:","(",/(?:[A-Za-z][-.+A-Za-z0-9]{0,63}:(?![A-Za-z][-.+A-Za-z0-9]{0,63}:\/\/)(?!\d+\/?)(?:\/\/)?)/.source,D(2),")","|","(","(//)?",/(?:www\.)/.source,D(6),")","|","(","(//)?",D(10)+"\\.",L.source,"(?![-"+P+"])",")",")","(?::[0-9]+)?","(?:"+d.source+")?"].join(""),"gi")),W=new RegExp("["+N+"]"),J=function(e){function t(t){var n=e.call(this,t)||this;return n.stripPrefix={scheme:!0,www:!0},n.stripTrailingSlash=!0,n.decodePercentEncoding=!0,n.matcherRegex=V,n.wordCharRegExp=W,n.stripPrefix=t.stripPrefix,n.stripTrailingSlash=t.stripTrailingSlash,n.decodePercentEncoding=t.decodePercentEncoding,n}return h(t,e),t.prototype.parseMatches=function(e){for(var t,n=this.matcherRegex,r=this.stripPrefix,o=this.stripTrailingSlash,s=this.decodePercentEncoding,i=this.tagBuilder,a=[],l=function(){var n=t[0],l=t[1],u=t[4],p=t[5],h=t[9],f=t.index,d=p||h,m=e.charAt(f-1);if(!z.isValid(n,l))return"continue";if(f>0&&"@"===m)return"continue";if(f>0&&d&&c.wordCharRegExp.test(m))return"continue";if(/\?$/.test(n)&&(n=n.substr(0,n.length-1)),c.matchHasUnbalancedClosingParen(n))n=n.substr(0,n.length-1);else{var g=c.matchHasInvalidCharAfterTld(n,l);g>-1&&(n=n.substr(0,g))}var y=["http://","https://"].find((function(e){return!!l&&-1!==l.indexOf(e)}));if(y){var v=n.indexOf(y);n=n.substr(v),l=l.substr(v),f+=v}var w=l?"scheme":u?"www":"tld",E=!!l;a.push(new b({tagBuilder:i,matchedText:n,offset:f,urlMatchType:w,url:n,protocolUrlMatch:E,protocolRelativeMatch:!!d,stripPrefix:r,stripTrailingSlash:o,decodePercentEncoding:s}))},c=this;null!==(t=n.exec(e));)l();return a},t.prototype.matchHasUnbalancedClosingParen=function(e){var t,n=e.charAt(e.length-1);if(")"===n)t="(";else if("]"===n)t="[";else{if("}"!==n)return!1;t="{"}for(var r=0,o=0,s=e.length-1;o-1&&s-i<=140){var o=e.slice(i,s),a=new g({tagBuilder:t,matchedText:o,offset:i,serviceName:n,hashtag:o.slice(1)});r.push(a)}}},t}(w),G=["twitter","facebook","instagram","tiktok"],Z=new RegExp("".concat(/(?:(?:(?:(\+)?\d{1,3}[-\040.]?)?\(?\d{3}\)?[-\040.]?\d{3}[-\040.]?\d{4})|(?:(\+)(?:9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)[-\040.]?(?:\d[-\040.]?){6,12}\d+))([,;]+[0-9]+#?)*/.source,"|").concat(/(0([1-9]{1}-?[1-9]\d{3}|[1-9]{2}-?\d{3}|[1-9]{2}\d{1}-?\d{2}|[1-9]{2}\d{2}-?\d{1})-?\d{4}|0[789]0-?\d{4}-?\d{4}|050-?\d{4}-?\d{4})/.source),"g"),Y=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.matcherRegex=Z,t}return h(t,e),t.prototype.parseMatches=function(e){for(var t,n=this.matcherRegex,r=this.tagBuilder,o=[];null!==(t=n.exec(e));){var s=t[0],i=s.replace(/[^0-9,;#]/g,""),a=!(!t[1]&&!t[2]),l=0==t.index?"":e.substr(t.index-1,1),c=e.substr(t.index+s.length,1),u=!l.match(/\d/)&&!c.match(/\d/);this.testMatch(t[3])&&this.testMatch(s)&&u&&o.push(new v({tagBuilder:r,matchedText:s,offset:t.index,number:i,plusSign:a}))}return o},t.prototype.testMatch=function(e){return S.test(e)},t}(w),X=new RegExp("@[_".concat(N,"]{1,50}(?![_").concat(N,"])"),"g"),Q=new RegExp("@[_.".concat(N,"]{1,30}(?![_").concat(N,"])"),"g"),ee=new RegExp("@[-_.".concat(N,"]{1,50}(?![-_").concat(N,"])"),"g"),te=new RegExp("@[_.".concat(N,"]{1,23}[_").concat(N,"](?![_").concat(N,"])"),"g"),ne=new RegExp("[^"+N+"]"),re=function(e){function t(t){var n=e.call(this,t)||this;return n.serviceName="twitter",n.matcherRegexes={twitter:X,instagram:Q,soundcloud:ee,tiktok:te},n.nonWordCharRegex=ne,n.serviceName=t.serviceName,n}return h(t,e),t.prototype.parseMatches=function(e){var t,n=this.serviceName,r=this.matcherRegexes[this.serviceName],o=this.nonWordCharRegex,s=this.tagBuilder,i=[];if(!r)return i;for(;null!==(t=r.exec(e));){var a=t.index,l=e.charAt(a-1);if(0===a||o.test(l)){var c=t[0].replace(/\.+$/g,""),u=c.slice(1);i.push(new y({tagBuilder:s,matchedText:c,offset:a,serviceName:n,mention:u}))}}return i},t}(w);function oe(e,t){for(var n,r=t.onOpenTag,o=t.onCloseTag,s=t.onText,i=t.onComment,l=t.onDoctype,c=new se,u=0,p=e.length,h=0,d=0,m=c;u"===e?(m=new se(f(f({},m),{name:J()})),W()):E.test(e)||x.test(e)||":"===e||z()}function w(e){">"===e?z():E.test(e)?h=3:z()}function S(e){_.test(e)||("/"===e?h=12:">"===e?W():"<"===e?V():"="===e||j.test(e)||O.test(e)?z():h=5)}function k(e){_.test(e)?h=6:"/"===e?h=12:"="===e?h=7:">"===e?W():"<"===e?V():j.test(e)&&z()}function A(e){_.test(e)||("/"===e?h=12:"="===e?h=7:">"===e?W():"<"===e?V():j.test(e)?z():h=5)}function C(e){_.test(e)||('"'===e?h=8:"'"===e?h=9:/[>=`]/.test(e)?z():"<"===e?V():h=10)}function P(e){'"'===e&&(h=11)}function N(e){"'"===e&&(h=11)}function I(e){_.test(e)?h=4:">"===e?W():"<"===e&&V()}function T(e){_.test(e)?h=4:"/"===e?h=12:">"===e?W():"<"===e?V():(h=4,u--)}function R(e){">"===e?(m=new se(f(f({},m),{isClosing:!0})),W()):h=4}function M(t){"--"===e.substr(u,2)?(u+=2,m=new se(f(f({},m),{type:"comment"})),h=14):"DOCTYPE"===e.substr(u,7).toUpperCase()?(u+=7,m=new se(f(f({},m),{type:"doctype"})),h=20):z()}function D(e){"-"===e?h=15:">"===e?z():h=16}function F(e){"-"===e?h=18:">"===e?z():h=16}function L(e){"-"===e&&(h=17)}function B(e){h="-"===e?18:16}function $(e){">"===e?W():"!"===e?h=19:"-"===e||(h=16)}function q(e){"-"===e?h=17:">"===e?W():h=16}function U(e){">"===e?W():"<"===e&&V()}function z(){h=0,m=c}function V(){h=1,m=new se({idx:u})}function W(){var t=e.slice(d,m.idx);t&&s(t,d),"comment"===m.type?i(m.idx):"doctype"===m.type?l(m.idx):(m.isOpening&&r(m.name,m.idx),m.isClosing&&o(m.name,m.idx)),z(),d=u+1}function J(){var t=m.idx+(m.isClosing?2:1);return e.slice(t,u).toLowerCase()}d=0&&r++},onText:function(e,n){if(0===r){var s=function(e,t){if(!t.global)throw new Error("`splitRegex` must have the 'g' flag set");for(var n,r=[],o=0;n=t.exec(e);)r.push(e.substring(o,n.index)),r.push(n[0]),o=n.index+n[0].length;return r.push(e.substring(o)),r}(e,/( | |<|<|>|>|"|"|')/gi),i=n;s.forEach((function(e,n){if(n%2==0){var r=t.parseText(e,i);o.push.apply(o,r)}i+=e.length}))}},onCloseTag:function(e){n.indexOf(e)>=0&&(r=Math.max(r-1,0))},onComment:function(e){},onDoctype:function(e){}}),o=this.compactMatches(o),o=this.removeUnwantedMatches(o)},e.prototype.compactMatches=function(e){e.sort((function(e,t){return e.getOffset()-t.getOffset()}));for(var t=0;to?t:t+1;e.splice(i,1);continue}if(e[t+1].getOffset()/g,">"));for(var t=this.parse(e),n=[],r=0,o=0,s=t.length;o/i.test(e)}function ce(){var e=[],t=new ie({stripPrefix:!1,url:!0,email:!0,replaceFn:function(t){switch(t.getType()){case"url":e.push({text:t.matchedText,url:t.getUrl()});break;case"email":e.push({text:t.matchedText,url:"mailto:"+t.getEmail().replace(/^mailto:/i,"")})}return!1}});return{links:e,autolinker:t}}function ue(e){var t,n,r,o,s,i,a,l,c,u,p,h,f,d,m=e.tokens,g=null;for(n=0,r=m.length;n=0;t--)if("link_close"!==(s=o[t]).type){if("htmltag"===s.type&&(d=s.content,/^\s]/i.test(d)&&p>0&&p--,le(s.content)&&p++),!(p>0)&&"text"===s.type&&ae.test(s.content)){if(g||(h=(g=ce()).links,f=g.autolinker),i=s.content,h.length=0,f.link(i),!h.length)continue;for(a=[],u=s.level,l=0;l({useUnsafeMarkdown:!1})};const ye=ge;function ve(e){let{useUnsafeMarkdown:t=!1}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const n=t,r=t?[]:["style","class"];return t&&!ve.hasWarnedAboutDeprecation&&(console.warn("useUnsafeMarkdown display configuration parameter is deprecated since >3.26.0 and will be removed in v4.0.0."),ve.hasWarnedAboutDeprecation=!0),fe().sanitize(e,{ADD_ATTR:["target"],FORBID_TAGS:["style","form"],ALLOW_DATA_ATTR:n,FORBID_ATTR:r})}ve.hasWarnedAboutDeprecation=!1},45308:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>h});var r,o=n(86),s=n.n(o),i=n(8712),a=n.n(i),l=n(90242),c=n(27621);const u=n(95102),p={},h=p;s()(r=a()(u).call(u)).call(r,(function(e){if("./index.js"===e)return;let t=u(e);p[(0,l.Zl)(e)]=t.default?t.default:t})),p.SafeRender=c.default},55812:(e,t,n)=>{"use strict";n.r(t),n.d(t,{AUTHORIZE:()=>h,AUTHORIZE_OAUTH2:()=>m,CONFIGURE_AUTH:()=>y,LOGOUT:()=>f,PRE_AUTHORIZE_OAUTH2:()=>d,RESTORE_AUTHORIZATION:()=>v,SHOW_AUTH_POPUP:()=>p,VALIDATE:()=>g,authPopup:()=>M,authorize:()=>w,authorizeAccessCodeWithBasicAuthentication:()=>P,authorizeAccessCodeWithFormParams:()=>C,authorizeApplication:()=>A,authorizeOauth2:()=>j,authorizeOauth2WithPersistOption:()=>O,authorizePassword:()=>k,authorizeRequest:()=>N,authorizeWithPersistOption:()=>E,configureAuth:()=>I,logout:()=>x,logoutWithPersistOption:()=>S,persistAuthorizationIfNeeded:()=>R,preAuthorizeImplicit:()=>_,restoreAuthorization:()=>T,showDefinitions:()=>b});var r=n(35627),o=n.n(r),s=n(76986),i=n.n(s),a=n(84564),l=n.n(a),c=n(27504),u=n(90242);const p="show_popup",h="authorize",f="logout",d="pre_authorize_oauth2",m="authorize_oauth2",g="validate",y="configure_auth",v="restore_authorization";function b(e){return{type:p,payload:e}}function w(e){return{type:h,payload:e}}const E=e=>t=>{let{authActions:n}=t;n.authorize(e),n.persistAuthorizationIfNeeded()};function x(e){return{type:f,payload:e}}const S=e=>t=>{let{authActions:n}=t;n.logout(e),n.persistAuthorizationIfNeeded()},_=e=>t=>{let{authActions:n,errActions:r}=t,{auth:s,token:i,isValid:a}=e,{schema:l,name:u}=s,p=l.get("flow");delete c.Z.swaggerUIRedirectOauth2,"accessCode"===p||a||r.newAuthErr({authId:u,source:"auth",level:"warning",message:"Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"}),i.error?r.newAuthErr({authId:u,source:"auth",level:"error",message:o()(i)}):n.authorizeOauth2WithPersistOption({auth:s,token:i})};function j(e){return{type:m,payload:e}}const O=e=>t=>{let{authActions:n}=t;n.authorizeOauth2(e),n.persistAuthorizationIfNeeded()},k=e=>t=>{let{authActions:n}=t,{schema:r,name:o,username:s,password:a,passwordType:l,clientId:c,clientSecret:p}=e,h={grant_type:"password",scope:e.scopes.join(" "),username:s,password:a},f={};switch(l){case"request-body":!function(e,t,n){t&&i()(e,{client_id:t});n&&i()(e,{client_secret:n})}(h,c,p);break;case"basic":f.Authorization="Basic "+(0,u.r3)(c+":"+p);break;default:console.warn(`Warning: invalid passwordType ${l} was passed, not including client id and secret`)}return n.authorizeRequest({body:(0,u.GZ)(h),url:r.get("tokenUrl"),name:o,headers:f,query:{},auth:e})};const A=e=>t=>{let{authActions:n}=t,{schema:r,scopes:o,name:s,clientId:i,clientSecret:a}=e,l={Authorization:"Basic "+(0,u.r3)(i+":"+a)},c={grant_type:"client_credentials",scope:o.join(" ")};return n.authorizeRequest({body:(0,u.GZ)(c),name:s,url:r.get("tokenUrl"),auth:e,headers:l})},C=e=>{let{auth:t,redirectUrl:n}=e;return e=>{let{authActions:r}=e,{schema:o,name:s,clientId:i,clientSecret:a,codeVerifier:l}=t,c={grant_type:"authorization_code",code:t.code,client_id:i,client_secret:a,redirect_uri:n,code_verifier:l};return r.authorizeRequest({body:(0,u.GZ)(c),name:s,url:o.get("tokenUrl"),auth:t})}},P=e=>{let{auth:t,redirectUrl:n}=e;return e=>{let{authActions:r}=e,{schema:o,name:s,clientId:i,clientSecret:a,codeVerifier:l}=t,c={Authorization:"Basic "+(0,u.r3)(i+":"+a)},p={grant_type:"authorization_code",code:t.code,client_id:i,redirect_uri:n,code_verifier:l};return r.authorizeRequest({body:(0,u.GZ)(p),name:s,url:o.get("tokenUrl"),auth:t,headers:c})}},N=e=>t=>{let n,{fn:r,getConfigs:s,authActions:a,errActions:c,oas3Selectors:u,specSelectors:p,authSelectors:h}=t,{body:f,query:d={},headers:m={},name:g,url:y,auth:v}=e,{additionalQueryStringParams:b}=h.getConfigs()||{};if(p.isOAS3()){let e=u.serverEffectiveValue(u.selectedServer());n=l()(y,e,!0)}else n=l()(y,p.url(),!0);"object"==typeof b&&(n.query=i()({},n.query,b));const w=n.toString();let E=i()({Accept:"application/json, text/plain, */*","Content-Type":"application/x-www-form-urlencoded","X-Requested-With":"XMLHttpRequest"},m);r.fetch({url:w,method:"post",headers:E,query:d,body:f,requestInterceptor:s().requestInterceptor,responseInterceptor:s().responseInterceptor}).then((function(e){let t=JSON.parse(e.data),n=t&&(t.error||""),r=t&&(t.parseError||"");e.ok?n||r?c.newAuthErr({authId:g,level:"error",source:"auth",message:o()(t)}):a.authorizeOauth2WithPersistOption({auth:v,token:t}):c.newAuthErr({authId:g,level:"error",source:"auth",message:e.statusText})})).catch((e=>{let t=new Error(e).message;if(e.response&&e.response.data){const n=e.response.data;try{const e="string"==typeof n?JSON.parse(n):n;e.error&&(t+=`, error: ${e.error}`),e.error_description&&(t+=`, description: ${e.error_description}`)}catch(e){}}c.newAuthErr({authId:g,level:"error",source:"auth",message:t})}))};function I(e){return{type:y,payload:e}}function T(e){return{type:v,payload:e}}const R=()=>e=>{let{authSelectors:t,getConfigs:n}=e;if(!n().persistAuthorization)return;const r=t.authorized().toJS();localStorage.setItem("authorized",o()(r))},M=(e,t)=>()=>{c.Z.swaggerUIRedirectOauth2=t,c.Z.open(e)}},53779:(e,t,n)=>{"use strict";n.r(t),n.d(t,{loaded:()=>r});const r=(e,t)=>n=>{const{getConfigs:r,authActions:o}=t,s=r();if(e(n),s.persistAuthorization){const e=localStorage.getItem("authorized");e&&o.restoreAuthorization({authorized:JSON.parse(e)})}}},93705:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p,preauthorizeApiKey:()=>f,preauthorizeBasic:()=>h});var r=n(11189),o=n.n(r),s=n(43962),i=n(55812),a=n(60035),l=n(60489),c=n(53779),u=n(22849);function p(){return{afterLoad(e){this.rootInjects=this.rootInjects||{},this.rootInjects.initOAuth=e.authActions.configureAuth,this.rootInjects.preauthorizeApiKey=o()(f).call(f,null,e),this.rootInjects.preauthorizeBasic=o()(h).call(h,null,e)},statePlugins:{auth:{reducers:s.default,actions:i,selectors:a,wrapActions:{authorize:u.authorize,logout:u.logout}},configs:{wrapActions:{loaded:c.loaded}},spec:{wrapActions:{execute:l.execute}}}}}function h(e,t,n,r){const{authActions:{authorize:o},specSelectors:{specJson:s,isOAS3:i}}=e,a=i()?["components","securitySchemes"]:["securityDefinitions"],l=s().getIn([...a,t]);return l?o({[t]:{value:{username:n,password:r},schema:l.toJS()}}):null}function f(e,t,n){const{authActions:{authorize:r},specSelectors:{specJson:o,isOAS3:s}}=e,i=s()?["components","securitySchemes"]:["securityDefinitions"],a=o().getIn([...i,t]);return a?r({[t]:{value:n,schema:a.toJS()}}):null}},43962:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(86),o=n.n(r),s=n(76986),i=n.n(s),a=n(43393),l=n(90242),c=n(55812);const u={[c.SHOW_AUTH_POPUP]:(e,t)=>{let{payload:n}=t;return e.set("showDefinitions",n)},[c.AUTHORIZE]:(e,t)=>{var n;let{payload:r}=t,s=(0,a.fromJS)(r),i=e.get("authorized")||(0,a.Map)();return o()(n=s.entrySeq()).call(n,(t=>{let[n,r]=t;if(!(0,l.Wl)(r.getIn))return e.set("authorized",i);let o=r.getIn(["schema","type"]);if("apiKey"===o||"http"===o)i=i.set(n,r);else if("basic"===o){let e=r.getIn(["value","username"]),t=r.getIn(["value","password"]);i=i.setIn([n,"value"],{username:e,header:"Basic "+(0,l.r3)(e+":"+t)}),i=i.setIn([n,"schema"],r.get("schema"))}})),e.set("authorized",i)},[c.AUTHORIZE_OAUTH2]:(e,t)=>{let n,{payload:r}=t,{auth:o,token:s}=r;o.token=i()({},s),n=(0,a.fromJS)(o);let l=e.get("authorized")||(0,a.Map)();return l=l.set(n.get("name"),n),e.set("authorized",l)},[c.LOGOUT]:(e,t)=>{let{payload:n}=t,r=e.get("authorized").withMutations((e=>{o()(n).call(n,(t=>{e.delete(t)}))}));return e.set("authorized",r)},[c.CONFIGURE_AUTH]:(e,t)=>{let{payload:n}=t;return e.set("configs",n)},[c.RESTORE_AUTHORIZATION]:(e,t)=>{let{payload:n}=t;return e.set("authorized",(0,a.fromJS)(n.authorized))}}},60035:(e,t,n)=>{"use strict";n.r(t),n.d(t,{authorized:()=>x,definitionsForRequirements:()=>E,definitionsToAuthorize:()=>b,getConfigs:()=>_,getDefinitionsByNames:()=>w,isAuthorized:()=>S,shownDefinitions:()=>v});var r=n(86),o=n.n(r),s=n(51679),i=n.n(s),a=n(14418),l=n.n(a),c=n(11882),u=n.n(c),p=n(97606),h=n.n(p),f=n(28222),d=n.n(f),m=n(20573),g=n(43393);const y=e=>e,v=(0,m.P1)(y,(e=>e.get("showDefinitions"))),b=(0,m.P1)(y,(()=>e=>{var t;let{specSelectors:n}=e,r=n.securityDefinitions()||(0,g.Map)({}),s=(0,g.List)();return o()(t=r.entrySeq()).call(t,(e=>{let[t,n]=e,r=(0,g.Map)();r=r.set(t,n),s=s.push(r)})),s})),w=(e,t)=>e=>{var n;let{specSelectors:r}=e;console.warn("WARNING: getDefinitionsByNames is deprecated and will be removed in the next major version.");let s=r.securityDefinitions(),i=(0,g.List)();return o()(n=t.valueSeq()).call(n,(e=>{var t;let n=(0,g.Map)();o()(t=e.entrySeq()).call(t,(e=>{let t,[r,i]=e,a=s.get(r);var l;"oauth2"===a.get("type")&&i.size&&(t=a.get("scopes"),o()(l=t.keySeq()).call(l,(e=>{i.contains(e)||(t=t.delete(e))})),a=a.set("allowedScopes",t));n=n.set(r,a)})),i=i.push(n)})),i},E=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:(0,g.List)();return e=>{let{authSelectors:n}=e;const r=n.definitionsToAuthorize()||(0,g.List)();let s=(0,g.List)();return o()(r).call(r,(e=>{let n=i()(t).call(t,(t=>t.get(e.keySeq().first())));n&&(o()(e).call(e,((t,r)=>{if("oauth2"===t.get("type")){const i=n.get(r);let a=t.get("scopes");var s;if(g.List.isList(i)&&g.Map.isMap(a))o()(s=a.keySeq()).call(s,(e=>{i.contains(e)||(a=a.delete(e))})),e=e.set(r,t.set("scopes",a))}})),s=s.push(e))})),s}},x=(0,m.P1)(y,(e=>e.get("authorized")||(0,g.Map)())),S=(e,t)=>e=>{var n;let{authSelectors:r}=e,o=r.authorized();return g.List.isList(t)?!!l()(n=t.toJS()).call(n,(e=>{var t,n;return-1===u()(t=h()(n=d()(e)).call(n,(e=>!!o.get(e)))).call(t,!1)})).length:null},_=(0,m.P1)(y,(e=>e.get("configs")))},60489:(e,t,n)=>{"use strict";n.r(t),n.d(t,{execute:()=>r});const r=(e,t)=>{let{authSelectors:n,specSelectors:r}=t;return t=>{let{path:o,method:s,operation:i,extras:a}=t,l={authorized:n.authorized()&&n.authorized().toJS(),definitions:r.securityDefinitions()&&r.securityDefinitions().toJS(),specSecurity:r.security()&&r.security().toJS()};return e({path:o,method:s,operation:i,securities:l,...a})}}},22849:(e,t,n)=>{"use strict";n.r(t),n.d(t,{authorize:()=>c,logout:()=>u});var r=n(3665),o=n.n(r),s=n(58309),i=n.n(s),a=n(86),l=n.n(a);const c=(e,t)=>n=>{e(n);if(t.getConfigs().persistAuthorization)try{const[{schema:e,value:t}]=o()(n),r="apiKey"===e.get("type"),s="cookie"===e.get("in");r&&s&&(document.cookie=`${e.get("name")}=${t}; SameSite=None; Secure`)}catch(e){console.error("Error persisting cookie based apiKey in document.cookie.",e)}},u=(e,t)=>n=>{const r=t.getConfigs(),o=t.authSelectors.authorized();try{r.persistAuthorization&&i()(n)&&l()(n).call(n,(e=>{const t=o.get(e,{}),n="apiKey"===t.getIn(["schema","type"]),r="cookie"===t.getIn(["schema","in"]);if(n&&r){const e=t.getIn(["schema","name"]);document.cookie=`${e}=; Max-Age=-99999999`}}))}catch(e){console.error("Error deleting cookie based apiKey from document.cookie.",e)}e(n)}},70714:(e,t,n)=>{"use strict";n.r(t),n.d(t,{TOGGLE_CONFIGS:()=>o,UPDATE_CONFIGS:()=>r,loaded:()=>a,toggle:()=>i,update:()=>s});const r="configs_update",o="configs_toggle";function s(e,t){return{type:r,payload:{[e]:t}}}function i(e){return{type:o,payload:e}}const a=()=>()=>{}},92256:(e,t,n)=>{"use strict";n.r(t),n.d(t,{parseYamlConfig:()=>o});var r=n(1272);const o=(e,t)=>{try{return r.ZP.load(e)}catch(e){return t&&t.errActions.newThrownErr(new Error(e)),{}}}},46709:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(92256),o=n(70714),s=n(22698),i=n(69018),a=n(37743);const l={getLocalConfig:()=>(0,r.parseYamlConfig)('---\nurl: "https://petstore.swagger.io/v2/swagger.json"\ndom_id: "#swagger-ui"\nvalidatorUrl: "https://validator.swagger.io/validator"\n')};function c(){return{statePlugins:{spec:{actions:s,selectors:l},configs:{reducers:a.default,actions:o,selectors:i}}}}},37743:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(43393),o=n(70714);const s={[o.UPDATE_CONFIGS]:(e,t)=>e.merge((0,r.fromJS)(t.payload)),[o.TOGGLE_CONFIGS]:(e,t)=>{const n=t.payload,r=e.get(n);return e.set(n,!r)}}},69018:(e,t,n)=>{"use strict";n.r(t),n.d(t,{get:()=>s});var r=n(58309),o=n.n(r);const s=(e,t)=>e.getIn(o()(t)?t:[t])},22698:(e,t,n)=>{"use strict";n.r(t),n.d(t,{downloadConfig:()=>o,getConfigByUrl:()=>s});var r=n(92256);const o=e=>t=>{const{fn:{fetch:n}}=t;return n(e)},s=(e,t)=>n=>{let{specActions:o}=n;if(e)return o.downloadConfig(e).then(s,s);function s(n){n instanceof Error||n.status>=400?(o.updateLoadingStatus("failedConfig"),o.updateLoadingStatus("failedConfig"),o.updateUrl(""),console.error(n.statusText+" "+e.url),t(null)):t((0,r.parseYamlConfig)(n.text))}}},31970:(e,t,n)=>{"use strict";n.r(t),n.d(t,{setHash:()=>r});const r=e=>e?history.pushState(null,null,`#${e}`):window.location.hash=""},34980:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(41599),o=n(60877),s=n(34584);function i(){return[r.default,{statePlugins:{configs:{wrapActions:{loaded:(e,t)=>function(){e(...arguments);const n=decodeURIComponent(window.location.hash);t.layoutActions.parseDeepLinkHash(n)}}}},wrapComponents:{operation:o.default,OperationTag:s.default}}]}},41599:(e,t,n)=>{"use strict";n.r(t),n.d(t,{clearScrollTo:()=>_,default:()=>j,parseDeepLinkHash:()=>E,readyToScroll:()=>x,scrollTo:()=>w,scrollToElement:()=>S,show:()=>b});var r=n(58309),o=n.n(r),s=n(24278),i=n.n(s),a=n(97606),l=n.n(a),c=n(11882),u=n.n(c),p=n(31970),h=n(45172),f=n.n(h),d=n(90242),m=n(43393),g=n.n(m);const y="layout_scroll_to",v="layout_clear_scroll",b=(e,t)=>{let{getConfigs:n,layoutSelectors:r}=t;return function(){for(var t=arguments.length,s=new Array(t),i=0;i({type:y,payload:o()(e)?e:[e]}),E=e=>t=>{let{layoutActions:n,layoutSelectors:r,getConfigs:o}=t;if(o().deepLinking&&e){var s;let t=i()(e).call(e,1);"!"===t[0]&&(t=i()(t).call(t,1)),"/"===t[0]&&(t=i()(t).call(t,1));const o=l()(s=t.split("/")).call(s,(e=>e||"")),a=r.isShownKeyFromUrlHashArray(o),[c,p="",h=""]=a;if("operations"===c){const e=r.isShownKeyFromUrlHashArray([p]);u()(p).call(p,"_")>-1&&(console.warn("Warning: escaping deep link whitespace with `_` will be unsupported in v4.0, use `%20` instead."),n.show(l()(e).call(e,(e=>e.replace(/_/g," "))),!0)),n.show(e,!0)}(u()(p).call(p,"_")>-1||u()(h).call(h,"_")>-1)&&(console.warn("Warning: escaping deep link whitespace with `_` will be unsupported in v4.0, use `%20` instead."),n.show(l()(a).call(a,(e=>e.replace(/_/g," "))),!0)),n.show(a,!0),n.scrollTo(a)}},x=(e,t)=>n=>{const r=n.layoutSelectors.getScrollToKey();g().is(r,(0,m.fromJS)(e))&&(n.layoutActions.scrollToElement(t),n.layoutActions.clearScrollTo())},S=(e,t)=>n=>{try{t=t||n.fn.getScrollParent(e),f().createScroller(t).to(e)}catch(e){console.error(e)}},_=()=>({type:v});const j={fn:{getScrollParent:function(e,t){const n=document.documentElement;let r=getComputedStyle(e);const o="absolute"===r.position,s=t?/(auto|scroll|hidden)/:/(auto|scroll)/;if("fixed"===r.position)return n;for(let t=e;t=t.parentElement;)if(r=getComputedStyle(t),(!o||"static"!==r.position)&&s.test(r.overflow+r.overflowY+r.overflowX))return t;return n}},statePlugins:{layout:{actions:{scrollToElement:S,scrollTo:w,clearScrollTo:_,readyToScroll:x,parseDeepLinkHash:E},selectors:{getScrollToKey:e=>e.get("scrollToKey"),isShownKeyFromUrlHashArray(e,t){const[n,r]=t;return r?["operations",n,r]:n?["operations-tag",n]:[]},urlHashArrayFromIsShownKey(e,t){let[n,r,o]=t;return"operations"==n?[r,o]:"operations-tag"==n?[r]:[]}},reducers:{[y]:(e,t)=>e.set("scrollToKey",g().fromJS(t.payload)),[v]:e=>e.delete("scrollToKey")},wrapActions:{show:b}}}}},34584:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(61125),o=n.n(r),s=n(67294);const i=(e,t)=>class extends s.Component{constructor(){super(...arguments),o()(this,"onLoad",(e=>{const{tag:n}=this.props,r=["operations-tag",n];t.layoutActions.readyToScroll(r,e)}))}render(){return s.createElement("span",{ref:this.onLoad},s.createElement(e,this.props))}}},60877:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(61125),o=n.n(r),s=n(67294);n(23930);const i=(e,t)=>class extends s.Component{constructor(){super(...arguments),o()(this,"onLoad",(e=>{const{operation:n}=this.props,{tag:r,operationId:o}=n.toObject();let{isShownKey:s}=n.toObject();s=s||["operations",r,o],t.layoutActions.readyToScroll(s,e)}))}render(){return s.createElement("span",{ref:this.onLoad},s.createElement(e,this.props))}}},48011:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>d});var r=n(76986),o=n.n(r),s=n(63460),i=n.n(s),a=n(11882),l=n.n(a),c=n(35627),u=n.n(c),p=n(20573),h=n(43393),f=n(27504);function d(e){let{fn:t}=e;return{statePlugins:{spec:{actions:{download:e=>n=>{let{errActions:r,specSelectors:s,specActions:a,getConfigs:l}=n,{fetch:c}=t;const u=l();function p(t){if(t instanceof Error||t.status>=400)return a.updateLoadingStatus("failed"),r.newThrownErr(o()(new Error((t.message||t.statusText)+" "+e),{source:"fetch"})),void(!t.status&&t instanceof Error&&function(){try{let t;if("URL"in f.Z?t=new(i())(e):(t=document.createElement("a"),t.href=e),"https:"!==t.protocol&&"https:"===f.Z.location.protocol){const e=o()(new Error(`Possible mixed-content issue? The page was loaded over https:// but a ${t.protocol}// URL was specified. Check that you are not attempting to load mixed content.`),{source:"fetch"});return void r.newThrownErr(e)}if(t.origin!==f.Z.location.origin){const e=o()(new Error(`Possible cross-origin (CORS) issue? The URL origin (${t.origin}) does not match the page (${f.Z.location.origin}). Check the server returns the correct 'Access-Control-Allow-*' headers.`),{source:"fetch"});r.newThrownErr(e)}}catch(e){return}}());a.updateLoadingStatus("success"),a.updateSpec(t.text),s.url()!==e&&a.updateUrl(e)}e=e||s.url(),a.updateLoadingStatus("loading"),r.clear({source:"fetch"}),c({url:e,loadSpec:!0,requestInterceptor:u.requestInterceptor||(e=>e),responseInterceptor:u.responseInterceptor||(e=>e),credentials:"same-origin",headers:{Accept:"application/json,*/*"}}).then(p,p)},updateLoadingStatus:e=>{let t=[null,"loading","failed","success","failedConfig"];return-1===l()(t).call(t,e)&&console.error(`Error: ${e} is not one of ${u()(t)}`),{type:"spec_update_loading_status",payload:e}}},reducers:{spec_update_loading_status:(e,t)=>"string"==typeof t.payload?e.set("loadingStatus",t.payload):e},selectors:{loadingStatus:(0,p.P1)((e=>e||(0,h.Map)()),(e=>e.get("loadingStatus")||null))}}}}}},34966:(e,t,n)=>{"use strict";n.r(t),n.d(t,{CLEAR:()=>c,CLEAR_BY:()=>u,NEW_AUTH_ERR:()=>l,NEW_SPEC_ERR:()=>i,NEW_SPEC_ERR_BATCH:()=>a,NEW_THROWN_ERR:()=>o,NEW_THROWN_ERR_BATCH:()=>s,clear:()=>g,clearBy:()=>y,newAuthErr:()=>m,newSpecErr:()=>f,newSpecErrBatch:()=>d,newThrownErr:()=>p,newThrownErrBatch:()=>h});var r=n(7710);const o="err_new_thrown_err",s="err_new_thrown_err_batch",i="err_new_spec_err",a="err_new_spec_err_batch",l="err_new_auth_err",c="err_clear",u="err_clear_by";function p(e){return{type:o,payload:(0,r.serializeError)(e)}}function h(e){return{type:s,payload:e}}function f(e){return{type:i,payload:e}}function d(e){return{type:a,payload:e}}function m(e){return{type:l,payload:e}}function g(){return{type:c,payload:arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}}}function y(){return{type:u,payload:arguments.length>0&&void 0!==arguments[0]?arguments[0]:()=>!0}}},56982:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(14418),o=n.n(r),s=n(97606),i=n.n(s),a=n(54061),l=n.n(a);const c=[n(2392),n(21835)];function u(e){var t;let n={jsSpec:{}},r=l()(c,((e,t)=>{try{let r=t.transform(e,n);return o()(r).call(r,(e=>!!e))}catch(t){return console.error("Transformer error:",t),e}}),e);return i()(t=o()(r).call(r,(e=>!!e))).call(t,(e=>(!e.get("line")&&e.get("path"),e)))}},2392:(e,t,n)=>{"use strict";n.r(t),n.d(t,{transform:()=>p});var r=n(97606),o=n.n(r),s=n(11882),i=n.n(s),a=n(24278),l=n.n(a),c=n(24282),u=n.n(c);function p(e){return o()(e).call(e,(e=>{var t;let n="is not of a type(s)",r=i()(t=e.get("message")).call(t,n);if(r>-1){var o,s;let t=l()(o=e.get("message")).call(o,r+19).split(",");return e.set("message",l()(s=e.get("message")).call(s,0,r)+function(e){return u()(e).call(e,((e,t,n,r)=>n===r.length-1&&r.length>1?e+"or "+t:r[n+1]&&r.length>2?e+t+", ":r[n+1]?e+t+" ":e+t),"should be a")}(t))}return e}))}},21835:(e,t,n)=>{"use strict";n.r(t),n.d(t,{transform:()=>r});n(97606),n(11882),n(27361),n(43393);function r(e,t){let{jsSpec:n}=t;return e}},77793:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(93527),o=n(34966),s=n(87667);function i(e){return{statePlugins:{err:{reducers:(0,r.default)(e),actions:o,selectors:s}}}}},93527:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>y});var r=n(76986),o=n.n(r),s=n(97606),i=n.n(s),a=n(39022),l=n.n(a),c=n(14418),u=n.n(c),p=n(2250),h=n.n(p),f=n(34966),d=n(43393),m=n(56982);let g={line:0,level:"error",message:"Unknown error"};function y(){return{[f.NEW_THROWN_ERR]:(e,t)=>{let{payload:n}=t,r=o()(g,n,{type:"thrown"});return e.update("errors",(e=>(e||(0,d.List)()).push((0,d.fromJS)(r)))).update("errors",(e=>(0,m.default)(e)))},[f.NEW_THROWN_ERR_BATCH]:(e,t)=>{let{payload:n}=t;return n=i()(n).call(n,(e=>(0,d.fromJS)(o()(g,e,{type:"thrown"})))),e.update("errors",(e=>{var t;return l()(t=e||(0,d.List)()).call(t,(0,d.fromJS)(n))})).update("errors",(e=>(0,m.default)(e)))},[f.NEW_SPEC_ERR]:(e,t)=>{let{payload:n}=t,r=(0,d.fromJS)(n);return r=r.set("type","spec"),e.update("errors",(e=>(e||(0,d.List)()).push((0,d.fromJS)(r)).sortBy((e=>e.get("line"))))).update("errors",(e=>(0,m.default)(e)))},[f.NEW_SPEC_ERR_BATCH]:(e,t)=>{let{payload:n}=t;return n=i()(n).call(n,(e=>(0,d.fromJS)(o()(g,e,{type:"spec"})))),e.update("errors",(e=>{var t;return l()(t=e||(0,d.List)()).call(t,(0,d.fromJS)(n))})).update("errors",(e=>(0,m.default)(e)))},[f.NEW_AUTH_ERR]:(e,t)=>{let{payload:n}=t,r=(0,d.fromJS)(o()({},n));return r=r.set("type","auth"),e.update("errors",(e=>(e||(0,d.List)()).push((0,d.fromJS)(r)))).update("errors",(e=>(0,m.default)(e)))},[f.CLEAR]:(e,t)=>{var n;let{payload:r}=t;if(!r||!e.get("errors"))return e;let o=u()(n=e.get("errors")).call(n,(e=>{var t;return h()(t=e.keySeq()).call(t,(t=>{const n=e.get(t),o=r[t];return!o||n!==o}))}));return e.merge({errors:o})},[f.CLEAR_BY]:(e,t)=>{var n;let{payload:r}=t;if(!r||"function"!=typeof r)return e;let o=u()(n=e.get("errors")).call(n,(e=>r(e)));return e.merge({errors:o})}}}},87667:(e,t,n)=>{"use strict";n.r(t),n.d(t,{allErrors:()=>s,lastError:()=>i});var r=n(43393),o=n(20573);const s=(0,o.P1)((e=>e),(e=>e.get("errors",(0,r.List)()))),i=(0,o.P1)(s,(e=>e.last()))},49978:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(4309);function o(){return{fn:{opsFilter:r.default}}}},4309:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(14418),o=n.n(r),s=n(11882),i=n.n(s);function a(e,t){return o()(e).call(e,((e,n)=>-1!==i()(n).call(n,t)))}},47349:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>l});var r=n(67294),o=n(94184),s=n.n(o),i=n(12603);const a=e=>{let{expanded:t,children:n,onChange:o}=e;const a=(0,i.useComponent)("ChevronRightIcon"),l=(0,r.useCallback)((e=>{o(e,!t)}),[t,o]);return r.createElement("button",{type:"button",className:"json-schema-2020-12-accordion",onClick:l},r.createElement("div",{className:"json-schema-2020-12-accordion__children"},n),r.createElement("span",{className:s()("json-schema-2020-12-accordion__icon",{"json-schema-2020-12-accordion__icon--expanded":t,"json-schema-2020-12-accordion__icon--collapsed":!t})},r.createElement(a,null)))};a.defaultProps={expanded:!1};const l=a},36867:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=e=>{let{expanded:t,onClick:n}=e;const o=(0,r.useCallback)((e=>{n(e,!t)}),[t,n]);return r.createElement("button",{type:"button",className:"json-schema-2020-12-expand-deep-button",onClick:o},t?"Collapse all":"Expand all")}},22675:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(97606),o=n.n(r),s=n(67294),i=n(94184),a=n.n(i),l=(n(16648),n(12603)),c=n(69006);const u=(0,s.forwardRef)(((e,t)=>{let{schema:n,name:r,dependentRequired:i,onExpand:u}=e;const p=(0,l.useFn)(),h=(0,l.useIsExpanded)(),f=(0,l.useIsExpandedDeeply)(),[d,m]=(0,s.useState)(h||f),[g,y]=(0,s.useState)(f),[v,b]=(0,l.useLevel)(),w=(0,l.useIsEmbedded)(),E=p.isExpandable(n)||i.length>0,x=(0,l.useIsCircular)(n),S=(0,l.useRenderedSchemas)(n),_=p.stringifyConstraints(n),j=(0,l.useComponent)("Accordion"),O=(0,l.useComponent)("Keyword$schema"),k=(0,l.useComponent)("Keyword$vocabulary"),A=(0,l.useComponent)("Keyword$id"),C=(0,l.useComponent)("Keyword$anchor"),P=(0,l.useComponent)("Keyword$dynamicAnchor"),N=(0,l.useComponent)("Keyword$ref"),I=(0,l.useComponent)("Keyword$dynamicRef"),T=(0,l.useComponent)("Keyword$defs"),R=(0,l.useComponent)("Keyword$comment"),M=(0,l.useComponent)("KeywordAllOf"),D=(0,l.useComponent)("KeywordAnyOf"),F=(0,l.useComponent)("KeywordOneOf"),L=(0,l.useComponent)("KeywordNot"),B=(0,l.useComponent)("KeywordIf"),$=(0,l.useComponent)("KeywordThen"),q=(0,l.useComponent)("KeywordElse"),U=(0,l.useComponent)("KeywordDependentSchemas"),z=(0,l.useComponent)("KeywordPrefixItems"),V=(0,l.useComponent)("KeywordItems"),W=(0,l.useComponent)("KeywordContains"),J=(0,l.useComponent)("KeywordProperties"),K=(0,l.useComponent)("KeywordPatternProperties"),H=(0,l.useComponent)("KeywordAdditionalProperties"),G=(0,l.useComponent)("KeywordPropertyNames"),Z=(0,l.useComponent)("KeywordUnevaluatedItems"),Y=(0,l.useComponent)("KeywordUnevaluatedProperties"),X=(0,l.useComponent)("KeywordType"),Q=(0,l.useComponent)("KeywordEnum"),ee=(0,l.useComponent)("KeywordConst"),te=(0,l.useComponent)("KeywordConstraint"),ne=(0,l.useComponent)("KeywordDependentRequired"),re=(0,l.useComponent)("KeywordContentSchema"),oe=(0,l.useComponent)("KeywordTitle"),se=(0,l.useComponent)("KeywordDescription"),ie=(0,l.useComponent)("KeywordDefault"),ae=(0,l.useComponent)("KeywordDeprecated"),le=(0,l.useComponent)("KeywordReadOnly"),ce=(0,l.useComponent)("KeywordWriteOnly"),ue=(0,l.useComponent)("ExpandDeepButton");(0,s.useEffect)((()=>{y(f)}),[f]),(0,s.useEffect)((()=>{y(g)}),[g]);const pe=(0,s.useCallback)(((e,t)=>{m(t),!t&&y(!1),u(e,t,!1)}),[u]),he=(0,s.useCallback)(((e,t)=>{m(t),y(t),u(e,t,!0)}),[u]);return s.createElement(c.JSONSchemaLevelContext.Provider,{value:b},s.createElement(c.JSONSchemaDeepExpansionContext.Provider,{value:g},s.createElement(c.JSONSchemaCyclesContext.Provider,{value:S},s.createElement("article",{ref:t,"data-json-schema-level":v,className:a()("json-schema-2020-12",{"json-schema-2020-12--embedded":w,"json-schema-2020-12--circular":x})},s.createElement("div",{className:"json-schema-2020-12-head"},E&&!x?s.createElement(s.Fragment,null,s.createElement(j,{expanded:d,onChange:pe},s.createElement(oe,{title:r,schema:n})),s.createElement(ue,{expanded:d,onClick:he})):s.createElement(oe,{title:r,schema:n}),s.createElement(ae,{schema:n}),s.createElement(le,{schema:n}),s.createElement(ce,{schema:n}),s.createElement(X,{schema:n,isCircular:x}),_.length>0&&o()(_).call(_,(e=>s.createElement(te,{key:`${e.scope}-${e.value}`,constraint:e})))),s.createElement("div",{className:a()("json-schema-2020-12-body",{"json-schema-2020-12-body--collapsed":!d})},d&&s.createElement(s.Fragment,null,s.createElement(se,{schema:n}),!x&&E&&s.createElement(s.Fragment,null,s.createElement(J,{schema:n}),s.createElement(K,{schema:n}),s.createElement(H,{schema:n}),s.createElement(Y,{schema:n}),s.createElement(G,{schema:n}),s.createElement(M,{schema:n}),s.createElement(D,{schema:n}),s.createElement(F,{schema:n}),s.createElement(L,{schema:n}),s.createElement(B,{schema:n}),s.createElement($,{schema:n}),s.createElement(q,{schema:n}),s.createElement(U,{schema:n}),s.createElement(z,{schema:n}),s.createElement(V,{schema:n}),s.createElement(Z,{schema:n}),s.createElement(W,{schema:n}),s.createElement(re,{schema:n})),s.createElement(Q,{schema:n}),s.createElement(ee,{schema:n}),s.createElement(ne,{schema:n,dependentRequired:i}),s.createElement(ie,{schema:n}),s.createElement(O,{schema:n}),s.createElement(k,{schema:n}),s.createElement(A,{schema:n}),s.createElement(C,{schema:n}),s.createElement(P,{schema:n}),s.createElement(N,{schema:n}),!x&&E&&s.createElement(T,{schema:n}),s.createElement(I,{schema:n}),s.createElement(R,{schema:n})))))))}));u.defaultProps={name:"",dependentRequired:[],onExpand:()=>{}};const p=u},12260:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=()=>r.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24"},r.createElement("path",{d:"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"}))},64922:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.$anchor?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$anchor"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$anchor"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t.$anchor)):null}},4685:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.$comment?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$comment"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$comment"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t.$comment)):null}},36418:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>d});var r=n(28222),o=n.n(r),s=n(97606),i=n.n(s),a=n(2018),l=n.n(a),c=n(67294),u=n(94184),p=n.n(u),h=(n(16648),n(12603)),f=n(69006);const d=e=>{var t;let{schema:n}=e;const r=(null==n?void 0:n.$defs)||{},s=(0,h.useIsExpandedDeeply)(),[a,u]=(0,c.useState)(s),[d,m]=(0,c.useState)(!1),g=(0,h.useComponent)("Accordion"),y=(0,h.useComponent)("ExpandDeepButton"),v=(0,h.useComponent)("JSONSchema"),b=(0,c.useCallback)((()=>{u((e=>!e))}),[]),w=(0,c.useCallback)(((e,t)=>{u(t),m(t)}),[]);return 0===o()(r).length?null:c.createElement(f.JSONSchemaDeepExpansionContext.Provider,{value:d},c.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$defs"},c.createElement(g,{expanded:a,onChange:b},c.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$defs")),c.createElement(y,{expanded:a,onClick:w}),c.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),c.createElement("ul",{className:p()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!a})},a&&c.createElement(c.Fragment,null,i()(t=l()(r)).call(t,(e=>{let[t,n]=e;return c.createElement("li",{key:t,className:"json-schema-2020-12-property"},c.createElement(v,{name:t,schema:n}))}))))))}},51338:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.$dynamicAnchor?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$dynamicAnchor"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$dynamicAnchor"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t.$dynamicAnchor)):null}},27655:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.$dynamicRef?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$dynamicRef"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$dynamicRef"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t.$dynamicRef)):null}},93460:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.$id?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$id"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$id"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t.$id)):null}},72348:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.$ref?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$ref"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$ref"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t.$ref)):null}},69359:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.$schema?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$schema"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$schema"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t.$schema)):null}},7568:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(97606),o=n.n(r),s=n(2018),i=n.n(s),a=n(67294),l=n(94184),c=n.n(l),u=(n(16648),n(12603));const p=e=>{var t;let{schema:n}=e;const r=(0,u.useIsExpandedDeeply)(),[s,l]=(0,a.useState)(r),p=(0,u.useComponent)("Accordion"),h=(0,a.useCallback)((()=>{l((e=>!e))}),[]);return null!=n&&n.$vocabulary?"object"!=typeof n.$vocabulary?null:a.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$vocabulary"},a.createElement(p,{expanded:s,onChange:h},a.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$vocabulary")),a.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),a.createElement("ul",null,s&&o()(t=i()(n.$vocabulary)).call(t,(e=>{let[t,n]=e;return a.createElement("li",{key:t,className:c()("json-schema-2020-12-$vocabulary-uri",{"json-schema-2020-12-$vocabulary-uri--disabled":!n})},a.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t))})))):null}},65253:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),{additionalProperties:s}=t,i=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"additionalProperties"))return null;const a=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Additional properties");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--additionalProperties"},!0===s?r.createElement(r.Fragment,null,a,r.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"allowed")):!1===s?r.createElement(r.Fragment,null,a,r.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"forbidden")):r.createElement(i,{name:a,schema:s}))}},46457:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>h});var r=n(58309),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294),l=n(94184),c=n.n(l),u=(n(16648),n(12603)),p=n(69006);const h=e=>{let{schema:t}=e;const n=(null==t?void 0:t.allOf)||[],r=(0,u.useFn)(),s=(0,u.useIsExpandedDeeply)(),[l,h]=(0,a.useState)(s),[f,d]=(0,a.useState)(!1),m=(0,u.useComponent)("Accordion"),g=(0,u.useComponent)("ExpandDeepButton"),y=(0,u.useComponent)("JSONSchema"),v=(0,u.useComponent)("KeywordType"),b=(0,a.useCallback)((()=>{h((e=>!e))}),[]),w=(0,a.useCallback)(((e,t)=>{h(t),d(t)}),[]);return o()(n)&&0!==n.length?a.createElement(p.JSONSchemaDeepExpansionContext.Provider,{value:f},a.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--allOf"},a.createElement(m,{expanded:l,onChange:b},a.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"All of")),a.createElement(g,{expanded:l,onClick:w}),a.createElement(v,{schema:{allOf:n}}),a.createElement("ul",{className:c()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!l})},l&&a.createElement(a.Fragment,null,i()(n).call(n,((e,t)=>a.createElement("li",{key:`#${t}`,className:"json-schema-2020-12-property"},a.createElement(y,{name:`#${t} ${r.getTitle(e)}`,schema:e})))))))):null}},8776:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>h});var r=n(58309),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294),l=n(94184),c=n.n(l),u=(n(16648),n(12603)),p=n(69006);const h=e=>{let{schema:t}=e;const n=(null==t?void 0:t.anyOf)||[],r=(0,u.useFn)(),s=(0,u.useIsExpandedDeeply)(),[l,h]=(0,a.useState)(s),[f,d]=(0,a.useState)(!1),m=(0,u.useComponent)("Accordion"),g=(0,u.useComponent)("ExpandDeepButton"),y=(0,u.useComponent)("JSONSchema"),v=(0,u.useComponent)("KeywordType"),b=(0,a.useCallback)((()=>{h((e=>!e))}),[]),w=(0,a.useCallback)(((e,t)=>{h(t),d(t)}),[]);return o()(n)&&0!==n.length?a.createElement(p.JSONSchemaDeepExpansionContext.Provider,{value:f},a.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--anyOf"},a.createElement(m,{expanded:l,onChange:b},a.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Any of")),a.createElement(g,{expanded:l,onClick:w}),a.createElement(v,{schema:{anyOf:n}}),a.createElement("ul",{className:c()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!l})},l&&a.createElement(a.Fragment,null,i()(n).call(n,((e,t)=>a.createElement("li",{key:`#${t}`,className:"json-schema-2020-12-property"},a.createElement(y,{name:`#${t} ${r.getTitle(e)}`,schema:e})))))))):null}},27308:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)();return n.hasKeyword(t,"const")?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--const"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Const"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const"},n.stringify(t.const))):null}},69956:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294);const o=e=>{let{constraint:t}=e;return r.createElement("span",{className:`json-schema-2020-12__constraint json-schema-2020-12__constraint--${t.scope}`},t.value)},s=r.memo(o)},38993:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),s=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"contains"))return null;const i=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Contains");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--contains"},r.createElement(s,{name:i,schema:t.contains}))}},3484:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),s=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"contentSchema"))return null;const i=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Content schema");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--contentSchema"},r.createElement(s,{name:i,schema:t.contentSchema}))}},55148:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)();return n.hasKeyword(t,"default")?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--default"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Default"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const"},n.stringify(t.default))):null}},24539:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(97606),o=n.n(r),s=n(67294);n(16648);const i=e=>{let{dependentRequired:t}=e;return 0===t.length?null:s.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--dependentRequired"},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Required when defined"),s.createElement("ul",null,o()(t).call(t,(e=>s.createElement("li",{key:e},s.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--warning"},e))))))}},26076:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>d});var r=n(28222),o=n.n(r),s=n(97606),i=n.n(s),a=n(2018),l=n.n(a),c=n(67294),u=n(94184),p=n.n(u),h=(n(16648),n(12603)),f=n(69006);const d=e=>{var t;let{schema:n}=e;const r=(null==n?void 0:n.dependentSchemas)||[],s=(0,h.useIsExpandedDeeply)(),[a,u]=(0,c.useState)(s),[d,m]=(0,c.useState)(!1),g=(0,h.useComponent)("Accordion"),y=(0,h.useComponent)("ExpandDeepButton"),v=(0,h.useComponent)("JSONSchema"),b=(0,c.useCallback)((()=>{u((e=>!e))}),[]),w=(0,c.useCallback)(((e,t)=>{u(t),m(t)}),[]);return"object"!=typeof r||0===o()(r).length?null:c.createElement(f.JSONSchemaDeepExpansionContext.Provider,{value:d},c.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--dependentSchemas"},c.createElement(g,{expanded:a,onChange:b},c.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Dependent schemas")),c.createElement(y,{expanded:a,onClick:w}),c.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),c.createElement("ul",{className:p()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!a})},a&&c.createElement(c.Fragment,null,i()(t=l()(r)).call(t,(e=>{let[t,n]=e;return c.createElement("li",{key:t,className:"json-schema-2020-12-property"},c.createElement(v,{name:t,schema:n}))}))))))}},26661:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return!0!==(null==t?void 0:t.deprecated)?null:r.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--warning"},"deprecated")}},79446:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.description?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--description"},r.createElement("div",{className:"json-schema-2020-12-core-keyword__value json-schema-2020-12-core-keyword__value--secondary"},t.description)):null}},67207:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),s=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"else"))return null;const i=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Else");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--if"},r.createElement(s,{name:i,schema:t.else}))}},91805:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(58309),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294),l=(n(16648),n(12603));const c=e=>{var t;let{schema:n}=e;const r=(0,l.useFn)();return o()(null==n?void 0:n.enum)?a.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--enum"},a.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Allowed values"),a.createElement("ul",null,i()(t=n.enum).call(t,(e=>{const t=r.stringify(e);return a.createElement("li",{key:t},a.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const"},t))})))):null}},40487:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),s=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"if"))return null;const i=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"If");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--if"},r.createElement(s,{name:i,schema:t.if}))}},89206:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),s=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"items"))return null;const i=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Items");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--items"},r.createElement(s,{name:i,schema:t.items}))}},65174:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),s=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"not"))return null;const i=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Not");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--not"},r.createElement(s,{name:i,schema:t.not}))}},13834:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>h});var r=n(58309),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294),l=n(94184),c=n.n(l),u=(n(16648),n(12603)),p=n(69006);const h=e=>{let{schema:t}=e;const n=(null==t?void 0:t.oneOf)||[],r=(0,u.useFn)(),s=(0,u.useIsExpandedDeeply)(),[l,h]=(0,a.useState)(s),[f,d]=(0,a.useState)(!1),m=(0,u.useComponent)("Accordion"),g=(0,u.useComponent)("ExpandDeepButton"),y=(0,u.useComponent)("JSONSchema"),v=(0,u.useComponent)("KeywordType"),b=(0,a.useCallback)((()=>{h((e=>!e))}),[]),w=(0,a.useCallback)(((e,t)=>{h(t),d(t)}),[]);return o()(n)&&0!==n.length?a.createElement(p.JSONSchemaDeepExpansionContext.Provider,{value:f},a.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--oneOf"},a.createElement(m,{expanded:l,onChange:b},a.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"One of")),a.createElement(g,{expanded:l,onClick:w}),a.createElement(v,{schema:{oneOf:n}}),a.createElement("ul",{className:c()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!l})},l&&a.createElement(a.Fragment,null,i()(n).call(n,((e,t)=>a.createElement("li",{key:`#${t}`,className:"json-schema-2020-12-property"},a.createElement(y,{name:`#${t} ${r.getTitle(e)}`,schema:e})))))))):null}},36746:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(28222),o=n.n(r),s=n(97606),i=n.n(s),a=n(2018),l=n.n(a),c=n(67294),u=(n(16648),n(12603));const p=e=>{var t;let{schema:n}=e;const r=(null==n?void 0:n.patternProperties)||{},s=(0,u.useComponent)("JSONSchema");return 0===o()(r).length?null:c.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--patternProperties"},c.createElement("ul",null,i()(t=l()(r)).call(t,(e=>{let[t,n]=e;return c.createElement("li",{key:t,className:"json-schema-2020-12-property"},c.createElement(s,{name:t,schema:n}))}))))}},93971:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>h});var r=n(58309),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294),l=n(94184),c=n.n(l),u=(n(16648),n(12603)),p=n(69006);const h=e=>{let{schema:t}=e;const n=(null==t?void 0:t.prefixItems)||[],r=(0,u.useFn)(),s=(0,u.useIsExpandedDeeply)(),[l,h]=(0,a.useState)(s),[f,d]=(0,a.useState)(!1),m=(0,u.useComponent)("Accordion"),g=(0,u.useComponent)("ExpandDeepButton"),y=(0,u.useComponent)("JSONSchema"),v=(0,u.useComponent)("KeywordType"),b=(0,a.useCallback)((()=>{h((e=>!e))}),[]),w=(0,a.useCallback)(((e,t)=>{h(t),d(t)}),[]);return o()(n)&&0!==n.length?a.createElement(p.JSONSchemaDeepExpansionContext.Provider,{value:f},a.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--prefixItems"},a.createElement(m,{expanded:l,onChange:b},a.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Prefix items")),a.createElement(g,{expanded:l,onClick:w}),a.createElement(v,{schema:{prefixItems:n}}),a.createElement("ul",{className:c()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!l})},l&&a.createElement(a.Fragment,null,i()(n).call(n,((e,t)=>a.createElement("li",{key:`#${t}`,className:"json-schema-2020-12-property"},a.createElement(y,{name:`#${t} ${r.getTitle(e)}`,schema:e})))))))):null}},25472:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>y});var r=n(58309),o=n.n(r),s=n(28222),i=n.n(s),a=n(97606),l=n.n(a),c=n(2018),u=n.n(c),p=n(58118),h=n.n(p),f=n(67294),d=n(94184),m=n.n(d),g=(n(16648),n(12603));const y=e=>{var t;let{schema:n}=e;const r=(0,g.useFn)(),s=(null==n?void 0:n.properties)||{},a=o()(null==n?void 0:n.required)?n.required:[],c=(0,g.useComponent)("JSONSchema");return 0===i()(s).length?null:f.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--properties"},f.createElement("ul",null,l()(t=u()(s)).call(t,(e=>{let[t,o]=e;const s=h()(a).call(a,t),i=r.getDependentRequired(t,n);return f.createElement("li",{key:t,className:m()("json-schema-2020-12-property",{"json-schema-2020-12-property--required":s})},f.createElement(c,{name:t,schema:o,dependentRequired:i}))}))))}},42338:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),{propertyNames:s}=t,i=(0,o.useComponent)("JSONSchema"),a=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Property names");return n.hasKeyword(t,"propertyNames")?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--propertyNames"},r.createElement(i,{name:a,schema:s})):null}},16456:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return!0!==(null==t?void 0:t.readOnly)?null:r.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},"read-only")}},67401:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),s=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"then"))return null;const i=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Then");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--then"},r.createElement(s,{name:i,schema:t.then}))}},78137:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{title:t,schema:n}=e;const s=(0,o.useFn)();return t||s.getTitle(n)?r.createElement("div",{className:"json-schema-2020-12__title"},t||s.getTitle(n)):null};s.defaultProps={title:""};const i=s},22285:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t,isCircular:n}=e;const s=(0,o.useFn)().getType(t),i=n?" [circular]":"";return r.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},`${s}${i}`)};s.defaultProps={isCircular:!1};const i=s},85828:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),{unevaluatedItems:s}=t,i=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"unevaluatedItems"))return null;const a=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Unevaluated items");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--unevaluatedItems"},r.createElement(i,{name:a,schema:s}))}},6907:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),{unevaluatedProperties:s}=t,i=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"unevaluatedProperties"))return null;const a=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Unevaluated properties");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--unevaluatedProperties"},r.createElement(i,{name:a,schema:s}))}},15789:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return!0!==(null==t?void 0:t.writeOnly)?null:r.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},"write-only")}},69006:(e,t,n)=>{"use strict";n.r(t),n.d(t,{JSONSchemaContext:()=>i,JSONSchemaCyclesContext:()=>c,JSONSchemaDeepExpansionContext:()=>l,JSONSchemaLevelContext:()=>a});var r=n(82737),o=n.n(r),s=n(67294);const i=(0,s.createContext)(null);i.displayName="JSONSchemaContext";const a=(0,s.createContext)(0);a.displayName="JSONSchemaLevelContext";const l=(0,s.createContext)(!1);l.displayName="JSONSchemaDeepExpansionContext";const c=(0,s.createContext)(new(o()))},33499:(e,t,n)=>{"use strict";n.r(t),n.d(t,{getDependentRequired:()=>F,getTitle:()=>C,getType:()=>P,hasKeyword:()=>I,isBooleanJSONSchema:()=>N,isExpandable:()=>T,stringify:()=>R,stringifyConstraints:()=>D,upperFirst:()=>A});var r=n(24278),o=n.n(r),s=n(19030),i=n.n(s),a=n(58309),l=n.n(a),c=n(97606),u=n.n(c),p=n(58118),h=n.n(p),f=n(91086),d=n.n(f),m=n(14418),g=n.n(m),y=n(35627),v=n.n(y),b=n(25110),w=n.n(b),E=n(24282),x=n.n(E),S=n(2018),_=n.n(S),j=n(82737),O=n.n(j),k=n(12603);const A=e=>"string"==typeof e?`${e.charAt(0).toUpperCase()}${o()(e).call(e,1)}`:e,C=e=>{const t=(0,k.useFn)();return null!=e&&e.title?t.upperFirst(e.title):null!=e&&e.$anchor?t.upperFirst(e.$anchor):null!=e&&e.$id?e.$id:""},P=function(e){var t,n;let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:new(i());const o=(0,k.useFn)();if(null==e)return"any";if(o.isBooleanJSONSchema(e))return e?"any":"never";if("object"!=typeof e)return"any";if(r.has(e))return"any";r.add(e);const{type:s,prefixItems:a,items:c}=e,p=()=>{if(l()(a)){const e=u()(a).call(a,(e=>P(e,r))),t=c?P(c,r):"any";return`array<[${e.join(", ")}], ${t}>`}if(c){return`array<${P(c,r)}>`}return"array"};if(e.not&&"any"===P(e.not))return"never";const f=l()(s)?u()(s).call(s,(e=>"array"===e?p():e)).join(" | "):"array"===s?p():h()(t=["null","boolean","object","array","number","string"]).call(t,s)?s:(()=>{var t,n;if(Object.hasOwn(e,"prefixItems")||Object.hasOwn(e,"items")||Object.hasOwn(e,"contains"))return p();if(Object.hasOwn(e,"properties")||Object.hasOwn(e,"additionalProperties")||Object.hasOwn(e,"patternProperties"))return"object";if(h()(t=["int32","int64"]).call(t,e.format))return"integer";if(h()(n=["float","double"]).call(n,e.format))return"number";if(Object.hasOwn(e,"minimum")||Object.hasOwn(e,"maximum")||Object.hasOwn(e,"exclusiveMinimum")||Object.hasOwn(e,"exclusiveMaximum")||Object.hasOwn(e,"multipleOf"))return"number | integer";if(Object.hasOwn(e,"pattern")||Object.hasOwn(e,"format")||Object.hasOwn(e,"minLength")||Object.hasOwn(e,"maxLength"))return"string";if(void 0!==e.const){if(null===e.const)return"null";if("boolean"==typeof e.const)return"boolean";if("number"==typeof e.const)return d()(e.const)?"integer":"number";if("string"==typeof e.const)return"string";if(l()(e.const))return"array";if("object"==typeof e.const)return"object"}return null})(),m=(t,n)=>{if(l()(e[t])){var o;return`(${u()(o=e[t]).call(o,(e=>P(e,r))).join(n)})`}return null},y=m("oneOf"," | "),v=m("anyOf"," | "),b=m("allOf"," & "),w=g()(n=[f,y,v,b]).call(n,Boolean).join(" | ");return r.delete(e),w||"any"},N=e=>"boolean"==typeof e,I=(e,t)=>null!==e&&"object"==typeof e&&Object.hasOwn(e,t),T=e=>{const t=(0,k.useFn)();return(null==e?void 0:e.$schema)||(null==e?void 0:e.$vocabulary)||(null==e?void 0:e.$id)||(null==e?void 0:e.$anchor)||(null==e?void 0:e.$dynamicAnchor)||(null==e?void 0:e.$ref)||(null==e?void 0:e.$dynamicRef)||(null==e?void 0:e.$defs)||(null==e?void 0:e.$comment)||(null==e?void 0:e.allOf)||(null==e?void 0:e.anyOf)||(null==e?void 0:e.oneOf)||t.hasKeyword(e,"not")||t.hasKeyword(e,"if")||t.hasKeyword(e,"then")||t.hasKeyword(e,"else")||(null==e?void 0:e.dependentSchemas)||(null==e?void 0:e.prefixItems)||t.hasKeyword(e,"items")||t.hasKeyword(e,"contains")||(null==e?void 0:e.properties)||(null==e?void 0:e.patternProperties)||t.hasKeyword(e,"additionalProperties")||t.hasKeyword(e,"propertyNames")||t.hasKeyword(e,"unevaluatedItems")||t.hasKeyword(e,"unevaluatedProperties")||(null==e?void 0:e.description)||(null==e?void 0:e.enum)||t.hasKeyword(e,"const")||t.hasKeyword(e,"contentSchema")||t.hasKeyword(e,"default")},R=e=>{var t;return null===e||h()(t=["number","bigint","boolean"]).call(t,typeof e)?String(e):l()(e)?`[${u()(e).call(e,R).join(", ")}]`:v()(e)},M=(e,t,n)=>{const r="number"==typeof t,o="number"==typeof n;return r&&o?t===n?`${t} ${e}`:`[${t}, ${n}] ${e}`:r?`>= ${t} ${e}`:o?`<= ${n} ${e}`:null},D=e=>{const t=[],n=(e=>{if("number"!=typeof(null==e?void 0:e.multipleOf))return null;if(e.multipleOf<=0)return null;if(1===e.multipleOf)return null;const{multipleOf:t}=e;if(d()(t))return`multiple of ${t}`;const n=10**t.toString().split(".")[1].length;return`multiple of ${t*n}/${n}`})(e);null!==n&&t.push({scope:"number",value:n});const r=(e=>{const t=null==e?void 0:e.minimum,n=null==e?void 0:e.maximum,r=null==e?void 0:e.exclusiveMinimum,o=null==e?void 0:e.exclusiveMaximum,s="number"==typeof t,i="number"==typeof n,a="number"==typeof r&&to;if(s&&i)return`${a?"(":"["}${a?r:t}, ${l?o:n}${l?")":"]"}`;if(s)return`${a?">":"โ‰ฅ"} ${a?r:t}`;if(i)return`${l?"<":"โ‰ค"} ${l?o:n}`;return null})(e);null!==r&&t.push({scope:"number",value:r}),null!=e&&e.format&&t.push({scope:"string",value:e.format});const o=M("characters",null==e?void 0:e.minLength,null==e?void 0:e.maxLength);null!==o&&t.push({scope:"string",value:o}),null!=e&&e.pattern&&t.push({scope:"string",value:`matches ${null==e?void 0:e.pattern}`}),null!=e&&e.contentMediaType&&t.push({scope:"string",value:`media type: ${e.contentMediaType}`}),null!=e&&e.contentEncoding&&t.push({scope:"string",value:`encoding: ${e.contentEncoding}`});const s=M(null!=e&&e.hasUniqueItems?"unique items":"items",null==e?void 0:e.minItems,null==e?void 0:e.maxItems);null!==s&&t.push({scope:"array",value:s});const i=M("contained items",null==e?void 0:e.minContains,null==e?void 0:e.maxContains);null!==i&&t.push({scope:"array",value:i});const a=M("properties",null==e?void 0:e.minProperties,null==e?void 0:e.maxProperties);return null!==a&&t.push({scope:"object",value:a}),t},F=(e,t)=>{var n;return null!=t&&t.dependentRequired?w()(x()(n=_()(t.dependentRequired)).call(n,((t,n)=>{let[r,o]=n;return l()(o)&&h()(o).call(o,e)?(t.add(r),t):t}),new(O()))):[]}},65077:(e,t,n)=>{"use strict";n.r(t),n.d(t,{withJSONSchemaContext:()=>H});var r=n(67294),o=n(22675),s=n(69359),i=n(7568),a=n(93460),l=n(64922),c=n(51338),u=n(72348),p=n(27655),h=n(36418),f=n(4685),d=n(46457),m=n(8776),g=n(13834),y=n(65174),v=n(40487),b=n(67401),w=n(67207),E=n(26076),x=n(93971),S=n(89206),_=n(38993),j=n(25472),O=n(36746),k=n(65253),A=n(42338),C=n(85828),P=n(6907),N=n(22285),I=n(91805),T=n(27308),R=n(69956),M=n(24539),D=n(3484),F=n(78137),L=n(79446),B=n(55148),$=n(26661),q=n(16456),U=n(15789),z=n(47349),V=n(36867),W=n(12260),J=n(69006),K=n(33499);const H=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const n={components:{JSONSchema:o.default,Keyword$schema:s.default,Keyword$vocabulary:i.default,Keyword$id:a.default,Keyword$anchor:l.default,Keyword$dynamicAnchor:c.default,Keyword$ref:u.default,Keyword$dynamicRef:p.default,Keyword$defs:h.default,Keyword$comment:f.default,KeywordAllOf:d.default,KeywordAnyOf:m.default,KeywordOneOf:g.default,KeywordNot:y.default,KeywordIf:v.default,KeywordThen:b.default,KeywordElse:w.default,KeywordDependentSchemas:E.default,KeywordPrefixItems:x.default,KeywordItems:S.default,KeywordContains:_.default,KeywordProperties:j.default,KeywordPatternProperties:O.default,KeywordAdditionalProperties:k.default,KeywordPropertyNames:A.default,KeywordUnevaluatedItems:C.default,KeywordUnevaluatedProperties:P.default,KeywordType:N.default,KeywordEnum:I.default,KeywordConst:T.default,KeywordConstraint:R.default,KeywordDependentRequired:M.default,KeywordContentSchema:D.default,KeywordTitle:F.default,KeywordDescription:L.default,KeywordDefault:B.default,KeywordDeprecated:$.default,KeywordReadOnly:q.default,KeywordWriteOnly:U.default,Accordion:z.default,ExpandDeepButton:V.default,ChevronRightIcon:W.default,...t.components},config:{default$schema:"https://json-schema.org/draft/2020-12/schema",defaultExpandedLevels:0,...t.config},fn:{upperFirst:K.upperFirst,getTitle:K.getTitle,getType:K.getType,isBooleanJSONSchema:K.isBooleanJSONSchema,hasKeyword:K.hasKeyword,isExpandable:K.isExpandable,stringify:K.stringify,stringifyConstraints:K.stringifyConstraints,getDependentRequired:K.getDependentRequired,...t.fn}},H=t=>r.createElement(J.JSONSchemaContext.Provider,{value:n},r.createElement(e,t));return H.contexts={JSONSchemaContext:J.JSONSchemaContext},H.displayName=e.displayName,H}},12603:(e,t,n)=>{"use strict";n.r(t),n.d(t,{useComponent:()=>l,useConfig:()=>a,useFn:()=>c,useIsCircular:()=>m,useIsEmbedded:()=>p,useIsExpanded:()=>h,useIsExpandedDeeply:()=>f,useLevel:()=>u,useRenderedSchemas:()=>d});var r=n(82737),o=n.n(r),s=n(67294),i=n(69006);const a=()=>{const{config:e}=(0,s.useContext)(i.JSONSchemaContext);return e},l=e=>{const{components:t}=(0,s.useContext)(i.JSONSchemaContext);return t[e]||null},c=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:void 0;const{fn:t}=(0,s.useContext)(i.JSONSchemaContext);return void 0!==e?t[e]:t},u=()=>{const e=(0,s.useContext)(i.JSONSchemaLevelContext);return[e,e+1]},p=()=>{const[e]=u();return e>0},h=()=>{const[e]=u(),{defaultExpandedLevels:t}=a();return t-e>0},f=()=>(0,s.useContext)(i.JSONSchemaDeepExpansionContext),d=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:void 0;if(void 0===e)return(0,s.useContext)(i.JSONSchemaCyclesContext);const t=(0,s.useContext)(i.JSONSchemaCyclesContext);return new(o())([...t,e])},m=e=>d().has(e)},97139:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>Z});var r=n(22675),o=n(69359),s=n(7568),i=n(93460),a=n(64922),l=n(51338),c=n(72348),u=n(27655),p=n(36418),h=n(4685),f=n(46457),d=n(8776),m=n(13834),g=n(65174),y=n(40487),v=n(67401),b=n(67207),w=n(26076),E=n(93971),x=n(89206),S=n(38993),_=n(25472),j=n(36746),O=n(65253),k=n(42338),A=n(85828),C=n(6907),P=n(22285),N=n(91805),I=n(27308),T=n(69956),R=n(24539),M=n(3484),D=n(78137),F=n(79446),L=n(55148),B=n(26661),$=n(16456),q=n(15789),U=n(47349),z=n(36867),V=n(12260),W=n(33499),J=n(78591),K=n(69006),H=n(12603),G=n(65077);const Z=()=>({components:{JSONSchema202012:r.default,JSONSchema202012Keyword$schema:o.default,JSONSchema202012Keyword$vocabulary:s.default,JSONSchema202012Keyword$id:i.default,JSONSchema202012Keyword$anchor:a.default,JSONSchema202012Keyword$dynamicAnchor:l.default,JSONSchema202012Keyword$ref:c.default,JSONSchema202012Keyword$dynamicRef:u.default,JSONSchema202012Keyword$defs:p.default,JSONSchema202012Keyword$comment:h.default,JSONSchema202012KeywordAllOf:f.default,JSONSchema202012KeywordAnyOf:d.default,JSONSchema202012KeywordOneOf:m.default,JSONSchema202012KeywordNot:g.default,JSONSchema202012KeywordIf:y.default,JSONSchema202012KeywordThen:v.default,JSONSchema202012KeywordElse:b.default,JSONSchema202012KeywordDependentSchemas:w.default,JSONSchema202012KeywordPrefixItems:E.default,JSONSchema202012KeywordItems:x.default,JSONSchema202012KeywordContains:S.default,JSONSchema202012KeywordProperties:_.default,JSONSchema202012KeywordPatternProperties:j.default,JSONSchema202012KeywordAdditionalProperties:O.default,JSONSchema202012KeywordPropertyNames:k.default,JSONSchema202012KeywordUnevaluatedItems:A.default,JSONSchema202012KeywordUnevaluatedProperties:C.default,JSONSchema202012KeywordType:P.default,JSONSchema202012KeywordEnum:N.default,JSONSchema202012KeywordConst:I.default,JSONSchema202012KeywordConstraint:T.default,JSONSchema202012KeywordDependentRequired:R.default,JSONSchema202012KeywordContentSchema:M.default,JSONSchema202012KeywordTitle:D.default,JSONSchema202012KeywordDescription:F.default,JSONSchema202012KeywordDefault:L.default,JSONSchema202012KeywordDeprecated:B.default,JSONSchema202012KeywordReadOnly:$.default,JSONSchema202012KeywordWriteOnly:q.default,JSONSchema202012Accordion:U.default,JSONSchema202012ExpandDeepButton:z.default,JSONSchema202012ChevronRightIcon:V.default,withJSONSchema202012Context:G.withJSONSchemaContext,JSONSchema202012DeepExpansionContext:()=>K.JSONSchemaDeepExpansionContext},fn:{upperFirst:W.upperFirst,jsonSchema202012:{isExpandable:W.isExpandable,hasKeyword:W.hasKeyword,useFn:H.useFn,useConfig:H.useConfig,useComponent:H.useComponent,useIsExpandedDeeply:H.useIsExpandedDeeply,sampleFromSchema:J.sampleFromSchema,sampleFromSchemaGeneric:J.sampleFromSchemaGeneric,sampleEncoderAPI:J.encoderAPI,sampleFormatAPI:J.formatAPI,sampleMediaTypeAPI:J.mediaTypeAPI,createXMLExample:J.createXMLExample,memoizedSampleFromSchema:J.memoizedSampleFromSchema,memoizedCreateXMLExample:J.memoizedCreateXMLExample}}})},16648:(e,t,n)=>{"use strict";n.r(t),n.d(t,{booleanSchema:()=>i,objectSchema:()=>s,schema:()=>a});var r=n(45697),o=n.n(r);const s=o().object,i=o().bool,a=o().oneOfType([s,i])},9507:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});const r=new(n(70674).default),o=(e,t)=>"function"==typeof t?r.register(e,t):null===t?r.unregister(e):r.get(e);o.getDefaults=()=>r.defaults;const s=o},22906:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});const r=new(n(14215).default),o=(e,t)=>"function"==typeof t?r.register(e,t):null===t?r.unregister(e):r.get(e)},90537:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});const r=new(n(43782).default),o=(e,t)=>{if("function"==typeof t)return r.register(e,t);if(null===t)return r.unregister(e);const n=e.split(";").at(0),o=`${n.split("/").at(0)}/*`;return r.get(e)||r.get(n)||r.get(o)};o.getDefaults=()=>r.defaults;const s=o},70674:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>w});var r=n(61125),o=n.n(r),s=n(47667),i=n.n(s),a=n(28886),l=n.n(a),c=n(14215),u=n(41433),p=n(58509),h=n(44366),f=n(65037),d=n(5709),m=n(54180),g=n(91967);function y(e,t,n){!function(e,t){if(t.has(e))throw new TypeError("Cannot initialize the same private elements twice on an object")}(e,t),t.set(e,n)}var v=new(l());class b extends c.default{constructor(){super(...arguments),y(this,v,{writable:!0,value:{"7bit":u.default,"8bit":p.default,binary:h.default,"quoted-printable":f.default,base16:d.default,base32:m.default,base64:g.default}}),o()(this,"data",{...i()(this,v)})}get defaults(){return{...i()(this,v)}}}const w=b},43782:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>v});var r=n(61125),o=n.n(r),s=n(47667),i=n.n(s),a=n(28886),l=n.n(a),c=n(14215),u=n(65378),p=n(46724),h=n(54342),f=n(92974),d=n(2672);function m(e,t,n){!function(e,t){if(t.has(e))throw new TypeError("Cannot initialize the same private elements twice on an object")}(e,t),t.set(e,n)}var g=new(l());class y extends c.default{constructor(){super(...arguments),m(this,g,{writable:!0,value:{...u.default,...p.default,...h.default,...f.default,...d.default}}),o()(this,"data",{...i()(this,g)})}get defaults(){return{...i()(this,g)}}}const v=y},14215:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(61125),o=n.n(r);const s=class{constructor(){o()(this,"data",{})}register(e,t){this.data[e]=t}unregister(e){void 0===e?this.data={}:delete this.data[e]}get(e){return this.data[e]}}},84539:(e,t,n)=>{"use strict";n.r(t),n.d(t,{ALL_TYPES:()=>o,SCALAR_TYPES:()=>r});const r=["number","integer","string","boolean","null"],o=["array","object",...r]},13783:(e,t,n)=>{"use strict";n.r(t),n.d(t,{extractExample:()=>a,hasExample:()=>i});var r=n(58309),o=n.n(r),s=n(23084);const i=e=>{if(!(0,s.isJSONSchemaObject)(e))return!1;const{examples:t,example:n,default:r}=e;return!!(o()(t)&&t.length>=1)||(void 0!==r||void 0!==n)},a=e=>{if(!(0,s.isJSONSchemaObject)(e))return null;const{examples:t,example:n,default:r}=e;return o()(t)&&t.length>=1?t.at(0):void 0!==r?r:void 0!==n?n:void 0}},37078:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>v});var r=n(58309),o=n.n(r),s=n(39022),i=n.n(s),a=n(25110),l=n.n(a),c=n(82737),u=n.n(c),p=n(28222),h=n.n(p),f=n(14418),d=n.n(f),m=n(90242),g=n(23084);const y=function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if((0,g.isBooleanJSONSchema)(e)&&!0===e)return!0;if((0,g.isBooleanJSONSchema)(e)&&!1===e)return!1;if((0,g.isBooleanJSONSchema)(t)&&!0===t)return!0;if((0,g.isBooleanJSONSchema)(t)&&!1===t)return!1;if(!(0,g.isJSONSchema)(e))return t;if(!(0,g.isJSONSchema)(t))return e;const r={...t,...e};if(t.type&&e.type&&o()(t.type)&&"string"==typeof t.type){var s;const n=i()(s=(0,m.AF)(t.type)).call(s,e.type);r.type=l()(new(u())(n))}if(o()(t.required)&&o()(e.required)&&(r.required=[...new(u())([...e.required,...t.required])]),t.properties&&e.properties){const o=new(u())([...h()(t.properties),...h()(e.properties)]);r.properties={};for(const s of o){const o=t.properties[s]||{},i=e.properties[s]||{};var a;if(o.readOnly&&!n.includeReadOnly||o.writeOnly&&!n.includeWriteOnly)r.required=d()(a=r.required||[]).call(a,(e=>e!==s));else r.properties[s]=y(i,o,n)}}return(0,g.isJSONSchema)(t.items)&&(0,g.isJSONSchema)(e.items)&&(r.items=y(e.items,t.items,n)),(0,g.isJSONSchema)(t.contains)&&(0,g.isJSONSchema)(e.contains)&&(r.contains=y(e.contains,t.contains,n)),(0,g.isJSONSchema)(t.contentSchema)&&(0,g.isJSONSchema)(e.contentSchema)&&(r.contentSchema=y(e.contentSchema,t.contentSchema,n)),r},v=y},23084:(e,t,n)=>{"use strict";n.r(t),n.d(t,{isBooleanJSONSchema:()=>s,isJSONSchema:()=>a,isJSONSchemaObject:()=>i});var r=n(68630),o=n.n(r);const s=e=>"boolean"==typeof e,i=e=>o()(e),a=e=>s(e)||i(e)},35202:(e,t,n)=>{"use strict";n.r(t),n.d(t,{bytes:()=>a,integer:()=>h,number:()=>p,pick:()=>c,randexp:()=>l,string:()=>u});var r=n(92282),o=n.n(r),s=n(14419),i=n.n(s);const a=e=>o()(e),l=e=>{try{return new(i())(e).gen()}catch{return"string"}},c=e=>e.at(0),u=()=>"string",p=()=>0,h=()=>0},96276:(e,t,n)=>{"use strict";n.r(t),n.d(t,{foldType:()=>_,getType:()=>O,inferType:()=>j});var r=n(58309),o=n.n(r),s=n(91086),i=n.n(s),a=n(58118),l=n.n(a),c=n(19030),u=n.n(c),p=n(28222),h=n.n(p),f=n(97606),d=n.n(f),m=n(14418),g=n.n(m),y=n(84539),v=n(23084),b=n(35202),w=n(13783);const E={array:["items","prefixItems","contains","maxContains","minContains","maxItems","minItems","uniqueItems","unevaluatedItems"],object:["properties","additionalProperties","patternProperties","propertyNames","minProperties","maxProperties","required","dependentSchemas","dependentRequired","unevaluatedProperties"],string:["pattern","format","minLength","maxLength","contentEncoding","contentMediaType","contentSchema"],integer:["minimum","maximum","exclusiveMinimum","exclusiveMaximum","multipleOf"]};E.number=E.integer;const x="string",S=e=>void 0===e?null:null===e?"null":o()(e)?"array":i()(e)?"integer":typeof e,_=e=>{if(o()(e)&&e.length>=1){if(l()(e).call(e,"array"))return"array";if(l()(e).call(e,"object"))return"object";{const t=(0,b.pick)(e);if(l()(y.ALL_TYPES).call(y.ALL_TYPES,t))return t}}return l()(y.ALL_TYPES).call(y.ALL_TYPES,e)?e:null},j=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:new(u());if(!(0,v.isJSONSchemaObject)(e))return x;if(t.has(e))return x;t.add(e);let{type:n,const:r}=e;if(n=_(n),"string"!=typeof n){const t=h()(E);e:for(let r=0;r{if(o()(e[n])){var r;const o=d()(r=e[n]).call(r,(e=>j(e,t)));return _(o)}return null},i=r("allOf"),a=r("anyOf"),l=r("oneOf"),c=e.not?j(e.not,t):null;var s;if(i||a||l||c)n=_(g()(s=[i,a,l,c]).call(s,Boolean))}if("string"!=typeof n&&(0,w.hasExample)(e)){const t=(0,w.extractExample)(e),r=S(t);n="string"==typeof r?r:n}return t.delete(e),n||x},O=e=>j(e)},99346:(e,t,n)=>{"use strict";n.r(t),n.d(t,{fromJSONBooleanSchema:()=>o,typeCast:()=>s});var r=n(23084);const o=e=>!1===e?{not:{}}:{},s=e=>(0,r.isBooleanJSONSchema)(e)?o(e):(0,r.isJSONSchemaObject)(e)?e:{}},41433:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(48764).Buffer;const o=e=>r.from(e).toString("ascii")},58509:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(48764).Buffer;const o=e=>r.from(e).toString("utf8")},5709:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(48764).Buffer;const o=e=>r.from(e).toString("hex")},54180:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(48764).Buffer;const o=e=>{const t=r.from(e).toString("utf8"),n="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";let o=0,s="",i=0,a=0;for(let e=0;e=5;)s+=n.charAt(i>>>a-5&31),a-=5;a>0&&(s+=n.charAt(i<<5-a&31),o=(8-8*t.length%5)%5);for(let e=0;e{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(48764).Buffer;const o=e=>r.from(e).toString("base64")},44366:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(48764).Buffer;const o=e=>r.from(e).toString("binary")},65037:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(24278),o=n.n(r);const s=e=>{let t="";for(let s=0;s=33&&i<=60||i>=62&&i<=126||9===i||32===i)t+=e.charAt(s);else if(13===i||10===i)t+="\r\n";else if(i>126){const r=unescape(encodeURIComponent(e.charAt(s)));for(let e=0;e{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>(new Date).toISOString()},81456:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>(new Date).toISOString().substring(0,10)},560:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>.1},64299:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"P3D"},3981:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"user@example.com"},51890:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>.1},69375:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"example.com"},94518:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"์‹ค๋ก€@example.com"},70273:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"์‹ค๋ก€.com"},57864:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>2**30>>>0},21726:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>2**53-1},28793:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"198.51.100.42"},98269:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"2001:0db8:5b96:0000:0000:426f:8e17:642a"},45693:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"path/์‹ค๋ก€.html"},13080:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"https://์‹ค๋ก€.com/"},37856:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"/a/b/c"},2672:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(57740),o=n.n(r),s=n(35202);const i={"application/json":()=>'{"key":"value"}',"application/ld+json":()=>'{"name": "John Doe"}',"application/x-httpd-php":()=>"Hello World!

'; ?>","application/rtf":()=>o()`{\rtf1\adeflang1025\ansi\ansicpg1252\uc1`,"application/x-sh":()=>'echo "Hello World!"',"application/xhtml+xml":()=>"

content

","application/*":()=>(0,s.bytes)(25).toString("binary")}},54342:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(35202);const o={"audio/*":()=>(0,r.bytes)(25).toString("binary")}},46724:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(35202);const o={"image/*":()=>(0,r.bytes)(25).toString("binary")}},65378:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r={"text/plain":()=>"string","text/css":()=>".selector { border: 1px solid red }","text/csv":()=>"value1,value2,value3","text/html":()=>"

content

","text/calendar":()=>"BEGIN:VCALENDAR","text/javascript":()=>"console.dir('Hello world!');","text/xml":()=>'John Doe',"text/*":()=>"string"}},92974:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(35202);const o={"video/*":()=>(0,r.bytes)(25).toString("binary")}},93393:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"********"},4335:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"^[a-z]+$"},80375:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"1/0"},65243:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>(new Date).toISOString().substring(11)},94692:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"path/index.html"},83829:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"https://example.com/dictionary/{term:1}/{term}"},52978:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"https://example.com/"},38859:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"3fa85f64-5717-4562-b3fc-2c963f66afa6"},78591:(e,t,n)=>{"use strict";n.r(t),n.d(t,{createXMLExample:()=>r.createXMLExample,encoderAPI:()=>o.default,formatAPI:()=>s.default,mediaTypeAPI:()=>i.default,memoizedCreateXMLExample:()=>r.memoizedCreateXMLExample,memoizedSampleFromSchema:()=>r.memoizedSampleFromSchema,sampleFromSchema:()=>r.sampleFromSchema,sampleFromSchemaGeneric:()=>r.sampleFromSchemaGeneric});var r=n(94277),o=n(9507),s=n(22906),i=n(90537)},94277:(e,t,n)=>{"use strict";n.r(t),n.d(t,{createXMLExample:()=>M,memoizedCreateXMLExample:()=>L,memoizedSampleFromSchema:()=>B,sampleFromSchema:()=>D,sampleFromSchemaGeneric:()=>R});var r=n(58309),o=n.n(r),s=n(91086),i=n.n(s),a=n(86),l=n.n(a),c=n(51679),u=n.n(c),p=n(58118),h=n.n(p),f=n(39022),d=n.n(f),m=n(97606),g=n.n(m),y=n(35627),v=n.n(y),b=n(53479),w=n.n(b),E=n(41609),x=n.n(E),S=n(68630),_=n.n(S),j=n(90242),O=n(60314),k=n(63273),A=n(96276),C=n(99346),P=n(13783),N=n(35202),I=n(37078),T=n(23084);const R=function(e){var t;let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0,s=arguments.length>3&&void 0!==arguments[3]&&arguments[3];"function"==typeof(null===(t=e)||void 0===t?void 0:t.toJS)&&(e=e.toJS()),e=(0,C.typeCast)(e);let a=void 0!==r||(0,P.hasExample)(e);const c=!a&&o()(e.oneOf)&&e.oneOf.length>0,p=!a&&o()(e.anyOf)&&e.anyOf.length>0;if(!a&&(c||p)){const t=(0,C.typeCast)(c?(0,N.pick)(e.oneOf):(0,N.pick)(e.anyOf));!(e=(0,I.default)(e,t,n)).xml&&t.xml&&(e.xml=t.xml),(0,P.hasExample)(e)&&(0,P.hasExample)(t)&&(a=!0)}const f={};let{xml:m,properties:y,additionalProperties:v,items:b,contains:w}=e||{},E=(0,A.getType)(e),{includeReadOnly:S,includeWriteOnly:O}=n;m=m||{};let M,{name:D,prefix:F,namespace:L}=m,B={};if(Object.hasOwn(e,"type")||(e.type=E),s&&(D=D||"notagname",M=(F?`${F}:`:"")+D,L)){f[F?`xmlns:${F}`:"xmlns"]=L}s&&(B[M]=[]);const $=(0,j.mz)(y);let q,U=0;const z=()=>i()(e.maxProperties)&&e.maxProperties>0&&U>=e.maxProperties,V=t=>!(i()(e.maxProperties)&&e.maxProperties>0)||!z()&&(!(t=>{var n;return!o()(e.required)||0===e.required.length||!h()(n=e.required).call(n,t)})(t)||e.maxProperties-U-(()=>{if(!o()(e.required)||0===e.required.length)return 0;let t=0;var n,r;return s?l()(n=e.required).call(n,(e=>t+=void 0===B[e]?0:1)):l()(r=e.required).call(r,(e=>{var n;t+=void 0===(null===(n=B[M])||void 0===n?void 0:u()(n).call(n,(t=>void 0!==t[e])))?0:1})),e.required.length-t})()>0);if(q=s?function(t){let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0;if(e&&$[t]){if($[t].xml=$[t].xml||{},$[t].xml.attribute){const e=o()($[t].enum)?(0,N.pick)($[t].enum):void 0;if((0,P.hasExample)($[t]))f[$[t].xml.name||t]=(0,P.extractExample)($[t]);else if(void 0!==e)f[$[t].xml.name||t]=e;else{const e=(0,C.typeCast)($[t]),n=(0,A.getType)(e),r=$[t].xml.name||t;f[r]=k.default[n](e)}return}$[t].xml.name=$[t].xml.name||t}else $[t]||!1===v||($[t]={xml:{name:t}});let i=R($[t],n,r,s);var a;V(t)&&(U++,o()(i)?B[M]=d()(a=B[M]).call(a,i):B[M].push(i))}:(t,r)=>{var o;if(V(t)){if(_()(null===(o=e.discriminator)||void 0===o?void 0:o.mapping)&&e.discriminator.propertyName===t&&"string"==typeof e.$$ref){for(const n in e.discriminator.mapping)if(-1!==e.$$ref.search(e.discriminator.mapping[n])){B[t]=n;break}}else B[t]=R($[t],n,r,s);U++}},a){let t;if(t=void 0!==r?r:(0,P.extractExample)(e),!s){if("number"==typeof t&&"string"===E)return`${t}`;if("string"!=typeof t||"string"===E)return t;try{return JSON.parse(t)}catch{return t}}if("array"===E){if(!o()(t)){if("string"==typeof t)return t;t=[t]}let r=[];return(0,T.isJSONSchemaObject)(b)&&(b.xml=b.xml||m||{},b.xml.name=b.xml.name||m.name,r=g()(t).call(t,(e=>R(b,n,e,s)))),(0,T.isJSONSchemaObject)(w)&&(w.xml=w.xml||m||{},w.xml.name=w.xml.name||m.name,r=[R(w,n,void 0,s),...r]),r=k.default.array(e,{sample:r}),m.wrapped?(B[M]=r,x()(f)||B[M].push({_attr:f})):B=r,B}if("object"===E){if("string"==typeof t)return t;for(const e in t){var W,J,K,H;Object.hasOwn(t,e)&&(null!==(W=$[e])&&void 0!==W&&W.readOnly&&!S||null!==(J=$[e])&&void 0!==J&&J.writeOnly&&!O||(null!==(K=$[e])&&void 0!==K&&null!==(H=K.xml)&&void 0!==H&&H.attribute?f[$[e].xml.name||e]=t[e]:q(e,t[e])))}return x()(f)||B[M].push({_attr:f}),B}return B[M]=x()(f)?t:[{_attr:f},t],B}if("array"===E){let t=[];var G,Z;if((0,T.isJSONSchemaObject)(w))if(s&&(w.xml=w.xml||e.xml||{},w.xml.name=w.xml.name||m.name),o()(w.anyOf))t.push(...g()(G=w.anyOf).call(G,(e=>R((0,I.default)(e,w,n),n,void 0,s))));else if(o()(w.oneOf)){var Y;t.push(...g()(Y=w.oneOf).call(Y,(e=>R((0,I.default)(e,w,n),n,void 0,s))))}else{if(!(!s||s&&m.wrapped))return R(w,n,void 0,s);t.push(R(w,n,void 0,s))}if((0,T.isJSONSchemaObject)(b))if(s&&(b.xml=b.xml||e.xml||{},b.xml.name=b.xml.name||m.name),o()(b.anyOf))t.push(...g()(Z=b.anyOf).call(Z,(e=>R((0,I.default)(e,b,n),n,void 0,s))));else if(o()(b.oneOf)){var X;t.push(...g()(X=b.oneOf).call(X,(e=>R((0,I.default)(e,b,n),n,void 0,s))))}else{if(!(!s||s&&m.wrapped))return R(b,n,void 0,s);t.push(R(b,n,void 0,s))}return t=k.default.array(e,{sample:t}),s&&m.wrapped?(B[M]=t,x()(f)||B[M].push({_attr:f}),B):t}if("object"===E){for(let e in $){var Q,ee,te;Object.hasOwn($,e)&&(null!==(Q=$[e])&&void 0!==Q&&Q.deprecated||null!==(ee=$[e])&&void 0!==ee&&ee.readOnly&&!S||null!==(te=$[e])&&void 0!==te&&te.writeOnly&&!O||q(e))}if(s&&f&&B[M].push({_attr:f}),z())return B;if((0,T.isBooleanJSONSchema)(v))s?B[M].push({additionalProp:"Anything can be here"}):B.additionalProp1={},U++;else if((0,T.isJSONSchemaObject)(v)){var ne,re;const t=v,r=R(t,n,void 0,s);if(s&&"string"==typeof(null==t||null===(ne=t.xml)||void 0===ne?void 0:ne.name)&&"notagname"!==(null==t||null===(re=t.xml)||void 0===re?void 0:re.name))B[M].push(r);else{const t=i()(e.minProperties)&&e.minProperties>0&&U{const r=R(e,t,n,!0);if(r)return"string"==typeof r?r:w()(r,{declaration:!0,indent:"\t"})},D=(e,t,n)=>R(e,t,n,!1),F=(e,t,n)=>[e,v()(t),v()(n)],L=(0,O.Z)(M,F),B=(0,O.Z)(D,F)},83982:(e,t,n)=>{"use strict";n.r(t),n.d(t,{applyArrayConstraints:()=>p,default:()=>h});var r=n(91086),o=n.n(r),s=n(24278),i=n.n(s),a=n(25110),l=n.n(a),c=n(82737),u=n.n(c);const p=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{minItems:n,maxItems:r,uniqueItems:s}=t,{contains:a,minContains:c,maxContains:p}=t;let h=[...e];if(null!=a&&"object"==typeof a){if(o()(c)&&c>1){const e=h.at(0);for(let t=1;t0&&(h=i()(e).call(e,0,r)),o()(n)&&n>0)for(let e=0;h.length{let{sample:n}=t;return p(n,e)}},34108:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=e=>"boolean"!=typeof e.default||e.default},63273:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(83982),o=n(46852),s=n(74522),i=n(83455),a=n(58864),l=n(34108),c=n(90853);const u={array:r.default,object:o.default,string:s.default,number:i.default,integer:a.default,boolean:l.default,null:c.default},p=new Proxy(u,{get:(e,t)=>"string"==typeof t&&Object.hasOwn(e,t)?e[t]:()=>`Unknown Type: ${t}`})},58864:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(35202),o=n(22906),s=n(57864),i=n(21726);const a=e=>{const{format:t}=e;return"string"==typeof t?(e=>{const{format:t}=e,n=(0,o.default)(t);if("function"==typeof n)return n(e);switch(t){case"int32":return(0,s.default)();case"int64":return(0,i.default)()}return(0,r.integer)()})(e):(0,r.integer)()}},90853:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>null},83455:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(91086),o=n.n(r),s=n(44081),i=n.n(s),a=n(35202),l=n(22906),c=n(51890),u=n(560);const p=e=>{const{format:t}=e;let n;return n="string"==typeof t?(e=>{const{format:t}=e,n=(0,l.default)(t);if("function"==typeof n)return n(e);switch(t){case"float":return(0,c.default)();case"double":return(0,u.default)()}return(0,a.number)()})(e):(0,a.number)(),function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{minimum:n,maximum:r,exclusiveMinimum:s,exclusiveMaximum:a}=t,{multipleOf:l}=t,c=o()(e)?1:i();let u="number"==typeof n?n:null,p="number"==typeof r?r:null,h=e;if("number"==typeof s&&(u=null!==u?Math.max(u,s+c):s+c),"number"==typeof a&&(p=null!==p?Math.min(p,a-c):a-c),h=u>p&&e||u||p||h,"number"==typeof l&&l>0){const e=h%l;h=0===e?h:h+l-e}return h}(n,e)}},46852:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>{throw new Error("Not implemented")}},74522:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>L});var r=n(91086),o=n.n(r),s=n(24278),i=n.n(s),a=n(58309),l=n.n(a),c=n(35627),u=n.n(c),p=n(6557),h=n.n(p),f=n(35202),d=n(23084),m=n(3981),g=n(94518),y=n(69375),v=n(70273),b=n(28793),w=n(98269),E=n(52978),x=n(94692),S=n(13080),_=n(45693),j=n(38859),O=n(83829),k=n(37856),A=n(80375),C=n(74045),P=n(81456),N=n(65243),I=n(64299),T=n(93393),R=n(4335),M=n(22906),D=n(9507),F=n(90537);const L=function(e){let{sample:t}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{contentEncoding:n,contentMediaType:r,contentSchema:s}=e,{pattern:a,format:c}=e,p=(0,D.default)(n)||h();let L;if("string"==typeof a)L=(0,f.randexp)(a);else if("string"==typeof c)L=(e=>{const{format:t}=e,n=(0,M.default)(t);if("function"==typeof n)return n(e);switch(t){case"email":return(0,m.default)();case"idn-email":return(0,g.default)();case"hostname":return(0,y.default)();case"idn-hostname":return(0,v.default)();case"ipv4":return(0,b.default)();case"ipv6":return(0,w.default)();case"uri":return(0,E.default)();case"uri-reference":return(0,x.default)();case"iri":return(0,S.default)();case"iri-reference":return(0,_.default)();case"uuid":return(0,j.default)();case"uri-template":return(0,O.default)();case"json-pointer":return(0,k.default)();case"relative-json-pointer":return(0,A.default)();case"date-time":return(0,C.default)();case"date":return(0,P.default)();case"time":return(0,N.default)();case"duration":return(0,I.default)();case"password":return(0,T.default)();case"regex":return(0,R.default)()}return(0,f.string)()})(e);else if((0,d.isJSONSchema)(s)&&"string"==typeof r&&void 0!==t)L=l()(t)||"object"==typeof t?u()(t):String(t);else if("string"==typeof r){const t=(0,F.default)(r);"function"==typeof t&&(L=t(e))}else L=(0,f.string)();return p(function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{maxLength:n,minLength:r}=t;let s=e;if(o()(n)&&n>0&&(s=i()(s).call(s,0,n)),o()(r)&&r>0){let e=0;for(;s.length{"use strict";n.r(t),n.d(t,{SHOW:()=>a,UPDATE_FILTER:()=>s,UPDATE_LAYOUT:()=>o,UPDATE_MODE:()=>i,changeMode:()=>p,show:()=>u,updateFilter:()=>c,updateLayout:()=>l});var r=n(90242);const o="layout_update_layout",s="layout_update_filter",i="layout_update_mode",a="layout_show";function l(e){return{type:o,payload:e}}function c(e){return{type:s,payload:e}}function u(e){let t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return e=(0,r.AF)(e),{type:a,payload:{thing:e,shown:t}}}function p(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return e=(0,r.AF)(e),{type:i,payload:{thing:e,mode:t}}}},26821:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(5672),o=n(25474),s=n(4400),i=n(28989);function a(){return{statePlugins:{layout:{reducers:r.default,actions:o,selectors:s},spec:{wrapSelectors:i}}}}},5672:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(39022),o=n.n(r),s=n(43393),i=n(25474);const a={[i.UPDATE_LAYOUT]:(e,t)=>e.set("layout",t.payload),[i.UPDATE_FILTER]:(e,t)=>e.set("filter",t.payload),[i.SHOW]:(e,t)=>{const n=t.payload.shown,r=(0,s.fromJS)(t.payload.thing);return e.update("shown",(0,s.fromJS)({}),(e=>e.set(r,n)))},[i.UPDATE_MODE]:(e,t)=>{var n;let r=t.payload.thing,s=t.payload.mode;return e.setIn(o()(n=["modes"]).call(n,r),(s||"")+"")}}},4400:(e,t,n)=>{"use strict";n.r(t),n.d(t,{current:()=>i,currentFilter:()=>a,isShown:()=>l,showSummary:()=>u,whatMode:()=>c});var r=n(20573),o=n(90242),s=n(43393);const i=e=>e.get("layout"),a=e=>e.get("filter"),l=(e,t,n)=>(t=(0,o.AF)(t),e.get("shown",(0,s.fromJS)({})).get((0,s.fromJS)(t),n)),c=function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";return t=(0,o.AF)(t),e.getIn(["modes",...t],n)},u=(0,r.P1)((e=>e),(e=>!l(e,"editor")))},28989:(e,t,n)=>{"use strict";n.r(t),n.d(t,{taggedOperations:()=>s});var r=n(24278),o=n.n(r);const s=(e,t)=>function(n){for(var r=arguments.length,s=new Array(r>1?r-1:0),i=1;i=0&&(a=o()(a).call(a,0,h)),a}},9150:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(11189),o=n.n(r);function s(e){let{configs:t}=e;const n={debug:0,info:1,log:2,warn:3,error:4},r=e=>n[e]||-1;let{logLevel:s}=t,i=r(s);function a(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),o=1;o=i&&console[e](...n)}return a.warn=o()(a).call(a,null,"warn"),a.error=o()(a).call(a,null,"error"),a.info=o()(a).call(a,null,"info"),a.debug=o()(a).call(a,null,"debug"),{rootInjects:{log:a}}}},67002:(e,t,n)=>{"use strict";n.r(t),n.d(t,{CLEAR_REQUEST_BODY_VALIDATE_ERROR:()=>h,CLEAR_REQUEST_BODY_VALUE:()=>f,SET_REQUEST_BODY_VALIDATE_ERROR:()=>p,UPDATE_ACTIVE_EXAMPLES_MEMBER:()=>a,UPDATE_REQUEST_BODY_INCLUSION:()=>i,UPDATE_REQUEST_BODY_VALUE:()=>o,UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG:()=>s,UPDATE_REQUEST_CONTENT_TYPE:()=>l,UPDATE_RESPONSE_CONTENT_TYPE:()=>c,UPDATE_SELECTED_SERVER:()=>r,UPDATE_SERVER_VARIABLE_VALUE:()=>u,clearRequestBodyValidateError:()=>S,clearRequestBodyValue:()=>j,initRequestBodyValidateError:()=>_,setActiveExamplesMember:()=>v,setRequestBodyInclusion:()=>y,setRequestBodyValidateError:()=>x,setRequestBodyValue:()=>m,setRequestContentType:()=>b,setResponseContentType:()=>w,setRetainRequestBodyValueFlag:()=>g,setSelectedServer:()=>d,setServerVariableValue:()=>E});const r="oas3_set_servers",o="oas3_set_request_body_value",s="oas3_set_request_body_retain_flag",i="oas3_set_request_body_inclusion",a="oas3_set_active_examples_member",l="oas3_set_request_content_type",c="oas3_set_response_content_type",u="oas3_set_server_variable_value",p="oas3_set_request_body_validate_error",h="oas3_clear_request_body_validate_error",f="oas3_clear_request_body_value";function d(e,t){return{type:r,payload:{selectedServerUrl:e,namespace:t}}}function m(e){let{value:t,pathMethod:n}=e;return{type:o,payload:{value:t,pathMethod:n}}}const g=e=>{let{value:t,pathMethod:n}=e;return{type:s,payload:{value:t,pathMethod:n}}};function y(e){let{value:t,pathMethod:n,name:r}=e;return{type:i,payload:{value:t,pathMethod:n,name:r}}}function v(e){let{name:t,pathMethod:n,contextType:r,contextName:o}=e;return{type:a,payload:{name:t,pathMethod:n,contextType:r,contextName:o}}}function b(e){let{value:t,pathMethod:n}=e;return{type:l,payload:{value:t,pathMethod:n}}}function w(e){let{value:t,path:n,method:r}=e;return{type:c,payload:{value:t,path:n,method:r}}}function E(e){let{server:t,namespace:n,key:r,val:o}=e;return{type:u,payload:{server:t,namespace:n,key:r,val:o}}}const x=e=>{let{path:t,method:n,validationErrors:r}=e;return{type:p,payload:{path:t,method:n,validationErrors:r}}},S=e=>{let{path:t,method:n}=e;return{type:h,payload:{path:t,method:n}}},_=e=>{let{pathMethod:t}=e;return{type:h,payload:{path:t[0],method:t[1]}}},j=e=>{let{pathMethod:t}=e;return{type:f,payload:{pathMethod:t}}}},73723:(e,t,n)=>{"use strict";n.r(t),n.d(t,{definitionsToAuthorize:()=>p});var r=n(86),o=n.n(r),s=n(14418),i=n.n(s),a=n(24282),l=n.n(a),c=n(20573),u=n(43393);const p=(h=(0,c.P1)((e=>e),(e=>{let{specSelectors:t}=e;return t.securityDefinitions()}),((e,t)=>{var n;let r=(0,u.List)();return t?(o()(n=t.entrySeq()).call(n,(e=>{let[t,n]=e;const s=n.get("type");var a;if("oauth2"===s&&o()(a=n.get("flows").entrySeq()).call(a,(e=>{let[o,s]=e,a=(0,u.fromJS)({flow:o,authorizationUrl:s.get("authorizationUrl"),tokenUrl:s.get("tokenUrl"),scopes:s.get("scopes"),type:n.get("type"),description:n.get("description")});r=r.push(new u.Map({[t]:i()(a).call(a,(e=>void 0!==e))}))})),"http"!==s&&"apiKey"!==s||(r=r.push(new u.Map({[t]:n}))),"openIdConnect"===s&&n.get("openIdConnectData")){let e=n.get("openIdConnectData"),s=e.get("grant_types_supported")||["authorization_code","implicit"];o()(s).call(s,(o=>{var s;let a=e.get("scopes_supported")&&l()(s=e.get("scopes_supported")).call(s,((e,t)=>e.set(t,"")),new u.Map),c=(0,u.fromJS)({flow:o,authorizationUrl:e.get("authorization_endpoint"),tokenUrl:e.get("token_endpoint"),scopes:a,type:"oauth2",openIdConnectUrl:n.get("openIdConnectUrl")});r=r.push(new u.Map({[t]:i()(c).call(c,(e=>void 0!==e))}))}))}})),r):r})),(e,t)=>function(){for(var n=arguments.length,r=new Array(n),o=0;o{"use strict";n.r(t),n.d(t,{default:()=>l});var r=n(28222),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294);n(23930);const l=e=>{let{callbacks:t,specPath:n,specSelectors:r,getComponent:s}=e;const l=r.callbacksOperations({callbacks:t,specPath:n}),c=o()(l),u=s("OperationContainer",!0);return 0===c.length?a.createElement("span",null,"No callbacks"):a.createElement("div",null,i()(c).call(c,(e=>{var t;return a.createElement("div",{key:`${e}`},a.createElement("h2",null,e),i()(t=l[e]).call(t,(t=>a.createElement(u,{key:`${e}-${t.path}-${t.method}`,op:t.operation,tag:"callbacks",method:t.method,path:t.path,specPath:t.specPath,allowTryItOut:!1}))))})))}},86775:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>h});var r=n(61125),o=n.n(r),s=n(76986),i=n.n(s),a=n(14418),l=n.n(a),c=n(97606),u=n.n(c),p=n(67294);class h extends p.Component{constructor(e,t){super(e,t),o()(this,"onChange",(e=>{let{onChange:t}=this.props,{value:n,name:r}=e.target,o=i()({},this.state.value);r?o[r]=n:o=n,this.setState({value:o},(()=>t(this.state)))}));let{name:n,schema:r}=this.props,s=this.getValue();this.state={name:n,schema:r,value:s}}getValue(){let{name:e,authorized:t}=this.props;return t&&t.getIn([e,"value"])}render(){var e;let{schema:t,getComponent:n,errSelectors:r,name:o}=this.props;const s=n("Input"),i=n("Row"),a=n("Col"),c=n("authError"),h=n("Markdown",!0),f=n("JumpToPath",!0),d=(t.get("scheme")||"").toLowerCase();let m=this.getValue(),g=l()(e=r.allErrors()).call(e,(e=>e.get("authId")===o));if("basic"===d){var y;let e=m?m.get("username"):null;return p.createElement("div",null,p.createElement("h4",null,p.createElement("code",null,o||t.get("name")),"ย  (http, Basic)",p.createElement(f,{path:["securityDefinitions",o]})),e&&p.createElement("h6",null,"Authorized"),p.createElement(i,null,p.createElement(h,{source:t.get("description")})),p.createElement(i,null,p.createElement("label",null,"Username:"),e?p.createElement("code",null," ",e," "):p.createElement(a,null,p.createElement(s,{type:"text",required:"required",name:"username","aria-label":"auth-basic-username",onChange:this.onChange,autoFocus:!0}))),p.createElement(i,null,p.createElement("label",null,"Password:"),e?p.createElement("code",null," ****** "):p.createElement(a,null,p.createElement(s,{autoComplete:"new-password",name:"password",type:"password","aria-label":"auth-basic-password",onChange:this.onChange}))),u()(y=g.valueSeq()).call(y,((e,t)=>p.createElement(c,{error:e,key:t}))))}var v;return"bearer"===d?p.createElement("div",null,p.createElement("h4",null,p.createElement("code",null,o||t.get("name")),"ย  (http, Bearer)",p.createElement(f,{path:["securityDefinitions",o]})),m&&p.createElement("h6",null,"Authorized"),p.createElement(i,null,p.createElement(h,{source:t.get("description")})),p.createElement(i,null,p.createElement("label",null,"Value:"),m?p.createElement("code",null," ****** "):p.createElement(a,null,p.createElement(s,{type:"text","aria-label":"auth-bearer-value",onChange:this.onChange,autoFocus:!0}))),u()(v=g.valueSeq()).call(v,((e,t)=>p.createElement(c,{error:e,key:t})))):p.createElement("div",null,p.createElement("em",null,p.createElement("b",null,o)," HTTP authentication: unsupported scheme ",`'${d}'`))}}},76467:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(33427),o=n(42458),s=n(15757),i=n(56617),a=n(9928),l=n(45327),c=n(86775),u=n(96796);const p={Callbacks:r.default,HttpAuth:c.default,RequestBody:o.default,Servers:i.default,ServersContainer:a.default,RequestBodyEditor:l.default,OperationServers:u.default,operationLink:s.default}},15757:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(35627),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294);n(23930);class l extends a.Component{render(){const{link:e,name:t,getComponent:n}=this.props,r=n("Markdown",!0);let s=e.get("operationId")||e.get("operationRef"),l=e.get("parameters")&&e.get("parameters").toJS(),c=e.get("description");return a.createElement("div",{className:"operation-link"},a.createElement("div",{className:"description"},a.createElement("b",null,a.createElement("code",null,t)),c?a.createElement(r,{source:c}):null),a.createElement("pre",null,"Operation `",s,"`",a.createElement("br",null),a.createElement("br",null),"Parameters ",function(e,t){var n;if("string"!=typeof t)return"";return i()(n=t.split("\n")).call(n,((t,n)=>n>0?Array(e+1).join(" ")+t:t)).join("\n")}(0,o()(l,null,2))||"{}",a.createElement("br",null)))}}const c=l},96796:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(61125),o=n.n(r),s=n(67294);n(23930);class i extends s.Component{constructor(){super(...arguments),o()(this,"setSelectedServer",(e=>{const{path:t,method:n}=this.props;return this.forceUpdate(),this.props.setSelectedServer(e,`${t}:${n}`)})),o()(this,"setServerVariableValue",(e=>{const{path:t,method:n}=this.props;return this.forceUpdate(),this.props.setServerVariableValue({...e,namespace:`${t}:${n}`})})),o()(this,"getSelectedServer",(()=>{const{path:e,method:t}=this.props;return this.props.getSelectedServer(`${e}:${t}`)})),o()(this,"getServerVariable",((e,t)=>{const{path:n,method:r}=this.props;return this.props.getServerVariable({namespace:`${n}:${r}`,server:e},t)})),o()(this,"getEffectiveServerValue",(e=>{const{path:t,method:n}=this.props;return this.props.getEffectiveServerValue({server:e,namespace:`${t}:${n}`})}))}render(){const{operationServers:e,pathServers:t,getComponent:n}=this.props;if(!e&&!t)return null;const r=n("Servers"),o=e||t,i=e?"operation":"path";return s.createElement("div",{className:"opblock-section operation-servers"},s.createElement("div",{className:"opblock-section-header"},s.createElement("div",{className:"tab-header"},s.createElement("h4",{className:"opblock-title"},"Servers"))),s.createElement("div",{className:"opblock-description-wrapper"},s.createElement("h4",{className:"message"},"These ",i,"-level options override the global server options."),s.createElement(r,{servers:o,currentServer:this.getSelectedServer(),setSelectedServer:this.setSelectedServer,setServerVariableValue:this.setServerVariableValue,getServerVariable:this.getServerVariable,getEffectiveServerValue:this.getEffectiveServerValue})))}}},45327:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(61125),o=n.n(r),s=n(67294),i=n(94184),a=n.n(i),l=n(90242);const c=Function.prototype;class u extends s.PureComponent{constructor(e,t){super(e,t),o()(this,"applyDefaultValue",(e=>{const{onChange:t,defaultValue:n}=e||this.props;return this.setState({value:n}),t(n)})),o()(this,"onChange",(e=>{this.props.onChange((0,l.Pz)(e))})),o()(this,"onDomChange",(e=>{const t=e.target.value;this.setState({value:t},(()=>this.onChange(t)))})),this.state={value:(0,l.Pz)(e.value)||e.defaultValue},e.onChange(e.value)}UNSAFE_componentWillReceiveProps(e){this.props.value!==e.value&&e.value!==this.state.value&&this.setState({value:(0,l.Pz)(e.value)}),!e.value&&e.defaultValue&&this.state.value&&this.applyDefaultValue(e)}render(){let{getComponent:e,errors:t}=this.props,{value:n}=this.state,r=t.size>0;const o=e("TextArea");return s.createElement("div",{className:"body-param"},s.createElement(o,{className:a()("body-param__text",{invalid:r}),title:t.size?t.join(", "):"",value:n,onChange:this.onDomChange}))}}o()(u,"defaultProps",{onChange:c,userHasEditedBody:!1})},42458:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>g,getDefaultRequestBodyValue:()=>m});var r=n(97606),o=n.n(r),s=n(11882),i=n.n(s),a=n(58118),l=n.n(a),c=n(58309),u=n.n(c),p=n(67294),h=(n(23930),n(43393)),f=n(90242),d=n(2518);const m=(e,t,n,r)=>{const o=e.getIn(["content",t]),s=o.get("schema").toJS(),i=void 0!==o.get("examples"),a=o.get("example"),l=i?o.getIn(["examples",n,"value"]):a,c=r.getSampleSchema(s,t,{includeWriteOnly:!0},l);return(0,f.Pz)(c)},g=e=>{let{userHasEditedBody:t,requestBody:n,requestBodyValue:r,requestBodyInclusionSetting:s,requestBodyErrors:a,getComponent:c,getConfigs:g,specSelectors:y,fn:v,contentType:b,isExecute:w,specPath:E,onChange:x,onChangeIncludeEmpty:S,activeExamplesKey:_,updateActiveExamplesKey:j,setRetainRequestBodyValueFlag:O}=e;const k=e=>{x(e.target.files[0])},A=e=>{let t={key:e,shouldDispatchInit:!1,defaultValue:!0};return"no value"===s.get(e,"no value")&&(t.shouldDispatchInit=!0),t},C=c("Markdown",!0),P=c("modelExample"),N=c("RequestBodyEditor"),I=c("highlightCode"),T=c("ExamplesSelectValueRetainer"),R=c("Example"),M=c("ParameterIncludeEmpty"),{showCommonExtensions:D}=g(),F=n&&n.get("description")||null,L=n&&n.get("content")||new h.OrderedMap;b=b||L.keySeq().first()||"";const B=L.get(b,(0,h.OrderedMap)()),$=B.get("schema",(0,h.OrderedMap)()),q=B.get("examples",null),U=null==q?void 0:o()(q).call(q,((e,t)=>{var r;const o=null===(r=e)||void 0===r?void 0:r.get("value",null);return o&&(e=e.set("value",m(n,b,t,v),o)),e}));if(a=h.List.isList(a)?a:(0,h.List)(),!B.size)return null;const z="object"===B.getIn(["schema","type"]),V="binary"===B.getIn(["schema","format"]),W="base64"===B.getIn(["schema","format"]);if("application/octet-stream"===b||0===i()(b).call(b,"image/")||0===i()(b).call(b,"audio/")||0===i()(b).call(b,"video/")||V||W){const e=c("Input");return w?p.createElement(e,{type:"file",onChange:k}):p.createElement("i",null,"Example values are not available for ",p.createElement("code",null,b)," media types.")}if(z&&("application/x-www-form-urlencoded"===b||0===i()(b).call(b,"multipart/"))&&$.get("properties",(0,h.OrderedMap)()).size>0){var J;const e=c("JsonSchemaForm"),t=c("ParameterExt"),n=$.get("properties",(0,h.OrderedMap)());return r=h.Map.isMap(r)?r:(0,h.OrderedMap)(),p.createElement("div",{className:"table-container"},F&&p.createElement(C,{source:F}),p.createElement("table",null,p.createElement("tbody",null,h.Map.isMap(n)&&o()(J=n.entrySeq()).call(J,(n=>{var i,d;let[m,g]=n;if(g.get("readOnly"))return;let y=D?(0,f.po)(g):null;const b=l()(i=$.get("required",(0,h.List)())).call(i,m),E=g.get("type"),_=g.get("format"),j=g.get("description"),O=r.getIn([m,"value"]),k=r.getIn([m,"errors"])||a,P=s.get(m)||!1,N=g.has("default")||g.has("example")||g.hasIn(["items","example"])||g.hasIn(["items","default"]),I=g.has("enum")&&(1===g.get("enum").size||b),T=N||I;let R="";"array"!==E||T||(R=[]),("object"===E||T)&&(R=v.getSampleSchema(g,!1,{includeWriteOnly:!0})),"string"!=typeof R&&"object"===E&&(R=(0,f.Pz)(R)),"string"==typeof R&&"array"===E&&(R=JSON.parse(R));const F="string"===E&&("binary"===_||"base64"===_);return p.createElement("tr",{key:m,className:"parameters","data-property-name":m},p.createElement("td",{className:"parameters-col_name"},p.createElement("div",{className:b?"parameter__name required":"parameter__name"},m,b?p.createElement("span",null,"ย *"):null),p.createElement("div",{className:"parameter__type"},E,_&&p.createElement("span",{className:"prop-format"},"($",_,")"),D&&y.size?o()(d=y.entrySeq()).call(d,(e=>{let[n,r]=e;return p.createElement(t,{key:`${n}-${r}`,xKey:n,xVal:r})})):null),p.createElement("div",{className:"parameter__deprecated"},g.get("deprecated")?"deprecated":null)),p.createElement("td",{className:"parameters-col_description"},p.createElement(C,{source:j}),w?p.createElement("div",null,p.createElement(e,{fn:v,dispatchInitialValue:!F,schema:g,description:m,getComponent:c,value:void 0===O?R:O,required:b,errors:k,onChange:e=>{x(e,[m])}}),b?null:p.createElement(M,{onChange:e=>S(m,e),isIncluded:P,isIncludedOptions:A(m),isDisabled:u()(O)?0!==O.length:!(0,f.O2)(O)})):null))})))))}const K=m(n,b,_,v);let H=null;return(0,d.O)(K)&&(H="json"),p.createElement("div",null,F&&p.createElement(C,{source:F}),U?p.createElement(T,{userHasEditedBody:t,examples:U,currentKey:_,currentUserInputValue:r,onSelect:e=>{j(e)},updateValue:x,defaultToFirstExample:!0,getComponent:c,setRetainRequestBodyValueFlag:O}):null,w?p.createElement("div",null,p.createElement(N,{value:r,errors:a,defaultValue:K,onChange:x,getComponent:c})):p.createElement(P,{getComponent:c,getConfigs:g,specSelectors:y,expandDepth:1,isExecute:w,schema:B.get("schema"),specPath:E.push("content",b),example:p.createElement(I,{className:"body-param__example",getConfigs:g,language:H,value:(0,f.Pz)(r)||K}),includeWriteOnly:!0}),U?p.createElement(R,{example:U.get(_),getComponent:c,getConfigs:g}):null)}},9928:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);class o extends r.Component{render(){const{specSelectors:e,oas3Selectors:t,oas3Actions:n,getComponent:o}=this.props,s=e.servers(),i=o("Servers");return s&&s.size?r.createElement("div",null,r.createElement("span",{className:"servers-title"},"Servers"),r.createElement(i,{servers:s,currentServer:t.selectedServer(),setSelectedServer:n.setSelectedServer,setServerVariableValue:n.setServerVariableValue,getServerVariable:t.serverVariableValue,getEffectiveServerValue:t.serverEffectiveValue})):null}}},56617:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(61125),o=n.n(r),s=n(51679),i=n.n(s),a=n(97606),l=n.n(a),c=n(67294),u=n(43393);n(23930);class p extends c.Component{constructor(){super(...arguments),o()(this,"onServerChange",(e=>{this.setServer(e.target.value)})),o()(this,"onServerVariableValueChange",(e=>{let{setServerVariableValue:t,currentServer:n}=this.props,r=e.target.getAttribute("data-variable"),o=e.target.value;"function"==typeof t&&t({server:n,key:r,val:o})})),o()(this,"setServer",(e=>{let{setSelectedServer:t}=this.props;t(e)}))}componentDidMount(){var e;let{servers:t,currentServer:n}=this.props;n||this.setServer(null===(e=t.first())||void 0===e?void 0:e.get("url"))}UNSAFE_componentWillReceiveProps(e){let{servers:t,setServerVariableValue:n,getServerVariable:r}=e;if(this.props.currentServer!==e.currentServer||this.props.servers!==e.servers){var o;let s=i()(t).call(t,(t=>t.get("url")===e.currentServer)),a=i()(o=this.props.servers).call(o,(e=>e.get("url")===this.props.currentServer))||(0,u.OrderedMap)();if(!s)return this.setServer(t.first().get("url"));let c=a.get("variables")||(0,u.OrderedMap)(),p=(i()(c).call(c,(e=>e.get("default")))||(0,u.OrderedMap)()).get("default"),h=s.get("variables")||(0,u.OrderedMap)(),f=(i()(h).call(h,(e=>e.get("default")))||(0,u.OrderedMap)()).get("default");l()(h).call(h,((t,o)=>{r(e.currentServer,o)&&p===f||n({server:e.currentServer,key:o,val:t.get("default")||""})}))}}render(){var e,t;let{servers:n,currentServer:r,getServerVariable:o,getEffectiveServerValue:s}=this.props,a=(i()(n).call(n,(e=>e.get("url")===r))||(0,u.OrderedMap)()).get("variables")||(0,u.OrderedMap)(),p=0!==a.size;return c.createElement("div",{className:"servers"},c.createElement("label",{htmlFor:"servers"},c.createElement("select",{onChange:this.onServerChange,value:r},l()(e=n.valueSeq()).call(e,(e=>c.createElement("option",{value:e.get("url"),key:e.get("url")},e.get("url"),e.get("description")&&` - ${e.get("description")}`))).toArray())),p?c.createElement("div",null,c.createElement("div",{className:"computed-url"},"Computed URL:",c.createElement("code",null,s(r))),c.createElement("h4",null,"Server variables"),c.createElement("table",null,c.createElement("tbody",null,l()(t=a.entrySeq()).call(t,(e=>{var t;let[n,s]=e;return c.createElement("tr",{key:n},c.createElement("td",null,n),c.createElement("td",null,s.get("enum")?c.createElement("select",{"data-variable":n,onChange:this.onServerVariableValueChange},l()(t=s.get("enum")).call(t,(e=>c.createElement("option",{selected:e===o(r,n),key:e,value:e},e)))):c.createElement("input",{type:"text",value:o(r,n)||"",onChange:this.onServerVariableValueChange,"data-variable":n})))}))))):null)}}},7779:(e,t,n)=>{"use strict";n.r(t),n.d(t,{OAS30ComponentWrapFactory:()=>c,OAS3ComponentWrapFactory:()=>l,isOAS30:()=>i,isSwagger2:()=>a});var r=n(23101),o=n.n(r),s=n(67294);function i(e){const t=e.get("openapi");return"string"==typeof t&&/^3\.0\.([0123])(?:-rc[012])?$/.test(t)}function a(e){const t=e.get("swagger");return"string"==typeof t&&"2.0"===t}function l(e){return(t,n)=>r=>{var i;return"function"==typeof(null===(i=n.specSelectors)||void 0===i?void 0:i.isOAS3)?n.specSelectors.isOAS3()?s.createElement(e,o()({},r,n,{Ori:t})):s.createElement(t,r):(console.warn("OAS3 wrapper: couldn't get spec"),null)}}function c(e){return(t,n)=>r=>{var i;return"function"==typeof(null===(i=n.specSelectors)||void 0===i?void 0:i.isOAS30)?n.specSelectors.isOAS30()?s.createElement(e,o()({},r,n,{Ori:t})):s.createElement(t,r):(console.warn("OAS30 wrapper: couldn't get spec"),null)}}},97451:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(92044),o=n(73723),s=n(91741),i=n(76467),a=n(37761),l=n(67002),c=n(5065),u=n(62109);function p(){return{components:i.default,wrapComponents:a.default,statePlugins:{spec:{wrapSelectors:r,selectors:s},auth:{wrapSelectors:o},oas3:{actions:l,reducers:u.default,selectors:c}}}}},62109:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(8712),o=n.n(r),s=n(86),i=n.n(s),a=n(24282),l=n.n(a),c=n(43393),u=n(67002);const p={[u.UPDATE_SELECTED_SERVER]:(e,t)=>{let{payload:{selectedServerUrl:n,namespace:r}}=t;const o=r?[r,"selectedServer"]:["selectedServer"];return e.setIn(o,n)},[u.UPDATE_REQUEST_BODY_VALUE]:(e,t)=>{let{payload:{value:n,pathMethod:r}}=t,[s,a]=r;if(!c.Map.isMap(n))return e.setIn(["requestData",s,a,"bodyValue"],n);let l,u=e.getIn(["requestData",s,a,"bodyValue"])||(0,c.Map)();c.Map.isMap(u)||(u=(0,c.Map)());const[...p]=o()(n).call(n);return i()(p).call(p,(e=>{let t=n.getIn([e]);u.has(e)&&c.Map.isMap(t)||(l=u.setIn([e,"value"],t))})),e.setIn(["requestData",s,a,"bodyValue"],l)},[u.UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG]:(e,t)=>{let{payload:{value:n,pathMethod:r}}=t,[o,s]=r;return e.setIn(["requestData",o,s,"retainBodyValue"],n)},[u.UPDATE_REQUEST_BODY_INCLUSION]:(e,t)=>{let{payload:{value:n,pathMethod:r,name:o}}=t,[s,i]=r;return e.setIn(["requestData",s,i,"bodyInclusion",o],n)},[u.UPDATE_ACTIVE_EXAMPLES_MEMBER]:(e,t)=>{let{payload:{name:n,pathMethod:r,contextType:o,contextName:s}}=t,[i,a]=r;return e.setIn(["examples",i,a,o,s,"activeExample"],n)},[u.UPDATE_REQUEST_CONTENT_TYPE]:(e,t)=>{let{payload:{value:n,pathMethod:r}}=t,[o,s]=r;return e.setIn(["requestData",o,s,"requestContentType"],n)},[u.UPDATE_RESPONSE_CONTENT_TYPE]:(e,t)=>{let{payload:{value:n,path:r,method:o}}=t;return e.setIn(["requestData",r,o,"responseContentType"],n)},[u.UPDATE_SERVER_VARIABLE_VALUE]:(e,t)=>{let{payload:{server:n,namespace:r,key:o,val:s}}=t;const i=r?[r,"serverVariableValues",n,o]:["serverVariableValues",n,o];return e.setIn(i,s)},[u.SET_REQUEST_BODY_VALIDATE_ERROR]:(e,t)=>{let{payload:{path:n,method:r,validationErrors:o}}=t,s=[];if(s.push("Required field is not provided"),o.missingBodyValue)return e.setIn(["requestData",n,r,"errors"],(0,c.fromJS)(s));if(o.missingRequiredKeys&&o.missingRequiredKeys.length>0){const{missingRequiredKeys:t}=o;return e.updateIn(["requestData",n,r,"bodyValue"],(0,c.fromJS)({}),(e=>l()(t).call(t,((e,t)=>e.setIn([t,"errors"],(0,c.fromJS)(s))),e)))}return console.warn("unexpected result: SET_REQUEST_BODY_VALIDATE_ERROR"),e},[u.CLEAR_REQUEST_BODY_VALIDATE_ERROR]:(e,t)=>{let{payload:{path:n,method:r}}=t;const s=e.getIn(["requestData",n,r,"bodyValue"]);if(!c.Map.isMap(s))return e.setIn(["requestData",n,r,"errors"],(0,c.fromJS)([]));const[...i]=o()(s).call(s);return i?e.updateIn(["requestData",n,r,"bodyValue"],(0,c.fromJS)({}),(e=>l()(i).call(i,((e,t)=>e.setIn([t,"errors"],(0,c.fromJS)([]))),e))):e},[u.CLEAR_REQUEST_BODY_VALUE]:(e,t)=>{let{payload:{pathMethod:n}}=t,[r,o]=n;const s=e.getIn(["requestData",r,o,"bodyValue"]);return s?c.Map.isMap(s)?e.setIn(["requestData",r,o,"bodyValue"],(0,c.Map)()):e.setIn(["requestData",r,o,"bodyValue"],""):e}}},5065:(e,t,n)=>{"use strict";n.r(t),n.d(t,{activeExamplesMember:()=>S,hasUserEditedBody:()=>w,requestBodyErrors:()=>x,requestBodyInclusionSetting:()=>E,requestBodyValue:()=>y,requestContentType:()=>_,responseContentType:()=>j,selectDefaultRequestBodyValue:()=>b,selectedServer:()=>g,serverEffectiveValue:()=>A,serverVariableValue:()=>O,serverVariables:()=>k,shouldRetainRequestBodyValue:()=>v,validOperationMethods:()=>I,validateBeforeExecute:()=>C,validateShallowRequired:()=>N});var r=n(97606),o=n.n(r),s=n(86),i=n.n(s),a=n(28222),l=n.n(a),c=n(11882),u=n.n(c),p=n(43393),h=n(20573),f=n(42458),d=n(90242);const m=e=>function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o{if(n.getSystem().specSelectors.isOAS3()){const o=e(t,...r);return"function"==typeof o?o(n):o}return null}};const g=m(((e,t)=>{const n=t?[t,"selectedServer"]:["selectedServer"];return e.getIn(n)||""})),y=m(((e,t,n)=>e.getIn(["requestData",t,n,"bodyValue"])||null)),v=m(((e,t,n)=>e.getIn(["requestData",t,n,"retainBodyValue"])||!1)),b=(e,t,n)=>e=>{const{oas3Selectors:r,specSelectors:o,fn:s}=e.getSystem();if(o.isOAS3()){const e=r.requestContentType(t,n);if(e)return(0,f.getDefaultRequestBodyValue)(o.specResolvedSubtree(["paths",t,n,"requestBody"]),e,r.activeExamplesMember(t,n,"requestBody","requestBody"),s)}return null},w=m(((e,t,n)=>e=>{const{oas3Selectors:r,specSelectors:o,fn:s}=e;let i=!1;const a=r.requestContentType(t,n);let l=r.requestBodyValue(t,n);const c=o.specResolvedSubtree(["paths",t,n,"requestBody"]);if(!c)return!1;if(p.Map.isMap(l)&&(l=(0,d.Pz)(l.mapEntries((e=>p.Map.isMap(e[1])?[e[0],e[1].get("value")]:e)).toJS())),p.List.isList(l)&&(l=(0,d.Pz)(l)),a){const e=(0,f.getDefaultRequestBodyValue)(c,a,r.activeExamplesMember(t,n,"requestBody","requestBody"),s);i=!!l&&l!==e}return i})),E=m(((e,t,n)=>e.getIn(["requestData",t,n,"bodyInclusion"])||(0,p.Map)())),x=m(((e,t,n)=>e.getIn(["requestData",t,n,"errors"])||null)),S=m(((e,t,n,r,o)=>e.getIn(["examples",t,n,r,o,"activeExample"])||null)),_=m(((e,t,n)=>e.getIn(["requestData",t,n,"requestContentType"])||null)),j=m(((e,t,n)=>e.getIn(["requestData",t,n,"responseContentType"])||null)),O=m(((e,t,n)=>{let r;if("string"!=typeof t){const{server:e,namespace:o}=t;r=o?[o,"serverVariableValues",e,n]:["serverVariableValues",e,n]}else{r=["serverVariableValues",t,n]}return e.getIn(r)||null})),k=m(((e,t)=>{let n;if("string"!=typeof t){const{server:e,namespace:r}=t;n=r?[r,"serverVariableValues",e]:["serverVariableValues",e]}else{n=["serverVariableValues",t]}return e.getIn(n)||(0,p.OrderedMap)()})),A=m(((e,t)=>{var n,r;if("string"!=typeof t){const{server:o,namespace:s}=t;r=o,n=s?e.getIn([s,"serverVariableValues",r]):e.getIn(["serverVariableValues",r])}else r=t,n=e.getIn(["serverVariableValues",r]);n=n||(0,p.OrderedMap)();let s=r;return o()(n).call(n,((e,t)=>{s=s.replace(new RegExp(`{${t}}`,"g"),e)})),s})),C=(P=(e,t)=>((e,t)=>(t=t||[],!!e.getIn(["requestData",...t,"bodyValue"])))(e,t),function(){for(var e=arguments.length,t=new Array(e),n=0;n{const n=e.getSystem().specSelectors.specJson();let r=[...t][1]||[];return!n.getIn(["paths",...r,"requestBody","required"])||P(...t)}});var P;const N=(e,t)=>{var n;let{oas3RequiredRequestBodyContentType:r,oas3RequestContentType:o,oas3RequestBodyValue:s}=t,a=[];if(!p.Map.isMap(s))return a;let c=[];return i()(n=l()(r.requestContentType)).call(n,(e=>{if(e===o){let t=r.requestContentType[e];i()(t).call(t,(e=>{u()(c).call(c,e)<0&&c.push(e)}))}})),i()(c).call(c,(e=>{s.getIn([e,"value"])||a.push(e)})),a},I=(0,h.P1)((()=>["get","put","post","delete","options","head","patch","trace"]))},91741:(e,t,n)=>{"use strict";n.r(t),n.d(t,{callbacksOperations:()=>E,isOAS3:()=>v,isOAS30:()=>y,isSwagger2:()=>g,servers:()=>w});var r=n(97606),o=n.n(r),s=n(24282),i=n.n(s),a=n(14418),l=n.n(a),c=n(58118),u=n.n(c),p=n(39022),h=n.n(p),f=n(43393),d=n(7779);const m=(0,f.Map)(),g=()=>e=>{const t=e.getSystem().specSelectors.specJson();return(0,d.isSwagger2)(t)},y=()=>e=>{const t=e.getSystem().specSelectors.specJson();return(0,d.isOAS30)(t)},v=()=>e=>e.getSystem().specSelectors.isOAS30();function b(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o{if(n.specSelectors.isOAS3()){const o=e(t,...r);return"function"==typeof o?o(n):o}return null}}}const w=b((()=>e=>e.specSelectors.specJson().get("servers",m))),E=b(((e,t)=>{let{callbacks:n,specPath:r}=t;return e=>{var t;const s=e.specSelectors.validOperationMethods();return f.Map.isMap(n)?o()(t=i()(n).call(n,((e,t,n)=>f.Map.isMap(t)?i()(t).call(t,((e,t,i)=>{var a,c;if(!f.Map.isMap(t))return e;const p=o()(a=l()(c=t.entrySeq()).call(c,(e=>{let[t]=e;return u()(s).call(s,t)}))).call(a,(e=>{let[t,o]=e;return{operation:(0,f.Map)({operation:o}),method:t,path:i,callbackName:n,specPath:h()(r).call(r,[n,i,t])}}));return h()(e).call(e,p)}),(0,f.List)()):e),(0,f.List)()).groupBy((e=>e.callbackName))).call(t,(e=>e.toArray())).toObject():{}}}))},92044:(e,t,n)=>{"use strict";n.r(t),n.d(t,{basePath:()=>d,consumes:()=>m,definitions:()=>c,hasHost:()=>u,host:()=>f,produces:()=>g,schemes:()=>y,securityDefinitions:()=>p,validOperationMethods:()=>h});var r=n(20573),o=n(33881),s=n(43393);const i=(0,s.Map)();function a(e){return(t,n)=>function(){if(n.getSystem().specSelectors.isOAS3()){const t=e(...arguments);return"function"==typeof t?t(n):t}return t(...arguments)}}const l=a((0,r.P1)((()=>null))),c=a((()=>e=>{const t=e.getSystem().specSelectors.specJson().getIn(["components","schemas"]);return s.Map.isMap(t)?t:i})),u=a((()=>e=>e.getSystem().specSelectors.specJson().hasIn(["servers",0]))),p=a((0,r.P1)(o.specJsonWithResolvedSubtrees,(e=>e.getIn(["components","securitySchemes"])||null))),h=(e,t)=>function(n){if(t.specSelectors.isOAS3())return t.oas3Selectors.validOperationMethods();for(var r=arguments.length,o=new Array(r>1?r-1:0),s=1;s{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(7779).OAS3ComponentWrapFactory)((e=>{let{Ori:t,...n}=e;const{schema:o,getComponent:s,errSelectors:i,authorized:a,onAuthChange:l,name:c}=n,u=s("HttpAuth");return"http"===o.get("type")?r.createElement(u,{key:c,schema:o,name:c,errSelectors:i,authorized:a,getComponent:s,onChange:l}):r.createElement(t,n)}))},37761:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(22460),o=n(70356),s=n(69487),i=n(50058),a=n(53499),l=n(90287);const c={Markdown:r.default,AuthItem:o.default,JsonSchema_string:l.default,VersionStamp:s.default,model:a.default,onlineValidatorBadge:i.default}},90287:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(7779).OAS3ComponentWrapFactory)((e=>{let{Ori:t,...n}=e;const{schema:o,getComponent:s,errors:i,onChange:a}=n,l=o&&o.get?o.get("format"):null,c=o&&o.get?o.get("type"):null,u=s("Input");return c&&"string"===c&&l&&("binary"===l||"base64"===l)?r.createElement(u,{type:"file",className:i.length?"invalid":"",title:i.length?i:"",onChange:e=>{a(e.target.files[0])},disabled:t.isDisabled}):r.createElement(t,n)}))},22460:(e,t,n)=>{"use strict";n.r(t),n.d(t,{Markdown:()=>h,default:()=>f});var r=n(81607),o=n.n(r),s=n(67294),i=n(94184),a=n.n(i),l=n(89927),c=n(7779),u=n(4599);const p=new l._("commonmark");p.block.ruler.enable(["table"]),p.set({linkTarget:"_blank"});const h=e=>{let{source:t,className:n="",getConfigs:r}=e;if("string"!=typeof t)return null;if(t){const{useUnsafeMarkdown:e}=r(),i=p.render(t),l=(0,u.s)(i,{useUnsafeMarkdown:e});let c;return"string"==typeof l&&(c=o()(l).call(l)),s.createElement("div",{dangerouslySetInnerHTML:{__html:c},className:a()(n,"renderedMarkdown")})}return null};h.defaultProps={getConfigs:()=>({useUnsafeMarkdown:!1})};const f=(0,c.OAS3ComponentWrapFactory)(h)},53499:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(23101),o=n.n(r),s=n(67294),i=n(7779),a=n(53795);class l extends s.Component{render(){let{getConfigs:e,schema:t}=this.props,n=["model-box"],r=null;return!0===t.get("deprecated")&&(n.push("deprecated"),r=s.createElement("span",{className:"model-deprecated-warning"},"Deprecated:")),s.createElement("div",{className:n.join(" ")},r,s.createElement(a.Z,o()({},this.props,{getConfigs:e,depth:1,expandDepth:this.props.expandDepth||0})))}}const c=(0,i.OAS3ComponentWrapFactory)(l)},50058:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(7779),o=n(5623);const s=(0,r.OAS3ComponentWrapFactory)(o.Z)},69487:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(7779).OAS30ComponentWrapFactory)((e=>{const{Ori:t}=e;return r.createElement("span",null,r.createElement(t,e),r.createElement("small",{className:"version-stamp"},r.createElement("pre",{className:"version"},"OAS 3.0")))}))},92372:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(76986),o=n.n(r),s=n(25800),i=n(84380);const a=function(e){let{fn:t,getSystem:n}=e;if(t.jsonSchema202012){const e=(0,s.makeIsExpandable)(t.jsonSchema202012.isExpandable,n);o()(this.fn.jsonSchema202012,{isExpandable:e,getProperties:s.getProperties})}if("function"==typeof t.sampleFromSchema&&t.jsonSchema202012){const e=(0,i.wrapOAS31Fn)({sampleFromSchema:t.jsonSchema202012.sampleFromSchema,sampleFromSchemaGeneric:t.jsonSchema202012.sampleFromSchemaGeneric,createXMLExample:t.jsonSchema202012.createXMLExample,memoizedSampleFromSchema:t.jsonSchema202012.memoizedSampleFromSchema,memoizedCreateXMLExample:t.jsonSchema202012.memoizedCreateXMLExample},n());o()(this.fn,e)}}},89503:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=n(90242);const s=e=>{let{getComponent:t,specSelectors:n}=e;const s=n.selectContactNameField(),i=n.selectContactUrl(),a=n.selectContactEmailField(),l=t("Link");return r.createElement("div",{className:"info__contact"},i&&r.createElement("div",null,r.createElement(l,{href:(0,o.Nm)(i),target:"_blank"},s," - Website")),a&&r.createElement(l,{href:(0,o.Nm)(`mailto:${a}`)},i?`Send email to ${s}`:`Contact ${s}`))}},16133:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=n(90242);const s=e=>{let{getComponent:t,specSelectors:n}=e;const s=n.version(),i=n.url(),a=n.basePath(),l=n.host(),c=n.selectInfoSummaryField(),u=n.selectInfoDescriptionField(),p=n.selectInfoTitleField(),h=n.selectInfoTermsOfServiceUrl(),f=n.selectExternalDocsUrl(),d=n.selectExternalDocsDescriptionField(),m=n.contact(),g=n.license(),y=t("Markdown",!0),v=t("Link"),b=t("VersionStamp"),w=t("InfoUrl"),E=t("InfoBasePath"),x=t("License",!0),S=t("Contact",!0),_=t("JsonSchemaDialect",!0);return r.createElement("div",{className:"info"},r.createElement("hgroup",{className:"main"},r.createElement("h2",{className:"title"},p,s&&r.createElement(b,{version:s})),(l||a)&&r.createElement(E,{host:l,basePath:a}),i&&r.createElement(w,{getComponent:t,url:i})),c&&r.createElement("p",{className:"info__summary"},c),r.createElement("div",{className:"info__description description"},r.createElement(y,{source:u})),h&&r.createElement("div",{className:"info__tos"},r.createElement(v,{target:"_blank",href:(0,o.Nm)(h)},"Terms of service")),m.size>0&&r.createElement(S,null),g.size>0&&r.createElement(x,null),f&&r.createElement(v,{className:"info__extdocs",target:"_blank",href:(0,o.Nm)(f)},d||f),r.createElement(_,null))}},92562:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=n(90242);const s=e=>{let{getComponent:t,specSelectors:n}=e;const s=n.selectJsonSchemaDialectField(),i=n.selectJsonSchemaDialectDefault(),a=t("Link");return r.createElement(r.Fragment,null,s&&s===i&&r.createElement("p",{className:"info__jsonschemadialect"},"JSON Schema dialect:"," ",r.createElement(a,{target:"_blank",href:(0,o.Nm)(s)},s)),s&&s!==i&&r.createElement("div",{className:"error-wrapper"},r.createElement("div",{className:"no-margin"},r.createElement("div",{className:"errors"},r.createElement("div",{className:"errors-wrapper"},r.createElement("h4",{className:"center"},"Warning"),r.createElement("p",{className:"message"},r.createElement("strong",null,"OpenAPI.jsonSchemaDialect")," field contains a value different from the default value of"," ",r.createElement(a,{target:"_blank",href:i},i),". Values different from the default one are currently not supported. Please either omit the field or provide it with the default value."))))))}},51876:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=n(90242);const s=e=>{let{getComponent:t,specSelectors:n}=e;const s=n.selectLicenseNameField(),i=n.selectLicenseUrl(),a=t("Link");return r.createElement("div",{className:"info__license"},i?r.createElement("div",{className:"info__license__url"},r.createElement(a,{target:"_blank",href:(0,o.Nm)(i)},s)):r.createElement("span",null,s))}},92718:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>l});var r=n(58118),o=n.n(r),s=n(67294);n(23930);const i=e=>"string"==typeof e&&o()(e).call(e,"#/components/schemas/")?(e=>{const t=e.replace(/~1/g,"/").replace(/~0/g,"~");try{return decodeURIComponent(t)}catch{return t}})(e.replace(/^.*#\/components\/schemas\//,"")):null,a=(0,s.forwardRef)(((e,t)=>{let{schema:n,getComponent:r,onToggle:o}=e;const a=r("JSONSchema202012"),l=i(n.get("$$ref")),c=(0,s.useCallback)(((e,t)=>{o(l,t)}),[l,o]);return s.createElement(a,{name:l,schema:n.toJS(),ref:t,onExpand:c})}));a.defaultProps={name:"",displayName:"",isRef:!1,required:!1,expandDepth:0,depth:1,includeReadOnly:!1,includeWriteOnly:!1,onToggle:()=>{}};const l=a},20263:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>h});var r=n(28222),o=n.n(r),s=n(97606),i=n.n(s),a=n(2018),l=n.n(a),c=n(67294),u=n(94184),p=n.n(u);const h=e=>{var t;let{specActions:n,specSelectors:r,layoutSelectors:s,layoutActions:a,getComponent:u,getConfigs:h}=e;const f=r.selectSchemas(),d=o()(f).length>0,m=["components","schemas"],{docExpansion:g,defaultModelsExpandDepth:y}=h(),v=y>0&&"none"!==g,b=s.isShown(m,v),w=u("Collapse"),E=u("JSONSchema202012");(0,c.useEffect)((()=>{const e=b&&y>1,t=null!=r.specResolvedSubtree(m);e&&!t&&n.requestResolvedSubtree(m)}),[b,y]);const x=(0,c.useCallback)((()=>{a.show(m,!b)}),[b]),S=(0,c.useCallback)((e=>{null!==e&&a.readyToScroll(m,e)}),[]),_=e=>t=>{null!==t&&a.readyToScroll([...m,e],t)},j=e=>(t,o)=>{if(o){const t=[...m,e];null!=r.specResolvedSubtree(t)||n.requestResolvedSubtree([...m,e])}};return!d||y<0?null:c.createElement("section",{className:p()("models",{"is-open":b}),ref:S},c.createElement("h4",null,c.createElement("button",{"aria-expanded":b,className:"models-control",onClick:x},c.createElement("span",null,"Schemas"),c.createElement("svg",{width:"20",height:"20","aria-hidden":"true",focusable:"false"},c.createElement("use",{xlinkHref:b?"#large-arrow-up":"#large-arrow-down"})))),c.createElement(w,{isOpened:b},i()(t=l()(f)).call(t,(e=>{let[t,n]=e;return c.createElement(E,{key:t,ref:_(t),schema:n,name:t,onExpand:j(t)})}))))}},33429:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=e=>{let{bypass:t,isSwagger2:n,isOAS3:o,isOAS31:s,alsoShow:i,children:a}=e;return t?r.createElement("div",null,a):n&&(o||s)?r.createElement("div",{className:"version-pragma"},i,r.createElement("div",{className:"version-pragma__message version-pragma__message--ambiguous"},r.createElement("div",null,r.createElement("h3",null,"Unable to render this definition"),r.createElement("p",null,r.createElement("code",null,"swagger")," and ",r.createElement("code",null,"openapi")," fields cannot be present in the same Swagger or OpenAPI definition. Please remove one of the fields."),r.createElement("p",null,"Supported version fields are ",r.createElement("code",null,'swagger: "2.0"')," and those that match ",r.createElement("code",null,"openapi: 3.x.y")," (for example,"," ",r.createElement("code",null,"openapi: 3.1.0"),").")))):n||o||s?r.createElement("div",null,a):r.createElement("div",{className:"version-pragma"},i,r.createElement("div",{className:"version-pragma__message version-pragma__message--missing"},r.createElement("div",null,r.createElement("h3",null,"Unable to render this definition"),r.createElement("p",null,"The provided definition does not specify a valid version field."),r.createElement("p",null,"Please indicate a valid Swagger or OpenAPI version field. Supported version fields are ",r.createElement("code",null,'swagger: "2.0"')," and those that match ",r.createElement("code",null,"openapi: 3.x.y")," (for example,"," ",r.createElement("code",null,"openapi: 3.1.0"),")."))))}},39508:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>l});var r=n(28222),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294);const l=e=>{let{specSelectors:t,getComponent:n}=e;const r=t.selectWebhooksOperations(),s=o()(r),l=n("OperationContainer",!0);return 0===s.length?null:a.createElement("div",{className:"webhooks"},a.createElement("h2",null,"Webhooks"),i()(s).call(s,(e=>{var t;return a.createElement("div",{key:`${e}-webhook`},i()(t=r[e]).call(t,(t=>a.createElement(l,{key:`${e}-${t.method}-webhook`,op:t.operation,tag:"webhooks",method:t.method,path:e,specPath:t.specPath,allowTryItOut:!1}))))})))}},84380:(e,t,n)=>{"use strict";n.r(t),n.d(t,{createOnlyOAS31ComponentWrapper:()=>g,createOnlyOAS31Selector:()=>f,createOnlyOAS31SelectorWrapper:()=>d,createSystemSelector:()=>m,isOAS31:()=>h,wrapOAS31Fn:()=>y});var r=n(23101),o=n.n(r),s=n(82865),i=n.n(s),a=n(97606),l=n.n(a),c=n(2018),u=n.n(c),p=n(67294);const h=e=>{const t=e.get("openapi");return"string"==typeof t&&/^3\.1\.(?:[1-9]\d*|0)$/.test(t)},f=e=>function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o{if(n.getSystem().specSelectors.isOAS31()){const o=e(t,...r);return"function"==typeof o?o(n):o}return null}},d=e=>(t,n)=>function(r){for(var o=arguments.length,s=new Array(o>1?o-1:0),i=1;ifunction(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o{const o=e(t,n,...r);return"function"==typeof o?o(n):o}},g=e=>(t,n)=>r=>n.specSelectors.isOAS31()?p.createElement(e,o()({},r,{originalComponent:t,getSystem:n.getSystem})):p.createElement(t,r),y=(e,t)=>{var n;const{fn:r,specSelectors:o}=t;return i()(l()(n=u()(e)).call(n,(e=>{let[t,n]=e;const s=r[t];return[t,function(){return o.isOAS31()?n(...arguments):"function"==typeof s?s(...arguments):void 0}]})))}},29806:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>P});var r=n(39508),o=n(51876),s=n(89503),i=n(16133),a=n(92562),l=n(33429),c=n(92718),u=n(20263),p=n(6608),h=n(77423),f=n(284),d=n(17042),m=n(22914),g=n(41434),y=n(1122),v=n(84380),b=n(9305),w=n(32884),E=n(64280),x=n(59450),S=n(36617),_=n(19525),j=n(25324),O=n(80809),k=n(14951),A=n(77536),C=n(92372);const P=e=>{let{fn:t}=e;const n=t.createSystemSelector||v.createSystemSelector,P=t.createOnlyOAS31Selector||v.createOnlyOAS31Selector;return{afterLoad:C.default,fn:{isOAS31:v.isOAS31,createSystemSelector:v.createSystemSelector,createOnlyOAS31Selector:v.createOnlyOAS31Selector},components:{Webhooks:r.default,JsonSchemaDialect:a.default,OAS31Info:i.default,OAS31License:o.default,OAS31Contact:s.default,OAS31VersionPragmaFilter:l.default,OAS31Model:c.default,OAS31Models:u.default,JSONSchema202012KeywordExample:x.default,JSONSchema202012KeywordXml:S.default,JSONSchema202012KeywordDiscriminator:_.default,JSONSchema202012KeywordExternalDocs:j.default},wrapComponents:{InfoContainer:f.default,License:p.default,Contact:h.default,VersionPragmaFilter:g.default,VersionStamp:y.default,Model:d.default,Models:m.default,JSONSchema202012KeywordDescription:O.default,JSONSchema202012KeywordDefault:k.default,JSONSchema202012KeywordProperties:A.default},statePlugins:{spec:{selectors:{isOAS31:n(b.isOAS31),license:b.license,selectLicenseNameField:b.selectLicenseNameField,selectLicenseUrlField:b.selectLicenseUrlField,selectLicenseIdentifierField:P(b.selectLicenseIdentifierField),selectLicenseUrl:n(b.selectLicenseUrl),contact:b.contact,selectContactNameField:b.selectContactNameField,selectContactEmailField:b.selectContactEmailField,selectContactUrlField:b.selectContactUrlField,selectContactUrl:n(b.selectContactUrl),selectInfoTitleField:b.selectInfoTitleField,selectInfoSummaryField:P(b.selectInfoSummaryField),selectInfoDescriptionField:b.selectInfoDescriptionField,selectInfoTermsOfServiceField:b.selectInfoTermsOfServiceField,selectInfoTermsOfServiceUrl:n(b.selectInfoTermsOfServiceUrl),selectExternalDocsDescriptionField:b.selectExternalDocsDescriptionField,selectExternalDocsUrlField:b.selectExternalDocsUrlField,selectExternalDocsUrl:n(b.selectExternalDocsUrl),webhooks:P(b.webhooks),selectWebhooksOperations:P(n(b.selectWebhooksOperations)),selectJsonSchemaDialectField:b.selectJsonSchemaDialectField,selectJsonSchemaDialectDefault:b.selectJsonSchemaDialectDefault,selectSchemas:n(b.selectSchemas)},wrapSelectors:{isOAS3:w.isOAS3,selectLicenseUrl:w.selectLicenseUrl}},oas31:{selectors:{selectLicenseUrl:P(n(E.selectLicenseUrl))}}}}}},45989:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=e=>{let{schema:t,getSystem:n}=e;if(null==t||!t.description)return null;const{getComponent:o}=n(),s=o("Markdown");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--description"},r.createElement("div",{className:"json-schema-2020-12-core-keyword__value json-schema-2020-12-core-keyword__value--secondary"},r.createElement(s,{source:t.description})))}},19525:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(28222),o=n.n(r),s=n(67294),i=n(94184),a=n.n(i),l=n(7749);const c=e=>{let{schema:t,getSystem:n}=e;const r=(null==t?void 0:t.discriminator)||{},{fn:i,getComponent:c}=n(),{useIsExpandedDeeply:u,useComponent:p}=i.jsonSchema202012,h=u(),f=!!r.mapping,[d,m]=(0,s.useState)(h),[g,y]=(0,s.useState)(!1),v=p("Accordion"),b=p("ExpandDeepButton"),w=c("JSONSchema202012DeepExpansionContext")(),E=(0,s.useCallback)((()=>{m((e=>!e))}),[]),x=(0,s.useCallback)(((e,t)=>{m(t),y(t)}),[]);return 0===o()(r).length?null:s.createElement(w.Provider,{value:g},s.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--discriminator"},f?s.createElement(s.Fragment,null,s.createElement(v,{expanded:d,onChange:E},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"Discriminator")),s.createElement(b,{expanded:d,onClick:x})):s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"Discriminator"),r.propertyName&&s.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},r.propertyName),s.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),s.createElement("ul",{className:a()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!d})},d&&s.createElement("li",{className:"json-schema-2020-12-property"},s.createElement(l.default,{discriminator:r})))))}},7749:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(28222),o=n.n(r),s=n(97606),i=n.n(s),a=n(2018),l=n.n(a),c=n(67294);const u=e=>{var t;let{discriminator:n}=e;const r=(null==n?void 0:n.mapping)||{};return 0===o()(r).length?null:i()(t=l()(r)).call(t,(e=>{let[t,n]=e;return c.createElement("div",{key:`${t}-${n}`,className:"json-schema-2020-12-keyword"},c.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},t),c.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},n))}))};u.defaultProps={mapping:void 0};const p=u},59450:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=e=>{let{schema:t,getSystem:n}=e;const{fn:o}=n(),{hasKeyword:s,stringify:i}=o.jsonSchema202012.useFn();return s(t,"example")?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--example"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"Example"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const"},i(t.example))):null}},25324:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(28222),o=n.n(r),s=n(67294),i=n(94184),a=n.n(i),l=n(90242);const c=e=>{let{schema:t,getSystem:n}=e;const r=(null==t?void 0:t.externalDocs)||{},{fn:i,getComponent:c}=n(),{useIsExpandedDeeply:u,useComponent:p}=i.jsonSchema202012,h=u(),f=!(!r.description&&!r.url),[d,m]=(0,s.useState)(h),[g,y]=(0,s.useState)(!1),v=p("Accordion"),b=p("ExpandDeepButton"),w=c("JSONSchema202012KeywordDescription"),E=c("Link"),x=c("JSONSchema202012DeepExpansionContext")(),S=(0,s.useCallback)((()=>{m((e=>!e))}),[]),_=(0,s.useCallback)(((e,t)=>{m(t),y(t)}),[]);return 0===o()(r).length?null:s.createElement(x.Provider,{value:g},s.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--externalDocs"},f?s.createElement(s.Fragment,null,s.createElement(v,{expanded:d,onChange:S},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"External documentation")),s.createElement(b,{expanded:d,onClick:_})):s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"External documentation"),s.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),s.createElement("ul",{className:a()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!d})},d&&s.createElement(s.Fragment,null,r.description&&s.createElement("li",{className:"json-schema-2020-12-property"},s.createElement(w,{schema:r,getSystem:n})),r.url&&s.createElement("li",{className:"json-schema-2020-12-property"},s.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword"},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"url"),s.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},s.createElement(E,{target:"_blank",href:(0,l.Nm)(r.url)},r.url))))))))}},9023:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>g});var r=n(58309),o=n.n(r),s=n(28222),i=n.n(s),a=n(97606),l=n.n(a),c=n(2018),u=n.n(c),p=n(58118),h=n.n(p),f=n(67294),d=n(94184),m=n.n(d);const g=e=>{var t;let{schema:n,getSystem:r}=e;const{fn:s}=r(),{useComponent:a}=s.jsonSchema202012,{getDependentRequired:c,getProperties:p}=s.jsonSchema202012.useFn(),d=s.jsonSchema202012.useConfig(),g=o()(null==n?void 0:n.required)?n.required:[],y=a("JSONSchema"),v=p(n,d);return 0===i()(v).length?null:f.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--properties"},f.createElement("ul",null,l()(t=u()(v)).call(t,(e=>{let[t,r]=e;const o=h()(g).call(g,t),s=c(t,n);return f.createElement("li",{key:t,className:m()("json-schema-2020-12-property",{"json-schema-2020-12-property--required":o})},f.createElement(y,{name:t,schema:r,dependentRequired:s}))}))))}},36617:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>l});var r=n(28222),o=n.n(r),s=n(67294),i=n(94184),a=n.n(i);const l=e=>{let{schema:t,getSystem:n}=e;const r=(null==t?void 0:t.xml)||{},{fn:i,getComponent:l}=n(),{useIsExpandedDeeply:c,useComponent:u}=i.jsonSchema202012,p=c(),h=!!(r.name||r.namespace||r.prefix),[f,d]=(0,s.useState)(p),[m,g]=(0,s.useState)(!1),y=u("Accordion"),v=u("ExpandDeepButton"),b=l("JSONSchema202012DeepExpansionContext")(),w=(0,s.useCallback)((()=>{d((e=>!e))}),[]),E=(0,s.useCallback)(((e,t)=>{d(t),g(t)}),[]);return 0===o()(r).length?null:s.createElement(b.Provider,{value:m},s.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--xml"},h?s.createElement(s.Fragment,null,s.createElement(y,{expanded:f,onChange:w},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"XML")),s.createElement(v,{expanded:f,onClick:E})):s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"XML"),!0===r.attribute&&s.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},"attribute"),!0===r.wrapped&&s.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},"wrapped"),s.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),s.createElement("ul",{className:a()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!f})},f&&s.createElement(s.Fragment,null,r.name&&s.createElement("li",{className:"json-schema-2020-12-property"},s.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword"},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"name"),s.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},r.name))),r.namespace&&s.createElement("li",{className:"json-schema-2020-12-property"},s.createElement("div",{className:"json-schema-2020-12-keyword"},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"namespace"),s.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},r.namespace))),r.prefix&&s.createElement("li",{className:"json-schema-2020-12-property"},s.createElement("div",{className:"json-schema-2020-12-keyword"},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"prefix"),s.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},r.prefix)))))))}},25800:(e,t,n)=>{"use strict";n.r(t),n.d(t,{getProperties:()=>u,makeIsExpandable:()=>c});var r=n(2018),o=n.n(r),s=n(14418),i=n.n(s),a=n(82865),l=n.n(a);const c=(e,t)=>{const{fn:n}=t();if("function"!=typeof e)return null;const{hasKeyword:r}=n.jsonSchema202012;return t=>e(t)||r(t,"example")||(null==t?void 0:t.xml)||(null==t?void 0:t.discriminator)||(null==t?void 0:t.externalDocs)},u=(e,t)=>{let{includeReadOnly:n,includeWriteOnly:r}=t;if(null==e||!e.properties)return{};const s=o()(e.properties),a=i()(s).call(s,(e=>{let[,t]=e;const o=!0===(null==t?void 0:t.readOnly),s=!0===(null==t?void 0:t.writeOnly);return(!o||n)&&(!s||r)}));return l()(a)}},14951:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)((e=>{let{schema:t,getSystem:n,originalComponent:o}=e;const{getComponent:s}=n(),i=s("JSONSchema202012KeywordDiscriminator"),a=s("JSONSchema202012KeywordXml"),l=s("JSONSchema202012KeywordExample"),c=s("JSONSchema202012KeywordExternalDocs");return r.createElement(r.Fragment,null,r.createElement(o,{schema:t}),r.createElement(i,{schema:t,getSystem:n}),r.createElement(a,{schema:t,getSystem:n}),r.createElement(c,{schema:t,getSystem:n}),r.createElement(l,{schema:t,getSystem:n}))}))},80809:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(45989);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)(r.default)},77536:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(9023);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)(r.default)},64280:(e,t,n)=>{"use strict";n.r(t),n.d(t,{selectLicenseUrl:()=>s});var r=n(20573),o=n(63543);const s=(0,r.P1)(((e,t)=>t.specSelectors.url()),((e,t)=>t.oas3Selectors.selectedServer()),((e,t)=>t.specSelectors.selectLicenseUrlField()),((e,t)=>t.specSelectors.selectLicenseIdentifierField()),((e,t,n,r)=>n?(0,o.mn)(n,e,{selectedServer:t}):r?`https://spdx.org/licenses/${r}.html`:void 0))},9305:(e,t,n)=>{"use strict";n.r(t),n.d(t,{contact:()=>A,isOAS31:()=>w,license:()=>S,selectContactEmailField:()=>P,selectContactNameField:()=>C,selectContactUrl:()=>I,selectContactUrlField:()=>N,selectExternalDocsDescriptionField:()=>L,selectExternalDocsUrl:()=>$,selectExternalDocsUrlField:()=>B,selectInfoDescriptionField:()=>M,selectInfoSummaryField:()=>R,selectInfoTermsOfServiceField:()=>D,selectInfoTermsOfServiceUrl:()=>F,selectInfoTitleField:()=>T,selectJsonSchemaDialectDefault:()=>U,selectJsonSchemaDialectField:()=>q,selectLicenseIdentifierField:()=>k,selectLicenseNameField:()=>_,selectLicenseUrl:()=>O,selectLicenseUrlField:()=>j,selectSchemas:()=>z,selectWebhooksOperations:()=>x,webhooks:()=>E});var r=n(97606),o=n.n(r),s=n(24282),i=n.n(s),a=n(14418),l=n.n(a),c=n(58118),u=n.n(c),p=n(39022),h=n.n(p),f=n(2018),d=n.n(f),m=n(43393),g=n(20573),y=n(63543),v=n(84380);const b=(0,m.Map)(),w=(0,g.P1)(((e,t)=>t.specSelectors.specJson()),v.isOAS31),E=()=>e=>e.specSelectors.specJson().get("webhooks",b),x=(0,g.P1)(((e,t)=>t.specSelectors.webhooks()),((e,t)=>t.specSelectors.validOperationMethods()),((e,t)=>t.specSelectors.specResolvedSubtree(["webhooks"])),((e,t)=>{var n;return m.Map.isMap(e)?o()(n=i()(e).call(e,((e,n,r)=>{var s,i;if(!m.Map.isMap(n))return e;const a=o()(s=l()(i=n.entrySeq()).call(i,(e=>{let[n]=e;return u()(t).call(t,n)}))).call(s,(e=>{let[t,n]=e;return{operation:(0,m.Map)({operation:n}),method:t,path:r,specPath:(0,m.List)(["webhooks",r,t])}}));return h()(e).call(e,a)}),(0,m.List)()).groupBy((e=>e.path))).call(n,(e=>e.toArray())).toObject():{}})),S=()=>e=>e.specSelectors.info().get("license",b),_=()=>e=>e.specSelectors.license().get("name","License"),j=()=>e=>e.specSelectors.license().get("url"),O=(0,g.P1)(((e,t)=>t.specSelectors.url()),((e,t)=>t.oas3Selectors.selectedServer()),((e,t)=>t.specSelectors.selectLicenseUrlField()),((e,t,n)=>{if(n)return(0,y.mn)(n,e,{selectedServer:t})})),k=()=>e=>e.specSelectors.license().get("identifier"),A=()=>e=>e.specSelectors.info().get("contact",b),C=()=>e=>e.specSelectors.contact().get("name","the developer"),P=()=>e=>e.specSelectors.contact().get("email"),N=()=>e=>e.specSelectors.contact().get("url"),I=(0,g.P1)(((e,t)=>t.specSelectors.url()),((e,t)=>t.oas3Selectors.selectedServer()),((e,t)=>t.specSelectors.selectContactUrlField()),((e,t,n)=>{if(n)return(0,y.mn)(n,e,{selectedServer:t})})),T=()=>e=>e.specSelectors.info().get("title"),R=()=>e=>e.specSelectors.info().get("summary"),M=()=>e=>e.specSelectors.info().get("description"),D=()=>e=>e.specSelectors.info().get("termsOfService"),F=(0,g.P1)(((e,t)=>t.specSelectors.url()),((e,t)=>t.oas3Selectors.selectedServer()),((e,t)=>t.specSelectors.selectInfoTermsOfServiceField()),((e,t,n)=>{if(n)return(0,y.mn)(n,e,{selectedServer:t})})),L=()=>e=>e.specSelectors.externalDocs().get("description"),B=()=>e=>e.specSelectors.externalDocs().get("url"),$=(0,g.P1)(((e,t)=>t.specSelectors.url()),((e,t)=>t.oas3Selectors.selectedServer()),((e,t)=>t.specSelectors.selectExternalDocsUrlField()),((e,t,n)=>{if(n)return(0,y.mn)(n,e,{selectedServer:t})})),q=()=>e=>e.specSelectors.specJson().get("jsonSchemaDialect"),U=()=>"https://spec.openapis.org/oas/3.1/dialect/base",z=(0,g.P1)(((e,t)=>t.specSelectors.definitions()),((e,t)=>t.specSelectors.specResolvedSubtree(["components","schemas"])),((e,t)=>{var n;return m.Map.isMap(e)?m.Map.isMap(t)?i()(n=d()(e.toJS())).call(n,((e,n)=>{let[r,o]=n;const s=t.get(r);return e[r]=(null==s?void 0:s.toJS())||o,e}),{}):e.toJS():{}}))},32884:(e,t,n)=>{"use strict";n.r(t),n.d(t,{isOAS3:()=>o,selectLicenseUrl:()=>s});var r=n(84380);const o=(e,t)=>function(n){const r=t.specSelectors.isOAS31();for(var o=arguments.length,s=new Array(o>1?o-1:0),i=1;i(e,t)=>t.oas31Selectors.selectLicenseUrl()))},77423:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)((e=>{let{getSystem:t}=e;const n=t().getComponent("OAS31Contact",!0);return r.createElement(n,null)}))},284:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)((e=>{let{getSystem:t}=e;const n=t().getComponent("OAS31Info",!0);return r.createElement(n,null)}))},6608:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)((e=>{let{getSystem:t}=e;const n=t().getComponent("OAS31License",!0);return r.createElement(n,null)}))},17042:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(67294),o=n(84380),s=n(25800);const i=(0,o.createOnlyOAS31ComponentWrapper)((e=>{let{getSystem:t,...n}=e;const o=t(),{getComponent:i,fn:a,getConfigs:l}=o,c=l(),u=i("OAS31Model"),p=i("JSONSchema202012"),h=i("JSONSchema202012Keyword$schema"),f=i("JSONSchema202012Keyword$vocabulary"),d=i("JSONSchema202012Keyword$id"),m=i("JSONSchema202012Keyword$anchor"),g=i("JSONSchema202012Keyword$dynamicAnchor"),y=i("JSONSchema202012Keyword$ref"),v=i("JSONSchema202012Keyword$dynamicRef"),b=i("JSONSchema202012Keyword$defs"),w=i("JSONSchema202012Keyword$comment"),E=i("JSONSchema202012KeywordAllOf"),x=i("JSONSchema202012KeywordAnyOf"),S=i("JSONSchema202012KeywordOneOf"),_=i("JSONSchema202012KeywordNot"),j=i("JSONSchema202012KeywordIf"),O=i("JSONSchema202012KeywordThen"),k=i("JSONSchema202012KeywordElse"),A=i("JSONSchema202012KeywordDependentSchemas"),C=i("JSONSchema202012KeywordPrefixItems"),P=i("JSONSchema202012KeywordItems"),N=i("JSONSchema202012KeywordContains"),I=i("JSONSchema202012KeywordProperties"),T=i("JSONSchema202012KeywordPatternProperties"),R=i("JSONSchema202012KeywordAdditionalProperties"),M=i("JSONSchema202012KeywordPropertyNames"),D=i("JSONSchema202012KeywordUnevaluatedItems"),F=i("JSONSchema202012KeywordUnevaluatedProperties"),L=i("JSONSchema202012KeywordType"),B=i("JSONSchema202012KeywordEnum"),$=i("JSONSchema202012KeywordConst"),q=i("JSONSchema202012KeywordConstraint"),U=i("JSONSchema202012KeywordDependentRequired"),z=i("JSONSchema202012KeywordContentSchema"),V=i("JSONSchema202012KeywordTitle"),W=i("JSONSchema202012KeywordDescription"),J=i("JSONSchema202012KeywordDefault"),K=i("JSONSchema202012KeywordDeprecated"),H=i("JSONSchema202012KeywordReadOnly"),G=i("JSONSchema202012KeywordWriteOnly"),Z=i("JSONSchema202012Accordion"),Y=i("JSONSchema202012ExpandDeepButton"),X=i("JSONSchema202012ChevronRightIcon"),Q=i("withJSONSchema202012Context")(u,{config:{default$schema:"https://spec.openapis.org/oas/3.1/dialect/base",defaultExpandedLevels:c.defaultModelExpandDepth,includeReadOnly:Boolean(n.includeReadOnly),includeWriteOnly:Boolean(n.includeWriteOnly)},components:{JSONSchema:p,Keyword$schema:h,Keyword$vocabulary:f,Keyword$id:d,Keyword$anchor:m,Keyword$dynamicAnchor:g,Keyword$ref:y,Keyword$dynamicRef:v,Keyword$defs:b,Keyword$comment:w,KeywordAllOf:E,KeywordAnyOf:x,KeywordOneOf:S,KeywordNot:_,KeywordIf:j,KeywordThen:O,KeywordElse:k,KeywordDependentSchemas:A,KeywordPrefixItems:C,KeywordItems:P,KeywordContains:N,KeywordProperties:I,KeywordPatternProperties:T,KeywordAdditionalProperties:R,KeywordPropertyNames:M,KeywordUnevaluatedItems:D,KeywordUnevaluatedProperties:F,KeywordType:L,KeywordEnum:B,KeywordConst:$,KeywordConstraint:q,KeywordDependentRequired:U,KeywordContentSchema:z,KeywordTitle:V,KeywordDescription:W,KeywordDefault:J,KeywordDeprecated:K,KeywordReadOnly:H,KeywordWriteOnly:G,Accordion:Z,ExpandDeepButton:Y,ChevronRightIcon:X},fn:{upperFirst:a.upperFirst,isExpandable:(0,s.makeIsExpandable)(a.jsonSchema202012.isExpandable,t),getProperties:s.getProperties}});return r.createElement(Q,n)}))},22914:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)((e=>{let{getSystem:t}=e;const{getComponent:n,fn:s,getConfigs:i}=t(),a=i();if(o.ModelsWithJSONSchemaContext)return r.createElement(o.ModelsWithJSONSchemaContext,null);const l=n("OAS31Models",!0),c=n("JSONSchema202012"),u=n("JSONSchema202012Keyword$schema"),p=n("JSONSchema202012Keyword$vocabulary"),h=n("JSONSchema202012Keyword$id"),f=n("JSONSchema202012Keyword$anchor"),d=n("JSONSchema202012Keyword$dynamicAnchor"),m=n("JSONSchema202012Keyword$ref"),g=n("JSONSchema202012Keyword$dynamicRef"),y=n("JSONSchema202012Keyword$defs"),v=n("JSONSchema202012Keyword$comment"),b=n("JSONSchema202012KeywordAllOf"),w=n("JSONSchema202012KeywordAnyOf"),E=n("JSONSchema202012KeywordOneOf"),x=n("JSONSchema202012KeywordNot"),S=n("JSONSchema202012KeywordIf"),_=n("JSONSchema202012KeywordThen"),j=n("JSONSchema202012KeywordElse"),O=n("JSONSchema202012KeywordDependentSchemas"),k=n("JSONSchema202012KeywordPrefixItems"),A=n("JSONSchema202012KeywordItems"),C=n("JSONSchema202012KeywordContains"),P=n("JSONSchema202012KeywordProperties"),N=n("JSONSchema202012KeywordPatternProperties"),I=n("JSONSchema202012KeywordAdditionalProperties"),T=n("JSONSchema202012KeywordPropertyNames"),R=n("JSONSchema202012KeywordUnevaluatedItems"),M=n("JSONSchema202012KeywordUnevaluatedProperties"),D=n("JSONSchema202012KeywordType"),F=n("JSONSchema202012KeywordEnum"),L=n("JSONSchema202012KeywordConst"),B=n("JSONSchema202012KeywordConstraint"),$=n("JSONSchema202012KeywordDependentRequired"),q=n("JSONSchema202012KeywordContentSchema"),U=n("JSONSchema202012KeywordTitle"),z=n("JSONSchema202012KeywordDescription"),V=n("JSONSchema202012KeywordDefault"),W=n("JSONSchema202012KeywordDeprecated"),J=n("JSONSchema202012KeywordReadOnly"),K=n("JSONSchema202012KeywordWriteOnly"),H=n("JSONSchema202012Accordion"),G=n("JSONSchema202012ExpandDeepButton"),Z=n("JSONSchema202012ChevronRightIcon"),Y=n("withJSONSchema202012Context");return o.ModelsWithJSONSchemaContext=Y(l,{config:{default$schema:"https://spec.openapis.org/oas/3.1/dialect/base",defaultExpandedLevels:a.defaultModelsExpandDepth-1,includeReadOnly:!0,includeWriteOnly:!0},components:{JSONSchema:c,Keyword$schema:u,Keyword$vocabulary:p,Keyword$id:h,Keyword$anchor:f,Keyword$dynamicAnchor:d,Keyword$ref:m,Keyword$dynamicRef:g,Keyword$defs:y,Keyword$comment:v,KeywordAllOf:b,KeywordAnyOf:w,KeywordOneOf:E,KeywordNot:x,KeywordIf:S,KeywordThen:_,KeywordElse:j,KeywordDependentSchemas:O,KeywordPrefixItems:k,KeywordItems:A,KeywordContains:C,KeywordProperties:P,KeywordPatternProperties:N,KeywordAdditionalProperties:I,KeywordPropertyNames:T,KeywordUnevaluatedItems:R,KeywordUnevaluatedProperties:M,KeywordType:D,KeywordEnum:F,KeywordConst:L,KeywordConstraint:B,KeywordDependentRequired:$,KeywordContentSchema:q,KeywordTitle:U,KeywordDescription:z,KeywordDefault:V,KeywordDeprecated:W,KeywordReadOnly:J,KeywordWriteOnly:K,Accordion:H,ExpandDeepButton:G,ChevronRightIcon:Z},fn:{upperFirst:s.upperFirst,isExpandable:s.jsonSchema202012.isExpandable,getProperties:s.jsonSchema202012.getProperties}}),r.createElement(o.ModelsWithJSONSchemaContext,null)}));o.ModelsWithJSONSchemaContext=null;const s=o},41434:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(23101),o=n.n(r),s=n(67294);const i=(e,t)=>e=>{const n=t.specSelectors.isOAS31(),r=t.getComponent("OAS31VersionPragmaFilter");return s.createElement(r,o()({isOAS31:n},e))}},1122:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)((e=>{let{originalComponent:t,...n}=e;return r.createElement("span",null,r.createElement(t,n),r.createElement("small",{className:"version-stamp"},r.createElement("pre",{className:"version"},"OAS 3.1")))}))},28560:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(87198),o=n.n(r);let s=!1;function i(){return{statePlugins:{spec:{wrapActions:{updateSpec:e=>function(){return s=!0,e(...arguments)},updateJsonSpec:(e,t)=>function(){const n=t.getConfigs().onComplete;return s&&"function"==typeof n&&(o()(n,0),s=!1),e(...arguments)}}}}}}},92135:(e,t,n)=>{"use strict";n.r(t),n.d(t,{requestSnippetGenerator_curl_bash:()=>j,requestSnippetGenerator_curl_cmd:()=>O,requestSnippetGenerator_curl_powershell:()=>_});var r=n(11882),o=n.n(r),s=n(81607),i=n.n(s),a=n(35627),l=n.n(a),c=n(97606),u=n.n(c),p=n(12196),h=n.n(p),f=n(74386),d=n.n(f),m=n(58118),g=n.n(m),y=n(27504),v=n(43393);const b=e=>{var t;const n="_**[]";return o()(e).call(e,n)<0?e:i()(t=e.split(n)[0]).call(t)},w=e=>"-d "===e||/^[_\/-]/g.test(e)?e:"'"+e.replace(/'/g,"'\\''")+"'",E=e=>"-d "===(e=e.replace(/\^/g,"^^").replace(/\\"/g,'\\\\"').replace(/"/g,'""').replace(/\n/g,"^\n"))?e.replace(/-d /g,"-d ^\n"):/^[_\/-]/g.test(e)?e:'"'+e+'"',x=e=>"-d "===e?e:/\n/.test(e)?'@"\n'+e.replace(/"/g,'\\"').replace(/`/g,"``").replace(/\$/,"`$")+'\n"@':/^[_\/-]/g.test(e)?e:"'"+e.replace(/"/g,'""').replace(/'/g,"''")+"'";const S=function(e,t,n){let r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"",o=!1,s="";const i=function(){for(var e=arguments.length,n=new Array(e),r=0;rs+=` ${n}`,p=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;return s+=h()(" ").call(" ",e)};let f=e.get("headers");if(s+="curl"+r,e.has("curlOptions")&&i(...e.get("curlOptions")),i("-X",e.get("method")),c(),p(),a(`${e.get("url")}`),f&&f.size)for(let t of d()(m=e.get("headers")).call(m)){var m;c(),p();let[e,n]=t;a("-H",`${e}: ${n}`),o=o||/^content-type$/i.test(e)&&/^multipart\/form-data$/i.test(n)}const w=e.get("body");var E;if(w)if(o&&g()(E=["POST","PUT","PATCH"]).call(E,e.get("method")))for(let[e,t]of w.entrySeq()){let n=b(e);c(),p(),a("-F"),t instanceof y.Z.File?i(`${n}=@${t.name}${t.type?`;type=${t.type}`:""}`):i(`${n}=${t}`)}else if(w instanceof y.Z.File)c(),p(),a(`--data-binary '@${w.name}'`);else{c(),p(),a("-d ");let t=w;v.Map.isMap(t)?a(function(e){let t=[];for(let[n,r]of e.get("body").entrySeq()){let e=b(n);r instanceof y.Z.File?t.push(` "${e}": {\n "name": "${r.name}"${r.type?`,\n "type": "${r.type}"`:""}\n }`):t.push(` "${e}": ${l()(r,null,2).replace(/(\r\n|\r|\n)/g,"\n ")}`)}return`{\n${t.join(",\n")}\n}`}(e)):("string"!=typeof t&&(t=l()(t)),a(t))}else w||"POST"!==e.get("method")||(c(),p(),a("-d ''"));return s},_=e=>S(e,x,"`\n",".exe"),j=e=>S(e,w,"\\\n"),O=e=>S(e,E,"^\n")},86575:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(92135),o=n(4669),s=n(84206);const i=()=>({components:{RequestSnippets:s.default},fn:r,statePlugins:{requestSnippets:{selectors:o}}})},84206:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>w});var r=n(14418),o=n.n(r),s=n(25110),i=n.n(s),a=n(86),l=n.n(a),c=n(97606),u=n.n(c),p=n(67294),h=n(27361),f=n.n(h),d=n(23560),m=n.n(d),g=n(74855),y=n(33424);const v={cursor:"pointer",lineHeight:1,display:"inline-flex",backgroundColor:"rgb(250, 250, 250)",paddingBottom:"0",paddingTop:"0",border:"1px solid rgb(51, 51, 51)",borderRadius:"4px 4px 0 0",boxShadow:"none",borderBottom:"none"},b={cursor:"pointer",lineHeight:1,display:"inline-flex",backgroundColor:"rgb(51, 51, 51)",boxShadow:"none",border:"1px solid rgb(51, 51, 51)",paddingBottom:"0",paddingTop:"0",borderRadius:"4px 4px 0 0",marginTop:"-5px",marginRight:"-5px",marginLeft:"-5px",zIndex:"9999",borderBottom:"none"},w=e=>{var t,n;let{request:r,requestSnippetsSelectors:s,getConfigs:a}=e;const c=m()(a)?a():null,h=!1!==f()(c,"syntaxHighlight")&&f()(c,"syntaxHighlight.activated",!0),d=(0,p.useRef)(null),[w,E]=(0,p.useState)(null===(t=s.getSnippetGenerators())||void 0===t?void 0:t.keySeq().first()),[x,S]=(0,p.useState)(null==s?void 0:s.getDefaultExpanded());(0,p.useEffect)((()=>{}),[]),(0,p.useEffect)((()=>{var e;const t=o()(e=i()(d.current.childNodes)).call(e,(e=>{var t;return!!e.nodeType&&(null===(t=e.classList)||void 0===t?void 0:t.contains("curl-command"))}));return l()(t).call(t,(e=>e.addEventListener("mousewheel",C,{passive:!1}))),()=>{l()(t).call(t,(e=>e.removeEventListener("mousewheel",C)))}}),[r]);const _=s.getSnippetGenerators(),j=_.get(w),O=j.get("fn")(r),k=()=>{S(!x)},A=e=>e===w?b:v,C=e=>{const{target:t,deltaY:n}=e,{scrollHeight:r,offsetHeight:o,scrollTop:s}=t;r>o&&(0===s&&n<0||o+s>=r&&n>0)&&e.preventDefault()},P=h?p.createElement(y.d3,{language:j.get("syntax"),className:"curl microlight",style:(0,y.C2)(f()(c,"syntaxHighlight.theme"))},O):p.createElement("textarea",{readOnly:!0,className:"curl",value:O});return p.createElement("div",{className:"request-snippets",ref:d},p.createElement("div",{style:{width:"100%",display:"flex",justifyContent:"flex-start",alignItems:"center",marginBottom:"15px"}},p.createElement("h4",{onClick:()=>k(),style:{cursor:"pointer"}},"Snippets"),p.createElement("button",{onClick:()=>k(),style:{border:"none",background:"none"},title:x?"Collapse operation":"Expand operation"},p.createElement("svg",{className:"arrow",width:"10",height:"10"},p.createElement("use",{href:x?"#large-arrow-down":"#large-arrow",xlinkHref:x?"#large-arrow-down":"#large-arrow"})))),x&&p.createElement("div",{className:"curl-command"},p.createElement("div",{style:{paddingLeft:"15px",paddingRight:"10px",width:"100%",display:"flex"}},u()(n=_.entrySeq()).call(n,(e=>{let[t,n]=e;return p.createElement("div",{style:A(t),className:"btn",key:t,onClick:()=>(e=>{w!==e&&E(e)})(t)},p.createElement("h4",{style:t===w?{color:"white"}:{}},n.get("title")))}))),p.createElement("div",{className:"copy-to-clipboard"},p.createElement(g.CopyToClipboard,{text:O},p.createElement("button",null))),p.createElement("div",null,P)))}},4669:(e,t,n)=>{"use strict";n.r(t),n.d(t,{getActiveLanguage:()=>d,getDefaultExpanded:()=>m,getGenerators:()=>h,getSnippetGenerators:()=>f});var r=n(14418),o=n.n(r),s=n(58118),i=n.n(s),a=n(97606),l=n.n(a),c=n(20573),u=n(43393);const p=e=>e||(0,u.Map)(),h=(0,c.P1)(p,(e=>{const t=e.get("languages"),n=e.get("generators",(0,u.Map)());return!t||t.isEmpty()?n:o()(n).call(n,((e,n)=>i()(t).call(t,n)))})),f=e=>t=>{var n,r;let{fn:s}=t;return o()(n=l()(r=h(e)).call(r,((e,t)=>{const n=(e=>s[`requestSnippetGenerator_${e}`])(t);return"function"!=typeof n?null:e.set("fn",n)}))).call(n,(e=>e))},d=(0,c.P1)(p,(e=>e.get("activeLanguage"))),m=(0,c.P1)(p,(e=>e.get("defaultExpanded")))},36195:(e,t,n)=>{"use strict";n.r(t),n.d(t,{ErrorBoundary:()=>i,default:()=>a});var r=n(67294),o=n(56189),s=n(29403);class i extends r.Component{static getDerivedStateFromError(e){return{hasError:!0,error:e}}constructor(){super(...arguments),this.state={hasError:!1,error:null}}componentDidCatch(e,t){this.props.fn.componentDidCatch(e,t)}render(){const{getComponent:e,targetName:t,children:n}=this.props;if(this.state.hasError){const n=e("Fallback");return r.createElement(n,{name:t})}return n}}i.defaultProps={targetName:"this component",getComponent:()=>s.default,fn:{componentDidCatch:o.componentDidCatch},children:null};const a=i},29403:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=e=>{let{name:t}=e;return r.createElement("div",{className:"fallback"},"๐Ÿ˜ฑ ",r.createElement("i",null,"Could not render ","t"===t?"this component":t,", see the console."))}},56189:(e,t,n)=>{"use strict";n.r(t),n.d(t,{componentDidCatch:()=>i,withErrorBoundary:()=>a});var r=n(23101),o=n.n(r),s=n(67294);const i=console.error,a=e=>t=>{const{getComponent:n,fn:r}=e(),i=n("ErrorBoundary"),a=r.getDisplayName(t);class l extends s.Component{render(){return s.createElement(i,{targetName:a,getComponent:n,fn:r},s.createElement(t,o()({},this.props,this.context)))}}var c;return l.displayName=`WithErrorBoundary(${a})`,(c=t).prototype&&c.prototype.isReactComponent&&(l.prototype.mapStateToProps=t.prototype.mapStateToProps),l}},27621:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(47475),o=n.n(r),s=n(7287),i=n.n(s),a=n(36195),l=n(29403),c=n(56189);const u=function(){let{componentList:e=[],fullOverride:t=!1}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return n=>{var r;let{getSystem:s}=n;const u=t?e:["App","BaseLayout","VersionPragmaFilter","InfoContainer","ServersContainer","SchemesContainer","AuthorizeBtnContainer","FilterContainer","Operations","OperationContainer","parameters","responses","OperationServers","Models","ModelWrapper",...e],p=i()(u,o()(r=Array(u.length)).call(r,((e,t)=>{let{fn:n}=t;return n.withErrorBoundary(e)})));return{fn:{componentDidCatch:c.componentDidCatch,withErrorBoundary:(0,c.withErrorBoundary)(s)},components:{ErrorBoundary:a.default,Fallback:l.default},wrapComponents:p}}}},72846:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(24282),o=n.n(r),s=n(35627),i=n.n(s),a=n(59704),l=n.n(a);const c=[{when:/json/,shouldStringifyTypes:["string"]}],u=["object"],p=e=>(t,n,r,s)=>{const{fn:a}=e(),p=a.memoizedSampleFromSchema(t,n,s),h=typeof p,f=o()(c).call(c,((e,t)=>t.when.test(r)?[...e,...t.shouldStringifyTypes]:e),u);return l()(f,(e=>e===h))?i()(p,null,2):p}},16132:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=e=>function(t){var n,r;let o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:void 0;const{fn:a}=e();return"function"==typeof(null===(n=t)||void 0===n?void 0:n.toJS)&&(t=t.toJS()),"function"==typeof(null===(r=i)||void 0===r?void 0:r.toJS)&&(i=i.toJS()),/xml/.test(o)?a.getXmlSampleSchema(t,s,i):/(yaml|yml)/.test(o)?a.getYamlSampleSchema(t,s,o,i):a.getJsonSampleSchema(t,s,o,i)}},81169:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=e=>(t,n,r)=>{const{fn:o}=e();if(t&&!t.xml&&(t.xml={}),t&&!t.xml.name){if(!t.$$ref&&(t.type||t.items||t.properties||t.additionalProperties))return'\n\x3c!-- XML example cannot be generated; root element name is undefined --\x3e';if(t.$$ref){let e=t.$$ref.match(/\S*\/(\S+)$/);t.xml.name=e[1]}}return o.memoizedCreateXMLExample(t,n,r)}},79431:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(24278),o=n.n(r),s=n(1272);const i=e=>(t,n,r,i)=>{const{fn:a}=e(),l=a.getJsonSampleSchema(t,n,r,i);let c;try{c=s.ZP.dump(s.ZP.load(l),{lineWidth:-1},{schema:s.A8}),"\n"===c[c.length-1]&&(c=o()(c).call(c,0,c.length-1))}catch(e){return console.error(e),"error: could not generate yaml example"}return c.replace(/\t/g," ")}},29812:(e,t,n)=>{"use strict";n.r(t),n.d(t,{createXMLExample:()=>q,inferSchema:()=>$,memoizedCreateXMLExample:()=>V,memoizedSampleFromSchema:()=>W,sampleFromSchema:()=>U,sampleFromSchemaGeneric:()=>B});var r=n(11882),o=n.n(r),s=n(86),i=n.n(s),a=n(58309),l=n.n(a),c=n(58118),u=n.n(c),p=n(92039),h=n.n(p),f=n(24278),d=n.n(f),m=n(51679),g=n.n(m),y=n(39022),v=n.n(y),b=n(97606),w=n.n(b),E=n(35627),x=n.n(E),S=n(53479),_=n.n(S),j=n(14419),O=n.n(j),k=n(41609),A=n.n(k),C=n(90242),P=n(60314);const N={string:e=>e.pattern?(e=>{try{return new(O())(e).gen()}catch(e){return"string"}})(e.pattern):"string",string_email:()=>"user@example.com","string_date-time":()=>(new Date).toISOString(),string_date:()=>(new Date).toISOString().substring(0,10),string_uuid:()=>"3fa85f64-5717-4562-b3fc-2c963f66afa6",string_hostname:()=>"example.com",string_ipv4:()=>"198.51.100.42",string_ipv6:()=>"2001:0db8:5b96:0000:0000:426f:8e17:642a",number:()=>0,number_float:()=>0,integer:()=>0,boolean:e=>"boolean"!=typeof e.default||e.default},I=e=>{e=(0,C.mz)(e);let{type:t,format:n}=e,r=N[`${t}_${n}`]||N[t];return(0,C.Wl)(r)?r(e):"Unknown Type: "+e.type},T=e=>(0,C.XV)(e,"$$ref",(e=>"string"==typeof e&&o()(e).call(e,"#")>-1)),R=["maxProperties","minProperties"],M=["minItems","maxItems"],D=["minimum","maximum","exclusiveMinimum","exclusiveMaximum"],F=["minLength","maxLength"],L=function(e,t){var n;let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};var s;(i()(n=["example","default","enum","xml","type",...R,...M,...D,...F]).call(n,(n=>(n=>{void 0===t[n]&&void 0!==e[n]&&(t[n]=e[n])})(n))),void 0!==e.required&&l()(e.required))&&(void 0!==t.required&&t.required.length||(t.required=[]),i()(s=e.required).call(s,(e=>{var n;u()(n=t.required).call(n,e)||t.required.push(e)})));if(e.properties){t.properties||(t.properties={});let n=(0,C.mz)(e.properties);for(let s in n){var a;if(Object.prototype.hasOwnProperty.call(n,s))if(!n[s]||!n[s].deprecated)if(!n[s]||!n[s].readOnly||r.includeReadOnly)if(!n[s]||!n[s].writeOnly||r.includeWriteOnly)if(!t.properties[s])t.properties[s]=n[s],!e.required&&l()(e.required)&&-1!==o()(a=e.required).call(a,s)&&(t.required?t.required.push(s):t.required=[s])}}return e.items&&(t.items||(t.items={}),t.items=L(e.items,t.items,r)),t},B=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0,r=arguments.length>3&&void 0!==arguments[3]&&arguments[3];e&&(0,C.Wl)(e.toJS)&&(e=e.toJS());let s=void 0!==n||e&&void 0!==e.example||e&&void 0!==e.default;const a=!s&&e&&e.oneOf&&e.oneOf.length>0,c=!s&&e&&e.anyOf&&e.anyOf.length>0;if(!s&&(a||c)){const n=(0,C.mz)(a?e.oneOf[0]:e.anyOf[0]);if(L(n,e,t),!e.xml&&n.xml&&(e.xml=n.xml),void 0!==e.example&&void 0!==n.example)s=!0;else if(n.properties){e.properties||(e.properties={});let r=(0,C.mz)(n.properties);for(let s in r){var p;if(Object.prototype.hasOwnProperty.call(r,s))if(!r[s]||!r[s].deprecated)if(!r[s]||!r[s].readOnly||t.includeReadOnly)if(!r[s]||!r[s].writeOnly||t.includeWriteOnly)if(!e.properties[s])e.properties[s]=r[s],!n.required&&l()(n.required)&&-1!==o()(p=n.required).call(p,s)&&(e.required?e.required.push(s):e.required=[s])}}}const f={};let{xml:m,type:y,example:b,properties:E,additionalProperties:x,items:S}=e||{},{includeReadOnly:_,includeWriteOnly:j}=t;m=m||{};let O,{name:k,prefix:P,namespace:N}=m,F={};if(r&&(k=k||"notagname",O=(P?P+":":"")+k,N)){f[P?"xmlns:"+P:"xmlns"]=N}r&&(F[O]=[]);const $=t=>h()(t).call(t,(t=>Object.prototype.hasOwnProperty.call(e,t)));e&&!y&&(E||x||$(R)?y="object":S||$(M)?y="array":$(D)?(y="number",e.type="number"):s||e.enum||(y="string",e.type="string"));const q=t=>{var n,r,o,s,i;null!==(null===(n=e)||void 0===n?void 0:n.maxItems)&&void 0!==(null===(r=e)||void 0===r?void 0:r.maxItems)&&(t=d()(t).call(t,0,null===(i=e)||void 0===i?void 0:i.maxItems));if(null!==(null===(o=e)||void 0===o?void 0:o.minItems)&&void 0!==(null===(s=e)||void 0===s?void 0:s.minItems)){let n=0;for(;t.length<(null===(a=e)||void 0===a?void 0:a.minItems);){var a;t.push(t[n++%t.length])}}return t},U=(0,C.mz)(E);let z,V=0;const W=()=>e&&null!==e.maxProperties&&void 0!==e.maxProperties&&V>=e.maxProperties,J=t=>!e||null===e.maxProperties||void 0===e.maxProperties||!W()&&(!(t=>{var n;return!(e&&e.required&&e.required.length&&u()(n=e.required).call(n,t))})(t)||e.maxProperties-V-(()=>{if(!e||!e.required)return 0;let t=0;var n,o;return r?i()(n=e.required).call(n,(e=>t+=void 0===F[e]?0:1)):i()(o=e.required).call(o,(e=>{var n;return t+=void 0===(null===(n=F[O])||void 0===n?void 0:g()(n).call(n,(t=>void 0!==t[e])))?0:1})),e.required.length-t})()>0);if(z=r?function(n){let o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0;if(e&&U[n]){if(U[n].xml=U[n].xml||{},U[n].xml.attribute){const e=l()(U[n].enum)?U[n].enum[0]:void 0,t=U[n].example,r=U[n].default;return void(f[U[n].xml.name||n]=void 0!==t?t:void 0!==r?r:void 0!==e?e:I(U[n]))}U[n].xml.name=U[n].xml.name||n}else U[n]||!1===x||(U[n]={xml:{name:n}});let s=B(e&&U[n]||void 0,t,o,r);var i;J(n)&&(V++,l()(s)?F[O]=v()(i=F[O]).call(i,s):F[O].push(s))}:(n,o)=>{if(J(n)){if(Object.prototype.hasOwnProperty.call(e,"discriminator")&&e.discriminator&&Object.prototype.hasOwnProperty.call(e.discriminator,"mapping")&&e.discriminator.mapping&&Object.prototype.hasOwnProperty.call(e,"$$ref")&&e.$$ref&&e.discriminator.propertyName===n){for(let t in e.discriminator.mapping)if(-1!==e.$$ref.search(e.discriminator.mapping[t])){F[n]=t;break}}else F[n]=B(U[n],t,o,r);V++}},s){let o;if(o=T(void 0!==n?n:void 0!==b?b:e.default),!r){if("number"==typeof o&&"string"===y)return`${o}`;if("string"!=typeof o||"string"===y)return o;try{return JSON.parse(o)}catch(e){return o}}if(e||(y=l()(o)?"array":typeof o),"array"===y){if(!l()(o)){if("string"==typeof o)return o;o=[o]}const n=e?e.items:void 0;n&&(n.xml=n.xml||m||{},n.xml.name=n.xml.name||m.name);let s=w()(o).call(o,(e=>B(n,t,e,r)));return s=q(s),m.wrapped?(F[O]=s,A()(f)||F[O].push({_attr:f})):F=s,F}if("object"===y){if("string"==typeof o)return o;for(let t in o)Object.prototype.hasOwnProperty.call(o,t)&&(e&&U[t]&&U[t].readOnly&&!_||e&&U[t]&&U[t].writeOnly&&!j||(e&&U[t]&&U[t].xml&&U[t].xml.attribute?f[U[t].xml.name||t]=o[t]:z(t,o[t])));return A()(f)||F[O].push({_attr:f}),F}return F[O]=A()(f)?o:[{_attr:f},o],F}if("object"===y){for(let e in U)Object.prototype.hasOwnProperty.call(U,e)&&(U[e]&&U[e].deprecated||U[e]&&U[e].readOnly&&!_||U[e]&&U[e].writeOnly&&!j||z(e));if(r&&f&&F[O].push({_attr:f}),W())return F;if(!0===x)r?F[O].push({additionalProp:"Anything can be here"}):F.additionalProp1={},V++;else if(x){const n=(0,C.mz)(x),o=B(n,t,void 0,r);if(r&&n.xml&&n.xml.name&&"notagname"!==n.xml.name)F[O].push(o);else{const t=null!==e.minProperties&&void 0!==e.minProperties&&VB(L(S,e,t),t,void 0,r)));else if(l()(S.oneOf)){var G;n=w()(G=S.oneOf).call(G,(e=>B(L(S,e,t),t,void 0,r)))}else{if(!(!r||r&&m.wrapped))return B(S,t,void 0,r);n=[B(S,t,void 0,r)]}return n=q(n),r&&m.wrapped?(F[O]=n,A()(f)||F[O].push({_attr:f}),F):n}let Z;if(e&&l()(e.enum))Z=(0,C.AF)(e.enum)[0];else{if(!e)return;if(Z=I(e),"number"==typeof Z){let t=e.minimum;null!=t&&(e.exclusiveMinimum&&t++,Z=t);let n=e.maximum;null!=n&&(e.exclusiveMaximum&&n--,Z=n)}if("string"==typeof Z&&(null!==e.maxLength&&void 0!==e.maxLength&&(Z=d()(Z).call(Z,0,e.maxLength)),null!==e.minLength&&void 0!==e.minLength)){let t=0;for(;Z.length(e.schema&&(e=e.schema),e.properties&&(e.type="object"),e),q=(e,t,n)=>{const r=B(e,t,n,!0);if(r)return"string"==typeof r?r:_()(r,{declaration:!0,indent:"\t"})},U=(e,t,n)=>B(e,t,n,!1),z=(e,t,n)=>[e,x()(t),x()(n)],V=(0,P.Z)(q,z),W=(0,P.Z)(U,z)},8883:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>l});var r=n(29812),o=n(72846),s=n(79431),i=n(81169),a=n(16132);const l=e=>{let{getSystem:t}=e;return{fn:{inferSchema:r.inferSchema,sampleFromSchema:r.sampleFromSchema,sampleFromSchemaGeneric:r.sampleFromSchemaGeneric,createXMLExample:r.createXMLExample,memoizedSampleFromSchema:r.memoizedSampleFromSchema,memoizedCreateXMLExample:r.memoizedCreateXMLExample,getJsonSampleSchema:(0,o.default)(t),getYamlSampleSchema:(0,s.default)(t),getXmlSampleSchema:(0,i.default)(t),getSampleSchema:(0,a.default)(t)}}}},51228:(e,t,n)=>{"use strict";n.r(t),n.d(t,{CLEAR_REQUEST:()=>ee,CLEAR_RESPONSE:()=>Q,CLEAR_VALIDATE_PARAMS:()=>te,LOG_REQUEST:()=>X,SET_MUTATED_REQUEST:()=>Y,SET_REQUEST:()=>Z,SET_RESPONSE:()=>G,SET_SCHEME:()=>se,UPDATE_EMPTY_PARAM_INCLUSION:()=>K,UPDATE_JSON:()=>W,UPDATE_OPERATION_META_VALUE:()=>ne,UPDATE_PARAM:()=>J,UPDATE_RESOLVED:()=>re,UPDATE_RESOLVED_SUBTREE:()=>oe,UPDATE_SPEC:()=>z,UPDATE_URL:()=>V,VALIDATE_PARAMS:()=>H,changeConsumesValue:()=>_e,changeParam:()=>ye,changeParamByIdentity:()=>ve,changeProducesValue:()=>je,clearRequest:()=>Te,clearResponse:()=>Ie,clearValidateParams:()=>Se,execute:()=>Ne,executeRequest:()=>Pe,invalidateResolvedSubtreeCache:()=>we,logRequest:()=>Ce,parseToJson:()=>pe,requestResolvedSubtree:()=>ge,resolveSpec:()=>fe,setMutatedRequest:()=>Ae,setRequest:()=>ke,setResponse:()=>Oe,setScheme:()=>Re,updateEmptyParamInclusion:()=>xe,updateJsonSpec:()=>ue,updateResolved:()=>le,updateResolvedSubtree:()=>be,updateSpec:()=>ae,updateUrl:()=>ce,validateParams:()=>Ee});var r=n(58309),o=n.n(r),s=n(97606),i=n.n(s),a=n(96718),l=n.n(a),c=n(24282),u=n.n(c),p=n(2250),h=n.n(p),f=n(6226),d=n.n(f),m=n(14418),g=n.n(m),y=n(3665),v=n.n(y),b=n(11882),w=n.n(b),E=n(86),x=n.n(E),S=n(28222),_=n.n(S),j=n(76986),O=n.n(j),k=n(70586),A=n.n(k),C=n(1272),P=n(43393),N=n(84564),I=n.n(N),T=n(7710),R=n(47037),M=n.n(R),D=n(23279),F=n.n(D),L=n(36968),B=n.n(L),$=n(72700),q=n.n($),U=n(90242);const z="spec_update_spec",V="spec_update_url",W="spec_update_json",J="spec_update_param",K="spec_update_empty_param_inclusion",H="spec_validate_param",G="spec_set_response",Z="spec_set_request",Y="spec_set_mutated_request",X="spec_log_request",Q="spec_clear_response",ee="spec_clear_request",te="spec_clear_validate_param",ne="spec_update_operation_meta_value",re="spec_update_resolved",oe="spec_update_resolved_subtree",se="set_scheme",ie=e=>M()(e)?e:"";function ae(e){const t=ie(e).replace(/\t/g," ");if("string"==typeof e)return{type:z,payload:t}}function le(e){return{type:re,payload:e}}function ce(e){return{type:V,payload:e}}function ue(e){return{type:W,payload:e}}const pe=e=>t=>{let{specActions:n,specSelectors:r,errActions:o}=t,{specStr:s}=r,i=null;try{e=e||s(),o.clear({source:"parser"}),i=C.ZP.load(e,{schema:C.A8})}catch(e){return console.error(e),o.newSpecErr({source:"parser",level:"error",message:e.reason,line:e.mark&&e.mark.line?e.mark.line+1:void 0})}return i&&"object"==typeof i?n.updateJsonSpec(i):{}};let he=!1;const fe=(e,t)=>n=>{let{specActions:r,specSelectors:s,errActions:a,fn:{fetch:c,resolve:u,AST:p={}},getConfigs:h}=n;he||(console.warn("specActions.resolveSpec is deprecated since v3.10.0 and will be removed in v4.0.0; use requestResolvedSubtree instead!"),he=!0);const{modelPropertyMacro:f,parameterMacro:d,requestInterceptor:m,responseInterceptor:g}=h();void 0===e&&(e=s.specJson()),void 0===t&&(t=s.url());let y=p.getLineNumberForPath?p.getLineNumberForPath:()=>{},v=s.specStr();return u({fetch:c,spec:e,baseDoc:t,modelPropertyMacro:f,parameterMacro:d,requestInterceptor:m,responseInterceptor:g}).then((e=>{let{spec:t,errors:n}=e;if(a.clear({type:"thrown"}),o()(n)&&n.length>0){let e=i()(n).call(n,(e=>(console.error(e),e.line=e.fullPath?y(v,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",l()(e,"message",{enumerable:!0,value:e.message}),e)));a.newThrownErrBatch(e)}return r.updateResolved(t)}))};let de=[];const me=F()((async()=>{const e=de.system;if(!e)return void console.error("debResolveSubtrees: don't have a system to operate on, aborting.");const{errActions:t,errSelectors:n,fn:{resolveSubtree:r,fetch:s,AST:a={}},specSelectors:c,specActions:p}=e;if(!r)return void console.error("Error: Swagger-Client did not provide a `resolveSubtree` method, doing nothing.");let f=a.getLineNumberForPath?a.getLineNumberForPath:()=>{};const m=c.specStr(),{modelPropertyMacro:y,parameterMacro:b,requestInterceptor:w,responseInterceptor:E}=e.getConfigs();try{var x=await u()(de).call(de,(async(e,a)=>{let{resultMap:u,specWithCurrentSubtrees:p}=await e;const{errors:x,spec:S}=await r(p,a,{baseDoc:c.url(),modelPropertyMacro:y,parameterMacro:b,requestInterceptor:w,responseInterceptor:E});if(n.allErrors().size&&t.clearBy((e=>{var t;return"thrown"!==e.get("type")||"resolver"!==e.get("source")||!h()(t=e.get("fullPath")).call(t,((e,t)=>e===a[t]||void 0===a[t]))})),o()(x)&&x.length>0){let e=i()(x).call(x,(e=>(e.line=e.fullPath?f(m,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",l()(e,"message",{enumerable:!0,value:e.message}),e)));t.newThrownErrBatch(e)}var _,j;S&&c.isOAS3()&&"components"===a[0]&&"securitySchemes"===a[1]&&await d().all(i()(_=g()(j=v()(S)).call(j,(e=>"openIdConnect"===e.type))).call(_,(async e=>{const t={url:e.openIdConnectUrl,requestInterceptor:w,responseInterceptor:E};try{const n=await s(t);n instanceof Error||n.status>=400?console.error(n.statusText+" "+t.url):e.openIdConnectData=JSON.parse(n.text)}catch(e){console.error(e)}})));return B()(u,a,S),p=q()(a,S,p),{resultMap:u,specWithCurrentSubtrees:p}}),d().resolve({resultMap:(c.specResolvedSubtree([])||(0,P.Map)()).toJS(),specWithCurrentSubtrees:c.specJS()}));delete de.system,de=[]}catch(e){console.error(e)}p.updateResolvedSubtree([],x.resultMap)}),35),ge=e=>t=>{var n;w()(n=i()(de).call(de,(e=>e.join("@@")))).call(n,e.join("@@"))>-1||(de.push(e),de.system=t,me())};function ye(e,t,n,r,o){return{type:J,payload:{path:e,value:r,paramName:t,paramIn:n,isXml:o}}}function ve(e,t,n,r){return{type:J,payload:{path:e,param:t,value:n,isXml:r}}}const be=(e,t)=>({type:oe,payload:{path:e,value:t}}),we=()=>({type:oe,payload:{path:[],value:(0,P.Map)()}}),Ee=(e,t)=>({type:H,payload:{pathMethod:e,isOAS3:t}}),xe=(e,t,n,r)=>({type:K,payload:{pathMethod:e,paramName:t,paramIn:n,includeEmptyValue:r}});function Se(e){return{type:te,payload:{pathMethod:e}}}function _e(e,t){return{type:ne,payload:{path:e,value:t,key:"consumes_value"}}}function je(e,t){return{type:ne,payload:{path:e,value:t,key:"produces_value"}}}const Oe=(e,t,n)=>({payload:{path:e,method:t,res:n},type:G}),ke=(e,t,n)=>({payload:{path:e,method:t,req:n},type:Z}),Ae=(e,t,n)=>({payload:{path:e,method:t,req:n},type:Y}),Ce=e=>({payload:e,type:X}),Pe=e=>t=>{let{fn:n,specActions:r,specSelectors:s,getConfigs:a,oas3Selectors:l}=t,{pathName:c,method:u,operation:p}=e,{requestInterceptor:h,responseInterceptor:f}=a(),d=p.toJS();var m,y;p&&p.get("parameters")&&x()(m=g()(y=p.get("parameters")).call(y,(e=>e&&!0===e.get("allowEmptyValue")))).call(m,(t=>{if(s.parameterInclusionSettingFor([c,u],t.get("name"),t.get("in"))){e.parameters=e.parameters||{};const n=(0,U.cz)(t,e.parameters);(!n||n&&0===n.size)&&(e.parameters[t.get("name")]="")}}));if(e.contextUrl=I()(s.url()).toString(),d&&d.operationId?e.operationId=d.operationId:d&&c&&u&&(e.operationId=n.opId(d,c,u)),s.isOAS3()){const t=`${c}:${u}`;e.server=l.selectedServer(t)||l.selectedServer();const n=l.serverVariables({server:e.server,namespace:t}).toJS(),r=l.serverVariables({server:e.server}).toJS();e.serverVariables=_()(n).length?n:r,e.requestContentType=l.requestContentType(c,u),e.responseContentType=l.responseContentType(c,u)||"*/*";const s=l.requestBodyValue(c,u),a=l.requestBodyInclusionSetting(c,u);var v;if(s&&s.toJS)e.requestBody=g()(v=i()(s).call(s,(e=>P.Map.isMap(e)?e.get("value"):e))).call(v,((e,t)=>(o()(e)?0!==e.length:!(0,U.O2)(e))||a.get(t))).toJS();else e.requestBody=s}let b=O()({},e);b=n.buildRequest(b),r.setRequest(e.pathName,e.method,b);e.requestInterceptor=async t=>{let n=await h.apply(void 0,[t]),o=O()({},n);return r.setMutatedRequest(e.pathName,e.method,o),n},e.responseInterceptor=f;const w=A()();return n.execute(e).then((t=>{t.duration=A()()-w,r.setResponse(e.pathName,e.method,t)})).catch((t=>{"Failed to fetch"===t.message&&(t.name="",t.message='**Failed to fetch.** \n**Possible Reasons:** \n - CORS \n - Network Failure \n - URL scheme must be "http" or "https" for CORS request.'),r.setResponse(e.pathName,e.method,{error:!0,err:(0,T.serializeError)(t)})}))},Ne=function(){let{path:e,method:t,...n}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return r=>{let{fn:{fetch:o},specSelectors:s,specActions:i}=r,a=s.specJsonWithResolvedSubtrees().toJS(),l=s.operationScheme(e,t),{requestContentType:c,responseContentType:u}=s.contentTypeValues([e,t]).toJS(),p=/xml/i.test(c),h=s.parameterValues([e,t],p).toJS();return i.executeRequest({...n,fetch:o,spec:a,pathName:e,method:t,parameters:h,requestContentType:c,scheme:l,responseContentType:u})}};function Ie(e,t){return{type:Q,payload:{path:e,method:t}}}function Te(e,t){return{type:ee,payload:{path:e,method:t}}}function Re(e,t,n){return{type:se,payload:{scheme:e,path:t,method:n}}}},37038:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(20032),o=n(51228),s=n(33881),i=n(77508);function a(){return{statePlugins:{spec:{wrapActions:i,reducers:r.default,actions:o,selectors:s}}}}},20032:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>d});var r=n(24282),o=n.n(r),s=n(97606),i=n.n(s),a=n(76986),l=n.n(a),c=n(43393),u=n(90242),p=n(27504),h=n(33881),f=n(51228);const d={[f.UPDATE_SPEC]:(e,t)=>"string"==typeof t.payload?e.set("spec",t.payload):e,[f.UPDATE_URL]:(e,t)=>e.set("url",t.payload+""),[f.UPDATE_JSON]:(e,t)=>e.set("json",(0,u.oG)(t.payload)),[f.UPDATE_RESOLVED]:(e,t)=>e.setIn(["resolved"],(0,u.oG)(t.payload)),[f.UPDATE_RESOLVED_SUBTREE]:(e,t)=>{const{value:n,path:r}=t.payload;return e.setIn(["resolvedSubtrees",...r],(0,u.oG)(n))},[f.UPDATE_PARAM]:(e,t)=>{let{payload:n}=t,{path:r,paramName:o,paramIn:s,param:i,value:a,isXml:l}=n,c=i?(0,u.V9)(i):`${s}.${o}`;const p=l?"value_xml":"value";return e.setIn(["meta","paths",...r,"parameters",c,p],a)},[f.UPDATE_EMPTY_PARAM_INCLUSION]:(e,t)=>{let{payload:n}=t,{pathMethod:r,paramName:o,paramIn:s,includeEmptyValue:i}=n;if(!o||!s)return console.warn("Warning: UPDATE_EMPTY_PARAM_INCLUSION could not generate a paramKey."),e;const a=`${s}.${o}`;return e.setIn(["meta","paths",...r,"parameter_inclusions",a],i)},[f.VALIDATE_PARAMS]:(e,t)=>{let{payload:{pathMethod:n,isOAS3:r}}=t;const s=(0,h.specJsonWithResolvedSubtrees)(e).getIn(["paths",...n]),i=(0,h.parameterValues)(e,n).toJS();return e.updateIn(["meta","paths",...n,"parameters"],(0,c.fromJS)({}),(t=>{var a;return o()(a=s.get("parameters",(0,c.List)())).call(a,((t,o)=>{const s=(0,u.cz)(o,i),a=(0,h.parameterInclusionSettingFor)(e,n,o.get("name"),o.get("in")),l=(0,u.Ik)(o,s,{bypassRequiredCheck:a,isOAS3:r});return t.setIn([(0,u.V9)(o),"errors"],(0,c.fromJS)(l))}),t)}))},[f.CLEAR_VALIDATE_PARAMS]:(e,t)=>{let{payload:{pathMethod:n}}=t;return e.updateIn(["meta","paths",...n,"parameters"],(0,c.fromJS)([]),(e=>i()(e).call(e,(e=>e.set("errors",(0,c.fromJS)([]))))))},[f.SET_RESPONSE]:(e,t)=>{let n,{payload:{res:r,path:o,method:s}}=t;n=r.error?l()({error:!0,name:r.err.name,message:r.err.message,statusCode:r.err.statusCode},r.err.response):r,n.headers=n.headers||{};let i=e.setIn(["responses",o,s],(0,u.oG)(n));return p.Z.Blob&&r.data instanceof p.Z.Blob&&(i=i.setIn(["responses",o,s,"text"],r.data)),i},[f.SET_REQUEST]:(e,t)=>{let{payload:{req:n,path:r,method:o}}=t;return e.setIn(["requests",r,o],(0,u.oG)(n))},[f.SET_MUTATED_REQUEST]:(e,t)=>{let{payload:{req:n,path:r,method:o}}=t;return e.setIn(["mutatedRequests",r,o],(0,u.oG)(n))},[f.UPDATE_OPERATION_META_VALUE]:(e,t)=>{let{payload:{path:n,value:r,key:o}}=t,s=["paths",...n],i=["meta","paths",...n];return e.getIn(["json",...s])||e.getIn(["resolved",...s])||e.getIn(["resolvedSubtrees",...s])?e.setIn([...i,o],(0,c.fromJS)(r)):e},[f.CLEAR_RESPONSE]:(e,t)=>{let{payload:{path:n,method:r}}=t;return e.deleteIn(["responses",n,r])},[f.CLEAR_REQUEST]:(e,t)=>{let{payload:{path:n,method:r}}=t;return e.deleteIn(["requests",n,r])},[f.SET_SCHEME]:(e,t)=>{let{payload:{scheme:n,path:r,method:o}}=t;return r&&o?e.setIn(["scheme",r,o],n):r||o?void 0:e.setIn(["scheme","_defaultScheme"],n)}}},33881:(e,t,n)=>{"use strict";n.r(t),n.d(t,{allowTryItOutFor:()=>fe,basePath:()=>Q,canExecuteScheme:()=>Ae,consumes:()=>K,consumesOptionsFor:()=>Oe,contentTypeValues:()=>Se,currentProducesFor:()=>_e,definitions:()=>X,externalDocs:()=>q,findDefinition:()=>Y,getOAS3RequiredRequestBodyContentType:()=>Ne,getParameter:()=>ve,hasHost:()=>be,host:()=>ee,info:()=>$,isMediaTypeSchemaPropertiesEqual:()=>Ie,isOAS3:()=>B,lastError:()=>A,mutatedRequestFor:()=>he,mutatedRequests:()=>ce,operationScheme:()=>ke,operationWithMeta:()=>ye,operations:()=>J,operationsWithRootInherited:()=>ne,operationsWithTags:()=>se,parameterInclusionSettingFor:()=>me,parameterValues:()=>we,parameterWithMeta:()=>ge,parameterWithMetaByIdentity:()=>de,parametersIncludeIn:()=>Ee,parametersIncludeType:()=>xe,paths:()=>V,produces:()=>H,producesOptionsFor:()=>je,requestFor:()=>pe,requests:()=>le,responseFor:()=>ue,responses:()=>ae,schemes:()=>te,security:()=>G,securityDefinitions:()=>Z,semver:()=>z,spec:()=>L,specJS:()=>T,specJson:()=>I,specJsonWithResolvedSubtrees:()=>F,specResolved:()=>R,specResolvedSubtree:()=>M,specSource:()=>N,specStr:()=>P,tagDetails:()=>oe,taggedOperations:()=>ie,tags:()=>re,url:()=>C,validOperationMethods:()=>W,validateBeforeExecute:()=>Pe,validationErrors:()=>Ce,version:()=>U});var r=n(24278),o=n.n(r),s=n(86),i=n.n(s),a=n(11882),l=n.n(a),c=n(97606),u=n.n(c),p=n(14418),h=n.n(p),f=n(51679),d=n.n(f),m=n(24282),g=n.n(m),y=n(2578),v=n.n(y),b=n(92039),w=n.n(b),E=n(58309),x=n.n(E),S=n(20573),_=n(90242),j=n(43393);const O=["get","put","post","delete","options","head","patch","trace"],k=e=>e||(0,j.Map)(),A=(0,S.P1)(k,(e=>e.get("lastError"))),C=(0,S.P1)(k,(e=>e.get("url"))),P=(0,S.P1)(k,(e=>e.get("spec")||"")),N=(0,S.P1)(k,(e=>e.get("specSource")||"not-editor")),I=(0,S.P1)(k,(e=>e.get("json",(0,j.Map)()))),T=(0,S.P1)(I,(e=>e.toJS())),R=(0,S.P1)(k,(e=>e.get("resolved",(0,j.Map)()))),M=(e,t)=>e.getIn(["resolvedSubtrees",...t],void 0),D=(e,t)=>j.Map.isMap(e)&&j.Map.isMap(t)?t.get("$$ref")?t:(0,j.OrderedMap)().mergeWith(D,e,t):t,F=(0,S.P1)(k,(e=>(0,j.OrderedMap)().mergeWith(D,e.get("json"),e.get("resolvedSubtrees")))),L=e=>I(e),B=(0,S.P1)(L,(()=>!1)),$=(0,S.P1)(L,(e=>Te(e&&e.get("info")))),q=(0,S.P1)(L,(e=>Te(e&&e.get("externalDocs")))),U=(0,S.P1)($,(e=>e&&e.get("version"))),z=(0,S.P1)(U,(e=>{var t;return o()(t=/v?([0-9]*)\.([0-9]*)\.([0-9]*)/i.exec(e)).call(t,1)})),V=(0,S.P1)(F,(e=>e.get("paths"))),W=(0,S.P1)((()=>["get","put","post","delete","options","head","patch"])),J=(0,S.P1)(V,(e=>{if(!e||e.size<1)return(0,j.List)();let t=(0,j.List)();return e&&i()(e)?(i()(e).call(e,((e,n)=>{if(!e||!i()(e))return{};i()(e).call(e,((e,r)=>{l()(O).call(O,r)<0||(t=t.push((0,j.fromJS)({path:n,method:r,operation:e,id:`${r}-${n}`})))}))})),t):(0,j.List)()})),K=(0,S.P1)(L,(e=>(0,j.Set)(e.get("consumes")))),H=(0,S.P1)(L,(e=>(0,j.Set)(e.get("produces")))),G=(0,S.P1)(L,(e=>e.get("security",(0,j.List)()))),Z=(0,S.P1)(L,(e=>e.get("securityDefinitions"))),Y=(e,t)=>{const n=e.getIn(["resolvedSubtrees","definitions",t],null),r=e.getIn(["json","definitions",t],null);return n||r||null},X=(0,S.P1)(L,(e=>{const t=e.get("definitions");return j.Map.isMap(t)?t:(0,j.Map)()})),Q=(0,S.P1)(L,(e=>e.get("basePath"))),ee=(0,S.P1)(L,(e=>e.get("host"))),te=(0,S.P1)(L,(e=>e.get("schemes",(0,j.Map)()))),ne=(0,S.P1)(J,K,H,((e,t,n)=>u()(e).call(e,(e=>e.update("operation",(e=>{if(e){if(!j.Map.isMap(e))return;return e.withMutations((e=>(e.get("consumes")||e.update("consumes",(e=>(0,j.Set)(e).merge(t))),e.get("produces")||e.update("produces",(e=>(0,j.Set)(e).merge(n))),e)))}return(0,j.Map)()})))))),re=(0,S.P1)(L,(e=>{const t=e.get("tags",(0,j.List)());return j.List.isList(t)?h()(t).call(t,(e=>j.Map.isMap(e))):(0,j.List)()})),oe=(e,t)=>{var n;let r=re(e)||(0,j.List)();return d()(n=h()(r).call(r,j.Map.isMap)).call(n,(e=>e.get("name")===t),(0,j.Map)())},se=(0,S.P1)(ne,re,((e,t)=>g()(e).call(e,((e,t)=>{let n=(0,j.Set)(t.getIn(["operation","tags"]));return n.count()<1?e.update("default",(0,j.List)(),(e=>e.push(t))):g()(n).call(n,((e,n)=>e.update(n,(0,j.List)(),(e=>e.push(t)))),e)}),g()(t).call(t,((e,t)=>e.set(t.get("name"),(0,j.List)())),(0,j.OrderedMap)())))),ie=e=>t=>{var n;let{getConfigs:r}=t,{tagsSorter:o,operationsSorter:s}=r();return u()(n=se(e).sortBy(((e,t)=>t),((e,t)=>{let n="function"==typeof o?o:_.wh.tagsSorter[o];return n?n(e,t):null}))).call(n,((t,n)=>{let r="function"==typeof s?s:_.wh.operationsSorter[s],o=r?v()(t).call(t,r):t;return(0,j.Map)({tagDetails:oe(e,n),operations:o})}))},ae=(0,S.P1)(k,(e=>e.get("responses",(0,j.Map)()))),le=(0,S.P1)(k,(e=>e.get("requests",(0,j.Map)()))),ce=(0,S.P1)(k,(e=>e.get("mutatedRequests",(0,j.Map)()))),ue=(e,t,n)=>ae(e).getIn([t,n],null),pe=(e,t,n)=>le(e).getIn([t,n],null),he=(e,t,n)=>ce(e).getIn([t,n],null),fe=()=>!0,de=(e,t,n)=>{const r=F(e).getIn(["paths",...t,"parameters"],(0,j.OrderedMap)()),o=e.getIn(["meta","paths",...t,"parameters"],(0,j.OrderedMap)()),s=u()(r).call(r,(e=>{const t=o.get(`${n.get("in")}.${n.get("name")}`),r=o.get(`${n.get("in")}.${n.get("name")}.hash-${n.hashCode()}`);return(0,j.OrderedMap)().merge(e,t,r)}));return d()(s).call(s,(e=>e.get("in")===n.get("in")&&e.get("name")===n.get("name")),(0,j.OrderedMap)())},me=(e,t,n,r)=>{const o=`${r}.${n}`;return e.getIn(["meta","paths",...t,"parameter_inclusions",o],!1)},ge=(e,t,n,r)=>{const o=F(e).getIn(["paths",...t,"parameters"],(0,j.OrderedMap)()),s=d()(o).call(o,(e=>e.get("in")===r&&e.get("name")===n),(0,j.OrderedMap)());return de(e,t,s)},ye=(e,t,n)=>{var r;const o=F(e).getIn(["paths",t,n],(0,j.OrderedMap)()),s=e.getIn(["meta","paths",t,n],(0,j.OrderedMap)()),i=u()(r=o.get("parameters",(0,j.List)())).call(r,(r=>de(e,[t,n],r)));return(0,j.OrderedMap)().merge(o,s).set("parameters",i)};function ve(e,t,n,r){t=t||[];let o=e.getIn(["meta","paths",...t,"parameters"],(0,j.fromJS)([]));return d()(o).call(o,(e=>j.Map.isMap(e)&&e.get("name")===n&&e.get("in")===r))||(0,j.Map)()}const be=(0,S.P1)(L,(e=>{const t=e.get("host");return"string"==typeof t&&t.length>0&&"/"!==t[0]}));function we(e,t,n){t=t||[];let r=ye(e,...t).get("parameters",(0,j.List)());return g()(r).call(r,((e,t)=>{let r=n&&"body"===t.get("in")?t.get("value_xml"):t.get("value");return e.set((0,_.V9)(t,{allowHashes:!1}),r)}),(0,j.fromJS)({}))}function Ee(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(j.List.isList(e))return w()(e).call(e,(e=>j.Map.isMap(e)&&e.get("in")===t))}function xe(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(j.List.isList(e))return w()(e).call(e,(e=>j.Map.isMap(e)&&e.get("type")===t))}function Se(e,t){t=t||[];let n=F(e).getIn(["paths",...t],(0,j.fromJS)({})),r=e.getIn(["meta","paths",...t],(0,j.fromJS)({})),o=_e(e,t);const s=n.get("parameters")||new j.List,i=r.get("consumes_value")?r.get("consumes_value"):xe(s,"file")?"multipart/form-data":xe(s,"formData")?"application/x-www-form-urlencoded":void 0;return(0,j.fromJS)({requestContentType:i,responseContentType:o})}function _e(e,t){t=t||[];const n=F(e).getIn(["paths",...t],null);if(null===n)return;const r=e.getIn(["meta","paths",...t,"produces_value"],null),o=n.getIn(["produces",0],null);return r||o||"application/json"}function je(e,t){t=t||[];const n=F(e),r=n.getIn(["paths",...t],null);if(null===r)return;const[o]=t,s=r.get("produces",null),i=n.getIn(["paths",o,"produces"],null),a=n.getIn(["produces"],null);return s||i||a}function Oe(e,t){t=t||[];const n=F(e),r=n.getIn(["paths",...t],null);if(null===r)return;const[o]=t,s=r.get("consumes",null),i=n.getIn(["paths",o,"consumes"],null),a=n.getIn(["consumes"],null);return s||i||a}const ke=(e,t,n)=>{let r=e.get("url").match(/^([a-z][a-z0-9+\-.]*):/),o=x()(r)?r[1]:null;return e.getIn(["scheme",t,n])||e.getIn(["scheme","_defaultScheme"])||o||""},Ae=(e,t,n)=>{var r;return l()(r=["http","https"]).call(r,ke(e,t,n))>-1},Ce=(e,t)=>{t=t||[];let n=e.getIn(["meta","paths",...t,"parameters"],(0,j.fromJS)([]));const r=[];return i()(n).call(n,(e=>{let t=e.get("errors");t&&t.count()&&i()(t).call(t,(e=>r.push(e)))})),r},Pe=(e,t)=>0===Ce(e,t).length,Ne=(e,t)=>{var n;let r={requestBody:!1,requestContentType:{}},o=e.getIn(["resolvedSubtrees","paths",...t,"requestBody"],(0,j.fromJS)([]));return o.size<1||(o.getIn(["required"])&&(r.requestBody=o.getIn(["required"])),i()(n=o.getIn(["content"]).entrySeq()).call(n,(e=>{const t=e[0];if(e[1].getIn(["schema","required"])){const n=e[1].getIn(["schema","required"]).toJS();r.requestContentType[t]=n}}))),r},Ie=(e,t,n,r)=>{if((n||r)&&n===r)return!0;let o=e.getIn(["resolvedSubtrees","paths",...t,"requestBody","content"],(0,j.fromJS)([]));if(o.size<2||!n||!r)return!1;let s=o.getIn([n,"schema","properties"],(0,j.fromJS)([])),i=o.getIn([r,"schema","properties"],(0,j.fromJS)([]));return!!s.equals(i)};function Te(e){return j.Map.isMap(e)?e:new j.Map}},77508:(e,t,n)=>{"use strict";n.r(t),n.d(t,{executeRequest:()=>p,updateJsonSpec:()=>u,updateSpec:()=>c,validateParams:()=>h});var r=n(28222),o=n.n(r),s=n(86),i=n.n(s),a=n(27361),l=n.n(a);const c=(e,t)=>{let{specActions:n}=t;return function(){e(...arguments),n.parseToJson(...arguments)}},u=(e,t)=>{let{specActions:n}=t;return function(){for(var t=arguments.length,r=new Array(t),s=0;s{l()(c,[e]).$ref&&n.requestResolvedSubtree(["paths",e])})),n.requestResolvedSubtree(["components","securitySchemes"])}},p=(e,t)=>{let{specActions:n}=t;return t=>(n.logRequest(t),e(t))},h=(e,t)=>{let{specSelectors:n}=t;return t=>e(t,n.isOAS3())}},34852:(e,t,n)=>{"use strict";n.r(t),n.d(t,{loaded:()=>r});const r=(e,t)=>function(){e(...arguments);const n=t.getConfigs().withCredentials;void 0!==n&&(t.fn.fetch.withCredentials="string"==typeof n?"true"===n:!!n)}},79934:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>BE});var r={};n.r(r),n.d(r,{JsonPatchError:()=>j,_areEquals:()=>M,applyOperation:()=>P,applyPatch:()=>N,applyReducer:()=>I,deepClone:()=>O,getValueByPointer:()=>C,validate:()=>R,validator:()=>T});var o={};n.r(o),n.d(o,{compare:()=>z,generate:()=>q,observe:()=>$,unobserve:()=>B});var s={};n.r(s),n.d(s,{hasElementSourceMap:()=>Cs,includesClasses:()=>Ns,includesSymbols:()=>Ps,isAnnotationElement:()=>_s,isArrayElement:()=>ws,isBooleanElement:()=>vs,isCommentElement:()=>js,isElement:()=>ds,isLinkElement:()=>xs,isMemberElement:()=>Es,isNullElement:()=>ys,isNumberElement:()=>gs,isObjectElement:()=>bs,isParseResultElement:()=>Os,isPrimitiveElement:()=>As,isRefElement:()=>Ss,isSourceMapElement:()=>ks,isStringElement:()=>ms});var i={};n.r(i),n.d(i,{isJSONReferenceElement:()=>cc,isJSONSchemaElement:()=>lc,isLinkDescriptionElement:()=>pc,isMediaElement:()=>uc});var a={};n.r(a),n.d(a,{isOpenApi3_0LikeElement:()=>$c,isOpenApiExtension:()=>Kc,isParameterLikeElement:()=>qc,isReferenceLikeElement:()=>Uc,isRequestBodyLikeElement:()=>zc,isResponseLikeElement:()=>Vc,isServerLikeElement:()=>Wc,isTagLikeElement:()=>Jc});var l={};n.r(l),n.d(l,{isBooleanJsonSchemaElement:()=>ap,isCallbackElement:()=>Lu,isComponentsElement:()=>Bu,isContactElement:()=>$u,isExampleElement:()=>qu,isExternalDocumentationElement:()=>Uu,isHeaderElement:()=>zu,isInfoElement:()=>Vu,isLicenseElement:()=>Wu,isLinkElement:()=>Ju,isLinkElementExternal:()=>Ku,isMediaTypeElement:()=>pp,isOpenApi3_0Element:()=>Gu,isOpenapiElement:()=>Hu,isOperationElement:()=>Zu,isParameterElement:()=>Yu,isPathItemElement:()=>Xu,isPathItemElementExternal:()=>Qu,isPathsElement:()=>ep,isReferenceElement:()=>tp,isReferenceElementExternal:()=>np,isRequestBodyElement:()=>rp,isResponseElement:()=>op,isResponsesElement:()=>sp,isSchemaElement:()=>ip,isSecurityRequirementElement:()=>lp,isServerElement:()=>cp,isServerVariableElement:()=>up});var c={};n.r(c),n.d(c,{isBooleanJsonSchemaElement:()=>Kg,isCallbackElement:()=>Sg,isComponentsElement:()=>_g,isContactElement:()=>jg,isExampleElement:()=>Og,isExternalDocumentationElement:()=>kg,isHeaderElement:()=>Ag,isInfoElement:()=>Cg,isJsonSchemaDialectElement:()=>Pg,isLicenseElement:()=>Ng,isLinkElement:()=>Ig,isLinkElementExternal:()=>Tg,isMediaTypeElement:()=>Yg,isOpenApi3_1Element:()=>Mg,isOpenapiElement:()=>Rg,isOperationElement:()=>Dg,isParameterElement:()=>Fg,isPathItemElement:()=>Lg,isPathItemElementExternal:()=>Bg,isPathsElement:()=>$g,isReferenceElement:()=>qg,isReferenceElementExternal:()=>Ug,isRequestBodyElement:()=>zg,isResponseElement:()=>Vg,isResponsesElement:()=>Wg,isSchemaElement:()=>Jg,isSecurityRequirementElement:()=>Hg,isServerElement:()=>Gg,isServerVariableElement:()=>Zg});var u={};n.r(u),n.d(u,{cookie:()=>EE,header:()=>wE,path:()=>yE,query:()=>vE});var p,h=n(58826),f=n.n(h),d=(p=function(e,t){return p=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])},p(e,t)},function(e,t){function n(){this.constructor=e}p(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),m=Object.prototype.hasOwnProperty;function g(e,t){return m.call(e,t)}function y(e){if(Array.isArray(e)){for(var t=new Array(e.length),n=0;n=48&&t<=57))return!1;n++}return!0}function w(e){return-1===e.indexOf("/")&&-1===e.indexOf("~")?e:e.replace(/~/g,"~0").replace(/\//g,"~1")}function E(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}function x(e){if(void 0===e)return!0;if(e)if(Array.isArray(e)){for(var t=0,n=e.length;t0&&"constructor"==a[c-1]))throw new TypeError("JSON-Patch: modifying `__proto__` or `constructor/prototype` prop is banned for security reasons, if this was on purpose, please set `banPrototypeModifications` flag false and pass it to this function. More info in fast-json-patch README");if(n&&void 0===p&&(void 0===l[h]?p=a.slice(0,c).join("/"):c==u-1&&(p=t.path),void 0!==p&&f(t,0,e,p)),c++,Array.isArray(l)){if("-"===h)h=l.length;else{if(n&&!b(h))throw new j("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index","OPERATION_PATH_ILLEGAL_ARRAY_INDEX",s,t,e);b(h)&&(h=~~h)}if(c>=u){if(n&&"add"===t.op&&h>l.length)throw new j("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",s,t,e);if(!1===(i=A[t.op].call(t,l,h,e)).test)throw new j("Test operation failed","TEST_OPERATION_FAILED",s,t,e);return i}}else if(c>=u){if(!1===(i=k[t.op].call(t,l,h,e)).test)throw new j("Test operation failed","TEST_OPERATION_FAILED",s,t,e);return i}if(l=l[h],n&&c0)throw new j('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",t,e,n);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new j("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new j("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&x(e.value))throw new j("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED",t,e,n);if(n)if("add"==e.op){var o=e.path.split("/").length,s=r.split("/").length;if(o!==s+1&&o!==s)throw new j("Cannot perform an `add` operation at the desired path","OPERATION_PATH_CANNOT_ADD",t,e,n)}else if("replace"===e.op||"remove"===e.op||"_get"===e.op){if(e.path!==r)throw new j("Cannot perform the operation at a path that does not exist","OPERATION_PATH_UNRESOLVABLE",t,e,n)}else if("move"===e.op||"copy"===e.op){var i=R([{op:"_get",path:e.from,value:void 0}],n);if(i&&"OPERATION_PATH_UNRESOLVABLE"===i.name)throw new j("Cannot perform the operation from a path that does not exist","OPERATION_FROM_UNRESOLVABLE",t,e,n)}}function R(e,t,n){try{if(!Array.isArray(e))throw new j("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");if(t)N(v(t),v(e),n||!0);else{n=n||T;for(var r=0;r0&&(e.patches=[],e.callback&&e.callback(r)),r}function U(e,t,n,r,o){if(t!==e){"function"==typeof t.toJSON&&(t=t.toJSON());for(var s=y(t),i=y(e),a=!1,l=i.length-1;l>=0;l--){var c=e[p=i[l]];if(!g(t,p)||void 0===t[p]&&void 0!==c&&!1===Array.isArray(t))Array.isArray(e)===Array.isArray(t)?(o&&n.push({op:"test",path:r+"/"+w(p),value:v(c)}),n.push({op:"remove",path:r+"/"+w(p)}),a=!0):(o&&n.push({op:"test",path:r,value:e}),n.push({op:"replace",path:r,value:t}),!0);else{var u=t[p];"object"==typeof c&&null!=c&&"object"==typeof u&&null!=u&&Array.isArray(c)===Array.isArray(u)?U(c,u,n,r+"/"+w(p),o):c!==u&&(!0,o&&n.push({op:"test",path:r+"/"+w(p),value:v(c)}),n.push({op:"replace",path:r+"/"+w(p),value:v(u)}))}}if(a||s.length!=i.length)for(l=0;lvoid 0!==t&&e?e[t]:e),e)},applyPatch:function(e,t,n){if(n=n||{},"merge"===(t=f()(f()({},t),{},{path:t.path&&K(t.path)})).op){const n=ae(e,t.path);Object.assign(n,t.value),N(e,[H(t.path,n)])}else if("mergeDeep"===t.op){const n=ae(e,t.path),r=W()(n,t.value);e=N(e,[H(t.path,r)]).newDocument}else if("add"===t.op&&""===t.path&&te(t.value)){N(e,Object.keys(t.value).reduce(((e,n)=>(e.push({op:"add",path:`/${K(n)}`,value:t.value[n]}),e)),[]))}else if("replace"===t.op&&""===t.path){let{value:r}=t;n.allowMetaPatches&&t.meta&&se(t)&&(Array.isArray(t.value)||te(t.value))&&(r=f()(f()({},r),t.meta)),e=r}else if(N(e,[t]),n.allowMetaPatches&&t.meta&&se(t)&&(Array.isArray(t.value)||te(t.value))){const n=ae(e,t.path),r=f()(f()({},n),t.meta);N(e,[H(t.path,r)])}return e},parentPathMatch:function(e,t){if(!Array.isArray(t))return!1;for(let n=0,r=t.length;n(e+"").replace(/~/g,"~0").replace(/\//g,"~1"))).join("/")}`:e}function H(e,t,n){return{op:"replace",path:e,value:t,meta:n}}function G(e,t,n){return ee(Q(e.filter(se).map((e=>t(e.value,n,e.path)))||[]))}function Z(e,t,n){return n=n||[],Array.isArray(e)?e.map(((e,r)=>Z(e,t,n.concat(r)))):te(e)?Object.keys(e).map((r=>Z(e[r],t,n.concat(r)))):t(e,n[n.length-1],n)}function Y(e,t,n){let r=[];if((n=n||[]).length>0){const o=t(e,n[n.length-1],n);o&&(r=r.concat(o))}if(Array.isArray(e)){const o=e.map(((e,r)=>Y(e,t,n.concat(r))));o&&(r=r.concat(o))}else if(te(e)){const o=Object.keys(e).map((r=>Y(e[r],t,n.concat(r))));o&&(r=r.concat(o))}return r=Q(r),r}function X(e){return Array.isArray(e)?e:[e]}function Q(e){return[].concat(...e.map((e=>Array.isArray(e)?Q(e):e)))}function ee(e){return e.filter((e=>void 0!==e))}function te(e){return e&&"object"==typeof e}function ne(e){return e&&"function"==typeof e}function re(e){if(ie(e)){const{op:t}=e;return"add"===t||"remove"===t||"replace"===t}return!1}function oe(e){return re(e)||ie(e)&&"mutation"===e.type}function se(e){return oe(e)&&("add"===e.op||"replace"===e.op||"merge"===e.op||"mergeDeep"===e.op)}function ie(e){return e&&"object"==typeof e}function ae(e,t){try{return C(e,t)}catch(e){return console.error(e),{}}}n(31905);var le=n(1272),ce=n(8575);function ue(e,t){function n(){Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack;for(var e=arguments.length,n=new Array(e),r=0;r-1&&-1===de.indexOf(n)||me.indexOf(r)>-1||ge.some((e=>r.indexOf(e)>-1))}function ve(e,t){const[n,r]=e.split("#"),o=ce.resolve(n||"",t||"");return r?`${o}#${r}`:o}const be="application/json, application/yaml",we=/^([a-z]+:\/\/|\/\/)/i,Ee=ue("JSONRefError",(function(e,t,n){this.originalError=n,Object.assign(this,t||{})})),xe={},Se=new WeakMap,_e=[e=>"paths"===e[0]&&"responses"===e[3]&&"examples"===e[5],e=>"paths"===e[0]&&"responses"===e[3]&&"content"===e[5]&&"example"===e[7],e=>"paths"===e[0]&&"responses"===e[3]&&"content"===e[5]&&"examples"===e[7]&&"value"===e[9],e=>"paths"===e[0]&&"requestBody"===e[3]&&"content"===e[4]&&"example"===e[6],e=>"paths"===e[0]&&"requestBody"===e[3]&&"content"===e[4]&&"examples"===e[6]&&"value"===e[8],e=>"paths"===e[0]&&"parameters"===e[2]&&"example"===e[4],e=>"paths"===e[0]&&"parameters"===e[3]&&"example"===e[5],e=>"paths"===e[0]&&"parameters"===e[2]&&"examples"===e[4]&&"value"===e[6],e=>"paths"===e[0]&&"parameters"===e[3]&&"examples"===e[5]&&"value"===e[7],e=>"paths"===e[0]&&"parameters"===e[2]&&"content"===e[4]&&"example"===e[6],e=>"paths"===e[0]&&"parameters"===e[2]&&"content"===e[4]&&"examples"===e[6]&&"value"===e[8],e=>"paths"===e[0]&&"parameters"===e[3]&&"content"===e[4]&&"example"===e[7],e=>"paths"===e[0]&&"parameters"===e[3]&&"content"===e[5]&&"examples"===e[7]&&"value"===e[9]],je={key:"$ref",plugin:(e,t,n,r)=>{const o=r.getInstance(),s=n.slice(0,-1);if(ye(s)||(e=>_e.some((t=>t(e))))(s))return;const{baseDoc:i}=r.getContext(n);if("string"!=typeof e)return new Ee("$ref: must be a string (JSON-Ref)",{$ref:e,baseDoc:i,fullPath:n});const a=Pe(e),l=a[0],c=a[1]||"";let u,p,h;try{u=i||l?Ae(l,i):null}catch(t){return Ce(t,{pointer:c,$ref:e,basePath:u,fullPath:n})}if(function(e,t,n,r){let o=Se.get(r);o||(o={},Se.set(r,o));const s=function(e){if(0===e.length)return"";return`/${e.map(De).join("/")}`}(n),i=`${t||""}#${e}`,a=s.replace(/allOf\/\d+\/?/g,""),l=r.contextTree.get([]).baseDoc;if(t===l&&Le(a,e))return!0;let c="";const u=n.some((e=>(c=`${c}/${De(e)}`,o[c]&&o[c].some((e=>Le(e,i)||Le(i,e))))));if(u)return!0;return void(o[a]=(o[a]||[]).concat(i))}(c,u,s,r)&&!o.useCircularStructures){const t=ve(e,u);return e===t?null:J.replace(n,t)}if(null==u?(h=Re(c),p=r.get(h),void 0===p&&(p=new Ee(`Could not resolve reference: ${e}`,{pointer:c,$ref:e,baseDoc:i,fullPath:n}))):(p=Ne(u,c),p=null!=p.__value?p.__value:p.catch((t=>{throw Ce(t,{pointer:c,$ref:e,baseDoc:i,fullPath:n})}))),p instanceof Error)return[J.remove(n),p];const f=ve(e,u),d=J.replace(s,p,{$$ref:f});if(u&&u!==i)return[d,J.context(s,{baseDoc:u})];try{if(!function(e,t){const n=[e];return t.path.reduce(((e,t)=>(n.push(e[t]),e[t])),e),r(t.value);function r(e){return J.isObject(e)&&(n.indexOf(e)>=0||Object.keys(e).some((t=>r(e[t]))))}}(r.state,d)||o.useCircularStructures)return d}catch(e){return null}}},Oe=Object.assign(je,{docCache:xe,absoluteify:Ae,clearCache:function(e){void 0!==e?delete xe[e]:Object.keys(xe).forEach((e=>{delete xe[e]}))},JSONRefError:Ee,wrapError:Ce,getDoc:Ie,split:Pe,extractFromDoc:Ne,fetchJSON:function(e){return fetch(e,{headers:{Accept:be},loadSpec:!0}).then((e=>e.text())).then((e=>le.ZP.load(e)))},extract:Te,jsonPointerToArray:Re,unescapeJsonPointerToken:Me}),ke=Oe;function Ae(e,t){if(!we.test(e)){if(!t)throw new Ee(`Tried to resolve a relative URL, without having a basePath. path: '${e}' basePath: '${t}'`);return ce.resolve(t,e)}return e}function Ce(e,t){let n;return n=e&&e.response&&e.response.body?`${e.response.body.code} ${e.response.body.message}`:e.message,new Ee(`Could not resolve reference: ${n}`,t,e)}function Pe(e){return(e+"").split("#")}function Ne(e,t){const n=xe[e];if(n&&!J.isPromise(n))try{const e=Te(t,n);return Object.assign(Promise.resolve(e),{__value:e})}catch(e){return Promise.reject(e)}return Ie(e).then((e=>Te(t,e)))}function Ie(e){const t=xe[e];return t?J.isPromise(t)?t:Promise.resolve(t):(xe[e]=Oe.fetchJSON(e).then((t=>(xe[e]=t,t))),xe[e])}function Te(e,t){const n=Re(e);if(n.length<1)return t;const r=J.getIn(t,n);if(void 0===r)throw new Ee(`Could not resolve pointer: ${e} does not exist in document`,{pointer:e});return r}function Re(e){if("string"!=typeof e)throw new TypeError("Expected a string, got a "+typeof e);return"/"===e[0]&&(e=e.substr(1)),""===e?[]:e.split("/").map(Me)}function Me(e){if("string"!=typeof e)return e;return new URLSearchParams(`=${e.replace(/~1/g,"/").replace(/~0/g,"~")}`).get("")}function De(e){return new URLSearchParams([["",e.replace(/~/g,"~0").replace(/\//g,"~1")]]).toString().slice(1)}const Fe=e=>!e||"/"===e||"#"===e;function Le(e,t){if(Fe(t))return!0;const n=e.charAt(t.length),r=t.slice(-1);return 0===e.indexOf(t)&&(!n||"/"===n||"#"===n)&&"#"!==r}const Be={key:"allOf",plugin:(e,t,n,r,o)=>{if(o.meta&&o.meta.$$ref)return;const s=n.slice(0,-1);if(ye(s))return;if(!Array.isArray(e)){const e=new TypeError("allOf must be an array");return e.fullPath=n,e}let i=!1,a=o.value;if(s.forEach((e=>{a&&(a=a[e])})),a=f()({},a),0===Object.keys(a).length)return;delete a.allOf;const l=[];return l.push(r.replace(s,{})),e.forEach(((e,t)=>{if(!r.isObject(e)){if(i)return null;i=!0;const e=new TypeError("Elements in allOf must be objects");return e.fullPath=n,l.push(e)}l.push(r.mergeDeep(s,e));const o=function(e,t){let{specmap:n,getBaseUrlForNodePath:r=(e=>n.getContext([...t,...e]).baseDoc),targetKeys:o=["$ref","$$ref"]}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const s=[];return he()(e).forEach((function(){if(o.includes(this.key)&&"string"==typeof this.node){const e=this.path,o=t.concat(this.path),i=ve(this.node,r(e));s.push(n.replace(o,i))}})),s}(e,n.slice(0,-1),{getBaseUrlForNodePath:e=>r.getContext([...n,t,...e]).baseDoc,specmap:r});l.push(...o)})),a.example&&l.push(r.remove([].concat(s,"example"))),l.push(r.mergeDeep(s,a)),a.$$ref||l.push(r.remove([].concat(s,"$$ref"))),l}},$e={key:"parameters",plugin:(e,t,n,r)=>{if(Array.isArray(e)&&e.length){const t=Object.assign([],e),o=n.slice(0,-1),s=f()({},J.getIn(r.spec,o));for(let o=0;o{const o=f()({},e);for(const t in e)try{o[t].default=r.modelPropertyMacro(o[t])}catch(e){const t=new Error(e);return t.fullPath=n,t}return J.replace(n,o)}};class Ue{constructor(e){this.root=ze(e||{})}set(e,t){const n=this.getParent(e,!0);if(!n)return void Ve(this.root,t,null);const r=e[e.length-1],{children:o}=n;o[r]?Ve(o[r],t,n):o[r]=ze(t,n)}get(e){if((e=e||[]).length<1)return this.root.value;let t,n,r=this.root;for(let o=0;o{if(!e)return e;const{children:r}=e;return!r[n]&&t&&(r[n]=ze(null,e)),r[n]}),this.root)}}function ze(e,t){return Ve({children:{}},e,t)}function Ve(e,t,n){return e.value=t||{},e.protoValue=n?f()(f()({},n.protoValue),e.value):e.value,Object.keys(e.children).forEach((t=>{const n=e.children[t];e.children[t]=Ve(n,n.value,e)})),e}const We=()=>{};class Je{static getPluginName(e){return e.pluginName}static getPatchesOfType(e,t){return e.filter(t)}constructor(e){Object.assign(this,{spec:"",debugLevel:"info",plugins:[],pluginHistory:{},errors:[],mutations:[],promisedPatches:[],state:{},patches:[],context:{},contextTree:new Ue,showDebug:!1,allPatches:[],pluginProp:"specMap",libMethods:Object.assign(Object.create(this),J,{getInstance:()=>this}),allowMetaPatches:!1},e),this.get=this._get.bind(this),this.getContext=this._getContext.bind(this),this.hasRun=this._hasRun.bind(this),this.wrappedPlugins=this.plugins.map(this.wrapPlugin.bind(this)).filter(J.isFunction),this.patches.push(J.add([],this.spec)),this.patches.push(J.context([],this.context)),this.updatePatches(this.patches)}debug(e){if(this.debugLevel===e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r1?t-1:0),r=1;r!Array.isArray(e)||e.every(((e,n)=>e===t[n]));return function*(r,o){const s={};for(const e of r.filter(J.isAdditiveMutation))yield*i(e.value,e.path,e);function*i(r,a,l){if(J.isObject(r)){const c=a.length-1,u=a[c],p=a.indexOf("properties"),h="properties"===u&&c===p,f=o.allowMetaPatches&&s[r.$$ref];for(const c of Object.keys(r)){const u=r[c],p=a.concat(c),d=J.isObject(u),m=r.$$ref;if(f||d&&(o.allowMetaPatches&&m&&(s[m]=!0),yield*i(u,p,l)),!h&&c===e.key){const r=t(n,a);n&&!r||(yield e.plugin(u,c,p,o,l))}}}else e.key===a[a.length-1]&&(yield e.plugin(r,e.key,a,o))}}}(e)),Object.assign(r.bind(o),{pluginName:e.name||t,isGenerator:J.isGenerator(r)})}nextPlugin(){return this.wrappedPlugins.find((e=>this.getMutationsForPlugin(e).length>0))}nextPromisedPatch(){if(this.promisedPatches.length>0)return Promise.race(this.promisedPatches.map((e=>e.value)))}getPluginHistory(e){const t=this.constructor.getPluginName(e);return this.pluginHistory[t]||[]}getPluginRunCount(e){return this.getPluginHistory(e).length}getPluginHistoryTip(e){const t=this.getPluginHistory(e);return t&&t[t.length-1]||{}}getPluginMutationIndex(e){const t=this.getPluginHistoryTip(e).mutationIndex;return"number"!=typeof t?-1:t}updatePluginHistory(e,t){const n=this.constructor.getPluginName(e);this.pluginHistory[n]=this.pluginHistory[n]||[],this.pluginHistory[n].push(t)}updatePatches(e){J.normalizeArray(e).forEach((e=>{if(e instanceof Error)this.errors.push(e);else try{if(!J.isObject(e))return void this.debug("updatePatches","Got a non-object patch",e);if(this.showDebug&&this.allPatches.push(e),J.isPromise(e.value))return this.promisedPatches.push(e),void this.promisedPatchThen(e);if(J.isContextPatch(e))return void this.setContext(e.path,e.value);J.isMutation(e)&&this.updateMutations(e)}catch(e){console.error(e),this.errors.push(e)}}))}updateMutations(e){"object"==typeof e.value&&!Array.isArray(e.value)&&this.allowMetaPatches&&(e.value=f()({},e.value));const t=J.applyPatch(this.state,e,{allowMetaPatches:this.allowMetaPatches});t&&(this.mutations.push(e),this.state=t)}removePromisedPatch(e){const t=this.promisedPatches.indexOf(e);t<0?this.debug("Tried to remove a promisedPatch that isn't there!"):this.promisedPatches.splice(t,1)}promisedPatchThen(e){return e.value=e.value.then((t=>{const n=f()(f()({},e),{},{value:t});this.removePromisedPatch(e),this.updatePatches(n)})).catch((t=>{this.removePromisedPatch(e),this.updatePatches(t)})),e.value}getMutations(e,t){return e=e||0,"number"!=typeof t&&(t=this.mutations.length),this.mutations.slice(e,t)}getCurrentMutations(){return this.getMutationsForPlugin(this.getCurrentPlugin())}getMutationsForPlugin(e){const t=this.getPluginMutationIndex(e);return this.getMutations(t+1)}getCurrentPlugin(){return this.currentPlugin}getLib(){return this.libMethods}_get(e){return J.getIn(this.state,e)}_getContext(e){return this.contextTree.get(e)}setContext(e,t){return this.contextTree.set(e,t)}_hasRun(e){return this.getPluginRunCount(this.getCurrentPlugin())>(e||0)}dispatch(){const e=this,t=this.nextPlugin();if(!t){const e=this.nextPromisedPatch();if(e)return e.then((()=>this.dispatch())).catch((()=>this.dispatch()));const t={spec:this.state,errors:this.errors};return this.showDebug&&(t.patches=this.allPatches),Promise.resolve(t)}if(e.pluginCount=e.pluginCount||{},e.pluginCount[t]=(e.pluginCount[t]||0)+1,e.pluginCount[t]>100)return Promise.resolve({spec:e.state,errors:e.errors.concat(new Error("We've reached a hard limit of 100 plugin runs"))});if(t!==this.currentPlugin&&this.promisedPatches.length){const e=this.promisedPatches.map((e=>e.value));return Promise.all(e.map((e=>e.then(We,We)))).then((()=>this.dispatch()))}return function(){e.currentPlugin=t;const r=e.getCurrentMutations(),o=e.mutations.length-1;try{if(t.isGenerator)for(const o of t(r,e.getLib()))n(o);else{n(t(r,e.getLib()))}}catch(e){console.error(e),n([Object.assign(Object.create(e),{plugin:t})])}finally{e.updatePluginHistory(t,{mutationIndex:o})}return e.dispatch()}();function n(n){n&&(n=J.fullyNormalizeArray(n),e.updatePatches(n,t))}}}const Ke={refs:ke,allOf:Be,parameters:$e,properties:qe};var He=n(32454);function Ge(e){const{spec:t}=e,{paths:n}=t,r={};if(!n||t.$$normalized)return e;for(const e in n){const o=n[e];if(null==o||!["object","function"].includes(typeof o))continue;const s=o.parameters;for(const n in o){const i=o[n];if(null==i||!["object","function"].includes(typeof i))continue;const a=(0,He.Z)(i,e,n);if(a){r[a]?r[a].push(i):r[a]=[i];const e=r[a];if(e.length>1)e.forEach(((e,t)=>{e.__originalOperationId=e.__originalOperationId||e.operationId,e.operationId=`${a}${t+1}`}));else if(void 0!==i.operationId){const t=e[0];t.__originalOperationId=t.__originalOperationId||i.operationId,t.operationId=a}}if("parameters"!==n){const e=[],n={};for(const r in t)"produces"!==r&&"consumes"!==r&&"security"!==r||(n[r]=t[r],e.push(n));if(s&&(n.parameters=s,e.push(n)),e.length)for(const t of e)for(const e in t)if(i[e]){if("parameters"===e)for(const n of t[e]){i[e].some((e=>e.name&&e.name===n.name||e.$ref&&e.$ref===n.$ref||e.$$ref&&e.$$ref===n.$$ref||e===n))||i[e].push(n)}}else i[e]=t[e]}}}return t.$$normalized=!0,e}function Ze(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{requestInterceptor:n,responseInterceptor:r}=t,o=e.withCredentials?"include":"same-origin";return t=>e({url:t,loadSpec:!0,requestInterceptor:n,responseInterceptor:r,headers:{Accept:be},credentials:o}).then((e=>e.body))}var Ye=n(80129),Xe=n.n(Ye);const Qe="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:window,{FormData:et,Blob:tt,File:nt}=Qe,rt=e=>":/?#[]@!$&'()*+,;=".indexOf(e)>-1,ot=e=>/^[a-z0-9\-._~]+$/i.test(e);function st(e){let{escape:t}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0;return"number"==typeof e&&(e=e.toString()),"string"==typeof e&&e.length&&t?n?JSON.parse(e):[...e].map((e=>{if(ot(e))return e;if(rt(e)&&"unsafe"===t)return e;const n=new TextEncoder;return Array.from(n.encode(e)).map((e=>`0${e.toString(16).toUpperCase()}`.slice(-2))).map((e=>`%${e}`)).join("")})).join(""):e}function it(e){const{value:t}=e;return Array.isArray(t)?function(e){let{key:t,value:n,style:r,explode:o,escape:s}=e;const i=e=>st(e,{escape:s});if("simple"===r)return n.map((e=>i(e))).join(",");if("label"===r)return`.${n.map((e=>i(e))).join(".")}`;if("matrix"===r)return n.map((e=>i(e))).reduce(((e,n)=>!e||o?`${e||""};${t}=${n}`:`${e},${n}`),"");if("form"===r){const e=o?`&${t}=`:",";return n.map((e=>i(e))).join(e)}if("spaceDelimited"===r){const e=o?`${t}=`:"";return n.map((e=>i(e))).join(` ${e}`)}if("pipeDelimited"===r){const e=o?`${t}=`:"";return n.map((e=>i(e))).join(`|${e}`)}return}(e):"object"==typeof t?function(e){let{key:t,value:n,style:r,explode:o,escape:s}=e;const i=e=>st(e,{escape:s}),a=Object.keys(n);if("simple"===r)return a.reduce(((e,t)=>{const r=i(n[t]);return`${e?`${e},`:""}${t}${o?"=":","}${r}`}),"");if("label"===r)return a.reduce(((e,t)=>{const r=i(n[t]);return`${e?`${e}.`:"."}${t}${o?"=":"."}${r}`}),"");if("matrix"===r&&o)return a.reduce(((e,t)=>`${e?`${e};`:";"}${t}=${i(n[t])}`),"");if("matrix"===r)return a.reduce(((e,r)=>{const o=i(n[r]);return`${e?`${e},`:`;${t}=`}${r},${o}`}),"");if("form"===r)return a.reduce(((e,t)=>{const r=i(n[t]);return`${e?`${e}${o?"&":","}`:""}${t}${o?"=":","}${r}`}),"");return}(e):function(e){let{key:t,value:n,style:r,escape:o}=e;const s=e=>st(e,{escape:o});if("simple"===r)return s(n);if("label"===r)return`.${s(n)}`;if("matrix"===r)return`;${t}=${s(n)}`;if("form"===r)return s(n);if("deepObject"===r)return s(n,{},!0);return}(e)}const at=(e,t)=>{t.body=e},lt={serializeRes:pt,mergeInQueryOrForm:wt};async function ct(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};"object"==typeof e&&(t=e,e=t.url),t.headers=t.headers||{},lt.mergeInQueryOrForm(t),t.headers&&Object.keys(t.headers).forEach((e=>{const n=t.headers[e];"string"==typeof n&&(t.headers[e]=n.replace(/\n+/g," "))})),t.requestInterceptor&&(t=await t.requestInterceptor(t)||t);const n=t.headers["content-type"]||t.headers["Content-Type"];let r;/multipart\/form-data/i.test(n)&&t.body instanceof et&&(delete t.headers["content-type"],delete t.headers["Content-Type"]);try{r=await(t.userFetch||fetch)(t.url,t),r=await lt.serializeRes(r,e,t),t.responseInterceptor&&(r=await t.responseInterceptor(r)||r)}catch(e){if(!r)throw e;const t=new Error(r.statusText||`response status is ${r.status}`);throw t.status=r.status,t.statusCode=r.status,t.responseError=e,t}if(!r.ok){const e=new Error(r.statusText||`response status is ${r.status}`);throw e.status=r.status,e.statusCode=r.status,e.response=r,e}return r}const ut=function(){return/(json|xml|yaml|text)\b/.test(arguments.length>0&&void 0!==arguments[0]?arguments[0]:"")};function pt(e,t){let{loadSpec:n=!1}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const r={ok:e.ok,url:e.url||t,status:e.status,statusText:e.statusText,headers:ht(e.headers)},o=r.headers["content-type"],s=n||ut(o);return(s?e.text:e.blob||e.buffer).call(e).then((e=>{if(r.text=e,r.data=e,s)try{const t=function(e,t){return t&&(0===t.indexOf("application/json")||t.indexOf("+json")>0)?JSON.parse(e):le.ZP.load(e)}(e,o);r.body=t,r.obj=t}catch(e){r.parseError=e}return r}))}function ht(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return"function"!=typeof e.entries?{}:Array.from(e.entries()).reduce(((e,t)=>{let[n,r]=t;return e[n]=function(e){return e.includes(", ")?e.split(", "):e}(r),e}),{})}function ft(e,t){return t||"undefined"==typeof navigator||(t=navigator),t&&"ReactNative"===t.product?!(!e||"object"!=typeof e||"string"!=typeof e.uri):void 0!==nt&&e instanceof nt||(void 0!==tt&&e instanceof tt||(!!ArrayBuffer.isView(e)||null!==e&&"object"==typeof e&&"function"==typeof e.pipe))}function dt(e,t){return Array.isArray(e)&&e.some((e=>ft(e,t)))}const mt={form:",",spaceDelimited:"%20",pipeDelimited:"|"},gt={csv:",",ssv:"%20",tsv:"%09",pipes:"|"};function yt(e,t){let n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const{collectionFormat:r,allowEmptyValue:o,serializationOption:s,encoding:i}=t,a="object"!=typeof t||Array.isArray(t)?t:t.value,l=n?e=>e.toString():e=>encodeURIComponent(e),c=l(e);if(void 0===a&&o)return[[c,""]];if(ft(a)||dt(a))return[[c,a]];if(s)return vt(e,a,n,s);if(i){if([typeof i.style,typeof i.explode,typeof i.allowReserved].some((e=>"undefined"!==e))){const{style:t,explode:r,allowReserved:o}=i;return vt(e,a,n,{style:t,explode:r,allowReserved:o})}if(i.contentType){if("application/json"===i.contentType){return[[c,l("string"==typeof a?a:JSON.stringify(a))]]}return[[c,l(a.toString())]]}return"object"!=typeof a?[[c,l(a)]]:Array.isArray(a)&&a.every((e=>"object"!=typeof e))?[[c,a.map(l).join(",")]]:[[c,l(JSON.stringify(a))]]}return"object"!=typeof a?[[c,l(a)]]:Array.isArray(a)?"multi"===r?[[c,a.map(l)]]:[[c,a.map(l).join(gt[r||"csv"])]]:[[c,""]]}function vt(e,t,n,r){const o=r.style||"form",s=void 0===r.explode?"form"===o:r.explode,i=!n&&(r&&r.allowReserved?"unsafe":"reserved"),a=e=>st(e,{escape:i}),l=n?e=>e:e=>st(e,{escape:i});return"object"!=typeof t?[[l(e),a(t)]]:Array.isArray(t)?s?[[l(e),t.map(a)]]:[[l(e),t.map(a).join(mt[o])]]:"deepObject"===o?Object.keys(t).map((n=>[l(`${e}[${n}]`),a(t[n])])):s?Object.keys(t).map((e=>[l(e),a(t[e])])):[[l(e),Object.keys(t).map((e=>[`${l(e)},${a(t[e])}`])).join(",")]]}function bt(e){const t=Object.keys(e).reduce(((t,n)=>{for(const[r,o]of yt(n,e[n]))t[r]=o;return t}),{});return Xe().stringify(t,{encode:!1,indices:!1})||""}function wt(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const{url:t="",query:n,form:r}=e;if(r){const t=Object.keys(r).some((e=>{const{value:t}=r[e];return ft(t)||dt(t)})),n=e.headers["content-type"]||e.headers["Content-Type"];if(t||/multipart\/form-data/i.test(n)){const t=(o=e.form,Object.entries(o).reduce(((e,t)=>{let[n,r]=t;for(const[t,o]of yt(n,r,!0))if(Array.isArray(o))for(const n of o)if(ArrayBuffer.isView(n)){const r=new tt([n]);e.append(t,r)}else e.append(t,n);else if(ArrayBuffer.isView(o)){const n=new tt([o]);e.append(t,n)}else e.append(t,o);return e}),new et));at(t,e)}else e.body=bt(r);delete e.form}var o;if(n){const[r,o]=t.split("?");let s="";if(o){const e=Xe().parse(o);Object.keys(n).forEach((t=>delete e[t])),s=Xe().stringify(e,{encode:!0})}const i=function(){for(var e=arguments.length,t=new Array(e),n=0;ne)).join("&");return r?`?${r}`:""}(s,bt(n));e.url=r+i,delete e.query}return e}const Et=e=>{const{baseDoc:t,url:n}=e;return t||n||""},xt=e=>{const{fetch:t,http:n}=e;return t||n||ct};async function St(e){const{spec:t,mode:n,allowMetaPatches:r=!0,pathDiscriminator:o,modelPropertyMacro:s,parameterMacro:i,requestInterceptor:a,responseInterceptor:l,skipNormalization:c,useCircularStructures:u}=e,p=Et(e),h=xt(e);return function(e){p&&(Ke.refs.docCache[p]=e);Ke.refs.fetchJSON=Ze(h,{requestInterceptor:a,responseInterceptor:l});const t=[Ke.refs];"function"==typeof i&&t.push(Ke.parameters);"function"==typeof s&&t.push(Ke.properties);"strict"!==n&&t.push(Ke.allOf);return(f={spec:e,context:{baseDoc:p},plugins:t,allowMetaPatches:r,pathDiscriminator:o,parameterMacro:i,modelPropertyMacro:s,useCircularStructures:u},new Je(f).dispatch()).then(c?async e=>e:Ge);var f}(t)}const _t={name:"generic",match:()=>!0,normalize(e){let{spec:t}=e;const{spec:n}=Ge({spec:t});return n},resolve:async e=>St(e)};const jt=e=>{try{const{openapi:t}=e;return"string"==typeof t&&/^3\.0\.([0123])(?:-rc[012])?$/.test(t)}catch{return!1}},Ot=e=>{try{const{openapi:t}=e;return"string"==typeof t&&/^3\.1\.(?:[1-9]\d*|0)$/.test(t)}catch{return!1}},kt=e=>jt(e)||Ot(e),At={name:"openapi-2",match(e){let{spec:t}=e;return(e=>{try{const{swagger:t}=e;return"2.0"===t}catch{return!1}})(t)},normalize(e){let{spec:t}=e;const{spec:n}=Ge({spec:t});return n},resolve:async e=>async function(e){return St(e)}(e)};const Ct={name:"openapi-3-0",match(e){let{spec:t}=e;return jt(t)},normalize(e){let{spec:t}=e;const{spec:n}=Ge({spec:t});return n},resolve:async e=>async function(e){return St(e)}(e)};var Pt=n(43500);class Nt extends Pt.RP{constructor(e,t,n){super(e,t,n),this.element="annotation"}get code(){return this.attributes.get("code")}set code(e){this.attributes.set("code",e)}}const It=Nt;class Tt extends Pt.RP{constructor(e,t,n){super(e,t,n),this.element="comment"}}const Rt=Tt;const Mt=function(){return!1};const Dt=function(){return!0};function Ft(e){return null!=e&&"object"==typeof e&&!0===e["@@functional/placeholder"]}function Lt(e){return function t(n){return 0===arguments.length||Ft(n)?t:e.apply(this,arguments)}}function Bt(e){return function t(n,r){switch(arguments.length){case 0:return t;case 1:return Ft(n)?t:Lt((function(t){return e(n,t)}));default:return Ft(n)&&Ft(r)?t:Ft(n)?Lt((function(t){return e(t,r)})):Ft(r)?Lt((function(t){return e(n,t)})):e(n,r)}}}const $t=Array.isArray||function(e){return null!=e&&e.length>=0&&"[object Array]"===Object.prototype.toString.call(e)};function qt(e,t,n){return function(){if(0===arguments.length)return n();var r=arguments[arguments.length-1];if(!$t(r)){for(var o=0;o=arguments.length)?a=t[i]:(a=arguments[o],o+=1),r[i]=a,Ft(a)||(s-=1),i+=1}return s<=0?n.apply(this,r):Ht(s,Gt(e,r,n))}}const Zt=Bt((function(e,t){return 1===e?Lt(t):Ht(e,Gt(e,[],t))}));function Yt(e){for(var t,n=[];!(t=e.next()).done;)n.push(t.value);return n}function Xt(e,t,n){for(var r=0,o=n.length;r=0;)Qt(t=on[n],e)&&!an(r,t)&&(r[r.length]=t),n-=1;return r})):Lt((function(e){return Object(e)!==e?[]:Object.keys(e)}));const cn=Lt((function(e){return null===e?"Null":void 0===e?"Undefined":Object.prototype.toString.call(e).slice(8,-1)}));function un(e,t,n,r){var o=Yt(e);function s(e,t){return pn(e,t,n.slice(),r.slice())}return!Xt((function(e,t){return!Xt(s,t,e)}),Yt(t),o)}function pn(e,t,n,r){if(en(e,t))return!0;var o,s,i=cn(e);if(i!==cn(t))return!1;if("function"==typeof e["fantasy-land/equals"]||"function"==typeof t["fantasy-land/equals"])return"function"==typeof e["fantasy-land/equals"]&&e["fantasy-land/equals"](t)&&"function"==typeof t["fantasy-land/equals"]&&t["fantasy-land/equals"](e);if("function"==typeof e.equals||"function"==typeof t.equals)return"function"==typeof e.equals&&e.equals(t)&&"function"==typeof t.equals&&t.equals(e);switch(i){case"Arguments":case"Array":case"Object":if("function"==typeof e.constructor&&"Promise"===(o=e.constructor,null==(s=String(o).match(/^function (\w*)/))?"":s[1]))return e===t;break;case"Boolean":case"Number":case"String":if(typeof e!=typeof t||!en(e.valueOf(),t.valueOf()))return!1;break;case"Date":if(!en(e.valueOf(),t.valueOf()))return!1;break;case"Error":return e.name===t.name&&e.message===t.message;case"RegExp":if(e.source!==t.source||e.global!==t.global||e.ignoreCase!==t.ignoreCase||e.multiline!==t.multiline||e.sticky!==t.sticky||e.unicode!==t.unicode)return!1}for(var a=n.length-1;a>=0;){if(n[a]===e)return r[a]===t;a-=1}switch(i){case"Map":return e.size===t.size&&un(e.entries(),t.entries(),n.concat([e]),r.concat([t]));case"Set":return e.size===t.size&&un(e.values(),t.values(),n.concat([e]),r.concat([t]));case"Arguments":case"Array":case"Object":case"Boolean":case"Number":case"String":case"Date":case"Error":case"RegExp":case"Int8Array":case"Uint8Array":case"Uint8ClampedArray":case"Int16Array":case"Uint16Array":case"Int32Array":case"Uint32Array":case"Float32Array":case"Float64Array":case"ArrayBuffer":break;default:return!1}var l=ln(e);if(l.length!==ln(t).length)return!1;var c=n.concat([e]),u=r.concat([t]);for(a=l.length-1;a>=0;){var p=l[a];if(!Qt(p,t)||!pn(t[p],e[p],c,u))return!1;a-=1}return!0}const hn=Bt((function(e,t){return pn(e,t,[],[])}));function fn(e,t){return function(e,t,n){var r,o;if("function"==typeof e.indexOf)switch(typeof t){case"number":if(0===t){for(r=1/t;n=0}function dn(e,t){for(var n=0,r=t.length,o=Array(r);n":jn(n,r)},r=function(e,t){return dn((function(t){return mn(t)+": "+n(e[t])}),t.slice().sort())};switch(Object.prototype.toString.call(e)){case"[object Arguments]":return"(function() { return arguments; }("+dn(n,e).join(", ")+"))";case"[object Array]":return"["+dn(n,e).concat(r(e,_n((function(e){return/^\d+$/.test(e)}),ln(e)))).join(", ")+"]";case"[object Boolean]":return"object"==typeof e?"new Boolean("+n(e.valueOf())+")":e.toString();case"[object Date]":return"new Date("+(isNaN(e.valueOf())?n(NaN):mn(yn(e)))+")";case"[object Map]":return"new Map("+n(Array.from(e))+")";case"[object Null]":return"null";case"[object Number]":return"object"==typeof e?"new Number("+n(e.valueOf())+")":1/e==-1/0?"-0":e.toString(10);case"[object Set]":return"new Set("+n(Array.from(e).sort())+")";case"[object String]":return"object"==typeof e?"new String("+n(e.valueOf())+")":mn(e);case"[object Undefined]":return"undefined";default:if("function"==typeof e.toString){var o=e.toString();if("[object Object]"!==o)return o}return"{"+r(e,ln(e)).join(", ")+"}"}}const On=Lt((function(e){return jn(e,[])}));const kn=Bt((function(e,t){if(e===t)return t;function n(e,t){if(e>t!=t>e)return t>e?t:e}var r=n(e,t);if(void 0!==r)return r;var o=n(typeof e,typeof t);if(void 0!==o)return o===typeof e?e:t;var s=On(e),i=n(s,On(t));return void 0!==i&&i===s?e:t}));var An=function(){function e(e,t){this.xf=t,this.f=e}return e.prototype["@@transducer/init"]=zt,e.prototype["@@transducer/result"]=Vt,e.prototype["@@transducer/step"]=function(e,t){return this.xf["@@transducer/step"](e,this.f(t))},e}();const Cn=Bt(qt(["fantasy-land/map","map"],(function(e){return function(t){return new An(e,t)}}),(function(e,t){switch(Object.prototype.toString.call(t)){case"[object Function]":return Zt(t.length,(function(){return e.call(this,t.apply(this,arguments))}));case"[object Object]":return bn((function(n,r){return n[r]=e(t[r]),n}),{},ln(t));default:return dn(e,t)}}))),Pn=Number.isInteger||function(e){return e<<0===e};function Nn(e){return"[object String]"===Object.prototype.toString.call(e)}const In=Bt((function(e,t){var n=e<0?t.length+e:e;return Nn(t)?t.charAt(n):t[n]}));const Tn=Bt((function(e,t){if(null!=t)return Pn(e)?In(e,t):t[e]}));const Rn=Bt((function(e,t){return Cn(Tn(e),t)}));function Mn(e){return function t(n,r,o){switch(arguments.length){case 0:return t;case 1:return Ft(n)?t:Bt((function(t,r){return e(n,t,r)}));case 2:return Ft(n)&&Ft(r)?t:Ft(n)?Bt((function(t,n){return e(t,r,n)})):Ft(r)?Bt((function(t,r){return e(n,t,r)})):Lt((function(t){return e(n,r,t)}));default:return Ft(n)&&Ft(r)&&Ft(o)?t:Ft(n)&&Ft(r)?Bt((function(t,n){return e(t,n,o)})):Ft(n)&&Ft(o)?Bt((function(t,n){return e(t,r,n)})):Ft(r)&&Ft(o)?Bt((function(t,r){return e(n,t,r)})):Ft(n)?Lt((function(t){return e(t,r,o)})):Ft(r)?Lt((function(t){return e(n,t,o)})):Ft(o)?Lt((function(t){return e(n,r,t)})):e(n,r,o)}}}const Dn=Lt((function(e){return!!$t(e)||!!e&&("object"==typeof e&&(!Nn(e)&&(0===e.length||e.length>0&&(e.hasOwnProperty(0)&&e.hasOwnProperty(e.length-1)))))}));var Fn="undefined"!=typeof Symbol?Symbol.iterator:"@@iterator";function Ln(e,t,n){return function(r,o,s){if(Dn(s))return e(r,o,s);if(null==s)return o;if("function"==typeof s["fantasy-land/reduce"])return t(r,o,s,"fantasy-land/reduce");if(null!=s[Fn])return n(r,o,s[Fn]());if("function"==typeof s.next)return n(r,o,s);if("function"==typeof s.reduce)return t(r,o,s,"reduce");throw new TypeError("reduce: list must be array or iterable")}}function Bn(e,t,n){for(var r=0,o=n.length;r1){var s=!rr(r)&&Qt(o,r)&&"object"==typeof r[o]?r[o]:Pn(t[1])?[]:{};n=e(Array.prototype.slice.call(t,1),n,s)}return function(e,t,n){if(Pn(e)&&$t(n)){var r=[].concat(n);return r[e]=t,r}var o={};for(var s in n)o[s]=n[s];return o[e]=t,o}(o,n,r)}));function sr(e){var t=Object.prototype.toString.call(e);return"[object Function]"===t||"[object AsyncFunction]"===t||"[object GeneratorFunction]"===t||"[object AsyncGeneratorFunction]"===t}const ir=Bt((function(e,t){return e&&t}));const ar=Bt((function(e,t){var n=Zt(e,t);return Zt(e,(function(){return bn(Qn,Cn(n,arguments[0]),Array.prototype.slice.call(arguments,1))}))}));const lr=Lt((function(e){return ar(e.length,e)}));const cr=Bt((function(e,t){return sr(e)?function(){return e.apply(this,arguments)&&t.apply(this,arguments)}:lr(ir)(e,t)}));const ur=Lt((function(e){return function(t,n){return e(t,n)?-1:e(n,t)?1:0}}));const pr=lr(Lt((function(e){return!e})));function hr(e,t){return function(){return t.call(this,e.apply(this,arguments))}}function fr(e,t){return function(){var n=arguments.length;if(0===n)return t();var r=arguments[n-1];return $t(r)||"function"!=typeof r[e]?t.apply(this,arguments):r[e].apply(r,Array.prototype.slice.call(arguments,0,n-1))}}const dr=Mn(fr("slice",(function(e,t,n){return Array.prototype.slice.call(n,e,t)})));const mr=Lt(fr("tail",dr(1,1/0)));function gr(){if(0===arguments.length)throw new Error("pipe requires at least one argument");return Ht(arguments[0].length,Jn(hr,arguments[0],mr(arguments)))}var yr=Bt((function(e,t){return Zt(Jn(kn,0,Rn("length",t)),(function(){var n=arguments,r=this;return e.apply(r,dn((function(e){return e.apply(r,n)}),t))}))}));const vr=yr;function br(e){return new RegExp(e.source,e.flags?e.flags:(e.global?"g":"")+(e.ignoreCase?"i":"")+(e.multiline?"m":"")+(e.sticky?"y":"")+(e.unicode?"u":"")+(e.dotAll?"s":""))}function wr(e,t,n){if(n||(n=new Er),function(e){var t=typeof e;return null==e||"object"!=t&&"function"!=t}(e))return e;var r=function(r){var o=n.get(e);if(o)return o;for(var s in n.set(e,r),e)Object.prototype.hasOwnProperty.call(e,s)&&(r[s]=t?wr(e[s],!0,n):e[s]);return r};switch(cn(e)){case"Object":return r(Object.create(Object.getPrototypeOf(e)));case"Array":return r([]);case"Date":return new Date(e.valueOf());case"RegExp":return br(e);case"Int8Array":case"Uint8Array":case"Uint8ClampedArray":case"Int16Array":case"Uint16Array":case"Int32Array":case"Uint32Array":case"Float32Array":case"Float64Array":case"BigInt64Array":case"BigUint64Array":return e.slice();default:return e}}var Er=function(){function e(){this.map={},this.length=0}return e.prototype.set=function(e,t){const n=this.hash(e);let r=this.map[n];r||(this.map[n]=r=[]),r.push([e,t]),this.length+=1},e.prototype.hash=function(e){let t=[];for(var n in e)t.push(Object.prototype.toString.call(e[n]));return t.join()},e.prototype.get=function(e){if(this.length<=180){for(const t in this.map){const n=this.map[t];for(let t=0;t=0&&this.i>=this.n?Ut(n):n},e}();function Ir(e){return function(t){return new Nr(e,t)}}const Tr=Bt(qt(["take"],Ir,(function(e,t){return dr(0,e<0?1/0:e,t)})));function Rr(e,t){for(var n=t.length-1;n>=0&&e(t[n]);)n-=1;return dr(0,n+1,t)}var Mr=function(){function e(e,t){this.f=e,this.retained=[],this.xf=t}return e.prototype["@@transducer/init"]=zt,e.prototype["@@transducer/result"]=function(e){return this.retained=null,this.xf["@@transducer/result"](e)},e.prototype["@@transducer/step"]=function(e,t){return this.f(t)?this.retain(e,t):this.flush(e,t)},e.prototype.flush=function(e,t){return e=zn(this.xf,e,this.retained),this.retained=[],this.xf["@@transducer/step"](e,t)},e.prototype.retain=function(e,t){return this.retained.push(t),e},e}();function Dr(e){return function(t){return new Mr(e,t)}}const Fr=Bt(qt([],Dr,Rr));var Lr=function(){function e(e,t){this.xf=t,this.f=e}return e.prototype["@@transducer/init"]=zt,e.prototype["@@transducer/result"]=Vt,e.prototype["@@transducer/step"]=function(e,t){if(this.f){if(this.f(t))return e;this.f=null}return this.xf["@@transducer/step"](e,t)},e}();function Br(e){return function(t){return new Lr(e,t)}}const $r=Bt(qt(["dropWhile"],Br,(function(e,t){for(var n=0,r=t.length;ne.classes.contains("api"))).first}get results(){return this.children.filter((e=>e.classes.contains("result")))}get result(){return this.results.first}get annotations(){return this.children.filter((e=>"annotation"===e.element))}get warnings(){return this.children.filter((e=>"annotation"===e.element&&e.classes.contains("warning")))}get errors(){return this.children.filter((e=>"annotation"===e.element&&e.classes.contains("error")))}get isEmpty(){return this.children.reject((e=>"annotation"===e.element)).isEmpty}replaceResult(e){const{result:t}=this;if(qo(t))return!1;const n=this.content.findIndex((e=>e===t));return-1!==n&&(this.content[n]=e,!0)}}const zo=Uo;class Vo extends Pt.ON{constructor(e,t,n){super(e,t,n),this.element="sourceMap"}get positionStart(){return this.children.filter((e=>e.classes.contains("position"))).get(0)}get positionEnd(){return this.children.filter((e=>e.classes.contains("position"))).get(1)}set position(e){if(null===e)return;const t=new Pt.ON([e.start.row,e.start.column,e.start.char]),n=new Pt.ON([e.end.row,e.end.column,e.end.char]);t.classes.push("position"),n.classes.push("position"),this.push(t).push(n)}}const Wo=Vo;var Jo=n(80621),Ko=n(52201),Ho=n(27398);function Go(e){return Go="function"==typeof Ko&&"symbol"==typeof Ho?function(e){return typeof e}:function(e){return e&&"function"==typeof Ko&&e.constructor===Ko&&e!==Ko.prototype?"symbol":typeof e},Go(e)}var Zo=n(26189);function Yo(e){var t=function(e,t){if("object"!==Go(e)||null===e)return e;var n=e[Zo];if(void 0!==n){var r=n.call(e,t||"default");if("object"!==Go(r))return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"===Go(t)?t:String(t)}function Xo(e,t,n){return(t=Yo(t))in e?Jo(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}const Qo=Zt(1,gr(cn,Xr("GeneratorFunction")));const es=Zt(1,gr(cn,Xr("AsyncFunction")));const ts=Gn([gr(cn,Xr("Function")),Qo,es]);const ns=pr(ts);const rs=Zt(1,ts(Array.isArray)?Array.isArray:gr(cn,Xr("Array")));const os=cr(rs,so);var ss=Zt(3,(function(e,t,n){var r=uo(e,n),o=uo(ro(e),n);if(!ns(r)&&!os(e)){var s=$n(r,o);return er(s,t)}}));const is=ss;const as=Wr(no),ls=(e,t)=>"function"==typeof(null==t?void 0:t[e]),cs=e=>null!=e&&Object.prototype.hasOwnProperty.call(e,"_storedElement")&&Object.prototype.hasOwnProperty.call(e,"_content"),us=(e,t)=>{var n;return(null==t||null===(n=t.primitive)||void 0===n?void 0:n.call(t))===e},ps=(e,t)=>{var n,r;return(null==t||null===(n=t.classes)||void 0===n||null===(r=n.includes)||void 0===r?void 0:r.call(n,e))||!1},hs=(e,t)=>(null==t?void 0:t.element)===e,fs=e=>e({hasMethod:ls,hasBasicElementProps:cs,primitiveEq:us,isElementType:hs,hasClass:ps}),ds=fs((({hasBasicElementProps:e,primitiveEq:t})=>n=>n instanceof Pt.W_||e(n)&&t(void 0,n))),ms=fs((({hasBasicElementProps:e,primitiveEq:t})=>n=>n instanceof Pt.RP||e(n)&&t("string",n))),gs=fs((({hasBasicElementProps:e,primitiveEq:t})=>n=>n instanceof Pt.VL||e(n)&&t("number",n))),ys=fs((({hasBasicElementProps:e,primitiveEq:t})=>n=>n instanceof Pt.zr||e(n)&&t("null",n))),vs=fs((({hasBasicElementProps:e,primitiveEq:t})=>n=>n instanceof Pt.hh||e(n)&&t("boolean",n))),bs=fs((({hasBasicElementProps:e,primitiveEq:t,hasMethod:n})=>r=>r instanceof Pt.Sb||e(r)&&t("object",r)&&n("keys",r)&&n("values",r)&&n("items",r))),ws=fs((({hasBasicElementProps:e,primitiveEq:t,hasMethod:n})=>r=>r instanceof Pt.ON&&!(r instanceof Pt.Sb)||e(r)&&t("array",r)&&n("push",r)&&n("unshift",r)&&n("map",r)&&n("reduce",r))),Es=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Pt.c6||e(r)&&t("member",r)&&n(void 0,r))),xs=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Pt.EA||e(r)&&t("link",r)&&n(void 0,r))),Ss=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Pt.tK||e(r)&&t("ref",r)&&n(void 0,r))),_s=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof It||e(r)&&t("annotation",r)&&n("array",r))),js=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Rt||e(r)&&t("comment",r)&&n("string",r))),Os=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof zo||e(r)&&t("parseResult",r)&&n("array",r))),ks=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Wo||e(r)&&t("sourceMap",r)&&n("array",r))),As=e=>hs("object",e)||hs("array",e)||hs("boolean",e)||hs("number",e)||hs("string",e)||hs("null",e)||hs("member",e),Cs=e=>{var t,n;return ks(null==e||null===(t=e.meta)||void 0===t||null===(n=t.get)||void 0===n?void 0:n.call(t,"sourceMap"))},Ps=(e,t)=>{if(0===e.length)return!0;const n=t.attributes.get("symbols");return!!ws(n)&&Kt(as(n.toValue()),e)},Ns=(e,t)=>0===e.length||Kt(as(t.classes.toValue()),e);const Is=hn(null);const Ts=pr(Is);function Rs(e){return Rs="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Rs(e)}const Ms=function(e){return"object"===Rs(e)};const Ds=Zt(1,cr(Ts,Ms));var Fs=gr(cn,Xr("Object")),Ls=gr(On,hn(On(Object))),Bs=wo(cr(ts,Ls),["constructor"]);const $s=Zt(1,(function(e){if(!Ds(e)||!Fs(e))return!1;var t=Object.getPrototypeOf(e);return!!Is(t)||Bs(t)}));class qs extends Pt.lS{constructor(){super(),this.register("annotation",It),this.register("comment",Rt),this.register("parseResult",zo),this.register("sourceMap",Wo)}}const Us=new qs,zs=e=>{const t=new qs;return $s(e)&&t.use(e),t},Vs=Us;function Ws(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}const Js=()=>({predicates:function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Ks){var s=Ks(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var Ys=n(43992);const Xs=Zt(1,gr(cn,Xr("String"))),Qs=(e,t,n)=>{const r=e[t];if(null!=r){if(!n&&"function"==typeof r)return r;const e=n?r.leave:r.enter;if("function"==typeof e)return e}else{const r=n?e.leave:e.enter;if(null!=r){if("function"==typeof r)return r;const e=r[t];if("function"==typeof e)return e}}return null},ei={},ti=e=>null==e?void 0:e.type,ni=e=>"string"==typeof ti(e),ri=(e,{visitFnGetter:t=Qs,nodeTypeGetter:n=ti}={})=>{const r=new Array(e.length);return{enter(o,...s){for(let i=0;i{const p=n||{};let h,f,d=Array.isArray(e),m=[e],g=-1,y=[];const v=[],b=[];let w=e;do{g+=1;const e=g===m.length;let n,E;const x=e&&0!==y.length;if(e){if(n=0===b.length?void 0:v.pop(),E=f,f=b.pop(),x){E=d?E.slice():Object.create(Object.getPrototypeOf(E),Object.getOwnPropertyDescriptors(E));let e=0;for(let t=0;t{const p=n||{};let h,f,d=Array.isArray(e),m=[e],g=-1,y=[];const v=[],b=[];let w=e;do{g+=1;const e=g===m.length;let n,E;const x=e&&0!==y.length;if(e){if(n=0===b.length?void 0:v.pop(),E=f,f=b.pop(),x){E=d?E.slice():Object.create(Object.getPrototypeOf(E),Object.getOwnPropertyDescriptors(E));let e=0;for(let t=0;tbs(e)?"ObjectElement":ws(e)?"ArrayElement":Es(e)?"MemberElement":ms(e)?"StringElement":vs(e)?"BooleanElement":gs(e)?"NumberElement":ys(e)?"NullElement":xs(e)?"LinkElement":Ss(e)?"RefElement":void 0,ui=gr(ci,Xs),pi={ObjectElement:["content"],ArrayElement:["content"],MemberElement:["key","value"],StringElement:[],BooleanElement:[],NumberElement:[],NullElement:[],RefElement:[],LinkElement:[],Annotation:[],Comment:[],ParseResultElement:["content"],SourceMap:["content"]},hi=Ys({props:{result:[],predicate:Mt,returnOnTrue:void 0,returnOnFalse:void 0},init({predicate:e=this.predicate,returnOnTrue:t=this.returnOnTrue,returnOnFalse:n=this.returnOnFalse}={}){this.result=[],this.predicate=e,this.returnOnTrue=t,this.returnOnFalse=n},methods:{enter(e){return this.predicate(e)?(this.result.push(e),this.returnOnTrue):this.returnOnFalse}}}),fi=(e,t,n={})=>{let{keyMap:r=pi}=n,o=Zs(n,si);return oi(e,t,li({keyMap:r,nodeTypeGetter:ci,nodePredicate:ui},o))};fi[Symbol.for("nodejs.util.promisify.custom")]=async(e,t,n={})=>{let{keyMap:r=pi}=n,o=Zs(n,ii);return oi[Symbol.for("nodejs.util.promisify.custom")](e,t,li({keyMap:r,nodeTypeGetter:ci,nodePredicate:ui},o))};const di=(e,t,n={})=>{if(0===t.length)return e;const r=So(Js,"toolboxCreator",n),o=So({},"visitorOptions",n),s=So(ci,"nodeTypeGetter",o),i=r(),a=t.map((e=>e(i))),l=ri(a.map(So({},"visitor")),{nodeTypeGetter:s});a.forEach(is(["pre"],[]));const c=fi(e,l,o);return a.forEach(is(["post"],[])),c};function mi(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function gi(e){for(var t=1;t{const r=new t(e);return di(r,n,{toolboxCreator:Js,visitorOptions:{nodeTypeGetter:ci}})},vi=e=>(t,n={})=>yi(t,gi(gi({},n),{},{Type:e}));Pt.Sb.refract=vi(Pt.Sb),Pt.ON.refract=vi(Pt.ON),Pt.RP.refract=vi(Pt.RP),Pt.hh.refract=vi(Pt.hh),Pt.zr.refract=vi(Pt.zr),Pt.VL.refract=vi(Pt.VL),Pt.EA.refract=vi(Pt.EA),Pt.tK.refract=vi(Pt.tK),It.refract=vi(It),Rt.refract=vi(Rt),zo.refract=vi(zo),Wo.refract=vi(Wo);const bi=(e,t=new WeakMap)=>(Es(e)?(t.set(e.key,e),bi(e.key,t),t.set(e.value,e),bi(e.value,t)):e.children.forEach((n=>{t.set(n,e),bi(n,t)})),t),wi=Ys.init((function({element:e}){let t;this.transclude=function(n,r){var o;if(n===e)return r;if(n===r)return e;t=null!==(o=t)&&void 0!==o?o:bi(e);const s=t.get(n);return qo(s)?void 0:(bs(s)?((e,t,n)=>{const r=n.get(e);bs(r)&&(r.content=r.map(((o,s,i)=>i===e?(n.delete(e),n.set(t,r),t):i)))})(n,r,t):ws(s)?((e,t,n)=>{const r=n.get(e);ws(r)&&(r.content=r.map((o=>o===e?(n.delete(e),n.set(t,r),t):o)))})(n,r,t):Es(s)&&((e,t,n)=>{const r=n.get(e);Es(r)&&(r.key===e&&(r.key=t,n.delete(e),n.set(t,r)),r.value===e&&(r.value=t,n.delete(e),n.set(t,r)))})(n,r,t),e)}})),Ei=wi,xi=["keyMap"],Si=["keyMap"];function _i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function ji(e){for(var t=1;t"string"==typeof(null==e?void 0:e.type)?e.type:ci(e),ki=ji({EphemeralObject:["content"],EphemeralArray:["content"]},pi),Ai=(e,t,n={})=>{let{keyMap:r=ki}=n,o=Zs(n,xi);return fi(e,t,ji({keyMap:r,nodeTypeGetter:Oi,nodePredicate:Dt,detectCycles:!1,deleteNodeSymbol:Symbol.for("delete-node"),skipVisitingNodeSymbol:Symbol.for("skip-visiting-node")},o))};Ai[Symbol.for("nodejs.util.promisify.custom")]=async(e,t={})=>{let{keyMap:n=ki}=t,r=Zs(t,Si);return fi[Symbol.for("nodejs.util.promisify.custom")](e,visitor,ji({keyMap:n,nodeTypeGetter:Oi,nodePredicate:Dt,detectCycles:!1,deleteNodeSymbol:Symbol.for("delete-node"),skipVisitingNodeSymbol:Symbol.for("skip-visiting-node")},r))};const Ci=class{constructor(e){Xo(this,"type","EphemeralArray"),Xo(this,"content",[]),Xo(this,"reference",void 0),this.content=e,this.reference=[]}toReference(){return this.reference}toArray(){return this.reference.push(...this.content),this.reference}};const Pi=class{constructor(e){Xo(this,"type","EphemeralObject"),Xo(this,"content",[]),Xo(this,"reference",void 0),this.content=e,this.reference={}}toReference(){return this.reference}toObject(){return Object.assign(this.reference,Object.fromEntries(this.content))}},Ni=Ys.init((function(){const e=new WeakMap;this.BooleanElement=function(e){return e.toValue()},this.NumberElement=function(e){return e.toValue()},this.StringElement=function(e){return e.toValue()},this.NullElement=function(){return null},this.ObjectElement={enter(t){if(e.has(t))return e.get(t).toReference();const n=new Pi(t.content);return e.set(t,n),n}},this.EphemeralObject={leave:e=>e.toObject()},this.MemberElement={enter:e=>[e.key,e.value]},this.ArrayElement={enter(t){if(e.has(t))return e.get(t).toReference();const n=new Ci(t.content);return e.set(t,n),n}},this.EphemeralArray={leave:e=>e.toArray()}})),Ii=(e,t=Vs)=>{if(Xs(e))try{return t.fromRefract(JSON.parse(e))}catch{}return $s(e)&&Hr("element",e)?t.fromRefract(e):t.toElement(e)},Ti=e=>Ai(e,Ni());const Ri=hn("");var Mi=cr(Zt(1,gr(cn,Xr("Number"))),isFinite);var Di=Zt(1,Mi);var Fi=cr(ts(Number.isFinite)?Zt(1,$n(Number.isFinite,Number)):Di,vr(hn,[Math.floor,eo]));var Li=Zt(1,Fi);const Bi=ts(Number.isInteger)?Zt(1,$n(Number.isInteger,Number)):Li;var $i=Or((function(e,t){return gr(Io(""),$r(as(e)),io(""))(t)}));const qi=$i;class Ui extends Error{constructor(e){super(`Invalid $ref pointer "${e}". Pointers must begin with "/"`),this.name=this.constructor.name,this.message=`Invalid $ref pointer "${e}". Pointers must begin with "/"`,"function"==typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error(`Invalid $ref pointer "${e}". Pointers must begin with "/"`).stack}}class zi extends Error{constructor(e){super(e),this.name=this.constructor.name,this.message=e,"function"==typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error(e).stack}}const Vi=gr(Co(/~/g,"~0"),Co(/\//g,"~1"),encodeURIComponent),Wi=gr(Co(/~1/g,"/"),Co(/~0/g,"~"),(e=>{try{return decodeURIComponent(e)}catch{return e}})),Ji=(e,t)=>{const n=(e=>{if(Ri(e))return[];if(!To("/",e))throw new Ui(e);const t=gr(Io("/"),Cn(Wi))(e);return mr(t)})(e);return n.reduce(((e,t)=>{if(bs(e)){if(!e.hasKey(t))throw new zi(`Evaluation failed on token: "${t}"`);return e.get(t)}if(ws(e)){if(!(t in e.content)||!Bi(Number(t)))throw new zi(`Evaluation failed on token: "${t}"`);return e.get(Number(t))}throw new zi(`Evaluation failed on token: "${t}"`)}),t)},Ki=e=>{const t=(e=>{const t=e.indexOf("#");return-1!==t?e.substring(t):"#"})(e);return qi("#",t)};class Hi extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="callback"}}const Gi=Hi;class Zi extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="components"}get schemas(){return this.get("schemas")}set schemas(e){this.set("schemas",e)}get responses(){return this.get("responses")}set responses(e){this.set("responses",e)}get parameters(){return this.get("parameters")}set parameters(e){this.set("parameters",e)}get examples(){return this.get("examples")}set examples(e){this.set("examples",e)}get requestBodies(){return this.get("requestBodies")}set requestBodies(e){this.set("requestBodies",e)}get headers(){return this.get("headers")}set headers(e){this.set("headers",e)}get securitySchemes(){return this.get("securitySchemes")}set securitySchemes(e){this.set("securitySchemes",e)}get links(){return this.get("links")}set links(e){this.set("links",e)}get callbacks(){return this.get("callbacks")}set callbacks(e){this.set("callbacks",e)}}const Yi=Zi;class Xi extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="contact"}get name(){return this.get("name")}set name(e){this.set("name",e)}get url(){return this.get("url")}set url(e){this.set("url",e)}get email(){return this.get("email")}set email(e){this.set("email",e)}}const Qi=Xi;class ea extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="discriminator"}get propertyName(){return this.get("propertyName")}set propertyName(e){this.set("propertyName",e)}get mapping(){return this.get("mapping")}set mapping(e){this.set("mapping",e)}}const ta=ea;class na extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="encoding"}get contentType(){return this.get("contentType")}set contentType(e){this.set("contentType",e)}get headers(){return this.get("headers")}set headers(e){this.set("headers",e)}get style(){return this.get("style")}set style(e){this.set("style",e)}get explode(){return this.get("explode")}set explode(e){this.set("explode",e)}get allowedReserved(){return this.get("allowedReserved")}set allowedReserved(e){this.set("allowedReserved",e)}}const ra=na;class oa extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="example"}get summary(){return this.get("summary")}set summary(e){this.set("summary",e)}get description(){return this.get("description")}set description(e){this.set("description",e)}get value(){return this.get("value")}set value(e){this.set("value",e)}get externalValue(){return this.get("externalValue")}set externalValue(e){this.set("externalValue",e)}}const sa=oa;class ia extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="externalDocumentation"}get description(){return this.get("description")}set description(e){this.set("description",e)}get url(){return this.get("url")}set url(e){this.set("url",e)}}const aa=ia;class la extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="header"}get required(){return this.hasKey("required")?this.get("required"):new Pt.hh(!1)}set required(e){this.set("required",e)}get deprecated(){return this.hasKey("deprecated")?this.get("deprecated"):new Pt.hh(!1)}set deprecated(e){this.set("deprecated",e)}get allowEmptyValue(){return this.get("allowEmptyValue")}set allowEmptyValue(e){this.set("allowEmptyValue",e)}get style(){return this.get("style")}set style(e){this.set("style",e)}get explode(){return this.get("explode")}set explode(e){this.set("explode",e)}get allowReserved(){return this.get("allowReserved")}set allowReserved(e){this.set("allowReserved",e)}get schema(){return this.get("schema")}set schema(e){this.set("schema",e)}get example(){return this.get("example")}set example(e){this.set("example",e)}get examples(){return this.get("examples")}set examples(e){this.set("examples",e)}get contentProp(){return this.get("content")}set contentProp(e){this.set("content",e)}}Object.defineProperty(la.prototype,"description",{get(){return this.get("description")},set(e){this.set("description",e)},enumerable:!0});const ca=la;class ua extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="info",this.classes.push("info")}get title(){return this.get("title")}set title(e){this.set("title",e)}get description(){return this.get("description")}set description(e){this.set("description",e)}get termsOfService(){return this.get("termsOfService")}set termsOfService(e){this.set("termsOfService",e)}get contact(){return this.get("contact")}set contact(e){this.set("contact",e)}get license(){return this.get("license")}set license(e){this.set("license",e)}get version(){return this.get("version")}set version(e){this.set("version",e)}}const pa=ua;class ha extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="license"}get name(){return this.get("name")}set name(e){this.set("name",e)}get url(){return this.get("url")}set url(e){this.set("url",e)}}const fa=ha;class da extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="link"}get operationRef(){return this.get("operationRef")}set operationRef(e){this.set("operationRef",e)}get operationId(){return this.get("operationId")}set operationId(e){this.set("operationId",e)}get operation(){var e,t;return ms(this.operationRef)?null===(e=this.operationRef)||void 0===e?void 0:e.meta.get("operation"):ms(this.operationId)?null===(t=this.operationId)||void 0===t?void 0:t.meta.get("operation"):void 0}set operation(e){this.set("operation",e)}get parameters(){return this.get("parameters")}set parameters(e){this.set("parameters",e)}get requestBody(){return this.get("requestBody")}set requestBody(e){this.set("requestBody",e)}get description(){return this.get("description")}set description(e){this.set("description",e)}get server(){return this.get("server")}set server(e){this.set("server",e)}}const ma=da;class ga extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="mediaType"}get schema(){return this.get("schema")}set schema(e){this.set("schema",e)}get example(){return this.get("example")}set example(e){this.set("example",e)}get examples(){return this.get("examples")}set examples(e){this.set("examples",e)}get encoding(){return this.get("encoding")}set encoding(e){this.set("encoding",e)}}const ya=ga;class va extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="oAuthFlow"}get authorizationUrl(){return this.get("authorizationUrl")}set authorizationUrl(e){this.set("authorizationUrl",e)}get tokenUrl(){return this.get("tokenUrl")}set tokenUrl(e){this.set("tokenUrl",e)}get refreshUrl(){return this.get("refreshUrl")}set refreshUrl(e){this.set("refreshUrl",e)}get scopes(){return this.get("scopes")}set scopes(e){this.set("scopes",e)}}const ba=va;class wa extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="oAuthFlows"}get implicit(){return this.get("implicit")}set implicit(e){this.set("implicit",e)}get password(){return this.get("password")}set password(e){this.set("password",e)}get clientCredentials(){return this.get("clientCredentials")}set clientCredentials(e){this.set("clientCredentials",e)}get authorizationCode(){return this.get("authorizationCode")}set authorizationCode(e){this.set("authorizationCode",e)}}const Ea=wa;class xa extends Pt.RP{constructor(e,t,n){super(e,t,n),this.element="openapi",this.classes.push("spec-version"),this.classes.push("version")}}const Sa=xa;class _a extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="openApi3_0",this.classes.push("api")}get openapi(){return this.get("openapi")}set openapi(e){this.set("openapi",e)}get info(){return this.get("info")}set info(e){this.set("info",e)}get servers(){return this.get("servers")}set servers(e){this.set("servers",e)}get paths(){return this.get("paths")}set paths(e){this.set("paths",e)}get components(){return this.get("components")}set components(e){this.set("components",e)}get security(){return this.get("security")}set security(e){this.set("security",e)}get tags(){return this.get("tags")}set tags(e){this.set("tags",e)}get externalDocs(){return this.get("externalDocs")}set externalDocs(e){this.set("externalDocs",e)}}const ja=_a;class Oa extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="operation"}get tags(){return this.get("tags")}set tags(e){this.set("tags",e)}get summary(){return this.get("summary")}set summary(e){this.set("summary",e)}get description(){return this.get("description")}set description(e){this.set("description",e)}set externalDocs(e){this.set("externalDocs",e)}get externalDocs(){return this.get("externalDocs")}get operationId(){return this.get("operationId")}set operationId(e){this.set("operationId",e)}get parameters(){return this.get("parameters")}set parameters(e){this.set("parameters",e)}get requestBody(){return this.get("requestBody")}set requestBody(e){this.set("requestBody",e)}get responses(){return this.get("responses")}set responses(e){this.set("responses",e)}get callbacks(){return this.get("callbacks")}set callbacks(e){this.set("callbacks",e)}get deprecated(){return this.hasKey("deprecated")?this.get("deprecated"):new Pt.hh(!1)}set deprecated(e){this.set("deprecated",e)}get security(){return this.get("security")}set security(e){this.set("security",e)}get servers(){return this.get("severs")}set servers(e){this.set("servers",e)}}const ka=Oa;class Aa extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="parameter"}get name(){return this.get("name")}set name(e){this.set("name",e)}get in(){return this.get("in")}set in(e){this.set("in",e)}get required(){return this.hasKey("required")?this.get("required"):new Pt.hh(!1)}set required(e){this.set("required",e)}get deprecated(){return this.hasKey("deprecated")?this.get("deprecated"):new Pt.hh(!1)}set deprecated(e){this.set("deprecated",e)}get allowEmptyValue(){return this.get("allowEmptyValue")}set allowEmptyValue(e){this.set("allowEmptyValue",e)}get style(){return this.get("style")}set style(e){this.set("style",e)}get explode(){return this.get("explode")}set explode(e){this.set("explode",e)}get allowReserved(){return this.get("allowReserved")}set allowReserved(e){this.set("allowReserved",e)}get schema(){return this.get("schema")}set schema(e){this.set("schema",e)}get example(){return this.get("example")}set example(e){this.set("example",e)}get examples(){return this.get("examples")}set examples(e){this.set("examples",e)}get contentProp(){return this.get("content")}set contentProp(e){this.set("content",e)}}Object.defineProperty(Aa.prototype,"description",{get(){return this.get("description")},set(e){this.set("description",e)},enumerable:!0});const Ca=Aa;class Pa extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="pathItem"}get $ref(){return this.get("$ref")}set $ref(e){this.set("$ref",e)}get summary(){return this.get("summary")}set summary(e){this.set("summary",e)}get description(){return this.get("description")}set description(e){this.set("description",e)}get GET(){return this.get("get")}set GET(e){this.set("GET",e)}get PUT(){return this.get("put")}set PUT(e){this.set("PUT",e)}get POST(){return this.get("post")}set POST(e){this.set("POST",e)}get DELETE(){return this.get("delete")}set DELETE(e){this.set("DELETE",e)}get OPTIONS(){return this.get("options")}set OPTIONS(e){this.set("OPTIONS",e)}get HEAD(){return this.get("head")}set HEAD(e){this.set("HEAD",e)}get PATCH(){return this.get("patch")}set PATCH(e){this.set("PATCH",e)}get TRACE(){return this.get("trace")}set TRACE(e){this.set("TRACE",e)}get servers(){return this.get("servers")}set servers(e){this.set("servers",e)}get parameters(){return this.get("parameters")}set parameters(e){this.set("parameters",e)}}const Na=Pa;class Ia extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="paths"}}const Ta=Ia;class Ra extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="reference",this.classes.push("openapi-reference")}get $ref(){return this.get("$ref")}set $ref(e){this.set("$ref",e)}}const Ma=Ra;class Da extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="requestBody"}get description(){return this.get("description")}set description(e){this.set("description",e)}get contentProp(){return this.get("content")}set contentProp(e){this.set("content",e)}get required(){return this.hasKey("required")?this.get("required"):new Pt.hh(!1)}set required(e){this.set("required",e)}}const Fa=Da;class La extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="response"}get description(){return this.get("description")}set description(e){this.set("description",e)}get headers(){return this.get("headers")}set headers(e){this.set("headers",e)}get contentProp(){return this.get("content")}set contentProp(e){this.set("content",e)}get links(){return this.get("links")}set links(e){this.set("links",e)}}const Ba=La;class $a extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="responses"}get default(){return this.get("default")}set default(e){this.set("default",e)}}const qa=$a;class Ua extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="JSONSchemaDraft4"}get idProp(){return this.get("id")}set idProp(e){this.set("id",e)}get $schema(){return this.get("$schema")}set $schema(e){this.set("idProp",e)}get multipleOf(){return this.get("multipleOf")}set multipleOf(e){this.set("multipleOf",e)}get maximum(){return this.get("maximum")}set maximum(e){this.set("maximum",e)}get exclusiveMaximum(){return this.get("exclusiveMaximum")}set exclusiveMaximum(e){this.set("exclusiveMaximum",e)}get minimum(){return this.get("minimum")}set minimum(e){this.set("minimum",e)}get exclusiveMinimum(){return this.get("exclusiveMinimum")}set exclusiveMinimum(e){this.set("exclusiveMinimum",e)}get maxLength(){return this.get("maxLength")}set maxLength(e){this.set("maxLength",e)}get minLength(){return this.get("minLength")}set minLength(e){this.set("minLength",e)}get pattern(){return this.get("pattern")}set pattern(e){this.set("pattern",e)}get additionalItems(){return this.get("additionalItems")}set additionalItems(e){this.set("additionalItems",e)}get items(){return this.get("items")}set items(e){this.set("items",e)}get maxItems(){return this.get("maxItems")}set maxItems(e){this.set("maxItems",e)}get minItems(){return this.get("minItems")}set minItems(e){this.set("minItems",e)}get uniqueItems(){return this.get("uniqueItems")}set uniqueItems(e){this.set("uniqueItems",e)}get maxProperties(){return this.get("maxProperties")}set maxProperties(e){this.set("maxProperties",e)}get minProperties(){return this.get("minProperties")}set minProperties(e){this.set("minProperties",e)}get required(){return this.get("required")}set required(e){this.set("required",e)}get properties(){return this.get("properties")}set properties(e){this.set("properties",e)}get additionalProperties(){return this.get("additionalProperties")}set additionalProperties(e){this.set("additionalProperties",e)}get patternProperties(){return this.get("patternProperties")}set patternProperties(e){this.set("patternProperties",e)}get dependencies(){return this.get("dependencies")}set dependencies(e){this.set("dependencies",e)}get enum(){return this.get("enum")}set enum(e){this.set("enum",e)}get type(){return this.get("type")}set type(e){this.set("type",e)}get allOf(){return this.get("allOf")}set allOf(e){this.set("allOf",e)}get anyOf(){return this.get("anyOf")}set anyOf(e){this.set("anyOf",e)}get oneOf(){return this.get("oneOf")}set oneOf(e){this.set("oneOf",e)}get not(){return this.get("not")}set not(e){this.set("not",e)}get definitions(){return this.get("definitions")}set definitions(e){this.set("definitions",e)}get title(){return this.get("title")}set title(e){this.set("title",e)}get description(){return this.get("description")}set description(e){this.set("description",e)}get default(){return this.get("default")}set default(e){this.set("default",e)}get format(){return this.get("format")}set format(e){this.set("format",e)}get base(){return this.get("base")}set base(e){this.set("base",e)}get links(){return this.get("links")}set links(e){this.set("links",e)}get media(){return this.get("media")}set media(e){this.set("media",e)}get readOnly(){return this.get("readOnly")}set readOnly(e){this.set("readOnly",e)}}const za=Ua;class Va extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="JSONReference",this.classes.push("json-reference")}get $ref(){return this.get("$ref")}set $ref(e){this.set("$ref",e)}}const Wa=Va;class Ja extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="media"}get binaryEncoding(){return this.get("binaryEncoding")}set binaryEncoding(e){this.set("binaryEncoding",e)}get type(){return this.get("type")}set type(e){this.set("type",e)}}const Ka=Ja;class Ha extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="linkDescription"}get href(){return this.get("href")}set href(e){this.set("href",e)}get rel(){return this.get("rel")}set rel(e){this.set("rel",e)}get title(){return this.get("title")}set title(e){this.set("title",e)}get targetSchema(){return this.get("targetSchema")}set targetSchema(e){this.set("targetSchema",e)}get mediaType(){return this.get("mediaType")}set mediaType(e){this.set("mediaType",e)}get method(){return this.get("method")}set method(e){this.set("method",e)}get encType(){return this.get("encType")}set encType(e){this.set("encType",e)}get schema(){return this.get("schema")}set schema(e){this.set("schema",e)}}const Ga=Ha,Za=(e,t)=>{const n=kr(e,t);return po((e=>{if($s(e)&&Hr("$ref",e)&&_o(Xs,"$ref",e)){const t=uo(["$ref"],e),r=qi("#/",t);return uo(r.split("/"),n)}return $s(e)?Za(e,n):e}),e)},Ya=Ys({props:{element:null},methods:{copyMetaAndAttributes(e,t){Cs(e)&&t.meta.set("sourceMap",e.meta.get("sourceMap"))}}}),Xa=Ya,Qa=Ys(Xa,{methods:{enter(e){return this.element=e.clone(),ei}}});const el=Hn($o());function tl(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}const nl=e=>{if(ds(e))return`${e.element.charAt(0).toUpperCase()+e.element.slice(1)}Element`},rl=function(e){for(var t=1;t{if(ms(r)&&n.includes(r.toValue())&&!this.ignoredFields.includes(r.toValue())){const n=this.toRefractedElement([...t,"fixedFields",r.toValue()],e),s=new Pt.c6(r.clone(),n);this.copyMetaAndAttributes(o,s),s.classes.push("fixed-field"),this.element.content.push(s)}else this.ignoredFields.includes(r.toValue())||this.element.content.push(o.clone())})),this.copyMetaAndAttributes(e,this.element),ei}}}),ll=al,cl=Ys(ll,Qa,{props:{specPath:Hn(["document","objects","JSONSchema"])},init(){this.element=new za}}),ul=Qa,pl=Qa,hl=Qa,fl=Qa,dl=Qa,ml=Qa,gl=Qa,yl=Qa,vl=Qa,bl=Qa,wl=Ys({props:{parent:null},init({parent:e=this.parent}){this.parent=e,this.passingOptionsNames=[...this.passingOptionsNames,"parent"]}}),El=e=>bs(e)&&e.hasKey("$ref"),xl=Ys(il,wl,Qa,{methods:{ObjectElement(e){const t=El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"];return this.element=this.toRefractedElement(t,e),ei},ArrayElement(e){return this.element=new Pt.ON,this.element.classes.push("json-schema-items"),e.forEach((e=>{const t=El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"],n=this.toRefractedElement(t,e);this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}}),Sl=Qa,_l=Qa,jl=Qa,Ol=Qa,kl=Qa,Al=Ys(Qa,{methods:{ArrayElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-required"),ei}}});const Cl=pr(Zt(1,cr(Ts,Ur(Ms,ts))));const Pl=pr(so);const Nl=Kn([Xs,Cl,Pl]),Il=Ys(il,{props:{fieldPatternPredicate:Mt,specPath:el,ignoredFields:[]},init({specPath:e=this.specPath,ignoredFields:t=this.ignoredFields}={}){this.specPath=e,this.ignoredFields=t},methods:{ObjectElement(e){return e.forEach(((e,t,n)=>{if(!this.ignoredFields.includes(t.toValue())&&this.fieldPatternPredicate(t.toValue())){const r=this.specPath(e),o=this.toRefractedElement(r,e),s=new Pt.c6(t.clone(),o);this.copyMetaAndAttributes(n,s),s.classes.push("patterned-field"),this.element.content.push(s)}else this.ignoredFields.includes(t.toValue())||this.element.content.push(n.clone())})),this.copyMetaAndAttributes(e,this.element),ei}}}),Tl=Ys(Il,{props:{fieldPatternPredicate:Nl}}),Rl=Ys(Tl,wl,Qa,{props:{specPath:e=>El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"]},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-properties")}}),Ml=Ys(Tl,wl,Qa,{props:{specPath:e=>El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"]},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-patternProperties")}}),Dl=Ys(Tl,wl,Qa,{props:{specPath:e=>El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"]},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-dependencies")}}),Fl=Ys(Qa,{methods:{ArrayElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-enum"),ei}}}),Ll=Ys(Qa,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-type"),ei},ArrayElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-type"),ei}}}),Bl=Ys(il,wl,Qa,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-allOf")},methods:{ArrayElement(e){return e.forEach((e=>{const t=El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"],n=this.toRefractedElement(t,e);this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}}),$l=Ys(il,wl,Qa,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-anyOf")},methods:{ArrayElement(e){return e.forEach((e=>{const t=El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"],n=this.toRefractedElement(t,e);this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}}),ql=Ys(il,wl,Qa,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-oneOf")},methods:{ArrayElement(e){return e.forEach((e=>{const t=El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"],n=this.toRefractedElement(t,e);this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}}),Ul=Ys(Tl,wl,Qa,{props:{specPath:e=>El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"]},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-definitions")}}),zl=Qa,Vl=Qa,Wl=Qa,Jl=Qa,Kl=Qa,Hl=Ys(il,wl,Qa,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-links")},methods:{ArrayElement(e){return e.forEach((e=>{const t=this.toRefractedElement(["document","objects","LinkDescription"],e);this.element.push(t)})),this.copyMetaAndAttributes(e,this.element),ei}}}),Gl=Qa,Zl=Ys(ll,Qa,{props:{specPath:Hn(["document","objects","JSONReference"])},init(){this.element=new Wa},methods:{ObjectElement(e){const t=ll.compose.methods.ObjectElement.call(this,e);return ms(this.element.$ref)&&this.element.classes.push("reference-element"),t}}}),Yl=Ys(Qa,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("reference-value"),ei}}});const Xl=pr(rr);const Ql=cr(rs,Pl);function ec(e){return function(e){if(Array.isArray(e))return tc(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return tc(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return tc(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function tc(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);nt.length}))),Zr,Tn("length")),rc=Or((function(e,t,n){var r=n.apply(void 0,ec(e));return Xl(r)?Ao(r):t}));const oc=to(Ql,(function(e){var t=nc(e);return Zt(t,(function(){for(var t=arguments.length,n=new Array(t),r=0;rto(e,Hn(t),$o))),n=oc(t)(e);return this.element=this.toRefractedElement(n,e),ei}}}),ic=Ys(sc,{props:{alternator:[{predicate:El,specPath:["document","objects","JSONReference"]},{predicate:Dt,specPath:["document","objects","JSONSchema"]}]}}),ac={visitors:{value:Qa,JSONSchemaOrJSONReferenceVisitor:ic,document:{objects:{JSONSchema:{$visitor:cl,fixedFields:{id:ul,$schema:pl,multipleOf:hl,maximum:fl,exclusiveMaximum:dl,minimum:ml,exclusiveMinimum:gl,maxLength:yl,minLength:vl,pattern:bl,additionalItems:ic,items:xl,maxItems:Sl,minItems:_l,uniqueItems:jl,maxProperties:Ol,minProperties:kl,required:Al,properties:Rl,additionalProperties:ic,patternProperties:Ml,dependencies:Dl,enum:Fl,type:Ll,allOf:Bl,anyOf:$l,oneOf:ql,not:ic,definitions:Ul,title:zl,description:Vl,default:Wl,format:Jl,base:Kl,links:Hl,media:{$ref:"#/visitors/document/objects/Media"},readOnly:Gl}},JSONReference:{$visitor:Zl,fixedFields:{$ref:Yl}},Media:{$visitor:Ys(ll,Qa,{props:{specPath:Hn(["document","objects","Media"])},init(){this.element=new Ka}}),fixedFields:{binaryEncoding:Qa,type:Qa}},LinkDescription:{$visitor:Ys(ll,Qa,{props:{specPath:Hn(["document","objects","LinkDescription"])},init(){this.element=new Ga}}),fixedFields:{href:Qa,rel:Qa,title:Qa,targetSchema:ic,mediaType:Qa,method:Qa,encType:Qa,schema:ic}}}}}},lc=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof za||e(r)&&t("JSONSchemaDraft4",r)&&n("object",r))),cc=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Wa||e(r)&&t("JSONReference",r)&&n("object",r))),uc=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Ka||e(r)&&t("media",r)&&n("object",r))),pc=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Ga||e(r)&&t("linkDescription",r)&&n("object",r))),hc={namespace:e=>{const{base:t}=e;return t.register("jSONSchemaDraft4",za),t.register("jSONReference",Wa),t.register("media",Ka),t.register("linkDescription",Ga),t}};function fc(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function dc(e){for(var t=1;t{const e=zs(hc);return{predicates:dc(dc({},i),{},{isStringElement:ms}),namespace:e}};function gc(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}const yc=(e,{specPath:t=["visitors","document","objects","JSONSchema","$visitor"],plugins:n=[],specificationObj:r=ac}={})=>{const o=(0,Pt.Qc)(e),s=Za(r),i=is(t,[],s);return fi(o,i,{state:{specObj:s}}),di(i.element,n,{toolboxCreator:mc,visitorOptions:{keyMap:rl,nodeTypeGetter:nl}})},vc=e=>(t,n={})=>yc(t,function(e){for(var t=1;t{if(ds(e))return`${e.element.charAt(0).toUpperCase()+e.element.slice(1)}Element`},Dc=function(e){for(var t=1;tbs(e)&&e.hasKey("openapi")&&e.hasKey("info"),qc=e=>bs(e)&&e.hasKey("name")&&e.hasKey("in"),Uc=e=>bs(e)&&e.hasKey("$ref"),zc=e=>bs(e)&&e.hasKey("content"),Vc=e=>bs(e)&&e.hasKey("description"),Wc=bs,Jc=bs,Kc=e=>ms(e.key)&&To("x-",e.key.toValue()),Hc=Ys(Bc,{props:{specPath:el,ignoredFields:[],canSupportSpecificationExtensions:!0,specificationExtensionPredicate:Kc},init({specPath:e=this.specPath,ignoredFields:t=this.ignoredFields,canSupportSpecificationExtensions:n=this.canSupportSpecificationExtensions,specificationExtensionPredicate:r=this.specificationExtensionPredicate}={}){this.specPath=e,this.ignoredFields=t,this.canSupportSpecificationExtensions=n,this.specificationExtensionPredicate=r},methods:{ObjectElement(e){const t=this.specPath(e),n=this.retrieveFixedFields(t);return e.forEach(((e,r,o)=>{if(ms(r)&&n.includes(r.toValue())&&!this.ignoredFields.includes(r.toValue())){const n=this.toRefractedElement([...t,"fixedFields",r.toValue()],e),s=new Pt.c6(r.clone(),n);this.copyMetaAndAttributes(o,s),s.classes.push("fixed-field"),this.element.content.push(s)}else if(this.canSupportSpecificationExtensions&&this.specificationExtensionPredicate(o)){const e=this.toRefractedElement(["document","extension"],o);this.element.content.push(e)}else this.ignoredFields.includes(r.toValue())||this.element.content.push(o.clone())})),this.copyMetaAndAttributes(e,this.element),ei}}}),Gc=Hc,Zc=Ys(Tc,{methods:{enter(e){return this.element=e.clone(),ei}}}),Yc=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","OpenApi"]),canSupportSpecificationExtensions:!0},init(){this.element=new ja},methods:{ObjectElement(e){return this.unrefractedElement=e,Gc.compose.methods.ObjectElement.call(this,e)}}}),Xc=Ys(Bc,Zc,{methods:{StringElement(e){const t=new Sa(e.toValue());return this.copyMetaAndAttributes(e,t),this.element=t,ei}}}),Qc=Ys(Bc,{methods:{MemberElement(e){return this.element=e.clone(),this.element.classes.push("specification-extension"),ei}}}),eu=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Info"]),canSupportSpecificationExtensions:!0},init(){this.element=new pa}}),tu=Zc,nu=Zc,ru=Zc,ou=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("api-version"),this.element.classes.push("version"),ei}}}),su=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Contact"]),canSupportSpecificationExtensions:!0},init(){this.element=new Qi}}),iu=Zc,au=Zc,lu=Zc,cu=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","License"]),canSupportSpecificationExtensions:!0},init(){this.element=new fa}}),uu=Zc,pu=Zc,hu=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Link"]),canSupportSpecificationExtensions:!0},init(){this.element=new ma},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return(ms(this.element.operationId)||ms(this.element.operationRef))&&this.element.classes.push("reference-element"),t}}}),fu=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("reference-value"),ei}}}),du=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("reference-value"),ei}}}),mu=Ys(Bc,{props:{fieldPatternPredicate:Mt,specPath:el,ignoredFields:[],canSupportSpecificationExtensions:!1,specificationExtensionPredicate:Kc},init({specPath:e=this.specPath,ignoredFields:t=this.ignoredFields,canSupportSpecificationExtensions:n=this.canSupportSpecificationExtensions,specificationExtensionPredicate:r=this.specificationExtensionPredicate}={}){this.specPath=e,this.ignoredFields=t,this.canSupportSpecificationExtensions=n,this.specificationExtensionPredicate=r},methods:{ObjectElement(e){return e.forEach(((e,t,n)=>{if(this.canSupportSpecificationExtensions&&this.specificationExtensionPredicate(n)){const e=this.toRefractedElement(["document","extension"],n);this.element.content.push(e)}else if(!this.ignoredFields.includes(t.toValue())&&this.fieldPatternPredicate(t.toValue())){const r=this.specPath(e),o=this.toRefractedElement(r,e),s=new Pt.c6(t.clone(),o);this.copyMetaAndAttributes(n,s),s.classes.push("patterned-field"),this.element.content.push(s)}else this.ignoredFields.includes(t.toValue())||this.element.content.push(n.clone())})),this.copyMetaAndAttributes(e,this.element),ei}}}),gu=mu,yu=Ys(gu,{props:{fieldPatternPredicate:Nl}});class vu extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(vu.primaryClass)}}Xo(vu,"primaryClass","link-parameters");const bu=vu,wu=Ys(yu,Zc,{props:{specPath:Hn(["value"])},init(){this.element=new bu}}),Eu=Zc,xu=Zc,Su=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Server"]),canSupportSpecificationExtensions:!0},init(){this.element=new jc}}),_u=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("server-url"),ei}}}),ju=Zc;class Ou extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(Ou.primaryClass)}}Xo(Ou,"primaryClass","servers");const ku=Ou,Au=Ys(Bc,Zc,{init(){this.element=new ku},methods:{ArrayElement(e){return e.forEach((e=>{const t=Wc(e)?["document","objects","Server"]:["value"],n=this.toRefractedElement(t,e);this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}}),Cu=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","ServerVariable"]),canSupportSpecificationExtensions:!0},init(){this.element=new kc}}),Pu=Zc,Nu=Zc,Iu=Zc;class Tu extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Tu.primaryClass)}}Xo(Tu,"primaryClass","server-variables");const Ru=Tu,Mu=Ys(yu,Zc,{props:{specPath:Hn(["document","objects","ServerVariable"])},init(){this.element=new Ru}}),Du=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","MediaType"]),canSupportSpecificationExtensions:!0},init(){this.element=new ya}}),Fu=Ys(Bc,{props:{alternator:[]},methods:{enter(e){const t=this.alternator.map((({predicate:e,specPath:t})=>to(e,Hn(t),$o))),n=oc(t)(e);return this.element=this.toRefractedElement(n,e),ei}}}),Lu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Gi||e(r)&&t("callback",r)&&n("object",r))),Bu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Yi||e(r)&&t("components",r)&&n("object",r))),$u=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Qi||e(r)&&t("contact",r)&&n("object",r))),qu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof sa||e(r)&&t("example",r)&&n("object",r))),Uu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof aa||e(r)&&t("externalDocumentation",r)&&n("object",r))),zu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof ca||e(r)&&t("header",r)&&n("object",r))),Vu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof pa||e(r)&&t("info",r)&&n("object",r))),Wu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof fa||e(r)&&t("license",r)&&n("object",r))),Ju=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof ma||e(r)&&t("link",r)&&n("object",r))),Ku=e=>{if(!Ju(e))return!1;if(!ms(e.operationRef))return!1;const t=e.operationRef.toValue();return"string"==typeof t&&t.length>0&&!t.startsWith("#")},Hu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Sa||e(r)&&t("openapi",r)&&n("string",r))),Gu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n,hasClass:r})=>o=>o instanceof ja||e(o)&&t("openApi3_0",o)&&n("object",o)&&r("api",o))),Zu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof ka||e(r)&&t("operation",r)&&n("object",r))),Yu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Ca||e(r)&&t("parameter",r)&&n("object",r))),Xu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Na||e(r)&&t("pathItem",r)&&n("object",r))),Qu=e=>{if(!Xu(e))return!1;if(!ms(e.$ref))return!1;const t=e.$ref.toValue();return"string"==typeof t&&t.length>0&&!t.startsWith("#")},ep=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Ta||e(r)&&t("paths",r)&&n("object",r))),tp=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Ma||e(r)&&t("reference",r)&&n("object",r))),np=e=>{if(!tp(e))return!1;if(!ms(e.$ref))return!1;const t=e.$ref.toValue();return"string"==typeof t&&t.length>0&&!t.startsWith("#")},rp=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Fa||e(r)&&t("requestBody",r)&&n("object",r))),op=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Ba||e(r)&&t("response",r)&&n("object",r))),sp=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof qa||e(r)&&t("responses",r)&&n("object",r))),ip=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof bc||e(r)&&t("schema",r)&&n("object",r))),ap=e=>vs(e)&&e.classes.includes("boolean-json-schema"),lp=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Ec||e(r)&&t("securityRequirement",r)&&n("object",r))),cp=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof jc||e(r)&&t("server",r)&&n("object",r))),up=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof kc||e(r)&&t("serverVariable",r)&&n("object",r))),pp=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof ya||e(r)&&t("mediaType",r)&&n("object",r))),hp=Ys(Fu,Zc,{props:{alternator:[{predicate:Uc,specPath:["document","objects","Reference"]},{predicate:Dt,specPath:["document","objects","Schema"]}]},methods:{ObjectElement(e){const t=Fu.compose.methods.enter.call(this,e);return tp(this.element)&&this.element.setMetaProperty("referenced-element","schema"),t}}}),fp=Zc,dp=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Example"],canSupportSpecificationExtensions:!0},init(){this.element=new Pt.Sb,this.element.classes.push("examples")},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","example")})),t}}});class mp extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(mp.primaryClass),this.classes.push("examples")}}Xo(mp,"primaryClass","media-type-examples");const gp=mp,yp=Ys(dp,{init(){this.element=new gp}});class vp extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(vp.primaryClass)}}Xo(vp,"primaryClass","media-type-encoding");const bp=vp,wp=Ys(yu,Zc,{props:{specPath:Hn(["document","objects","Encoding"])},init(){this.element=new bp}}),Ep=Ys(yu,Zc,{props:{specPath:Hn(["value"])},init(){this.element=new Ec}});class xp extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(xp.primaryClass)}}Xo(xp,"primaryClass","security");const Sp=xp,_p=Ys(Bc,Zc,{init(){this.element=new Sp},methods:{ArrayElement(e){return e.forEach((e=>{if(bs(e)){const t=this.toRefractedElement(["document","objects","SecurityRequirement"],e);this.element.push(t)}else this.element.push(e.clone())})),this.copyMetaAndAttributes(e,this.element),ei}}}),jp=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Components"]),canSupportSpecificationExtensions:!0},init(){this.element=new Yi}}),Op=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Tag"]),canSupportSpecificationExtensions:!0},init(){this.element=new Cc}}),kp=Zc,Ap=Zc,Cp=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Reference"]),canSupportSpecificationExtensions:!1},init(){this.element=new Ma},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return ms(this.element.$ref)&&this.element.classes.push("reference-element"),t}}}),Pp=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("reference-value"),ei}}}),Np=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Parameter"]),canSupportSpecificationExtensions:!0},init(){this.element=new Ca},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return bs(this.element.contentProp)&&this.element.contentProp.filter(pp).forEach(((e,t)=>{e.setMetaProperty("media-type",t.toValue())})),t}}}),Ip=Zc,Tp=Zc,Rp=Zc,Mp=Zc,Dp=Zc,Fp=Zc,Lp=Zc,Bp=Zc,$p=Zc,qp=Ys(Fu,Zc,{props:{alternator:[{predicate:Uc,specPath:["document","objects","Reference"]},{predicate:Dt,specPath:["document","objects","Schema"]}]},methods:{ObjectElement(e){const t=Fu.compose.methods.enter.call(this,e);return tp(this.element)&&this.element.setMetaProperty("referenced-element","schema"),t}}}),Up=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Header"]),canSupportSpecificationExtensions:!0},init(){this.element=new ca}}),zp=Zc,Vp=Zc,Wp=Zc,Jp=Zc,Kp=Zc,Hp=Zc,Gp=Zc,Zp=Ys(Fu,Zc,{props:{alternator:[{predicate:Uc,specPath:["document","objects","Reference"]},{predicate:Dt,specPath:["document","objects","Schema"]}]},methods:{ObjectElement(e){const t=Fu.compose.methods.enter.call(this,e);return tp(this.element)&&this.element.setMetaProperty("referenced-element","schema"),t}}}),Yp=Zc;class Xp extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Xp.primaryClass),this.classes.push("examples")}}Xo(Xp,"primaryClass","header-examples");const Qp=Xp,eh=Ys(dp,{init(){this.element=new Qp}}),th=Ys(yu,Zc,{props:{specPath:Hn(["document","objects","MediaType"])},init(){this.element=new Pt.Sb,this.element.classes.push("content")}});class nh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(nh.primaryClass),this.classes.push("content")}}Xo(nh,"primaryClass","header-content");const rh=nh,oh=Ys(th,{init(){this.element=new rh}}),sh=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Schema"]),canSupportSpecificationExtensions:!0},init(){this.element=new bc}}),{items:ih}=ac.visitors.document.objects.JSONSchema.fixedFields,ah=Ys(ih,{methods:{ObjectElement(e){const t=ih.compose.methods.ObjectElement.call(this,e);return tp(this.element)&&this.element.setMetaProperty("referenced-element","schema"),t},ArrayElement(e){return this.element=e.clone(),ei}}}),{properties:lh}=ac.visitors.document.objects.JSONSchema.fixedFields,ch=Ys(lh,{methods:{ObjectElement(e){const t=lh.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","schema")})),t}}}),{type:uh}=ac.visitors.document.objects.JSONSchema.fixedFields,ph=Ys(uh,{methods:{ArrayElement(e){return this.element=e.clone(),ei}}}),hh=Zc,fh=Zc,dh=Zc,mh=Zc,{JSONSchemaOrJSONReferenceVisitor:gh}=ac.visitors,yh=Ys(gh,{methods:{ObjectElement(e){const t=gh.compose.methods.enter.call(this,e);return tp(this.element)&&this.element.setMetaProperty("referenced-element","schema"),t}}}),vh=Object.fromEntries(Object.entries(ac.visitors.document.objects.JSONSchema.fixedFields).map((([e,t])=>t===ac.visitors.JSONSchemaOrJSONReferenceVisitor?[e,yh]:[e,t]))),bh=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Discriminator"]),canSupportSpecificationExtensions:!1},init(){this.element=new ta}}),wh=Zc;class Eh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Eh.primaryClass)}}Xo(Eh,"primaryClass","discriminator-mapping");const xh=Eh,Sh=Ys(yu,Zc,{props:{specPath:Hn(["value"])},init(){this.element=new xh}}),_h=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","XML"]),canSupportSpecificationExtensions:!0},init(){this.element=new Nc}}),jh=Zc,Oh=Zc,kh=Zc,Ah=Zc,Ch=Zc,Ph=Zc;class Nh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Nh.primaryClass),this.classes.push("examples")}}Xo(Nh,"primaryClass","parameter-examples");const Ih=Nh,Th=Ys(dp,{init(){this.element=new Ih}});class Rh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Rh.primaryClass),this.classes.push("content")}}Xo(Rh,"primaryClass","parameter-content");const Mh=Rh,Dh=Ys(th,{init(){this.element=new Mh}});class Fh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Fh.primaryClass)}}Xo(Fh,"primaryClass","components-schemas");const Lh=Fh,Bh=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Schema"]},init(){this.element=new Lh},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","schema")})),t}}});class $h extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push($h.primaryClass)}}Xo($h,"primaryClass","components-responses");const qh=$h,Uh=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Response"]},init(){this.element=new qh},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","response")})),this.element.filter(op).forEach(((e,t)=>{e.setMetaProperty("http-status-code",t.toValue())})),t}}}),zh=Uh;class Vh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Vh.primaryClass),this.classes.push("parameters")}}Xo(Vh,"primaryClass","components-parameters");const Wh=Vh,Jh=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Parameter"]},init(){this.element=new Wh},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","parameter")})),t}}});class Kh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Kh.primaryClass),this.classes.push("examples")}}Xo(Kh,"primaryClass","components-examples");const Hh=Kh,Gh=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Example"]},init(){this.element=new Hh},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","example")})),t}}});class Zh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Zh.primaryClass)}}Xo(Zh,"primaryClass","components-request-bodies");const Yh=Zh,Xh=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","RequestBody"]},init(){this.element=new Yh},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","requestBody")})),t}}});class Qh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Qh.primaryClass)}}Xo(Qh,"primaryClass","components-headers");const ef=Qh,tf=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Header"]},init(){this.element=new ef},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","header")})),this.element.filter(zu).forEach(((e,t)=>{e.setMetaProperty("header-name",t.toValue())})),t}}}),nf=tf;class rf extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(rf.primaryClass)}}Xo(rf,"primaryClass","components-security-schemes");const of=rf,sf=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","SecurityScheme"]},init(){this.element=new of},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","securityScheme")})),t}}});class af extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(af.primaryClass)}}Xo(af,"primaryClass","components-links");const lf=af,cf=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Link"]},init(){this.element=new lf},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","link")})),t}}});class uf extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(uf.primaryClass)}}Xo(uf,"primaryClass","components-callbacks");const pf=uf,hf=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Callback"]},init(){this.element=new pf},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","callback")})),t}}}),ff=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Example"]),canSupportSpecificationExtensions:!0},init(){this.element=new sa},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return ms(this.element.externalValue)&&this.element.classes.push("reference-element"),t}}}),df=Zc,mf=Zc,gf=Zc,yf=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("reference-value"),ei}}}),vf=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","ExternalDocumentation"]),canSupportSpecificationExtensions:!0},init(){this.element=new aa}}),bf=Zc,wf=Zc,Ef=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Encoding"]),canSupportSpecificationExtensions:!0},init(){this.element=new ra},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return bs(this.element.headers)&&this.element.headers.filter(zu).forEach(((e,t)=>{e.setMetaProperty("header-name",t.toValue())})),t}}}),xf=Zc;class Sf extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Sf.primaryClass)}}Xo(Sf,"primaryClass","encoding-headers");const _f=Sf,jf=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Header"]},init(){this.element=new _f},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","header")})),this.element.forEach(((e,t)=>{if(!zu(e))return;const n=t.toValue();e.setMetaProperty("headerName",n)})),t}}}),Of=jf,kf=Zc,Af=Zc,Cf=Zc,Pf=Ys(gu,Zc,{props:{fieldPatternPredicate:Ro(/^\/(?.*)$/),specPath:Hn(["document","objects","PathItem"]),canSupportSpecificationExtensions:!0},init(){this.element=new Ta},methods:{ObjectElement(e){const t=gu.compose.methods.ObjectElement.call(this,e);return this.element.filter(Xu).forEach(((e,t)=>{e.setMetaProperty("path",t.clone())})),t}}}),Nf=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","RequestBody"])},init(){this.element=new Fa},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return bs(this.element.contentProp)&&this.element.contentProp.filter(pp).forEach(((e,t)=>{e.setMetaProperty("media-type",t.toValue())})),t}}}),If=Zc;class Tf extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Tf.primaryClass),this.classes.push("content")}}Xo(Tf,"primaryClass","request-body-content");const Rf=Tf,Mf=Ys(th,{init(){this.element=new Rf}}),Df=Zc,Ff=Ys(gu,Zc,{props:{fieldPatternPredicate:Ro(/{(?.*)}/),specPath:Hn(["document","objects","PathItem"]),canSupportSpecificationExtensions:!0},init(){this.element=new Gi},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(Xu).forEach(((e,t)=>{e.setMetaProperty("runtime-expression",t.toValue())})),t}}}),Lf=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Response"])},init(){this.element=new Ba},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return bs(this.element.contentProp)&&this.element.contentProp.filter(pp).forEach(((e,t)=>{e.setMetaProperty("media-type",t.toValue())})),bs(this.element.headers)&&this.element.headers.filter(zu).forEach(((e,t)=>{e.setMetaProperty("header-name",t.toValue())})),t}}}),Bf=Zc;class $f extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push($f.primaryClass)}}Xo($f,"primaryClass","response-headers");const qf=$f,Uf=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Header"]},init(){this.element=new qf},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","header")})),this.element.forEach(((e,t)=>{if(!zu(e))return;const n=t.toValue();e.setMetaProperty("header-name",n)})),t}}}),zf=Uf;class Vf extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Vf.primaryClass),this.classes.push("content")}}Xo(Vf,"primaryClass","response-content");const Wf=Vf,Jf=Ys(th,{init(){this.element=new Wf}});class Kf extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Kf.primaryClass)}}Xo(Kf,"primaryClass","response-links");const Hf=Kf,Gf=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Link"]},init(){this.element=new Hf},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","link")})),t}}}),Zf=Ys(Gc,gu,{props:{specPathFixedFields:el,specPathPatternedFields:el},methods:{ObjectElement(e){const{specPath:t,ignoredFields:n}=this;try{this.specPath=this.specPathFixedFields;const t=this.retrieveFixedFields(this.specPath(e));this.ignoredFields=[...n,...Pr(e.keys(),t)],Gc.compose.methods.ObjectElement.call(this,e),this.specPath=this.specPathPatternedFields,this.ignoredFields=t,gu.compose.methods.ObjectElement.call(this,e)}catch(e){throw this.specPath=t,e}return ei}}}),Yf=Ys(Zf,Zc,{props:{specPathFixedFields:Hn(["document","objects","Responses"]),specPathPatternedFields:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Response"],fieldPatternPredicate:Ro(new RegExp(`^(1XX|2XX|3XX|4XX|5XX|${ko(100,600).join("|")})$`)),canSupportSpecificationExtensions:!0},init(){this.element=new qa},methods:{ObjectElement(e){const t=Zf.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","response")})),this.element.filter(op).forEach(((e,t)=>{const n=t.clone();this.fieldPatternPredicate(n.toValue())&&e.setMetaProperty("http-status-code",n)})),t}}}),Xf=Yf,Qf=Ys(Fu,Zc,{props:{alternator:[{predicate:Uc,specPath:["document","objects","Reference"]},{predicate:Dt,specPath:["document","objects","Response"]}]},methods:{ObjectElement(e){const t=Fu.compose.methods.enter.call(this,e);return tp(this.element)?this.element.setMetaProperty("referenced-element","response"):op(this.element)&&this.element.setMetaProperty("http-status-code","default"),t}}}),ed=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Operation"])},init(){this.element=new ka}});class td extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(td.primaryClass)}}Xo(td,"primaryClass","operation-tags");const nd=td,rd=Ys(Zc,{init(){this.element=new nd},methods:{ArrayElement(e){return this.element=this.element.concat(e.clone()),ei}}}),od=Zc,sd=Zc,id=Zc;class ad extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(ad.primaryClass),this.classes.push("parameters")}}Xo(ad,"primaryClass","operation-parameters");const ld=ad,cd=Ys(Bc,Zc,{init(){this.element=new Pt.ON,this.element.classes.push("parameters")},methods:{ArrayElement(e){return e.forEach((e=>{const t=Uc(e)?["document","objects","Reference"]:["document","objects","Parameter"],n=this.toRefractedElement(t,e);tp(n)&&n.setMetaProperty("referenced-element","parameter"),this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}}),ud=Ys(cd,{init(){this.element=new ld}}),pd=Ys(Fu,{props:{alternator:[{predicate:Uc,specPath:["document","objects","Reference"]},{predicate:Dt,specPath:["document","objects","RequestBody"]}]},methods:{ObjectElement(e){const t=Fu.compose.methods.enter.call(this,e);return tp(this.element)&&this.element.setMetaProperty("referenced-element","requestBody"),t}}});class hd extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(hd.primaryClass)}}Xo(hd,"primaryClass","operation-callbacks");const fd=hd,dd=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Callback"]},init(){this.element=new fd},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","callback")})),t}}}),md=Zc;class gd extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(gd.primaryClass),this.classes.push("security")}}Xo(gd,"primaryClass","operation-security");const yd=gd,vd=Ys(Bc,Zc,{init(){this.element=new yd},methods:{ArrayElement(e){return e.forEach((e=>{const t=bs(e)?["document","objects","SecurityRequirement"]:["value"],n=this.toRefractedElement(t,e);this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}});class bd extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(bd.primaryClass),this.classes.push("servers")}}Xo(bd,"primaryClass","operation-servers");const wd=bd,Ed=Ys(Au,{init(){this.element=new wd}}),xd=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","PathItem"])},init(){this.element=new Na},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return this.element.filter(Zu).forEach(((e,t)=>{const n=t.clone();n.content=n.toValue().toUpperCase(),e.setMetaProperty("http-method",n)})),ms(this.element.$ref)&&this.element.classes.push("reference-element"),t}}}),Sd=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("reference-value"),ei}}}),_d=Zc,jd=Zc;class Od extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(Od.primaryClass),this.classes.push("servers")}}Xo(Od,"primaryClass","path-item-servers");const kd=Od,Ad=Ys(Au,{init(){this.element=new kd}});class Cd extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(Cd.primaryClass),this.classes.push("parameters")}}Xo(Cd,"primaryClass","path-item-parameters");const Pd=Cd,Nd=Ys(cd,{init(){this.element=new Pd}}),Id=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","SecurityScheme"]),canSupportSpecificationExtensions:!0},init(){this.element=new Sc}}),Td=Zc,Rd=Zc,Md=Zc,Dd=Zc,Fd=Zc,Ld=Zc,Bd=Zc,$d=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","OAuthFlows"]),canSupportSpecificationExtensions:!0},init(){this.element=new Ea}}),qd=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","OAuthFlow"]),canSupportSpecificationExtensions:!0},init(){this.element=new ba}}),Ud=Zc,zd=Zc,Vd=Zc;class Wd extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Wd.primaryClass)}}Xo(Wd,"primaryClass","oauth-flow-scopes");const Jd=Wd,Kd=Ys(yu,Zc,{props:{specPath:Hn(["value"])},init(){this.element=new Jd}});class Hd extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(Hd.primaryClass)}}Xo(Hd,"primaryClass","tags");const Gd=Hd,Zd=Ys(Bc,Zc,{init(){this.element=new Gd},methods:{ArrayElement(e){return e.forEach((e=>{const t=Jc(e)?["document","objects","Tag"]:["value"],n=this.toRefractedElement(t,e);this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}});function Yd(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Xd(e){for(var t=1;t{const{base:t}=e;return t.register("callback",Gi),t.register("components",Yi),t.register("contact",Qi),t.register("discriminator",ta),t.register("encoding",ra),t.register("example",sa),t.register("externalDocumentation",aa),t.register("header",ca),t.register("info",pa),t.register("license",fa),t.register("link",ma),t.register("mediaType",ya),t.register("oAuthFlow",ba),t.register("oAuthFlows",Ea),t.register("openapi",Sa),t.register("openApi3_0",ja),t.register("operation",ka),t.register("parameter",Ca),t.register("pathItem",Na),t.register("paths",Ta),t.register("reference",Ma),t.register("requestBody",Fa),t.register("response",Ba),t.register("responses",qa),t.register("schema",bc),t.register("securityRequirement",Ec),t.register("securityScheme",Sc),t.register("server",jc),t.register("serverVariable",kc),t.register("tag",Cc),t.register("xml",Nc),t}};function rm(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function om(e){for(var t=1;t{const e=zs(nm);return{predicates:om(om(om({},a),l),{},{isStringElement:ms}),namespace:e}};function im(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}const am=(e,{specPath:t=["visitors","document","objects","OpenApi","$visitor"],plugins:n=[]}={})=>{const r=(0,Pt.Qc)(e),o=Za(tm),s=is(t,[],o);return fi(r,s,{state:{specObj:o}}),di(s.element,n,{toolboxCreator:sm,visitorOptions:{keyMap:Dc,nodeTypeGetter:Mc}})},lm=e=>(t,n={})=>am(t,function(e){for(var t=1;tr=>r instanceof cm||e(r)&&t("callback",r)&&n("object",r))),_g=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof um||e(r)&&t("components",r)&&n("object",r))),jg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof pm||e(r)&&t("contact",r)&&n("object",r))),Og=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof dm||e(r)&&t("example",r)&&n("object",r))),kg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof mm||e(r)&&t("externalDocumentation",r)&&n("object",r))),Ag=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof gm||e(r)&&t("header",r)&&n("object",r))),Cg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof ym||e(r)&&t("info",r)&&n("object",r))),Pg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof bm||e(r)&&t("jsonSchemaDialect",r)&&n("string",r))),Ng=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof wm||e(r)&&t("license",r)&&n("object",r))),Ig=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Em||e(r)&&t("link",r)&&n("object",r))),Tg=e=>{if(!Ig(e))return!1;if(!ms(e.operationRef))return!1;const t=e.operationRef.toValue();return"string"==typeof t&&t.length>0&&!t.startsWith("#")},Rg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof jm||e(r)&&t("openapi",r)&&n("string",r))),Mg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n,hasClass:r})=>o=>o instanceof km||e(o)&&t("openApi3_1",o)&&n("object",o)&&r("api",o))),Dg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Am||e(r)&&t("operation",r)&&n("object",r))),Fg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Cm||e(r)&&t("parameter",r)&&n("object",r))),Lg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Pm||e(r)&&t("pathItem",r)&&n("object",r))),Bg=e=>{if(!Lg(e))return!1;if(!ms(e.$ref))return!1;const t=e.$ref.toValue();return"string"==typeof t&&t.length>0&&!t.startsWith("#")},$g=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Nm||e(r)&&t("paths",r)&&n("object",r))),qg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Tm||e(r)&&t("reference",r)&&n("object",r))),Ug=e=>{if(!qg(e))return!1;if(!ms(e.$ref))return!1;const t=e.$ref.toValue();return"string"==typeof t&&t.length>0&&!t.startsWith("#")},zg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Rm||e(r)&&t("requestBody",r)&&n("object",r))),Vg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Mm||e(r)&&t("response",r)&&n("object",r))),Wg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Dm||e(r)&&t("responses",r)&&n("object",r))),Jg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Lm||e(r)&&t("schema",r)&&n("object",r))),Kg=e=>vs(e)&&e.classes.includes("boolean-json-schema"),Hg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Bm||e(r)&&t("securityRequirement",r)&&n("object",r))),Gg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof qm||e(r)&&t("server",r)&&n("object",r))),Zg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Um||e(r)&&t("serverVariable",r)&&n("object",r))),Yg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof xm||e(r)&&t("mediaType",r)&&n("object",r))),Xg=Ys({props:{parent:null},init({parent:e=this.parent}){this.parent=e,this.passingOptionsNames=[...this.passingOptionsNames,"parent"]}}),Qg=Ys(Gc,Xg,Zc,{props:{specPath:Hn(["document","objects","Schema"]),canSupportSpecificationExtensions:!0},init(){const e=()=>{let e;return e=null!==this.openApiSemanticElement&&Pg(this.openApiSemanticElement.jsonSchemaDialect)?this.openApiSemanticElement.jsonSchemaDialect.toValue():null!==this.openApiGenericElement&&ms(this.openApiGenericElement.get("jsonSchemaDialect"))?this.openApiGenericElement.get("jsonSchemaDialect").toValue():bm.default.toValue(),e},t=t=>{if(Is(this.parent)&&!ms(t.get("$schema")))this.element.setMetaProperty("inherited$schema",e());else if(Jg(this.parent)&&!ms(t.get("$schema"))){var n,r;const e=kr(null===(n=this.parent.meta.get("inherited$schema"))||void 0===n?void 0:n.toValue(),null===(r=this.parent.$schema)||void 0===r?void 0:r.toValue());this.element.setMetaProperty("inherited$schema",e)}},n=e=>{var t;const n=null!==this.parent?this.parent.getMetaProperty("inherited$id",[]).clone():new Pt.ON,r=null===(t=e.get("$id"))||void 0===t?void 0:t.toValue();Nl(r)&&n.push(r),this.element.setMetaProperty("inherited$id",n)};this.ObjectElement=function(e){this.element=new Lm,t(e),n(e),this.parent=this.element;const r=Gc.compose.methods.ObjectElement.call(this,e);return ms(this.element.$ref)&&(this.element.classes.push("reference-element"),this.element.setMetaProperty("referenced-element","schema")),r},this.BooleanElement=function(e){return this.element=e.clone(),this.element.classes.push("boolean-json-schema"),ei}}}),ey=Zc,ty=Ys(Zc,{methods:{ObjectElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-$vocabulary"),ei}}}),ny=Zc,ry=Zc,oy=Zc,sy=Zc,iy=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("reference-value"),ei}}}),ay=Ys(yu,Xg,Zc,{props:{specPath:Hn(["document","objects","Schema"])},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-$defs")}}),ly=Zc,cy=Ys(Bc,Xg,Zc,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-allOf")},methods:{ArrayElement(e){return e.forEach((e=>{if(bs(e)){const t=this.toRefractedElement(["document","objects","Schema"],e);this.element.push(t)}else{const t=e.clone();this.element.push(t)}})),this.copyMetaAndAttributes(e,this.element),ei}}}),uy=Ys(Bc,Xg,Zc,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-anyOf")},methods:{ArrayElement(e){return e.forEach((e=>{if(bs(e)){const t=this.toRefractedElement(["document","objects","Schema"],e);this.element.push(t)}else{const t=e.clone();this.element.push(t)}})),this.copyMetaAndAttributes(e,this.element),ei}}}),py=Ys(Bc,Xg,Zc,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-oneOf")},methods:{ArrayElement(e){return e.forEach((e=>{if(bs(e)){const t=this.toRefractedElement(["document","objects","Schema"],e);this.element.push(t)}else{const t=e.clone();this.element.push(t)}})),this.copyMetaAndAttributes(e,this.element),ei}}}),hy=Ys(yu,Xg,Zc,{props:{specPath:Hn(["document","objects","Schema"])},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-dependentSchemas")}}),fy=Ys(Bc,Xg,Zc,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-prefixItems")},methods:{ArrayElement(e){return e.forEach((e=>{if(bs(e)){const t=this.toRefractedElement(["document","objects","Schema"],e);this.element.push(t)}else{const t=e.clone();this.element.push(t)}})),this.copyMetaAndAttributes(e,this.element),ei}}}),dy=Ys(yu,Xg,Zc,{props:{specPath:Hn(["document","objects","Schema"])},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-properties")}}),my=Ys(yu,Xg,Zc,{props:{specPath:Hn(["document","objects","Schema"])},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-patternProperties")}}),gy=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-type"),ei},ArrayElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-type"),ei}}}),yy=Ys(Zc,{methods:{ArrayElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-enum"),ei}}}),vy=Zc,by=Zc,wy=Zc,Ey=Zc,xy=Zc,Sy=Zc,_y=Zc,jy=Zc,Oy=Zc,ky=Zc,Ay=Zc,Cy=Zc,Py=Zc,Ny=Zc,Iy=Zc,Ty=Zc,Ry=Ys(Zc,{methods:{ArrayElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-required"),ei}}}),My=Ys(Zc,{methods:{ObjectElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-dependentRequired"),ei}}}),Dy=Zc,Fy=Zc,Ly=Zc,By=Zc,$y=Zc,qy=Zc,Uy=Ys(Zc,{methods:{ArrayElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-examples"),ei}}}),zy=Zc,Vy=Zc,Wy=Zc,Jy=Zc,{visitors:{document:{objects:{Discriminator:{$visitor:Ky}}}}}=tm,Hy=Ys(Ky,{props:{canSupportSpecificationExtensions:!0},init(){this.element=new hm}}),{visitors:{document:{objects:{XML:{$visitor:Gy}}}}}=tm,Zy=Ys(Gy,{init(){this.element=new Vm}}),Yy=Ys(yu,Zc,{props:{specPath:Hn(["document","objects","Schema"])},init(){this.element=new Lh}});class Xy extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Xy.primaryClass)}}Xo(Xy,"primaryClass","components-path-items");const Qy=Xy,ev=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","PathItem"]},init(){this.element=new Qy},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(qg).forEach((e=>{e.setMetaProperty("referenced-element","pathItem")})),t}}}),{visitors:{document:{objects:{Example:{$visitor:tv}}}}}=tm,nv=Ys(tv,{init(){this.element=new dm}}),{visitors:{document:{objects:{ExternalDocumentation:{$visitor:rv}}}}}=tm,ov=Ys(rv,{init(){this.element=new mm}}),{visitors:{document:{objects:{Encoding:{$visitor:sv}}}}}=tm,iv=Ys(sv,{init(){this.element=new fm}}),{visitors:{document:{objects:{Paths:{$visitor:av}}}}}=tm,lv=Ys(av,{init(){this.element=new Nm}}),{visitors:{document:{objects:{RequestBody:{$visitor:cv}}}}}=tm,uv=Ys(cv,{init(){this.element=new Rm}}),{visitors:{document:{objects:{Callback:{$visitor:pv}}}}}=tm,hv=Ys(pv,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","PathItem"]},init(){this.element=new cm},methods:{ObjectElement(e){const t=pv.compose.methods.ObjectElement.call(this,e);return this.element.filter(qg).forEach((e=>{e.setMetaProperty("referenced-element","pathItem")})),t}}}),{visitors:{document:{objects:{Response:{$visitor:fv}}}}}=tm,dv=Ys(fv,{init(){this.element=new Mm}}),{visitors:{document:{objects:{Responses:{$visitor:mv}}}}}=tm,gv=Ys(mv,{init(){this.element=new Dm}}),{visitors:{document:{objects:{Operation:{$visitor:yv}}}}}=tm,vv=Ys(yv,{init(){this.element=new Am}}),{visitors:{document:{objects:{PathItem:{$visitor:bv}}}}}=tm,wv=Ys(bv,{init(){this.element=new Pm}}),{visitors:{document:{objects:{SecurityScheme:{$visitor:Ev}}}}}=tm,xv=Ys(Ev,{init(){this.element=new $m}}),{visitors:{document:{objects:{OAuthFlows:{$visitor:Sv}}}}}=tm,_v=Ys(Sv,{init(){this.element=new _m}}),{visitors:{document:{objects:{OAuthFlow:{$visitor:jv}}}}}=tm,Ov=Ys(jv,{init(){this.element=new Sm}});class kv extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(kv.primaryClass)}}Xo(kv,"primaryClass","webhooks");const Av=kv,Cv=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","PathItem"]},init(){this.element=new Av},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(qg).forEach((e=>{e.setMetaProperty("referenced-element","pathItem")})),this.element.filter(Lg).forEach(((e,t)=>{e.setMetaProperty("webhook-name",t.toValue())})),t}}}),Pv={visitors:{value:tm.visitors.value,document:{objects:{OpenApi:{$visitor:Wm,fixedFields:{openapi:tm.visitors.document.objects.OpenApi.fixedFields.openapi,info:{$ref:"#/visitors/document/objects/Info"},jsonSchemaDialect:ng,servers:tm.visitors.document.objects.OpenApi.fixedFields.servers,paths:{$ref:"#/visitors/document/objects/Paths"},webhooks:Cv,components:{$ref:"#/visitors/document/objects/Components"},security:tm.visitors.document.objects.OpenApi.fixedFields.security,tags:tm.visitors.document.objects.OpenApi.fixedFields.tags,externalDocs:{$ref:"#/visitors/document/objects/ExternalDocumentation"}}},Info:{$visitor:Km,fixedFields:{title:tm.visitors.document.objects.Info.fixedFields.title,description:tm.visitors.document.objects.Info.fixedFields.description,summary:Hm,termsOfService:tm.visitors.document.objects.Info.fixedFields.termsOfService,contact:{$ref:"#/visitors/document/objects/Contact"},license:{$ref:"#/visitors/document/objects/License"},version:tm.visitors.document.objects.Info.fixedFields.version}},Contact:{$visitor:Zm,fixedFields:{name:tm.visitors.document.objects.Contact.fixedFields.name,url:tm.visitors.document.objects.Contact.fixedFields.url,email:tm.visitors.document.objects.Contact.fixedFields.email}},License:{$visitor:Xm,fixedFields:{name:tm.visitors.document.objects.License.fixedFields.name,identifier:Qm,url:tm.visitors.document.objects.License.fixedFields.url}},Server:{$visitor:og,fixedFields:{url:tm.visitors.document.objects.Server.fixedFields.url,description:tm.visitors.document.objects.Server.fixedFields.description,variables:tm.visitors.document.objects.Server.fixedFields.variables}},ServerVariable:{$visitor:ig,fixedFields:{enum:tm.visitors.document.objects.ServerVariable.fixedFields.enum,default:tm.visitors.document.objects.ServerVariable.fixedFields.default,description:tm.visitors.document.objects.ServerVariable.fixedFields.description}},Components:{$visitor:hg,fixedFields:{schemas:Yy,responses:tm.visitors.document.objects.Components.fixedFields.responses,parameters:tm.visitors.document.objects.Components.fixedFields.parameters,examples:tm.visitors.document.objects.Components.fixedFields.examples,requestBodies:tm.visitors.document.objects.Components.fixedFields.requestBodies,headers:tm.visitors.document.objects.Components.fixedFields.headers,securitySchemes:tm.visitors.document.objects.Components.fixedFields.securitySchemes,links:tm.visitors.document.objects.Components.fixedFields.links,callbacks:tm.visitors.document.objects.Components.fixedFields.callbacks,pathItems:ev}},Paths:{$visitor:lv},PathItem:{$visitor:wv,fixedFields:{$ref:tm.visitors.document.objects.PathItem.fixedFields.$ref,summary:tm.visitors.document.objects.PathItem.fixedFields.summary,description:tm.visitors.document.objects.PathItem.fixedFields.description,get:{$ref:"#/visitors/document/objects/Operation"},put:{$ref:"#/visitors/document/objects/Operation"},post:{$ref:"#/visitors/document/objects/Operation"},delete:{$ref:"#/visitors/document/objects/Operation"},options:{$ref:"#/visitors/document/objects/Operation"},head:{$ref:"#/visitors/document/objects/Operation"},patch:{$ref:"#/visitors/document/objects/Operation"},trace:{$ref:"#/visitors/document/objects/Operation"},servers:tm.visitors.document.objects.PathItem.fixedFields.servers,parameters:tm.visitors.document.objects.PathItem.fixedFields.parameters}},Operation:{$visitor:vv,fixedFields:{tags:tm.visitors.document.objects.Operation.fixedFields.tags,summary:tm.visitors.document.objects.Operation.fixedFields.summary,description:tm.visitors.document.objects.Operation.fixedFields.description,externalDocs:{$ref:"#/visitors/document/objects/ExternalDocumentation"},operationId:tm.visitors.document.objects.Operation.fixedFields.operationId,parameters:tm.visitors.document.objects.Operation.fixedFields.parameters,requestBody:tm.visitors.document.objects.Operation.fixedFields.requestBody,responses:{$ref:"#/visitors/document/objects/Responses"},callbacks:tm.visitors.document.objects.Operation.fixedFields.callbacks,deprecated:tm.visitors.document.objects.Operation.fixedFields.deprecated,security:tm.visitors.document.objects.Operation.fixedFields.security,servers:tm.visitors.document.objects.Operation.fixedFields.servers}},ExternalDocumentation:{$visitor:ov,fixedFields:{description:tm.visitors.document.objects.ExternalDocumentation.fixedFields.description,url:tm.visitors.document.objects.ExternalDocumentation.fixedFields.url}},Parameter:{$visitor:wg,fixedFields:{name:tm.visitors.document.objects.Parameter.fixedFields.name,in:tm.visitors.document.objects.Parameter.fixedFields.in,description:tm.visitors.document.objects.Parameter.fixedFields.description,required:tm.visitors.document.objects.Parameter.fixedFields.required,deprecated:tm.visitors.document.objects.Parameter.fixedFields.deprecated,allowEmptyValue:tm.visitors.document.objects.Parameter.fixedFields.allowEmptyValue,style:tm.visitors.document.objects.Parameter.fixedFields.style,explode:tm.visitors.document.objects.Parameter.fixedFields.explode,allowReserved:tm.visitors.document.objects.Parameter.fixedFields.allowReserved,schema:{$ref:"#/visitors/document/objects/Schema"},example:tm.visitors.document.objects.Parameter.fixedFields.example,examples:tm.visitors.document.objects.Parameter.fixedFields.examples,content:tm.visitors.document.objects.Parameter.fixedFields.content}},RequestBody:{$visitor:uv,fixedFields:{description:tm.visitors.document.objects.RequestBody.fixedFields.description,content:tm.visitors.document.objects.RequestBody.fixedFields.content,required:tm.visitors.document.objects.RequestBody.fixedFields.required}},MediaType:{$visitor:lg,fixedFields:{schema:{$ref:"#/visitors/document/objects/Schema"},example:tm.visitors.document.objects.MediaType.fixedFields.example,examples:tm.visitors.document.objects.MediaType.fixedFields.examples,encoding:tm.visitors.document.objects.MediaType.fixedFields.encoding}},Encoding:{$visitor:iv,fixedFields:{contentType:tm.visitors.document.objects.Encoding.fixedFields.contentType,headers:tm.visitors.document.objects.Encoding.fixedFields.headers,style:tm.visitors.document.objects.Encoding.fixedFields.style,explode:tm.visitors.document.objects.Encoding.fixedFields.explode,allowReserved:tm.visitors.document.objects.Encoding.fixedFields.allowReserved}},Responses:{$visitor:gv,fixedFields:{default:tm.visitors.document.objects.Responses.fixedFields.default}},Response:{$visitor:dv,fixedFields:{description:tm.visitors.document.objects.Response.fixedFields.description,headers:tm.visitors.document.objects.Response.fixedFields.headers,content:tm.visitors.document.objects.Response.fixedFields.content,links:tm.visitors.document.objects.Response.fixedFields.links}},Callback:{$visitor:hv},Example:{$visitor:nv,fixedFields:{summary:tm.visitors.document.objects.Example.fixedFields.summary,description:tm.visitors.document.objects.Example.fixedFields.description,value:tm.visitors.document.objects.Example.fixedFields.value,externalValue:tm.visitors.document.objects.Example.fixedFields.externalValue}},Link:{$visitor:tg,fixedFields:{operationRef:tm.visitors.document.objects.Link.fixedFields.operationRef,operationId:tm.visitors.document.objects.Link.fixedFields.operationId,parameters:tm.visitors.document.objects.Link.fixedFields.parameters,requestBody:tm.visitors.document.objects.Link.fixedFields.requestBody,description:tm.visitors.document.objects.Link.fixedFields.description,server:{$ref:"#/visitors/document/objects/Server"}}},Header:{$visitor:xg,fixedFields:{description:tm.visitors.document.objects.Header.fixedFields.description,required:tm.visitors.document.objects.Header.fixedFields.required,deprecated:tm.visitors.document.objects.Header.fixedFields.deprecated,allowEmptyValue:tm.visitors.document.objects.Header.fixedFields.allowEmptyValue,style:tm.visitors.document.objects.Header.fixedFields.style,explode:tm.visitors.document.objects.Header.fixedFields.explode,allowReserved:tm.visitors.document.objects.Header.fixedFields.allowReserved,schema:{$ref:"#/visitors/document/objects/Schema"},example:tm.visitors.document.objects.Header.fixedFields.example,examples:tm.visitors.document.objects.Header.fixedFields.examples,content:tm.visitors.document.objects.Header.fixedFields.content}},Tag:{$visitor:dg,fixedFields:{name:tm.visitors.document.objects.Tag.fixedFields.name,description:tm.visitors.document.objects.Tag.fixedFields.description,externalDocs:{$ref:"#/visitors/document/objects/ExternalDocumentation"}}},Reference:{$visitor:gg,fixedFields:{$ref:tm.visitors.document.objects.Reference.fixedFields.$ref,summary:yg,description:vg}},Schema:{$visitor:Qg,fixedFields:{$schema:ey,$vocabulary:ty,$id:ny,$anchor:ry,$dynamicAnchor:oy,$dynamicRef:sy,$ref:iy,$defs:ay,$comment:ly,allOf:cy,anyOf:uy,oneOf:py,not:{$ref:"#/visitors/document/objects/Schema"},if:{$ref:"#/visitors/document/objects/Schema"},then:{$ref:"#/visitors/document/objects/Schema"},else:{$ref:"#/visitors/document/objects/Schema"},dependentSchemas:hy,prefixItems:fy,items:{$ref:"#/visitors/document/objects/Schema"},contains:{$ref:"#/visitors/document/objects/Schema"},properties:dy,patternProperties:my,additionalProperties:{$ref:"#/visitors/document/objects/Schema"},propertyNames:{$ref:"#/visitors/document/objects/Schema"},unevaluatedItems:{$ref:"#/visitors/document/objects/Schema"},unevaluatedProperties:{$ref:"#/visitors/document/objects/Schema"},type:gy,enum:yy,const:vy,multipleOf:by,maximum:wy,exclusiveMaximum:Ey,minimum:xy,exclusiveMinimum:Sy,maxLength:_y,minLength:jy,pattern:Oy,maxItems:ky,minItems:Ay,uniqueItems:Cy,maxContains:Py,minContains:Ny,maxProperties:Iy,minProperties:Ty,required:Ry,dependentRequired:My,title:Dy,description:Fy,default:Ly,deprecated:By,readOnly:$y,writeOnly:qy,examples:Uy,format:zy,contentEncoding:Vy,contentMediaType:Wy,contentSchema:{$ref:"#/visitors/document/objects/Schema"},discriminator:{$ref:"#/visitors/document/objects/Discriminator"},xml:{$ref:"#/visitors/document/objects/XML"},externalDocs:{$ref:"#/visitors/document/objects/ExternalDocumentation"},example:Jy}},Discriminator:{$visitor:Hy,fixedFields:{propertyName:tm.visitors.document.objects.Discriminator.fixedFields.propertyName,mapping:tm.visitors.document.objects.Discriminator.fixedFields.mapping}},XML:{$visitor:Zy,fixedFields:{name:tm.visitors.document.objects.XML.fixedFields.name,namespace:tm.visitors.document.objects.XML.fixedFields.namespace,prefix:tm.visitors.document.objects.XML.fixedFields.prefix,attribute:tm.visitors.document.objects.XML.fixedFields.attribute,wrapped:tm.visitors.document.objects.XML.fixedFields.wrapped}},SecurityScheme:{$visitor:xv,fixedFields:{type:tm.visitors.document.objects.SecurityScheme.fixedFields.type,description:tm.visitors.document.objects.SecurityScheme.fixedFields.description,name:tm.visitors.document.objects.SecurityScheme.fixedFields.name,in:tm.visitors.document.objects.SecurityScheme.fixedFields.in,scheme:tm.visitors.document.objects.SecurityScheme.fixedFields.scheme,bearerFormat:tm.visitors.document.objects.SecurityScheme.fixedFields.bearerFormat,flows:{$ref:"#/visitors/document/objects/OAuthFlows"},openIdConnectUrl:tm.visitors.document.objects.SecurityScheme.fixedFields.openIdConnectUrl}},OAuthFlows:{$visitor:_v,fixedFields:{implicit:{$ref:"#/visitors/document/objects/OAuthFlow"},password:{$ref:"#/visitors/document/objects/OAuthFlow"},clientCredentials:{$ref:"#/visitors/document/objects/OAuthFlow"},authorizationCode:{$ref:"#/visitors/document/objects/OAuthFlow"}}},OAuthFlow:{$visitor:Ov,fixedFields:{authorizationUrl:tm.visitors.document.objects.OAuthFlow.fixedFields.authorizationUrl,tokenUrl:tm.visitors.document.objects.OAuthFlow.fixedFields.tokenUrl,refreshUrl:tm.visitors.document.objects.OAuthFlow.fixedFields.refreshUrl,scopes:tm.visitors.document.objects.OAuthFlow.fixedFields.scopes}},SecurityRequirement:{$visitor:ug}},extension:{$visitor:tm.visitors.document.extension.$visitor}}}};function Nv(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}const Iv=e=>{if(ds(e))return`${e.element.charAt(0).toUpperCase()+e.element.slice(1)}Element`},Tv=function(e){for(var t=1;t{const{base:t}=e;return t.register("callback",cm),t.register("components",um),t.register("contact",pm),t.register("discriminator",hm),t.register("encoding",fm),t.register("example",dm),t.register("externalDocumentation",mm),t.register("header",gm),t.register("info",ym),t.register("jsonSchemaDialect",bm),t.register("license",wm),t.register("link",Em),t.register("mediaType",xm),t.register("oAuthFlow",Sm),t.register("oAuthFlows",_m),t.register("openapi",jm),t.register("openApi3_1",km),t.register("operation",Am),t.register("parameter",Cm),t.register("pathItem",Pm),t.register("paths",Nm),t.register("reference",Tm),t.register("requestBody",Rm),t.register("response",Mm),t.register("responses",Dm),t.register("schema",Lm),t.register("securityRequirement",Bm),t.register("securityScheme",$m),t.register("server",qm),t.register("serverVariable",Um),t.register("tag",zm),t.register("xml",Vm),t}};function Mv(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Dv(e){for(var t=1;t{const e=zs(Rv);return{predicates:Dv(Dv({},c),{},{isStringElement:ms,isArrayElement:ws,isObjectElement:bs,includesClasses:Ns}),namespace:e}};function Lv(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}const Bv=(e,{specPath:t=["visitors","document","objects","OpenApi","$visitor"],plugins:n=[]}={})=>{const r=(0,Pt.Qc)(e),o=Za(Pv),s=is(t,[],o);return fi(r,s,{state:{specObj:o}}),di(s.element,n,{toolboxCreator:Fv,visitorOptions:{keyMap:Tv,nodeTypeGetter:Iv}})},$v=e=>(t,n={})=>Bv(t,function(e){for(var t=1;te.includes(t)))}findBy(e="3.1.0",t="generic"){const n="generic"===t?`vnd.oai.openapi;version=${e}`:`vnd.oai.openapi+${t};version=${e}`;return this.find((e=>e.includes(n)))||this.unknownMediaType}latest(e="generic"){return ao(this.filterByFormat(e))}}const zv=new Uv("application/vnd.oai.openapi;version=3.1.0","application/vnd.oai.openapi+json;version=3.1.0","application/vnd.oai.openapi+yaml;version=3.1.0");var Vv=n(34155),Wv=Or((function(e,t){return gr(Io(""),Fr(as(e)),io(""))(t)}));const Jv=Wv;const Kv=pr(qo);const Hv=Zt(1,gr(cn,Xr("RegExp")));const Gv=Bo(Xs,Co(/[.*+?^${}()|[\]\\-]/g,"\\$&"));var Zv=function(e,t){if("string"!=typeof e&&!(e instanceof String))throw TypeError("`".concat(t,"` must be a string"))};var Yv=Zt(3,(function(e,t,n){!function(e,t,n){if(null==n||null==e||null==t)throw TypeError("Input values must not be `null` or `undefined`")}(e,t,n),Zv(n,"str"),Zv(t,"replaceValue"),function(e){if(!("string"==typeof e||e instanceof String||e instanceof RegExp))throw TypeError("`searchValue` must be a string or an regexp")}(e);var r=new RegExp(Hv(e)?e:Gv(e),"g");return Co(r,t,n)})),Xv=oo(2,"replaceAll");const Qv=ts(String.prototype.replaceAll)?Xv:Yv,eb=()=>wo(Ro(/^win/),["platform"],Vv),tb=e=>{try{const t=new URL(e);return Jv(":",t.protocol)}catch{return}},nb=(gr(tb,Kv),e=>{if(Vv.browser)return!1;const t=tb(e);return qo(t)||"file"===t||/^[a-zA-Z]$/.test(t)}),rb=e=>{const t=tb(e);return"http"===t||"https"===t},ob=(e,t)=>{const n=[/%23/g,"#",/%24/g,"$",/%26/g,"&",/%2C/g,",",/%40/g,"@"],r=So(!1,"keepFileProtocol",t),o=So(eb,"isWindows",t);let s=decodeURI(e);for(let e=0;e{const t=e.indexOf("#");return-1!==t?e.substr(t):"#"},ib=e=>{const t=e.indexOf("#");let n=e;return t>=0&&(n=e.substr(0,t)),n},ab=()=>{if(Vv.browser)return ib(globalThis.location.href);const e=Vv.cwd(),t=ao(e);return["/","\\"].includes(t)?e:e+(eb()?"\\":"/")},lb=(e,t)=>{const n=new URL(t,new URL(e,"resolve://"));if("resolve:"===n.protocol){const{pathname:e,search:t,hash:r}=n;return e+t+r}return n.toString()},cb=e=>nb(e)?(e=>{const t=[/\?/g,"%3F",/#/g,"%23"];let n=e;eb()&&(n=n.replace(/\\/g,"/")),n=encodeURI(n);for(let e=0;enb(e)?ob(e):decodeURI(e),pb=Ys({props:{uri:"",value:null,depth:0,refSet:null,errors:[]},init({depth:e=this.depth,refSet:t=this.refSet,uri:n=this.uri,value:r=this.value}={}){this.uri=n,this.value=r,this.depth=e,this.refSet=t,this.errors=[]}}),hb=pb,fb=Ys({props:{rootRef:null,refs:[],circular:!1},init({refs:e=[]}={}){this.refs=[],e.forEach((e=>this.add(e)))},methods:{get size(){return this.refs.length},add(e){return this.has(e)||(this.refs.push(e),this.rootRef=null===this.rootRef?e:this.rootRef,e.refSet=this),this},merge(e){for(const t of e.values())this.add(t);return this},has(e){const t=Xs(e)?e:e.uri;return Kv(this.find(xo(t,"uri")))},find(e){return this.refs.find(e)},*values(){yield*this.refs},clean(){this.refs.forEach((e=>{e.refSet=null})),this.refs=[]}}}),db=fb,mb={parse:{mediaType:"text/plain",parsers:[],parserOpts:{}},resolve:{baseURI:"",resolvers:[],resolverOpts:{},strategies:[],external:!0,maxDepth:1/0},dereference:{strategies:[],refSet:null,maxDepth:1/0}},gb=lo(uo(["resolve","baseURI"]),or(["resolve","baseURI"])),yb=e=>Ri(e)?ab():e,vb=Ys({props:{uri:null,mediaType:"text/plain",data:null,parseResult:null},init({uri:e=this.uri,mediaType:t=this.mediaType,data:n=this.data,parseResult:r=this.parseResult}={}){this.uri=e,this.mediaType=t,this.data=n,this.parseResult=r},methods:{get extension(){return Xs(this.uri)?(e=>{const t=e.lastIndexOf(".");return t>=0?e.substr(t).toLowerCase():""})(this.uri):""},toString(){if("string"==typeof this.data)return this.data;if(this.data instanceof ArrayBuffer||["ArrayBuffer"].includes(cn(this.data))||ArrayBuffer.isView(this.data)){return new TextDecoder("utf-8").decode(this.data)}return String(this.data)}}});class bb extends Error{constructor(e,t){if(super(e),this.name=this.constructor.name,this.message=e,"function"==typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error(e).stack,$s(t)&&Gr("cause",t)&&!Gr("cause",this)){const{cause:e}=t;this.cause=e,Gr("stack",e)&&(this.stack=`${this.stack}\nCAUSE: ${null==e?void 0:e.stack}`)}}}const wb=bb;const Eb=class extends wb{constructor(e,t){super(e,{cause:t.cause}),Xo(this,"plugin",void 0),this.plugin=t.plugin}},xb=async(e,t,n)=>{const r=await Promise.all(n.map(is([e],[t])));return n.filter(((e,t)=>r[t]))},Sb=async(e,t,n)=>{let r;for(const o of n)try{const n=await o[e].call(o,...t);return{plugin:o,result:n}}catch(e){r=new Eb("Error while running plugin",{cause:e,plugin:o})}return Promise.reject(r)};const _b=class extends wb{};const jb=class extends _b{};const Ob=class extends wb{},kb=async(e,t)=>{let n=e,r=!1;if(!Os(e)){const t=new e.constructor(e.content,e.meta.clone(),e.attributes);t.classes.push("result"),n=new zo([t]),r=!0}const o=vb({uri:t.resolve.baseURI,parseResult:n,mediaType:t.parse.mediaType}),s=await xb("canDereference",o,t.dereference.strategies);if(so(s))throw new jb(o.uri);try{const{result:e}=await Sb("dereference",[o,t],s);return r?e.get(0):e}catch(e){throw new Ob(`Error while dereferencing file "${o.uri}"`,{cause:e})}},Ab=async(e,t={})=>{const n=((e,t)=>{const n=mo(e,t);return vo(gb,yb,n)})(mb,t);return kb(e,n)};const Cb=class extends wb{constructor(e="Not Implemented",t){super(e,t)}},Pb=Ys({props:{name:"",allowEmpty:!0,sourceMap:!1,fileExtensions:[],mediaTypes:[]},init({allowEmpty:e=this.allowEmpty,sourceMap:t=this.sourceMap,fileExtensions:n=this.fileExtensions,mediaTypes:r=this.mediaTypes}={}){this.allowEmpty=e,this.sourceMap=t,this.fileExtensions=n,this.mediaTypes=r},methods:{async canParse(){throw new Cb},async parse(){throw new Cb}}}),Nb=Pb,Ib=Ys(Nb,{props:{name:"binary"},methods:{async canParse(e){return 0===this.fileExtensions.length||this.fileExtensions.includes(e.extension)},async parse(e){try{const t=unescape(encodeURIComponent(e.toString())),n=btoa(t),r=new zo;if(0!==n.length){const e=new Pt.RP(n);e.classes.push("result"),r.push(e)}return r}catch(t){throw new _b(`Error parsing "${e.uri}"`,{cause:t})}}}}),Tb=Ys({props:{name:null},methods:{canResolve:()=>!1,async resolve(){throw new Cb}}});const Rb=Zt(1,$n(Promise.all,Promise));const Mb=class extends wb{};const Db=class extends Mb{};const Fb=class extends Ob{};const Lb=class extends Mb{};function Bb(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function $b(e){for(var t=1;t{const n=vb({uri:cb(ib(e)),mediaType:t.parse.mediaType}),r=await(async(e,t)=>{const n=t.resolve.resolvers.map((e=>{const n=Object.create(e);return Object.assign(n,t.resolve.resolverOpts)})),r=await xb("canRead",e,n);if(so(r))throw new Lb(e.uri);try{const{result:t}=await Sb("read",[e],r);return t}catch(t){throw new Mb(`Error while reading file "${e.uri}"`,{cause:t})}})(n,t);return(async(e,t)=>{const n=t.parse.parsers.map((e=>{const n=Object.create(e);return Object.assign(n,t.parse.parserOpts)})),r=await xb("canParse",e,n);if(so(r))throw new Lb(e.uri);try{const{plugin:t,result:n}=await Sb("parse",[e],r);return!t.allowEmpty&&n.isEmpty?Promise.reject(new _b(`Error while parsing file "${e.uri}". File is empty.`)):n}catch(t){throw new _b(`Error while parsing file "${e.uri}"`,{cause:t})}})(vb($b($b({},n),{},{data:r})),t)},Ub=(e,t)=>{const n=hi({predicate:e});return fi(t,n),new Pt.O4(n.result)};class zb extends Error{constructor(e){super(e),this.name=this.constructor.name,this.message=e,"function"==typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error(e).stack}}const Vb=(e,t)=>{const n=hi({predicate:e,returnOnTrue:ei});return fi(t,n),bo(void 0,[0],n.result)};const Wb=class extends wb{};class Jb extends Wb{constructor(e){super(`Invalid JSON Schema $anchor "${e}".`)}}class Kb extends Error{constructor(e){super(e),this.name=this.constructor.name,this.message=e,"function"==typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error(e).stack}}const Hb=e=>/^[A-Za-z_][A-Za-z_0-9.-]*$/.test(e),Gb=e=>{const t=sb(e);return qi("#",t)},Zb=(e,t)=>{const n=(e=>{if(!Hb(e))throw new Jb(e);return e})(e),r=Vb((e=>{var t;return Jg(e)&&(null===(t=e.$anchor)||void 0===t?void 0:t.toValue())===n}),t);if(qo(r))throw new Kb(`Evaluation failed on token: "${n}"`);return r},Yb=(e,t)=>{if(void 0===t.$ref)return;const n=sb(t.$ref.toValue()),r=t.meta.get("inherited$id").toValue();return`${Jn(((e,t)=>lb(e,cb(ib(t)))),e,[...r,t.$ref.toValue()])}${"#"===n?"":n}`},Xb=e=>{if(Xb.cache.has(e))return Xb.cache.get(e);const t=Lm.refract(e);return Xb.cache.set(e,t),t};Xb.cache=new WeakMap;const Qb=e=>As(e)?Xb(e):e,ew=(e,t)=>{const{cache:n}=ew,r=ib(e),o=e=>Jg(e)&&void 0!==e.$id;if(!n.has(t)){const e=Ub(o,t);n.set(t,Array.from(e))}const s=n.get(t).find((e=>((e,t)=>{if(void 0===t.$id)return;const n=t.meta.get("inherited$id").toValue();return Jn(((e,t)=>lb(e,cb(ib(t)))),e,[...n,t.$id.toValue()])})(r,e)===r));if(qo(s))throw new zb(`Evaluation failed on URI: "${e}"`);let i,a;return Hb(Gb(e))?(i=Zb,a=Gb(e)):(i=Ji,a=Ki(e)),i(a,s)};function tw(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function nw(e){for(var t=1;t=this.options.resolve.maxDepth)throw new Db(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`);const t=this.toBaseURI(e),{refSet:n}=this.reference;if(n.has(t))return n.find(xo(t,"uri"));const r=await qb(ub(t),nw(nw({},this.options),{},{parse:nw(nw({},this.options.parse),{},{mediaType:"text/plain"})})),o=hb({uri:t,value:r,depth:this.reference.depth+1});return n.add(o),o},ReferenceElement(e){var t;if(!this.options.resolve.external&&Ug(e))return!1;const n=null===(t=e.$ref)||void 0===t?void 0:t.toValue(),r=this.toBaseURI(n);Hr(r,this.crawlingMap)||(this.crawlingMap[r]=this.toReference(n)),this.crawledElements.push(e)},PathItemElement(e){var t;if(!ms(e.$ref))return;if(!this.options.resolve.external&&Bg(e))return;const n=null===(t=e.$ref)||void 0===t?void 0:t.toValue(),r=this.toBaseURI(n);Hr(r,this.crawlingMap)||(this.crawlingMap[r]=this.toReference(n)),this.crawledElements.push(e)},LinkElement(e){if((ms(e.operationRef)||ms(e.operationId))&&(this.options.resolve.external||!Tg(e))){if(ms(e.operationRef)&&ms(e.operationId))throw new Error("LinkElement operationRef and operationId are mutually exclusive.");if(Tg(e)){var t;const n=null===(t=e.operationRef)||void 0===t?void 0:t.toValue(),r=this.toBaseURI(n);Hr(r,this.crawlingMap)||(this.crawlingMap[r]=this.toReference(n))}}},ExampleElement(e){var t;if(!ms(e.externalValue))return;if(!this.options.resolve.external&&ms(e.externalValue))return;if(e.hasKey("value")&&ms(e.externalValue))throw new Error("ExampleElement value and externalValue fields are mutually exclusive.");const n=null===(t=e.externalValue)||void 0===t?void 0:t.toValue(),r=this.toBaseURI(n);Hr(r,this.crawlingMap)||(this.crawlingMap[r]=this.toReference(n))},SchemaElement(e){if(this.visited.has(e))return!1;if(!ms(e.$ref))return void this.visited.add(e);const t=this.reference.uri,n=Yb(t,e),r=ib(n),o=vb({uri:r}),s=go((e=>e.canRead(o)),this.options.resolve.resolvers),i=!s,a=!s&&this.reference.uri!==r;if(this.options.resolve.external||!a){if(!Hr(r,this.crawlingMap))try{this.crawlingMap[r]=s||i?this.reference:this.toReference(ub(n))}catch(e){if(!(i&&e instanceof zb))throw e;this.crawlingMap[r]=this.toReference(ub(n))}this.crawledElements.push(e)}else this.visited.add(e)},async crawlReferenceElement(e){var t;const n=await this.toReference(e.$ref.toValue());this.indirections.push(e);const r=Ki(null===(t=e.$ref)||void 0===t?void 0:t.toValue());let o=Ji(r,n.value.result);if(As(o)){const t=e.meta.get("referenced-element").toValue();if(Uc(o))o=Tm.refract(o),o.setMetaProperty("referenced-element",t);else{o=this.namespace.getElementClass(t).refract(o)}}if(this.indirections.includes(o))throw new Error("Recursive Reference Object detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);const s=ow({reference:n,namespace:this.namespace,indirections:[...this.indirections],options:this.options});await rw(o,s,{keyMap:Tv,nodeTypeGetter:Iv}),await s.crawl(),this.indirections.pop()},async crawlPathItemElement(e){var t;const n=await this.toReference(e.$ref.toValue());this.indirections.push(e);const r=Ki(null===(t=e.$ref)||void 0===t?void 0:t.toValue());let o=Ji(r,n.value.result);if(As(o)&&(o=Pm.refract(o)),this.indirections.includes(o))throw new Error("Recursive Path Item Object reference detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);const s=ow({reference:n,namespace:this.namespace,indirections:[...this.indirections],options:this.options});await rw(o,s,{keyMap:Tv,nodeTypeGetter:Iv}),await s.crawl(),this.indirections.pop()},async crawlSchemaElement(e){const t=this.reference.uri,n=Yb(t,e),r=ib(n),o=vb({uri:r}),s=go((e=>e.canRead(o)),this.options.resolve.resolvers),i=!s;let a,l;this.indirections.push(e);try{if(s||i){a=this.reference;l=ew(n,Qb(a.value.result))}else{a=await this.toReference(ub(n));const e=Ki(n);l=Qb(Ji(e,a.value.result))}}catch(e){if(!(i&&e instanceof zb))throw e;if(Hb(Gb(n))){a=await this.toReference(ub(n));const e=Gb(n);l=Zb(e,Qb(a.value.result))}else{a=await this.toReference(ub(n));const e=Ki(n);l=Qb(Ji(e,a.value.result))}}if(this.visited.add(e),this.indirections.includes(l))throw new Error("Recursive Schema Object reference detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);const c=ow({reference:a,namespace:this.namespace,indirections:[...this.indirections],options:this.options,visited:this.visited});await rw(l,c,{keyMap:Tv,nodeTypeGetter:Iv}),await c.crawl(),this.indirections.pop()},async crawl(){await gr(nr,Rb)(this.crawlingMap),this.crawlingMap=null;for(const e of this.crawledElements)qg(e)?await this.crawlReferenceElement(e):Jg(e)?await this.crawlSchemaElement(e):Lg(e)&&await this.crawlPathItemElement(e)}}}),sw=ow,iw=fi[Symbol.for("nodejs.util.promisify.custom")],aw=Ys(Tb,{init(){this.name="openapi-3-1"},methods:{canResolve(e){var t;return"text/plain"!==e.mediaType?zv.includes(e.mediaType):Mg(null===(t=e.parseResult)||void 0===t?void 0:t.result)},async resolve(e,t){const n=zs(Rv),r=hb({uri:e.uri,value:e.parseResult}),o=sw({reference:r,namespace:n,options:t}),s=db();return s.add(r),await iw(s.rootRef.value,o,{keyMap:Tv,nodeTypeGetter:Iv}),await o.crawl(),s}}}),lw=aw,cw=e=>e.replace(/\s/g,""),uw=e=>e.replace(/\W/gi,"_"),pw=(e,t,n)=>{const r=cw(e);return r.length>0?uw(r):((e,t)=>`${uw(cw(t.toLowerCase()))}${uw(cw(e))}`)(t,n)},hw=({operationIdNormalizer:e=pw}={})=>({predicates:t,namespace:n})=>{const r=[],o=[],s=[];return{visitor:{OpenApi3_1Element:{leave(){const e=Jr((e=>Ti(e.operationId)),o);Object.entries(e).forEach((([e,t])=>{t.length<=1||t.forEach(((t,r)=>{const o=`${e}${r+1}`;t.operationId=new n.elements.String(o)}))})),s.forEach((e=>{var t;if(void 0===e.operationId)return;const n=String(Ti(e.operationId)),r=o.find((e=>Ti(e.meta.get("originalOperationId"))===n));void 0!==r&&(e.operationId=null===(t=r.operationId)||void 0===t?void 0:t.clone(),e.meta.set("originalOperationId",n),e.set("__originalOperationId",n))})),o.length=0,s.length=0}},PathItemElement:{enter(e){const t=kr("path",Ti(e.meta.get("path")));r.push(t)},leave(){r.pop()}},OperationElement:{enter(t){if(void 0===t.operationId)return;const s=String(Ti(t.operationId)),i=ao(r),a=kr("method",Ti(t.meta.get("http-method"))),l=e(s,i,a);s!==l&&(t.operationId=new n.elements.String(l),t.set("__originalOperationId",s),t.meta.set("originalOperationId",s),o.push(t))}},LinkElement:{leave(e){t.isLinkElement(e)&&void 0!==e.operationId&&s.push(e)}}}}},fw=()=>({predicates:e})=>{const t=(t,n)=>!!e.isParameterElement(t)&&(!!e.isParameterElement(n)&&(!!e.isStringElement(t.name)&&(!!e.isStringElement(t.in)&&(!!e.isStringElement(n.name)&&(!!e.isStringElement(n.in)&&(Ti(t.name)===Ti(n.name)&&Ti(t.in)===Ti(n.in))))))),n=[];return{visitor:{PathItemElement:{enter(t,r,o,s,i){if(i.some(e.isComponentsElement))return;const{parameters:a}=t;e.isArrayElement(a)?n.push([...a.content]):n.push([])},leave(){n.pop()}},OperationElement:{leave(e){const r=ao(n);if(!Array.isArray(r)||0===r.length)return;const o=bo([],["parameters","content"],e),s=Lo(t,[...o,...r]);e.parameters=new ld(s)}}}}},dw=()=>({predicates:e})=>{let t;return{visitor:{OpenApi3_1Element:{enter(n){e.isArrayElement(n.security)&&(t=n.security)},leave(){t=void 0}},OperationElement:{leave(n,r,o,s,i){if(i.some(e.isComponentsElement))return;var a;void 0===n.security&&void 0!==t&&(n.security=new yd(null===(a=t)||void 0===a?void 0:a.content))}}}}},mw=()=>({predicates:e})=>{let t;const n=[];return{visitor:{OpenApi3_1Element:{enter(n){var r;e.isArrayElement(n.servers)&&(t=null===(r=n.servers)||void 0===r?void 0:r.content)},leave(){t=void 0}},PathItemElement:{enter(r,o,s,i,a){if(a.some(e.isComponentsElement))return;void 0===r.servers&&void 0!==t&&(r.servers=new kd(t));const{servers:l}=r;void 0!==l&&e.isArrayElement(l)?n.push([...l.content]):n.push(void 0)},leave(){n.pop()}},OperationElement:{enter(t){const r=ao(n);void 0!==r&&(e.isArrayElement(t.servers)||(t.servers=new wd(r)))}}}}},gw=()=>({predicates:e})=>({visitor:{ParameterElement:{leave(t,n,r,o,s){var i,a;if(!s.some(e.isComponentsElement)&&void 0!==t.schema&&e.isSchemaElement(t.schema)&&(void 0!==(null===(i=t.schema)||void 0===i?void 0:i.example)||void 0!==(null===(a=t.schema)||void 0===a?void 0:a.examples))){if(void 0!==t.examples&&e.isObjectElement(t.examples)){const e=t.examples.map((e=>{var t;return null===(t=e.value)||void 0===t?void 0:t.clone()}));return void 0!==t.schema.examples&&t.schema.set("examples",e),void(void 0!==t.schema.example&&t.schema.set("example",e))}void 0!==t.example&&(void 0!==t.schema.examples&&t.schema.set("examples",[t.example.clone()]),void 0!==t.schema.example&&t.schema.set("example",t.example.clone()))}}}}}),yw=()=>({predicates:e})=>({visitor:{HeaderElement:{leave(t,n,r,o,s){var i,a;if(!s.some(e.isComponentsElement)&&void 0!==t.schema&&e.isSchemaElement(t.schema)&&(void 0!==(null===(i=t.schema)||void 0===i?void 0:i.example)||void 0!==(null===(a=t.schema)||void 0===a?void 0:a.examples))){if(void 0!==t.examples&&e.isObjectElement(t.examples)){const e=t.examples.map((e=>{var t;return null===(t=e.value)||void 0===t?void 0:t.clone()}));return void 0!==t.schema.examples&&t.schema.set("examples",e),void(void 0!==t.schema.example&&t.schema.set("example",e))}void 0!==t.example&&(void 0!==t.schema.examples&&t.schema.set("examples",[t.example.clone()]),void 0!==t.schema.example&&t.schema.set("example",t.example.clone()))}}}}}),vw=e=>t=>{if(t?.$$normalized)return t;if(vw.cache.has(t))return t;const n=km.refract(t),r=e(n),o=Ti(r);return vw.cache.set(t,o),o};vw.cache=new WeakMap;const bw=e=>{if(!bs(e))return e;if(e.hasKey("$$normalized"))return e;const t=[hw({operationIdNormalizer:(e,t,n)=>(0,He.Z)({operationId:e},t,n,{v2OperationIdCompatibilityMode:!1})}),fw(),dw(),mw(),gw(),yw()],n=di(e,t,{toolboxCreator:Fv,visitorOptions:{keyMap:Tv,nodeTypeGetter:Iv}});return n.set("$$normalized",!0),n},ww=Ys({props:{name:null},methods:{canRead:()=>!1,async read(){throw new Cb}}}),Ew=Ys(ww,{props:{timeout:5e3,redirects:5,withCredentials:!1},init({timeout:e=this.timeout,redirects:t=this.redirects,withCredentials:n=this.withCredentials}={}){this.timeout=e,this.redirects=t,this.withCredentials=n},methods:{canRead:e=>rb(e.uri),async read(){throw new Cb},getHttpClient(){throw new Cb}}}).compose({props:{name:"http-swagger-client",swaggerHTTPClient:ct,swaggerHTTPClientConfig:{}},init(){let{swaggerHTTPClient:e=this.swaggerHTTPClient}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.swaggerHTTPClient=e},methods:{getHttpClient(){return this.swaggerHTTPClient},async read(e){const t=this.getHttpClient(),n=new AbortController,{signal:r}=n,o=setTimeout((()=>{n.abort()}),this.timeout),s=this.getHttpClient().withCredentials||this.withCredentials?"include":"same-origin",i=0===this.redirects?"error":"follow",a=this.redirects>0?this.redirects:void 0;try{return(await t(f()({url:e.uri,signal:r,userFetch:async(e,t)=>{let n=await fetch(e,t);try{n.headers.delete("Content-Type")}catch{n=new Response(n.body,f()(f()({},n),{},{headers:new Headers(n.headers)})),n.headers.delete("Content-Type")}return n},credentials:s,redirects:i,follow:a},this.swaggerHTTPClientConfig))).text.arrayBuffer()}catch(t){throw new Mb(`Error downloading "${e.uri}"`,{cause:t})}finally{clearTimeout(o)}}}}),xw=Nb.compose({props:{name:"json-swagger-client",fileExtensions:[".json"],mediaTypes:["application/json"]},methods:{async canParse(e){const t=0===this.fileExtensions.length||this.fileExtensions.includes(e.extension),n=this.mediaTypes.includes(e.mediaType);if(!t)return!1;if(n)return!0;if(!n)try{return JSON.parse(e.toString()),!0}catch(e){return!1}return!1},async parse(e){if(this.sourceMap)throw new _b("json-swagger-client parser plugin doesn't support sourceMaps option");const t=new zo,n=e.toString();if(this.allowEmpty&&""===n.trim())return t;try{const e=Ii(JSON.parse(n));return e.classes.push("result"),t.push(e),t}catch(t){throw new _b(`Error parsing "${e.uri}"`,{cause:t})}}}}),Sw=Nb.compose({props:{name:"yaml-1-2-swagger-client",fileExtensions:[".yaml",".yml"],mediaTypes:["text/yaml","application/yaml"]},methods:{async canParse(e){const t=0===this.fileExtensions.length||this.fileExtensions.includes(e.extension),n=this.mediaTypes.includes(e.mediaType);if(!t)return!1;if(n)return!0;if(!n)try{return le.ZP.load(e.toString(),{schema:le.A8}),!0}catch(e){return!1}return!1},async parse(e){if(this.sourceMap)throw new _b("yaml-1-2-swagger-client parser plugin doesn't support sourceMaps option");const t=new zo,n=e.toString();try{const e=le.ZP.load(n,{schema:le.A8});if(this.allowEmpty&&void 0===e)return t;const r=Ii(e);return r.classes.push("result"),t.push(r),t}catch(t){throw new _b(`Error parsing "${e.uri}"`,{cause:t})}}}}),_w=Nb.compose({props:{name:"openapi-json-3-1-swagger-client",fileExtensions:[".json"],mediaTypes:new Uv(...zv.filterByFormat("generic"),...zv.filterByFormat("json")),detectionRegExp:/"openapi"\s*:\s*"(?3\.1\.(?:[1-9]\d*|0))"/},methods:{async canParse(e){const t=0===this.fileExtensions.length||this.fileExtensions.includes(e.extension),n=this.mediaTypes.includes(e.mediaType);if(!t)return!1;if(n)return!0;if(!n)try{const t=e.toString();return JSON.parse(t),this.detectionRegExp.test(t)}catch(e){return!1}return!1},async parse(e){if(this.sourceMap)throw new _b("openapi-json-3-1-swagger-client parser plugin doesn't support sourceMaps option");const t=new zo,n=e.toString();if(this.allowEmpty&&""===n.trim())return t;try{const e=JSON.parse(n),r=km.refract(e,this.refractorOpts);return r.classes.push("result"),t.push(r),t}catch(t){throw new _b(`Error parsing "${e.uri}"`,{cause:t})}}}}),jw=Nb.compose({props:{name:"openapi-yaml-3-1-swagger-client",fileExtensions:[".yaml",".yml"],mediaTypes:new Uv(...zv.filterByFormat("generic"),...zv.filterByFormat("yaml")),detectionRegExp:/(?^(["']?)openapi\2\s*:\s*(["']?)(?3\.1\.(?:[1-9]\d*|0))\3(?:\s+|$))|(?"openapi"\s*:\s*"(?3\.1\.(?:[1-9]\d*|0))")/m},methods:{async canParse(e){const t=0===this.fileExtensions.length||this.fileExtensions.includes(e.extension),n=this.mediaTypes.includes(e.mediaType);if(!t)return!1;if(n)return!0;if(!n)try{const t=e.toString();return le.ZP.load(t),this.detectionRegExp.test(t)}catch(e){return!1}return!1},async parse(e){if(this.sourceMap)throw new _b("openapi-yaml-3-1-swagger-client parser plugin doesn't support sourceMaps option");const t=new zo,n=e.toString();try{const e=le.ZP.load(n,{schema:le.A8});if(this.allowEmpty&&void 0===e)return t;const r=km.refract(e,this.refractorOpts);return r.classes.push("result"),t.push(r),t}catch(t){throw new _b(`Error parsing "${e.uri}"`,{cause:t})}}}}),Ow=Ys({props:{name:null},methods:{canDereference:()=>!1,async dereference(){throw new Cb}}});function kw(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Aw(e){for(var t=1;t=this.options.resolve.maxDepth)throw new Db(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`);const t=this.toBaseURI(e),{refSet:n}=this.reference;if(n.has(t))return n.find(xo(t,"uri"));const r=await qb(ub(t),Aw(Aw({},this.options),{},{parse:Aw(Aw({},this.options.parse),{},{mediaType:"text/plain"})})),o=hb({uri:t,value:r,depth:this.reference.depth+1});return n.add(o),o},async ReferenceElement(e,t,n,r,o){var s,i,a,l,c;const[u,p]=this.toAncestorLineage([...o,n]);if(u.some((t=>t.has(e))))return!1;if(!this.options.resolve.external&&Ug(e))return!1;const h=await this.toReference(null===(s=e.$ref)||void 0===s?void 0:s.toValue()),f=h.uri,d=lb(f,null===(i=e.$ref)||void 0===i?void 0:i.toValue());this.indirections.push(e);const m=Ki(d);let g=Ji(m,h.value.result);if(As(g)){const t=e.meta.get("referenced-element").toValue();if(Uc(g))g=Tm.refract(g),g.setMetaProperty("referenced-element",t);else{g=this.namespace.getElementClass(t).refract(g)}}if(this.indirections.includes(g))throw new Error("Recursive Reference Object detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);p.add(e);const y=Pw({reference:h,namespace:this.namespace,indirections:[...this.indirections],options:this.options,ancestors:u});g=await Cw(g,y,{keyMap:Tv,nodeTypeGetter:Iv}),p.delete(e),this.indirections.pop(),g=g.clone(),g.setMetaProperty("ref-fields",{$ref:null===(a=e.$ref)||void 0===a?void 0:a.toValue(),description:null===(l=e.description)||void 0===l?void 0:l.toValue(),summary:null===(c=e.summary)||void 0===c?void 0:c.toValue()}),g.setMetaProperty("ref-origin",h.uri);const v=wo(Kv,["description"],e),b=wo(Kv,["summary"],e);return v&&Gr("description",g)&&(g.description=e.description),b&&Gr("summary",g)&&(g.summary=e.summary),this.indirections.pop(),g},async PathItemElement(e,t,n,r,o){var s,i,a;const[l,c]=this.toAncestorLineage([...o,n]);if(!ms(e.$ref))return;if(l.some((t=>t.has(e))))return!1;if(!this.options.resolve.external&&Bg(e))return;const u=await this.toReference(null===(s=e.$ref)||void 0===s?void 0:s.toValue()),p=u.uri,h=lb(p,null===(i=e.$ref)||void 0===i?void 0:i.toValue());this.indirections.push(e);const f=Ki(h);let d=Ji(f,u.value.result);if(As(d)&&(d=Pm.refract(d)),this.indirections.includes(d))throw new Error("Recursive Path Item Object reference detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);c.add(e);const m=Pw({reference:u,namespace:this.namespace,indirections:[...this.indirections],options:this.options,ancestors:l});d=await Cw(d,m,{keyMap:Tv,nodeTypeGetter:Iv}),c.delete(e),this.indirections.pop();const g=new Pm([...d.content],d.meta.clone(),d.attributes.clone());return e.forEach(((e,t,n)=>{g.remove(t.toValue()),g.content.push(n)})),g.remove("$ref"),g.setMetaProperty("ref-fields",{$ref:null===(a=e.$ref)||void 0===a?void 0:a.toValue()}),g.setMetaProperty("ref-origin",u.uri),g},async LinkElement(e){if(!ms(e.operationRef)&&!ms(e.operationId))return;if(!this.options.resolve.external&&Tg(e))return;if(ms(e.operationRef)&&ms(e.operationId))throw new Error("LinkElement operationRef and operationId fields are mutually exclusive.");let t;if(ms(e.operationRef)){var n,r,o;const s=Ki(null===(n=e.operationRef)||void 0===n?void 0:n.toValue()),i=await this.toReference(null===(r=e.operationRef)||void 0===r?void 0:r.toValue());t=Ji(s,i.value.result),As(t)&&(t=Am.refract(t)),t=new Am([...t.content],t.meta.clone(),t.attributes.clone()),t.setMetaProperty("ref-origin",i.uri),null===(o=e.operationRef)||void 0===o||o.meta.set("operation",t)}else if(ms(e.operationId)){var s,i;const n=null===(s=e.operationId)||void 0===s?void 0:s.toValue();if(t=Vb((e=>Dg(e)&&e.operationId.equals(n)),this.reference.value.result),qo(t))throw new Error(`OperationElement(operationId=${n}) not found.`);null===(i=e.operationId)||void 0===i||i.meta.set("operation",t)}},async ExampleElement(e){var t;if(!ms(e.externalValue))return;if(!this.options.resolve.external&&ms(e.externalValue))return;if(e.hasKey("value")&&ms(e.externalValue))throw new Error("ExampleElement value and externalValue fields are mutually exclusive.");const n=await this.toReference(null===(t=e.externalValue)||void 0===t?void 0:t.toValue()),r=new n.value.result.constructor(n.value.result.content,n.value.result.meta.clone(),n.value.result.attributes.clone());r.setMetaProperty("ref-origin",n.uri),e.value=r},async SchemaElement(e,t,n,r,o){var s;const[i,a]=this.toAncestorLineage([...o,n]);if(!ms(e.$ref))return;if(i.some((t=>t.has(e))))return!1;let{reference:l}=this,{uri:c}=l;const u=Yb(c,e),p=ib(u),h=vb({uri:p}),f=go((e=>e.canRead(h)),this.options.resolve.resolvers),d=!f,m=d&&c!==p;if(!this.options.resolve.external&&m)return;let g;this.indirections.push(e);try{if(f||d){g=ew(u,Qb(l.value.result))}else{l=await this.toReference(ub(u));const e=Ki(u);g=Qb(Ji(e,l.value.result))}}catch(e){if(!(d&&e instanceof zb))throw e;if(Hb(Gb(u))){l=await this.toReference(ub(u)),c=l.uri;const e=Gb(u);g=Zb(e,Qb(l.value.result))}else{l=await this.toReference(ub(u)),c=l.uri;const e=Ki(u);g=Qb(Ji(e,l.value.result))}}if(this.indirections.includes(g))throw new Error("Recursive Schema Object reference detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);a.add(e);const y=Pw({reference:l,namespace:this.namespace,indirections:[...this.indirections],options:this.options,ancestors:i});if(g=await Cw(g,y,{keyMap:Tv,nodeTypeGetter:Iv}),a.delete(e),this.indirections.pop(),Kg(g)){var v;const t=g.clone();return t.setMetaProperty("ref-fields",{$ref:null===(v=e.$ref)||void 0===v?void 0:v.toValue()}),t.setMetaProperty("ref-origin",l.uri),t}const b=new Lm([...g.content],g.meta.clone(),g.attributes.clone());return e.forEach(((e,t,n)=>{b.remove(t.toValue()),b.content.push(n)})),b.remove("$ref"),b.setMetaProperty("ref-fields",{$ref:null===(s=e.$ref)||void 0===s?void 0:s.toValue()}),b.setMetaProperty("ref-origin",l.uri),b}}}),Nw=Pw,Iw=fi[Symbol.for("nodejs.util.promisify.custom")],Tw=Ys(Ow,{init(){this.name="openapi-3-1"},methods:{canDereference(e){var t;return"text/plain"!==e.mediaType?zv.includes(e.mediaType):Mg(null===(t=e.parseResult)||void 0===t?void 0:t.result)},async dereference(e,t){const n=zs(Rv),r=kr(db(),t.dereference.refSet);let o;r.has(e.uri)?o=r.find(xo(e.uri,"uri")):(o=hb({uri:e.uri,value:e.parseResult}),r.add(o));const s=Nw({reference:o,namespace:n,options:t}),i=await Iw(r.rootRef.value,s,{keyMap:Tv,nodeTypeGetter:Iv});return null===t.dereference.refSet&&r.clean(),i}}}),Rw=Tw,Mw=e=>{const t=(e=>e.slice(2))(e);return t.reduce(((e,n,r)=>{if(Es(n)){const t=String(n.key.toValue());e.push(t)}else if(ws(t[r-2])){const o=t[r-2].content.indexOf(n);e.push(o)}return e}),[])},Dw=e=>{if(null==e.cause)return e;let{cause:t}=e;for(;null!=t.cause;)t=t.cause;return t},Fw=ue("SchemaRefError",(function(e,t,n){this.originalError=n,Object.assign(this,t||{})})),{wrapError:Lw}=ke,Bw=fi[Symbol.for("nodejs.util.promisify.custom")],$w=Nw.compose({props:{useCircularStructures:!0,allowMetaPatches:!1,basePath:null},init(e){let{allowMetaPatches:t=this.allowMetaPatches,useCircularStructures:n=this.useCircularStructures,basePath:r=this.basePath}=e;this.allowMetaPatches=t,this.useCircularStructures=n,this.basePath=r},methods:{async ReferenceElement(e,t,n,r,o){try{const[t,r]=this.toAncestorLineage([...o,n]);if(Ns(["cycle"],e.$ref))return!1;if(t.some((t=>t.has(e))))return!1;if(!this.options.resolve.external&&Ug(e))return!1;const s=await this.toReference(e.$ref.toValue()),i=s.uri,a=lb(i,e.$ref.toValue());this.indirections.push(e);const l=Ki(a);let c=Ji(l,s.value.result);if(As(c)){const t=e.meta.get("referenced-element").toValue();if(Uc(c))c=Tm.refract(c),c.setMetaProperty("referenced-element",t);else{const e=this.namespace.getElementClass(t);c=e.refract(c)}}if(this.indirections.includes(c))throw new Error("Recursive JSON Pointer detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);if(!this.useCircularStructures){if(t.some((e=>e.has(c)))){if(rb(i)||nb(i)){const t=new Tm({$ref:a},e.meta.clone(),e.attributes.clone());return t.get("$ref").classes.push("cycle"),t}return!1}}r.add(e);const u=$w({reference:s,namespace:this.namespace,indirections:[...this.indirections],options:this.options,ancestors:t,allowMetaPatches:this.allowMetaPatches,useCircularStructures:this.useCircularStructures,basePath:this.basePath??[...Mw([...o,n,e]),"$ref"]});c=await Bw(c,u,{keyMap:Tv,nodeTypeGetter:Iv}),r.delete(e),this.indirections.pop(),c=c.clone(),c.setMetaProperty("ref-fields",{$ref:e.$ref?.toValue(),description:e.description?.toValue(),summary:e.summary?.toValue()}),c.setMetaProperty("ref-origin",s.uri);const p=void 0!==e.description,h=void 0!==e.summary;if(p&&"description"in c&&(c.description=e.description),h&&"summary"in c&&(c.summary=e.summary),this.allowMetaPatches&&bs(c)){const e=c;if(void 0===e.get("$$ref")){const t=lb(i,a);e.set("$$ref",t)}}return c}catch(t){const r=Dw(t),s=Lw(r,{baseDoc:this.reference.uri,$ref:e.$ref.toValue(),pointer:Ki(e.$ref.toValue()),fullPath:this.basePath??[...Mw([...o,n,e]),"$ref"]});return void this.options.dereference.dereferenceOpts?.errors?.push?.(s)}},async PathItemElement(e,t,n,r,o){try{const[t,r]=this.toAncestorLineage([...o,n]);if(!ms(e.$ref))return;if(Ns(["cycle"],e.$ref))return!1;if(t.some((t=>t.has(e))))return!1;if(!this.options.resolve.external&&Bg(e))return;const s=await this.toReference(e.$ref.toValue()),i=s.uri,a=lb(i,e.$ref.toValue());this.indirections.push(e);const l=Ki(a);let c=Ji(l,s.value.result);if(As(c)&&(c=Pm.refract(c)),this.indirections.includes(c))throw new Error("Recursive JSON Pointer detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);if(!this.useCircularStructures){if(t.some((e=>e.has(c)))){if(rb(i)||nb(i)){const t=new Pm({$ref:a},e.meta.clone(),e.attributes.clone());return t.get("$ref").classes.push("cycle"),t}return!1}}r.add(e);const u=$w({reference:s,namespace:this.namespace,indirections:[...this.indirections],options:this.options,ancestors:t,allowMetaPatches:this.allowMetaPatches,useCircularStructures:this.useCircularStructures,basePath:this.basePath??[...Mw([...o,n,e]),"$ref"]});c=await Bw(c,u,{keyMap:Tv,nodeTypeGetter:Iv}),r.delete(e),this.indirections.pop();const p=new Pm([...c.content],c.meta.clone(),c.attributes.clone());if(e.forEach(((e,t,n)=>{p.remove(t.toValue()),p.content.push(n)})),p.remove("$ref"),p.setMetaProperty("ref-fields",{$ref:e.$ref?.toValue()}),p.setMetaProperty("ref-origin",s.uri),this.allowMetaPatches&&void 0===p.get("$$ref")){const e=lb(i,a);p.set("$$ref",e)}return p}catch(t){const r=Dw(t),s=Lw(r,{baseDoc:this.reference.uri,$ref:e.$ref.toValue(),pointer:Ki(e.$ref.toValue()),fullPath:this.basePath??[...Mw([...o,n,e]),"$ref"]});return void this.options.dereference.dereferenceOpts?.errors?.push?.(s)}},async SchemaElement(e,t,n,r,o){try{const[t,r]=this.toAncestorLineage([...o,n]);if(!ms(e.$ref))return;if(Ns(["cycle"],e.$ref))return!1;if(t.some((t=>t.has(e))))return!1;let{reference:s}=this,{uri:i}=s;const a=Yb(i,e),l=ib(a),c=vb({uri:l}),u=!this.options.resolve.resolvers.some((e=>e.canRead(c))),p=!u,h=p&&i!==l;if(!this.options.resolve.external&&h)return;let f;this.indirections.push(e);try{if(u||p){f=ew(a,Qb(s.value.result))}else{s=await this.toReference(ub(a)),i=s.uri;const e=Ki(a);f=Qb(Ji(e,s.value.result))}}catch(e){if(!(p&&e instanceof zb))throw e;if(Hb(Gb(a))){s=await this.toReference(ub(a)),i=s.uri;const e=Gb(a);f=Zb(e,Qb(s.value.result))}else{s=await this.toReference(ub(a)),i=s.uri;const e=Ki(a);f=Qb(Ji(e,s.value.result))}}if(this.indirections.includes(f))throw new Error("Recursive Schema Object reference detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);if(!this.useCircularStructures){if(t.some((e=>e.has(f)))){if(rb(i)||nb(i)){const t=lb(i,a),n=new Lm({$ref:t},e.meta.clone(),e.attributes.clone());return n.get("$ref").classes.push("cycle"),n}return!1}}r.add(e);const d=$w({reference:s,namespace:this.namespace,indirections:[...this.indirections],options:this.options,useCircularStructures:this.useCircularStructures,allowMetaPatches:this.allowMetaPatches,ancestors:t,basePath:this.basePath??[...Mw([...o,n,e]),"$ref"]});if(f=await Bw(f,d,{keyMap:Tv,nodeTypeGetter:Iv}),r.delete(e),this.indirections.pop(),Kg(f)){const t=f.clone();return t.setMetaProperty("ref-fields",{$ref:e.$ref?.toValue()}),t.setMetaProperty("ref-origin",i),t}const m=new Lm([...f.content],f.meta.clone(),f.attributes.clone());if(e.forEach(((e,t,n)=>{m.remove(t.toValue()),m.content.push(n)})),m.remove("$ref"),m.setMetaProperty("ref-fields",{$ref:e.$ref?.toValue()}),m.setMetaProperty("ref-origin",i),this.allowMetaPatches&&void 0===m.get("$$ref")){const e=lb(i,a);m.set("$$ref",e)}return m}catch(t){const r=Dw(t),s=new Fw(`Could not resolve reference: ${r.message}`,{baseDoc:this.reference.uri,$ref:e.$ref.toValue(),fullPath:this.basePath??[...Mw([...o,n,e]),"$ref"]},r);return void this.options.dereference.dereferenceOpts?.errors?.push?.(s)}},async LinkElement(){},async ExampleElement(e,t,n,r,o){try{return await Nw.compose.methods.ExampleElement.call(this,e,t,n,r,o)}catch(t){const r=Dw(t),s=Lw(r,{baseDoc:this.reference.uri,externalValue:e.externalValue?.toValue(),fullPath:this.basePath??[...Mw([...o,n,e]),"externalValue"]});return void this.options.dereference.dereferenceOpts?.errors?.push?.(s)}}}}),qw=$w,Uw=Rw.compose.bind(),zw=Uw({init(e){let{parameterMacro:t,options:n}=e;this.parameterMacro=t,this.options=n},props:{parameterMacro:null,options:null,macroOperation:null,OperationElement:{enter(e){this.macroOperation=e},leave(){this.macroOperation=null}},ParameterElement:{leave(e,t,n,r,o){const s=null===this.macroOperation?null:Ti(this.macroOperation),i=Ti(e);try{const t=this.parameterMacro(s,i);e.set("default",t)}catch(e){const t=new Error(e,{cause:e});t.fullPath=Mw([...o,n]),this.options.dereference.dereferenceOpts?.errors?.push?.(t)}}}}}),Vw=Uw({init(e){let{modelPropertyMacro:t,options:n}=e;this.modelPropertyMacro=t,this.options=n},props:{modelPropertyMacro:null,options:null,SchemaElement:{leave(e,t,n,r,o){void 0!==e.properties&&bs(e.properties)&&e.properties.forEach((t=>{if(bs(t))try{const e=this.modelPropertyMacro(Ti(t));t.set("default",e)}catch(t){const r=new Error(t,{cause:t});r.fullPath=[...Mw([...o,n,e]),"properties"],this.options.dereference.dereferenceOpts?.errors?.push?.(r)}}))}}}});function Ww(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Jw(e){for(var t=1;t{const t=e.meta.clone(),n=e.attributes.clone();return new e.constructor(void 0,t,n)},Hw=e=>new Pt.c6(e.key,e.value,e.meta.clone(),e.attributes.clone()),Gw=(e,t)=>t.clone&&t.isMergeableElement(e)?Xw(Kw(e),e,t):e,Zw=(e,t,n)=>e.concat(t)["fantasy-land/map"]((e=>Gw(e,n))),Yw=(e,t,n)=>{const r=bs(e)?Kw(e):Kw(t);return bs(e)&&e.forEach(((e,t,o)=>{const s=Hw(o);s.value=Gw(e,n),r.content.push(s)})),t.forEach(((t,o,s)=>{const i=o.toValue();let a;if(bs(e)&&e.hasKey(i)&&n.isMergeableElement(t)){const r=e.get(i);a=Hw(s),a.value=((e,t)=>{if("function"!=typeof t.customMerge)return Xw;const n=t.customMerge(e,t);return"function"==typeof n?n:Xw})(o,n)(r,t)}else a=Hw(s),a.value=Gw(t,n);r.remove(i),r.content.push(a)})),r};function Xw(e,t,n){var r,o,s;const i={clone:!0,isMergeableElement:e=>bs(e)||ws(e),arrayElementMerge:Zw,objectElementMerge:Yw,customMerge:void 0},a=Jw(Jw({},i),n);a.isMergeableElement=null!==(r=a.isMergeableElement)&&void 0!==r?r:i.isMergeableElement,a.arrayElementMerge=null!==(o=a.arrayElementMerge)&&void 0!==o?o:i.arrayElementMerge,a.objectElementMerge=null!==(s=a.objectElementMerge)&&void 0!==s?s:i.objectElementMerge;const l=ws(t);return l===ws(e)?l&&"function"==typeof a.arrayElementMerge?a.arrayElementMerge(e,t,a):a.objectElementMerge(e,t,a):Gw(t,a)}Xw.all=(e,t)=>{if(!Array.isArray(e))throw new Error("first argument should be an array");return 0===e.length?new Pt.Sb:e.reduce(((e,n)=>Xw(e,n,t)),Kw(e[0]))};const Qw=Uw({init(e){let{options:t}=e;this.options=t},props:{options:null,SchemaElement:{leave(e,t,n,r,o){if(void 0===e.allOf)return;if(!ws(e.allOf)){const t=new TypeError("allOf must be an array");return t.fullPath=[...Mw([...o,n,e]),"allOf"],void this.options.dereference.dereferenceOpts?.errors?.push?.(t)}if(e.allOf.isEmpty)return new Lm(e.content.filter((e=>"allOf"!==e.key.toValue())),e.meta.clone(),e.attributes.clone());if(!e.allOf.content.every(Jg)){const t=new TypeError("Elements in allOf must be objects");return t.fullPath=[...Mw([...o,n,e]),"allOf"],void this.options.dereference.dereferenceOpts?.errors?.push?.(t)}const s=Xw.all([...e.allOf.content,e]);if(e.hasKey("$$ref")||s.remove("$$ref"),e.hasKey("example")){s.getMember("example").value=e.get("example")}if(e.hasKey("examples")){s.getMember("examples").value=e.get("examples")}return s.remove("allOf"),s}}}}),eE=fi[Symbol.for("nodejs.util.promisify.custom")],tE=Rw.compose({props:{useCircularStructures:!0,allowMetaPatches:!1,parameterMacro:null,modelPropertyMacro:null,mode:"non-strict",ancestors:null},init(){let{useCircularStructures:e=this.useCircularStructures,allowMetaPatches:t=this.allowMetaPatches,parameterMacro:n=this.parameterMacro,modelPropertyMacro:r=this.modelPropertyMacro,mode:o=this.mode,ancestors:s=[]}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.name="openapi-3-1-swagger-client",this.useCircularStructures=e,this.allowMetaPatches=t,this.parameterMacro=n,this.modelPropertyMacro=r,this.mode=o,this.ancestors=[...s]},methods:{async dereference(e,t){const n=[],r=zs(Rv),o=t.dereference.refSet??db();let s;o.has(e.uri)?s=o.find((t=>t.uri===e.uri)):(s=hb({uri:e.uri,value:e.parseResult}),o.add(s));const i=qw({reference:s,namespace:r,options:t,useCircularStructures:this.useCircularStructures,allowMetaPatches:this.allowMetaPatches,ancestors:this.ancestors});if(n.push(i),"function"==typeof this.parameterMacro){const e=zw({parameterMacro:this.parameterMacro,options:t});n.push(e)}if("function"==typeof this.modelPropertyMacro){const e=Vw({modelPropertyMacro:this.modelPropertyMacro,options:t});n.push(e)}if("strict"!==this.mode){const e=Qw({options:t});n.push(e)}const a=ri(n,{nodeTypeGetter:Iv}),l=await eE(o.rootRef.value,a,{keyMap:Tv,nodeTypeGetter:Iv});return null===t.dereference.refSet&&o.clean(),l}}}),nE=tE,rE=async e=>{const{spec:t,timeout:n,redirects:r,requestInterceptor:o,responseInterceptor:s,pathDiscriminator:i=[],allowMetaPatches:a=!1,useCircularStructures:l=!1,skipNormalization:c=!1,parameterMacro:u=null,modelPropertyMacro:p=null,mode:h="non-strict"}=e;try{const{cache:d}=rE,m=rb(ab())?ab():"https://smartbear.com/",g=Et(e),y=lb(m,g);let v;d.has(t)?v=d.get(t):(v=km.refract(t),v.classes.push("result"),d.set(t,v));const b=new zo([v]),w=0===(f=i).length?"":`/${f.map(Vi).join("/")}`,E=""===w?"":`#${w}`,x=Ji(w,v),S=hb({uri:y,value:b}),_=db({refs:[S]});""!==w&&(_.rootRef=null);const j=[new WeakSet([x])],O=[],k=((e,t,n)=>Ei({element:n}).transclude(e,t))(x,await Ab(x,{resolve:{baseURI:`${y}${E}`,resolvers:[Ew({timeout:n||1e4,redirects:r||10})],resolverOpts:{swaggerHTTPClientConfig:{requestInterceptor:o,responseInterceptor:s}},strategies:[lw()]},parse:{mediaType:zv.latest(),parsers:[_w({allowEmpty:!1,sourceMap:!1}),jw({allowEmpty:!1,sourceMap:!1}),xw({allowEmpty:!1,sourceMap:!1}),Sw({allowEmpty:!1,sourceMap:!1}),Ib({allowEmpty:!1,sourceMap:!1})]},dereference:{maxDepth:100,strategies:[nE({allowMetaPatches:a,useCircularStructures:l,parameterMacro:u,modelPropertyMacro:p,mode:h,ancestors:j})],refSet:_,dereferenceOpts:{errors:O}}}),v),A=c?k:bw(k);return{spec:Ti(A),errors:O}}catch(e){if(e instanceof Ui||e instanceof zi)return{spec:null,errors:[]};throw e}var f};rE.cache=new WeakMap;const oE=rE,sE={name:"openapi-3-1-apidom",match(e){let{spec:t}=e;return Ot(t)},normalize(e){let{spec:t}=e;return vw(bw)(t)},resolve:async e=>oE(e)},iE=e=>async t=>(async e=>{const{spec:t,requestInterceptor:n,responseInterceptor:r}=e,o=Et(e),s=xt(e),i=t||await Ze(s,{requestInterceptor:n,responseInterceptor:r})(o),a=f()(f()({},e),{},{spec:i});return e.strategies.find((e=>e.match(a))).resolve(a)})(f()(f()({},e),t)),aE=iE({strategies:[Ct,At,_t]});var lE=n(88436),cE=n.n(lE),uE=n(27361),pE=n.n(uE),hE=n(76489);function fE(e){return"[object Object]"===Object.prototype.toString.call(e)}function dE(e){var t,n;return!1!==fE(e)&&(void 0===(t=e.constructor)||!1!==fE(n=t.prototype)&&!1!==n.hasOwnProperty("isPrototypeOf"))}const mE={body:function(e){let{req:t,value:n}=e;t.body=n},header:function(e){let{req:t,parameter:n,value:r}=e;t.headers=t.headers||{},void 0!==r&&(t.headers[n.name]=r)},query:function(e){let{req:t,value:n,parameter:r}=e;t.query=t.query||{},!1===n&&"boolean"===r.type&&(n="false");0===n&&["number","integer"].indexOf(r.type)>-1&&(n="0");if(n)t.query[r.name]={collectionFormat:r.collectionFormat,value:n};else if(r.allowEmptyValue&&void 0!==n){const e=r.name;t.query[e]=t.query[e]||{},t.query[e].allowEmptyValue=!0}},path:function(e){let{req:t,value:n,parameter:r}=e;t.url=t.url.split(`{${r.name}}`).join(encodeURIComponent(n))},formData:function(e){let{req:t,value:n,parameter:r}=e;(n||r.allowEmptyValue)&&(t.form=t.form||{},t.form[r.name]={value:n,allowEmptyValue:r.allowEmptyValue,collectionFormat:r.collectionFormat})}};function gE(e,t){return t.includes("application/json")?"string"==typeof e?e:JSON.stringify(e):e.toString()}function yE(e){let{req:t,value:n,parameter:r}=e;const{name:o,style:s,explode:i,content:a}=r;if(a){const e=Object.keys(a)[0];return void(t.url=t.url.split(`{${o}}`).join(st(gE(n,e),{escape:!0})))}const l=it({key:r.name,value:n,style:s||"simple",explode:i||!1,escape:!0});t.url=t.url.split(`{${o}}`).join(l)}function vE(e){let{req:t,value:n,parameter:r}=e;if(t.query=t.query||{},r.content){const e=gE(n,Object.keys(r.content)[0]);if(e)t.query[r.name]=e;else if(r.allowEmptyValue&&void 0!==n){const e=r.name;t.query[e]=t.query[e]||{},t.query[e].allowEmptyValue=!0}}else if(!1===n&&(n="false"),0===n&&(n="0"),n){const{style:e,explode:o,allowReserved:s}=r;t.query[r.name]={value:n,serializationOption:{style:e,explode:o,allowReserved:s}}}else if(r.allowEmptyValue&&void 0!==n){const e=r.name;t.query[e]=t.query[e]||{},t.query[e].allowEmptyValue=!0}}const bE=["accept","authorization","content-type"];function wE(e){let{req:t,parameter:n,value:r}=e;if(t.headers=t.headers||{},!(bE.indexOf(n.name.toLowerCase())>-1))if(n.content){const e=Object.keys(n.content)[0];t.headers[n.name]=gE(r,e)}else void 0!==r&&(t.headers[n.name]=it({key:n.name,value:r,style:n.style||"simple",explode:void 0!==n.explode&&n.explode,escape:!1}))}function EE(e){let{req:t,parameter:n,value:r}=e;t.headers=t.headers||{};const o=typeof r;if(n.content){const e=Object.keys(n.content)[0];t.headers.Cookie=`${n.name}=${gE(r,e)}`}else if("undefined"!==o){const e="object"===o&&!Array.isArray(r)&&n.explode?"":`${n.name}=`;t.headers.Cookie=e+it({key:n.name,value:r,escape:!1,style:n.style||"form",explode:void 0!==n.explode&&n.explode})}}const xE="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:window,{btoa:SE}=xE,_E=SE;function jE(e,t){const{operation:n,requestBody:r,securities:o,spec:s,attachContentTypeForEmptyPayload:i}=e;let{requestContentType:a}=e;t=function(e){let{request:t,securities:n={},operation:r={},spec:o}=e;const s=f()({},t),{authorized:i={}}=n,a=r.security||o.security||[],l=i&&!!Object.keys(i).length,c=pE()(o,["components","securitySchemes"])||{};if(s.headers=s.headers||{},s.query=s.query||{},!Object.keys(n).length||!l||!a||Array.isArray(r.security)&&!r.security.length)return t;return a.forEach((e=>{Object.keys(e).forEach((e=>{const t=i[e],n=c[e];if(!t)return;const r=t.value||t,{type:o}=n;if(t)if("apiKey"===o)"query"===n.in&&(s.query[n.name]=r),"header"===n.in&&(s.headers[n.name]=r),"cookie"===n.in&&(s.cookies[n.name]=r);else if("http"===o){if(/^basic$/i.test(n.scheme)){const e=r.username||"",t=r.password||"",n=_E(`${e}:${t}`);s.headers.Authorization=`Basic ${n}`}/^bearer$/i.test(n.scheme)&&(s.headers.Authorization=`Bearer ${r}`)}else if("oauth2"===o||"openIdConnect"===o){const e=t.token||{},r=e[n["x-tokenName"]||"access_token"];let o=e.token_type;o&&"bearer"!==o.toLowerCase()||(o="Bearer"),s.headers.Authorization=`${o} ${r}`}}))})),s}({request:t,securities:o,operation:n,spec:s});const l=n.requestBody||{},c=Object.keys(l.content||{}),u=a&&c.indexOf(a)>-1;if(r||i){if(a&&u)t.headers["Content-Type"]=a;else if(!a){const e=c[0];e&&(t.headers["Content-Type"]=e,a=e)}}else a&&u&&(t.headers["Content-Type"]=a);if(!e.responseContentType&&n.responses){const e=Object.entries(n.responses).filter((e=>{let[t,n]=e;const r=parseInt(t,10);return r>=200&&r<300&&dE(n.content)})).reduce(((e,t)=>{let[,n]=t;return e.concat(Object.keys(n.content))}),[]);e.length>0&&(t.headers.accept=e.join(", "))}if(r)if(a){if(c.indexOf(a)>-1)if("application/x-www-form-urlencoded"===a||"multipart/form-data"===a)if("object"==typeof r){const e=(l.content[a]||{}).encoding||{};t.form={},Object.keys(r).forEach((n=>{t.form[n]={value:r[n],encoding:e[n]||{}}}))}else t.form=r;else t.body=r}else t.body=r;return t}function OE(e,t){const{spec:n,operation:r,securities:o,requestContentType:s,responseContentType:i,attachContentTypeForEmptyPayload:a}=e;if(t=function(e){let{request:t,securities:n={},operation:r={},spec:o}=e;const s=f()({},t),{authorized:i={},specSecurity:a=[]}=n,l=r.security||a,c=i&&!!Object.keys(i).length,u=o.securityDefinitions;if(s.headers=s.headers||{},s.query=s.query||{},!Object.keys(n).length||!c||!l||Array.isArray(r.security)&&!r.security.length)return t;return l.forEach((e=>{Object.keys(e).forEach((e=>{const t=i[e];if(!t)return;const{token:n}=t,r=t.value||t,o=u[e],{type:a}=o,l=o["x-tokenName"]||"access_token",c=n&&n[l];let p=n&&n.token_type;if(t)if("apiKey"===a){const e="query"===o.in?"query":"headers";s[e]=s[e]||{},s[e][o.name]=r}else if("basic"===a)if(r.header)s.headers.authorization=r.header;else{const e=r.username||"",t=r.password||"";r.base64=_E(`${e}:${t}`),s.headers.authorization=`Basic ${r.base64}`}else"oauth2"===a&&c&&(p=p&&"bearer"!==p.toLowerCase()?p:"Bearer",s.headers.authorization=`${p} ${c}`)}))})),s}({request:t,securities:o,operation:r,spec:n}),t.body||t.form||a)s?t.headers["Content-Type"]=s:Array.isArray(r.consumes)?[t.headers["Content-Type"]]=r.consumes:Array.isArray(n.consumes)?[t.headers["Content-Type"]]=n.consumes:r.parameters&&r.parameters.filter((e=>"file"===e.type)).length?t.headers["Content-Type"]="multipart/form-data":r.parameters&&r.parameters.filter((e=>"formData"===e.in)).length&&(t.headers["Content-Type"]="application/x-www-form-urlencoded");else if(s){const e=r.parameters&&r.parameters.filter((e=>"body"===e.in)).length>0,n=r.parameters&&r.parameters.filter((e=>"formData"===e.in)).length>0;(e||n)&&(t.headers["Content-Type"]=s)}return!i&&Array.isArray(r.produces)&&r.produces.length>0&&(t.headers.accept=r.produces.join(", ")),t}function kE(e,t){return`${t.toLowerCase()}-${e}`}const AE=["http","fetch","spec","operationId","pathName","method","parameters","securities"],CE=e=>Array.isArray(e)?e:[],PE=ue("OperationNotFoundError",(function(e,t,n){this.originalError=n,Object.assign(this,t||{})})),NE=(e,t)=>t.filter((t=>t.name===e)),IE=e=>{const t={};e.forEach((e=>{t[e.in]||(t[e.in]={}),t[e.in][e.name]=e}));const n=[];return Object.keys(t).forEach((e=>{Object.keys(t[e]).forEach((r=>{n.push(t[e][r])}))})),n},TE={buildRequest:ME};function RE(e){let{http:t,fetch:n,spec:r,operationId:o,pathName:s,method:i,parameters:a,securities:l}=e,c=cE()(e,AE);const u=t||n||ct;s&&i&&!o&&(o=kE(s,i));const p=TE.buildRequest(f()({spec:r,operationId:o,parameters:a,securities:l,http:u},c));return p.body&&(dE(p.body)||Array.isArray(p.body))&&(p.body=JSON.stringify(p.body)),u(p)}function ME(e){const{spec:t,operationId:n,responseContentType:r,scheme:o,requestInterceptor:s,responseInterceptor:i,contextUrl:a,userFetch:l,server:c,serverVariables:p,http:h,signal:d}=e;let{parameters:m,parameterBuilders:g}=e;const y=kt(t);g||(g=y?u:mE);let v={url:"",credentials:h&&h.withCredentials?"include":"same-origin",headers:{},cookies:{}};d&&(v.signal=d),s&&(v.requestInterceptor=s),i&&(v.responseInterceptor=i),l&&(v.userFetch=l);const b=function(e,t){return e&&e.paths?function(e,t){return function(e,t,n){if(!e||"object"!=typeof e||!e.paths||"object"!=typeof e.paths)return null;const{paths:r}=e;for(const o in r)for(const s in r[o]){if("PARAMETERS"===s.toUpperCase())continue;const i=r[o][s];if(!i||"object"!=typeof i)continue;const a={spec:e,pathName:o,method:s.toUpperCase(),operation:i},l=t(a);if(n&&l)return a}}(e,t,!0)||null}(e,(e=>{let{pathName:n,method:r,operation:o}=e;if(!o||"object"!=typeof o)return!1;const s=o.operationId;return[(0,He.Z)(o,n,r),kE(n,r),s].some((e=>e&&e===t))})):null}(t,n);if(!b)throw new PE(`Operation ${n} not found`);const{operation:w={},method:E,pathName:x}=b;if(v.url+=function(e){const t=kt(e.spec);return t?function(e){let{spec:t,pathName:n,method:r,server:o,contextUrl:s,serverVariables:i={}}=e;const a=pE()(t,["paths",n,(r||"").toLowerCase(),"servers"])||pE()(t,["paths",n,"servers"])||pE()(t,["servers"]);let l="",c=null;if(o&&a&&a.length){const e=a.map((e=>e.url));e.indexOf(o)>-1&&(l=o,c=a[e.indexOf(o)])}!l&&a&&a.length&&(l=a[0].url,[c]=a);if(l.indexOf("{")>-1){(function(e){const t=[],n=/{([^}]+)}/g;let r;for(;r=n.exec(e);)t.push(r[1]);return t})(l).forEach((e=>{if(c.variables&&c.variables[e]){const t=c.variables[e],n=i[e]||t.default,r=new RegExp(`{${e}}`,"g");l=l.replace(r,n)}}))}return function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";const n=e&&t?ce.parse(ce.resolve(t,e)):ce.parse(e),r=ce.parse(t),o=DE(n.protocol)||DE(r.protocol)||"",s=n.host||r.host,i=n.pathname||"";let a;a=o&&s?`${o}://${s+i}`:i;return"/"===a[a.length-1]?a.slice(0,-1):a}(l,s)}(e):function(e){let{spec:t,scheme:n,contextUrl:r=""}=e;const o=ce.parse(r),s=Array.isArray(t.schemes)?t.schemes[0]:null,i=n||s||DE(o.protocol)||"http",a=t.host||o.host||"",l=t.basePath||"";let c;c=i&&a?`${i}://${a+l}`:l;return"/"===c[c.length-1]?c.slice(0,-1):c}(e)}({spec:t,scheme:o,contextUrl:a,server:c,serverVariables:p,pathName:x,method:E}),!n)return delete v.cookies,v;v.url+=x,v.method=`${E}`.toUpperCase(),m=m||{};const S=t.paths[x]||{};r&&(v.headers.accept=r);const _=IE([].concat(CE(w.parameters)).concat(CE(S.parameters)));_.forEach((e=>{const n=g[e.in];let r;if("body"===e.in&&e.schema&&e.schema.properties&&(r=m),r=e&&e.name&&m[e.name],void 0===r?r=e&&e.name&&m[`${e.in}.${e.name}`]:NE(e.name,_).length>1&&console.warn(`Parameter '${e.name}' is ambiguous because the defined spec has more than one parameter with the name: '${e.name}' and the passed-in parameter values did not define an 'in' value.`),null!==r){if(void 0!==e.default&&void 0===r&&(r=e.default),void 0===r&&e.required&&!e.allowEmptyValue)throw new Error(`Required parameter ${e.name} is not provided`);if(y&&e.schema&&"object"===e.schema.type&&"string"==typeof r)try{r=JSON.parse(r)}catch(e){throw new Error("Could not parse object parameter value string as JSON")}n&&n({req:v,parameter:e,value:r,operation:w,spec:t})}}));const j=f()(f()({},e),{},{operation:w});if(v=y?jE(j,v):OE(j,v),v.cookies&&Object.keys(v.cookies).length){const e=Object.keys(v.cookies).reduce(((e,t)=>{const n=v.cookies[t];return e+(e?"&":"")+hE.serialize(t,n)}),"");v.headers.Cookie=e}return v.cookies&&delete v.cookies,wt(v),v}const DE=e=>e?e.replace(/\W/g,""):null;const FE=e=>async function(t,n){let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return async function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{returnEntireTree:r,baseDoc:o,requestInterceptor:s,responseInterceptor:i,parameterMacro:a,modelPropertyMacro:l,useCircularStructures:c,strategies:u}=n,p={spec:e,pathDiscriminator:t,baseDoc:o,requestInterceptor:s,responseInterceptor:i,parameterMacro:a,modelPropertyMacro:l,useCircularStructures:c,strategies:u},h=u.find((e=>e.match(p))).normalize(p),d=await aE(f()(f()({},p),{},{spec:h,allowMetaPatches:!0,skipNormalization:!0}));return!r&&Array.isArray(t)&&t.length&&(d.spec=pE()(d.spec,t)||null),d}(t,n,f()(f()({},e),r))};FE({strategies:[Ct,At,_t]});var LE=n(34852);function BE(e){let{configs:t,getConfigs:n}=e;return{fn:{fetch:(r=ct,o=t.preFetch,s=t.postFetch,s=s||(e=>e),o=o||(e=>e),e=>("string"==typeof e&&(e={url:e}),lt.mergeInQueryOrForm(e),e=o(e),s(r(e)))),buildRequest:ME,execute:RE,resolve:iE({strategies:[sE,Ct,At,_t]}),resolveSubtree:async function(e,t){let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const o=n(),s={modelPropertyMacro:o.modelPropertyMacro,parameterMacro:o.parameterMacro,requestInterceptor:o.requestInterceptor,responseInterceptor:o.responseInterceptor,strategies:[sE,Ct,At,_t]};return FE(s)(e,t,r)},serializeRes:pt,opId:He.Z},statePlugins:{configs:{wrapActions:{loaded:LE.loaded}}}};var r,o,s}},98525:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(90242);function o(){return{fn:{shallowEqualKeys:r.be}}}},48347:(e,t,n)=>{"use strict";n.r(t),n.d(t,{getDisplayName:()=>r});const r=e=>e.displayName||e.name||"Component"},73420:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(35627),o=n.n(r),s=n(90242),i=n(11092),a=n(48347),l=n(60314);const c=e=>{let{getComponents:t,getStore:n,getSystem:r}=e;const c=(u=(0,i.getComponent)(r,n,t),(0,s.HP)(u,(function(){for(var e=arguments.length,t=new Array(e),n=0;n(0,l.Z)(e,(function(){for(var e=arguments.length,t=new Array(e),n=0;n{"use strict";n.r(t),n.d(t,{getComponent:()=>X,render:()=>Y,withMappedContainer:()=>Z});var r=n(23101),o=n.n(r),s=n(28222),i=n.n(s),a=n(67294),l=n(73935),c=n(97779),u=n(61688),p=n(52798);let h=function(e){e()};const f=()=>h,d=(0,a.createContext)(null);let m=null;var g=n(87462),y=n(63366),v=n(8679),b=n.n(v),w=n(59864);const E=["initMapStateToProps","initMapDispatchToProps","initMergeProps"];function x(e,t,n,r,{areStatesEqual:o,areOwnPropsEqual:s,areStatePropsEqual:i}){let a,l,c,u,p,h=!1;function f(h,f){const d=!s(f,l),m=!o(h,a,f,l);return a=h,l=f,d&&m?(c=e(a,l),t.dependsOnOwnProps&&(u=t(r,l)),p=n(c,u,l),p):d?(e.dependsOnOwnProps&&(c=e(a,l)),t.dependsOnOwnProps&&(u=t(r,l)),p=n(c,u,l),p):m?function(){const t=e(a,l),r=!i(t,c);return c=t,r&&(p=n(c,u,l)),p}():p}return function(o,s){return h?f(o,s):(a=o,l=s,c=e(a,l),u=t(r,l),p=n(c,u,l),h=!0,p)}}function S(e){return function(t){const n=e(t);function r(){return n}return r.dependsOnOwnProps=!1,r}}function _(e){return e.dependsOnOwnProps?Boolean(e.dependsOnOwnProps):1!==e.length}function j(e,t){return function(t,{displayName:n}){const r=function(e,t){return r.dependsOnOwnProps?r.mapToProps(e,t):r.mapToProps(e,void 0)};return r.dependsOnOwnProps=!0,r.mapToProps=function(t,n){r.mapToProps=e,r.dependsOnOwnProps=_(e);let o=r(t,n);return"function"==typeof o&&(r.mapToProps=o,r.dependsOnOwnProps=_(o),o=r(t,n)),o},r}}function O(e,t){return(n,r)=>{throw new Error(`Invalid value of type ${typeof e} for ${t} argument when connecting component ${r.wrappedComponentName}.`)}}function k(e,t,n){return(0,g.Z)({},n,e,t)}const A={notify(){},get:()=>[]};function C(e,t){let n,r=A;function o(){i.onStateChange&&i.onStateChange()}function s(){n||(n=t?t.addNestedSub(o):e.subscribe(o),r=function(){const e=f();let t=null,n=null;return{clear(){t=null,n=null},notify(){e((()=>{let e=t;for(;e;)e.callback(),e=e.next}))},get(){let e=[],n=t;for(;n;)e.push(n),n=n.next;return e},subscribe(e){let r=!0,o=n={callback:e,next:null,prev:n};return o.prev?o.prev.next=o:t=o,function(){r&&null!==t&&(r=!1,o.next?o.next.prev=o.prev:n=o.prev,o.prev?o.prev.next=o.next:t=o.next)}}}}())}const i={addNestedSub:function(e){return s(),r.subscribe(e)},notifyNestedSubs:function(){r.notify()},handleChangeWrapper:o,isSubscribed:function(){return Boolean(n)},trySubscribe:s,tryUnsubscribe:function(){n&&(n(),n=void 0,r.clear(),r=A)},getListeners:()=>r};return i}const P=!("undefined"==typeof window||void 0===window.document||void 0===window.document.createElement)?a.useLayoutEffect:a.useEffect;function N(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function I(e,t){if(N(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;const n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(let r=0;r{throw new Error("uSES not initialized!")};const M=[null,null];function D(e,t,n,r,o,s){e.current=r,n.current=!1,o.current&&(o.current=null,s())}function F(e,t){return e===t}const L=function(e,t,n,{pure:r,areStatesEqual:o=F,areOwnPropsEqual:s=I,areStatePropsEqual:i=I,areMergedPropsEqual:l=I,forwardRef:c=!1,context:u=d}={}){const p=u,h=function(e){return e?"function"==typeof e?j(e):O(e,"mapStateToProps"):S((()=>({})))}(e),f=function(e){return e&&"object"==typeof e?S((t=>function(e,t){const n={};for(const r in e){const o=e[r];"function"==typeof o&&(n[r]=(...e)=>t(o(...e)))}return n}(e,t))):e?"function"==typeof e?j(e):O(e,"mapDispatchToProps"):S((e=>({dispatch:e})))}(t),m=function(e){return e?"function"==typeof e?function(e){return function(t,{displayName:n,areMergedPropsEqual:r}){let o,s=!1;return function(t,n,i){const a=e(t,n,i);return s?r(a,o)||(o=a):(s=!0,o=a),o}}}(e):O(e,"mergeProps"):()=>k}(n),v=Boolean(e);return e=>{const t=e.displayName||e.name||"Component",n=`Connect(${t})`,r={shouldHandleStateChanges:v,displayName:n,wrappedComponentName:t,WrappedComponent:e,initMapStateToProps:h,initMapDispatchToProps:f,initMergeProps:m,areStatesEqual:o,areStatePropsEqual:i,areOwnPropsEqual:s,areMergedPropsEqual:l};function u(t){const[n,o,s]=(0,a.useMemo)((()=>{const{reactReduxForwardedRef:e}=t,n=(0,y.Z)(t,T);return[t.context,e,n]}),[t]),i=(0,a.useMemo)((()=>n&&n.Consumer&&(0,w.isContextConsumer)(a.createElement(n.Consumer,null))?n:p),[n,p]),l=(0,a.useContext)(i),c=Boolean(t.store)&&Boolean(t.store.getState)&&Boolean(t.store.dispatch),u=Boolean(l)&&Boolean(l.store);const h=c?t.store:l.store,f=u?l.getServerState:h.getState,d=(0,a.useMemo)((()=>function(e,t){let{initMapStateToProps:n,initMapDispatchToProps:r,initMergeProps:o}=t,s=(0,y.Z)(t,E);return x(n(e,s),r(e,s),o(e,s),e,s)}(h.dispatch,r)),[h]),[m,b]=(0,a.useMemo)((()=>{if(!v)return M;const e=C(h,c?void 0:l.subscription),t=e.notifyNestedSubs.bind(e);return[e,t]}),[h,c,l]),S=(0,a.useMemo)((()=>c?l:(0,g.Z)({},l,{subscription:m})),[c,l,m]),_=(0,a.useRef)(),j=(0,a.useRef)(s),O=(0,a.useRef)(),k=(0,a.useRef)(!1),A=((0,a.useRef)(!1),(0,a.useRef)(!1)),N=(0,a.useRef)();P((()=>(A.current=!0,()=>{A.current=!1})),[]);const I=(0,a.useMemo)((()=>()=>O.current&&s===j.current?O.current:d(h.getState(),s)),[h,s]),F=(0,a.useMemo)((()=>e=>m?function(e,t,n,r,o,s,i,a,l,c,u){if(!e)return()=>{};let p=!1,h=null;const f=()=>{if(p||!a.current)return;const e=t.getState();let n,f;try{n=r(e,o.current)}catch(e){f=e,h=e}f||(h=null),n===s.current?i.current||c():(s.current=n,l.current=n,i.current=!0,u())};return n.onStateChange=f,n.trySubscribe(),f(),()=>{if(p=!0,n.tryUnsubscribe(),n.onStateChange=null,h)throw h}}(v,h,m,d,j,_,k,A,O,b,e):()=>{}),[m]);var L,B,$;let q;L=D,B=[j,_,k,s,O,b],P((()=>L(...B)),$);try{q=R(F,I,f?()=>d(f(),s):I)}catch(e){throw N.current&&(e.message+=`\nThe error may be correlated with this previous error:\n${N.current.stack}\n\n`),e}P((()=>{N.current=void 0,O.current=void 0,_.current=q}));const U=(0,a.useMemo)((()=>a.createElement(e,(0,g.Z)({},q,{ref:o}))),[o,e,q]);return(0,a.useMemo)((()=>v?a.createElement(i.Provider,{value:S},U):U),[i,U,S])}const d=a.memo(u);if(d.WrappedComponent=e,d.displayName=u.displayName=n,c){const t=a.forwardRef((function(e,t){return a.createElement(d,(0,g.Z)({},e,{reactReduxForwardedRef:t}))}));return t.displayName=n,t.WrappedComponent=e,b()(t,e)}return b()(d,e)}};const B=function({store:e,context:t,children:n,serverState:r}){const o=(0,a.useMemo)((()=>{const t=C(e);return{store:e,subscription:t,getServerState:r?()=>r:void 0}}),[e,r]),s=(0,a.useMemo)((()=>e.getState()),[e]);P((()=>{const{subscription:t}=o;return t.onStateChange=t.notifyNestedSubs,t.trySubscribe(),s!==e.getState()&&t.notifyNestedSubs(),()=>{t.tryUnsubscribe(),t.onStateChange=void 0}}),[o,s]);const i=t||d;return a.createElement(i.Provider,{value:o},n)};var $,q;$=p.useSyncExternalStoreWithSelector,m=$,(e=>{R=e})(u.useSyncExternalStore),q=l.unstable_batchedUpdates,h=q;var U=n(57557),z=n.n(U),V=n(6557),W=n.n(V);const J=e=>t=>{const{fn:n}=e();class r extends a.Component{render(){return a.createElement(t,o()({},e(),this.props,this.context))}}return r.displayName=`WithSystem(${n.getDisplayName(t)})`,r},K=(e,t)=>n=>{const{fn:r}=e();class s extends a.Component{render(){return a.createElement(B,{store:t},a.createElement(n,o()({},this.props,this.context)))}}return s.displayName=`WithRoot(${r.getDisplayName(n)})`,s},H=(e,t,n)=>(0,c.qC)(n?K(e,n):W(),L(((n,r)=>{var o;const s={...r,...e()},i=(null===(o=t.prototype)||void 0===o?void 0:o.mapStateToProps)||(e=>({state:e}));return i(n,s)})),J(e))(t),G=(e,t,n,r)=>{for(const o in t){const s=t[o];"function"==typeof s&&s(n[o],r[o],e())}},Z=(e,t,n)=>(t,r)=>{const{fn:o}=e(),s=n(t,"root");class l extends a.Component{constructor(t,n){super(t,n),G(e,r,t,{})}UNSAFE_componentWillReceiveProps(t){G(e,r,t,this.props)}render(){const e=z()(this.props,r?i()(r):[]);return a.createElement(s,e)}}return l.displayName=`WithMappedContainer(${o.getDisplayName(s)})`,l},Y=(e,t,n,r)=>o=>{const s=n(e,t,r)("App","root");l.render(a.createElement(s,null),o)},X=(e,t,n)=>function(r,o){let s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if("string"!=typeof r)throw new TypeError("Need a string, to fetch a component. Was given a "+typeof r);const i=n(r);return i?o?"root"===o?H(e,i,t()):H(e,i):i:(s.failSilently||e().log.warn("Could not find component:",r),null)}},33424:(e,t,n)=>{"use strict";n.d(t,{d3:()=>D,C2:()=>ee});var r=n(28222),o=n.n(r),s=n(58118),i=n.n(s),a=n(63366);function l(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0;return function(e){if(0===e.length||1===e.length)return e;var t,n,r=e.join(".");return m[r]||(m[r]=0===(n=(t=e).length)||1===n?t:2===n?[t[0],t[1],"".concat(t[0],".").concat(t[1]),"".concat(t[1],".").concat(t[0])]:3===n?[t[0],t[1],t[2],"".concat(t[0],".").concat(t[1]),"".concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[0]),"".concat(t[1],".").concat(t[2]),"".concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[1]),"".concat(t[0],".").concat(t[1],".").concat(t[2]),"".concat(t[0],".").concat(t[2],".").concat(t[1]),"".concat(t[1],".").concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[0],".").concat(t[1]),"".concat(t[2],".").concat(t[1],".").concat(t[0])]:n>=4?[t[0],t[1],t[2],t[3],"".concat(t[0],".").concat(t[1]),"".concat(t[0],".").concat(t[2]),"".concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[0]),"".concat(t[1],".").concat(t[2]),"".concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[1]),"".concat(t[2],".").concat(t[3]),"".concat(t[3],".").concat(t[0]),"".concat(t[3],".").concat(t[1]),"".concat(t[3],".").concat(t[2]),"".concat(t[0],".").concat(t[1],".").concat(t[2]),"".concat(t[0],".").concat(t[1],".").concat(t[3]),"".concat(t[0],".").concat(t[2],".").concat(t[1]),"".concat(t[0],".").concat(t[2],".").concat(t[3]),"".concat(t[0],".").concat(t[3],".").concat(t[1]),"".concat(t[0],".").concat(t[3],".").concat(t[2]),"".concat(t[1],".").concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[2],".").concat(t[0]),"".concat(t[1],".").concat(t[2],".").concat(t[3]),"".concat(t[1],".").concat(t[3],".").concat(t[0]),"".concat(t[1],".").concat(t[3],".").concat(t[2]),"".concat(t[2],".").concat(t[0],".").concat(t[1]),"".concat(t[2],".").concat(t[0],".").concat(t[3]),"".concat(t[2],".").concat(t[1],".").concat(t[0]),"".concat(t[2],".").concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[3],".").concat(t[0]),"".concat(t[2],".").concat(t[3],".").concat(t[1]),"".concat(t[3],".").concat(t[0],".").concat(t[1]),"".concat(t[3],".").concat(t[0],".").concat(t[2]),"".concat(t[3],".").concat(t[1],".").concat(t[0]),"".concat(t[3],".").concat(t[1],".").concat(t[2]),"".concat(t[3],".").concat(t[2],".").concat(t[0]),"".concat(t[3],".").concat(t[2],".").concat(t[1]),"".concat(t[0],".").concat(t[1],".").concat(t[2],".").concat(t[3]),"".concat(t[0],".").concat(t[1],".").concat(t[3],".").concat(t[2]),"".concat(t[0],".").concat(t[2],".").concat(t[1],".").concat(t[3]),"".concat(t[0],".").concat(t[2],".").concat(t[3],".").concat(t[1]),"".concat(t[0],".").concat(t[3],".").concat(t[1],".").concat(t[2]),"".concat(t[0],".").concat(t[3],".").concat(t[2],".").concat(t[1]),"".concat(t[1],".").concat(t[0],".").concat(t[2],".").concat(t[3]),"".concat(t[1],".").concat(t[0],".").concat(t[3],".").concat(t[2]),"".concat(t[1],".").concat(t[2],".").concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[2],".").concat(t[3],".").concat(t[0]),"".concat(t[1],".").concat(t[3],".").concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[3],".").concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[0],".").concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[0],".").concat(t[3],".").concat(t[1]),"".concat(t[2],".").concat(t[1],".").concat(t[0],".").concat(t[3]),"".concat(t[2],".").concat(t[1],".").concat(t[3],".").concat(t[0]),"".concat(t[2],".").concat(t[3],".").concat(t[0],".").concat(t[1]),"".concat(t[2],".").concat(t[3],".").concat(t[1],".").concat(t[0]),"".concat(t[3],".").concat(t[0],".").concat(t[1],".").concat(t[2]),"".concat(t[3],".").concat(t[0],".").concat(t[2],".").concat(t[1]),"".concat(t[3],".").concat(t[1],".").concat(t[0],".").concat(t[2]),"".concat(t[3],".").concat(t[1],".").concat(t[2],".").concat(t[0]),"".concat(t[3],".").concat(t[2],".").concat(t[0],".").concat(t[1]),"".concat(t[3],".").concat(t[2],".").concat(t[1],".").concat(t[0])]:void 0),m[r]}(e.filter((function(e){return"token"!==e}))).reduce((function(e,t){return d(d({},e),n[t])}),t)}function y(e){return e.join(" ")}function v(e){var t=e.node,n=e.stylesheet,r=e.style,o=void 0===r?{}:r,s=e.useInlineStyles,i=e.key,a=t.properties,l=t.type,c=t.tagName,u=t.value;if("text"===l)return u;if(c){var f,m=function(e,t){var n=0;return function(r){return n+=1,r.map((function(r,o){return v({node:r,stylesheet:e,useInlineStyles:t,key:"code-segment-".concat(n,"-").concat(o)})}))}}(n,s);if(s){var b=Object.keys(n).reduce((function(e,t){return t.split(".").forEach((function(t){e.includes(t)||e.push(t)})),e}),[]),w=a.className&&a.className.includes("token")?["token"]:[],E=a.className&&w.concat(a.className.filter((function(e){return!b.includes(e)})));f=d(d({},a),{},{className:y(E)||void 0,style:g(a.className,Object.assign({},a.style,o),n)})}else f=d(d({},a),{},{className:y(a.className)});var x=m(t.children);return p.createElement(c,(0,h.Z)({key:i},f),x)}}const b=function(e,t){return-1!==e.listLanguages().indexOf(t)};var w=["language","children","style","customStyle","codeTagProps","useInlineStyles","showLineNumbers","showInlineLineNumbers","startingLineNumber","lineNumberContainerStyle","lineNumberStyle","wrapLines","wrapLongLines","lineProps","renderer","PreTag","CodeTag","code","astGenerator"];function E(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function x(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=0;r2&&void 0!==arguments[2]?arguments[2]:[];return t||c.length>0?function(e,t){return k({children:e,lineNumber:t,lineNumberStyle:a,largestLineNumber:i,showInlineLineNumbers:o,lineProps:n,className:arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],showLineNumbers:r,wrapLongLines:l})}(e,s,c):function(e,t){if(r&&t&&o){var n=O(a,t,i);e.unshift(j(t,n))}return e}(e,s)}for(var m=function(){var e=u[f],t=e.children[0].value;if(t.match(S)){var n=t.split("\n");n.forEach((function(t,o){var i=r&&p.length+s,a={type:"text",value:"".concat(t,"\n")};if(0===o){var l=d(u.slice(h+1,f).concat(k({children:[a],className:e.properties.className})),i);p.push(l)}else if(o===n.length-1){var c=u[f+1]&&u[f+1].children&&u[f+1].children[0],m={type:"text",value:"".concat(t)};if(c){var g=k({children:[m],className:e.properties.className});u.splice(f+1,0,g)}else{var y=d([m],i,e.properties.className);p.push(y)}}else{var v=d([a],i,e.properties.className);p.push(v)}})),h=f}f++};f=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,w);z=z||I;var W=d?p.createElement(_,{containerStyle:E,codeStyle:c.style||{},numberStyle:j,startingLineNumber:v,codeString:U}):null,J=o.hljs||o['pre[class*="language-"]']||{backgroundColor:"#fff"},K=N(z)?"hljs":"prismjs",H=h?Object.assign({},V,{style:Object.assign({},J,i)}):Object.assign({},V,{className:V.className?"".concat(K," ").concat(V.className):K,style:Object.assign({},i)});if(c.style=x(x({},c.style),{},A?{whiteSpace:"pre-wrap"}:{whiteSpace:"pre"}),!z)return p.createElement(L,H,W,p.createElement($,c,U));(void 0===O&&D||A)&&(O=!0),D=D||P;var G=[{type:"text",value:U}],Z=function(e){var t=e.astGenerator,n=e.language,r=e.code,o=e.defaultCodeValue;if(N(t)){var s=b(t,n);return"text"===n?{value:o,language:"text"}:s?t.highlight(n,r):t.highlightAuto(r)}try{return n&&"text"!==n?{value:t.highlight(r,n)}:{value:o}}catch(e){return{value:o}}}({astGenerator:z,language:t,code:U,defaultCodeValue:G});null===Z.language&&(Z.value=G);var Y=C(Z,O,M,d,g,v,Z.value.length+v,j,A);return p.createElement(L,H,p.createElement($,c,!g&&W,D({rows:Y,stylesheet:o,useInlineStyles:h})))});M.registerLanguage=R.registerLanguage;const D=M;var F=n(96344);const L=n.n(F)();var B=n(82026);const $=n.n(B)();var q=n(42157);const U=n.n(q)();var z=n(61519);const V=n.n(z)();var W=n(54587);const J=n.n(W)();var K=n(30786);const H=n.n(K)();var G=n(66336);const Z=n.n(G)(),Y={hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#333",color:"white"},"hljs-name":{fontWeight:"bold"},"hljs-strong":{fontWeight:"bold"},"hljs-code":{fontStyle:"italic",color:"#888"},"hljs-emphasis":{fontStyle:"italic"},"hljs-tag":{color:"#62c8f3"},"hljs-variable":{color:"#ade5fc"},"hljs-template-variable":{color:"#ade5fc"},"hljs-selector-id":{color:"#ade5fc"},"hljs-selector-class":{color:"#ade5fc"},"hljs-string":{color:"#a2fca2"},"hljs-bullet":{color:"#d36363"},"hljs-type":{color:"#ffa"},"hljs-title":{color:"#ffa"},"hljs-section":{color:"#ffa"},"hljs-attribute":{color:"#ffa"},"hljs-quote":{color:"#ffa"},"hljs-built_in":{color:"#ffa"},"hljs-builtin-name":{color:"#ffa"},"hljs-number":{color:"#d36363"},"hljs-symbol":{color:"#d36363"},"hljs-keyword":{color:"#fcc28c"},"hljs-selector-tag":{color:"#fcc28c"},"hljs-literal":{color:"#fcc28c"},"hljs-comment":{color:"#888"},"hljs-deletion":{color:"#333",backgroundColor:"#fc9b9b"},"hljs-regexp":{color:"#c6b4f0"},"hljs-link":{color:"#c6b4f0"},"hljs-meta":{color:"#fc9b9b"},"hljs-addition":{backgroundColor:"#a2fca2",color:"#333"}};D.registerLanguage("json",$),D.registerLanguage("js",L),D.registerLanguage("xml",U),D.registerLanguage("yaml",J),D.registerLanguage("http",H),D.registerLanguage("bash",V),D.registerLanguage("powershell",Z),D.registerLanguage("javascript",L);const X={agate:Y,arta:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#222",color:"#aaa"},"hljs-subst":{color:"#aaa"},"hljs-section":{color:"#fff",fontWeight:"bold"},"hljs-comment":{color:"#444"},"hljs-quote":{color:"#444"},"hljs-meta":{color:"#444"},"hljs-string":{color:"#ffcc33"},"hljs-symbol":{color:"#ffcc33"},"hljs-bullet":{color:"#ffcc33"},"hljs-regexp":{color:"#ffcc33"},"hljs-number":{color:"#00cc66"},"hljs-addition":{color:"#00cc66"},"hljs-built_in":{color:"#32aaee"},"hljs-builtin-name":{color:"#32aaee"},"hljs-literal":{color:"#32aaee"},"hljs-type":{color:"#32aaee"},"hljs-template-variable":{color:"#32aaee"},"hljs-attribute":{color:"#32aaee"},"hljs-link":{color:"#32aaee"},"hljs-keyword":{color:"#6644aa"},"hljs-selector-tag":{color:"#6644aa"},"hljs-name":{color:"#6644aa"},"hljs-selector-id":{color:"#6644aa"},"hljs-selector-class":{color:"#6644aa"},"hljs-title":{color:"#bb1166"},"hljs-variable":{color:"#bb1166"},"hljs-deletion":{color:"#bb1166"},"hljs-template-tag":{color:"#bb1166"},"hljs-doctag":{fontWeight:"bold"},"hljs-strong":{fontWeight:"bold"},"hljs-emphasis":{fontStyle:"italic"}},monokai:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#272822",color:"#ddd"},"hljs-tag":{color:"#f92672"},"hljs-keyword":{color:"#f92672",fontWeight:"bold"},"hljs-selector-tag":{color:"#f92672",fontWeight:"bold"},"hljs-literal":{color:"#f92672",fontWeight:"bold"},"hljs-strong":{color:"#f92672"},"hljs-name":{color:"#f92672"},"hljs-code":{color:"#66d9ef"},"hljs-class .hljs-title":{color:"white"},"hljs-attribute":{color:"#bf79db"},"hljs-symbol":{color:"#bf79db"},"hljs-regexp":{color:"#bf79db"},"hljs-link":{color:"#bf79db"},"hljs-string":{color:"#a6e22e"},"hljs-bullet":{color:"#a6e22e"},"hljs-subst":{color:"#a6e22e"},"hljs-title":{color:"#a6e22e",fontWeight:"bold"},"hljs-section":{color:"#a6e22e",fontWeight:"bold"},"hljs-emphasis":{color:"#a6e22e"},"hljs-type":{color:"#a6e22e",fontWeight:"bold"},"hljs-built_in":{color:"#a6e22e"},"hljs-builtin-name":{color:"#a6e22e"},"hljs-selector-attr":{color:"#a6e22e"},"hljs-selector-pseudo":{color:"#a6e22e"},"hljs-addition":{color:"#a6e22e"},"hljs-variable":{color:"#a6e22e"},"hljs-template-tag":{color:"#a6e22e"},"hljs-template-variable":{color:"#a6e22e"},"hljs-comment":{color:"#75715e"},"hljs-quote":{color:"#75715e"},"hljs-deletion":{color:"#75715e"},"hljs-meta":{color:"#75715e"},"hljs-doctag":{fontWeight:"bold"},"hljs-selector-id":{fontWeight:"bold"}},nord:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#2E3440",color:"#D8DEE9"},"hljs-subst":{color:"#D8DEE9"},"hljs-selector-tag":{color:"#81A1C1"},"hljs-selector-id":{color:"#8FBCBB",fontWeight:"bold"},"hljs-selector-class":{color:"#8FBCBB"},"hljs-selector-attr":{color:"#8FBCBB"},"hljs-selector-pseudo":{color:"#88C0D0"},"hljs-addition":{backgroundColor:"rgba(163, 190, 140, 0.5)"},"hljs-deletion":{backgroundColor:"rgba(191, 97, 106, 0.5)"},"hljs-built_in":{color:"#8FBCBB"},"hljs-type":{color:"#8FBCBB"},"hljs-class":{color:"#8FBCBB"},"hljs-function":{color:"#88C0D0"},"hljs-function > .hljs-title":{color:"#88C0D0"},"hljs-keyword":{color:"#81A1C1"},"hljs-literal":{color:"#81A1C1"},"hljs-symbol":{color:"#81A1C1"},"hljs-number":{color:"#B48EAD"},"hljs-regexp":{color:"#EBCB8B"},"hljs-string":{color:"#A3BE8C"},"hljs-title":{color:"#8FBCBB"},"hljs-params":{color:"#D8DEE9"},"hljs-bullet":{color:"#81A1C1"},"hljs-code":{color:"#8FBCBB"},"hljs-emphasis":{fontStyle:"italic"},"hljs-formula":{color:"#8FBCBB"},"hljs-strong":{fontWeight:"bold"},"hljs-link:hover":{textDecoration:"underline"},"hljs-quote":{color:"#4C566A"},"hljs-comment":{color:"#4C566A"},"hljs-doctag":{color:"#8FBCBB"},"hljs-meta":{color:"#5E81AC"},"hljs-meta-keyword":{color:"#5E81AC"},"hljs-meta-string":{color:"#A3BE8C"},"hljs-attr":{color:"#8FBCBB"},"hljs-attribute":{color:"#D8DEE9"},"hljs-builtin-name":{color:"#81A1C1"},"hljs-name":{color:"#81A1C1"},"hljs-section":{color:"#88C0D0"},"hljs-tag":{color:"#81A1C1"},"hljs-variable":{color:"#D8DEE9"},"hljs-template-variable":{color:"#D8DEE9"},"hljs-template-tag":{color:"#5E81AC"},"abnf .hljs-attribute":{color:"#88C0D0"},"abnf .hljs-symbol":{color:"#EBCB8B"},"apache .hljs-attribute":{color:"#88C0D0"},"apache .hljs-section":{color:"#81A1C1"},"arduino .hljs-built_in":{color:"#88C0D0"},"aspectj .hljs-meta":{color:"#D08770"},"aspectj > .hljs-title":{color:"#88C0D0"},"bnf .hljs-attribute":{color:"#8FBCBB"},"clojure .hljs-name":{color:"#88C0D0"},"clojure .hljs-symbol":{color:"#EBCB8B"},"coq .hljs-built_in":{color:"#88C0D0"},"cpp .hljs-meta-string":{color:"#8FBCBB"},"css .hljs-built_in":{color:"#88C0D0"},"css .hljs-keyword":{color:"#D08770"},"diff .hljs-meta":{color:"#8FBCBB"},"ebnf .hljs-attribute":{color:"#8FBCBB"},"glsl .hljs-built_in":{color:"#88C0D0"},"groovy .hljs-meta:not(:first-child)":{color:"#D08770"},"haxe .hljs-meta":{color:"#D08770"},"java .hljs-meta":{color:"#D08770"},"ldif .hljs-attribute":{color:"#8FBCBB"},"lisp .hljs-name":{color:"#88C0D0"},"lua .hljs-built_in":{color:"#88C0D0"},"moonscript .hljs-built_in":{color:"#88C0D0"},"nginx .hljs-attribute":{color:"#88C0D0"},"nginx .hljs-section":{color:"#5E81AC"},"pf .hljs-built_in":{color:"#88C0D0"},"processing .hljs-built_in":{color:"#88C0D0"},"scss .hljs-keyword":{color:"#81A1C1"},"stylus .hljs-keyword":{color:"#81A1C1"},"swift .hljs-meta":{color:"#D08770"},"vim .hljs-built_in":{color:"#88C0D0",fontStyle:"italic"},"yaml .hljs-meta":{color:"#D08770"}},obsidian:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#282b2e",color:"#e0e2e4"},"hljs-keyword":{color:"#93c763",fontWeight:"bold"},"hljs-selector-tag":{color:"#93c763",fontWeight:"bold"},"hljs-literal":{color:"#93c763",fontWeight:"bold"},"hljs-selector-id":{color:"#93c763"},"hljs-number":{color:"#ffcd22"},"hljs-attribute":{color:"#668bb0"},"hljs-code":{color:"white"},"hljs-class .hljs-title":{color:"white"},"hljs-section":{color:"white",fontWeight:"bold"},"hljs-regexp":{color:"#d39745"},"hljs-link":{color:"#d39745"},"hljs-meta":{color:"#557182"},"hljs-tag":{color:"#8cbbad"},"hljs-name":{color:"#8cbbad",fontWeight:"bold"},"hljs-bullet":{color:"#8cbbad"},"hljs-subst":{color:"#8cbbad"},"hljs-emphasis":{color:"#8cbbad"},"hljs-type":{color:"#8cbbad",fontWeight:"bold"},"hljs-built_in":{color:"#8cbbad"},"hljs-selector-attr":{color:"#8cbbad"},"hljs-selector-pseudo":{color:"#8cbbad"},"hljs-addition":{color:"#8cbbad"},"hljs-variable":{color:"#8cbbad"},"hljs-template-tag":{color:"#8cbbad"},"hljs-template-variable":{color:"#8cbbad"},"hljs-string":{color:"#ec7600"},"hljs-symbol":{color:"#ec7600"},"hljs-comment":{color:"#818e96"},"hljs-quote":{color:"#818e96"},"hljs-deletion":{color:"#818e96"},"hljs-selector-class":{color:"#A082BD"},"hljs-doctag":{fontWeight:"bold"},"hljs-title":{fontWeight:"bold"},"hljs-strong":{fontWeight:"bold"}},"tomorrow-night":{"hljs-comment":{color:"#969896"},"hljs-quote":{color:"#969896"},"hljs-variable":{color:"#cc6666"},"hljs-template-variable":{color:"#cc6666"},"hljs-tag":{color:"#cc6666"},"hljs-name":{color:"#cc6666"},"hljs-selector-id":{color:"#cc6666"},"hljs-selector-class":{color:"#cc6666"},"hljs-regexp":{color:"#cc6666"},"hljs-deletion":{color:"#cc6666"},"hljs-number":{color:"#de935f"},"hljs-built_in":{color:"#de935f"},"hljs-builtin-name":{color:"#de935f"},"hljs-literal":{color:"#de935f"},"hljs-type":{color:"#de935f"},"hljs-params":{color:"#de935f"},"hljs-meta":{color:"#de935f"},"hljs-link":{color:"#de935f"},"hljs-attribute":{color:"#f0c674"},"hljs-string":{color:"#b5bd68"},"hljs-symbol":{color:"#b5bd68"},"hljs-bullet":{color:"#b5bd68"},"hljs-addition":{color:"#b5bd68"},"hljs-title":{color:"#81a2be"},"hljs-section":{color:"#81a2be"},"hljs-keyword":{color:"#b294bb"},"hljs-selector-tag":{color:"#b294bb"},hljs:{display:"block",overflowX:"auto",background:"#1d1f21",color:"#c5c8c6",padding:"0.5em"},"hljs-emphasis":{fontStyle:"italic"},"hljs-strong":{fontWeight:"bold"}}},Q=o()(X),ee=e=>i()(Q).call(Q,e)?X[e]:(console.warn(`Request style '${e}' is not available, returning default instead`),Y)},90242:(e,t,n)=>{"use strict";n.d(t,{AF:()=>ae,Ay:()=>fe,D$:()=>De,DR:()=>ve,GZ:()=>je,HP:()=>he,Ik:()=>Ee,J6:()=>Ne,Kn:()=>ce,LQ:()=>le,Nm:()=>ke,O2:()=>Ue,Pz:()=>Me,Q2:()=>de,QG:()=>Ce,UG:()=>xe,Uj:()=>Be,V9:()=>Fe,Wl:()=>ue,XV:()=>Re,Xb:()=>$e,Zl:()=>be,_5:()=>me,be:()=>Oe,cz:()=>Le,gp:()=>ye,hW:()=>Ae,iQ:()=>ge,kJ:()=>pe,mz:()=>se,nX:()=>Ie,oG:()=>ie,oJ:()=>Pe,po:()=>Te,r3:()=>Se,wh:()=>_e});var r=n(58309),o=n.n(r),s=n(97606),i=n.n(s),a=n(74386),l=n.n(a),c=n(86),u=n.n(c),p=n(14418),h=n.n(p),f=n(28222),d=n.n(f),m=(n(11189),n(24282)),g=n.n(m),y=n(76986),v=n.n(y),b=n(2578),w=n.n(b),E=(n(24278),n(39022),n(92039)),x=n.n(E),S=(n(58118),n(11882)),_=n.n(S),j=n(51679),O=n.n(j),k=n(27043),A=n.n(k),C=n(81607),P=n.n(C),N=n(35627),I=n.n(N),T=n(43393),R=n.n(T),M=n(17967),D=n(68929),F=n.n(D),L=n(11700),B=n.n(L),$=n(88306),q=n.n($),U=n(13311),z=n.n(U),V=(n(59704),n(77813)),W=n.n(V),J=n(23560),K=n.n(J),H=n(27504),G=n(8269),Z=n.n(G),Y=n(19069),X=n(92282),Q=n.n(X),ee=n(89072),te=n.n(ee),ne=n(48764).Buffer;const re="default",oe=e=>R().Iterable.isIterable(e);function se(e){return ce(e)?oe(e)?e.toJS():e:{}}function ie(e){var t,n;if(oe(e))return e;if(e instanceof H.Z.File)return e;if(!ce(e))return e;if(o()(e))return i()(n=R().Seq(e)).call(n,ie).toList();if(K()(l()(e))){var r;const t=function(e){if(!K()(l()(e)))return e;const t={},n="_**[]",r={};for(let o of l()(e).call(e))if(t[o[0]]||r[o[0]]&&r[o[0]].containsMultiple){if(!r[o[0]]){r[o[0]]={containsMultiple:!0,length:1},t[`${o[0]}${n}${r[o[0]].length}`]=t[o[0]],delete t[o[0]]}r[o[0]].length+=1,t[`${o[0]}${n}${r[o[0]].length}`]=o[1]}else t[o[0]]=o[1];return t}(e);return i()(r=R().OrderedMap(t)).call(r,ie)}return i()(t=R().OrderedMap(e)).call(t,ie)}function ae(e){return o()(e)?e:[e]}function le(e){return"function"==typeof e}function ce(e){return!!e&&"object"==typeof e}function ue(e){return"function"==typeof e}function pe(e){return o()(e)}const he=q();function fe(e,t){var n;return g()(n=d()(e)).call(n,((n,r)=>(n[r]=t(e[r],r),n)),{})}function de(e,t){var n;return g()(n=d()(e)).call(n,((n,r)=>{let o=t(e[r],r);return o&&"object"==typeof o&&v()(n,o),n}),{})}function me(e){return t=>{let{dispatch:n,getState:r}=t;return t=>n=>"function"==typeof n?n(e()):t(n)}}function ge(e){var t;let n=e.keySeq();return n.contains(re)?re:w()(t=h()(n).call(n,(e=>"2"===(e+"")[0]))).call(t).first()}function ye(e,t){if(!R().Iterable.isIterable(e))return R().List();let n=e.getIn(o()(t)?t:[t]);return R().List.isList(n)?n:R().List()}function ve(e){let t,n=[/filename\*=[^']+'\w*'"([^"]+)";?/i,/filename\*=[^']+'\w*'([^;]+);?/i,/filename="([^;]*);?"/i,/filename=([^;]*);?/i];if(x()(n).call(n,(n=>(t=n.exec(e),null!==t))),null!==t&&t.length>1)try{return decodeURIComponent(t[1])}catch(e){console.error(e)}return null}function be(e){return t=e.replace(/\.[^./]*$/,""),B()(F()(t));var t}function we(e,t,n,r,s){if(!t)return[];let a=[],l=t.get("nullable"),c=t.get("required"),p=t.get("maximum"),f=t.get("minimum"),d=t.get("type"),m=t.get("format"),g=t.get("maxLength"),y=t.get("minLength"),v=t.get("uniqueItems"),b=t.get("maxItems"),w=t.get("minItems"),E=t.get("pattern");const S=n||!0===c,_=null!=e;if(l&&null===e||!d||!(S||_&&"array"===d||!(!S&&!_)))return[];let j="string"===d&&e,O="array"===d&&o()(e)&&e.length,k="array"===d&&R().List.isList(e)&&e.count();const A=[j,O,k,"array"===d&&"string"==typeof e&&e,"file"===d&&e instanceof H.Z.File,"boolean"===d&&(e||!1===e),"number"===d&&(e||0===e),"integer"===d&&(e||0===e),"object"===d&&"object"==typeof e&&null!==e,"object"===d&&"string"==typeof e&&e],C=x()(A).call(A,(e=>!!e));if(S&&!C&&!r)return a.push("Required field is not provided"),a;if("object"===d&&(null===s||"application/json"===s)){let n=e;if("string"==typeof e)try{n=JSON.parse(e)}catch(e){return a.push("Parameter string value must be valid JSON"),a}var P;if(t&&t.has("required")&&ue(c.isList)&&c.isList()&&u()(c).call(c,(e=>{void 0===n[e]&&a.push({propKey:e,error:"Required property not found"})})),t&&t.has("properties"))u()(P=t.get("properties")).call(P,((e,t)=>{const o=we(n[t],e,!1,r,s);a.push(...i()(o).call(o,(e=>({propKey:t,error:e}))))}))}if(E){let t=((e,t)=>{if(!new RegExp(t).test(e))return"Value must follow pattern "+t})(e,E);t&&a.push(t)}if(w&&"array"===d){let t=((e,t)=>{if(!e&&t>=1||e&&e.length{if(e&&e.length>t)return`Array must not contain more then ${t} item${1===t?"":"s"}`})(e,b);t&&a.push({needRemove:!0,error:t})}if(v&&"array"===d){let t=((e,t)=>{if(e&&("true"===t||!0===t)){const t=(0,T.fromJS)(e),n=t.toSet();if(e.length>n.size){let e=(0,T.Set)();if(u()(t).call(t,((n,r)=>{h()(t).call(t,(e=>ue(e.equals)?e.equals(n):e===n)).size>1&&(e=e.add(r))})),0!==e.size)return i()(e).call(e,(e=>({index:e,error:"No duplicates allowed."}))).toArray()}}})(e,v);t&&a.push(...t)}if(g||0===g){let t=((e,t)=>{if(e.length>t)return`Value must be no longer than ${t} character${1!==t?"s":""}`})(e,g);t&&a.push(t)}if(y){let t=((e,t)=>{if(e.length{if(e>t)return`Value must be less than ${t}`})(e,p);t&&a.push(t)}if(f||0===f){let t=((e,t)=>{if(e{if(isNaN(Date.parse(e)))return"Value must be a DateTime"})(e):"uuid"===m?(e=>{if(e=e.toString().toLowerCase(),!/^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[)}]?$/.test(e))return"Value must be a Guid"})(e):(e=>{if(e&&"string"!=typeof e)return"Value must be a string"})(e),!t)return a;a.push(t)}else if("boolean"===d){let t=(e=>{if("true"!==e&&"false"!==e&&!0!==e&&!1!==e)return"Value must be a boolean"})(e);if(!t)return a;a.push(t)}else if("number"===d){let t=(e=>{if(!/^-?\d+(\.?\d+)?$/.test(e))return"Value must be a number"})(e);if(!t)return a;a.push(t)}else if("integer"===d){let t=(e=>{if(!/^-?\d+$/.test(e))return"Value must be an integer"})(e);if(!t)return a;a.push(t)}else if("array"===d){if(!O&&!k)return a;e&&u()(e).call(e,((e,n)=>{const o=we(e,t.get("items"),!1,r,s);a.push(...i()(o).call(o,(e=>({index:n,error:e}))))}))}else if("file"===d){let t=(e=>{if(e&&!(e instanceof H.Z.File))return"Value must be a file"})(e);if(!t)return a;a.push(t)}return a}const Ee=function(e,t){let{isOAS3:n=!1,bypassRequiredCheck:r=!1}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=e.get("required"),{schema:s,parameterContentMediaType:i}=(0,Y.Z)(e,{isOAS3:n});return we(t,s,o,r,i)},xe=()=>{let e={},t=H.Z.location.search;if(!t)return{};if(""!=t){let n=t.substr(1).split("&");for(let t in n)Object.prototype.hasOwnProperty.call(n,t)&&(t=n[t].split("="),e[decodeURIComponent(t[0])]=t[1]&&decodeURIComponent(t[1])||"")}return e},Se=e=>{let t;return t=e instanceof ne?e:ne.from(e.toString(),"utf-8"),t.toString("base64")},_e={operationsSorter:{alpha:(e,t)=>e.get("path").localeCompare(t.get("path")),method:(e,t)=>e.get("method").localeCompare(t.get("method"))},tagsSorter:{alpha:(e,t)=>e.localeCompare(t)}},je=e=>{let t=[];for(let n in e){let r=e[n];void 0!==r&&""!==r&&t.push([n,"=",encodeURIComponent(r).replace(/%20/g,"+")].join(""))}return t.join("&")},Oe=(e,t,n)=>!!z()(n,(n=>W()(e[n],t[n])));function ke(e){return"string"!=typeof e||""===e?"":(0,M.N)(e)}function Ae(e){return!(!e||_()(e).call(e,"localhost")>=0||_()(e).call(e,"127.0.0.1")>=0||"none"===e)}function Ce(e){if(!R().OrderedMap.isOrderedMap(e))return null;if(!e.size)return null;const t=O()(e).call(e,((e,t)=>A()(t).call(t,"2")&&d()(e.get("content")||{}).length>0)),n=e.get("default")||R().OrderedMap(),r=(n.get("content")||R().OrderedMap()).keySeq().toJS().length?n:null;return t||r}const Pe=e=>"string"==typeof e||e instanceof String?P()(e).call(e).replace(/\s/g,"%20"):"",Ne=e=>Z()(Pe(e).replace(/%20/g,"_")),Ie=e=>h()(e).call(e,((e,t)=>/^x-/.test(t))),Te=e=>h()(e).call(e,((e,t)=>/^pattern|maxLength|minLength|maximum|minimum/.test(t)));function Re(e,t){var n;let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:()=>!0;if("object"!=typeof e||o()(e)||null===e||!t)return e;const s=v()({},e);return u()(n=d()(s)).call(n,(e=>{e===t&&r(s[e],e)?delete s[e]:s[e]=Re(s[e],t,r)})),s}function Me(e){if("string"==typeof e)return e;if(e&&e.toJS&&(e=e.toJS()),"object"==typeof e&&null!==e)try{return I()(e,null,2)}catch(t){return String(e)}return null==e?"":e.toString()}function De(e){return"number"==typeof e?e.toString():e}function Fe(e){let{returnAll:t=!1,allowHashes:n=!0}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!R().Map.isMap(e))throw new Error("paramToIdentifier: received a non-Im.Map parameter as input");const r=e.get("name"),o=e.get("in");let s=[];return e&&e.hashCode&&o&&r&&n&&s.push(`${o}.${r}.hash-${e.hashCode()}`),o&&r&&s.push(`${o}.${r}`),s.push(r),t?s:s[0]||""}function Le(e,t){var n;const r=Fe(e,{returnAll:!0});return h()(n=i()(r).call(r,(e=>t[e]))).call(n,(e=>void 0!==e))[0]}function Be(){return qe(Q()(32).toString("base64"))}function $e(e){return qe(te()("sha256").update(e).digest("base64"))}function qe(e){return e.replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}const Ue=e=>!e||!(!oe(e)||!e.isEmpty())},2518:(e,t,n)=>{"use strict";function r(e){return function(e){try{return!!JSON.parse(e)}catch(e){return null}}(e)?"json":null}n.d(t,{O:()=>r})},63543:(e,t,n)=>{"use strict";n.d(t,{mn:()=>a});var r=n(63460),o=n.n(r);function s(e){return e.match(/^(?:[a-z]+:)?\/\//i)}function i(e,t){return e?s(e)?function(e){return e.match(/^\/\//i)?`${window.location.protocol}${e}`:e}(e):new(o())(e,t).href:t}function a(e,t){let{selectedServer:n=""}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};try{return function(e,t){let{selectedServer:n=""}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(!e)return;if(s(e))return e;const r=i(n,t);return s(r)?new(o())(e,r).href:new(o())(e,window.location.href).href}(e,t,{selectedServer:n})}catch{return}}},27504:(e,t,n)=>{"use strict";n.d(t,{Z:()=>r});const r=function(){var e={location:{},history:{},open:()=>{},close:()=>{},File:function(){}};if("undefined"==typeof window)return e;try{e=window;for(var t of["File","Blob","FormData"])t in window&&(e[t]=window[t])}catch(e){console.error(e)}return e}()},19069:(e,t,n)=>{"use strict";n.d(t,{Z:()=>u});var r=n(14418),o=n.n(r),s=n(58118),i=n.n(s),a=n(43393),l=n.n(a);const c=l().Set.of("type","format","items","default","maximum","exclusiveMaximum","minimum","exclusiveMinimum","maxLength","minLength","pattern","maxItems","minItems","uniqueItems","enum","multipleOf");function u(e){let{isOAS3:t}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!l().Map.isMap(e))return{schema:l().Map(),parameterContentMediaType:null};if(!t)return"body"===e.get("in")?{schema:e.get("schema",l().Map()),parameterContentMediaType:null}:{schema:o()(e).call(e,((e,t)=>i()(c).call(c,t))),parameterContentMediaType:null};if(e.get("content")){const t=e.get("content",l().Map({})).keySeq().first();return{schema:e.getIn(["content",t,"schema"],l().Map()),parameterContentMediaType:t}}return{schema:e.get("schema")?e.get("schema",l().Map()):l().Map(),parameterContentMediaType:null}}},60314:(e,t,n)=>{"use strict";n.d(t,{Z:()=>x});var r=n(58309),o=n.n(r),s=n(2250),i=n.n(s),a=n(25110),l=n.n(a),c=n(8712),u=n.n(c),p=n(51679),h=n.n(p),f=n(12373),d=n.n(f),m=n(18492),g=n.n(m),y=n(88306),v=n.n(y);const b=e=>t=>o()(e)&&o()(t)&&e.length===t.length&&i()(e).call(e,((e,n)=>e===t[n])),w=function(){for(var e=arguments.length,t=new Array(e),n=0;n1&&void 0!==arguments[1]?arguments[1]:w;const{Cache:n}=v();v().Cache=E;const r=v()(e,t);return v().Cache=n,r}},79742:(e,t)=>{"use strict";t.byteLength=function(e){var t=a(e),n=t[0],r=t[1];return 3*(n+r)/4-r},t.toByteArray=function(e){var t,n,s=a(e),i=s[0],l=s[1],c=new o(function(e,t,n){return 3*(t+n)/4-n}(0,i,l)),u=0,p=l>0?i-4:i;for(n=0;n>16&255,c[u++]=t>>8&255,c[u++]=255&t;2===l&&(t=r[e.charCodeAt(n)]<<2|r[e.charCodeAt(n+1)]>>4,c[u++]=255&t);1===l&&(t=r[e.charCodeAt(n)]<<10|r[e.charCodeAt(n+1)]<<4|r[e.charCodeAt(n+2)]>>2,c[u++]=t>>8&255,c[u++]=255&t);return c},t.fromByteArray=function(e){for(var t,r=e.length,o=r%3,s=[],i=16383,a=0,c=r-o;ac?c:a+i));1===o?(t=e[r-1],s.push(n[t>>2]+n[t<<4&63]+"==")):2===o&&(t=(e[r-2]<<8)+e[r-1],s.push(n[t>>10]+n[t>>4&63]+n[t<<2&63]+"="));return s.join("")};for(var n=[],r=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",i=0;i<64;++i)n[i]=s[i],r[s.charCodeAt(i)]=i;function a(e){var t=e.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var n=e.indexOf("=");return-1===n&&(n=t),[n,n===t?0:4-n%4]}function l(e,t,r){for(var o,s,i=[],a=t;a>18&63]+n[s>>12&63]+n[s>>6&63]+n[63&s]);return i.join("")}r["-".charCodeAt(0)]=62,r["_".charCodeAt(0)]=63},48764:(e,t,n)=>{"use strict";const r=n(79742),o=n(80645),s="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;t.Buffer=l,t.SlowBuffer=function(e){+e!=e&&(e=0);return l.alloc(+e)},t.INSPECT_MAX_BYTES=50;const i=2147483647;function a(e){if(e>i)throw new RangeError('The value "'+e+'" is invalid for option "size"');const t=new Uint8Array(e);return Object.setPrototypeOf(t,l.prototype),t}function l(e,t,n){if("number"==typeof e){if("string"==typeof t)throw new TypeError('The "string" argument must be of type string. Received type number');return p(e)}return c(e,t,n)}function c(e,t,n){if("string"==typeof e)return function(e,t){"string"==typeof t&&""!==t||(t="utf8");if(!l.isEncoding(t))throw new TypeError("Unknown encoding: "+t);const n=0|m(e,t);let r=a(n);const o=r.write(e,t);o!==n&&(r=r.slice(0,o));return r}(e,t);if(ArrayBuffer.isView(e))return function(e){if(G(e,Uint8Array)){const t=new Uint8Array(e);return f(t.buffer,t.byteOffset,t.byteLength)}return h(e)}(e);if(null==e)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e);if(G(e,ArrayBuffer)||e&&G(e.buffer,ArrayBuffer))return f(e,t,n);if("undefined"!=typeof SharedArrayBuffer&&(G(e,SharedArrayBuffer)||e&&G(e.buffer,SharedArrayBuffer)))return f(e,t,n);if("number"==typeof e)throw new TypeError('The "value" argument must not be of type number. Received type number');const r=e.valueOf&&e.valueOf();if(null!=r&&r!==e)return l.from(r,t,n);const o=function(e){if(l.isBuffer(e)){const t=0|d(e.length),n=a(t);return 0===n.length||e.copy(n,0,0,t),n}if(void 0!==e.length)return"number"!=typeof e.length||Z(e.length)?a(0):h(e);if("Buffer"===e.type&&Array.isArray(e.data))return h(e.data)}(e);if(o)return o;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof e[Symbol.toPrimitive])return l.from(e[Symbol.toPrimitive]("string"),t,n);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e)}function u(e){if("number"!=typeof e)throw new TypeError('"size" argument must be of type number');if(e<0)throw new RangeError('The value "'+e+'" is invalid for option "size"')}function p(e){return u(e),a(e<0?0:0|d(e))}function h(e){const t=e.length<0?0:0|d(e.length),n=a(t);for(let r=0;r=i)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+i.toString(16)+" bytes");return 0|e}function m(e,t){if(l.isBuffer(e))return e.length;if(ArrayBuffer.isView(e)||G(e,ArrayBuffer))return e.byteLength;if("string"!=typeof e)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof e);const n=e.length,r=arguments.length>2&&!0===arguments[2];if(!r&&0===n)return 0;let o=!1;for(;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":return J(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return K(e).length;default:if(o)return r?-1:J(e).length;t=(""+t).toLowerCase(),o=!0}}function g(e,t,n){let r=!1;if((void 0===t||t<0)&&(t=0),t>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return P(this,t,n);case"utf8":case"utf-8":return O(this,t,n);case"ascii":return A(this,t,n);case"latin1":case"binary":return C(this,t,n);case"base64":return j(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return N(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}function y(e,t,n){const r=e[t];e[t]=e[n],e[n]=r}function v(e,t,n,r,o){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),Z(n=+n)&&(n=o?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(o)return-1;n=e.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof t&&(t=l.from(t,r)),l.isBuffer(t))return 0===t.length?-1:b(e,t,n,r,o);if("number"==typeof t)return t&=255,"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):b(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function b(e,t,n,r,o){let s,i=1,a=e.length,l=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;i=2,a/=2,l/=2,n/=2}function c(e,t){return 1===i?e[t]:e.readUInt16BE(t*i)}if(o){let r=-1;for(s=n;sa&&(n=a-l),s=n;s>=0;s--){let n=!0;for(let r=0;ro&&(r=o):r=o;const s=t.length;let i;for(r>s/2&&(r=s/2),i=0;i>8,o=n%256,s.push(o),s.push(r);return s}(t,e.length-n),e,n,r)}function j(e,t,n){return 0===t&&n===e.length?r.fromByteArray(e):r.fromByteArray(e.slice(t,n))}function O(e,t,n){n=Math.min(e.length,n);const r=[];let o=t;for(;o239?4:t>223?3:t>191?2:1;if(o+i<=n){let n,r,a,l;switch(i){case 1:t<128&&(s=t);break;case 2:n=e[o+1],128==(192&n)&&(l=(31&t)<<6|63&n,l>127&&(s=l));break;case 3:n=e[o+1],r=e[o+2],128==(192&n)&&128==(192&r)&&(l=(15&t)<<12|(63&n)<<6|63&r,l>2047&&(l<55296||l>57343)&&(s=l));break;case 4:n=e[o+1],r=e[o+2],a=e[o+3],128==(192&n)&&128==(192&r)&&128==(192&a)&&(l=(15&t)<<18|(63&n)<<12|(63&r)<<6|63&a,l>65535&&l<1114112&&(s=l))}}null===s?(s=65533,i=1):s>65535&&(s-=65536,r.push(s>>>10&1023|55296),s=56320|1023&s),r.push(s),o+=i}return function(e){const t=e.length;if(t<=k)return String.fromCharCode.apply(String,e);let n="",r=0;for(;rr.length?(l.isBuffer(t)||(t=l.from(t)),t.copy(r,o)):Uint8Array.prototype.set.call(r,t,o);else{if(!l.isBuffer(t))throw new TypeError('"list" argument must be an Array of Buffers');t.copy(r,o)}o+=t.length}return r},l.byteLength=m,l.prototype._isBuffer=!0,l.prototype.swap16=function(){const e=this.length;if(e%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let t=0;tn&&(e+=" ... "),""},s&&(l.prototype[s]=l.prototype.inspect),l.prototype.compare=function(e,t,n,r,o){if(G(e,Uint8Array)&&(e=l.from(e,e.offset,e.byteLength)),!l.isBuffer(e))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),t<0||n>e.length||r<0||o>this.length)throw new RangeError("out of range index");if(r>=o&&t>=n)return 0;if(r>=o)return-1;if(t>=n)return 1;if(this===e)return 0;let s=(o>>>=0)-(r>>>=0),i=(n>>>=0)-(t>>>=0);const a=Math.min(s,i),c=this.slice(r,o),u=e.slice(t,n);for(let e=0;e>>=0,isFinite(n)?(n>>>=0,void 0===r&&(r="utf8")):(r=n,n=void 0)}const o=this.length-t;if((void 0===n||n>o)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");let s=!1;for(;;)switch(r){case"hex":return w(this,e,t,n);case"utf8":case"utf-8":return E(this,e,t,n);case"ascii":case"latin1":case"binary":return x(this,e,t,n);case"base64":return S(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return _(this,e,t,n);default:if(s)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),s=!0}},l.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};const k=4096;function A(e,t,n){let r="";n=Math.min(e.length,n);for(let o=t;or)&&(n=r);let o="";for(let r=t;rn)throw new RangeError("Trying to access beyond buffer length")}function T(e,t,n,r,o,s){if(!l.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>o||te.length)throw new RangeError("Index out of range")}function R(e,t,n,r,o){U(t,r,o,e,n,7);let s=Number(t&BigInt(4294967295));e[n++]=s,s>>=8,e[n++]=s,s>>=8,e[n++]=s,s>>=8,e[n++]=s;let i=Number(t>>BigInt(32)&BigInt(4294967295));return e[n++]=i,i>>=8,e[n++]=i,i>>=8,e[n++]=i,i>>=8,e[n++]=i,n}function M(e,t,n,r,o){U(t,r,o,e,n,7);let s=Number(t&BigInt(4294967295));e[n+7]=s,s>>=8,e[n+6]=s,s>>=8,e[n+5]=s,s>>=8,e[n+4]=s;let i=Number(t>>BigInt(32)&BigInt(4294967295));return e[n+3]=i,i>>=8,e[n+2]=i,i>>=8,e[n+1]=i,i>>=8,e[n]=i,n+8}function D(e,t,n,r,o,s){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function F(e,t,n,r,s){return t=+t,n>>>=0,s||D(e,0,n,4),o.write(e,t,n,r,23,4),n+4}function L(e,t,n,r,s){return t=+t,n>>>=0,s||D(e,0,n,8),o.write(e,t,n,r,52,8),n+8}l.prototype.slice=function(e,t){const n=this.length;(e=~~e)<0?(e+=n)<0&&(e=0):e>n&&(e=n),(t=void 0===t?n:~~t)<0?(t+=n)<0&&(t=0):t>n&&(t=n),t>>=0,t>>>=0,n||I(e,t,this.length);let r=this[e],o=1,s=0;for(;++s>>=0,t>>>=0,n||I(e,t,this.length);let r=this[e+--t],o=1;for(;t>0&&(o*=256);)r+=this[e+--t]*o;return r},l.prototype.readUint8=l.prototype.readUInt8=function(e,t){return e>>>=0,t||I(e,1,this.length),this[e]},l.prototype.readUint16LE=l.prototype.readUInt16LE=function(e,t){return e>>>=0,t||I(e,2,this.length),this[e]|this[e+1]<<8},l.prototype.readUint16BE=l.prototype.readUInt16BE=function(e,t){return e>>>=0,t||I(e,2,this.length),this[e]<<8|this[e+1]},l.prototype.readUint32LE=l.prototype.readUInt32LE=function(e,t){return e>>>=0,t||I(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},l.prototype.readUint32BE=l.prototype.readUInt32BE=function(e,t){return e>>>=0,t||I(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},l.prototype.readBigUInt64LE=X((function(e){z(e>>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=t+256*this[++e]+65536*this[++e]+this[++e]*2**24,o=this[++e]+256*this[++e]+65536*this[++e]+n*2**24;return BigInt(r)+(BigInt(o)<>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=t*2**24+65536*this[++e]+256*this[++e]+this[++e],o=this[++e]*2**24+65536*this[++e]+256*this[++e]+n;return(BigInt(r)<>>=0,t>>>=0,n||I(e,t,this.length);let r=this[e],o=1,s=0;for(;++s=o&&(r-=Math.pow(2,8*t)),r},l.prototype.readIntBE=function(e,t,n){e>>>=0,t>>>=0,n||I(e,t,this.length);let r=t,o=1,s=this[e+--r];for(;r>0&&(o*=256);)s+=this[e+--r]*o;return o*=128,s>=o&&(s-=Math.pow(2,8*t)),s},l.prototype.readInt8=function(e,t){return e>>>=0,t||I(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},l.prototype.readInt16LE=function(e,t){e>>>=0,t||I(e,2,this.length);const n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},l.prototype.readInt16BE=function(e,t){e>>>=0,t||I(e,2,this.length);const n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},l.prototype.readInt32LE=function(e,t){return e>>>=0,t||I(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},l.prototype.readInt32BE=function(e,t){return e>>>=0,t||I(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},l.prototype.readBigInt64LE=X((function(e){z(e>>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=this[e+4]+256*this[e+5]+65536*this[e+6]+(n<<24);return(BigInt(r)<>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=(t<<24)+65536*this[++e]+256*this[++e]+this[++e];return(BigInt(r)<>>=0,t||I(e,4,this.length),o.read(this,e,!0,23,4)},l.prototype.readFloatBE=function(e,t){return e>>>=0,t||I(e,4,this.length),o.read(this,e,!1,23,4)},l.prototype.readDoubleLE=function(e,t){return e>>>=0,t||I(e,8,this.length),o.read(this,e,!0,52,8)},l.prototype.readDoubleBE=function(e,t){return e>>>=0,t||I(e,8,this.length),o.read(this,e,!1,52,8)},l.prototype.writeUintLE=l.prototype.writeUIntLE=function(e,t,n,r){if(e=+e,t>>>=0,n>>>=0,!r){T(this,e,t,n,Math.pow(2,8*n)-1,0)}let o=1,s=0;for(this[t]=255&e;++s>>=0,n>>>=0,!r){T(this,e,t,n,Math.pow(2,8*n)-1,0)}let o=n-1,s=1;for(this[t+o]=255&e;--o>=0&&(s*=256);)this[t+o]=e/s&255;return t+n},l.prototype.writeUint8=l.prototype.writeUInt8=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,1,255,0),this[t]=255&e,t+1},l.prototype.writeUint16LE=l.prototype.writeUInt16LE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},l.prototype.writeUint16BE=l.prototype.writeUInt16BE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},l.prototype.writeUint32LE=l.prototype.writeUInt32LE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},l.prototype.writeUint32BE=l.prototype.writeUInt32BE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},l.prototype.writeBigUInt64LE=X((function(e,t=0){return R(this,e,t,BigInt(0),BigInt("0xffffffffffffffff"))})),l.prototype.writeBigUInt64BE=X((function(e,t=0){return M(this,e,t,BigInt(0),BigInt("0xffffffffffffffff"))})),l.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t>>>=0,!r){const r=Math.pow(2,8*n-1);T(this,e,t,n,r-1,-r)}let o=0,s=1,i=0;for(this[t]=255&e;++o>0)-i&255;return t+n},l.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t>>>=0,!r){const r=Math.pow(2,8*n-1);T(this,e,t,n,r-1,-r)}let o=n-1,s=1,i=0;for(this[t+o]=255&e;--o>=0&&(s*=256);)e<0&&0===i&&0!==this[t+o+1]&&(i=1),this[t+o]=(e/s>>0)-i&255;return t+n},l.prototype.writeInt8=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,1,127,-128),e<0&&(e=255+e+1),this[t]=255&e,t+1},l.prototype.writeInt16LE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},l.prototype.writeInt16BE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},l.prototype.writeInt32LE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},l.prototype.writeInt32BE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},l.prototype.writeBigInt64LE=X((function(e,t=0){return R(this,e,t,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),l.prototype.writeBigInt64BE=X((function(e,t=0){return M(this,e,t,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),l.prototype.writeFloatLE=function(e,t,n){return F(this,e,t,!0,n)},l.prototype.writeFloatBE=function(e,t,n){return F(this,e,t,!1,n)},l.prototype.writeDoubleLE=function(e,t,n){return L(this,e,t,!0,n)},l.prototype.writeDoubleBE=function(e,t,n){return L(this,e,t,!1,n)},l.prototype.copy=function(e,t,n,r){if(!l.isBuffer(e))throw new TypeError("argument should be a Buffer");if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(o=t;o=r+4;n-=3)t=`_${e.slice(n-3,n)}${t}`;return`${e.slice(0,n)}${t}`}function U(e,t,n,r,o,s){if(e>n||e3?0===t||t===BigInt(0)?`>= 0${r} and < 2${r} ** ${8*(s+1)}${r}`:`>= -(2${r} ** ${8*(s+1)-1}${r}) and < 2 ** ${8*(s+1)-1}${r}`:`>= ${t}${r} and <= ${n}${r}`,new B.ERR_OUT_OF_RANGE("value",o,e)}!function(e,t,n){z(t,"offset"),void 0!==e[t]&&void 0!==e[t+n]||V(t,e.length-(n+1))}(r,o,s)}function z(e,t){if("number"!=typeof e)throw new B.ERR_INVALID_ARG_TYPE(t,"number",e)}function V(e,t,n){if(Math.floor(e)!==e)throw z(e,n),new B.ERR_OUT_OF_RANGE(n||"offset","an integer",e);if(t<0)throw new B.ERR_BUFFER_OUT_OF_BOUNDS;throw new B.ERR_OUT_OF_RANGE(n||"offset",`>= ${n?1:0} and <= ${t}`,e)}$("ERR_BUFFER_OUT_OF_BOUNDS",(function(e){return e?`${e} is outside of buffer bounds`:"Attempt to access memory outside buffer bounds"}),RangeError),$("ERR_INVALID_ARG_TYPE",(function(e,t){return`The "${e}" argument must be of type number. Received type ${typeof t}`}),TypeError),$("ERR_OUT_OF_RANGE",(function(e,t,n){let r=`The value of "${e}" is out of range.`,o=n;return Number.isInteger(n)&&Math.abs(n)>2**32?o=q(String(n)):"bigint"==typeof n&&(o=String(n),(n>BigInt(2)**BigInt(32)||n<-(BigInt(2)**BigInt(32)))&&(o=q(o)),o+="n"),r+=` It must be ${t}. Received ${o}`,r}),RangeError);const W=/[^+/0-9A-Za-z-_]/g;function J(e,t){let n;t=t||1/0;const r=e.length;let o=null;const s=[];for(let i=0;i55295&&n<57344){if(!o){if(n>56319){(t-=3)>-1&&s.push(239,191,189);continue}if(i+1===r){(t-=3)>-1&&s.push(239,191,189);continue}o=n;continue}if(n<56320){(t-=3)>-1&&s.push(239,191,189),o=n;continue}n=65536+(o-55296<<10|n-56320)}else o&&(t-=3)>-1&&s.push(239,191,189);if(o=null,n<128){if((t-=1)<0)break;s.push(n)}else if(n<2048){if((t-=2)<0)break;s.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;s.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;s.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return s}function K(e){return r.toByteArray(function(e){if((e=(e=e.split("=")[0]).trim().replace(W,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function H(e,t,n,r){let o;for(o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}function G(e,t){return e instanceof t||null!=e&&null!=e.constructor&&null!=e.constructor.name&&e.constructor.name===t.name}function Z(e){return e!=e}const Y=function(){const e="0123456789abcdef",t=new Array(256);for(let n=0;n<16;++n){const r=16*n;for(let o=0;o<16;++o)t[r+o]=e[n]+e[o]}return t}();function X(e){return"undefined"==typeof BigInt?Q:e}function Q(){throw new Error("BigInt not supported")}},21924:(e,t,n)=>{"use strict";var r=n(40210),o=n(55559),s=o(r("String.prototype.indexOf"));e.exports=function(e,t){var n=r(e,!!t);return"function"==typeof n&&s(e,".prototype.")>-1?o(n):n}},55559:(e,t,n)=>{"use strict";var r=n(58612),o=n(40210),s=o("%Function.prototype.apply%"),i=o("%Function.prototype.call%"),a=o("%Reflect.apply%",!0)||r.call(i,s),l=o("%Object.getOwnPropertyDescriptor%",!0),c=o("%Object.defineProperty%",!0),u=o("%Math.max%");if(c)try{c({},"a",{value:1})}catch(e){c=null}e.exports=function(e){var t=a(r,i,arguments);l&&c&&(l(t,"length").configurable&&c(t,"length",{value:1+u(0,e.length-(arguments.length-1))}));return t};var p=function(){return a(r,s,arguments)};c?c(e.exports,"apply",{value:p}):e.exports.apply=p},94184:(e,t)=>{var n;!function(){"use strict";var r={}.hasOwnProperty;function o(){for(var e=[],t=0;t{"use strict";t.parse=function(e,t){if("string"!=typeof e)throw new TypeError("argument str must be a string");var n={},r=(t||{}).decode||o,s=0;for(;s{"use strict";var r=n(11742),o={"text/plain":"Text","text/html":"Url",default:"Text"};e.exports=function(e,t){var n,s,i,a,l,c,u=!1;t||(t={}),n=t.debug||!1;try{if(i=r(),a=document.createRange(),l=document.getSelection(),(c=document.createElement("span")).textContent=e,c.ariaHidden="true",c.style.all="unset",c.style.position="fixed",c.style.top=0,c.style.clip="rect(0, 0, 0, 0)",c.style.whiteSpace="pre",c.style.webkitUserSelect="text",c.style.MozUserSelect="text",c.style.msUserSelect="text",c.style.userSelect="text",c.addEventListener("copy",(function(r){if(r.stopPropagation(),t.format)if(r.preventDefault(),void 0===r.clipboardData){n&&console.warn("unable to use e.clipboardData"),n&&console.warn("trying IE specific stuff"),window.clipboardData.clearData();var s=o[t.format]||o.default;window.clipboardData.setData(s,e)}else r.clipboardData.clearData(),r.clipboardData.setData(t.format,e);t.onCopy&&(r.preventDefault(),t.onCopy(r.clipboardData))})),document.body.appendChild(c),a.selectNodeContents(c),l.addRange(a),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");u=!0}catch(r){n&&console.error("unable to copy using execCommand: ",r),n&&console.warn("trying IE specific stuff");try{window.clipboardData.setData(t.format||"text",e),t.onCopy&&t.onCopy(window.clipboardData),u=!0}catch(r){n&&console.error("unable to copy using clipboardData: ",r),n&&console.error("falling back to prompt"),s=function(e){var t=(/mac os x/i.test(navigator.userAgent)?"โŒ˜":"Ctrl")+"+C";return e.replace(/#{\s*key\s*}/g,t)}("message"in t?t.message:"Copy to clipboard: #{key}, Enter"),window.prompt(s,e)}}finally{l&&("function"==typeof l.removeRange?l.removeRange(a):l.removeAllRanges()),c&&document.body.removeChild(c),i()}return u}},90093:(e,t,n)=>{var r=n(28196);e.exports=r},3688:(e,t,n)=>{var r=n(11955);e.exports=r},83838:(e,t,n)=>{var r=n(46279);e.exports=r},15684:(e,t,n)=>{var r=n(19373);e.exports=r},81331:(e,t,n)=>{var r=n(52759);e.exports=r},65362:(e,t,n)=>{var r=n(63383);e.exports=r},91254:(e,t,n)=>{var r=n(57396);e.exports=r},43536:(e,t,n)=>{var r=n(41910);e.exports=r},37331:(e,t,n)=>{var r=n(79427);e.exports=r},68522:(e,t,n)=>{var r=n(62857);e.exports=r},73151:(e,t,n)=>{var r=n(9534);e.exports=r},45012:(e,t,n)=>{var r=n(23059);e.exports=r},80281:(e,t,n)=>{var r=n(92547);n(97522),n(43975),n(45414),e.exports=r},40031:(e,t,n)=>{var r=n(46509);e.exports=r},17487:(e,t,n)=>{var r=n(35774);e.exports=r},54493:(e,t,n)=>{n(77971),n(53242);var r=n(54058);e.exports=r.Array.from},24034:(e,t,n)=>{n(92737);var r=n(54058);e.exports=r.Array.isArray},15367:(e,t,n)=>{n(85906);var r=n(35703);e.exports=r("Array").concat},12710:(e,t,n)=>{n(66274),n(55967);var r=n(35703);e.exports=r("Array").entries},51459:(e,t,n)=>{n(48851);var r=n(35703);e.exports=r("Array").every},6172:(e,t,n)=>{n(80290);var r=n(35703);e.exports=r("Array").fill},62383:(e,t,n)=>{n(21501);var r=n(35703);e.exports=r("Array").filter},60009:(e,t,n)=>{n(44929);var r=n(35703);e.exports=r("Array").findIndex},17671:(e,t,n)=>{n(80833);var r=n(35703);e.exports=r("Array").find},99324:(e,t,n)=>{n(2437);var r=n(35703);e.exports=r("Array").forEach},80991:(e,t,n)=>{n(97690);var r=n(35703);e.exports=r("Array").includes},8700:(e,t,n)=>{n(99076);var r=n(35703);e.exports=r("Array").indexOf},95909:(e,t,n)=>{n(66274),n(55967);var r=n(35703);e.exports=r("Array").keys},6442:(e,t,n)=>{n(75915);var r=n(35703);e.exports=r("Array").lastIndexOf},23866:(e,t,n)=>{n(68787);var r=n(35703);e.exports=r("Array").map},9896:(e,t,n)=>{n(48528);var r=n(35703);e.exports=r("Array").push},52999:(e,t,n)=>{n(81876);var r=n(35703);e.exports=r("Array").reduce},24900:(e,t,n)=>{n(60186);var r=n(35703);e.exports=r("Array").slice},3824:(e,t,n)=>{n(36026);var r=n(35703);e.exports=r("Array").some},2948:(e,t,n)=>{n(4115);var r=n(35703);e.exports=r("Array").sort},78209:(e,t,n)=>{n(98611);var r=n(35703);e.exports=r("Array").splice},14423:(e,t,n)=>{n(66274),n(55967);var r=n(35703);e.exports=r("Array").values},81103:(e,t,n)=>{n(95160);var r=n(54058);e.exports=r.Date.now},27700:(e,t,n)=>{n(73381);var r=n(35703);e.exports=r("Function").bind},16246:(e,t,n)=>{var r=n(7046),o=n(27700),s=Function.prototype;e.exports=function(e){var t=e.bind;return e===s||r(s,e)&&t===s.bind?o:t}},56043:(e,t,n)=>{var r=n(7046),o=n(15367),s=Array.prototype;e.exports=function(e){var t=e.concat;return e===s||r(s,e)&&t===s.concat?o:t}},13160:(e,t,n)=>{var r=n(7046),o=n(51459),s=Array.prototype;e.exports=function(e){var t=e.every;return e===s||r(s,e)&&t===s.every?o:t}},80446:(e,t,n)=>{var r=n(7046),o=n(6172),s=Array.prototype;e.exports=function(e){var t=e.fill;return e===s||r(s,e)&&t===s.fill?o:t}},2480:(e,t,n)=>{var r=n(7046),o=n(62383),s=Array.prototype;e.exports=function(e){var t=e.filter;return e===s||r(s,e)&&t===s.filter?o:t}},7147:(e,t,n)=>{var r=n(7046),o=n(60009),s=Array.prototype;e.exports=function(e){var t=e.findIndex;return e===s||r(s,e)&&t===s.findIndex?o:t}},32236:(e,t,n)=>{var r=n(7046),o=n(17671),s=Array.prototype;e.exports=function(e){var t=e.find;return e===s||r(s,e)&&t===s.find?o:t}},58557:(e,t,n)=>{var r=n(7046),o=n(80991),s=n(21631),i=Array.prototype,a=String.prototype;e.exports=function(e){var t=e.includes;return e===i||r(i,e)&&t===i.includes?o:"string"==typeof e||e===a||r(a,e)&&t===a.includes?s:t}},34570:(e,t,n)=>{var r=n(7046),o=n(8700),s=Array.prototype;e.exports=function(e){var t=e.indexOf;return e===s||r(s,e)&&t===s.indexOf?o:t}},57564:(e,t,n)=>{var r=n(7046),o=n(6442),s=Array.prototype;e.exports=function(e){var t=e.lastIndexOf;return e===s||r(s,e)&&t===s.lastIndexOf?o:t}},88287:(e,t,n)=>{var r=n(7046),o=n(23866),s=Array.prototype;e.exports=function(e){var t=e.map;return e===s||r(s,e)&&t===s.map?o:t}},93993:(e,t,n)=>{var r=n(7046),o=n(9896),s=Array.prototype;e.exports=function(e){var t=e.push;return e===s||r(s,e)&&t===s.push?o:t}},68025:(e,t,n)=>{var r=n(7046),o=n(52999),s=Array.prototype;e.exports=function(e){var t=e.reduce;return e===s||r(s,e)&&t===s.reduce?o:t}},59257:(e,t,n)=>{var r=n(7046),o=n(80454),s=String.prototype;e.exports=function(e){var t=e.repeat;return"string"==typeof e||e===s||r(s,e)&&t===s.repeat?o:t}},69601:(e,t,n)=>{var r=n(7046),o=n(24900),s=Array.prototype;e.exports=function(e){var t=e.slice;return e===s||r(s,e)&&t===s.slice?o:t}},28299:(e,t,n)=>{var r=n(7046),o=n(3824),s=Array.prototype;e.exports=function(e){var t=e.some;return e===s||r(s,e)&&t===s.some?o:t}},69355:(e,t,n)=>{var r=n(7046),o=n(2948),s=Array.prototype;e.exports=function(e){var t=e.sort;return e===s||r(s,e)&&t===s.sort?o:t}},18339:(e,t,n)=>{var r=n(7046),o=n(78209),s=Array.prototype;e.exports=function(e){var t=e.splice;return e===s||r(s,e)&&t===s.splice?o:t}},71611:(e,t,n)=>{var r=n(7046),o=n(3269),s=String.prototype;e.exports=function(e){var t=e.startsWith;return"string"==typeof e||e===s||r(s,e)&&t===s.startsWith?o:t}},62774:(e,t,n)=>{var r=n(7046),o=n(13348),s=String.prototype;e.exports=function(e){var t=e.trim;return"string"==typeof e||e===s||r(s,e)&&t===s.trim?o:t}},84426:(e,t,n)=>{n(32619);var r=n(54058),o=n(79730);r.JSON||(r.JSON={stringify:JSON.stringify}),e.exports=function(e,t,n){return o(r.JSON.stringify,null,arguments)}},91018:(e,t,n)=>{n(66274),n(37501),n(55967),n(77971);var r=n(54058);e.exports=r.Map},97849:(e,t,n)=>{n(54973),e.exports=Math.pow(2,-52)},3820:(e,t,n)=>{n(30800);var r=n(54058);e.exports=r.Number.isInteger},45999:(e,t,n)=>{n(49221);var r=n(54058);e.exports=r.Object.assign},7702:(e,t,n)=>{n(74979);var r=n(54058).Object,o=e.exports=function(e,t){return r.defineProperties(e,t)};r.defineProperties.sham&&(o.sham=!0)},48171:(e,t,n)=>{n(86450);var r=n(54058).Object,o=e.exports=function(e,t,n){return r.defineProperty(e,t,n)};r.defineProperty.sham&&(o.sham=!0)},73081:(e,t,n)=>{n(94366);var r=n(54058);e.exports=r.Object.entries},7699:(e,t,n)=>{n(66274),n(28387);var r=n(54058);e.exports=r.Object.fromEntries},286:(e,t,n)=>{n(46924);var r=n(54058).Object,o=e.exports=function(e,t){return r.getOwnPropertyDescriptor(e,t)};r.getOwnPropertyDescriptor.sham&&(o.sham=!0)},92766:(e,t,n)=>{n(88482);var r=n(54058);e.exports=r.Object.getOwnPropertyDescriptors},30498:(e,t,n)=>{n(35824);var r=n(54058);e.exports=r.Object.getOwnPropertySymbols},48494:(e,t,n)=>{n(21724);var r=n(54058);e.exports=r.Object.keys},98430:(e,t,n)=>{n(26614);var r=n(54058);e.exports=r.Object.values},52956:(e,t,n)=>{n(47627),n(66274),n(55967),n(98881),n(4560),n(91302),n(44349),n(77971);var r=n(54058);e.exports=r.Promise},76998:(e,t,n)=>{n(66274),n(55967),n(69008),n(77971);var r=n(54058);e.exports=r.Set},97089:(e,t,n)=>{n(74679);var r=n(54058);e.exports=r.String.raw},21631:(e,t,n)=>{n(11035);var r=n(35703);e.exports=r("String").includes},80454:(e,t,n)=>{n(60986);var r=n(35703);e.exports=r("String").repeat},3269:(e,t,n)=>{n(94761);var r=n(35703);e.exports=r("String").startsWith},13348:(e,t,n)=>{n(57398);var r=n(35703);e.exports=r("String").trim},57473:(e,t,n)=>{n(85906),n(55967),n(35824),n(8555),n(52615),n(21732),n(35903),n(1825),n(28394),n(45915),n(61766),n(62737),n(89911),n(74315),n(63131),n(64714),n(70659),n(69120),n(79413),n(1502);var r=n(54058);e.exports=r.Symbol},24227:(e,t,n)=>{n(66274),n(55967),n(77971),n(1825);var r=n(11477);e.exports=r.f("iterator")},62978:(e,t,n)=>{n(18084),n(63131);var r=n(11477);e.exports=r.f("toPrimitive")},32304:(e,t,n)=>{n(66274),n(55967),n(54334);var r=n(54058);e.exports=r.WeakMap},29567:(e,t,n)=>{n(66274),n(55967),n(1773);var r=n(54058);e.exports=r.WeakSet},14122:(e,t,n)=>{e.exports=n(89097)},44442:(e,t,n)=>{e.exports=n(51675)},57152:(e,t,n)=>{e.exports=n(82507)},69447:(e,t,n)=>{e.exports=n(628)},1449:(e,t,n)=>{e.exports=n(34501)},60269:(e,t,n)=>{e.exports=n(76936)},70573:(e,t,n)=>{e.exports=n(18180)},73685:(e,t,n)=>{e.exports=n(80621)},27533:(e,t,n)=>{e.exports=n(22948)},39057:(e,t,n)=>{e.exports=n(82108)},84710:(e,t,n)=>{e.exports=n(14058)},93799:(e,t,n)=>{e.exports=n(92093)},86600:(e,t,n)=>{e.exports=n(52201)},9759:(e,t,n)=>{e.exports=n(27398)},71384:(e,t,n)=>{e.exports=n(26189)},89097:(e,t,n)=>{var r=n(90093);e.exports=r},51675:(e,t,n)=>{var r=n(3688);e.exports=r},82507:(e,t,n)=>{var r=n(83838);e.exports=r},628:(e,t,n)=>{var r=n(15684);e.exports=r},34501:(e,t,n)=>{var r=n(81331);e.exports=r},76936:(e,t,n)=>{var r=n(65362);e.exports=r},18180:(e,t,n)=>{var r=n(91254);e.exports=r},80621:(e,t,n)=>{var r=n(43536);e.exports=r},22948:(e,t,n)=>{var r=n(37331);e.exports=r},82108:(e,t,n)=>{var r=n(68522);e.exports=r},14058:(e,t,n)=>{var r=n(73151);e.exports=r},92093:(e,t,n)=>{var r=n(45012);e.exports=r},52201:(e,t,n)=>{var r=n(80281);n(28783),n(97618),n(6989),n(65799),n(46774),n(22731),n(85605),n(31943),n(80620),n(36172),e.exports=r},27398:(e,t,n)=>{var r=n(40031);e.exports=r},26189:(e,t,n)=>{var r=n(17487);e.exports=r},24883:(e,t,n)=>{var r=n(57475),o=n(69826),s=TypeError;e.exports=function(e){if(r(e))return e;throw s(o(e)+" is not a function")}},174:(e,t,n)=>{var r=n(24284),o=n(69826),s=TypeError;e.exports=function(e){if(r(e))return e;throw s(o(e)+" is not a constructor")}},11851:(e,t,n)=>{var r=n(57475),o=String,s=TypeError;e.exports=function(e){if("object"==typeof e||r(e))return e;throw s("Can't set "+o(e)+" as a prototype")}},18479:e=>{e.exports=function(){}},5743:(e,t,n)=>{var r=n(7046),o=TypeError;e.exports=function(e,t){if(r(t,e))return e;throw o("Incorrect invocation")}},96059:(e,t,n)=>{var r=n(10941),o=String,s=TypeError;e.exports=function(e){if(r(e))return e;throw s(o(e)+" is not an object")}},97135:(e,t,n)=>{var r=n(95981);e.exports=r((function(){if("function"==typeof ArrayBuffer){var e=new ArrayBuffer(8);Object.isExtensible(e)&&Object.defineProperty(e,"a",{value:8})}}))},91860:(e,t,n)=>{"use strict";var r=n(89678),o=n(59413),s=n(10623);e.exports=function(e){for(var t=r(this),n=s(t),i=arguments.length,a=o(i>1?arguments[1]:void 0,n),l=i>2?arguments[2]:void 0,c=void 0===l?n:o(l,n);c>a;)t[a++]=e;return t}},56837:(e,t,n)=>{"use strict";var r=n(3610).forEach,o=n(34194)("forEach");e.exports=o?[].forEach:function(e){return r(this,e,arguments.length>1?arguments[1]:void 0)}},11354:(e,t,n)=>{"use strict";var r=n(86843),o=n(78834),s=n(89678),i=n(75196),a=n(6782),l=n(24284),c=n(10623),u=n(55449),p=n(53476),h=n(22902),f=Array;e.exports=function(e){var t=s(e),n=l(this),d=arguments.length,m=d>1?arguments[1]:void 0,g=void 0!==m;g&&(m=r(m,d>2?arguments[2]:void 0));var y,v,b,w,E,x,S=h(t),_=0;if(!S||this===f&&a(S))for(y=c(t),v=n?new this(y):f(y);y>_;_++)x=g?m(t[_],_):t[_],u(v,_,x);else for(E=(w=p(t,S)).next,v=n?new this:[];!(b=o(E,w)).done;_++)x=g?i(w,m,[b.value,_],!0):b.value,u(v,_,x);return v.length=_,v}},31692:(e,t,n)=>{var r=n(74529),o=n(59413),s=n(10623),i=function(e){return function(t,n,i){var a,l=r(t),c=s(l),u=o(i,c);if(e&&n!=n){for(;c>u;)if((a=l[u++])!=a)return!0}else for(;c>u;u++)if((e||u in l)&&l[u]===n)return e||u||0;return!e&&-1}};e.exports={includes:i(!0),indexOf:i(!1)}},3610:(e,t,n)=>{var r=n(86843),o=n(95329),s=n(37026),i=n(89678),a=n(10623),l=n(64692),c=o([].push),u=function(e){var t=1==e,n=2==e,o=3==e,u=4==e,p=6==e,h=7==e,f=5==e||p;return function(d,m,g,y){for(var v,b,w=i(d),E=s(w),x=r(m,g),S=a(E),_=0,j=y||l,O=t?j(d,S):n||h?j(d,0):void 0;S>_;_++)if((f||_ in E)&&(b=x(v=E[_],_,w),e))if(t)O[_]=b;else if(b)switch(e){case 3:return!0;case 5:return v;case 6:return _;case 2:c(O,v)}else switch(e){case 4:return!1;case 7:c(O,v)}return p?-1:o||u?u:O}};e.exports={forEach:u(0),map:u(1),filter:u(2),some:u(3),every:u(4),find:u(5),findIndex:u(6),filterReject:u(7)}},67145:(e,t,n)=>{"use strict";var r=n(79730),o=n(74529),s=n(62435),i=n(10623),a=n(34194),l=Math.min,c=[].lastIndexOf,u=!!c&&1/[1].lastIndexOf(1,-0)<0,p=a("lastIndexOf"),h=u||!p;e.exports=h?function(e){if(u)return r(c,this,arguments)||0;var t=o(this),n=i(t),a=n-1;for(arguments.length>1&&(a=l(a,s(arguments[1]))),a<0&&(a=n+a);a>=0;a--)if(a in t&&t[a]===e)return a||0;return-1}:c},50568:(e,t,n)=>{var r=n(95981),o=n(99813),s=n(53385),i=o("species");e.exports=function(e){return s>=51||!r((function(){var t=[];return(t.constructor={})[i]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},34194:(e,t,n)=>{"use strict";var r=n(95981);e.exports=function(e,t){var n=[][e];return!!n&&r((function(){n.call(null,t||function(){return 1},1)}))}},46499:(e,t,n)=>{var r=n(24883),o=n(89678),s=n(37026),i=n(10623),a=TypeError,l=function(e){return function(t,n,l,c){r(n);var u=o(t),p=s(u),h=i(u),f=e?h-1:0,d=e?-1:1;if(l<2)for(;;){if(f in p){c=p[f],f+=d;break}if(f+=d,e?f<0:h<=f)throw a("Reduce of empty array with no initial value")}for(;e?f>=0:h>f;f+=d)f in p&&(c=n(c,p[f],f,u));return c}};e.exports={left:l(!1),right:l(!0)}},89779:(e,t,n)=>{"use strict";var r=n(55746),o=n(1052),s=TypeError,i=Object.getOwnPropertyDescriptor,a=r&&!function(){if(void 0!==this)return!0;try{Object.defineProperty([],"length",{writable:!1}).length=1}catch(e){return e instanceof TypeError}}();e.exports=a?function(e,t){if(o(e)&&!i(e,"length").writable)throw s("Cannot set read only .length");return e.length=t}:function(e,t){return e.length=t}},15790:(e,t,n)=>{var r=n(59413),o=n(10623),s=n(55449),i=Array,a=Math.max;e.exports=function(e,t,n){for(var l=o(e),c=r(t,l),u=r(void 0===n?l:n,l),p=i(a(u-c,0)),h=0;c{var r=n(95329);e.exports=r([].slice)},61388:(e,t,n)=>{var r=n(15790),o=Math.floor,s=function(e,t){var n=e.length,l=o(n/2);return n<8?i(e,t):a(e,s(r(e,0,l),t),s(r(e,l),t),t)},i=function(e,t){for(var n,r,o=e.length,s=1;s0;)e[r]=e[--r];r!==s++&&(e[r]=n)}return e},a=function(e,t,n,r){for(var o=t.length,s=n.length,i=0,a=0;i{var r=n(1052),o=n(24284),s=n(10941),i=n(99813)("species"),a=Array;e.exports=function(e){var t;return r(e)&&(t=e.constructor,(o(t)&&(t===a||r(t.prototype))||s(t)&&null===(t=t[i]))&&(t=void 0)),void 0===t?a:t}},64692:(e,t,n)=>{var r=n(5693);e.exports=function(e,t){return new(r(e))(0===t?0:t)}},75196:(e,t,n)=>{var r=n(96059),o=n(7609);e.exports=function(e,t,n,s){try{return s?t(r(n)[0],n[1]):t(n)}catch(t){o(e,"throw",t)}}},21385:(e,t,n)=>{var r=n(99813)("iterator"),o=!1;try{var s=0,i={next:function(){return{done:!!s++}},return:function(){o=!0}};i[r]=function(){return this},Array.from(i,(function(){throw 2}))}catch(e){}e.exports=function(e,t){if(!t&&!o)return!1;var n=!1;try{var s={};s[r]=function(){return{next:function(){return{done:n=!0}}}},e(s)}catch(e){}return n}},82532:(e,t,n)=>{var r=n(95329),o=r({}.toString),s=r("".slice);e.exports=function(e){return s(o(e),8,-1)}},9697:(e,t,n)=>{var r=n(22885),o=n(57475),s=n(82532),i=n(99813)("toStringTag"),a=Object,l="Arguments"==s(function(){return arguments}());e.exports=r?s:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=a(e),i))?n:l?s(t):"Object"==(r=s(t))&&o(t.callee)?"Arguments":r}},85616:(e,t,n)=>{"use strict";var r=n(29290),o=n(29202),s=n(94380),i=n(86843),a=n(5743),l=n(82119),c=n(93091),u=n(75105),p=n(23538),h=n(94431),f=n(55746),d=n(21647).fastKey,m=n(45402),g=m.set,y=m.getterFor;e.exports={getConstructor:function(e,t,n,u){var p=e((function(e,o){a(e,h),g(e,{type:t,index:r(null),first:void 0,last:void 0,size:0}),f||(e.size=0),l(o)||c(o,e[u],{that:e,AS_ENTRIES:n})})),h=p.prototype,m=y(t),v=function(e,t,n){var r,o,s=m(e),i=b(e,t);return i?i.value=n:(s.last=i={index:o=d(t,!0),key:t,value:n,previous:r=s.last,next:void 0,removed:!1},s.first||(s.first=i),r&&(r.next=i),f?s.size++:e.size++,"F"!==o&&(s.index[o]=i)),e},b=function(e,t){var n,r=m(e),o=d(t);if("F"!==o)return r.index[o];for(n=r.first;n;n=n.next)if(n.key==t)return n};return s(h,{clear:function(){for(var e=m(this),t=e.index,n=e.first;n;)n.removed=!0,n.previous&&(n.previous=n.previous.next=void 0),delete t[n.index],n=n.next;e.first=e.last=void 0,f?e.size=0:this.size=0},delete:function(e){var t=this,n=m(t),r=b(t,e);if(r){var o=r.next,s=r.previous;delete n.index[r.index],r.removed=!0,s&&(s.next=o),o&&(o.previous=s),n.first==r&&(n.first=o),n.last==r&&(n.last=s),f?n.size--:t.size--}return!!r},forEach:function(e){for(var t,n=m(this),r=i(e,arguments.length>1?arguments[1]:void 0);t=t?t.next:n.first;)for(r(t.value,t.key,this);t&&t.removed;)t=t.previous},has:function(e){return!!b(this,e)}}),s(h,n?{get:function(e){var t=b(this,e);return t&&t.value},set:function(e,t){return v(this,0===e?0:e,t)}}:{add:function(e){return v(this,e=0===e?0:e,e)}}),f&&o(h,"size",{configurable:!0,get:function(){return m(this).size}}),p},setStrong:function(e,t,n){var r=t+" Iterator",o=y(t),s=y(r);u(e,t,(function(e,t){g(this,{type:r,target:e,state:o(e),kind:t,last:void 0})}),(function(){for(var e=s(this),t=e.kind,n=e.last;n&&n.removed;)n=n.previous;return e.target&&(e.last=n=n?n.next:e.state.first)?p("keys"==t?n.key:"values"==t?n.value:[n.key,n.value],!1):(e.target=void 0,p(void 0,!0))}),n?"entries":"values",!n,!0),h(t)}}},8850:(e,t,n)=>{"use strict";var r=n(95329),o=n(94380),s=n(21647).getWeakData,i=n(5743),a=n(96059),l=n(82119),c=n(10941),u=n(93091),p=n(3610),h=n(90953),f=n(45402),d=f.set,m=f.getterFor,g=p.find,y=p.findIndex,v=r([].splice),b=0,w=function(e){return e.frozen||(e.frozen=new E)},E=function(){this.entries=[]},x=function(e,t){return g(e.entries,(function(e){return e[0]===t}))};E.prototype={get:function(e){var t=x(this,e);if(t)return t[1]},has:function(e){return!!x(this,e)},set:function(e,t){var n=x(this,e);n?n[1]=t:this.entries.push([e,t])},delete:function(e){var t=y(this.entries,(function(t){return t[0]===e}));return~t&&v(this.entries,t,1),!!~t}},e.exports={getConstructor:function(e,t,n,r){var p=e((function(e,o){i(e,f),d(e,{type:t,id:b++,frozen:void 0}),l(o)||u(o,e[r],{that:e,AS_ENTRIES:n})})),f=p.prototype,g=m(t),y=function(e,t,n){var r=g(e),o=s(a(t),!0);return!0===o?w(r).set(t,n):o[r.id]=n,e};return o(f,{delete:function(e){var t=g(this);if(!c(e))return!1;var n=s(e);return!0===n?w(t).delete(e):n&&h(n,t.id)&&delete n[t.id]},has:function(e){var t=g(this);if(!c(e))return!1;var n=s(e);return!0===n?w(t).has(e):n&&h(n,t.id)}}),o(f,n?{get:function(e){var t=g(this);if(c(e)){var n=s(e);return!0===n?w(t).get(e):n?n[t.id]:void 0}},set:function(e,t){return y(this,e,t)}}:{add:function(e){return y(this,e,!0)}}),p}}},24683:(e,t,n)=>{"use strict";var r=n(76887),o=n(21899),s=n(21647),i=n(95981),a=n(32029),l=n(93091),c=n(5743),u=n(57475),p=n(10941),h=n(90904),f=n(65988).f,d=n(3610).forEach,m=n(55746),g=n(45402),y=g.set,v=g.getterFor;e.exports=function(e,t,n){var g,b=-1!==e.indexOf("Map"),w=-1!==e.indexOf("Weak"),E=b?"set":"add",x=o[e],S=x&&x.prototype,_={};if(m&&u(x)&&(w||S.forEach&&!i((function(){(new x).entries().next()})))){var j=(g=t((function(t,n){y(c(t,j),{type:e,collection:new x}),null!=n&&l(n,t[E],{that:t,AS_ENTRIES:b})}))).prototype,O=v(e);d(["add","clear","delete","forEach","get","has","set","keys","values","entries"],(function(e){var t="add"==e||"set"==e;!(e in S)||w&&"clear"==e||a(j,e,(function(n,r){var o=O(this).collection;if(!t&&w&&!p(n))return"get"==e&&void 0;var s=o[e](0===n?0:n,r);return t?this:s}))})),w||f(j,"size",{configurable:!0,get:function(){return O(this).collection.size}})}else g=n.getConstructor(t,e,b,E),s.enable();return h(g,e,!1,!0),_[e]=g,r({global:!0,forced:!0},_),w||n.setStrong(g,e,b),g}},23489:(e,t,n)=>{var r=n(90953),o=n(31136),s=n(49677),i=n(65988);e.exports=function(e,t,n){for(var a=o(t),l=i.f,c=s.f,u=0;u{var r=n(99813)("match");e.exports=function(e){var t=/./;try{"/./"[e](t)}catch(n){try{return t[r]=!1,"/./"[e](t)}catch(e){}}return!1}},64160:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype}))},23538:e=>{e.exports=function(e,t){return{value:e,done:t}}},32029:(e,t,n)=>{var r=n(55746),o=n(65988),s=n(31887);e.exports=r?function(e,t,n){return o.f(e,t,s(1,n))}:function(e,t,n){return e[t]=n,e}},31887:e=>{e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},55449:(e,t,n)=>{"use strict";var r=n(83894),o=n(65988),s=n(31887);e.exports=function(e,t,n){var i=r(t);i in e?o.f(e,i,s(0,n)):e[i]=n}},29202:(e,t,n)=>{var r=n(65988);e.exports=function(e,t,n){return r.f(e,t,n)}},95929:(e,t,n)=>{var r=n(32029);e.exports=function(e,t,n,o){return o&&o.enumerable?e[t]=n:r(e,t,n),e}},94380:(e,t,n)=>{var r=n(95929);e.exports=function(e,t,n){for(var o in t)n&&n.unsafe&&e[o]?e[o]=t[o]:r(e,o,t[o],n);return e}},75609:(e,t,n)=>{var r=n(21899),o=Object.defineProperty;e.exports=function(e,t){try{o(r,e,{value:t,configurable:!0,writable:!0})}catch(n){r[e]=t}return t}},15863:(e,t,n)=>{"use strict";var r=n(69826),o=TypeError;e.exports=function(e,t){if(!delete e[t])throw o("Cannot delete property "+r(t)+" of "+r(e))}},55746:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},76616:e=>{var t="object"==typeof document&&document.all,n=void 0===t&&void 0!==t;e.exports={all:t,IS_HTMLDDA:n}},61333:(e,t,n)=>{var r=n(21899),o=n(10941),s=r.document,i=o(s)&&o(s.createElement);e.exports=function(e){return i?s.createElement(e):{}}},66796:e=>{var t=TypeError;e.exports=function(e){if(e>9007199254740991)throw t("Maximum allowed index exceeded");return e}},63281:e=>{e.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},34342:(e,t,n)=>{var r=n(2861).match(/firefox\/(\d+)/i);e.exports=!!r&&+r[1]},23321:(e,t,n)=>{var r=n(48501),o=n(6049);e.exports=!r&&!o&&"object"==typeof window&&"object"==typeof document},56491:e=>{e.exports="function"==typeof Bun&&Bun&&"string"==typeof Bun.version},48501:e=>{e.exports="object"==typeof Deno&&Deno&&"object"==typeof Deno.version},81046:(e,t,n)=>{var r=n(2861);e.exports=/MSIE|Trident/.test(r)},4470:(e,t,n)=>{var r=n(2861);e.exports=/ipad|iphone|ipod/i.test(r)&&"undefined"!=typeof Pebble},22749:(e,t,n)=>{var r=n(2861);e.exports=/(?:ipad|iphone|ipod).*applewebkit/i.test(r)},6049:(e,t,n)=>{var r=n(34155),o=n(82532);e.exports=void 0!==r&&"process"==o(r)},58045:(e,t,n)=>{var r=n(2861);e.exports=/web0s(?!.*chrome)/i.test(r)},2861:e=>{e.exports="undefined"!=typeof navigator&&String(navigator.userAgent)||""},53385:(e,t,n)=>{var r,o,s=n(21899),i=n(2861),a=s.process,l=s.Deno,c=a&&a.versions||l&&l.version,u=c&&c.v8;u&&(o=(r=u.split("."))[0]>0&&r[0]<4?1:+(r[0]+r[1])),!o&&i&&(!(r=i.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=i.match(/Chrome\/(\d+)/))&&(o=+r[1]),e.exports=o},18938:(e,t,n)=>{var r=n(2861).match(/AppleWebKit\/(\d+)\./);e.exports=!!r&&+r[1]},35703:(e,t,n)=>{var r=n(54058);e.exports=function(e){return r[e+"Prototype"]}},56759:e=>{e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},53995:(e,t,n)=>{var r=n(95329),o=Error,s=r("".replace),i=String(o("zxcasd").stack),a=/\n\s*at [^:]*:[^\n]*/,l=a.test(i);e.exports=function(e,t){if(l&&"string"==typeof e&&!o.prepareStackTrace)for(;t--;)e=s(e,a,"");return e}},79585:(e,t,n)=>{var r=n(32029),o=n(53995),s=n(18780),i=Error.captureStackTrace;e.exports=function(e,t,n,a){s&&(i?i(e,t):r(e,"stack",o(n,a)))}},18780:(e,t,n)=>{var r=n(95981),o=n(31887);e.exports=!r((function(){var e=Error("a");return!("stack"in e)||(Object.defineProperty(e,"stack",o(1,7)),7!==e.stack)}))},76887:(e,t,n)=>{"use strict";var r=n(21899),o=n(79730),s=n(97484),i=n(57475),a=n(49677).f,l=n(37252),c=n(54058),u=n(86843),p=n(32029),h=n(90953),f=function(e){var t=function(n,r,s){if(this instanceof t){switch(arguments.length){case 0:return new e;case 1:return new e(n);case 2:return new e(n,r)}return new e(n,r,s)}return o(e,this,arguments)};return t.prototype=e.prototype,t};e.exports=function(e,t){var n,o,d,m,g,y,v,b,w,E=e.target,x=e.global,S=e.stat,_=e.proto,j=x?r:S?r[E]:(r[E]||{}).prototype,O=x?c:c[E]||p(c,E,{})[E],k=O.prototype;for(m in t)o=!(n=l(x?m:E+(S?".":"#")+m,e.forced))&&j&&h(j,m),y=O[m],o&&(v=e.dontCallGetSet?(w=a(j,m))&&w.value:j[m]),g=o&&v?v:t[m],o&&typeof y==typeof g||(b=e.bind&&o?u(g,r):e.wrap&&o?f(g):_&&i(g)?s(g):g,(e.sham||g&&g.sham||y&&y.sham)&&p(b,"sham",!0),p(O,m,b),_&&(h(c,d=E+"Prototype")||p(c,d,{}),p(c[d],m,g),e.real&&k&&(n||!k[m])&&p(k,m,g)))}},95981:e=>{e.exports=function(e){try{return!!e()}catch(e){return!0}}},45602:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){return Object.isExtensible(Object.preventExtensions({}))}))},79730:(e,t,n)=>{var r=n(18285),o=Function.prototype,s=o.apply,i=o.call;e.exports="object"==typeof Reflect&&Reflect.apply||(r?i.bind(s):function(){return i.apply(s,arguments)})},86843:(e,t,n)=>{var r=n(97484),o=n(24883),s=n(18285),i=r(r.bind);e.exports=function(e,t){return o(e),void 0===t?e:s?i(e,t):function(){return e.apply(t,arguments)}}},18285:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){var e=function(){}.bind();return"function"!=typeof e||e.hasOwnProperty("prototype")}))},98308:(e,t,n)=>{"use strict";var r=n(95329),o=n(24883),s=n(10941),i=n(90953),a=n(93765),l=n(18285),c=Function,u=r([].concat),p=r([].join),h={};e.exports=l?c.bind:function(e){var t=o(this),n=t.prototype,r=a(arguments,1),l=function(){var n=u(r,a(arguments));return this instanceof l?function(e,t,n){if(!i(h,t)){for(var r=[],o=0;o{var r=n(18285),o=Function.prototype.call;e.exports=r?o.bind(o):function(){return o.apply(o,arguments)}},79417:(e,t,n)=>{var r=n(55746),o=n(90953),s=Function.prototype,i=r&&Object.getOwnPropertyDescriptor,a=o(s,"name"),l=a&&"something"===function(){}.name,c=a&&(!r||r&&i(s,"name").configurable);e.exports={EXISTS:a,PROPER:l,CONFIGURABLE:c}},45526:(e,t,n)=>{var r=n(95329),o=n(24883);e.exports=function(e,t,n){try{return r(o(Object.getOwnPropertyDescriptor(e,t)[n]))}catch(e){}}},97484:(e,t,n)=>{var r=n(82532),o=n(95329);e.exports=function(e){if("Function"===r(e))return o(e)}},95329:(e,t,n)=>{var r=n(18285),o=Function.prototype,s=o.call,i=r&&o.bind.bind(s,s);e.exports=r?i:function(e){return function(){return s.apply(e,arguments)}}},626:(e,t,n)=>{var r=n(54058),o=n(21899),s=n(57475),i=function(e){return s(e)?e:void 0};e.exports=function(e,t){return arguments.length<2?i(r[e])||i(o[e]):r[e]&&r[e][t]||o[e]&&o[e][t]}},22902:(e,t,n)=>{var r=n(9697),o=n(14229),s=n(82119),i=n(12077),a=n(99813)("iterator");e.exports=function(e){if(!s(e))return o(e,a)||o(e,"@@iterator")||i[r(e)]}},53476:(e,t,n)=>{var r=n(78834),o=n(24883),s=n(96059),i=n(69826),a=n(22902),l=TypeError;e.exports=function(e,t){var n=arguments.length<2?a(e):t;if(o(n))return s(r(n,e));throw l(i(e)+" is not iterable")}},33323:(e,t,n)=>{var r=n(95329),o=n(1052),s=n(57475),i=n(82532),a=n(85803),l=r([].push);e.exports=function(e){if(s(e))return e;if(o(e)){for(var t=e.length,n=[],r=0;r{var r=n(24883),o=n(82119);e.exports=function(e,t){var n=e[t];return o(n)?void 0:r(n)}},21899:function(e,t,n){var r=function(e){return e&&e.Math==Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof n.g&&n.g)||function(){return this}()||this||Function("return this")()},90953:(e,t,n)=>{var r=n(95329),o=n(89678),s=r({}.hasOwnProperty);e.exports=Object.hasOwn||function(e,t){return s(o(e),t)}},27748:e=>{e.exports={}},34845:e=>{e.exports=function(e,t){try{1==arguments.length?console.error(e):console.error(e,t)}catch(e){}}},15463:(e,t,n)=>{var r=n(626);e.exports=r("document","documentElement")},2840:(e,t,n)=>{var r=n(55746),o=n(95981),s=n(61333);e.exports=!r&&!o((function(){return 7!=Object.defineProperty(s("div"),"a",{get:function(){return 7}}).a}))},37026:(e,t,n)=>{var r=n(95329),o=n(95981),s=n(82532),i=Object,a=r("".split);e.exports=o((function(){return!i("z").propertyIsEnumerable(0)}))?function(e){return"String"==s(e)?a(e,""):i(e)}:i},81302:(e,t,n)=>{var r=n(95329),o=n(57475),s=n(63030),i=r(Function.toString);o(s.inspectSource)||(s.inspectSource=function(e){return i(e)}),e.exports=s.inspectSource},53794:(e,t,n)=>{var r=n(10941),o=n(32029);e.exports=function(e,t){r(t)&&"cause"in t&&o(e,"cause",t.cause)}},21647:(e,t,n)=>{var r=n(76887),o=n(95329),s=n(27748),i=n(10941),a=n(90953),l=n(65988).f,c=n(10946),u=n(684),p=n(91584),h=n(99418),f=n(45602),d=!1,m=h("meta"),g=0,y=function(e){l(e,m,{value:{objectID:"O"+g++,weakData:{}}})},v=e.exports={enable:function(){v.enable=function(){},d=!0;var e=c.f,t=o([].splice),n={};n[m]=1,e(n).length&&(c.f=function(n){for(var r=e(n),o=0,s=r.length;o{var r,o,s,i=n(47093),a=n(21899),l=n(10941),c=n(32029),u=n(90953),p=n(63030),h=n(44262),f=n(27748),d="Object already initialized",m=a.TypeError,g=a.WeakMap;if(i||p.state){var y=p.state||(p.state=new g);y.get=y.get,y.has=y.has,y.set=y.set,r=function(e,t){if(y.has(e))throw m(d);return t.facade=e,y.set(e,t),t},o=function(e){return y.get(e)||{}},s=function(e){return y.has(e)}}else{var v=h("state");f[v]=!0,r=function(e,t){if(u(e,v))throw m(d);return t.facade=e,c(e,v,t),t},o=function(e){return u(e,v)?e[v]:{}},s=function(e){return u(e,v)}}e.exports={set:r,get:o,has:s,enforce:function(e){return s(e)?o(e):r(e,{})},getterFor:function(e){return function(t){var n;if(!l(t)||(n=o(t)).type!==e)throw m("Incompatible receiver, "+e+" required");return n}}}},6782:(e,t,n)=>{var r=n(99813),o=n(12077),s=r("iterator"),i=Array.prototype;e.exports=function(e){return void 0!==e&&(o.Array===e||i[s]===e)}},1052:(e,t,n)=>{var r=n(82532);e.exports=Array.isArray||function(e){return"Array"==r(e)}},57475:(e,t,n)=>{var r=n(76616),o=r.all;e.exports=r.IS_HTMLDDA?function(e){return"function"==typeof e||e===o}:function(e){return"function"==typeof e}},24284:(e,t,n)=>{var r=n(95329),o=n(95981),s=n(57475),i=n(9697),a=n(626),l=n(81302),c=function(){},u=[],p=a("Reflect","construct"),h=/^\s*(?:class|function)\b/,f=r(h.exec),d=!h.exec(c),m=function(e){if(!s(e))return!1;try{return p(c,u,e),!0}catch(e){return!1}},g=function(e){if(!s(e))return!1;switch(i(e)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return d||!!f(h,l(e))}catch(e){return!0}};g.sham=!0,e.exports=!p||o((function(){var e;return m(m.call)||!m(Object)||!m((function(){e=!0}))||e}))?g:m},37252:(e,t,n)=>{var r=n(95981),o=n(57475),s=/#|\.prototype\./,i=function(e,t){var n=l[a(e)];return n==u||n!=c&&(o(t)?r(t):!!t)},a=i.normalize=function(e){return String(e).replace(s,".").toLowerCase()},l=i.data={},c=i.NATIVE="N",u=i.POLYFILL="P";e.exports=i},54639:(e,t,n)=>{var r=n(10941),o=Math.floor;e.exports=Number.isInteger||function(e){return!r(e)&&isFinite(e)&&o(e)===e}},82119:e=>{e.exports=function(e){return null==e}},10941:(e,t,n)=>{var r=n(57475),o=n(76616),s=o.all;e.exports=o.IS_HTMLDDA?function(e){return"object"==typeof e?null!==e:r(e)||e===s}:function(e){return"object"==typeof e?null!==e:r(e)}},82529:e=>{e.exports=!0},60685:(e,t,n)=>{var r=n(10941),o=n(82532),s=n(99813)("match");e.exports=function(e){var t;return r(e)&&(void 0!==(t=e[s])?!!t:"RegExp"==o(e))}},56664:(e,t,n)=>{var r=n(626),o=n(57475),s=n(7046),i=n(32302),a=Object;e.exports=i?function(e){return"symbol"==typeof e}:function(e){var t=r("Symbol");return o(t)&&s(t.prototype,a(e))}},93091:(e,t,n)=>{var r=n(86843),o=n(78834),s=n(96059),i=n(69826),a=n(6782),l=n(10623),c=n(7046),u=n(53476),p=n(22902),h=n(7609),f=TypeError,d=function(e,t){this.stopped=e,this.result=t},m=d.prototype;e.exports=function(e,t,n){var g,y,v,b,w,E,x,S=n&&n.that,_=!(!n||!n.AS_ENTRIES),j=!(!n||!n.IS_RECORD),O=!(!n||!n.IS_ITERATOR),k=!(!n||!n.INTERRUPTED),A=r(t,S),C=function(e){return g&&h(g,"normal",e),new d(!0,e)},P=function(e){return _?(s(e),k?A(e[0],e[1],C):A(e[0],e[1])):k?A(e,C):A(e)};if(j)g=e.iterator;else if(O)g=e;else{if(!(y=p(e)))throw f(i(e)+" is not iterable");if(a(y)){for(v=0,b=l(e);b>v;v++)if((w=P(e[v]))&&c(m,w))return w;return new d(!1)}g=u(e,y)}for(E=j?e.next:g.next;!(x=o(E,g)).done;){try{w=P(x.value)}catch(e){h(g,"throw",e)}if("object"==typeof w&&w&&c(m,w))return w}return new d(!1)}},7609:(e,t,n)=>{var r=n(78834),o=n(96059),s=n(14229);e.exports=function(e,t,n){var i,a;o(e);try{if(!(i=s(e,"return"))){if("throw"===t)throw n;return n}i=r(i,e)}catch(e){a=!0,i=e}if("throw"===t)throw n;if(a)throw i;return o(i),n}},53847:(e,t,n)=>{"use strict";var r=n(35143).IteratorPrototype,o=n(29290),s=n(31887),i=n(90904),a=n(12077),l=function(){return this};e.exports=function(e,t,n,c){var u=t+" Iterator";return e.prototype=o(r,{next:s(+!c,n)}),i(e,u,!1,!0),a[u]=l,e}},75105:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),s=n(82529),i=n(79417),a=n(57475),l=n(53847),c=n(249),u=n(88929),p=n(90904),h=n(32029),f=n(95929),d=n(99813),m=n(12077),g=n(35143),y=i.PROPER,v=i.CONFIGURABLE,b=g.IteratorPrototype,w=g.BUGGY_SAFARI_ITERATORS,E=d("iterator"),x="keys",S="values",_="entries",j=function(){return this};e.exports=function(e,t,n,i,d,g,O){l(n,t,i);var k,A,C,P=function(e){if(e===d&&M)return M;if(!w&&e in T)return T[e];switch(e){case x:case S:case _:return function(){return new n(this,e)}}return function(){return new n(this)}},N=t+" Iterator",I=!1,T=e.prototype,R=T[E]||T["@@iterator"]||d&&T[d],M=!w&&R||P(d),D="Array"==t&&T.entries||R;if(D&&(k=c(D.call(new e)))!==Object.prototype&&k.next&&(s||c(k)===b||(u?u(k,b):a(k[E])||f(k,E,j)),p(k,N,!0,!0),s&&(m[N]=j)),y&&d==S&&R&&R.name!==S&&(!s&&v?h(T,"name",S):(I=!0,M=function(){return o(R,this)})),d)if(A={values:P(S),keys:g?M:P(x),entries:P(_)},O)for(C in A)(w||I||!(C in T))&&f(T,C,A[C]);else r({target:t,proto:!0,forced:w||I},A);return s&&!O||T[E]===M||f(T,E,M,{name:d}),m[t]=M,A}},35143:(e,t,n)=>{"use strict";var r,o,s,i=n(95981),a=n(57475),l=n(10941),c=n(29290),u=n(249),p=n(95929),h=n(99813),f=n(82529),d=h("iterator"),m=!1;[].keys&&("next"in(s=[].keys())?(o=u(u(s)))!==Object.prototype&&(r=o):m=!0),!l(r)||i((function(){var e={};return r[d].call(e)!==e}))?r={}:f&&(r=c(r)),a(r[d])||p(r,d,(function(){return this})),e.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:m}},12077:e=>{e.exports={}},10623:(e,t,n)=>{var r=n(43057);e.exports=function(e){return r(e.length)}},35331:e=>{var t=Math.ceil,n=Math.floor;e.exports=Math.trunc||function(e){var r=+e;return(r>0?n:t)(r)}},66132:(e,t,n)=>{var r,o,s,i,a,l=n(21899),c=n(86843),u=n(49677).f,p=n(42941).set,h=n(18397),f=n(22749),d=n(4470),m=n(58045),g=n(6049),y=l.MutationObserver||l.WebKitMutationObserver,v=l.document,b=l.process,w=l.Promise,E=u(l,"queueMicrotask"),x=E&&E.value;if(!x){var S=new h,_=function(){var e,t;for(g&&(e=b.domain)&&e.exit();t=S.get();)try{t()}catch(e){throw S.head&&r(),e}e&&e.enter()};f||g||m||!y||!v?!d&&w&&w.resolve?((i=w.resolve(void 0)).constructor=w,a=c(i.then,i),r=function(){a(_)}):g?r=function(){b.nextTick(_)}:(p=c(p,l),r=function(){p(_)}):(o=!0,s=v.createTextNode(""),new y(_).observe(s,{characterData:!0}),r=function(){s.data=o=!o}),x=function(e){S.head||r(),S.add(e)}}e.exports=x},69520:(e,t,n)=>{"use strict";var r=n(24883),o=TypeError,s=function(e){var t,n;this.promise=new e((function(e,r){if(void 0!==t||void 0!==n)throw o("Bad Promise constructor");t=e,n=r})),this.resolve=r(t),this.reject=r(n)};e.exports.f=function(e){return new s(e)}},14649:(e,t,n)=>{var r=n(85803);e.exports=function(e,t){return void 0===e?arguments.length<2?"":t:r(e)}},70344:(e,t,n)=>{var r=n(60685),o=TypeError;e.exports=function(e){if(r(e))throw o("The method doesn't accept regular expressions");return e}},24420:(e,t,n)=>{"use strict";var r=n(55746),o=n(95329),s=n(78834),i=n(95981),a=n(14771),l=n(87857),c=n(36760),u=n(89678),p=n(37026),h=Object.assign,f=Object.defineProperty,d=o([].concat);e.exports=!h||i((function(){if(r&&1!==h({b:1},h(f({},"a",{enumerable:!0,get:function(){f(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var e={},t={},n=Symbol(),o="abcdefghijklmnopqrst";return e[n]=7,o.split("").forEach((function(e){t[e]=e})),7!=h({},e)[n]||a(h({},t)).join("")!=o}))?function(e,t){for(var n=u(e),o=arguments.length,i=1,h=l.f,f=c.f;o>i;)for(var m,g=p(arguments[i++]),y=h?d(a(g),h(g)):a(g),v=y.length,b=0;v>b;)m=y[b++],r&&!s(f,g,m)||(n[m]=g[m]);return n}:h},29290:(e,t,n)=>{var r,o=n(96059),s=n(59938),i=n(56759),a=n(27748),l=n(15463),c=n(61333),u=n(44262),p="prototype",h="script",f=u("IE_PROTO"),d=function(){},m=function(e){return"<"+h+">"+e+""},g=function(e){e.write(m("")),e.close();var t=e.parentWindow.Object;return e=null,t},y=function(){try{r=new ActiveXObject("htmlfile")}catch(e){}var e,t,n;y="undefined"!=typeof document?document.domain&&r?g(r):(t=c("iframe"),n="java"+h+":",t.style.display="none",l.appendChild(t),t.src=String(n),(e=t.contentWindow.document).open(),e.write(m("document.F=Object")),e.close(),e.F):g(r);for(var o=i.length;o--;)delete y[p][i[o]];return y()};a[f]=!0,e.exports=Object.create||function(e,t){var n;return null!==e?(d[p]=o(e),n=new d,d[p]=null,n[f]=e):n=y(),void 0===t?n:s.f(n,t)}},59938:(e,t,n)=>{var r=n(55746),o=n(83937),s=n(65988),i=n(96059),a=n(74529),l=n(14771);t.f=r&&!o?Object.defineProperties:function(e,t){i(e);for(var n,r=a(t),o=l(t),c=o.length,u=0;c>u;)s.f(e,n=o[u++],r[n]);return e}},65988:(e,t,n)=>{var r=n(55746),o=n(2840),s=n(83937),i=n(96059),a=n(83894),l=TypeError,c=Object.defineProperty,u=Object.getOwnPropertyDescriptor,p="enumerable",h="configurable",f="writable";t.f=r?s?function(e,t,n){if(i(e),t=a(t),i(n),"function"==typeof e&&"prototype"===t&&"value"in n&&f in n&&!n[f]){var r=u(e,t);r&&r[f]&&(e[t]=n.value,n={configurable:h in n?n[h]:r[h],enumerable:p in n?n[p]:r[p],writable:!1})}return c(e,t,n)}:c:function(e,t,n){if(i(e),t=a(t),i(n),o)try{return c(e,t,n)}catch(e){}if("get"in n||"set"in n)throw l("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},49677:(e,t,n)=>{var r=n(55746),o=n(78834),s=n(36760),i=n(31887),a=n(74529),l=n(83894),c=n(90953),u=n(2840),p=Object.getOwnPropertyDescriptor;t.f=r?p:function(e,t){if(e=a(e),t=l(t),u)try{return p(e,t)}catch(e){}if(c(e,t))return i(!o(s.f,e,t),e[t])}},684:(e,t,n)=>{var r=n(82532),o=n(74529),s=n(10946).f,i=n(15790),a="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];e.exports.f=function(e){return a&&"Window"==r(e)?function(e){try{return s(e)}catch(e){return i(a)}}(e):s(o(e))}},10946:(e,t,n)=>{var r=n(55629),o=n(56759).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,o)}},87857:(e,t)=>{t.f=Object.getOwnPropertySymbols},249:(e,t,n)=>{var r=n(90953),o=n(57475),s=n(89678),i=n(44262),a=n(64160),l=i("IE_PROTO"),c=Object,u=c.prototype;e.exports=a?c.getPrototypeOf:function(e){var t=s(e);if(r(t,l))return t[l];var n=t.constructor;return o(n)&&t instanceof n?n.prototype:t instanceof c?u:null}},91584:(e,t,n)=>{var r=n(95981),o=n(10941),s=n(82532),i=n(97135),a=Object.isExtensible,l=r((function(){a(1)}));e.exports=l||i?function(e){return!!o(e)&&((!i||"ArrayBuffer"!=s(e))&&(!a||a(e)))}:a},7046:(e,t,n)=>{var r=n(95329);e.exports=r({}.isPrototypeOf)},55629:(e,t,n)=>{var r=n(95329),o=n(90953),s=n(74529),i=n(31692).indexOf,a=n(27748),l=r([].push);e.exports=function(e,t){var n,r=s(e),c=0,u=[];for(n in r)!o(a,n)&&o(r,n)&&l(u,n);for(;t.length>c;)o(r,n=t[c++])&&(~i(u,n)||l(u,n));return u}},14771:(e,t,n)=>{var r=n(55629),o=n(56759);e.exports=Object.keys||function(e){return r(e,o)}},36760:(e,t)=>{"use strict";var n={}.propertyIsEnumerable,r=Object.getOwnPropertyDescriptor,o=r&&!n.call({1:2},1);t.f=o?function(e){var t=r(this,e);return!!t&&t.enumerable}:n},88929:(e,t,n)=>{var r=n(45526),o=n(96059),s=n(11851);e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,n={};try{(e=r(Object.prototype,"__proto__","set"))(n,[]),t=n instanceof Array}catch(e){}return function(n,r){return o(n),s(r),t?e(n,r):n.__proto__=r,n}}():void 0)},88810:(e,t,n)=>{var r=n(55746),o=n(95981),s=n(95329),i=n(249),a=n(14771),l=n(74529),c=s(n(36760).f),u=s([].push),p=r&&o((function(){var e=Object.create(null);return e[2]=2,!c(e,2)})),h=function(e){return function(t){for(var n,o=l(t),s=a(o),h=p&&null===i(o),f=s.length,d=0,m=[];f>d;)n=s[d++],r&&!(h?n in o:c(o,n))||u(m,e?[n,o[n]]:o[n]);return m}};e.exports={entries:h(!0),values:h(!1)}},95623:(e,t,n)=>{"use strict";var r=n(22885),o=n(9697);e.exports=r?{}.toString:function(){return"[object "+o(this)+"]"}},39811:(e,t,n)=>{var r=n(78834),o=n(57475),s=n(10941),i=TypeError;e.exports=function(e,t){var n,a;if("string"===t&&o(n=e.toString)&&!s(a=r(n,e)))return a;if(o(n=e.valueOf)&&!s(a=r(n,e)))return a;if("string"!==t&&o(n=e.toString)&&!s(a=r(n,e)))return a;throw i("Can't convert object to primitive value")}},31136:(e,t,n)=>{var r=n(626),o=n(95329),s=n(10946),i=n(87857),a=n(96059),l=o([].concat);e.exports=r("Reflect","ownKeys")||function(e){var t=s.f(a(e)),n=i.f;return n?l(t,n(e)):t}},54058:e=>{e.exports={}},40002:e=>{e.exports=function(e){try{return{error:!1,value:e()}}catch(e){return{error:!0,value:e}}}},67742:(e,t,n)=>{var r=n(21899),o=n(6991),s=n(57475),i=n(37252),a=n(81302),l=n(99813),c=n(23321),u=n(48501),p=n(82529),h=n(53385),f=o&&o.prototype,d=l("species"),m=!1,g=s(r.PromiseRejectionEvent),y=i("Promise",(function(){var e=a(o),t=e!==String(o);if(!t&&66===h)return!0;if(p&&(!f.catch||!f.finally))return!0;if(!h||h<51||!/native code/.test(e)){var n=new o((function(e){e(1)})),r=function(e){e((function(){}),(function(){}))};if((n.constructor={})[d]=r,!(m=n.then((function(){}))instanceof r))return!0}return!t&&(c||u)&&!g}));e.exports={CONSTRUCTOR:y,REJECTION_EVENT:g,SUBCLASSING:m}},6991:(e,t,n)=>{var r=n(21899);e.exports=r.Promise},56584:(e,t,n)=>{var r=n(96059),o=n(10941),s=n(69520);e.exports=function(e,t){if(r(e),o(t)&&t.constructor===e)return t;var n=s.f(e);return(0,n.resolve)(t),n.promise}},31542:(e,t,n)=>{var r=n(6991),o=n(21385),s=n(67742).CONSTRUCTOR;e.exports=s||!o((function(e){r.all(e).then(void 0,(function(){}))}))},18397:e=>{var t=function(){this.head=null,this.tail=null};t.prototype={add:function(e){var t={item:e,next:null},n=this.tail;n?n.next=t:this.head=t,this.tail=t},get:function(){var e=this.head;if(e)return null===(this.head=e.next)&&(this.tail=null),e.item}},e.exports=t},48219:(e,t,n)=>{var r=n(82119),o=TypeError;e.exports=function(e){if(r(e))throw o("Can't call method on "+e);return e}},37620:(e,t,n)=>{"use strict";var r,o=n(21899),s=n(79730),i=n(57475),a=n(56491),l=n(2861),c=n(93765),u=n(18348),p=o.Function,h=/MSIE .\./.test(l)||a&&((r=o.Bun.version.split(".")).length<3||0==r[0]&&(r[1]<3||3==r[1]&&0==r[2]));e.exports=function(e,t){var n=t?2:1;return h?function(r,o){var a=u(arguments.length,1)>n,l=i(r)?r:p(r),h=a?c(arguments,n):[],f=a?function(){s(l,this,h)}:l;return t?e(f,o):e(f)}:e}},94431:(e,t,n)=>{"use strict";var r=n(626),o=n(29202),s=n(99813),i=n(55746),a=s("species");e.exports=function(e){var t=r(e);i&&t&&!t[a]&&o(t,a,{configurable:!0,get:function(){return this}})}},90904:(e,t,n)=>{var r=n(22885),o=n(65988).f,s=n(32029),i=n(90953),a=n(95623),l=n(99813)("toStringTag");e.exports=function(e,t,n,c){if(e){var u=n?e:e.prototype;i(u,l)||o(u,l,{configurable:!0,value:t}),c&&!r&&s(u,"toString",a)}}},44262:(e,t,n)=>{var r=n(68726),o=n(99418),s=r("keys");e.exports=function(e){return s[e]||(s[e]=o(e))}},63030:(e,t,n)=>{var r=n(21899),o=n(75609),s="__core-js_shared__",i=r[s]||o(s,{});e.exports=i},68726:(e,t,n)=>{var r=n(82529),o=n(63030);(e.exports=function(e,t){return o[e]||(o[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.31.0",mode:r?"pure":"global",copyright:"ยฉ 2014-2023 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.31.0/LICENSE",source:"https://github.com/zloirock/core-js"})},70487:(e,t,n)=>{var r=n(96059),o=n(174),s=n(82119),i=n(99813)("species");e.exports=function(e,t){var n,a=r(e).constructor;return void 0===a||s(n=r(a)[i])?t:o(n)}},64620:(e,t,n)=>{var r=n(95329),o=n(62435),s=n(85803),i=n(48219),a=r("".charAt),l=r("".charCodeAt),c=r("".slice),u=function(e){return function(t,n){var r,u,p=s(i(t)),h=o(n),f=p.length;return h<0||h>=f?e?"":void 0:(r=l(p,h))<55296||r>56319||h+1===f||(u=l(p,h+1))<56320||u>57343?e?a(p,h):r:e?c(p,h,h+2):u-56320+(r-55296<<10)+65536}};e.exports={codeAt:u(!1),charAt:u(!0)}},73291:(e,t,n)=>{var r=n(95329),o=2147483647,s=/[^\0-\u007E]/,i=/[.\u3002\uFF0E\uFF61]/g,a="Overflow: input needs wider integers to process",l=RangeError,c=r(i.exec),u=Math.floor,p=String.fromCharCode,h=r("".charCodeAt),f=r([].join),d=r([].push),m=r("".replace),g=r("".split),y=r("".toLowerCase),v=function(e){return e+22+75*(e<26)},b=function(e,t,n){var r=0;for(e=n?u(e/700):e>>1,e+=u(e/t);e>455;)e=u(e/35),r+=36;return u(r+36*e/(e+38))},w=function(e){var t=[];e=function(e){for(var t=[],n=0,r=e.length;n=55296&&o<=56319&&n=i&&ru((o-c)/E))throw l(a);for(c+=(w-i)*E,i=w,n=0;no)throw l(a);if(r==i){for(var x=c,S=36;;){var _=S<=m?1:S>=m+26?26:S-m;if(x<_)break;var j=x-_,O=36-_;d(t,p(v(_+j%O))),x=u(j/O),S+=36}d(t,p(v(x))),m=b(c,E,y==g),c=0,y++}}c++,i++}return f(t,"")};e.exports=function(e){var t,n,r=[],o=g(m(y(e),i,"."),".");for(t=0;t{"use strict";var r=n(62435),o=n(85803),s=n(48219),i=RangeError;e.exports=function(e){var t=o(s(this)),n="",a=r(e);if(a<0||a==1/0)throw i("Wrong number of repetitions");for(;a>0;(a>>>=1)&&(t+=t))1&a&&(n+=t);return n}},93093:(e,t,n)=>{var r=n(79417).PROPER,o=n(95981),s=n(73483);e.exports=function(e){return o((function(){return!!s[e]()||"โ€‹ย…แ Ž"!=="โ€‹ย…แ Ž"[e]()||r&&s[e].name!==e}))}},74853:(e,t,n)=>{var r=n(95329),o=n(48219),s=n(85803),i=n(73483),a=r("".replace),l=RegExp("^["+i+"]+"),c=RegExp("(^|[^"+i+"])["+i+"]+$"),u=function(e){return function(t){var n=s(o(t));return 1&e&&(n=a(n,l,"")),2&e&&(n=a(n,c,"$1")),n}};e.exports={start:u(1),end:u(2),trim:u(3)}},63405:(e,t,n)=>{var r=n(53385),o=n(95981),s=n(21899).String;e.exports=!!Object.getOwnPropertySymbols&&!o((function(){var e=Symbol();return!s(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&r&&r<41}))},29630:(e,t,n)=>{var r=n(78834),o=n(626),s=n(99813),i=n(95929);e.exports=function(){var e=o("Symbol"),t=e&&e.prototype,n=t&&t.valueOf,a=s("toPrimitive");t&&!t[a]&&i(t,a,(function(e){return r(n,this)}),{arity:1})}},32087:(e,t,n)=>{var r=n(626),o=n(95329),s=r("Symbol"),i=s.keyFor,a=o(s.prototype.valueOf);e.exports=s.isRegisteredSymbol||function(e){try{return void 0!==i(a(e))}catch(e){return!1}}},96559:(e,t,n)=>{for(var r=n(68726),o=n(626),s=n(95329),i=n(56664),a=n(99813),l=o("Symbol"),c=l.isWellKnownSymbol,u=o("Object","getOwnPropertyNames"),p=s(l.prototype.valueOf),h=r("wks"),f=0,d=u(l),m=d.length;f{var r=n(63405);e.exports=r&&!!Symbol.for&&!!Symbol.keyFor},42941:(e,t,n)=>{var r,o,s,i,a=n(21899),l=n(79730),c=n(86843),u=n(57475),p=n(90953),h=n(95981),f=n(15463),d=n(93765),m=n(61333),g=n(18348),y=n(22749),v=n(6049),b=a.setImmediate,w=a.clearImmediate,E=a.process,x=a.Dispatch,S=a.Function,_=a.MessageChannel,j=a.String,O=0,k={},A="onreadystatechange";h((function(){r=a.location}));var C=function(e){if(p(k,e)){var t=k[e];delete k[e],t()}},P=function(e){return function(){C(e)}},N=function(e){C(e.data)},I=function(e){a.postMessage(j(e),r.protocol+"//"+r.host)};b&&w||(b=function(e){g(arguments.length,1);var t=u(e)?e:S(e),n=d(arguments,1);return k[++O]=function(){l(t,void 0,n)},o(O),O},w=function(e){delete k[e]},v?o=function(e){E.nextTick(P(e))}:x&&x.now?o=function(e){x.now(P(e))}:_&&!y?(i=(s=new _).port2,s.port1.onmessage=N,o=c(i.postMessage,i)):a.addEventListener&&u(a.postMessage)&&!a.importScripts&&r&&"file:"!==r.protocol&&!h(I)?(o=I,a.addEventListener("message",N,!1)):o=A in m("script")?function(e){f.appendChild(m("script"))[A]=function(){f.removeChild(this),C(e)}}:function(e){setTimeout(P(e),0)}),e.exports={set:b,clear:w}},59413:(e,t,n)=>{var r=n(62435),o=Math.max,s=Math.min;e.exports=function(e,t){var n=r(e);return n<0?o(n+t,0):s(n,t)}},74529:(e,t,n)=>{var r=n(37026),o=n(48219);e.exports=function(e){return r(o(e))}},62435:(e,t,n)=>{var r=n(35331);e.exports=function(e){var t=+e;return t!=t||0===t?0:r(t)}},43057:(e,t,n)=>{var r=n(62435),o=Math.min;e.exports=function(e){return e>0?o(r(e),9007199254740991):0}},89678:(e,t,n)=>{var r=n(48219),o=Object;e.exports=function(e){return o(r(e))}},46935:(e,t,n)=>{var r=n(78834),o=n(10941),s=n(56664),i=n(14229),a=n(39811),l=n(99813),c=TypeError,u=l("toPrimitive");e.exports=function(e,t){if(!o(e)||s(e))return e;var n,l=i(e,u);if(l){if(void 0===t&&(t="default"),n=r(l,e,t),!o(n)||s(n))return n;throw c("Can't convert object to primitive value")}return void 0===t&&(t="number"),a(e,t)}},83894:(e,t,n)=>{var r=n(46935),o=n(56664);e.exports=function(e){var t=r(e,"string");return o(t)?t:t+""}},22885:(e,t,n)=>{var r={};r[n(99813)("toStringTag")]="z",e.exports="[object z]"===String(r)},85803:(e,t,n)=>{var r=n(9697),o=String;e.exports=function(e){if("Symbol"===r(e))throw TypeError("Cannot convert a Symbol value to a string");return o(e)}},69826:e=>{var t=String;e.exports=function(e){try{return t(e)}catch(e){return"Object"}}},99418:(e,t,n)=>{var r=n(95329),o=0,s=Math.random(),i=r(1..toString);e.exports=function(e){return"Symbol("+(void 0===e?"":e)+")_"+i(++o+s,36)}},14766:(e,t,n)=>{var r=n(95981),o=n(99813),s=n(55746),i=n(82529),a=o("iterator");e.exports=!r((function(){var e=new URL("b?a=1&b=2&c=3","http://a"),t=e.searchParams,n=new URLSearchParams("a=1&a=2"),r="";return e.pathname="c%20d",t.forEach((function(e,n){t.delete("b"),r+=n+e})),n.delete("a",2),i&&(!e.toJSON||!n.has("a",1)||n.has("a",2))||!t.size&&(i||!s)||!t.sort||"http://a/c%20d?a=1&c=3"!==e.href||"3"!==t.get("c")||"a=1"!==String(new URLSearchParams("?a=1"))||!t[a]||"a"!==new URL("https://a@b").username||"b"!==new URLSearchParams(new URLSearchParams("a=b")).get("a")||"xn--e1aybc"!==new URL("http://ั‚ะตัั‚").host||"#%D0%B1"!==new URL("http://a#ะฑ").hash||"a1c3"!==r||"x"!==new URL("http://x",void 0).host}))},32302:(e,t,n)=>{var r=n(63405);e.exports=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},83937:(e,t,n)=>{var r=n(55746),o=n(95981);e.exports=r&&o((function(){return 42!=Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))},18348:e=>{var t=TypeError;e.exports=function(e,n){if(e{var r=n(21899),o=n(57475),s=r.WeakMap;e.exports=o(s)&&/native code/.test(String(s))},73464:(e,t,n)=>{var r=n(54058),o=n(90953),s=n(11477),i=n(65988).f;e.exports=function(e){var t=r.Symbol||(r.Symbol={});o(t,e)||i(t,e,{value:s.f(e)})}},11477:(e,t,n)=>{var r=n(99813);t.f=r},99813:(e,t,n)=>{var r=n(21899),o=n(68726),s=n(90953),i=n(99418),a=n(63405),l=n(32302),c=r.Symbol,u=o("wks"),p=l?c.for||c:c&&c.withoutSetter||i;e.exports=function(e){return s(u,e)||(u[e]=a&&s(c,e)?c[e]:p("Symbol."+e)),u[e]}},73483:e=>{e.exports="\t\n\v\f\r ย แš€โ€€โ€โ€‚โ€ƒโ€„โ€…โ€†โ€‡โ€ˆโ€‰โ€Šโ€ฏโŸใ€€\u2028\u2029\ufeff"},49812:(e,t,n)=>{"use strict";var r=n(76887),o=n(7046),s=n(249),i=n(88929),a=n(23489),l=n(29290),c=n(32029),u=n(31887),p=n(53794),h=n(79585),f=n(93091),d=n(14649),m=n(99813)("toStringTag"),g=Error,y=[].push,v=function(e,t){var n,r=o(b,this);i?n=i(g(),r?s(this):b):(n=r?this:l(b),c(n,m,"Error")),void 0!==t&&c(n,"message",d(t)),h(n,v,n.stack,1),arguments.length>2&&p(n,arguments[2]);var a=[];return f(e,y,{that:a}),c(n,"errors",a),n};i?i(v,g):a(v,g,{name:!0});var b=v.prototype=l(g.prototype,{constructor:u(1,v),message:u(1,""),name:u(1,"AggregateError")});r({global:!0,constructor:!0,arity:2},{AggregateError:v})},47627:(e,t,n)=>{n(49812)},85906:(e,t,n)=>{"use strict";var r=n(76887),o=n(95981),s=n(1052),i=n(10941),a=n(89678),l=n(10623),c=n(66796),u=n(55449),p=n(64692),h=n(50568),f=n(99813),d=n(53385),m=f("isConcatSpreadable"),g=d>=51||!o((function(){var e=[];return e[m]=!1,e.concat()[0]!==e})),y=function(e){if(!i(e))return!1;var t=e[m];return void 0!==t?!!t:s(e)};r({target:"Array",proto:!0,arity:1,forced:!g||!h("concat")},{concat:function(e){var t,n,r,o,s,i=a(this),h=p(i,0),f=0;for(t=-1,r=arguments.length;t{"use strict";var r=n(76887),o=n(3610).every;r({target:"Array",proto:!0,forced:!n(34194)("every")},{every:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},80290:(e,t,n)=>{var r=n(76887),o=n(91860),s=n(18479);r({target:"Array",proto:!0},{fill:o}),s("fill")},21501:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).filter;r({target:"Array",proto:!0,forced:!n(50568)("filter")},{filter:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},44929:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).findIndex,s=n(18479),i="findIndex",a=!0;i in[]&&Array(1)[i]((function(){a=!1})),r({target:"Array",proto:!0,forced:a},{findIndex:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),s(i)},80833:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).find,s=n(18479),i="find",a=!0;i in[]&&Array(1)[i]((function(){a=!1})),r({target:"Array",proto:!0,forced:a},{find:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),s(i)},2437:(e,t,n)=>{"use strict";var r=n(76887),o=n(56837);r({target:"Array",proto:!0,forced:[].forEach!=o},{forEach:o})},53242:(e,t,n)=>{var r=n(76887),o=n(11354);r({target:"Array",stat:!0,forced:!n(21385)((function(e){Array.from(e)}))},{from:o})},97690:(e,t,n)=>{"use strict";var r=n(76887),o=n(31692).includes,s=n(95981),i=n(18479);r({target:"Array",proto:!0,forced:s((function(){return!Array(1).includes()}))},{includes:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),i("includes")},99076:(e,t,n)=>{"use strict";var r=n(76887),o=n(97484),s=n(31692).indexOf,i=n(34194),a=o([].indexOf),l=!!a&&1/a([1],1,-0)<0;r({target:"Array",proto:!0,forced:l||!i("indexOf")},{indexOf:function(e){var t=arguments.length>1?arguments[1]:void 0;return l?a(this,e,t)||0:s(this,e,t)}})},92737:(e,t,n)=>{n(76887)({target:"Array",stat:!0},{isArray:n(1052)})},66274:(e,t,n)=>{"use strict";var r=n(74529),o=n(18479),s=n(12077),i=n(45402),a=n(65988).f,l=n(75105),c=n(23538),u=n(82529),p=n(55746),h="Array Iterator",f=i.set,d=i.getterFor(h);e.exports=l(Array,"Array",(function(e,t){f(this,{type:h,target:r(e),index:0,kind:t})}),(function(){var e=d(this),t=e.target,n=e.kind,r=e.index++;return!t||r>=t.length?(e.target=void 0,c(void 0,!0)):c("keys"==n?r:"values"==n?t[r]:[r,t[r]],!1)}),"values");var m=s.Arguments=s.Array;if(o("keys"),o("values"),o("entries"),!u&&p&&"values"!==m.name)try{a(m,"name",{value:"values"})}catch(e){}},75915:(e,t,n)=>{var r=n(76887),o=n(67145);r({target:"Array",proto:!0,forced:o!==[].lastIndexOf},{lastIndexOf:o})},68787:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).map;r({target:"Array",proto:!0,forced:!n(50568)("map")},{map:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},48528:(e,t,n)=>{"use strict";var r=n(76887),o=n(89678),s=n(10623),i=n(89779),a=n(66796);r({target:"Array",proto:!0,arity:1,forced:n(95981)((function(){return 4294967297!==[].push.call({length:4294967296},1)}))||!function(){try{Object.defineProperty([],"length",{writable:!1}).push()}catch(e){return e instanceof TypeError}}()},{push:function(e){var t=o(this),n=s(t),r=arguments.length;a(n+r);for(var l=0;l{"use strict";var r=n(76887),o=n(46499).left,s=n(34194),i=n(53385);r({target:"Array",proto:!0,forced:!n(6049)&&i>79&&i<83||!s("reduce")},{reduce:function(e){var t=arguments.length;return o(this,e,t,t>1?arguments[1]:void 0)}})},60186:(e,t,n)=>{"use strict";var r=n(76887),o=n(1052),s=n(24284),i=n(10941),a=n(59413),l=n(10623),c=n(74529),u=n(55449),p=n(99813),h=n(50568),f=n(93765),d=h("slice"),m=p("species"),g=Array,y=Math.max;r({target:"Array",proto:!0,forced:!d},{slice:function(e,t){var n,r,p,h=c(this),d=l(h),v=a(e,d),b=a(void 0===t?d:t,d);if(o(h)&&(n=h.constructor,(s(n)&&(n===g||o(n.prototype))||i(n)&&null===(n=n[m]))&&(n=void 0),n===g||void 0===n))return f(h,v,b);for(r=new(void 0===n?g:n)(y(b-v,0)),p=0;v{"use strict";var r=n(76887),o=n(3610).some;r({target:"Array",proto:!0,forced:!n(34194)("some")},{some:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},4115:(e,t,n)=>{"use strict";var r=n(76887),o=n(95329),s=n(24883),i=n(89678),a=n(10623),l=n(15863),c=n(85803),u=n(95981),p=n(61388),h=n(34194),f=n(34342),d=n(81046),m=n(53385),g=n(18938),y=[],v=o(y.sort),b=o(y.push),w=u((function(){y.sort(void 0)})),E=u((function(){y.sort(null)})),x=h("sort"),S=!u((function(){if(m)return m<70;if(!(f&&f>3)){if(d)return!0;if(g)return g<603;var e,t,n,r,o="";for(e=65;e<76;e++){switch(t=String.fromCharCode(e),e){case 66:case 69:case 70:case 72:n=3;break;case 68:case 71:n=4;break;default:n=2}for(r=0;r<47;r++)y.push({k:t+r,v:n})}for(y.sort((function(e,t){return t.v-e.v})),r=0;rc(n)?1:-1}}(e)),n=a(o),r=0;r{"use strict";var r=n(76887),o=n(89678),s=n(59413),i=n(62435),a=n(10623),l=n(89779),c=n(66796),u=n(64692),p=n(55449),h=n(15863),f=n(50568)("splice"),d=Math.max,m=Math.min;r({target:"Array",proto:!0,forced:!f},{splice:function(e,t){var n,r,f,g,y,v,b=o(this),w=a(b),E=s(e,w),x=arguments.length;for(0===x?n=r=0:1===x?(n=0,r=w-E):(n=x-2,r=m(d(i(t),0),w-E)),c(w+n-r),f=u(b,r),g=0;gw-r+n;g--)h(b,g-1)}else if(n>r)for(g=w-r;g>E;g--)v=g+n-1,(y=g+r-1)in b?b[v]=b[y]:h(b,v);for(g=0;g{var r=n(76887),o=n(95329),s=Date,i=o(s.prototype.getTime);r({target:"Date",stat:!0},{now:function(){return i(new s)}})},18084:()=>{},73381:(e,t,n)=>{var r=n(76887),o=n(98308);r({target:"Function",proto:!0,forced:Function.bind!==o},{bind:o})},32619:(e,t,n)=>{var r=n(76887),o=n(626),s=n(79730),i=n(78834),a=n(95329),l=n(95981),c=n(57475),u=n(56664),p=n(93765),h=n(33323),f=n(63405),d=String,m=o("JSON","stringify"),g=a(/./.exec),y=a("".charAt),v=a("".charCodeAt),b=a("".replace),w=a(1..toString),E=/[\uD800-\uDFFF]/g,x=/^[\uD800-\uDBFF]$/,S=/^[\uDC00-\uDFFF]$/,_=!f||l((function(){var e=o("Symbol")();return"[null]"!=m([e])||"{}"!=m({a:e})||"{}"!=m(Object(e))})),j=l((function(){return'"\\udf06\\ud834"'!==m("\udf06\ud834")||'"\\udead"'!==m("\udead")})),O=function(e,t){var n=p(arguments),r=h(t);if(c(r)||void 0!==e&&!u(e))return n[1]=function(e,t){if(c(r)&&(t=i(r,this,d(e),t)),!u(t))return t},s(m,null,n)},k=function(e,t,n){var r=y(n,t-1),o=y(n,t+1);return g(x,e)&&!g(S,o)||g(S,e)&&!g(x,r)?"\\u"+w(v(e,0),16):e};m&&r({target:"JSON",stat:!0,arity:3,forced:_||j},{stringify:function(e,t,n){var r=p(arguments),o=s(_?O:m,null,r);return j&&"string"==typeof o?b(o,E,k):o}})},69120:(e,t,n)=>{var r=n(21899);n(90904)(r.JSON,"JSON",!0)},23112:(e,t,n)=>{"use strict";n(24683)("Map",(function(e){return function(){return e(this,arguments.length?arguments[0]:void 0)}}),n(85616))},37501:(e,t,n)=>{n(23112)},79413:()=>{},54973:(e,t,n)=>{n(76887)({target:"Number",stat:!0,nonConfigurable:!0,nonWritable:!0},{EPSILON:Math.pow(2,-52)})},30800:(e,t,n)=>{n(76887)({target:"Number",stat:!0},{isInteger:n(54639)})},49221:(e,t,n)=>{var r=n(76887),o=n(24420);r({target:"Object",stat:!0,arity:2,forced:Object.assign!==o},{assign:o})},74979:(e,t,n)=>{var r=n(76887),o=n(55746),s=n(59938).f;r({target:"Object",stat:!0,forced:Object.defineProperties!==s,sham:!o},{defineProperties:s})},86450:(e,t,n)=>{var r=n(76887),o=n(55746),s=n(65988).f;r({target:"Object",stat:!0,forced:Object.defineProperty!==s,sham:!o},{defineProperty:s})},94366:(e,t,n)=>{var r=n(76887),o=n(88810).entries;r({target:"Object",stat:!0},{entries:function(e){return o(e)}})},28387:(e,t,n)=>{var r=n(76887),o=n(93091),s=n(55449);r({target:"Object",stat:!0},{fromEntries:function(e){var t={};return o(e,(function(e,n){s(t,e,n)}),{AS_ENTRIES:!0}),t}})},46924:(e,t,n)=>{var r=n(76887),o=n(95981),s=n(74529),i=n(49677).f,a=n(55746);r({target:"Object",stat:!0,forced:!a||o((function(){i(1)})),sham:!a},{getOwnPropertyDescriptor:function(e,t){return i(s(e),t)}})},88482:(e,t,n)=>{var r=n(76887),o=n(55746),s=n(31136),i=n(74529),a=n(49677),l=n(55449);r({target:"Object",stat:!0,sham:!o},{getOwnPropertyDescriptors:function(e){for(var t,n,r=i(e),o=a.f,c=s(r),u={},p=0;c.length>p;)void 0!==(n=o(r,t=c[p++]))&&l(u,t,n);return u}})},37144:(e,t,n)=>{var r=n(76887),o=n(63405),s=n(95981),i=n(87857),a=n(89678);r({target:"Object",stat:!0,forced:!o||s((function(){i.f(1)}))},{getOwnPropertySymbols:function(e){var t=i.f;return t?t(a(e)):[]}})},21724:(e,t,n)=>{var r=n(76887),o=n(89678),s=n(14771);r({target:"Object",stat:!0,forced:n(95981)((function(){s(1)}))},{keys:function(e){return s(o(e))}})},55967:()=>{},26614:(e,t,n)=>{var r=n(76887),o=n(88810).values;r({target:"Object",stat:!0},{values:function(e){return o(e)}})},4560:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),s=n(24883),i=n(69520),a=n(40002),l=n(93091);r({target:"Promise",stat:!0,forced:n(31542)},{allSettled:function(e){var t=this,n=i.f(t),r=n.resolve,c=n.reject,u=a((function(){var n=s(t.resolve),i=[],a=0,c=1;l(e,(function(e){var s=a++,l=!1;c++,o(n,t,e).then((function(e){l||(l=!0,i[s]={status:"fulfilled",value:e},--c||r(i))}),(function(e){l||(l=!0,i[s]={status:"rejected",reason:e},--c||r(i))}))})),--c||r(i)}));return u.error&&c(u.value),n.promise}})},16890:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),s=n(24883),i=n(69520),a=n(40002),l=n(93091);r({target:"Promise",stat:!0,forced:n(31542)},{all:function(e){var t=this,n=i.f(t),r=n.resolve,c=n.reject,u=a((function(){var n=s(t.resolve),i=[],a=0,u=1;l(e,(function(e){var s=a++,l=!1;u++,o(n,t,e).then((function(e){l||(l=!0,i[s]=e,--u||r(i))}),c)})),--u||r(i)}));return u.error&&c(u.value),n.promise}})},91302:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),s=n(24883),i=n(626),a=n(69520),l=n(40002),c=n(93091),u=n(31542),p="No one promise resolved";r({target:"Promise",stat:!0,forced:u},{any:function(e){var t=this,n=i("AggregateError"),r=a.f(t),u=r.resolve,h=r.reject,f=l((function(){var r=s(t.resolve),i=[],a=0,l=1,f=!1;c(e,(function(e){var s=a++,c=!1;l++,o(r,t,e).then((function(e){c||f||(f=!0,u(e))}),(function(e){c||f||(c=!0,i[s]=e,--l||h(new n(i,p)))}))})),--l||h(new n(i,p))}));return f.error&&h(f.value),r.promise}})},83376:(e,t,n)=>{"use strict";var r=n(76887),o=n(82529),s=n(67742).CONSTRUCTOR,i=n(6991),a=n(626),l=n(57475),c=n(95929),u=i&&i.prototype;if(r({target:"Promise",proto:!0,forced:s,real:!0},{catch:function(e){return this.then(void 0,e)}}),!o&&l(i)){var p=a("Promise").prototype.catch;u.catch!==p&&c(u,"catch",p,{unsafe:!0})}},26934:(e,t,n)=>{"use strict";var r,o,s,i=n(76887),a=n(82529),l=n(6049),c=n(21899),u=n(78834),p=n(95929),h=n(88929),f=n(90904),d=n(94431),m=n(24883),g=n(57475),y=n(10941),v=n(5743),b=n(70487),w=n(42941).set,E=n(66132),x=n(34845),S=n(40002),_=n(18397),j=n(45402),O=n(6991),k=n(67742),A=n(69520),C="Promise",P=k.CONSTRUCTOR,N=k.REJECTION_EVENT,I=k.SUBCLASSING,T=j.getterFor(C),R=j.set,M=O&&O.prototype,D=O,F=M,L=c.TypeError,B=c.document,$=c.process,q=A.f,U=q,z=!!(B&&B.createEvent&&c.dispatchEvent),V="unhandledrejection",W=function(e){var t;return!(!y(e)||!g(t=e.then))&&t},J=function(e,t){var n,r,o,s=t.value,i=1==t.state,a=i?e.ok:e.fail,l=e.resolve,c=e.reject,p=e.domain;try{a?(i||(2===t.rejection&&Y(t),t.rejection=1),!0===a?n=s:(p&&p.enter(),n=a(s),p&&(p.exit(),o=!0)),n===e.promise?c(L("Promise-chain cycle")):(r=W(n))?u(r,n,l,c):l(n)):c(s)}catch(e){p&&!o&&p.exit(),c(e)}},K=function(e,t){e.notified||(e.notified=!0,E((function(){for(var n,r=e.reactions;n=r.get();)J(n,e);e.notified=!1,t&&!e.rejection&&G(e)})))},H=function(e,t,n){var r,o;z?((r=B.createEvent("Event")).promise=t,r.reason=n,r.initEvent(e,!1,!0),c.dispatchEvent(r)):r={promise:t,reason:n},!N&&(o=c["on"+e])?o(r):e===V&&x("Unhandled promise rejection",n)},G=function(e){u(w,c,(function(){var t,n=e.facade,r=e.value;if(Z(e)&&(t=S((function(){l?$.emit("unhandledRejection",r,n):H(V,n,r)})),e.rejection=l||Z(e)?2:1,t.error))throw t.value}))},Z=function(e){return 1!==e.rejection&&!e.parent},Y=function(e){u(w,c,(function(){var t=e.facade;l?$.emit("rejectionHandled",t):H("rejectionhandled",t,e.value)}))},X=function(e,t,n){return function(r){e(t,r,n)}},Q=function(e,t,n){e.done||(e.done=!0,n&&(e=n),e.value=t,e.state=2,K(e,!0))},ee=function(e,t,n){if(!e.done){e.done=!0,n&&(e=n);try{if(e.facade===t)throw L("Promise can't be resolved itself");var r=W(t);r?E((function(){var n={done:!1};try{u(r,t,X(ee,n,e),X(Q,n,e))}catch(t){Q(n,t,e)}})):(e.value=t,e.state=1,K(e,!1))}catch(t){Q({done:!1},t,e)}}};if(P&&(F=(D=function(e){v(this,F),m(e),u(r,this);var t=T(this);try{e(X(ee,t),X(Q,t))}catch(e){Q(t,e)}}).prototype,(r=function(e){R(this,{type:C,done:!1,notified:!1,parent:!1,reactions:new _,rejection:!1,state:0,value:void 0})}).prototype=p(F,"then",(function(e,t){var n=T(this),r=q(b(this,D));return n.parent=!0,r.ok=!g(e)||e,r.fail=g(t)&&t,r.domain=l?$.domain:void 0,0==n.state?n.reactions.add(r):E((function(){J(r,n)})),r.promise})),o=function(){var e=new r,t=T(e);this.promise=e,this.resolve=X(ee,t),this.reject=X(Q,t)},A.f=q=function(e){return e===D||undefined===e?new o(e):U(e)},!a&&g(O)&&M!==Object.prototype)){s=M.then,I||p(M,"then",(function(e,t){var n=this;return new D((function(e,t){u(s,n,e,t)})).then(e,t)}),{unsafe:!0});try{delete M.constructor}catch(e){}h&&h(M,F)}i({global:!0,constructor:!0,wrap:!0,forced:P},{Promise:D}),f(D,C,!1,!0),d(C)},44349:(e,t,n)=>{"use strict";var r=n(76887),o=n(82529),s=n(6991),i=n(95981),a=n(626),l=n(57475),c=n(70487),u=n(56584),p=n(95929),h=s&&s.prototype;if(r({target:"Promise",proto:!0,real:!0,forced:!!s&&i((function(){h.finally.call({then:function(){}},(function(){}))}))},{finally:function(e){var t=c(this,a("Promise")),n=l(e);return this.then(n?function(n){return u(t,e()).then((function(){return n}))}:e,n?function(n){return u(t,e()).then((function(){throw n}))}:e)}}),!o&&l(s)){var f=a("Promise").prototype.finally;h.finally!==f&&p(h,"finally",f,{unsafe:!0})}},98881:(e,t,n)=>{n(26934),n(16890),n(83376),n(55921),n(64069),n(14482)},55921:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),s=n(24883),i=n(69520),a=n(40002),l=n(93091);r({target:"Promise",stat:!0,forced:n(31542)},{race:function(e){var t=this,n=i.f(t),r=n.reject,c=a((function(){var i=s(t.resolve);l(e,(function(e){o(i,t,e).then(n.resolve,r)}))}));return c.error&&r(c.value),n.promise}})},64069:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),s=n(69520);r({target:"Promise",stat:!0,forced:n(67742).CONSTRUCTOR},{reject:function(e){var t=s.f(this);return o(t.reject,void 0,e),t.promise}})},14482:(e,t,n)=>{"use strict";var r=n(76887),o=n(626),s=n(82529),i=n(6991),a=n(67742).CONSTRUCTOR,l=n(56584),c=o("Promise"),u=s&&!a;r({target:"Promise",stat:!0,forced:s||a},{resolve:function(e){return l(u&&this===c?i:this,e)}})},1502:()=>{},82266:(e,t,n)=>{"use strict";n(24683)("Set",(function(e){return function(){return e(this,arguments.length?arguments[0]:void 0)}}),n(85616))},69008:(e,t,n)=>{n(82266)},11035:(e,t,n)=>{"use strict";var r=n(76887),o=n(95329),s=n(70344),i=n(48219),a=n(85803),l=n(67772),c=o("".indexOf);r({target:"String",proto:!0,forced:!l("includes")},{includes:function(e){return!!~c(a(i(this)),a(s(e)),arguments.length>1?arguments[1]:void 0)}})},77971:(e,t,n)=>{"use strict";var r=n(64620).charAt,o=n(85803),s=n(45402),i=n(75105),a=n(23538),l="String Iterator",c=s.set,u=s.getterFor(l);i(String,"String",(function(e){c(this,{type:l,string:o(e),index:0})}),(function(){var e,t=u(this),n=t.string,o=t.index;return o>=n.length?a(void 0,!0):(e=r(n,o),t.index+=e.length,a(e,!1))}))},74679:(e,t,n)=>{var r=n(76887),o=n(95329),s=n(74529),i=n(89678),a=n(85803),l=n(10623),c=o([].push),u=o([].join);r({target:"String",stat:!0},{raw:function(e){var t=s(i(e).raw),n=l(t);if(!n)return"";for(var r=arguments.length,o=[],p=0;;){if(c(o,a(t[p++])),p===n)return u(o,"");p{n(76887)({target:"String",proto:!0},{repeat:n(16178)})},94761:(e,t,n)=>{"use strict";var r,o=n(76887),s=n(97484),i=n(49677).f,a=n(43057),l=n(85803),c=n(70344),u=n(48219),p=n(67772),h=n(82529),f=s("".startsWith),d=s("".slice),m=Math.min,g=p("startsWith");o({target:"String",proto:!0,forced:!!(h||g||(r=i(String.prototype,"startsWith"),!r||r.writable))&&!g},{startsWith:function(e){var t=l(u(this));c(e);var n=a(m(arguments.length>1?arguments[1]:void 0,t.length)),r=l(e);return f?f(t,r,n):d(t,n,n+r.length)===r}})},57398:(e,t,n)=>{"use strict";var r=n(76887),o=n(74853).trim;r({target:"String",proto:!0,forced:n(93093)("trim")},{trim:function(){return o(this)}})},8555:(e,t,n)=>{n(73464)("asyncIterator")},48616:(e,t,n)=>{"use strict";var r=n(76887),o=n(21899),s=n(78834),i=n(95329),a=n(82529),l=n(55746),c=n(63405),u=n(95981),p=n(90953),h=n(7046),f=n(96059),d=n(74529),m=n(83894),g=n(85803),y=n(31887),v=n(29290),b=n(14771),w=n(10946),E=n(684),x=n(87857),S=n(49677),_=n(65988),j=n(59938),O=n(36760),k=n(95929),A=n(29202),C=n(68726),P=n(44262),N=n(27748),I=n(99418),T=n(99813),R=n(11477),M=n(73464),D=n(29630),F=n(90904),L=n(45402),B=n(3610).forEach,$=P("hidden"),q="Symbol",U="prototype",z=L.set,V=L.getterFor(q),W=Object[U],J=o.Symbol,K=J&&J[U],H=o.TypeError,G=o.QObject,Z=S.f,Y=_.f,X=E.f,Q=O.f,ee=i([].push),te=C("symbols"),ne=C("op-symbols"),re=C("wks"),oe=!G||!G[U]||!G[U].findChild,se=l&&u((function(){return 7!=v(Y({},"a",{get:function(){return Y(this,"a",{value:7}).a}})).a}))?function(e,t,n){var r=Z(W,t);r&&delete W[t],Y(e,t,n),r&&e!==W&&Y(W,t,r)}:Y,ie=function(e,t){var n=te[e]=v(K);return z(n,{type:q,tag:e,description:t}),l||(n.description=t),n},ae=function(e,t,n){e===W&&ae(ne,t,n),f(e);var r=m(t);return f(n),p(te,r)?(n.enumerable?(p(e,$)&&e[$][r]&&(e[$][r]=!1),n=v(n,{enumerable:y(0,!1)})):(p(e,$)||Y(e,$,y(1,{})),e[$][r]=!0),se(e,r,n)):Y(e,r,n)},le=function(e,t){f(e);var n=d(t),r=b(n).concat(he(n));return B(r,(function(t){l&&!s(ce,n,t)||ae(e,t,n[t])})),e},ce=function(e){var t=m(e),n=s(Q,this,t);return!(this===W&&p(te,t)&&!p(ne,t))&&(!(n||!p(this,t)||!p(te,t)||p(this,$)&&this[$][t])||n)},ue=function(e,t){var n=d(e),r=m(t);if(n!==W||!p(te,r)||p(ne,r)){var o=Z(n,r);return!o||!p(te,r)||p(n,$)&&n[$][r]||(o.enumerable=!0),o}},pe=function(e){var t=X(d(e)),n=[];return B(t,(function(e){p(te,e)||p(N,e)||ee(n,e)})),n},he=function(e){var t=e===W,n=X(t?ne:d(e)),r=[];return B(n,(function(e){!p(te,e)||t&&!p(W,e)||ee(r,te[e])})),r};c||(k(K=(J=function(){if(h(K,this))throw H("Symbol is not a constructor");var e=arguments.length&&void 0!==arguments[0]?g(arguments[0]):void 0,t=I(e),n=function(e){this===W&&s(n,ne,e),p(this,$)&&p(this[$],t)&&(this[$][t]=!1),se(this,t,y(1,e))};return l&&oe&&se(W,t,{configurable:!0,set:n}),ie(t,e)})[U],"toString",(function(){return V(this).tag})),k(J,"withoutSetter",(function(e){return ie(I(e),e)})),O.f=ce,_.f=ae,j.f=le,S.f=ue,w.f=E.f=pe,x.f=he,R.f=function(e){return ie(T(e),e)},l&&(A(K,"description",{configurable:!0,get:function(){return V(this).description}}),a||k(W,"propertyIsEnumerable",ce,{unsafe:!0}))),r({global:!0,constructor:!0,wrap:!0,forced:!c,sham:!c},{Symbol:J}),B(b(re),(function(e){M(e)})),r({target:q,stat:!0,forced:!c},{useSetter:function(){oe=!0},useSimple:function(){oe=!1}}),r({target:"Object",stat:!0,forced:!c,sham:!l},{create:function(e,t){return void 0===t?v(e):le(v(e),t)},defineProperty:ae,defineProperties:le,getOwnPropertyDescriptor:ue}),r({target:"Object",stat:!0,forced:!c},{getOwnPropertyNames:pe}),D(),F(J,q),N[$]=!0},52615:()=>{},64523:(e,t,n)=>{var r=n(76887),o=n(626),s=n(90953),i=n(85803),a=n(68726),l=n(34680),c=a("string-to-symbol-registry"),u=a("symbol-to-string-registry");r({target:"Symbol",stat:!0,forced:!l},{for:function(e){var t=i(e);if(s(c,t))return c[t];var n=o("Symbol")(t);return c[t]=n,u[n]=t,n}})},21732:(e,t,n)=>{n(73464)("hasInstance")},35903:(e,t,n)=>{n(73464)("isConcatSpreadable")},1825:(e,t,n)=>{n(73464)("iterator")},35824:(e,t,n)=>{n(48616),n(64523),n(38608),n(32619),n(37144)},38608:(e,t,n)=>{var r=n(76887),o=n(90953),s=n(56664),i=n(69826),a=n(68726),l=n(34680),c=a("symbol-to-string-registry");r({target:"Symbol",stat:!0,forced:!l},{keyFor:function(e){if(!s(e))throw TypeError(i(e)+" is not a symbol");if(o(c,e))return c[e]}})},45915:(e,t,n)=>{n(73464)("matchAll")},28394:(e,t,n)=>{n(73464)("match")},61766:(e,t,n)=>{n(73464)("replace")},62737:(e,t,n)=>{n(73464)("search")},89911:(e,t,n)=>{n(73464)("species")},74315:(e,t,n)=>{n(73464)("split")},63131:(e,t,n)=>{var r=n(73464),o=n(29630);r("toPrimitive"),o()},64714:(e,t,n)=>{var r=n(626),o=n(73464),s=n(90904);o("toStringTag"),s(r("Symbol"),"Symbol")},70659:(e,t,n)=>{n(73464)("unscopables")},94776:(e,t,n)=>{"use strict";var r,o=n(45602),s=n(21899),i=n(95329),a=n(94380),l=n(21647),c=n(24683),u=n(8850),p=n(10941),h=n(45402).enforce,f=n(95981),d=n(47093),m=Object,g=Array.isArray,y=m.isExtensible,v=m.isFrozen,b=m.isSealed,w=m.freeze,E=m.seal,x={},S={},_=!s.ActiveXObject&&"ActiveXObject"in s,j=function(e){return function(){return e(this,arguments.length?arguments[0]:void 0)}},O=c("WeakMap",j,u),k=O.prototype,A=i(k.set);if(d)if(_){r=u.getConstructor(j,"WeakMap",!0),l.enable();var C=i(k.delete),P=i(k.has),N=i(k.get);a(k,{delete:function(e){if(p(e)&&!y(e)){var t=h(this);return t.frozen||(t.frozen=new r),C(this,e)||t.frozen.delete(e)}return C(this,e)},has:function(e){if(p(e)&&!y(e)){var t=h(this);return t.frozen||(t.frozen=new r),P(this,e)||t.frozen.has(e)}return P(this,e)},get:function(e){if(p(e)&&!y(e)){var t=h(this);return t.frozen||(t.frozen=new r),P(this,e)?N(this,e):t.frozen.get(e)}return N(this,e)},set:function(e,t){if(p(e)&&!y(e)){var n=h(this);n.frozen||(n.frozen=new r),P(this,e)?A(this,e,t):n.frozen.set(e,t)}else A(this,e,t);return this}})}else o&&f((function(){var e=w([]);return A(new O,e,1),!v(e)}))&&a(k,{set:function(e,t){var n;return g(e)&&(v(e)?n=x:b(e)&&(n=S)),A(this,e,t),n==x&&w(e),n==S&&E(e),this}})},54334:(e,t,n)=>{n(94776)},31115:(e,t,n)=>{"use strict";n(24683)("WeakSet",(function(e){return function(){return e(this,arguments.length?arguments[0]:void 0)}}),n(8850))},1773:(e,t,n)=>{n(31115)},97522:(e,t,n)=>{var r=n(99813),o=n(65988).f,s=r("metadata"),i=Function.prototype;void 0===i[s]&&o(i,s,{value:null})},28783:(e,t,n)=>{n(73464)("asyncDispose")},43975:(e,t,n)=>{n(73464)("dispose")},97618:(e,t,n)=>{n(76887)({target:"Symbol",stat:!0},{isRegisteredSymbol:n(32087)})},22731:(e,t,n)=>{n(76887)({target:"Symbol",stat:!0,name:"isRegisteredSymbol"},{isRegistered:n(32087)})},6989:(e,t,n)=>{n(76887)({target:"Symbol",stat:!0,forced:!0},{isWellKnownSymbol:n(96559)})},85605:(e,t,n)=>{n(76887)({target:"Symbol",stat:!0,name:"isWellKnownSymbol",forced:!0},{isWellKnown:n(96559)})},65799:(e,t,n)=>{n(73464)("matcher")},31943:(e,t,n)=>{n(73464)("metadataKey")},45414:(e,t,n)=>{n(73464)("metadata")},46774:(e,t,n)=>{n(73464)("observable")},80620:(e,t,n)=>{n(73464)("patternMatch")},36172:(e,t,n)=>{n(73464)("replaceAll")},7634:(e,t,n)=>{n(66274);var r=n(63281),o=n(21899),s=n(9697),i=n(32029),a=n(12077),l=n(99813)("toStringTag");for(var c in r){var u=o[c],p=u&&u.prototype;p&&s(p)!==l&&i(p,l,c),a[c]=a.Array}},79229:(e,t,n)=>{var r=n(76887),o=n(21899),s=n(37620)(o.setInterval,!0);r({global:!0,bind:!0,forced:o.setInterval!==s},{setInterval:s})},17749:(e,t,n)=>{var r=n(76887),o=n(21899),s=n(37620)(o.setTimeout,!0);r({global:!0,bind:!0,forced:o.setTimeout!==s},{setTimeout:s})},71249:(e,t,n)=>{n(79229),n(17749)},62524:(e,t,n)=>{"use strict";n(66274);var r=n(76887),o=n(21899),s=n(78834),i=n(95329),a=n(55746),l=n(14766),c=n(95929),u=n(29202),p=n(94380),h=n(90904),f=n(53847),d=n(45402),m=n(5743),g=n(57475),y=n(90953),v=n(86843),b=n(9697),w=n(96059),E=n(10941),x=n(85803),S=n(29290),_=n(31887),j=n(53476),O=n(22902),k=n(18348),A=n(99813),C=n(61388),P=A("iterator"),N="URLSearchParams",I=N+"Iterator",T=d.set,R=d.getterFor(N),M=d.getterFor(I),D=Object.getOwnPropertyDescriptor,F=function(e){if(!a)return o[e];var t=D(o,e);return t&&t.value},L=F("fetch"),B=F("Request"),$=F("Headers"),q=B&&B.prototype,U=$&&$.prototype,z=o.RegExp,V=o.TypeError,W=o.decodeURIComponent,J=o.encodeURIComponent,K=i("".charAt),H=i([].join),G=i([].push),Z=i("".replace),Y=i([].shift),X=i([].splice),Q=i("".split),ee=i("".slice),te=/\+/g,ne=Array(4),re=function(e){return ne[e-1]||(ne[e-1]=z("((?:%[\\da-f]{2}){"+e+"})","gi"))},oe=function(e){try{return W(e)}catch(t){return e}},se=function(e){var t=Z(e,te," "),n=4;try{return W(t)}catch(e){for(;n;)t=Z(t,re(n--),oe);return t}},ie=/[!'()~]|%20/g,ae={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"},le=function(e){return ae[e]},ce=function(e){return Z(J(e),ie,le)},ue=f((function(e,t){T(this,{type:I,iterator:j(R(e).entries),kind:t})}),"Iterator",(function(){var e=M(this),t=e.kind,n=e.iterator.next(),r=n.value;return n.done||(n.value="keys"===t?r.key:"values"===t?r.value:[r.key,r.value]),n}),!0),pe=function(e){this.entries=[],this.url=null,void 0!==e&&(E(e)?this.parseObject(e):this.parseQuery("string"==typeof e?"?"===K(e,0)?ee(e,1):e:x(e)))};pe.prototype={type:N,bindURL:function(e){this.url=e,this.update()},parseObject:function(e){var t,n,r,o,i,a,l,c=O(e);if(c)for(n=(t=j(e,c)).next;!(r=s(n,t)).done;){if(i=(o=j(w(r.value))).next,(a=s(i,o)).done||(l=s(i,o)).done||!s(i,o).done)throw V("Expected sequence with length 2");G(this.entries,{key:x(a.value),value:x(l.value)})}else for(var u in e)y(e,u)&&G(this.entries,{key:u,value:x(e[u])})},parseQuery:function(e){if(e)for(var t,n,r=Q(e,"&"),o=0;o0?arguments[0]:void 0));a||(this.size=e.entries.length)},fe=he.prototype;if(p(fe,{append:function(e,t){var n=R(this);k(arguments.length,2),G(n.entries,{key:x(e),value:x(t)}),a||this.length++,n.updateURL()},delete:function(e){for(var t=R(this),n=k(arguments.length,1),r=t.entries,o=x(e),s=n<2?void 0:arguments[1],i=void 0===s?s:x(s),l=0;lt.key?1:-1})),e.updateURL()},forEach:function(e){for(var t,n=R(this).entries,r=v(e,arguments.length>1?arguments[1]:void 0),o=0;o1?ge(arguments[1]):{})}}),g(B)){var ye=function(e){return m(this,q),new B(e,arguments.length>1?ge(arguments[1]):{})};q.constructor=ye,ye.prototype=q,r({global:!0,constructor:!0,dontCallGetSet:!0,forced:!0},{Request:ye})}}e.exports={URLSearchParams:he,getState:R}},16454:()=>{},73305:()=>{},95304:(e,t,n)=>{n(62524)},62337:()=>{},84630:(e,t,n)=>{var r=n(76887),o=n(626),s=n(95981),i=n(18348),a=n(85803),l=n(14766),c=o("URL");r({target:"URL",stat:!0,forced:!(l&&s((function(){c.canParse()})))},{canParse:function(e){var t=i(arguments.length,1),n=a(e),r=t<2||void 0===arguments[1]?void 0:a(arguments[1]);try{return!!new c(n,r)}catch(e){return!1}}})},47250:(e,t,n)=>{"use strict";n(77971);var r,o=n(76887),s=n(55746),i=n(14766),a=n(21899),l=n(86843),c=n(95329),u=n(95929),p=n(29202),h=n(5743),f=n(90953),d=n(24420),m=n(11354),g=n(15790),y=n(64620).codeAt,v=n(73291),b=n(85803),w=n(90904),E=n(18348),x=n(62524),S=n(45402),_=S.set,j=S.getterFor("URL"),O=x.URLSearchParams,k=x.getState,A=a.URL,C=a.TypeError,P=a.parseInt,N=Math.floor,I=Math.pow,T=c("".charAt),R=c(/./.exec),M=c([].join),D=c(1..toString),F=c([].pop),L=c([].push),B=c("".replace),$=c([].shift),q=c("".split),U=c("".slice),z=c("".toLowerCase),V=c([].unshift),W="Invalid scheme",J="Invalid host",K="Invalid port",H=/[a-z]/i,G=/[\d+-.a-z]/i,Z=/\d/,Y=/^0x/i,X=/^[0-7]+$/,Q=/^\d+$/,ee=/^[\da-f]+$/i,te=/[\0\t\n\r #%/:<>?@[\\\]^|]/,ne=/[\0\t\n\r #/:<>?@[\\\]^|]/,re=/^[\u0000-\u0020]+/,oe=/(^|[^\u0000-\u0020])[\u0000-\u0020]+$/,se=/[\t\n\r]/g,ie=function(e){var t,n,r,o;if("number"==typeof e){for(t=[],n=0;n<4;n++)V(t,e%256),e=N(e/256);return M(t,".")}if("object"==typeof e){for(t="",r=function(e){for(var t=null,n=1,r=null,o=0,s=0;s<8;s++)0!==e[s]?(o>n&&(t=r,n=o),r=null,o=0):(null===r&&(r=s),++o);return o>n&&(t=r,n=o),t}(e),n=0;n<8;n++)o&&0===e[n]||(o&&(o=!1),r===n?(t+=n?":":"::",o=!0):(t+=D(e[n],16),n<7&&(t+=":")));return"["+t+"]"}return e},ae={},le=d({},ae,{" ":1,'"':1,"<":1,">":1,"`":1}),ce=d({},le,{"#":1,"?":1,"{":1,"}":1}),ue=d({},ce,{"/":1,":":1,";":1,"=":1,"@":1,"[":1,"\\":1,"]":1,"^":1,"|":1}),pe=function(e,t){var n=y(e,0);return n>32&&n<127&&!f(t,e)?e:encodeURIComponent(e)},he={ftp:21,file:null,http:80,https:443,ws:80,wss:443},fe=function(e,t){var n;return 2==e.length&&R(H,T(e,0))&&(":"==(n=T(e,1))||!t&&"|"==n)},de=function(e){var t;return e.length>1&&fe(U(e,0,2))&&(2==e.length||"/"===(t=T(e,2))||"\\"===t||"?"===t||"#"===t)},me=function(e){return"."===e||"%2e"===z(e)},ge={},ye={},ve={},be={},we={},Ee={},xe={},Se={},_e={},je={},Oe={},ke={},Ae={},Ce={},Pe={},Ne={},Ie={},Te={},Re={},Me={},De={},Fe=function(e,t,n){var r,o,s,i=b(e);if(t){if(o=this.parse(i))throw C(o);this.searchParams=null}else{if(void 0!==n&&(r=new Fe(n,!0)),o=this.parse(i,null,r))throw C(o);(s=k(new O)).bindURL(this),this.searchParams=s}};Fe.prototype={type:"URL",parse:function(e,t,n){var o,s,i,a,l,c=this,u=t||ge,p=0,h="",d=!1,y=!1,v=!1;for(e=b(e),t||(c.scheme="",c.username="",c.password="",c.host=null,c.port=null,c.path=[],c.query=null,c.fragment=null,c.cannotBeABaseURL=!1,e=B(e,re,""),e=B(e,oe,"$1")),e=B(e,se,""),o=m(e);p<=o.length;){switch(s=o[p],u){case ge:if(!s||!R(H,s)){if(t)return W;u=ve;continue}h+=z(s),u=ye;break;case ye:if(s&&(R(G,s)||"+"==s||"-"==s||"."==s))h+=z(s);else{if(":"!=s){if(t)return W;h="",u=ve,p=0;continue}if(t&&(c.isSpecial()!=f(he,h)||"file"==h&&(c.includesCredentials()||null!==c.port)||"file"==c.scheme&&!c.host))return;if(c.scheme=h,t)return void(c.isSpecial()&&he[c.scheme]==c.port&&(c.port=null));h="","file"==c.scheme?u=Ce:c.isSpecial()&&n&&n.scheme==c.scheme?u=be:c.isSpecial()?u=Se:"/"==o[p+1]?(u=we,p++):(c.cannotBeABaseURL=!0,L(c.path,""),u=Re)}break;case ve:if(!n||n.cannotBeABaseURL&&"#"!=s)return W;if(n.cannotBeABaseURL&&"#"==s){c.scheme=n.scheme,c.path=g(n.path),c.query=n.query,c.fragment="",c.cannotBeABaseURL=!0,u=De;break}u="file"==n.scheme?Ce:Ee;continue;case be:if("/"!=s||"/"!=o[p+1]){u=Ee;continue}u=_e,p++;break;case we:if("/"==s){u=je;break}u=Te;continue;case Ee:if(c.scheme=n.scheme,s==r)c.username=n.username,c.password=n.password,c.host=n.host,c.port=n.port,c.path=g(n.path),c.query=n.query;else if("/"==s||"\\"==s&&c.isSpecial())u=xe;else if("?"==s)c.username=n.username,c.password=n.password,c.host=n.host,c.port=n.port,c.path=g(n.path),c.query="",u=Me;else{if("#"!=s){c.username=n.username,c.password=n.password,c.host=n.host,c.port=n.port,c.path=g(n.path),c.path.length--,u=Te;continue}c.username=n.username,c.password=n.password,c.host=n.host,c.port=n.port,c.path=g(n.path),c.query=n.query,c.fragment="",u=De}break;case xe:if(!c.isSpecial()||"/"!=s&&"\\"!=s){if("/"!=s){c.username=n.username,c.password=n.password,c.host=n.host,c.port=n.port,u=Te;continue}u=je}else u=_e;break;case Se:if(u=_e,"/"!=s||"/"!=T(h,p+1))continue;p++;break;case _e:if("/"!=s&&"\\"!=s){u=je;continue}break;case je:if("@"==s){d&&(h="%40"+h),d=!0,i=m(h);for(var w=0;w65535)return K;c.port=c.isSpecial()&&S===he[c.scheme]?null:S,h=""}if(t)return;u=Ie;continue}return K}h+=s;break;case Ce:if(c.scheme="file","/"==s||"\\"==s)u=Pe;else{if(!n||"file"!=n.scheme){u=Te;continue}if(s==r)c.host=n.host,c.path=g(n.path),c.query=n.query;else if("?"==s)c.host=n.host,c.path=g(n.path),c.query="",u=Me;else{if("#"!=s){de(M(g(o,p),""))||(c.host=n.host,c.path=g(n.path),c.shortenPath()),u=Te;continue}c.host=n.host,c.path=g(n.path),c.query=n.query,c.fragment="",u=De}}break;case Pe:if("/"==s||"\\"==s){u=Ne;break}n&&"file"==n.scheme&&!de(M(g(o,p),""))&&(fe(n.path[0],!0)?L(c.path,n.path[0]):c.host=n.host),u=Te;continue;case Ne:if(s==r||"/"==s||"\\"==s||"?"==s||"#"==s){if(!t&&fe(h))u=Te;else if(""==h){if(c.host="",t)return;u=Ie}else{if(a=c.parseHost(h))return a;if("localhost"==c.host&&(c.host=""),t)return;h="",u=Ie}continue}h+=s;break;case Ie:if(c.isSpecial()){if(u=Te,"/"!=s&&"\\"!=s)continue}else if(t||"?"!=s)if(t||"#"!=s){if(s!=r&&(u=Te,"/"!=s))continue}else c.fragment="",u=De;else c.query="",u=Me;break;case Te:if(s==r||"/"==s||"\\"==s&&c.isSpecial()||!t&&("?"==s||"#"==s)){if(".."===(l=z(l=h))||"%2e."===l||".%2e"===l||"%2e%2e"===l?(c.shortenPath(),"/"==s||"\\"==s&&c.isSpecial()||L(c.path,"")):me(h)?"/"==s||"\\"==s&&c.isSpecial()||L(c.path,""):("file"==c.scheme&&!c.path.length&&fe(h)&&(c.host&&(c.host=""),h=T(h,0)+":"),L(c.path,h)),h="","file"==c.scheme&&(s==r||"?"==s||"#"==s))for(;c.path.length>1&&""===c.path[0];)$(c.path);"?"==s?(c.query="",u=Me):"#"==s&&(c.fragment="",u=De)}else h+=pe(s,ce);break;case Re:"?"==s?(c.query="",u=Me):"#"==s?(c.fragment="",u=De):s!=r&&(c.path[0]+=pe(s,ae));break;case Me:t||"#"!=s?s!=r&&("'"==s&&c.isSpecial()?c.query+="%27":c.query+="#"==s?"%23":pe(s,ae)):(c.fragment="",u=De);break;case De:s!=r&&(c.fragment+=pe(s,le))}p++}},parseHost:function(e){var t,n,r;if("["==T(e,0)){if("]"!=T(e,e.length-1))return J;if(t=function(e){var t,n,r,o,s,i,a,l=[0,0,0,0,0,0,0,0],c=0,u=null,p=0,h=function(){return T(e,p)};if(":"==h()){if(":"!=T(e,1))return;p+=2,u=++c}for(;h();){if(8==c)return;if(":"!=h()){for(t=n=0;n<4&&R(ee,h());)t=16*t+P(h(),16),p++,n++;if("."==h()){if(0==n)return;if(p-=n,c>6)return;for(r=0;h();){if(o=null,r>0){if(!("."==h()&&r<4))return;p++}if(!R(Z,h()))return;for(;R(Z,h());){if(s=P(h(),10),null===o)o=s;else{if(0==o)return;o=10*o+s}if(o>255)return;p++}l[c]=256*l[c]+o,2!=++r&&4!=r||c++}if(4!=r)return;break}if(":"==h()){if(p++,!h())return}else if(h())return;l[c++]=t}else{if(null!==u)return;p++,u=++c}}if(null!==u)for(i=c-u,c=7;0!=c&&i>0;)a=l[c],l[c--]=l[u+i-1],l[u+--i]=a;else if(8!=c)return;return l}(U(e,1,-1)),!t)return J;this.host=t}else if(this.isSpecial()){if(e=v(e),R(te,e))return J;if(t=function(e){var t,n,r,o,s,i,a,l=q(e,".");if(l.length&&""==l[l.length-1]&&l.length--,(t=l.length)>4)return e;for(n=[],r=0;r1&&"0"==T(o,0)&&(s=R(Y,o)?16:8,o=U(o,8==s?1:2)),""===o)i=0;else{if(!R(10==s?Q:8==s?X:ee,o))return e;i=P(o,s)}L(n,i)}for(r=0;r=I(256,5-t))return null}else if(i>255)return null;for(a=F(n),r=0;r1?arguments[1]:void 0,r=_(t,new Fe(e,!1,n));s||(t.href=r.serialize(),t.origin=r.getOrigin(),t.protocol=r.getProtocol(),t.username=r.getUsername(),t.password=r.getPassword(),t.host=r.getHost(),t.hostname=r.getHostname(),t.port=r.getPort(),t.pathname=r.getPathname(),t.search=r.getSearch(),t.searchParams=r.getSearchParams(),t.hash=r.getHash())},Be=Le.prototype,$e=function(e,t){return{get:function(){return j(this)[e]()},set:t&&function(e){return j(this)[t](e)},configurable:!0,enumerable:!0}};if(s&&(p(Be,"href",$e("serialize","setHref")),p(Be,"origin",$e("getOrigin")),p(Be,"protocol",$e("getProtocol","setProtocol")),p(Be,"username",$e("getUsername","setUsername")),p(Be,"password",$e("getPassword","setPassword")),p(Be,"host",$e("getHost","setHost")),p(Be,"hostname",$e("getHostname","setHostname")),p(Be,"port",$e("getPort","setPort")),p(Be,"pathname",$e("getPathname","setPathname")),p(Be,"search",$e("getSearch","setSearch")),p(Be,"searchParams",$e("getSearchParams")),p(Be,"hash",$e("getHash","setHash"))),u(Be,"toJSON",(function(){return j(this).serialize()}),{enumerable:!0}),u(Be,"toString",(function(){return j(this).serialize()}),{enumerable:!0}),A){var qe=A.createObjectURL,Ue=A.revokeObjectURL;qe&&u(Le,"createObjectURL",l(qe,A)),Ue&&u(Le,"revokeObjectURL",l(Ue,A))}w(Le,"URL"),o({global:!0,constructor:!0,forced:!i,sham:!s},{URL:Le})},33601:(e,t,n)=>{n(47250)},98947:()=>{},24848:(e,t,n)=>{var r=n(54493);e.exports=r},83363:(e,t,n)=>{var r=n(24034);e.exports=r},62908:(e,t,n)=>{var r=n(12710);e.exports=r},49216:(e,t,n)=>{var r=n(99324);e.exports=r},56668:(e,t,n)=>{var r=n(95909);e.exports=r},74719:(e,t,n)=>{var r=n(14423);e.exports=r},57784:(e,t,n)=>{var r=n(81103);e.exports=r},28196:(e,t,n)=>{var r=n(16246);e.exports=r},8065:(e,t,n)=>{var r=n(56043);e.exports=r},57448:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),s=n(7046),i=n(62908),a=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.entries;return e===a||s(a,e)&&t===a.entries||o(l,r(e))?i:t}},29455:(e,t,n)=>{var r=n(13160);e.exports=r},69743:(e,t,n)=>{var r=n(80446);e.exports=r},11955:(e,t,n)=>{var r=n(2480);e.exports=r},96064:(e,t,n)=>{var r=n(7147);e.exports=r},61577:(e,t,n)=>{var r=n(32236);e.exports=r},46279:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),s=n(7046),i=n(49216),a=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.forEach;return e===a||s(a,e)&&t===a.forEach||o(l,r(e))?i:t}},33778:(e,t,n)=>{var r=n(58557);e.exports=r},19373:(e,t,n)=>{var r=n(34570);e.exports=r},73819:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),s=n(7046),i=n(56668),a=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.keys;return e===a||s(a,e)&&t===a.keys||o(l,r(e))?i:t}},11022:(e,t,n)=>{var r=n(57564);e.exports=r},61798:(e,t,n)=>{var r=n(88287);e.exports=r},52759:(e,t,n)=>{var r=n(93993);e.exports=r},52527:(e,t,n)=>{var r=n(68025);e.exports=r},36857:(e,t,n)=>{var r=n(59257);e.exports=r},82073:(e,t,n)=>{var r=n(69601);e.exports=r},45286:(e,t,n)=>{var r=n(28299);e.exports=r},62856:(e,t,n)=>{var r=n(69355);e.exports=r},2348:(e,t,n)=>{var r=n(18339);e.exports=r},35178:(e,t,n)=>{var r=n(71611);e.exports=r},76361:(e,t,n)=>{var r=n(62774);e.exports=r},71815:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),s=n(7046),i=n(74719),a=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.values;return e===a||s(a,e)&&t===a.values||o(l,r(e))?i:t}},8933:(e,t,n)=>{var r=n(84426);e.exports=r},15868:(e,t,n)=>{var r=n(91018);n(7634),e.exports=r},14873:(e,t,n)=>{var r=n(97849);e.exports=r},38849:(e,t,n)=>{var r=n(3820);e.exports=r},63383:(e,t,n)=>{var r=n(45999);e.exports=r},57396:(e,t,n)=>{var r=n(7702);e.exports=r},41910:(e,t,n)=>{var r=n(48171);e.exports=r},86209:(e,t,n)=>{var r=n(73081);e.exports=r},53402:(e,t,n)=>{var r=n(7699);n(7634),e.exports=r},79427:(e,t,n)=>{var r=n(286);e.exports=r},62857:(e,t,n)=>{var r=n(92766);e.exports=r},9534:(e,t,n)=>{var r=n(30498);e.exports=r},23059:(e,t,n)=>{var r=n(48494);e.exports=r},47795:(e,t,n)=>{var r=n(98430);e.exports=r},27460:(e,t,n)=>{var r=n(52956);n(7634),e.exports=r},27989:(e,t,n)=>{n(71249);var r=n(54058);e.exports=r.setTimeout},5519:(e,t,n)=>{var r=n(76998);n(7634),e.exports=r},23452:(e,t,n)=>{var r=n(97089);e.exports=r},92547:(e,t,n)=>{var r=n(57473);n(7634),e.exports=r},46509:(e,t,n)=>{var r=n(24227);n(7634),e.exports=r},35774:(e,t,n)=>{var r=n(62978);e.exports=r},57641:(e,t,n)=>{var r=n(71459);e.exports=r},72010:(e,t,n)=>{var r=n(32304);n(7634),e.exports=r},93726:(e,t,n)=>{var r=n(29567);n(7634),e.exports=r},47610:(e,t,n)=>{n(95304),n(16454),n(73305),n(62337);var r=n(54058);e.exports=r.URLSearchParams},71459:(e,t,n)=>{n(47610),n(33601),n(84630),n(98947);var r=n(54058);e.exports=r.URL},31905:function(){!function(e){!function(t){var n="URLSearchParams"in e,r="Symbol"in e&&"iterator"in Symbol,o="FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),s="FormData"in e,i="ArrayBuffer"in e;if(i)var a=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],l=ArrayBuffer.isView||function(e){return e&&a.indexOf(Object.prototype.toString.call(e))>-1};function c(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name");return e.toLowerCase()}function u(e){return"string"!=typeof e&&(e=String(e)),e}function p(e){var t={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return r&&(t[Symbol.iterator]=function(){return t}),t}function h(e){this.map={},e instanceof h?e.forEach((function(e,t){this.append(t,e)}),this):Array.isArray(e)?e.forEach((function(e){this.append(e[0],e[1])}),this):e&&Object.getOwnPropertyNames(e).forEach((function(t){this.append(t,e[t])}),this)}function f(e){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}function d(e){return new Promise((function(t,n){e.onload=function(){t(e.result)},e.onerror=function(){n(e.error)}}))}function m(e){var t=new FileReader,n=d(t);return t.readAsArrayBuffer(e),n}function g(e){if(e.slice)return e.slice(0);var t=new Uint8Array(e.byteLength);return t.set(new Uint8Array(e)),t.buffer}function y(){return this.bodyUsed=!1,this._initBody=function(e){var t;this._bodyInit=e,e?"string"==typeof e?this._bodyText=e:o&&Blob.prototype.isPrototypeOf(e)?this._bodyBlob=e:s&&FormData.prototype.isPrototypeOf(e)?this._bodyFormData=e:n&&URLSearchParams.prototype.isPrototypeOf(e)?this._bodyText=e.toString():i&&o&&((t=e)&&DataView.prototype.isPrototypeOf(t))?(this._bodyArrayBuffer=g(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):i&&(ArrayBuffer.prototype.isPrototypeOf(e)||l(e))?this._bodyArrayBuffer=g(e):this._bodyText=e=Object.prototype.toString.call(e):this._bodyText="",this.headers.get("content-type")||("string"==typeof e?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):n&&URLSearchParams.prototype.isPrototypeOf(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},o&&(this.blob=function(){var e=f(this);if(e)return e;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this._bodyArrayBuffer?f(this)||Promise.resolve(this._bodyArrayBuffer):this.blob().then(m)}),this.text=function(){var e,t,n,r=f(this);if(r)return r;if(this._bodyBlob)return e=this._bodyBlob,t=new FileReader,n=d(t),t.readAsText(e),n;if(this._bodyArrayBuffer)return Promise.resolve(function(e){for(var t=new Uint8Array(e),n=new Array(t.length),r=0;r-1?r:n),this.mode=t.mode||this.mode||null,this.signal=t.signal||this.signal,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&o)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(o)}function w(e){var t=new FormData;return e.trim().split("&").forEach((function(e){if(e){var n=e.split("="),r=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");t.append(decodeURIComponent(r),decodeURIComponent(o))}})),t}function E(e,t){t||(t={}),this.type="default",this.status=void 0===t.status?200:t.status,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in t?t.statusText:"OK",this.headers=new h(t.headers),this.url=t.url||"",this._initBody(e)}b.prototype.clone=function(){return new b(this,{body:this._bodyInit})},y.call(b.prototype),y.call(E.prototype),E.prototype.clone=function(){return new E(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new h(this.headers),url:this.url})},E.error=function(){var e=new E(null,{status:0,statusText:""});return e.type="error",e};var x=[301,302,303,307,308];E.redirect=function(e,t){if(-1===x.indexOf(t))throw new RangeError("Invalid status code");return new E(null,{status:t,headers:{location:e}})},t.DOMException=e.DOMException;try{new t.DOMException}catch(e){t.DOMException=function(e,t){this.message=e,this.name=t;var n=Error(e);this.stack=n.stack},t.DOMException.prototype=Object.create(Error.prototype),t.DOMException.prototype.constructor=t.DOMException}function S(e,n){return new Promise((function(r,s){var i=new b(e,n);if(i.signal&&i.signal.aborted)return s(new t.DOMException("Aborted","AbortError"));var a=new XMLHttpRequest;function l(){a.abort()}a.onload=function(){var e,t,n={status:a.status,statusText:a.statusText,headers:(e=a.getAllResponseHeaders()||"",t=new h,e.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach((function(e){var n=e.split(":"),r=n.shift().trim();if(r){var o=n.join(":").trim();t.append(r,o)}})),t)};n.url="responseURL"in a?a.responseURL:n.headers.get("X-Request-URL");var o="response"in a?a.response:a.responseText;r(new E(o,n))},a.onerror=function(){s(new TypeError("Network request failed"))},a.ontimeout=function(){s(new TypeError("Network request failed"))},a.onabort=function(){s(new t.DOMException("Aborted","AbortError"))},a.open(i.method,i.url,!0),"include"===i.credentials?a.withCredentials=!0:"omit"===i.credentials&&(a.withCredentials=!1),"responseType"in a&&o&&(a.responseType="blob"),i.headers.forEach((function(e,t){a.setRequestHeader(t,e)})),i.signal&&(i.signal.addEventListener("abort",l),a.onreadystatechange=function(){4===a.readyState&&i.signal.removeEventListener("abort",l)}),a.send(void 0===i._bodyInit?null:i._bodyInit)}))}S.polyfill=!0,e.fetch||(e.fetch=S,e.Headers=h,e.Request=b,e.Response=E),t.Headers=h,t.Request=b,t.Response=E,t.fetch=S,Object.defineProperty(t,"__esModule",{value:!0})}({})}("undefined"!=typeof self?self:this)},8269:function(e,t,n){var r;r=void 0!==n.g?n.g:this,e.exports=function(e){if(e.CSS&&e.CSS.escape)return e.CSS.escape;var t=function(e){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var t,n=String(e),r=n.length,o=-1,s="",i=n.charCodeAt(0);++o=1&&t<=31||127==t||0==o&&t>=48&&t<=57||1==o&&t>=48&&t<=57&&45==i?"\\"+t.toString(16)+" ":0==o&&1==r&&45==t||!(t>=128||45==t||95==t||t>=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122)?"\\"+n.charAt(o):n.charAt(o):s+="๏ฟฝ";return s};return e.CSS||(e.CSS={}),e.CSS.escape=t,t}(r)},27698:(e,t,n)=>{"use strict";var r=n(48764).Buffer;function o(e){return e instanceof r||e instanceof Date||e instanceof RegExp}function s(e){if(e instanceof r){var t=r.alloc?r.alloc(e.length):new r(e.length);return e.copy(t),t}if(e instanceof Date)return new Date(e.getTime());if(e instanceof RegExp)return new RegExp(e);throw new Error("Unexpected situation")}function i(e){var t=[];return e.forEach((function(e,n){"object"==typeof e&&null!==e?Array.isArray(e)?t[n]=i(e):o(e)?t[n]=s(e):t[n]=l({},e):t[n]=e})),t}function a(e,t){return"__proto__"===t?void 0:e[t]}var l=e.exports=function(){if(arguments.length<1||"object"!=typeof arguments[0])return!1;if(arguments.length<2)return arguments[0];var e,t,n=arguments[0];return Array.prototype.slice.call(arguments,1).forEach((function(r){"object"!=typeof r||null===r||Array.isArray(r)||Object.keys(r).forEach((function(c){return t=a(n,c),(e=a(r,c))===n?void 0:"object"!=typeof e||null===e?void(n[c]=e):Array.isArray(e)?void(n[c]=i(e)):o(e)?void(n[c]=s(e)):"object"!=typeof t||null===t||Array.isArray(t)?void(n[c]=l({},e)):void(n[c]=l(t,e))}))})),n}},9996:e=>{"use strict";var t=function(e){return function(e){return!!e&&"object"==typeof e}(e)&&!function(e){var t=Object.prototype.toString.call(e);return"[object RegExp]"===t||"[object Date]"===t||function(e){return e.$$typeof===n}(e)}(e)};var n="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function r(e,t){return!1!==t.clone&&t.isMergeableObject(e)?l((n=e,Array.isArray(n)?[]:{}),e,t):e;var n}function o(e,t,n){return e.concat(t).map((function(e){return r(e,n)}))}function s(e){return Object.keys(e).concat(function(e){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter((function(t){return Object.propertyIsEnumerable.call(e,t)})):[]}(e))}function i(e,t){try{return t in e}catch(e){return!1}}function a(e,t,n){var o={};return n.isMergeableObject(e)&&s(e).forEach((function(t){o[t]=r(e[t],n)})),s(t).forEach((function(s){(function(e,t){return i(e,t)&&!(Object.hasOwnProperty.call(e,t)&&Object.propertyIsEnumerable.call(e,t))})(e,s)||(i(e,s)&&n.isMergeableObject(t[s])?o[s]=function(e,t){if(!t.customMerge)return l;var n=t.customMerge(e);return"function"==typeof n?n:l}(s,n)(e[s],t[s],n):o[s]=r(t[s],n))})),o}function l(e,n,s){(s=s||{}).arrayMerge=s.arrayMerge||o,s.isMergeableObject=s.isMergeableObject||t,s.cloneUnlessOtherwiseSpecified=r;var i=Array.isArray(n);return i===Array.isArray(e)?i?s.arrayMerge(e,n,s):a(e,n,s):r(n,s)}l.all=function(e,t){if(!Array.isArray(e))throw new Error("first argument should be an array");return e.reduce((function(e,n){return l(e,n,t)}),{})};var c=l;e.exports=c},27856:function(e){e.exports=function(){"use strict";const{entries:e,setPrototypeOf:t,isFrozen:n,getPrototypeOf:r,getOwnPropertyDescriptor:o}=Object;let{freeze:s,seal:i,create:a}=Object,{apply:l,construct:c}="undefined"!=typeof Reflect&&Reflect;l||(l=function(e,t,n){return e.apply(t,n)}),s||(s=function(e){return e}),i||(i=function(e){return e}),c||(c=function(e,t){return new e(...t)});const u=E(Array.prototype.forEach),p=E(Array.prototype.pop),h=E(Array.prototype.push),f=E(String.prototype.toLowerCase),d=E(String.prototype.toString),m=E(String.prototype.match),g=E(String.prototype.replace),y=E(String.prototype.indexOf),v=E(String.prototype.trim),b=E(RegExp.prototype.test),w=x(TypeError);function E(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o/gm),B=i(/\${[\w\W]*}/gm),$=i(/^data-[\-\w.\u00B7-\uFFFF]/),q=i(/^aria-[\-\w]+$/),U=i(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),z=i(/^(?:\w+script|data):/i),V=i(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),W=i(/^html$/i);var J=Object.freeze({__proto__:null,MUSTACHE_EXPR:F,ERB_EXPR:L,TMPLIT_EXPR:B,DATA_ATTR:$,ARIA_ATTR:q,IS_ALLOWED_URI:U,IS_SCRIPT_OR_DATA:z,ATTR_WHITESPACE:V,DOCTYPE_NAME:W});const K=()=>"undefined"==typeof window?null:window,H=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let n=null;const r="data-tt-policy-suffix";t&&t.hasAttribute(r)&&(n=t.getAttribute(r));const o="dompurify"+(n?"#"+n:"");try{return e.createPolicy(o,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+o+" could not be created."),null}};function G(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:K();const n=e=>G(e);if(n.version="3.0.3",n.removed=[],!t||!t.document||9!==t.document.nodeType)return n.isSupported=!1,n;const r=t.document,o=r.currentScript;let{document:i}=t;const{DocumentFragment:a,HTMLTemplateElement:l,Node:c,Element:E,NodeFilter:x,NamedNodeMap:F=t.NamedNodeMap||t.MozNamedAttrMap,HTMLFormElement:L,DOMParser:B,trustedTypes:$}=t,q=E.prototype,z=j(q,"cloneNode"),V=j(q,"nextSibling"),Z=j(q,"childNodes"),Y=j(q,"parentNode");if("function"==typeof l){const e=i.createElement("template");e.content&&e.content.ownerDocument&&(i=e.content.ownerDocument)}let X,Q="";const{implementation:ee,createNodeIterator:te,createDocumentFragment:ne,getElementsByTagName:re}=i,{importNode:oe}=r;let se={};n.isSupported="function"==typeof e&&"function"==typeof Y&&ee&&void 0!==ee.createHTMLDocument;const{MUSTACHE_EXPR:ie,ERB_EXPR:ae,TMPLIT_EXPR:le,DATA_ATTR:ce,ARIA_ATTR:ue,IS_SCRIPT_OR_DATA:pe,ATTR_WHITESPACE:he}=J;let{IS_ALLOWED_URI:fe}=J,de=null;const me=S({},[...O,...k,...A,...P,...I]);let ge=null;const ye=S({},[...T,...R,...M,...D]);let ve=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),be=null,we=null,Ee=!0,xe=!0,Se=!1,_e=!0,je=!1,Oe=!1,ke=!1,Ae=!1,Ce=!1,Pe=!1,Ne=!1,Ie=!0,Te=!1;const Re="user-content-";let Me=!0,De=!1,Fe={},Le=null;const Be=S({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let $e=null;const qe=S({},["audio","video","img","source","image","track"]);let Ue=null;const ze=S({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Ve="http://www.w3.org/1998/Math/MathML",We="http://www.w3.org/2000/svg",Je="http://www.w3.org/1999/xhtml";let Ke=Je,He=!1,Ge=null;const Ze=S({},[Ve,We,Je],d);let Ye;const Xe=["application/xhtml+xml","text/html"],Qe="text/html";let et,tt=null;const nt=i.createElement("form"),rt=function(e){return e instanceof RegExp||e instanceof Function},ot=function(e){if(!tt||tt!==e){if(e&&"object"==typeof e||(e={}),e=_(e),Ye=Ye=-1===Xe.indexOf(e.PARSER_MEDIA_TYPE)?Qe:e.PARSER_MEDIA_TYPE,et="application/xhtml+xml"===Ye?d:f,de="ALLOWED_TAGS"in e?S({},e.ALLOWED_TAGS,et):me,ge="ALLOWED_ATTR"in e?S({},e.ALLOWED_ATTR,et):ye,Ge="ALLOWED_NAMESPACES"in e?S({},e.ALLOWED_NAMESPACES,d):Ze,Ue="ADD_URI_SAFE_ATTR"in e?S(_(ze),e.ADD_URI_SAFE_ATTR,et):ze,$e="ADD_DATA_URI_TAGS"in e?S(_(qe),e.ADD_DATA_URI_TAGS,et):qe,Le="FORBID_CONTENTS"in e?S({},e.FORBID_CONTENTS,et):Be,be="FORBID_TAGS"in e?S({},e.FORBID_TAGS,et):{},we="FORBID_ATTR"in e?S({},e.FORBID_ATTR,et):{},Fe="USE_PROFILES"in e&&e.USE_PROFILES,Ee=!1!==e.ALLOW_ARIA_ATTR,xe=!1!==e.ALLOW_DATA_ATTR,Se=e.ALLOW_UNKNOWN_PROTOCOLS||!1,_e=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,je=e.SAFE_FOR_TEMPLATES||!1,Oe=e.WHOLE_DOCUMENT||!1,Ce=e.RETURN_DOM||!1,Pe=e.RETURN_DOM_FRAGMENT||!1,Ne=e.RETURN_TRUSTED_TYPE||!1,Ae=e.FORCE_BODY||!1,Ie=!1!==e.SANITIZE_DOM,Te=e.SANITIZE_NAMED_PROPS||!1,Me=!1!==e.KEEP_CONTENT,De=e.IN_PLACE||!1,fe=e.ALLOWED_URI_REGEXP||U,Ke=e.NAMESPACE||Je,ve=e.CUSTOM_ELEMENT_HANDLING||{},e.CUSTOM_ELEMENT_HANDLING&&rt(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(ve.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&rt(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(ve.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(ve.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),je&&(xe=!1),Pe&&(Ce=!0),Fe&&(de=S({},[...I]),ge=[],!0===Fe.html&&(S(de,O),S(ge,T)),!0===Fe.svg&&(S(de,k),S(ge,R),S(ge,D)),!0===Fe.svgFilters&&(S(de,A),S(ge,R),S(ge,D)),!0===Fe.mathMl&&(S(de,P),S(ge,M),S(ge,D))),e.ADD_TAGS&&(de===me&&(de=_(de)),S(de,e.ADD_TAGS,et)),e.ADD_ATTR&&(ge===ye&&(ge=_(ge)),S(ge,e.ADD_ATTR,et)),e.ADD_URI_SAFE_ATTR&&S(Ue,e.ADD_URI_SAFE_ATTR,et),e.FORBID_CONTENTS&&(Le===Be&&(Le=_(Le)),S(Le,e.FORBID_CONTENTS,et)),Me&&(de["#text"]=!0),Oe&&S(de,["html","head","body"]),de.table&&(S(de,["tbody"]),delete be.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw w('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw w('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');X=e.TRUSTED_TYPES_POLICY,Q=X.createHTML("")}else void 0===X&&(X=H($,o)),null!==X&&"string"==typeof Q&&(Q=X.createHTML(""));s&&s(e),tt=e}},st=S({},["mi","mo","mn","ms","mtext"]),it=S({},["foreignobject","desc","title","annotation-xml"]),at=S({},["title","style","font","a","script"]),lt=S({},k);S(lt,A),S(lt,C);const ct=S({},P);S(ct,N);const ut=function(e){let t=Y(e);t&&t.tagName||(t={namespaceURI:Ke,tagName:"template"});const n=f(e.tagName),r=f(t.tagName);return!!Ge[e.namespaceURI]&&(e.namespaceURI===We?t.namespaceURI===Je?"svg"===n:t.namespaceURI===Ve?"svg"===n&&("annotation-xml"===r||st[r]):Boolean(lt[n]):e.namespaceURI===Ve?t.namespaceURI===Je?"math"===n:t.namespaceURI===We?"math"===n&&it[r]:Boolean(ct[n]):e.namespaceURI===Je?!(t.namespaceURI===We&&!it[r])&&!(t.namespaceURI===Ve&&!st[r])&&!ct[n]&&(at[n]||!lt[n]):!("application/xhtml+xml"!==Ye||!Ge[e.namespaceURI]))},pt=function(e){h(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){e.remove()}},ht=function(e,t){try{h(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){h(n.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!ge[e])if(Ce||Pe)try{pt(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},ft=function(e){let t,n;if(Ae)e=""+e;else{const t=m(e,/^[\r\n\t ]+/);n=t&&t[0]}"application/xhtml+xml"===Ye&&Ke===Je&&(e=''+e+"");const r=X?X.createHTML(e):e;if(Ke===Je)try{t=(new B).parseFromString(r,Ye)}catch(e){}if(!t||!t.documentElement){t=ee.createDocument(Ke,"template",null);try{t.documentElement.innerHTML=He?Q:r}catch(e){}}const o=t.body||t.documentElement;return e&&n&&o.insertBefore(i.createTextNode(n),o.childNodes[0]||null),Ke===Je?re.call(t,Oe?"html":"body")[0]:Oe?t.documentElement:o},dt=function(e){return te.call(e.ownerDocument||e,e,x.SHOW_ELEMENT|x.SHOW_COMMENT|x.SHOW_TEXT,null,!1)},mt=function(e){return e instanceof L&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof F)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},gt=function(e){return"object"==typeof c?e instanceof c:e&&"object"==typeof e&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},yt=function(e,t,r){se[e]&&u(se[e],(e=>{e.call(n,t,r,tt)}))},vt=function(e){let t;if(yt("beforeSanitizeElements",e,null),mt(e))return pt(e),!0;const r=et(e.nodeName);if(yt("uponSanitizeElement",e,{tagName:r,allowedTags:de}),e.hasChildNodes()&&!gt(e.firstElementChild)&&(!gt(e.content)||!gt(e.content.firstElementChild))&&b(/<[/\w]/g,e.innerHTML)&&b(/<[/\w]/g,e.textContent))return pt(e),!0;if(!de[r]||be[r]){if(!be[r]&&wt(r)){if(ve.tagNameCheck instanceof RegExp&&b(ve.tagNameCheck,r))return!1;if(ve.tagNameCheck instanceof Function&&ve.tagNameCheck(r))return!1}if(Me&&!Le[r]){const t=Y(e)||e.parentNode,n=Z(e)||e.childNodes;if(n&&t)for(let r=n.length-1;r>=0;--r)t.insertBefore(z(n[r],!0),V(e))}return pt(e),!0}return e instanceof E&&!ut(e)?(pt(e),!0):"noscript"!==r&&"noembed"!==r||!b(/<\/no(script|embed)/i,e.innerHTML)?(je&&3===e.nodeType&&(t=e.textContent,t=g(t,ie," "),t=g(t,ae," "),t=g(t,le," "),e.textContent!==t&&(h(n.removed,{element:e.cloneNode()}),e.textContent=t)),yt("afterSanitizeElements",e,null),!1):(pt(e),!0)},bt=function(e,t,n){if(Ie&&("id"===t||"name"===t)&&(n in i||n in nt))return!1;if(xe&&!we[t]&&b(ce,t));else if(Ee&&b(ue,t));else if(!ge[t]||we[t]){if(!(wt(e)&&(ve.tagNameCheck instanceof RegExp&&b(ve.tagNameCheck,e)||ve.tagNameCheck instanceof Function&&ve.tagNameCheck(e))&&(ve.attributeNameCheck instanceof RegExp&&b(ve.attributeNameCheck,t)||ve.attributeNameCheck instanceof Function&&ve.attributeNameCheck(t))||"is"===t&&ve.allowCustomizedBuiltInElements&&(ve.tagNameCheck instanceof RegExp&&b(ve.tagNameCheck,n)||ve.tagNameCheck instanceof Function&&ve.tagNameCheck(n))))return!1}else if(Ue[t]);else if(b(fe,g(n,he,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==y(n,"data:")||!$e[e])if(Se&&!b(pe,g(n,he,"")));else if(n)return!1;return!0},wt=function(e){return e.indexOf("-")>0},Et=function(e){let t,r,o,s;yt("beforeSanitizeAttributes",e,null);const{attributes:i}=e;if(!i)return;const a={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:ge};for(s=i.length;s--;){t=i[s];const{name:l,namespaceURI:c}=t;if(r="value"===l?t.value:v(t.value),o=et(l),a.attrName=o,a.attrValue=r,a.keepAttr=!0,a.forceKeepAttr=void 0,yt("uponSanitizeAttribute",e,a),r=a.attrValue,a.forceKeepAttr)continue;if(ht(l,e),!a.keepAttr)continue;if(!_e&&b(/\/>/i,r)){ht(l,e);continue}je&&(r=g(r,ie," "),r=g(r,ae," "),r=g(r,le," "));const u=et(e.nodeName);if(bt(u,o,r)){if(!Te||"id"!==o&&"name"!==o||(ht(l,e),r=Re+r),X&&"object"==typeof $&&"function"==typeof $.getAttributeType)if(c);else switch($.getAttributeType(u,o)){case"TrustedHTML":r=X.createHTML(r);break;case"TrustedScriptURL":r=X.createScriptURL(r)}try{c?e.setAttributeNS(c,l,r):e.setAttribute(l,r),p(n.removed)}catch(e){}}}yt("afterSanitizeAttributes",e,null)},xt=function e(t){let n;const r=dt(t);for(yt("beforeSanitizeShadowDOM",t,null);n=r.nextNode();)yt("uponSanitizeShadowNode",n,null),vt(n)||(n.content instanceof a&&e(n.content),Et(n));yt("afterSanitizeShadowDOM",t,null)};return n.sanitize=function(e){let t,o,s,i,l=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(He=!e,He&&(e="\x3c!--\x3e"),"string"!=typeof e&&!gt(e)){if("function"!=typeof e.toString)throw w("toString is not a function");if("string"!=typeof(e=e.toString()))throw w("dirty is not a string, aborting")}if(!n.isSupported)return e;if(ke||ot(l),n.removed=[],"string"==typeof e&&(De=!1),De){if(e.nodeName){const t=et(e.nodeName);if(!de[t]||be[t])throw w("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof c)t=ft("\x3c!----\x3e"),o=t.ownerDocument.importNode(e,!0),1===o.nodeType&&"BODY"===o.nodeName||"HTML"===o.nodeName?t=o:t.appendChild(o);else{if(!Ce&&!je&&!Oe&&-1===e.indexOf("<"))return X&&Ne?X.createHTML(e):e;if(t=ft(e),!t)return Ce?null:Ne?Q:""}t&&Ae&&pt(t.firstChild);const u=dt(De?e:t);for(;s=u.nextNode();)vt(s)||(s.content instanceof a&&xt(s.content),Et(s));if(De)return e;if(Ce){if(Pe)for(i=ne.call(t.ownerDocument);t.firstChild;)i.appendChild(t.firstChild);else i=t;return(ge.shadowroot||ge.shadowrootmod)&&(i=oe.call(r,i,!0)),i}let p=Oe?t.outerHTML:t.innerHTML;return Oe&&de["!doctype"]&&t.ownerDocument&&t.ownerDocument.doctype&&t.ownerDocument.doctype.name&&b(W,t.ownerDocument.doctype.name)&&(p="\n"+p),je&&(p=g(p,ie," "),p=g(p,ae," "),p=g(p,le," ")),X&&Ne?X.createHTML(p):p},n.setConfig=function(e){ot(e),ke=!0},n.clearConfig=function(){tt=null,ke=!1},n.isValidAttribute=function(e,t,n){tt||ot({});const r=et(e),o=et(t);return bt(r,o,n)},n.addHook=function(e,t){"function"==typeof t&&(se[e]=se[e]||[],h(se[e],t))},n.removeHook=function(e){if(se[e])return p(se[e])},n.removeHooks=function(e){se[e]&&(se[e]=[])},n.removeAllHooks=function(){se={}},n}return G()}()},69450:e=>{"use strict";class t{constructor(e,t){this.low=e,this.high=t,this.length=1+t-e}overlaps(e){return!(this.highe.high)}touches(e){return!(this.high+1e.high)}add(e){return new t(Math.min(this.low,e.low),Math.max(this.high,e.high))}subtract(e){return e.low<=this.low&&e.high>=this.high?[]:e.low>this.low&&e.highe+t.length),0)}add(e,r){var o=e=>{for(var t=0;t{for(var t=0;t{for(var n=0;n{for(var n=t.low;n<=t.high;)e.push(n),n++;return e}),[])}subranges(){return this.ranges.map((e=>({low:e.low,high:e.high,length:1+e.high-e.low})))}}e.exports=n},17187:e=>{"use strict";var t,n="object"==typeof Reflect?Reflect:null,r=n&&"function"==typeof n.apply?n.apply:function(e,t,n){return Function.prototype.apply.call(e,t,n)};t=n&&"function"==typeof n.ownKeys?n.ownKeys:Object.getOwnPropertySymbols?function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:function(e){return Object.getOwnPropertyNames(e)};var o=Number.isNaN||function(e){return e!=e};function s(){s.init.call(this)}e.exports=s,e.exports.once=function(e,t){return new Promise((function(n,r){function o(n){e.removeListener(t,s),r(n)}function s(){"function"==typeof e.removeListener&&e.removeListener("error",o),n([].slice.call(arguments))}m(e,t,s,{once:!0}),"error"!==t&&function(e,t,n){"function"==typeof e.on&&m(e,"error",t,n)}(e,o,{once:!0})}))},s.EventEmitter=s,s.prototype._events=void 0,s.prototype._eventsCount=0,s.prototype._maxListeners=void 0;var i=10;function a(e){if("function"!=typeof e)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof e)}function l(e){return void 0===e._maxListeners?s.defaultMaxListeners:e._maxListeners}function c(e,t,n,r){var o,s,i,c;if(a(n),void 0===(s=e._events)?(s=e._events=Object.create(null),e._eventsCount=0):(void 0!==s.newListener&&(e.emit("newListener",t,n.listener?n.listener:n),s=e._events),i=s[t]),void 0===i)i=s[t]=n,++e._eventsCount;else if("function"==typeof i?i=s[t]=r?[n,i]:[i,n]:r?i.unshift(n):i.push(n),(o=l(e))>0&&i.length>o&&!i.warned){i.warned=!0;var u=new Error("Possible EventEmitter memory leak detected. "+i.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");u.name="MaxListenersExceededWarning",u.emitter=e,u.type=t,u.count=i.length,c=u,console&&console.warn&&console.warn(c)}return e}function u(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function p(e,t,n){var r={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},o=u.bind(r);return o.listener=n,r.wrapFn=o,o}function h(e,t,n){var r=e._events;if(void 0===r)return[];var o=r[t];return void 0===o?[]:"function"==typeof o?n?[o.listener||o]:[o]:n?function(e){for(var t=new Array(e.length),n=0;n0&&(i=t[0]),i instanceof Error)throw i;var a=new Error("Unhandled error."+(i?" ("+i.message+")":""));throw a.context=i,a}var l=s[e];if(void 0===l)return!1;if("function"==typeof l)r(l,this,t);else{var c=l.length,u=d(l,c);for(n=0;n=0;s--)if(n[s]===t||n[s].listener===t){i=n[s].listener,o=s;break}if(o<0)return this;0===o?n.shift():function(e,t){for(;t+1=0;r--)this.removeListener(e,t[r]);return this},s.prototype.listeners=function(e){return h(this,e,!0)},s.prototype.rawListeners=function(e){return h(this,e,!1)},s.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):f.call(e,t)},s.prototype.listenerCount=f,s.prototype.eventNames=function(){return this._eventsCount>0?t(this._events):[]}},21102:(e,t,n)=>{"use strict";var r=n(46291),o=s(Error);function s(e){return t.displayName=e.displayName||e.name,t;function t(t){return t&&(t=r.apply(null,arguments)),new e(t)}}e.exports=o,o.eval=s(EvalError),o.range=s(RangeError),o.reference=s(ReferenceError),o.syntax=s(SyntaxError),o.type=s(TypeError),o.uri=s(URIError),o.create=s},46291:e=>{!function(){var t;function n(e){for(var t,n,r,o,s=1,i=[].slice.call(arguments),a=0,l=e.length,c="",u=!1,p=!1,h=function(){return i[s++]},f=function(){for(var n="";/\d/.test(e[a]);)n+=e[a++],t=e[a];return n.length>0?parseInt(n):null};a{"use strict";var t=Array.prototype.slice,n=Object.prototype.toString;e.exports=function(e){var r=this;if("function"!=typeof r||"[object Function]"!==n.call(r))throw new TypeError("Function.prototype.bind called on incompatible "+r);for(var o,s=t.call(arguments,1),i=Math.max(0,r.length-s.length),a=[],l=0;l{"use strict";var r=n(17648);e.exports=Function.prototype.bind||r},40210:(e,t,n)=>{"use strict";var r,o=SyntaxError,s=Function,i=TypeError,a=function(e){try{return s('"use strict"; return ('+e+").constructor;")()}catch(e){}},l=Object.getOwnPropertyDescriptor;if(l)try{l({},"")}catch(e){l=null}var c=function(){throw new i},u=l?function(){try{return c}catch(e){try{return l(arguments,"callee").get}catch(e){return c}}}():c,p=n(41405)(),h=n(28185)(),f=Object.getPrototypeOf||(h?function(e){return e.__proto__}:null),d={},m="undefined"!=typeof Uint8Array&&f?f(Uint8Array):r,g={"%AggregateError%":"undefined"==typeof AggregateError?r:AggregateError,"%Array%":Array,"%ArrayBuffer%":"undefined"==typeof ArrayBuffer?r:ArrayBuffer,"%ArrayIteratorPrototype%":p&&f?f([][Symbol.iterator]()):r,"%AsyncFromSyncIteratorPrototype%":r,"%AsyncFunction%":d,"%AsyncGenerator%":d,"%AsyncGeneratorFunction%":d,"%AsyncIteratorPrototype%":d,"%Atomics%":"undefined"==typeof Atomics?r:Atomics,"%BigInt%":"undefined"==typeof BigInt?r:BigInt,"%BigInt64Array%":"undefined"==typeof BigInt64Array?r:BigInt64Array,"%BigUint64Array%":"undefined"==typeof BigUint64Array?r:BigUint64Array,"%Boolean%":Boolean,"%DataView%":"undefined"==typeof DataView?r:DataView,"%Date%":Date,"%decodeURI%":decodeURI,"%decodeURIComponent%":decodeURIComponent,"%encodeURI%":encodeURI,"%encodeURIComponent%":encodeURIComponent,"%Error%":Error,"%eval%":eval,"%EvalError%":EvalError,"%Float32Array%":"undefined"==typeof Float32Array?r:Float32Array,"%Float64Array%":"undefined"==typeof Float64Array?r:Float64Array,"%FinalizationRegistry%":"undefined"==typeof FinalizationRegistry?r:FinalizationRegistry,"%Function%":s,"%GeneratorFunction%":d,"%Int8Array%":"undefined"==typeof Int8Array?r:Int8Array,"%Int16Array%":"undefined"==typeof Int16Array?r:Int16Array,"%Int32Array%":"undefined"==typeof Int32Array?r:Int32Array,"%isFinite%":isFinite,"%isNaN%":isNaN,"%IteratorPrototype%":p&&f?f(f([][Symbol.iterator]())):r,"%JSON%":"object"==typeof JSON?JSON:r,"%Map%":"undefined"==typeof Map?r:Map,"%MapIteratorPrototype%":"undefined"!=typeof Map&&p&&f?f((new Map)[Symbol.iterator]()):r,"%Math%":Math,"%Number%":Number,"%Object%":Object,"%parseFloat%":parseFloat,"%parseInt%":parseInt,"%Promise%":"undefined"==typeof Promise?r:Promise,"%Proxy%":"undefined"==typeof Proxy?r:Proxy,"%RangeError%":RangeError,"%ReferenceError%":ReferenceError,"%Reflect%":"undefined"==typeof Reflect?r:Reflect,"%RegExp%":RegExp,"%Set%":"undefined"==typeof Set?r:Set,"%SetIteratorPrototype%":"undefined"!=typeof Set&&p&&f?f((new Set)[Symbol.iterator]()):r,"%SharedArrayBuffer%":"undefined"==typeof SharedArrayBuffer?r:SharedArrayBuffer,"%String%":String,"%StringIteratorPrototype%":p&&f?f(""[Symbol.iterator]()):r,"%Symbol%":p?Symbol:r,"%SyntaxError%":o,"%ThrowTypeError%":u,"%TypedArray%":m,"%TypeError%":i,"%Uint8Array%":"undefined"==typeof Uint8Array?r:Uint8Array,"%Uint8ClampedArray%":"undefined"==typeof Uint8ClampedArray?r:Uint8ClampedArray,"%Uint16Array%":"undefined"==typeof Uint16Array?r:Uint16Array,"%Uint32Array%":"undefined"==typeof Uint32Array?r:Uint32Array,"%URIError%":URIError,"%WeakMap%":"undefined"==typeof WeakMap?r:WeakMap,"%WeakRef%":"undefined"==typeof WeakRef?r:WeakRef,"%WeakSet%":"undefined"==typeof WeakSet?r:WeakSet};if(f)try{null.error}catch(e){var y=f(f(e));g["%Error.prototype%"]=y}var v=function e(t){var n;if("%AsyncFunction%"===t)n=a("async function () {}");else if("%GeneratorFunction%"===t)n=a("function* () {}");else if("%AsyncGeneratorFunction%"===t)n=a("async function* () {}");else if("%AsyncGenerator%"===t){var r=e("%AsyncGeneratorFunction%");r&&(n=r.prototype)}else if("%AsyncIteratorPrototype%"===t){var o=e("%AsyncGenerator%");o&&f&&(n=f(o.prototype))}return g[t]=n,n},b={"%ArrayBufferPrototype%":["ArrayBuffer","prototype"],"%ArrayPrototype%":["Array","prototype"],"%ArrayProto_entries%":["Array","prototype","entries"],"%ArrayProto_forEach%":["Array","prototype","forEach"],"%ArrayProto_keys%":["Array","prototype","keys"],"%ArrayProto_values%":["Array","prototype","values"],"%AsyncFunctionPrototype%":["AsyncFunction","prototype"],"%AsyncGenerator%":["AsyncGeneratorFunction","prototype"],"%AsyncGeneratorPrototype%":["AsyncGeneratorFunction","prototype","prototype"],"%BooleanPrototype%":["Boolean","prototype"],"%DataViewPrototype%":["DataView","prototype"],"%DatePrototype%":["Date","prototype"],"%ErrorPrototype%":["Error","prototype"],"%EvalErrorPrototype%":["EvalError","prototype"],"%Float32ArrayPrototype%":["Float32Array","prototype"],"%Float64ArrayPrototype%":["Float64Array","prototype"],"%FunctionPrototype%":["Function","prototype"],"%Generator%":["GeneratorFunction","prototype"],"%GeneratorPrototype%":["GeneratorFunction","prototype","prototype"],"%Int8ArrayPrototype%":["Int8Array","prototype"],"%Int16ArrayPrototype%":["Int16Array","prototype"],"%Int32ArrayPrototype%":["Int32Array","prototype"],"%JSONParse%":["JSON","parse"],"%JSONStringify%":["JSON","stringify"],"%MapPrototype%":["Map","prototype"],"%NumberPrototype%":["Number","prototype"],"%ObjectPrototype%":["Object","prototype"],"%ObjProto_toString%":["Object","prototype","toString"],"%ObjProto_valueOf%":["Object","prototype","valueOf"],"%PromisePrototype%":["Promise","prototype"],"%PromiseProto_then%":["Promise","prototype","then"],"%Promise_all%":["Promise","all"],"%Promise_reject%":["Promise","reject"],"%Promise_resolve%":["Promise","resolve"],"%RangeErrorPrototype%":["RangeError","prototype"],"%ReferenceErrorPrototype%":["ReferenceError","prototype"],"%RegExpPrototype%":["RegExp","prototype"],"%SetPrototype%":["Set","prototype"],"%SharedArrayBufferPrototype%":["SharedArrayBuffer","prototype"],"%StringPrototype%":["String","prototype"],"%SymbolPrototype%":["Symbol","prototype"],"%SyntaxErrorPrototype%":["SyntaxError","prototype"],"%TypedArrayPrototype%":["TypedArray","prototype"],"%TypeErrorPrototype%":["TypeError","prototype"],"%Uint8ArrayPrototype%":["Uint8Array","prototype"],"%Uint8ClampedArrayPrototype%":["Uint8ClampedArray","prototype"],"%Uint16ArrayPrototype%":["Uint16Array","prototype"],"%Uint32ArrayPrototype%":["Uint32Array","prototype"],"%URIErrorPrototype%":["URIError","prototype"],"%WeakMapPrototype%":["WeakMap","prototype"],"%WeakSetPrototype%":["WeakSet","prototype"]},w=n(58612),E=n(17642),x=w.call(Function.call,Array.prototype.concat),S=w.call(Function.apply,Array.prototype.splice),_=w.call(Function.call,String.prototype.replace),j=w.call(Function.call,String.prototype.slice),O=w.call(Function.call,RegExp.prototype.exec),k=/[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g,A=/\\(\\)?/g,C=function(e,t){var n,r=e;if(E(b,r)&&(r="%"+(n=b[r])[0]+"%"),E(g,r)){var s=g[r];if(s===d&&(s=v(r)),void 0===s&&!t)throw new i("intrinsic "+e+" exists, but is not available. Please file an issue!");return{alias:n,name:r,value:s}}throw new o("intrinsic "+e+" does not exist!")};e.exports=function(e,t){if("string"!=typeof e||0===e.length)throw new i("intrinsic name must be a non-empty string");if(arguments.length>1&&"boolean"!=typeof t)throw new i('"allowMissing" argument must be a boolean');if(null===O(/^%?[^%]*%?$/,e))throw new o("`%` may not be present anywhere but at the beginning and end of the intrinsic name");var n=function(e){var t=j(e,0,1),n=j(e,-1);if("%"===t&&"%"!==n)throw new o("invalid intrinsic syntax, expected closing `%`");if("%"===n&&"%"!==t)throw new o("invalid intrinsic syntax, expected opening `%`");var r=[];return _(e,k,(function(e,t,n,o){r[r.length]=n?_(o,A,"$1"):t||e})),r}(e),r=n.length>0?n[0]:"",s=C("%"+r+"%",t),a=s.name,c=s.value,u=!1,p=s.alias;p&&(r=p[0],S(n,x([0,1],p)));for(var h=1,f=!0;h=n.length){var v=l(c,d);c=(f=!!v)&&"get"in v&&!("originalValue"in v.get)?v.get:c[d]}else f=E(c,d),c=c[d];f&&!u&&(g[a]=c)}}return c}},28185:e=>{"use strict";var t={foo:{}},n=Object;e.exports=function(){return{__proto__:t}.foo===t.foo&&!({__proto__:null}instanceof n)}},41405:(e,t,n)=>{"use strict";var r="undefined"!=typeof Symbol&&Symbol,o=n(55419);e.exports=function(){return"function"==typeof r&&("function"==typeof Symbol&&("symbol"==typeof r("foo")&&("symbol"==typeof Symbol("bar")&&o())))}},55419:e=>{"use strict";e.exports=function(){if("function"!=typeof Symbol||"function"!=typeof Object.getOwnPropertySymbols)return!1;if("symbol"==typeof Symbol.iterator)return!0;var e={},t=Symbol("test"),n=Object(t);if("string"==typeof t)return!1;if("[object Symbol]"!==Object.prototype.toString.call(t))return!1;if("[object Symbol]"!==Object.prototype.toString.call(n))return!1;for(t in e[t]=42,e)return!1;if("function"==typeof Object.keys&&0!==Object.keys(e).length)return!1;if("function"==typeof Object.getOwnPropertyNames&&0!==Object.getOwnPropertyNames(e).length)return!1;var r=Object.getOwnPropertySymbols(e);if(1!==r.length||r[0]!==t)return!1;if(!Object.prototype.propertyIsEnumerable.call(e,t))return!1;if("function"==typeof Object.getOwnPropertyDescriptor){var o=Object.getOwnPropertyDescriptor(e,t);if(42!==o.value||!0!==o.enumerable)return!1}return!0}},17642:(e,t,n)=>{"use strict";var r=n(58612);e.exports=r.call(Function.call,Object.prototype.hasOwnProperty)},47802:e=>{function t(e){return e instanceof Map?e.clear=e.delete=e.set=function(){throw new Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=function(){throw new Error("set is read-only")}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach((function(n){var r=e[n];"object"!=typeof r||Object.isFrozen(r)||t(r)})),e}var n=t,r=t;n.default=r;class o{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function s(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function i(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t];return t.forEach((function(e){for(const t in e)n[t]=e[t]})),n}const a=e=>!!e.kind;class l{constructor(e,t){this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){this.buffer+=s(e)}openNode(e){if(!a(e))return;let t=e.kind;e.sublanguage||(t=`${this.classPrefix}${t}`),this.span(t)}closeNode(e){a(e)&&(this.buffer+="")}value(){return this.buffer}span(e){this.buffer+=``}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const t={kind:e,children:[]};this.add(t),this.stack.push(t)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t),t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{c._collapse(e)})))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root;n.kind=t,n.sublanguage=!0,this.add(n)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function p(e){return e?"string"==typeof e?e:e.source:null}const h=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;const f="[a-zA-Z]\\w*",d="[a-zA-Z_]\\w*",m="\\b\\d+(\\.\\d+)?",g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",y="\\b(0b[01]+)",v={begin:"\\\\[\\s\\S]",relevance:0},b={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[v]},w={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[v]},E={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},x=function(e,t,n={}){const r=i({className:"comment",begin:e,end:t,contains:[]},n);return r.contains.push(E),r.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),r},S=x("//","$"),_=x("/\\*","\\*/"),j=x("#","$"),O={className:"number",begin:m,relevance:0},k={className:"number",begin:g,relevance:0},A={className:"number",begin:y,relevance:0},C={className:"number",begin:m+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},P={begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[v,{begin:/\[/,end:/\]/,relevance:0,contains:[v]}]}]},N={className:"title",begin:f,relevance:0},I={className:"title",begin:d,relevance:0},T={begin:"\\.\\s*"+d,relevance:0};var R=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:f,UNDERSCORE_IDENT_RE:d,NUMBER_RE:m,C_NUMBER_RE:g,BINARY_NUMBER_RE:y,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const t=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map((e=>p(e))).join("")}(t,/.*\b/,e.binary,/\b.*/)),i({className:"meta",begin:t,end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)},BACKSLASH_ESCAPE:v,APOS_STRING_MODE:b,QUOTE_STRING_MODE:w,PHRASAL_WORDS_MODE:E,COMMENT:x,C_LINE_COMMENT_MODE:S,C_BLOCK_COMMENT_MODE:_,HASH_COMMENT_MODE:j,NUMBER_MODE:O,C_NUMBER_MODE:k,BINARY_NUMBER_MODE:A,CSS_NUMBER_MODE:C,REGEXP_MODE:P,TITLE_MODE:N,UNDERSCORE_TITLE_MODE:I,METHOD_GUARD:T,END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}})}});function M(e,t){"."===e.input[e.index-1]&&t.ignoreMatch()}function D(e,t){t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",e.__beforeBegin=M,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,void 0===e.relevance&&(e.relevance=0))}function F(e,t){Array.isArray(e.illegal)&&(e.illegal=function(...e){return"("+e.map((e=>p(e))).join("|")+")"}(...e.illegal))}function L(e,t){if(e.match){if(e.begin||e.end)throw new Error("begin & end are not supported with match");e.begin=e.match,delete e.match}}function B(e,t){void 0===e.relevance&&(e.relevance=1)}const $=["of","and","for","in","not","or","if","then","parent","list","value"],q="keyword";function U(e,t,n=q){const r={};return"string"==typeof e?o(n,e.split(" ")):Array.isArray(e)?o(n,e):Object.keys(e).forEach((function(n){Object.assign(r,U(e[n],t,n))})),r;function o(e,n){t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((function(t){const n=t.split("|");r[n[0]]=[e,z(n[0],n[1])]}))}}function z(e,t){return t?Number(t):function(e){return $.includes(e.toLowerCase())}(e)?0:1}function V(e,{plugins:t}){function n(t,n){return new RegExp(p(t),"m"+(e.case_insensitive?"i":"")+(n?"g":""))}class r{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,t){t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),this.matchAt+=function(e){return new RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map((e=>e[1]));this.matcherRe=n(function(e,t="|"){let n=0;return e.map((e=>{n+=1;const t=n;let r=p(e),o="";for(;r.length>0;){const e=h.exec(r);if(!e){o+=r;break}o+=r.substring(0,e.index),r=r.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?o+="\\"+String(Number(e[1])+t):(o+=e[0],"("===e[0]&&n++)}return o})).map((e=>`(${e})`)).join(t)}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const t=this.matcherRe.exec(e);if(!t)return null;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),r=this.matchIndexes[n];return t.splice(0,n),Object.assign(t,r)}}class o{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const t=new r;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))),t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex;let n=t.exec(e);if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}return n&&(this.regexIndex+=n.position+1,this.regexIndex===this.count&&this.considerAll()),n}}if(e.compilerExtensions||(e.compilerExtensions=[]),e.contains&&e.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return e.classNameAliases=i(e.classNameAliases||{}),function t(r,s){const a=r;if(r.isCompiled)return a;[L].forEach((e=>e(r,s))),e.compilerExtensions.forEach((e=>e(r,s))),r.__beforeBegin=null,[D,F,B].forEach((e=>e(r,s))),r.isCompiled=!0;let l=null;if("object"==typeof r.keywords&&(l=r.keywords.$pattern,delete r.keywords.$pattern),r.keywords&&(r.keywords=U(r.keywords,e.case_insensitive)),r.lexemes&&l)throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l=l||r.lexemes||/\w+/,a.keywordPatternRe=n(l,!0),s&&(r.begin||(r.begin=/\B|\b/),a.beginRe=n(r.begin),r.endSameAsBegin&&(r.end=r.begin),r.end||r.endsWithParent||(r.end=/\B|\b/),r.end&&(a.endRe=n(r.end)),a.terminatorEnd=p(r.end)||"",r.endsWithParent&&s.terminatorEnd&&(a.terminatorEnd+=(r.end?"|":"")+s.terminatorEnd)),r.illegal&&(a.illegalRe=n(r.illegal)),r.contains||(r.contains=[]),r.contains=[].concat(...r.contains.map((function(e){return function(e){e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((function(t){return i(e,{variants:null},t)})));if(e.cachedVariants)return e.cachedVariants;if(W(e))return i(e,{starts:e.starts?i(e.starts):null});if(Object.isFrozen(e))return i(e);return e}("self"===e?r:e)}))),r.contains.forEach((function(e){t(e,a)})),r.starts&&t(r.starts,s),a.matcher=function(e){const t=new o;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin"}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end"}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t}(a),a}(e)}function W(e){return!!e&&(e.endsWithParent||W(e.starts))}function J(e){const t={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!e.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,s(this.code);let t={};return this.autoDetect?(t=e.highlightAuto(this.code),this.detectedLanguage=t.language):(t=e.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),t.value},autoDetect(){return!this.language||(e=this.autodetect,Boolean(e||""===e));var e},ignoreIllegals:()=>!0},render(e){return e("pre",{},[e("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{Component:t,VuePlugin:{install(e){e.component("highlightjs",t)}}}}const K={"after:highlightElement":({el:e,result:t,text:n})=>{const r=G(e);if(!r.length)return;const o=document.createElement("div");o.innerHTML=t.value,t.value=function(e,t,n){let r=0,o="";const i=[];function a(){return e.length&&t.length?e[0].offset!==t[0].offset?e[0].offset"}function c(e){o+=""}function u(e){("start"===e.event?l:c)(e.node)}for(;e.length||t.length;){let t=a();if(o+=s(n.substring(r,t[0].offset)),r=t[0].offset,t===e){i.reverse().forEach(c);do{u(t.splice(0,1)[0]),t=a()}while(t===e&&t.length&&t[0].offset===r);i.reverse().forEach(l)}else"start"===t[0].event?i.push(t[0].node):i.pop(),u(t.splice(0,1)[0])}return o+s(n.substr(r))}(r,G(o),n)}};function H(e){return e.nodeName.toLowerCase()}function G(e){const t=[];return function e(n,r){for(let o=n.firstChild;o;o=o.nextSibling)3===o.nodeType?r+=o.nodeValue.length:1===o.nodeType&&(t.push({event:"start",offset:r,node:o}),r=e(o,r),H(o).match(/br|hr|img|input/)||t.push({event:"stop",offset:r,node:o}));return r}(e,0),t}const Z={},Y=e=>{console.error(e)},X=(e,...t)=>{console.log(`WARN: ${e}`,...t)},Q=(e,t)=>{Z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),Z[`${e}/${t}`]=!0)},ee=s,te=i,ne=Symbol("nomatch");var re=function(e){const t=Object.create(null),r=Object.create(null),s=[];let i=!0;const a=/(^(<[^>]+>|\t|)+|\n)/gm,l="Could not find the language '{}', did you forget to load/include a language module?",c={disableAutodetect:!0,name:"Plain text",contains:[]};let p={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function h(e){return p.noHighlightRe.test(e)}function f(e,t,n,r){let o="",s="";"object"==typeof t?(o=e,n=t.ignoreIllegals,s=t.language,r=void 0):(Q("10.7.0","highlight(lang, code, ...args) has been deprecated."),Q("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),s=e,o=t);const i={code:o,language:s};O("before:highlight",i);const a=i.result?i.result:d(i.language,i.code,n,r);return a.code=i.code,O("after:highlight",a),a}function d(e,n,r,a){function c(e,t){const n=E.case_insensitive?t[0].toLowerCase():t[0];return Object.prototype.hasOwnProperty.call(e.keywords,n)&&e.keywords[n]}function u(){null!=j.subLanguage?function(){if(""===A)return;let e=null;if("string"==typeof j.subLanguage){if(!t[j.subLanguage])return void k.addText(A);e=d(j.subLanguage,A,!0,O[j.subLanguage]),O[j.subLanguage]=e.top}else e=m(A,j.subLanguage.length?j.subLanguage:null);j.relevance>0&&(C+=e.relevance),k.addSublanguage(e.emitter,e.language)}():function(){if(!j.keywords)return void k.addText(A);let e=0;j.keywordPatternRe.lastIndex=0;let t=j.keywordPatternRe.exec(A),n="";for(;t;){n+=A.substring(e,t.index);const r=c(j,t);if(r){const[e,o]=r;if(k.addText(n),n="",C+=o,e.startsWith("_"))n+=t[0];else{const n=E.classNameAliases[e]||e;k.addKeyword(t[0],n)}}else n+=t[0];e=j.keywordPatternRe.lastIndex,t=j.keywordPatternRe.exec(A)}n+=A.substr(e),k.addText(n)}(),A=""}function h(e){return e.className&&k.openNode(E.classNameAliases[e.className]||e.className),j=Object.create(e,{parent:{value:j}}),j}function f(e,t,n){let r=function(e,t){const n=e&&e.exec(t);return n&&0===n.index}(e.endRe,n);if(r){if(e["on:end"]){const n=new o(e);e["on:end"](t,n),n.isMatchIgnored&&(r=!1)}if(r){for(;e.endsParent&&e.parent;)e=e.parent;return e}}if(e.endsWithParent)return f(e.parent,t,n)}function g(e){return 0===j.matcher.regexIndex?(A+=e[0],1):(I=!0,0)}function y(e){const t=e[0],n=e.rule,r=new o(n),s=[n.__beforeBegin,n["on:begin"]];for(const n of s)if(n&&(n(e,r),r.isMatchIgnored))return g(t);return n&&n.endSameAsBegin&&(n.endRe=new RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),n.skip?A+=t:(n.excludeBegin&&(A+=t),u(),n.returnBegin||n.excludeBegin||(A=t)),h(n),n.returnBegin?0:t.length}function v(e){const t=e[0],r=n.substr(e.index),o=f(j,e,r);if(!o)return ne;const s=j;s.skip?A+=t:(s.returnEnd||s.excludeEnd||(A+=t),u(),s.excludeEnd&&(A=t));do{j.className&&k.closeNode(),j.skip||j.subLanguage||(C+=j.relevance),j=j.parent}while(j!==o.parent);return o.starts&&(o.endSameAsBegin&&(o.starts.endRe=o.endRe),h(o.starts)),s.returnEnd?0:t.length}let b={};function w(t,o){const s=o&&o[0];if(A+=t,null==s)return u(),0;if("begin"===b.type&&"end"===o.type&&b.index===o.index&&""===s){if(A+=n.slice(o.index,o.index+1),!i){const t=new Error("0 width match regex");throw t.languageName=e,t.badRule=b.rule,t}return 1}if(b=o,"begin"===o.type)return y(o);if("illegal"===o.type&&!r){const e=new Error('Illegal lexeme "'+s+'" for mode "'+(j.className||"")+'"');throw e.mode=j,e}if("end"===o.type){const e=v(o);if(e!==ne)return e}if("illegal"===o.type&&""===s)return 1;if(N>1e5&&N>3*o.index){throw new Error("potential infinite loop, way more iterations than matches")}return A+=s,s.length}const E=S(e);if(!E)throw Y(l.replace("{}",e)),new Error('Unknown language: "'+e+'"');const x=V(E,{plugins:s});let _="",j=a||x;const O={},k=new p.__emitter(p);!function(){const e=[];for(let t=j;t!==E;t=t.parent)t.className&&e.unshift(t.className);e.forEach((e=>k.openNode(e)))}();let A="",C=0,P=0,N=0,I=!1;try{for(j.matcher.considerAll();;){N++,I?I=!1:j.matcher.considerAll(),j.matcher.lastIndex=P;const e=j.matcher.exec(n);if(!e)break;const t=w(n.substring(P,e.index),e);P=e.index+t}return w(n.substr(P)),k.closeAllNodes(),k.finalize(),_=k.toHTML(),{relevance:Math.floor(C),value:_,language:e,illegal:!1,emitter:k,top:j}}catch(t){if(t.message&&t.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:t.message,context:n.slice(P-100,P+100),mode:t.mode},sofar:_,relevance:0,value:ee(n),emitter:k};if(i)return{illegal:!1,relevance:0,value:ee(n),emitter:k,language:e,top:j,errorRaised:t};throw t}}function m(e,n){n=n||p.languages||Object.keys(t);const r=function(e){const t={relevance:0,emitter:new p.__emitter(p),value:ee(e),illegal:!1,top:c};return t.emitter.addText(e),t}(e),o=n.filter(S).filter(j).map((t=>d(t,e,!1)));o.unshift(r);const s=o.sort(((e,t)=>{if(e.relevance!==t.relevance)return t.relevance-e.relevance;if(e.language&&t.language){if(S(e.language).supersetOf===t.language)return 1;if(S(t.language).supersetOf===e.language)return-1}return 0})),[i,a]=s,l=i;return l.second_best=a,l}const g={"before:highlightElement":({el:e})=>{p.useBR&&(e.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"))},"after:highlightElement":({result:e})=>{p.useBR&&(e.value=e.value.replace(/\n/g,"
"))}},y=/^(<[^>]+>|\t)+/gm,v={"after:highlightElement":({result:e})=>{p.tabReplace&&(e.value=e.value.replace(y,(e=>e.replace(/\t/g,p.tabReplace))))}};function b(e){let t=null;const n=function(e){let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"";const n=p.languageDetectRe.exec(t);if(n){const t=S(n[1]);return t||(X(l.replace("{}",n[1])),X("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"}return t.split(/\s+/).find((e=>h(e)||S(e)))}(e);if(h(n))return;O("before:highlightElement",{el:e,language:n}),t=e;const o=t.textContent,s=n?f(o,{language:n,ignoreIllegals:!0}):m(o);O("after:highlightElement",{el:e,result:s,text:o}),e.innerHTML=s.value,function(e,t,n){const o=t?r[t]:n;e.classList.add("hljs"),o&&e.classList.add(o)}(e,n,s.language),e.result={language:s.language,re:s.relevance,relavance:s.relevance},s.second_best&&(e.second_best={language:s.second_best.language,re:s.second_best.relevance,relavance:s.second_best.relevance})}const w=()=>{if(w.called)return;w.called=!0,Q("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead.");document.querySelectorAll("pre code").forEach(b)};let E=!1;function x(){if("loading"===document.readyState)return void(E=!0);document.querySelectorAll("pre code").forEach(b)}function S(e){return e=(e||"").toLowerCase(),t[e]||t[r[e]]}function _(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{r[e.toLowerCase()]=t}))}function j(e){const t=S(e);return t&&!t.disableAutodetect}function O(e,t){const n=e;s.forEach((function(e){e[n]&&e[n](t)}))}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(function(){E&&x()}),!1),Object.assign(e,{highlight:f,highlightAuto:m,highlightAll:x,fixMarkup:function(e){return Q("10.2.0","fixMarkup will be removed entirely in v11.0"),Q("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),t=e,p.tabReplace||p.useBR?t.replace(a,(e=>"\n"===e?p.useBR?"
":e:p.tabReplace?e.replace(/\t/g,p.tabReplace):e)):t;var t},highlightElement:b,highlightBlock:function(e){return Q("10.7.0","highlightBlock will be removed entirely in v12.0"),Q("10.7.0","Please use highlightElement now."),b(e)},configure:function(e){e.useBR&&(Q("10.3.0","'useBR' will be removed entirely in v11.0"),Q("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),p=te(p,e)},initHighlighting:w,initHighlightingOnLoad:function(){Q("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),E=!0},registerLanguage:function(n,r){let o=null;try{o=r(e)}catch(e){if(Y("Language definition for '{}' could not be registered.".replace("{}",n)),!i)throw e;Y(e),o=c}o.name||(o.name=n),t[n]=o,o.rawDefinition=r.bind(null,e),o.aliases&&_(o.aliases,{languageName:n})},unregisterLanguage:function(e){delete t[e];for(const t of Object.keys(r))r[t]===e&&delete r[t]},listLanguages:function(){return Object.keys(t)},getLanguage:S,registerAliases:_,requireLanguage:function(e){Q("10.4.0","requireLanguage will be removed entirely in v11."),Q("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844");const t=S(e);if(t)return t;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:j,inherit:te,addPlugin:function(e){!function(e){e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{e["before:highlightBlock"](Object.assign({block:t.el},t))}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{e["after:highlightBlock"](Object.assign({block:t.el},t))})}(e),s.push(e)},vuePlugin:J(e).VuePlugin}),e.debugMode=function(){i=!1},e.safeMode=function(){i=!0},e.versionString="10.7.3";for(const e in R)"object"==typeof R[e]&&n(R[e]);return Object.assign(e,R),e.addPlugin(g),e.addPlugin(K),e.addPlugin(v),e}({});e.exports=re},61519:e=>{function t(...e){return e.map((e=>{return(t=e)?"string"==typeof t?t:t.source:null;var t})).join("")}e.exports=function(e){const n={},r={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[n]}]};Object.assign(n,{className:"variable",variants:[{begin:t(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},r]});const o={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},s={begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},i={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,n,o]};o.contains.push(i);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,n]},l=e.SHEBANG({binary:`(${["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"].join("|")})`,relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[l,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,s,i,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},n]}}},30786:e=>{function t(...e){return e.map((e=>{return(t=e)?"string"==typeof t?t:t.source:null;var t})).join("")}e.exports=function(e){const n="HTTP/(2|1\\.[01])",r={className:"attribute",begin:t("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},o=[r,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+n+" \\d{3})",end:/$/,contains:[{className:"meta",begin:n},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:o}},{begin:"(?=^[A-Z]+ (.*?) "+n+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:n},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:o}},e.inherit(r,{relevance:0})]}}},96344:e=>{const t="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],r=["true","false","null","undefined","NaN","Infinity"],o=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return i("(?=",e,")")}function i(...e){return e.map((e=>{return(t=e)?"string"==typeof t?t:t.source:null;var t})).join("")}e.exports=function(e){const a=t,l="<>",c="",u={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,t)=>{const n=e[0].length+e.index,r=e.input[n];"<"!==r?">"===r&&(((e,{after:t})=>{const n="",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:p,contains:S}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:l,end:c},{begin:u.begin,"on:begin":u.isTrulyOpeningTag,end:u.end}],subLanguage:"xml",contains:[{begin:u.begin,end:u.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:p,contains:["self",e.inherit(e.TITLE_MODE,{begin:a}),_],illegal:/%/},{beginKeywords:"while if switch catch for"},{className:"function",begin:e.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,contains:[_,e.inherit(e.TITLE_MODE,{begin:a})]},{variants:[{begin:"\\."+a},{begin:"\\$"+a}],relevance:0},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{beginKeywords:"extends"},e.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,end:/[{;]/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:a}),"self",_]},{begin:"(get|set)\\s+(?="+a+"\\()",end:/\{/,keywords:"get set",contains:[e.inherit(e.TITLE_MODE,{begin:a}),{begin:/\(\)/},_]},{begin:/\$[(.]/}]}}},82026:e=>{e.exports=function(e){const t={literal:"true false null"},n=[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],r=[e.QUOTE_STRING_MODE,e.C_NUMBER_MODE],o={end:",",endsWithParent:!0,excludeEnd:!0,contains:r,keywords:t},s={begin:/\{/,end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE],illegal:"\\n"},e.inherit(o,{begin:/:/})].concat(n),illegal:"\\S"},i={begin:"\\[",end:"\\]",contains:[e.inherit(o)],illegal:"\\S"};return r.push(s,i),n.forEach((function(e){r.push(e)})),{name:"JSON",contains:r,keywords:t,illegal:"\\S"}}},66336:e=>{e.exports=function(e){const t={$pattern:/-?[A-z\.\-]+\b/,keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"},n={begin:"`[\\s\\S]",relevance:0},r={className:"variable",variants:[{begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]},o={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],contains:[n,r,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},s={className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]},i=e.inherit(e.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,end:/#>/}],contains:[{className:"doctag",variants:[{begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),a={className:"built_in",variants:[{begin:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},l={className:"class",beginKeywords:"class enum",end:/\s*[{]/,excludeEnd:!0,relevance:0,contains:[e.TITLE_MODE]},c={className:"function",begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,className:"params",relevance:0,contains:[r]}]},u={begin:/using\s/,end:/$/,returnBegin:!0,contains:[o,s,{className:"keyword",begin:/(using|assembly|command|module|namespace|type)/}]},p={variants:[{className:"operator",begin:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},h={className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,relevance:0,contains:[{className:"keyword",begin:"(".concat(t.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},e.inherit(e.TITLE_MODE,{endsParent:!0})]},f=[h,i,n,e.NUMBER_MODE,o,s,a,r,{className:"literal",begin:/\$(null|true|false)\b/},{className:"selector-tag",begin:/@\B/,relevance:0}],d={begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",f,{begin:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,relevance:0})};return h.contains.unshift(d),{name:"PowerShell",aliases:["ps","ps1"],case_insensitive:!0,keywords:t,contains:f.concat(l,c,u,p,d)}}},42157:e=>{function t(e){return e?"string"==typeof e?e:e.source:null}function n(e){return r("(?=",e,")")}function r(...e){return e.map((e=>t(e))).join("")}function o(...e){return"("+e.map((e=>t(e))).join("|")+")"}e.exports=function(e){const t=r(/[A-Z_]/,r("(",/[A-Z0-9_.-]*:/,")?"),/[A-Z0-9_.-]*/),s={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},i={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},a=e.inherit(i,{begin:/\(/,end:/\)/}),l=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),c=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),u={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[i,c,l,a,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[i,a,c,l]}]}]},e.COMMENT(//,{relevance:10}),{begin://,relevance:10},s,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[u],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[u],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:r(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:t,relevance:0,starts:u}]},{className:"tag",begin:r(/<\//,n(r(t,/>/))),contains:[{className:"name",begin:t,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}},54587:e=>{e.exports=function(e){var t="true false yes no null",n="[\\w#;/?:@&=+$,.~*'()[\\]]+",r={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},o=e.inherit(r,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),s={className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},i={end:",",endsWithParent:!0,excludeEnd:!0,keywords:t,relevance:0},a={begin:/\{/,end:/\}/,contains:[i],illegal:"\\n",relevance:0},l={begin:"\\[",end:"\\]",contains:[i],illegal:"\\n",relevance:0},c=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+n},{className:"type",begin:"!<"+n+">"},{className:"type",begin:"!"+n},{className:"type",begin:"!!"+n},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:t,keywords:{literal:t}},s,{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},a,l,r],u=[...c];return u.pop(),u.push(o),i.contains=u,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:c}}},8679:(e,t,n)=>{"use strict";var r=n(59864),o={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},s={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},a={};function l(e){return r.isMemo(e)?i:a[e.$$typeof]||o}a[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},a[r.Memo]=i;var c=Object.defineProperty,u=Object.getOwnPropertyNames,p=Object.getOwnPropertySymbols,h=Object.getOwnPropertyDescriptor,f=Object.getPrototypeOf,d=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(d){var o=f(n);o&&o!==d&&e(t,o,r)}var i=u(n);p&&(i=i.concat(p(n)));for(var a=l(t),m=l(n),g=0;g{t.read=function(e,t,n,r,o){var s,i,a=8*o-r-1,l=(1<>1,u=-7,p=n?o-1:0,h=n?-1:1,f=e[t+p];for(p+=h,s=f&(1<<-u)-1,f>>=-u,u+=a;u>0;s=256*s+e[t+p],p+=h,u-=8);for(i=s&(1<<-u)-1,s>>=-u,u+=r;u>0;i=256*i+e[t+p],p+=h,u-=8);if(0===s)s=1-c;else{if(s===l)return i?NaN:1/0*(f?-1:1);i+=Math.pow(2,r),s-=c}return(f?-1:1)*i*Math.pow(2,s-r)},t.write=function(e,t,n,r,o,s){var i,a,l,c=8*s-o-1,u=(1<>1,h=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,f=r?0:s-1,d=r?1:-1,m=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,i=u):(i=Math.floor(Math.log(t)/Math.LN2),t*(l=Math.pow(2,-i))<1&&(i--,l*=2),(t+=i+p>=1?h/l:h*Math.pow(2,1-p))*l>=2&&(i++,l/=2),i+p>=u?(a=0,i=u):i+p>=1?(a=(t*l-1)*Math.pow(2,o),i+=p):(a=t*Math.pow(2,p-1)*Math.pow(2,o),i=0));o>=8;e[n+f]=255&a,f+=d,a/=256,o-=8);for(i=i<0;e[n+f]=255&i,f+=d,i/=256,c-=8);e[n+f-d]|=128*m}},43393:function(e){e.exports=function(){"use strict";var e=Array.prototype.slice;function t(e,t){t&&(e.prototype=Object.create(t.prototype)),e.prototype.constructor=e}function n(e){return i(e)?e:K(e)}function r(e){return a(e)?e:H(e)}function o(e){return l(e)?e:G(e)}function s(e){return i(e)&&!c(e)?e:Z(e)}function i(e){return!(!e||!e[p])}function a(e){return!(!e||!e[h])}function l(e){return!(!e||!e[f])}function c(e){return a(e)||l(e)}function u(e){return!(!e||!e[d])}t(r,n),t(o,n),t(s,n),n.isIterable=i,n.isKeyed=a,n.isIndexed=l,n.isAssociative=c,n.isOrdered=u,n.Keyed=r,n.Indexed=o,n.Set=s;var p="@@__IMMUTABLE_ITERABLE__@@",h="@@__IMMUTABLE_KEYED__@@",f="@@__IMMUTABLE_INDEXED__@@",d="@@__IMMUTABLE_ORDERED__@@",m="delete",g=5,y=1<>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?O(e)+t:t}function A(){return!0}function C(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function P(e,t){return I(e,t,0)}function N(e,t){return I(e,t,t)}function I(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var T=0,R=1,M=2,D="function"==typeof Symbol&&Symbol.iterator,F="@@iterator",L=D||F;function B(e){this.next=e}function $(e,t,n,r){var o=0===e?t:1===e?n:[t,n];return r?r.value=o:r={value:o,done:!1},r}function q(){return{value:void 0,done:!0}}function U(e){return!!W(e)}function z(e){return e&&"function"==typeof e.next}function V(e){var t=W(e);return t&&t.call(e)}function W(e){var t=e&&(D&&e[D]||e[F]);if("function"==typeof t)return t}function J(e){return e&&"number"==typeof e.length}function K(e){return null==e?ie():i(e)?e.toSeq():ce(e)}function H(e){return null==e?ie().toKeyedSeq():i(e)?a(e)?e.toSeq():e.fromEntrySeq():ae(e)}function G(e){return null==e?ie():i(e)?a(e)?e.entrySeq():e.toIndexedSeq():le(e)}function Z(e){return(null==e?ie():i(e)?a(e)?e.entrySeq():e:le(e)).toSetSeq()}B.prototype.toString=function(){return"[Iterator]"},B.KEYS=T,B.VALUES=R,B.ENTRIES=M,B.prototype.inspect=B.prototype.toSource=function(){return this.toString()},B.prototype[L]=function(){return this},t(K,n),K.of=function(){return K(arguments)},K.prototype.toSeq=function(){return this},K.prototype.toString=function(){return this.__toString("Seq {","}")},K.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},K.prototype.__iterate=function(e,t){return pe(this,e,t,!0)},K.prototype.__iterator=function(e,t){return he(this,e,t,!0)},t(H,K),H.prototype.toKeyedSeq=function(){return this},t(G,K),G.of=function(){return G(arguments)},G.prototype.toIndexedSeq=function(){return this},G.prototype.toString=function(){return this.__toString("Seq [","]")},G.prototype.__iterate=function(e,t){return pe(this,e,t,!1)},G.prototype.__iterator=function(e,t){return he(this,e,t,!1)},t(Z,K),Z.of=function(){return Z(arguments)},Z.prototype.toSetSeq=function(){return this},K.isSeq=se,K.Keyed=H,K.Set=Z,K.Indexed=G;var Y,X,Q,ee="@@__IMMUTABLE_SEQ__@@";function te(e){this._array=e,this.size=e.length}function ne(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function re(e){this._iterable=e,this.size=e.length||e.size}function oe(e){this._iterator=e,this._iteratorCache=[]}function se(e){return!(!e||!e[ee])}function ie(){return Y||(Y=new te([]))}function ae(e){var t=Array.isArray(e)?new te(e).fromEntrySeq():z(e)?new oe(e).fromEntrySeq():U(e)?new re(e).fromEntrySeq():"object"==typeof e?new ne(e):void 0;if(!t)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e);return t}function le(e){var t=ue(e);if(!t)throw new TypeError("Expected Array or iterable object of values: "+e);return t}function ce(e){var t=ue(e)||"object"==typeof e&&new ne(e);if(!t)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+e);return t}function ue(e){return J(e)?new te(e):z(e)?new oe(e):U(e)?new re(e):void 0}function pe(e,t,n,r){var o=e._cache;if(o){for(var s=o.length-1,i=0;i<=s;i++){var a=o[n?s-i:i];if(!1===t(a[1],r?a[0]:i,e))return i+1}return i}return e.__iterateUncached(t,n)}function he(e,t,n,r){var o=e._cache;if(o){var s=o.length-1,i=0;return new B((function(){var e=o[n?s-i:i];return i++>s?q():$(t,r?e[0]:i-1,e[1])}))}return e.__iteratorUncached(t,n)}function fe(e,t){return t?de(t,e,"",{"":e}):me(e)}function de(e,t,n,r){return Array.isArray(t)?e.call(r,n,G(t).map((function(n,r){return de(e,n,r,t)}))):ge(t)?e.call(r,n,H(t).map((function(n,r){return de(e,n,r,t)}))):t}function me(e){return Array.isArray(e)?G(e).map(me).toList():ge(e)?H(e).map(me).toMap():e}function ge(e){return e&&(e.constructor===Object||void 0===e.constructor)}function ye(e,t){if(e===t||e!=e&&t!=t)return!0;if(!e||!t)return!1;if("function"==typeof e.valueOf&&"function"==typeof t.valueOf){if((e=e.valueOf())===(t=t.valueOf())||e!=e&&t!=t)return!0;if(!e||!t)return!1}return!("function"!=typeof e.equals||"function"!=typeof t.equals||!e.equals(t))}function ve(e,t){if(e===t)return!0;if(!i(t)||void 0!==e.size&&void 0!==t.size&&e.size!==t.size||void 0!==e.__hash&&void 0!==t.__hash&&e.__hash!==t.__hash||a(e)!==a(t)||l(e)!==l(t)||u(e)!==u(t))return!1;if(0===e.size&&0===t.size)return!0;var n=!c(e);if(u(e)){var r=e.entries();return t.every((function(e,t){var o=r.next().value;return o&&ye(o[1],e)&&(n||ye(o[0],t))}))&&r.next().done}var o=!1;if(void 0===e.size)if(void 0===t.size)"function"==typeof e.cacheResult&&e.cacheResult();else{o=!0;var s=e;e=t,t=s}var p=!0,h=t.__iterate((function(t,r){if(n?!e.has(t):o?!ye(t,e.get(r,b)):!ye(e.get(r,b),t))return p=!1,!1}));return p&&e.size===h}function be(e,t){if(!(this instanceof be))return new be(e,t);if(this._value=e,this.size=void 0===t?1/0:Math.max(0,t),0===this.size){if(X)return X;X=this}}function we(e,t){if(!e)throw new Error(t)}function Ee(e,t,n){if(!(this instanceof Ee))return new Ee(e,t,n);if(we(0!==n,"Cannot step a Range by 0"),e=e||0,void 0===t&&(t=1/0),n=void 0===n?1:Math.abs(n),tr?q():$(e,o,n[t?r-o++:o++])}))},t(ne,H),ne.prototype.get=function(e,t){return void 0===t||this.has(e)?this._object[e]:t},ne.prototype.has=function(e){return this._object.hasOwnProperty(e)},ne.prototype.__iterate=function(e,t){for(var n=this._object,r=this._keys,o=r.length-1,s=0;s<=o;s++){var i=r[t?o-s:s];if(!1===e(n[i],i,this))return s+1}return s},ne.prototype.__iterator=function(e,t){var n=this._object,r=this._keys,o=r.length-1,s=0;return new B((function(){var i=r[t?o-s:s];return s++>o?q():$(e,i,n[i])}))},ne.prototype[d]=!0,t(re,G),re.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);var n=V(this._iterable),r=0;if(z(n))for(var o;!(o=n.next()).done&&!1!==e(o.value,r++,this););return r},re.prototype.__iteratorUncached=function(e,t){if(t)return this.cacheResult().__iterator(e,t);var n=V(this._iterable);if(!z(n))return new B(q);var r=0;return new B((function(){var t=n.next();return t.done?t:$(e,r++,t.value)}))},t(oe,G),oe.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);for(var n,r=this._iterator,o=this._iteratorCache,s=0;s=r.length){var t=n.next();if(t.done)return t;r[o]=t.value}return $(e,o,r[o++])}))},t(be,G),be.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},be.prototype.get=function(e,t){return this.has(e)?this._value:t},be.prototype.includes=function(e){return ye(this._value,e)},be.prototype.slice=function(e,t){var n=this.size;return C(e,t,n)?this:new be(this._value,N(t,n)-P(e,n))},be.prototype.reverse=function(){return this},be.prototype.indexOf=function(e){return ye(this._value,e)?0:-1},be.prototype.lastIndexOf=function(e){return ye(this._value,e)?this.size:-1},be.prototype.__iterate=function(e,t){for(var n=0;n=0&&t=0&&nn?q():$(e,s++,i)}))},Ee.prototype.equals=function(e){return e instanceof Ee?this._start===e._start&&this._end===e._end&&this._step===e._step:ve(this,e)},t(xe,n),t(Se,xe),t(_e,xe),t(je,xe),xe.Keyed=Se,xe.Indexed=_e,xe.Set=je;var Oe="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function(e,t){var n=65535&(e|=0),r=65535&(t|=0);return n*r+((e>>>16)*r+n*(t>>>16)<<16>>>0)|0};function ke(e){return e>>>1&1073741824|3221225471&e}function Ae(e){if(!1===e||null==e)return 0;if("function"==typeof e.valueOf&&(!1===(e=e.valueOf())||null==e))return 0;if(!0===e)return 1;var t=typeof e;if("number"===t){if(e!=e||e===1/0)return 0;var n=0|e;for(n!==e&&(n^=4294967295*e);e>4294967295;)n^=e/=4294967295;return ke(n)}if("string"===t)return e.length>Be?Ce(e):Pe(e);if("function"==typeof e.hashCode)return e.hashCode();if("object"===t)return Ne(e);if("function"==typeof e.toString)return Pe(e.toString());throw new Error("Value type "+t+" cannot be hashed.")}function Ce(e){var t=Ue[e];return void 0===t&&(t=Pe(e),qe===$e&&(qe=0,Ue={}),qe++,Ue[e]=t),t}function Pe(e){for(var t=0,n=0;n0)switch(e.nodeType){case 1:return e.uniqueID;case 9:return e.documentElement&&e.documentElement.uniqueID}}var Me,De="function"==typeof WeakMap;De&&(Me=new WeakMap);var Fe=0,Le="__immutablehash__";"function"==typeof Symbol&&(Le=Symbol(Le));var Be=16,$e=255,qe=0,Ue={};function ze(e){we(e!==1/0,"Cannot perform this action with an infinite size.")}function Ve(e){return null==e?ot():We(e)&&!u(e)?e:ot().withMutations((function(t){var n=r(e);ze(n.size),n.forEach((function(e,n){return t.set(n,e)}))}))}function We(e){return!(!e||!e[Ke])}t(Ve,Se),Ve.of=function(){var t=e.call(arguments,0);return ot().withMutations((function(e){for(var n=0;n=t.length)throw new Error("Missing value for key: "+t[n]);e.set(t[n],t[n+1])}}))},Ve.prototype.toString=function(){return this.__toString("Map {","}")},Ve.prototype.get=function(e,t){return this._root?this._root.get(0,void 0,e,t):t},Ve.prototype.set=function(e,t){return st(this,e,t)},Ve.prototype.setIn=function(e,t){return this.updateIn(e,b,(function(){return t}))},Ve.prototype.remove=function(e){return st(this,e,b)},Ve.prototype.deleteIn=function(e){return this.updateIn(e,(function(){return b}))},Ve.prototype.update=function(e,t,n){return 1===arguments.length?e(this):this.updateIn([e],t,n)},Ve.prototype.updateIn=function(e,t,n){n||(n=t,t=void 0);var r=gt(this,xn(e),t,n);return r===b?void 0:r},Ve.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):ot()},Ve.prototype.merge=function(){return ht(this,void 0,arguments)},Ve.prototype.mergeWith=function(t){return ht(this,t,e.call(arguments,1))},Ve.prototype.mergeIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,ot(),(function(e){return"function"==typeof e.merge?e.merge.apply(e,n):n[n.length-1]}))},Ve.prototype.mergeDeep=function(){return ht(this,ft,arguments)},Ve.prototype.mergeDeepWith=function(t){var n=e.call(arguments,1);return ht(this,dt(t),n)},Ve.prototype.mergeDeepIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,ot(),(function(e){return"function"==typeof e.mergeDeep?e.mergeDeep.apply(e,n):n[n.length-1]}))},Ve.prototype.sort=function(e){return Ut(pn(this,e))},Ve.prototype.sortBy=function(e,t){return Ut(pn(this,t,e))},Ve.prototype.withMutations=function(e){var t=this.asMutable();return e(t),t.wasAltered()?t.__ensureOwner(this.__ownerID):this},Ve.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new _)},Ve.prototype.asImmutable=function(){return this.__ensureOwner()},Ve.prototype.wasAltered=function(){return this.__altered},Ve.prototype.__iterator=function(e,t){return new et(this,e,t)},Ve.prototype.__iterate=function(e,t){var n=this,r=0;return this._root&&this._root.iterate((function(t){return r++,e(t[1],t[0],n)}),t),r},Ve.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?rt(this.size,this._root,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},Ve.isMap=We;var Je,Ke="@@__IMMUTABLE_MAP__@@",He=Ve.prototype;function Ge(e,t){this.ownerID=e,this.entries=t}function Ze(e,t,n){this.ownerID=e,this.bitmap=t,this.nodes=n}function Ye(e,t,n){this.ownerID=e,this.count=t,this.nodes=n}function Xe(e,t,n){this.ownerID=e,this.keyHash=t,this.entries=n}function Qe(e,t,n){this.ownerID=e,this.keyHash=t,this.entry=n}function et(e,t,n){this._type=t,this._reverse=n,this._stack=e._root&&nt(e._root)}function tt(e,t){return $(e,t[0],t[1])}function nt(e,t){return{node:e,index:0,__prev:t}}function rt(e,t,n,r){var o=Object.create(He);return o.size=e,o._root=t,o.__ownerID=n,o.__hash=r,o.__altered=!1,o}function ot(){return Je||(Je=rt(0))}function st(e,t,n){var r,o;if(e._root){var s=x(w),i=x(E);if(r=it(e._root,e.__ownerID,0,void 0,t,n,s,i),!i.value)return e;o=e.size+(s.value?n===b?-1:1:0)}else{if(n===b)return e;o=1,r=new Ge(e.__ownerID,[[t,n]])}return e.__ownerID?(e.size=o,e._root=r,e.__hash=void 0,e.__altered=!0,e):r?rt(o,r):ot()}function it(e,t,n,r,o,s,i,a){return e?e.update(t,n,r,o,s,i,a):s===b?e:(S(a),S(i),new Qe(t,r,[o,s]))}function at(e){return e.constructor===Qe||e.constructor===Xe}function lt(e,t,n,r,o){if(e.keyHash===r)return new Xe(t,r,[e.entry,o]);var s,i=(0===n?e.keyHash:e.keyHash>>>n)&v,a=(0===n?r:r>>>n)&v;return new Ze(t,1<>>=1)i[a]=1&n?t[s++]:void 0;return i[r]=o,new Ye(e,s+1,i)}function ht(e,t,n){for(var o=[],s=0;s>1&1431655765))+(e>>2&858993459))+(e>>4)&252645135,e+=e>>8,127&(e+=e>>16)}function vt(e,t,n,r){var o=r?e:j(e);return o[t]=n,o}function bt(e,t,n,r){var o=e.length+1;if(r&&t+1===o)return e[t]=n,e;for(var s=new Array(o),i=0,a=0;a=Et)return ct(e,l,r,o);var h=e&&e===this.ownerID,f=h?l:j(l);return p?a?c===u-1?f.pop():f[c]=f.pop():f[c]=[r,o]:f.push([r,o]),h?(this.entries=f,this):new Ge(e,f)}},Ze.prototype.get=function(e,t,n,r){void 0===t&&(t=Ae(n));var o=1<<((0===e?t:t>>>e)&v),s=this.bitmap;return 0==(s&o)?r:this.nodes[yt(s&o-1)].get(e+g,t,n,r)},Ze.prototype.update=function(e,t,n,r,o,s,i){void 0===n&&(n=Ae(r));var a=(0===t?n:n>>>t)&v,l=1<=xt)return pt(e,h,c,a,d);if(u&&!d&&2===h.length&&at(h[1^p]))return h[1^p];if(u&&d&&1===h.length&&at(d))return d;var m=e&&e===this.ownerID,y=u?d?c:c^l:c|l,w=u?d?vt(h,p,d,m):wt(h,p,m):bt(h,p,d,m);return m?(this.bitmap=y,this.nodes=w,this):new Ze(e,y,w)},Ye.prototype.get=function(e,t,n,r){void 0===t&&(t=Ae(n));var o=(0===e?t:t>>>e)&v,s=this.nodes[o];return s?s.get(e+g,t,n,r):r},Ye.prototype.update=function(e,t,n,r,o,s,i){void 0===n&&(n=Ae(r));var a=(0===t?n:n>>>t)&v,l=o===b,c=this.nodes,u=c[a];if(l&&!u)return this;var p=it(u,e,t+g,n,r,o,s,i);if(p===u)return this;var h=this.count;if(u){if(!p&&--h0&&r=0&&e>>t&v;if(r>=this.array.length)return new At([],e);var o,s=0===r;if(t>0){var i=this.array[r];if((o=i&&i.removeBefore(e,t-g,n))===i&&s)return this}if(s&&!o)return this;var a=Ft(this,e);if(!s)for(var l=0;l>>t&v;if(o>=this.array.length)return this;if(t>0){var s=this.array[o];if((r=s&&s.removeAfter(e,t-g,n))===s&&o===this.array.length-1)return this}var i=Ft(this,e);return i.array.splice(o+1),r&&(i.array[o]=r),i};var Ct,Pt,Nt={};function It(e,t){var n=e._origin,r=e._capacity,o=qt(r),s=e._tail;return i(e._root,e._level,0);function i(e,t,n){return 0===t?a(e,n):l(e,t,n)}function a(e,i){var a=i===o?s&&s.array:e&&e.array,l=i>n?0:n-i,c=r-i;return c>y&&(c=y),function(){if(l===c)return Nt;var e=t?--c:l++;return a&&a[e]}}function l(e,o,s){var a,l=e&&e.array,c=s>n?0:n-s>>o,u=1+(r-s>>o);return u>y&&(u=y),function(){for(;;){if(a){var e=a();if(e!==Nt)return e;a=null}if(c===u)return Nt;var n=t?--u:c++;a=i(l&&l[n],o-g,s+(n<=e.size||t<0)return e.withMutations((function(e){t<0?Bt(e,t).set(0,n):Bt(e,0,t+1).set(t,n)}));t+=e._origin;var r=e._tail,o=e._root,s=x(E);return t>=qt(e._capacity)?r=Dt(r,e.__ownerID,0,t,n,s):o=Dt(o,e.__ownerID,e._level,t,n,s),s.value?e.__ownerID?(e._root=o,e._tail=r,e.__hash=void 0,e.__altered=!0,e):Tt(e._origin,e._capacity,e._level,o,r):e}function Dt(e,t,n,r,o,s){var i,a=r>>>n&v,l=e&&a0){var c=e&&e.array[a],u=Dt(c,t,n-g,r,o,s);return u===c?e:((i=Ft(e,t)).array[a]=u,i)}return l&&e.array[a]===o?e:(S(s),i=Ft(e,t),void 0===o&&a===i.array.length-1?i.array.pop():i.array[a]=o,i)}function Ft(e,t){return t&&e&&t===e.ownerID?e:new At(e?e.array.slice():[],t)}function Lt(e,t){if(t>=qt(e._capacity))return e._tail;if(t<1<0;)n=n.array[t>>>r&v],r-=g;return n}}function Bt(e,t,n){void 0!==t&&(t|=0),void 0!==n&&(n|=0);var r=e.__ownerID||new _,o=e._origin,s=e._capacity,i=o+t,a=void 0===n?s:n<0?s+n:o+n;if(i===o&&a===s)return e;if(i>=a)return e.clear();for(var l=e._level,c=e._root,u=0;i+u<0;)c=new At(c&&c.array.length?[void 0,c]:[],r),u+=1<<(l+=g);u&&(i+=u,o+=u,a+=u,s+=u);for(var p=qt(s),h=qt(a);h>=1<p?new At([],r):f;if(f&&h>p&&ig;y-=g){var b=p>>>y&v;m=m.array[b]=Ft(m.array[b],r)}m.array[p>>>g&v]=f}if(a=h)i-=h,a-=h,l=g,c=null,d=d&&d.removeBefore(r,0,i);else if(i>o||h>>l&v;if(w!==h>>>l&v)break;w&&(u+=(1<o&&(c=c.removeBefore(r,l,i-u)),c&&hs&&(s=c.size),i(l)||(c=c.map((function(e){return fe(e)}))),r.push(c)}return s>e.size&&(e=e.setSize(s)),mt(e,t,r)}function qt(e){return e>>g<=y&&i.size>=2*s.size?(r=(o=i.filter((function(e,t){return void 0!==e&&a!==t}))).toKeyedSeq().map((function(e){return e[0]})).flip().toMap(),e.__ownerID&&(r.__ownerID=o.__ownerID=e.__ownerID)):(r=s.remove(t),o=a===i.size-1?i.pop():i.set(a,void 0))}else if(l){if(n===i.get(a)[1])return e;r=s,o=i.set(a,[t,n])}else r=s.set(t,i.size),o=i.set(i.size,[t,n]);return e.__ownerID?(e.size=r.size,e._map=r,e._list=o,e.__hash=void 0,e):Vt(r,o)}function Kt(e,t){this._iter=e,this._useKeys=t,this.size=e.size}function Ht(e){this._iter=e,this.size=e.size}function Gt(e){this._iter=e,this.size=e.size}function Zt(e){this._iter=e,this.size=e.size}function Yt(e){var t=bn(e);return t._iter=e,t.size=e.size,t.flip=function(){return e},t.reverse=function(){var t=e.reverse.apply(this);return t.flip=function(){return e.reverse()},t},t.has=function(t){return e.includes(t)},t.includes=function(t){return e.has(t)},t.cacheResult=wn,t.__iterateUncached=function(t,n){var r=this;return e.__iterate((function(e,n){return!1!==t(n,e,r)}),n)},t.__iteratorUncached=function(t,n){if(t===M){var r=e.__iterator(t,n);return new B((function(){var e=r.next();if(!e.done){var t=e.value[0];e.value[0]=e.value[1],e.value[1]=t}return e}))}return e.__iterator(t===R?T:R,n)},t}function Xt(e,t,n){var r=bn(e);return r.size=e.size,r.has=function(t){return e.has(t)},r.get=function(r,o){var s=e.get(r,b);return s===b?o:t.call(n,s,r,e)},r.__iterateUncached=function(r,o){var s=this;return e.__iterate((function(e,o,i){return!1!==r(t.call(n,e,o,i),o,s)}),o)},r.__iteratorUncached=function(r,o){var s=e.__iterator(M,o);return new B((function(){var o=s.next();if(o.done)return o;var i=o.value,a=i[0];return $(r,a,t.call(n,i[1],a,e),o)}))},r}function Qt(e,t){var n=bn(e);return n._iter=e,n.size=e.size,n.reverse=function(){return e},e.flip&&(n.flip=function(){var t=Yt(e);return t.reverse=function(){return e.flip()},t}),n.get=function(n,r){return e.get(t?n:-1-n,r)},n.has=function(n){return e.has(t?n:-1-n)},n.includes=function(t){return e.includes(t)},n.cacheResult=wn,n.__iterate=function(t,n){var r=this;return e.__iterate((function(e,n){return t(e,n,r)}),!n)},n.__iterator=function(t,n){return e.__iterator(t,!n)},n}function en(e,t,n,r){var o=bn(e);return r&&(o.has=function(r){var o=e.get(r,b);return o!==b&&!!t.call(n,o,r,e)},o.get=function(r,o){var s=e.get(r,b);return s!==b&&t.call(n,s,r,e)?s:o}),o.__iterateUncached=function(o,s){var i=this,a=0;return e.__iterate((function(e,s,l){if(t.call(n,e,s,l))return a++,o(e,r?s:a-1,i)}),s),a},o.__iteratorUncached=function(o,s){var i=e.__iterator(M,s),a=0;return new B((function(){for(;;){var s=i.next();if(s.done)return s;var l=s.value,c=l[0],u=l[1];if(t.call(n,u,c,e))return $(o,r?c:a++,u,s)}}))},o}function tn(e,t,n){var r=Ve().asMutable();return e.__iterate((function(o,s){r.update(t.call(n,o,s,e),0,(function(e){return e+1}))})),r.asImmutable()}function nn(e,t,n){var r=a(e),o=(u(e)?Ut():Ve()).asMutable();e.__iterate((function(s,i){o.update(t.call(n,s,i,e),(function(e){return(e=e||[]).push(r?[i,s]:s),e}))}));var s=vn(e);return o.map((function(t){return mn(e,s(t))}))}function rn(e,t,n,r){var o=e.size;if(void 0!==t&&(t|=0),void 0!==n&&(n===1/0?n=o:n|=0),C(t,n,o))return e;var s=P(t,o),i=N(n,o);if(s!=s||i!=i)return rn(e.toSeq().cacheResult(),t,n,r);var a,l=i-s;l==l&&(a=l<0?0:l);var c=bn(e);return c.size=0===a?a:e.size&&a||void 0,!r&&se(e)&&a>=0&&(c.get=function(t,n){return(t=k(this,t))>=0&&ta)return q();var e=o.next();return r||t===R?e:$(t,l-1,t===T?void 0:e.value[1],e)}))},c}function on(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var s=this;if(o)return this.cacheResult().__iterate(r,o);var i=0;return e.__iterate((function(e,o,a){return t.call(n,e,o,a)&&++i&&r(e,o,s)})),i},r.__iteratorUncached=function(r,o){var s=this;if(o)return this.cacheResult().__iterator(r,o);var i=e.__iterator(M,o),a=!0;return new B((function(){if(!a)return q();var e=i.next();if(e.done)return e;var o=e.value,l=o[0],c=o[1];return t.call(n,c,l,s)?r===M?e:$(r,l,c,e):(a=!1,q())}))},r}function sn(e,t,n,r){var o=bn(e);return o.__iterateUncached=function(o,s){var i=this;if(s)return this.cacheResult().__iterate(o,s);var a=!0,l=0;return e.__iterate((function(e,s,c){if(!a||!(a=t.call(n,e,s,c)))return l++,o(e,r?s:l-1,i)})),l},o.__iteratorUncached=function(o,s){var i=this;if(s)return this.cacheResult().__iterator(o,s);var a=e.__iterator(M,s),l=!0,c=0;return new B((function(){var e,s,u;do{if((e=a.next()).done)return r||o===R?e:$(o,c++,o===T?void 0:e.value[1],e);var p=e.value;s=p[0],u=p[1],l&&(l=t.call(n,u,s,i))}while(l);return o===M?e:$(o,s,u,e)}))},o}function an(e,t){var n=a(e),o=[e].concat(t).map((function(e){return i(e)?n&&(e=r(e)):e=n?ae(e):le(Array.isArray(e)?e:[e]),e})).filter((function(e){return 0!==e.size}));if(0===o.length)return e;if(1===o.length){var s=o[0];if(s===e||n&&a(s)||l(e)&&l(s))return s}var c=new te(o);return n?c=c.toKeyedSeq():l(e)||(c=c.toSetSeq()),(c=c.flatten(!0)).size=o.reduce((function(e,t){if(void 0!==e){var n=t.size;if(void 0!==n)return e+n}}),0),c}function ln(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var s=0,a=!1;function l(e,c){var u=this;e.__iterate((function(e,o){return(!t||c0}function dn(e,t,r){var o=bn(e);return o.size=new te(r).map((function(e){return e.size})).min(),o.__iterate=function(e,t){for(var n,r=this.__iterator(R,t),o=0;!(n=r.next()).done&&!1!==e(n.value,o++,this););return o},o.__iteratorUncached=function(e,o){var s=r.map((function(e){return e=n(e),V(o?e.reverse():e)})),i=0,a=!1;return new B((function(){var n;return a||(n=s.map((function(e){return e.next()})),a=n.some((function(e){return e.done}))),a?q():$(e,i++,t.apply(null,n.map((function(e){return e.value}))))}))},o}function mn(e,t){return se(e)?t:e.constructor(t)}function gn(e){if(e!==Object(e))throw new TypeError("Expected [K, V] tuple: "+e)}function yn(e){return ze(e.size),O(e)}function vn(e){return a(e)?r:l(e)?o:s}function bn(e){return Object.create((a(e)?H:l(e)?G:Z).prototype)}function wn(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):K.prototype.cacheResult.call(this)}function En(e,t){return e>t?1:e=0;n--)t={value:arguments[n],next:t};return this.__ownerID?(this.size=e,this._head=t,this.__hash=void 0,this.__altered=!0,this):Hn(e,t)},zn.prototype.pushAll=function(e){if(0===(e=o(e)).size)return this;ze(e.size);var t=this.size,n=this._head;return e.reverse().forEach((function(e){t++,n={value:e,next:n}})),this.__ownerID?(this.size=t,this._head=n,this.__hash=void 0,this.__altered=!0,this):Hn(t,n)},zn.prototype.pop=function(){return this.slice(1)},zn.prototype.unshift=function(){return this.push.apply(this,arguments)},zn.prototype.unshiftAll=function(e){return this.pushAll(e)},zn.prototype.shift=function(){return this.pop.apply(this,arguments)},zn.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):Gn()},zn.prototype.slice=function(e,t){if(C(e,t,this.size))return this;var n=P(e,this.size);if(N(t,this.size)!==this.size)return _e.prototype.slice.call(this,e,t);for(var r=this.size-n,o=this._head;n--;)o=o.next;return this.__ownerID?(this.size=r,this._head=o,this.__hash=void 0,this.__altered=!0,this):Hn(r,o)},zn.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?Hn(this.size,this._head,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},zn.prototype.__iterate=function(e,t){if(t)return this.reverse().__iterate(e);for(var n=0,r=this._head;r&&!1!==e(r.value,n++,this);)r=r.next;return n},zn.prototype.__iterator=function(e,t){if(t)return this.reverse().__iterator(e);var n=0,r=this._head;return new B((function(){if(r){var t=r.value;return r=r.next,$(e,n++,t)}return q()}))},zn.isStack=Vn;var Wn,Jn="@@__IMMUTABLE_STACK__@@",Kn=zn.prototype;function Hn(e,t,n,r){var o=Object.create(Kn);return o.size=e,o._head=t,o.__ownerID=n,o.__hash=r,o.__altered=!1,o}function Gn(){return Wn||(Wn=Hn(0))}function Zn(e,t){var n=function(n){e.prototype[n]=t[n]};return Object.keys(t).forEach(n),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(t).forEach(n),e}Kn[Jn]=!0,Kn.withMutations=He.withMutations,Kn.asMutable=He.asMutable,Kn.asImmutable=He.asImmutable,Kn.wasAltered=He.wasAltered,n.Iterator=B,Zn(n,{toArray:function(){ze(this.size);var e=new Array(this.size||0);return this.valueSeq().__iterate((function(t,n){e[n]=t})),e},toIndexedSeq:function(){return new Ht(this)},toJS:function(){return this.toSeq().map((function(e){return e&&"function"==typeof e.toJS?e.toJS():e})).__toJS()},toJSON:function(){return this.toSeq().map((function(e){return e&&"function"==typeof e.toJSON?e.toJSON():e})).__toJS()},toKeyedSeq:function(){return new Kt(this,!0)},toMap:function(){return Ve(this.toKeyedSeq())},toObject:function(){ze(this.size);var e={};return this.__iterate((function(t,n){e[n]=t})),e},toOrderedMap:function(){return Ut(this.toKeyedSeq())},toOrderedSet:function(){return Fn(a(this)?this.valueSeq():this)},toSet:function(){return Cn(a(this)?this.valueSeq():this)},toSetSeq:function(){return new Gt(this)},toSeq:function(){return l(this)?this.toIndexedSeq():a(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return zn(a(this)?this.valueSeq():this)},toList:function(){return _t(a(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(e,t){return 0===this.size?e+t:e+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+t},concat:function(){return mn(this,an(this,e.call(arguments,0)))},includes:function(e){return this.some((function(t){return ye(t,e)}))},entries:function(){return this.__iterator(M)},every:function(e,t){ze(this.size);var n=!0;return this.__iterate((function(r,o,s){if(!e.call(t,r,o,s))return n=!1,!1})),n},filter:function(e,t){return mn(this,en(this,e,t,!0))},find:function(e,t,n){var r=this.findEntry(e,t);return r?r[1]:n},forEach:function(e,t){return ze(this.size),this.__iterate(t?e.bind(t):e)},join:function(e){ze(this.size),e=void 0!==e?""+e:",";var t="",n=!0;return this.__iterate((function(r){n?n=!1:t+=e,t+=null!=r?r.toString():""})),t},keys:function(){return this.__iterator(T)},map:function(e,t){return mn(this,Xt(this,e,t))},reduce:function(e,t,n){var r,o;return ze(this.size),arguments.length<2?o=!0:r=t,this.__iterate((function(t,s,i){o?(o=!1,r=t):r=e.call(n,r,t,s,i)})),r},reduceRight:function(e,t,n){var r=this.toKeyedSeq().reverse();return r.reduce.apply(r,arguments)},reverse:function(){return mn(this,Qt(this,!0))},slice:function(e,t){return mn(this,rn(this,e,t,!0))},some:function(e,t){return!this.every(tr(e),t)},sort:function(e){return mn(this,pn(this,e))},values:function(){return this.__iterator(R)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function(e,t){return O(e?this.toSeq().filter(e,t):this)},countBy:function(e,t){return tn(this,e,t)},equals:function(e){return ve(this,e)},entrySeq:function(){var e=this;if(e._cache)return new te(e._cache);var t=e.toSeq().map(er).toIndexedSeq();return t.fromEntrySeq=function(){return e.toSeq()},t},filterNot:function(e,t){return this.filter(tr(e),t)},findEntry:function(e,t,n){var r=n;return this.__iterate((function(n,o,s){if(e.call(t,n,o,s))return r=[o,n],!1})),r},findKey:function(e,t){var n=this.findEntry(e,t);return n&&n[0]},findLast:function(e,t,n){return this.toKeyedSeq().reverse().find(e,t,n)},findLastEntry:function(e,t,n){return this.toKeyedSeq().reverse().findEntry(e,t,n)},findLastKey:function(e,t){return this.toKeyedSeq().reverse().findKey(e,t)},first:function(){return this.find(A)},flatMap:function(e,t){return mn(this,cn(this,e,t))},flatten:function(e){return mn(this,ln(this,e,!0))},fromEntrySeq:function(){return new Zt(this)},get:function(e,t){return this.find((function(t,n){return ye(n,e)}),void 0,t)},getIn:function(e,t){for(var n,r=this,o=xn(e);!(n=o.next()).done;){var s=n.value;if((r=r&&r.get?r.get(s,b):b)===b)return t}return r},groupBy:function(e,t){return nn(this,e,t)},has:function(e){return this.get(e,b)!==b},hasIn:function(e){return this.getIn(e,b)!==b},isSubset:function(e){return e="function"==typeof e.includes?e:n(e),this.every((function(t){return e.includes(t)}))},isSuperset:function(e){return(e="function"==typeof e.isSubset?e:n(e)).isSubset(this)},keyOf:function(e){return this.findKey((function(t){return ye(t,e)}))},keySeq:function(){return this.toSeq().map(Qn).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(e){return this.toKeyedSeq().reverse().keyOf(e)},max:function(e){return hn(this,e)},maxBy:function(e,t){return hn(this,t,e)},min:function(e){return hn(this,e?nr(e):sr)},minBy:function(e,t){return hn(this,t?nr(t):sr,e)},rest:function(){return this.slice(1)},skip:function(e){return this.slice(Math.max(0,e))},skipLast:function(e){return mn(this,this.toSeq().reverse().skip(e).reverse())},skipWhile:function(e,t){return mn(this,sn(this,e,t,!0))},skipUntil:function(e,t){return this.skipWhile(tr(e),t)},sortBy:function(e,t){return mn(this,pn(this,t,e))},take:function(e){return this.slice(0,Math.max(0,e))},takeLast:function(e){return mn(this,this.toSeq().reverse().take(e).reverse())},takeWhile:function(e,t){return mn(this,on(this,e,t))},takeUntil:function(e,t){return this.takeWhile(tr(e),t)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=ir(this))}});var Yn=n.prototype;Yn[p]=!0,Yn[L]=Yn.values,Yn.__toJS=Yn.toArray,Yn.__toStringMapper=rr,Yn.inspect=Yn.toSource=function(){return this.toString()},Yn.chain=Yn.flatMap,Yn.contains=Yn.includes,Zn(r,{flip:function(){return mn(this,Yt(this))},mapEntries:function(e,t){var n=this,r=0;return mn(this,this.toSeq().map((function(o,s){return e.call(t,[s,o],r++,n)})).fromEntrySeq())},mapKeys:function(e,t){var n=this;return mn(this,this.toSeq().flip().map((function(r,o){return e.call(t,r,o,n)})).flip())}});var Xn=r.prototype;function Qn(e,t){return t}function er(e,t){return[t,e]}function tr(e){return function(){return!e.apply(this,arguments)}}function nr(e){return function(){return-e.apply(this,arguments)}}function rr(e){return"string"==typeof e?JSON.stringify(e):String(e)}function or(){return j(arguments)}function sr(e,t){return et?-1:0}function ir(e){if(e.size===1/0)return 0;var t=u(e),n=a(e),r=t?1:0;return ar(e.__iterate(n?t?function(e,t){r=31*r+lr(Ae(e),Ae(t))|0}:function(e,t){r=r+lr(Ae(e),Ae(t))|0}:t?function(e){r=31*r+Ae(e)|0}:function(e){r=r+Ae(e)|0}),r)}function ar(e,t){return t=Oe(t,3432918353),t=Oe(t<<15|t>>>-15,461845907),t=Oe(t<<13|t>>>-13,5),t=Oe((t=(t+3864292196|0)^e)^t>>>16,2246822507),t=ke((t=Oe(t^t>>>13,3266489909))^t>>>16)}function lr(e,t){return e^t+2654435769+(e<<6)+(e>>2)|0}return Xn[h]=!0,Xn[L]=Yn.entries,Xn.__toJS=Yn.toObject,Xn.__toStringMapper=function(e,t){return JSON.stringify(t)+": "+rr(e)},Zn(o,{toKeyedSeq:function(){return new Kt(this,!1)},filter:function(e,t){return mn(this,en(this,e,t,!1))},findIndex:function(e,t){var n=this.findEntry(e,t);return n?n[0]:-1},indexOf:function(e){var t=this.keyOf(e);return void 0===t?-1:t},lastIndexOf:function(e){var t=this.lastKeyOf(e);return void 0===t?-1:t},reverse:function(){return mn(this,Qt(this,!1))},slice:function(e,t){return mn(this,rn(this,e,t,!1))},splice:function(e,t){var n=arguments.length;if(t=Math.max(0|t,0),0===n||2===n&&!t)return this;e=P(e,e<0?this.count():this.size);var r=this.slice(0,e);return mn(this,1===n?r:r.concat(j(arguments,2),this.slice(e+t)))},findLastIndex:function(e,t){var n=this.findLastEntry(e,t);return n?n[0]:-1},first:function(){return this.get(0)},flatten:function(e){return mn(this,ln(this,e,!1))},get:function(e,t){return(e=k(this,e))<0||this.size===1/0||void 0!==this.size&&e>this.size?t:this.find((function(t,n){return n===e}),void 0,t)},has:function(e){return(e=k(this,e))>=0&&(void 0!==this.size?this.size===1/0||e{"function"==typeof Object.create?e.exports=function(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:e.exports=function(e,t){if(t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}}},35823:e=>{e.exports=function(e,t,n,r){var o=new Blob(void 0!==r?[r,e]:[e],{type:n||"application/octet-stream"});if(void 0!==window.navigator.msSaveBlob)window.navigator.msSaveBlob(o,t);else{var s=window.URL&&window.URL.createObjectURL?window.URL.createObjectURL(o):window.webkitURL.createObjectURL(o),i=document.createElement("a");i.style.display="none",i.href=s,i.setAttribute("download",t),void 0===i.download&&i.setAttribute("target","_blank"),document.body.appendChild(i),i.click(),setTimeout((function(){document.body.removeChild(i),window.URL.revokeObjectURL(s)}),200)}}},91296:(e,t,n)=>{var r=NaN,o="[object Symbol]",s=/^\s+|\s+$/g,i=/^[-+]0x[0-9a-f]+$/i,a=/^0b[01]+$/i,l=/^0o[0-7]+$/i,c=parseInt,u="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g,p="object"==typeof self&&self&&self.Object===Object&&self,h=u||p||Function("return this")(),f=Object.prototype.toString,d=Math.max,m=Math.min,g=function(){return h.Date.now()};function y(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function v(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&f.call(e)==o}(e))return r;if(y(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=y(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(s,"");var n=a.test(e);return n||l.test(e)?c(e.slice(2),n?2:8):i.test(e)?r:+e}e.exports=function(e,t,n){var r,o,s,i,a,l,c=0,u=!1,p=!1,h=!0;if("function"!=typeof e)throw new TypeError("Expected a function");function f(t){var n=r,s=o;return r=o=void 0,c=t,i=e.apply(s,n)}function b(e){var n=e-l;return void 0===l||n>=t||n<0||p&&e-c>=s}function w(){var e=g();if(b(e))return E(e);a=setTimeout(w,function(e){var n=t-(e-l);return p?m(n,s-(e-c)):n}(e))}function E(e){return a=void 0,h&&r?f(e):(r=o=void 0,i)}function x(){var e=g(),n=b(e);if(r=arguments,o=this,l=e,n){if(void 0===a)return function(e){return c=e,a=setTimeout(w,t),u?f(e):i}(l);if(p)return a=setTimeout(w,t),f(l)}return void 0===a&&(a=setTimeout(w,t)),i}return t=v(t)||0,y(n)&&(u=!!n.leading,s=(p="maxWait"in n)?d(v(n.maxWait)||0,t):s,h="trailing"in n?!!n.trailing:h),x.cancel=function(){void 0!==a&&clearTimeout(a),c=0,r=l=o=a=void 0},x.flush=function(){return void 0===a?i:E(g())},x}},18552:(e,t,n)=>{var r=n(10852)(n(55639),"DataView");e.exports=r},1989:(e,t,n)=>{var r=n(51789),o=n(80401),s=n(57667),i=n(21327),a=n(81866);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{var r=n(3118),o=n(9435);function s(e){this.__wrapped__=e,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}s.prototype=r(o.prototype),s.prototype.constructor=s,e.exports=s},38407:(e,t,n)=>{var r=n(27040),o=n(14125),s=n(82117),i=n(67518),a=n(54705);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{var r=n(3118),o=n(9435);function s(e,t){this.__wrapped__=e,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=void 0}s.prototype=r(o.prototype),s.prototype.constructor=s,e.exports=s},57071:(e,t,n)=>{var r=n(10852)(n(55639),"Map");e.exports=r},83369:(e,t,n)=>{var r=n(24785),o=n(11285),s=n(96e3),i=n(49916),a=n(95265);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{var r=n(10852)(n(55639),"Promise");e.exports=r},58525:(e,t,n)=>{var r=n(10852)(n(55639),"Set");e.exports=r},88668:(e,t,n)=>{var r=n(83369),o=n(90619),s=n(72385);function i(e){var t=-1,n=null==e?0:e.length;for(this.__data__=new r;++t{var r=n(38407),o=n(37465),s=n(63779),i=n(67599),a=n(44758),l=n(34309);function c(e){var t=this.__data__=new r(e);this.size=t.size}c.prototype.clear=o,c.prototype.delete=s,c.prototype.get=i,c.prototype.has=a,c.prototype.set=l,e.exports=c},62705:(e,t,n)=>{var r=n(55639).Symbol;e.exports=r},11149:(e,t,n)=>{var r=n(55639).Uint8Array;e.exports=r},70577:(e,t,n)=>{var r=n(10852)(n(55639),"WeakMap");e.exports=r},96874:e=>{e.exports=function(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}},77412:e=>{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length;++n{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=0,s=[];++n{var r=n(42118);e.exports=function(e,t){return!!(null==e?0:e.length)&&r(e,t,0)>-1}},14636:(e,t,n)=>{var r=n(22545),o=n(35694),s=n(1469),i=n(44144),a=n(65776),l=n(36719),c=Object.prototype.hasOwnProperty;e.exports=function(e,t){var n=s(e),u=!n&&o(e),p=!n&&!u&&i(e),h=!n&&!u&&!p&&l(e),f=n||u||p||h,d=f?r(e.length,String):[],m=d.length;for(var g in e)!t&&!c.call(e,g)||f&&("length"==g||p&&("offset"==g||"parent"==g)||h&&("buffer"==g||"byteLength"==g||"byteOffset"==g)||a(g,m))||d.push(g);return d}},29932:e=>{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=Array(r);++n{e.exports=function(e,t){for(var n=-1,r=t.length,o=e.length;++n{e.exports=function(e,t,n,r){var o=-1,s=null==e?0:e.length;for(r&&s&&(n=e[++o]);++o{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length;++n{e.exports=function(e){return e.split("")}},49029:e=>{var t=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;e.exports=function(e){return e.match(t)||[]}},86556:(e,t,n)=>{var r=n(89465),o=n(77813);e.exports=function(e,t,n){(void 0!==n&&!o(e[t],n)||void 0===n&&!(t in e))&&r(e,t,n)}},34865:(e,t,n)=>{var r=n(89465),o=n(77813),s=Object.prototype.hasOwnProperty;e.exports=function(e,t,n){var i=e[t];s.call(e,t)&&o(i,n)&&(void 0!==n||t in e)||r(e,t,n)}},18470:(e,t,n)=>{var r=n(77813);e.exports=function(e,t){for(var n=e.length;n--;)if(r(e[n][0],t))return n;return-1}},44037:(e,t,n)=>{var r=n(98363),o=n(3674);e.exports=function(e,t){return e&&r(t,o(t),e)}},63886:(e,t,n)=>{var r=n(98363),o=n(81704);e.exports=function(e,t){return e&&r(t,o(t),e)}},89465:(e,t,n)=>{var r=n(38777);e.exports=function(e,t,n){"__proto__"==t&&r?r(e,t,{configurable:!0,enumerable:!0,value:n,writable:!0}):e[t]=n}},85990:(e,t,n)=>{var r=n(46384),o=n(77412),s=n(34865),i=n(44037),a=n(63886),l=n(64626),c=n(278),u=n(18805),p=n(1911),h=n(58234),f=n(46904),d=n(98882),m=n(43824),g=n(29148),y=n(38517),v=n(1469),b=n(44144),w=n(56688),E=n(13218),x=n(72928),S=n(3674),_=n(81704),j="[object Arguments]",O="[object Function]",k="[object Object]",A={};A[j]=A["[object Array]"]=A["[object ArrayBuffer]"]=A["[object DataView]"]=A["[object Boolean]"]=A["[object Date]"]=A["[object Float32Array]"]=A["[object Float64Array]"]=A["[object Int8Array]"]=A["[object Int16Array]"]=A["[object Int32Array]"]=A["[object Map]"]=A["[object Number]"]=A[k]=A["[object RegExp]"]=A["[object Set]"]=A["[object String]"]=A["[object Symbol]"]=A["[object Uint8Array]"]=A["[object Uint8ClampedArray]"]=A["[object Uint16Array]"]=A["[object Uint32Array]"]=!0,A["[object Error]"]=A[O]=A["[object WeakMap]"]=!1,e.exports=function e(t,n,C,P,N,I){var T,R=1&n,M=2&n,D=4&n;if(C&&(T=N?C(t,P,N,I):C(t)),void 0!==T)return T;if(!E(t))return t;var F=v(t);if(F){if(T=m(t),!R)return c(t,T)}else{var L=d(t),B=L==O||"[object GeneratorFunction]"==L;if(b(t))return l(t,R);if(L==k||L==j||B&&!N){if(T=M||B?{}:y(t),!R)return M?p(t,a(T,t)):u(t,i(T,t))}else{if(!A[L])return N?t:{};T=g(t,L,R)}}I||(I=new r);var $=I.get(t);if($)return $;I.set(t,T),x(t)?t.forEach((function(r){T.add(e(r,n,C,r,t,I))})):w(t)&&t.forEach((function(r,o){T.set(o,e(r,n,C,o,t,I))}));var q=F?void 0:(D?M?f:h:M?_:S)(t);return o(q||t,(function(r,o){q&&(r=t[o=r]),s(T,o,e(r,n,C,o,t,I))})),T}},3118:(e,t,n)=>{var r=n(13218),o=Object.create,s=function(){function e(){}return function(t){if(!r(t))return{};if(o)return o(t);e.prototype=t;var n=new e;return e.prototype=void 0,n}}();e.exports=s},89881:(e,t,n)=>{var r=n(47816),o=n(99291)(r);e.exports=o},41848:e=>{e.exports=function(e,t,n,r){for(var o=e.length,s=n+(r?1:-1);r?s--:++s{var r=n(62488),o=n(37285);e.exports=function e(t,n,s,i,a){var l=-1,c=t.length;for(s||(s=o),a||(a=[]);++l0&&s(u)?n>1?e(u,n-1,s,i,a):r(a,u):i||(a[a.length]=u)}return a}},28483:(e,t,n)=>{var r=n(25063)();e.exports=r},47816:(e,t,n)=>{var r=n(28483),o=n(3674);e.exports=function(e,t){return e&&r(e,t,o)}},97786:(e,t,n)=>{var r=n(71811),o=n(40327);e.exports=function(e,t){for(var n=0,s=(t=r(t,e)).length;null!=e&&n{var r=n(62488),o=n(1469);e.exports=function(e,t,n){var s=t(e);return o(e)?s:r(s,n(e))}},44239:(e,t,n)=>{var r=n(62705),o=n(89607),s=n(2333),i=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":i&&i in Object(e)?o(e):s(e)}},13:e=>{e.exports=function(e,t){return null!=e&&t in Object(e)}},42118:(e,t,n)=>{var r=n(41848),o=n(62722),s=n(42351);e.exports=function(e,t,n){return t==t?s(e,t,n):r(e,o,n)}},9454:(e,t,n)=>{var r=n(44239),o=n(37005);e.exports=function(e){return o(e)&&"[object Arguments]"==r(e)}},90939:(e,t,n)=>{var r=n(2492),o=n(37005);e.exports=function e(t,n,s,i,a){return t===n||(null==t||null==n||!o(t)&&!o(n)?t!=t&&n!=n:r(t,n,s,i,e,a))}},2492:(e,t,n)=>{var r=n(46384),o=n(67114),s=n(18351),i=n(16096),a=n(98882),l=n(1469),c=n(44144),u=n(36719),p="[object Arguments]",h="[object Array]",f="[object Object]",d=Object.prototype.hasOwnProperty;e.exports=function(e,t,n,m,g,y){var v=l(e),b=l(t),w=v?h:a(e),E=b?h:a(t),x=(w=w==p?f:w)==f,S=(E=E==p?f:E)==f,_=w==E;if(_&&c(e)){if(!c(t))return!1;v=!0,x=!1}if(_&&!x)return y||(y=new r),v||u(e)?o(e,t,n,m,g,y):s(e,t,w,n,m,g,y);if(!(1&n)){var j=x&&d.call(e,"__wrapped__"),O=S&&d.call(t,"__wrapped__");if(j||O){var k=j?e.value():e,A=O?t.value():t;return y||(y=new r),g(k,A,n,m,y)}}return!!_&&(y||(y=new r),i(e,t,n,m,g,y))}},25588:(e,t,n)=>{var r=n(98882),o=n(37005);e.exports=function(e){return o(e)&&"[object Map]"==r(e)}},2958:(e,t,n)=>{var r=n(46384),o=n(90939);e.exports=function(e,t,n,s){var i=n.length,a=i,l=!s;if(null==e)return!a;for(e=Object(e);i--;){var c=n[i];if(l&&c[2]?c[1]!==e[c[0]]:!(c[0]in e))return!1}for(;++i{e.exports=function(e){return e!=e}},28458:(e,t,n)=>{var r=n(23560),o=n(15346),s=n(13218),i=n(80346),a=/^\[object .+?Constructor\]$/,l=Function.prototype,c=Object.prototype,u=l.toString,p=c.hasOwnProperty,h=RegExp("^"+u.call(p).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");e.exports=function(e){return!(!s(e)||o(e))&&(r(e)?h:a).test(i(e))}},29221:(e,t,n)=>{var r=n(98882),o=n(37005);e.exports=function(e){return o(e)&&"[object Set]"==r(e)}},38749:(e,t,n)=>{var r=n(44239),o=n(41780),s=n(37005),i={};i["[object Float32Array]"]=i["[object Float64Array]"]=i["[object Int8Array]"]=i["[object Int16Array]"]=i["[object Int32Array]"]=i["[object Uint8Array]"]=i["[object Uint8ClampedArray]"]=i["[object Uint16Array]"]=i["[object Uint32Array]"]=!0,i["[object Arguments]"]=i["[object Array]"]=i["[object ArrayBuffer]"]=i["[object Boolean]"]=i["[object DataView]"]=i["[object Date]"]=i["[object Error]"]=i["[object Function]"]=i["[object Map]"]=i["[object Number]"]=i["[object Object]"]=i["[object RegExp]"]=i["[object Set]"]=i["[object String]"]=i["[object WeakMap]"]=!1,e.exports=function(e){return s(e)&&o(e.length)&&!!i[r(e)]}},67206:(e,t,n)=>{var r=n(91573),o=n(16432),s=n(6557),i=n(1469),a=n(39601);e.exports=function(e){return"function"==typeof e?e:null==e?s:"object"==typeof e?i(e)?o(e[0],e[1]):r(e):a(e)}},280:(e,t,n)=>{var r=n(25726),o=n(86916),s=Object.prototype.hasOwnProperty;e.exports=function(e){if(!r(e))return o(e);var t=[];for(var n in Object(e))s.call(e,n)&&"constructor"!=n&&t.push(n);return t}},10313:(e,t,n)=>{var r=n(13218),o=n(25726),s=n(33498),i=Object.prototype.hasOwnProperty;e.exports=function(e){if(!r(e))return s(e);var t=o(e),n=[];for(var a in e)("constructor"!=a||!t&&i.call(e,a))&&n.push(a);return n}},9435:e=>{e.exports=function(){}},91573:(e,t,n)=>{var r=n(2958),o=n(1499),s=n(42634);e.exports=function(e){var t=o(e);return 1==t.length&&t[0][2]?s(t[0][0],t[0][1]):function(n){return n===e||r(n,e,t)}}},16432:(e,t,n)=>{var r=n(90939),o=n(27361),s=n(79095),i=n(15403),a=n(89162),l=n(42634),c=n(40327);e.exports=function(e,t){return i(e)&&a(t)?l(c(e),t):function(n){var i=o(n,e);return void 0===i&&i===t?s(n,e):r(t,i,3)}}},42980:(e,t,n)=>{var r=n(46384),o=n(86556),s=n(28483),i=n(59783),a=n(13218),l=n(81704),c=n(36390);e.exports=function e(t,n,u,p,h){t!==n&&s(n,(function(s,l){if(h||(h=new r),a(s))i(t,n,l,u,e,p,h);else{var f=p?p(c(t,l),s,l+"",t,n,h):void 0;void 0===f&&(f=s),o(t,l,f)}}),l)}},59783:(e,t,n)=>{var r=n(86556),o=n(64626),s=n(77133),i=n(278),a=n(38517),l=n(35694),c=n(1469),u=n(29246),p=n(44144),h=n(23560),f=n(13218),d=n(68630),m=n(36719),g=n(36390),y=n(59881);e.exports=function(e,t,n,v,b,w,E){var x=g(e,n),S=g(t,n),_=E.get(S);if(_)r(e,n,_);else{var j=w?w(x,S,n+"",e,t,E):void 0,O=void 0===j;if(O){var k=c(S),A=!k&&p(S),C=!k&&!A&&m(S);j=S,k||A||C?c(x)?j=x:u(x)?j=i(x):A?(O=!1,j=o(S,!0)):C?(O=!1,j=s(S,!0)):j=[]:d(S)||l(S)?(j=x,l(x)?j=y(x):f(x)&&!h(x)||(j=a(S))):O=!1}O&&(E.set(S,j),b(j,S,v,w,E),E.delete(S)),r(e,n,j)}}},40371:e=>{e.exports=function(e){return function(t){return null==t?void 0:t[e]}}},79152:(e,t,n)=>{var r=n(97786);e.exports=function(e){return function(t){return r(t,e)}}},18674:e=>{e.exports=function(e){return function(t){return null==e?void 0:e[t]}}},10107:e=>{e.exports=function(e,t,n,r,o){return o(e,(function(e,o,s){n=r?(r=!1,e):t(n,e,o,s)})),n}},5976:(e,t,n)=>{var r=n(6557),o=n(45357),s=n(30061);e.exports=function(e,t){return s(o(e,t,r),e+"")}},10611:(e,t,n)=>{var r=n(34865),o=n(71811),s=n(65776),i=n(13218),a=n(40327);e.exports=function(e,t,n,l){if(!i(e))return e;for(var c=-1,u=(t=o(t,e)).length,p=u-1,h=e;null!=h&&++c{var r=n(6557),o=n(89250),s=o?function(e,t){return o.set(e,t),e}:r;e.exports=s},56560:(e,t,n)=>{var r=n(75703),o=n(38777),s=n(6557),i=o?function(e,t){return o(e,"toString",{configurable:!0,enumerable:!1,value:r(t),writable:!0})}:s;e.exports=i},14259:e=>{e.exports=function(e,t,n){var r=-1,o=e.length;t<0&&(t=-t>o?0:o+t),(n=n>o?o:n)<0&&(n+=o),o=t>n?0:n-t>>>0,t>>>=0;for(var s=Array(o);++r{var r=n(89881);e.exports=function(e,t){var n;return r(e,(function(e,r,o){return!(n=t(e,r,o))})),!!n}},22545:e=>{e.exports=function(e,t){for(var n=-1,r=Array(e);++n{var r=n(62705),o=n(29932),s=n(1469),i=n(33448),a=r?r.prototype:void 0,l=a?a.toString:void 0;e.exports=function e(t){if("string"==typeof t)return t;if(s(t))return o(t,e)+"";if(i(t))return l?l.call(t):"";var n=t+"";return"0"==n&&1/t==-Infinity?"-0":n}},27561:(e,t,n)=>{var r=n(67990),o=/^\s+/;e.exports=function(e){return e?e.slice(0,r(e)+1).replace(o,""):e}},7518:e=>{e.exports=function(e){return function(t){return e(t)}}},57406:(e,t,n)=>{var r=n(71811),o=n(10928),s=n(40292),i=n(40327);e.exports=function(e,t){return t=r(t,e),null==(e=s(e,t))||delete e[i(o(t))]}},1757:e=>{e.exports=function(e,t,n){for(var r=-1,o=e.length,s=t.length,i={};++r{e.exports=function(e,t){return e.has(t)}},71811:(e,t,n)=>{var r=n(1469),o=n(15403),s=n(55514),i=n(79833);e.exports=function(e,t){return r(e)?e:o(e,t)?[e]:s(i(e))}},40180:(e,t,n)=>{var r=n(14259);e.exports=function(e,t,n){var o=e.length;return n=void 0===n?o:n,!t&&n>=o?e:r(e,t,n)}},74318:(e,t,n)=>{var r=n(11149);e.exports=function(e){var t=new e.constructor(e.byteLength);return new r(t).set(new r(e)),t}},64626:(e,t,n)=>{e=n.nmd(e);var r=n(55639),o=t&&!t.nodeType&&t,s=o&&e&&!e.nodeType&&e,i=s&&s.exports===o?r.Buffer:void 0,a=i?i.allocUnsafe:void 0;e.exports=function(e,t){if(t)return e.slice();var n=e.length,r=a?a(n):new e.constructor(n);return e.copy(r),r}},57157:(e,t,n)=>{var r=n(74318);e.exports=function(e,t){var n=t?r(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.byteLength)}},93147:e=>{var t=/\w*$/;e.exports=function(e){var n=new e.constructor(e.source,t.exec(e));return n.lastIndex=e.lastIndex,n}},40419:(e,t,n)=>{var r=n(62705),o=r?r.prototype:void 0,s=o?o.valueOf:void 0;e.exports=function(e){return s?Object(s.call(e)):{}}},77133:(e,t,n)=>{var r=n(74318);e.exports=function(e,t){var n=t?r(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.length)}},52157:e=>{var t=Math.max;e.exports=function(e,n,r,o){for(var s=-1,i=e.length,a=r.length,l=-1,c=n.length,u=t(i-a,0),p=Array(c+u),h=!o;++l{var t=Math.max;e.exports=function(e,n,r,o){for(var s=-1,i=e.length,a=-1,l=r.length,c=-1,u=n.length,p=t(i-l,0),h=Array(p+u),f=!o;++s{e.exports=function(e,t){var n=-1,r=e.length;for(t||(t=Array(r));++n{var r=n(34865),o=n(89465);e.exports=function(e,t,n,s){var i=!n;n||(n={});for(var a=-1,l=t.length;++a{var r=n(98363),o=n(99551);e.exports=function(e,t){return r(e,o(e),t)}},1911:(e,t,n)=>{var r=n(98363),o=n(51442);e.exports=function(e,t){return r(e,o(e),t)}},14429:(e,t,n)=>{var r=n(55639)["__core-js_shared__"];e.exports=r},97991:e=>{e.exports=function(e,t){for(var n=e.length,r=0;n--;)e[n]===t&&++r;return r}},21463:(e,t,n)=>{var r=n(5976),o=n(16612);e.exports=function(e){return r((function(t,n){var r=-1,s=n.length,i=s>1?n[s-1]:void 0,a=s>2?n[2]:void 0;for(i=e.length>3&&"function"==typeof i?(s--,i):void 0,a&&o(n[0],n[1],a)&&(i=s<3?void 0:i,s=1),t=Object(t);++r{var r=n(98612);e.exports=function(e,t){return function(n,o){if(null==n)return n;if(!r(n))return e(n,o);for(var s=n.length,i=t?s:-1,a=Object(n);(t?i--:++i{e.exports=function(e){return function(t,n,r){for(var o=-1,s=Object(t),i=r(t),a=i.length;a--;){var l=i[e?a:++o];if(!1===n(s[l],l,s))break}return t}}},22402:(e,t,n)=>{var r=n(71774),o=n(55639);e.exports=function(e,t,n){var s=1&t,i=r(e);return function t(){return(this&&this!==o&&this instanceof t?i:e).apply(s?n:this,arguments)}}},98805:(e,t,n)=>{var r=n(40180),o=n(62689),s=n(83140),i=n(79833);e.exports=function(e){return function(t){t=i(t);var n=o(t)?s(t):void 0,a=n?n[0]:t.charAt(0),l=n?r(n,1).join(""):t.slice(1);return a[e]()+l}}},35393:(e,t,n)=>{var r=n(62663),o=n(53816),s=n(58748),i=RegExp("['โ€™]","g");e.exports=function(e){return function(t){return r(s(o(t).replace(i,"")),e,"")}}},71774:(e,t,n)=>{var r=n(3118),o=n(13218);e.exports=function(e){return function(){var t=arguments;switch(t.length){case 0:return new e;case 1:return new e(t[0]);case 2:return new e(t[0],t[1]);case 3:return new e(t[0],t[1],t[2]);case 4:return new e(t[0],t[1],t[2],t[3]);case 5:return new e(t[0],t[1],t[2],t[3],t[4]);case 6:return new e(t[0],t[1],t[2],t[3],t[4],t[5]);case 7:return new e(t[0],t[1],t[2],t[3],t[4],t[5],t[6])}var n=r(e.prototype),s=e.apply(n,t);return o(s)?s:n}}},46347:(e,t,n)=>{var r=n(96874),o=n(71774),s=n(86935),i=n(94487),a=n(20893),l=n(46460),c=n(55639);e.exports=function(e,t,n){var u=o(e);return function o(){for(var p=arguments.length,h=Array(p),f=p,d=a(o);f--;)h[f]=arguments[f];var m=p<3&&h[0]!==d&&h[p-1]!==d?[]:l(h,d);return(p-=m.length){var r=n(67206),o=n(98612),s=n(3674);e.exports=function(e){return function(t,n,i){var a=Object(t);if(!o(t)){var l=r(n,3);t=s(t),n=function(e){return l(a[e],e,a)}}var c=e(t,n,i);return c>-1?a[l?t[c]:c]:void 0}}},86935:(e,t,n)=>{var r=n(52157),o=n(14054),s=n(97991),i=n(71774),a=n(94487),l=n(20893),c=n(90451),u=n(46460),p=n(55639);e.exports=function e(t,n,h,f,d,m,g,y,v,b){var w=128&n,E=1&n,x=2&n,S=24&n,_=512&n,j=x?void 0:i(t);return function O(){for(var k=arguments.length,A=Array(k),C=k;C--;)A[C]=arguments[C];if(S)var P=l(O),N=s(A,P);if(f&&(A=r(A,f,d,S)),m&&(A=o(A,m,g,S)),k-=N,S&&k1&&A.reverse(),w&&v{var r=n(96874),o=n(71774),s=n(55639);e.exports=function(e,t,n,i){var a=1&t,l=o(e);return function t(){for(var o=-1,c=arguments.length,u=-1,p=i.length,h=Array(p+c),f=this&&this!==s&&this instanceof t?l:e;++u{var r=n(86528),o=n(258),s=n(69255);e.exports=function(e,t,n,i,a,l,c,u,p,h){var f=8&t;t|=f?32:64,4&(t&=~(f?64:32))||(t&=-4);var d=[e,t,a,f?l:void 0,f?c:void 0,f?void 0:l,f?void 0:c,u,p,h],m=n.apply(void 0,d);return r(e)&&o(m,d),m.placeholder=i,s(m,e,t)}},97727:(e,t,n)=>{var r=n(28045),o=n(22402),s=n(46347),i=n(86935),a=n(84375),l=n(66833),c=n(63833),u=n(258),p=n(69255),h=n(40554),f=Math.max;e.exports=function(e,t,n,d,m,g,y,v){var b=2&t;if(!b&&"function"!=typeof e)throw new TypeError("Expected a function");var w=d?d.length:0;if(w||(t&=-97,d=m=void 0),y=void 0===y?y:f(h(y),0),v=void 0===v?v:h(v),w-=m?m.length:0,64&t){var E=d,x=m;d=m=void 0}var S=b?void 0:l(e),_=[e,t,n,d,m,E,x,g,y,v];if(S&&c(_,S),e=_[0],t=_[1],n=_[2],d=_[3],m=_[4],!(v=_[9]=void 0===_[9]?b?0:e.length:f(_[9]-w,0))&&24&t&&(t&=-25),t&&1!=t)j=8==t||16==t?s(e,t,v):32!=t&&33!=t||m.length?i.apply(void 0,_):a(e,t,n,d);else var j=o(e,t,n);return p((S?r:u)(j,_),e,t)}},60696:(e,t,n)=>{var r=n(68630);e.exports=function(e){return r(e)?void 0:e}},69389:(e,t,n)=>{var r=n(18674)({ร€:"A",ร:"A",ร‚:"A",รƒ:"A",ร„:"A",ร…:"A",ร :"a",รก:"a",รข:"a",รฃ:"a",รค:"a",รฅ:"a",ร‡:"C",รง:"c",ร:"D",รฐ:"d",รˆ:"E",ร‰:"E",รŠ:"E",ร‹:"E",รจ:"e",รฉ:"e",รช:"e",รซ:"e",รŒ:"I",ร:"I",รŽ:"I",ร:"I",รฌ:"i",รญ:"i",รฎ:"i",รฏ:"i",ร‘:"N",รฑ:"n",ร’:"O",ร“:"O",ร”:"O",ร•:"O",ร–:"O",ร˜:"O",รฒ:"o",รณ:"o",รด:"o",รต:"o",รถ:"o",รธ:"o",ร™:"U",รš:"U",ร›:"U",รœ:"U",รน:"u",รบ:"u",รป:"u",รผ:"u",ร:"Y",รฝ:"y",รฟ:"y",ร†:"Ae",รฆ:"ae",รž:"Th",รพ:"th",รŸ:"ss",ฤ€:"A",ฤ‚:"A",ฤ„:"A",ฤ:"a",ฤƒ:"a",ฤ…:"a",ฤ†:"C",ฤˆ:"C",ฤŠ:"C",ฤŒ:"C",ฤ‡:"c",ฤ‰:"c",ฤ‹:"c",ฤ:"c",ฤŽ:"D",ฤ:"D",ฤ:"d",ฤ‘:"d",ฤ’:"E",ฤ”:"E",ฤ–:"E",ฤ˜:"E",ฤš:"E",ฤ“:"e",ฤ•:"e",ฤ—:"e",ฤ™:"e",ฤ›:"e",ฤœ:"G",ฤž:"G",ฤ :"G",ฤข:"G",ฤ:"g",ฤŸ:"g",ฤก:"g",ฤฃ:"g",ฤค:"H",ฤฆ:"H",ฤฅ:"h",ฤง:"h",ฤจ:"I",ฤช:"I",ฤฌ:"I",ฤฎ:"I",ฤฐ:"I",ฤฉ:"i",ฤซ:"i",ฤญ:"i",ฤฏ:"i",ฤฑ:"i",ฤด:"J",ฤต:"j",ฤถ:"K",ฤท:"k",ฤธ:"k",ฤน:"L",ฤป:"L",ฤฝ:"L",ฤฟ:"L",ล:"L",ฤบ:"l",ฤผ:"l",ฤพ:"l",ล€:"l",ล‚:"l",ลƒ:"N",ล…:"N",ล‡:"N",ลŠ:"N",ล„:"n",ล†:"n",ลˆ:"n",ล‹:"n",ลŒ:"O",ลŽ:"O",ล:"O",ล:"o",ล:"o",ล‘:"o",ล”:"R",ล–:"R",ล˜:"R",ล•:"r",ล—:"r",ล™:"r",ลš:"S",ลœ:"S",ลž:"S",ล :"S",ล›:"s",ล:"s",ลŸ:"s",ลก:"s",ลข:"T",ลค:"T",ลฆ:"T",ลฃ:"t",ลฅ:"t",ลง:"t",ลจ:"U",ลช:"U",ลฌ:"U",ลฎ:"U",ลฐ:"U",ลฒ:"U",ลฉ:"u",ลซ:"u",ลญ:"u",ลฏ:"u",ลฑ:"u",ลณ:"u",ลด:"W",ลต:"w",ลถ:"Y",ลท:"y",ลธ:"Y",ลน:"Z",ลป:"Z",ลฝ:"Z",ลบ:"z",ลผ:"z",ลพ:"z",ฤฒ:"IJ",ฤณ:"ij",ล’:"Oe",ล“:"oe",ล‰:"'n",ลฟ:"s"});e.exports=r},38777:(e,t,n)=>{var r=n(10852),o=function(){try{var e=r(Object,"defineProperty");return e({},"",{}),e}catch(e){}}();e.exports=o},67114:(e,t,n)=>{var r=n(88668),o=n(82908),s=n(74757);e.exports=function(e,t,n,i,a,l){var c=1&n,u=e.length,p=t.length;if(u!=p&&!(c&&p>u))return!1;var h=l.get(e),f=l.get(t);if(h&&f)return h==t&&f==e;var d=-1,m=!0,g=2&n?new r:void 0;for(l.set(e,t),l.set(t,e);++d{var r=n(62705),o=n(11149),s=n(77813),i=n(67114),a=n(68776),l=n(21814),c=r?r.prototype:void 0,u=c?c.valueOf:void 0;e.exports=function(e,t,n,r,c,p,h){switch(n){case"[object DataView]":if(e.byteLength!=t.byteLength||e.byteOffset!=t.byteOffset)return!1;e=e.buffer,t=t.buffer;case"[object ArrayBuffer]":return!(e.byteLength!=t.byteLength||!p(new o(e),new o(t)));case"[object Boolean]":case"[object Date]":case"[object Number]":return s(+e,+t);case"[object Error]":return e.name==t.name&&e.message==t.message;case"[object RegExp]":case"[object String]":return e==t+"";case"[object Map]":var f=a;case"[object Set]":var d=1&r;if(f||(f=l),e.size!=t.size&&!d)return!1;var m=h.get(e);if(m)return m==t;r|=2,h.set(e,t);var g=i(f(e),f(t),r,c,p,h);return h.delete(e),g;case"[object Symbol]":if(u)return u.call(e)==u.call(t)}return!1}},16096:(e,t,n)=>{var r=n(58234),o=Object.prototype.hasOwnProperty;e.exports=function(e,t,n,s,i,a){var l=1&n,c=r(e),u=c.length;if(u!=r(t).length&&!l)return!1;for(var p=u;p--;){var h=c[p];if(!(l?h in t:o.call(t,h)))return!1}var f=a.get(e),d=a.get(t);if(f&&d)return f==t&&d==e;var m=!0;a.set(e,t),a.set(t,e);for(var g=l;++p{var r=n(85564),o=n(45357),s=n(30061);e.exports=function(e){return s(o(e,void 0,r),e+"")}},31957:(e,t,n)=>{var r="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g;e.exports=r},58234:(e,t,n)=>{var r=n(68866),o=n(99551),s=n(3674);e.exports=function(e){return r(e,s,o)}},46904:(e,t,n)=>{var r=n(68866),o=n(51442),s=n(81704);e.exports=function(e){return r(e,s,o)}},66833:(e,t,n)=>{var r=n(89250),o=n(50308),s=r?function(e){return r.get(e)}:o;e.exports=s},97658:(e,t,n)=>{var r=n(52060),o=Object.prototype.hasOwnProperty;e.exports=function(e){for(var t=e.name+"",n=r[t],s=o.call(r,t)?n.length:0;s--;){var i=n[s],a=i.func;if(null==a||a==e)return i.name}return t}},20893:e=>{e.exports=function(e){return e.placeholder}},45050:(e,t,n)=>{var r=n(37019);e.exports=function(e,t){var n=e.__data__;return r(t)?n["string"==typeof t?"string":"hash"]:n.map}},1499:(e,t,n)=>{var r=n(89162),o=n(3674);e.exports=function(e){for(var t=o(e),n=t.length;n--;){var s=t[n],i=e[s];t[n]=[s,i,r(i)]}return t}},10852:(e,t,n)=>{var r=n(28458),o=n(47801);e.exports=function(e,t){var n=o(e,t);return r(n)?n:void 0}},85924:(e,t,n)=>{var r=n(5569)(Object.getPrototypeOf,Object);e.exports=r},89607:(e,t,n)=>{var r=n(62705),o=Object.prototype,s=o.hasOwnProperty,i=o.toString,a=r?r.toStringTag:void 0;e.exports=function(e){var t=s.call(e,a),n=e[a];try{e[a]=void 0;var r=!0}catch(e){}var o=i.call(e);return r&&(t?e[a]=n:delete e[a]),o}},99551:(e,t,n)=>{var r=n(34963),o=n(70479),s=Object.prototype.propertyIsEnumerable,i=Object.getOwnPropertySymbols,a=i?function(e){return null==e?[]:(e=Object(e),r(i(e),(function(t){return s.call(e,t)})))}:o;e.exports=a},51442:(e,t,n)=>{var r=n(62488),o=n(85924),s=n(99551),i=n(70479),a=Object.getOwnPropertySymbols?function(e){for(var t=[];e;)r(t,s(e)),e=o(e);return t}:i;e.exports=a},98882:(e,t,n)=>{var r=n(18552),o=n(57071),s=n(53818),i=n(58525),a=n(70577),l=n(44239),c=n(80346),u="[object Map]",p="[object Promise]",h="[object Set]",f="[object WeakMap]",d="[object DataView]",m=c(r),g=c(o),y=c(s),v=c(i),b=c(a),w=l;(r&&w(new r(new ArrayBuffer(1)))!=d||o&&w(new o)!=u||s&&w(s.resolve())!=p||i&&w(new i)!=h||a&&w(new a)!=f)&&(w=function(e){var t=l(e),n="[object Object]"==t?e.constructor:void 0,r=n?c(n):"";if(r)switch(r){case m:return d;case g:return u;case y:return p;case v:return h;case b:return f}return t}),e.exports=w},47801:e=>{e.exports=function(e,t){return null==e?void 0:e[t]}},58775:e=>{var t=/\{\n\/\* \[wrapped with (.+)\] \*/,n=/,? & /;e.exports=function(e){var r=e.match(t);return r?r[1].split(n):[]}},222:(e,t,n)=>{var r=n(71811),o=n(35694),s=n(1469),i=n(65776),a=n(41780),l=n(40327);e.exports=function(e,t,n){for(var c=-1,u=(t=r(t,e)).length,p=!1;++c{var t=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");e.exports=function(e){return t.test(e)}},93157:e=>{var t=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;e.exports=function(e){return t.test(e)}},51789:(e,t,n)=>{var r=n(94536);e.exports=function(){this.__data__=r?r(null):{},this.size=0}},80401:e=>{e.exports=function(e){var t=this.has(e)&&delete this.__data__[e];return this.size-=t?1:0,t}},57667:(e,t,n)=>{var r=n(94536),o=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;if(r){var n=t[e];return"__lodash_hash_undefined__"===n?void 0:n}return o.call(t,e)?t[e]:void 0}},21327:(e,t,n)=>{var r=n(94536),o=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;return r?void 0!==t[e]:o.call(t,e)}},81866:(e,t,n)=>{var r=n(94536);e.exports=function(e,t){var n=this.__data__;return this.size+=this.has(e)?0:1,n[e]=r&&void 0===t?"__lodash_hash_undefined__":t,this}},43824:e=>{var t=Object.prototype.hasOwnProperty;e.exports=function(e){var n=e.length,r=new e.constructor(n);return n&&"string"==typeof e[0]&&t.call(e,"index")&&(r.index=e.index,r.input=e.input),r}},29148:(e,t,n)=>{var r=n(74318),o=n(57157),s=n(93147),i=n(40419),a=n(77133);e.exports=function(e,t,n){var l=e.constructor;switch(t){case"[object ArrayBuffer]":return r(e);case"[object Boolean]":case"[object Date]":return new l(+e);case"[object DataView]":return o(e,n);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":case"[object Uint16Array]":case"[object Uint32Array]":return a(e,n);case"[object Map]":case"[object Set]":return new l;case"[object Number]":case"[object String]":return new l(e);case"[object RegExp]":return s(e);case"[object Symbol]":return i(e)}}},38517:(e,t,n)=>{var r=n(3118),o=n(85924),s=n(25726);e.exports=function(e){return"function"!=typeof e.constructor||s(e)?{}:r(o(e))}},83112:e=>{var t=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/;e.exports=function(e,n){var r=n.length;if(!r)return e;var o=r-1;return n[o]=(r>1?"& ":"")+n[o],n=n.join(r>2?", ":" "),e.replace(t,"{\n/* [wrapped with "+n+"] */\n")}},37285:(e,t,n)=>{var r=n(62705),o=n(35694),s=n(1469),i=r?r.isConcatSpreadable:void 0;e.exports=function(e){return s(e)||o(e)||!!(i&&e&&e[i])}},65776:e=>{var t=/^(?:0|[1-9]\d*)$/;e.exports=function(e,n){var r=typeof e;return!!(n=null==n?9007199254740991:n)&&("number"==r||"symbol"!=r&&t.test(e))&&e>-1&&e%1==0&&e{var r=n(77813),o=n(98612),s=n(65776),i=n(13218);e.exports=function(e,t,n){if(!i(n))return!1;var a=typeof t;return!!("number"==a?o(n)&&s(t,n.length):"string"==a&&t in n)&&r(n[t],e)}},15403:(e,t,n)=>{var r=n(1469),o=n(33448),s=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,i=/^\w*$/;e.exports=function(e,t){if(r(e))return!1;var n=typeof e;return!("number"!=n&&"symbol"!=n&&"boolean"!=n&&null!=e&&!o(e))||(i.test(e)||!s.test(e)||null!=t&&e in Object(t))}},37019:e=>{e.exports=function(e){var t=typeof e;return"string"==t||"number"==t||"symbol"==t||"boolean"==t?"__proto__"!==e:null===e}},86528:(e,t,n)=>{var r=n(96425),o=n(66833),s=n(97658),i=n(8111);e.exports=function(e){var t=s(e),n=i[t];if("function"!=typeof n||!(t in r.prototype))return!1;if(e===n)return!0;var a=o(n);return!!a&&e===a[0]}},15346:(e,t,n)=>{var r,o=n(14429),s=(r=/[^.]+$/.exec(o&&o.keys&&o.keys.IE_PROTO||""))?"Symbol(src)_1."+r:"";e.exports=function(e){return!!s&&s in e}},25726:e=>{var t=Object.prototype;e.exports=function(e){var n=e&&e.constructor;return e===("function"==typeof n&&n.prototype||t)}},89162:(e,t,n)=>{var r=n(13218);e.exports=function(e){return e==e&&!r(e)}},27040:e=>{e.exports=function(){this.__data__=[],this.size=0}},14125:(e,t,n)=>{var r=n(18470),o=Array.prototype.splice;e.exports=function(e){var t=this.__data__,n=r(t,e);return!(n<0)&&(n==t.length-1?t.pop():o.call(t,n,1),--this.size,!0)}},82117:(e,t,n)=>{var r=n(18470);e.exports=function(e){var t=this.__data__,n=r(t,e);return n<0?void 0:t[n][1]}},67518:(e,t,n)=>{var r=n(18470);e.exports=function(e){return r(this.__data__,e)>-1}},54705:(e,t,n)=>{var r=n(18470);e.exports=function(e,t){var n=this.__data__,o=r(n,e);return o<0?(++this.size,n.push([e,t])):n[o][1]=t,this}},24785:(e,t,n)=>{var r=n(1989),o=n(38407),s=n(57071);e.exports=function(){this.size=0,this.__data__={hash:new r,map:new(s||o),string:new r}}},11285:(e,t,n)=>{var r=n(45050);e.exports=function(e){var t=r(this,e).delete(e);return this.size-=t?1:0,t}},96e3:(e,t,n)=>{var r=n(45050);e.exports=function(e){return r(this,e).get(e)}},49916:(e,t,n)=>{var r=n(45050);e.exports=function(e){return r(this,e).has(e)}},95265:(e,t,n)=>{var r=n(45050);e.exports=function(e,t){var n=r(this,e),o=n.size;return n.set(e,t),this.size+=n.size==o?0:1,this}},68776:e=>{e.exports=function(e){var t=-1,n=Array(e.size);return e.forEach((function(e,r){n[++t]=[r,e]})),n}},42634:e=>{e.exports=function(e,t){return function(n){return null!=n&&(n[e]===t&&(void 0!==t||e in Object(n)))}}},24523:(e,t,n)=>{var r=n(88306);e.exports=function(e){var t=r(e,(function(e){return 500===n.size&&n.clear(),e})),n=t.cache;return t}},63833:(e,t,n)=>{var r=n(52157),o=n(14054),s=n(46460),i="__lodash_placeholder__",a=128,l=Math.min;e.exports=function(e,t){var n=e[1],c=t[1],u=n|c,p=u<131,h=c==a&&8==n||c==a&&256==n&&e[7].length<=t[8]||384==c&&t[7].length<=t[8]&&8==n;if(!p&&!h)return e;1&c&&(e[2]=t[2],u|=1&n?0:4);var f=t[3];if(f){var d=e[3];e[3]=d?r(d,f,t[4]):f,e[4]=d?s(e[3],i):t[4]}return(f=t[5])&&(d=e[5],e[5]=d?o(d,f,t[6]):f,e[6]=d?s(e[5],i):t[6]),(f=t[7])&&(e[7]=f),c&a&&(e[8]=null==e[8]?t[8]:l(e[8],t[8])),null==e[9]&&(e[9]=t[9]),e[0]=t[0],e[1]=u,e}},89250:(e,t,n)=>{var r=n(70577),o=r&&new r;e.exports=o},94536:(e,t,n)=>{var r=n(10852)(Object,"create");e.exports=r},86916:(e,t,n)=>{var r=n(5569)(Object.keys,Object);e.exports=r},33498:e=>{e.exports=function(e){var t=[];if(null!=e)for(var n in Object(e))t.push(n);return t}},31167:(e,t,n)=>{e=n.nmd(e);var r=n(31957),o=t&&!t.nodeType&&t,s=o&&e&&!e.nodeType&&e,i=s&&s.exports===o&&r.process,a=function(){try{var e=s&&s.require&&s.require("util").types;return e||i&&i.binding&&i.binding("util")}catch(e){}}();e.exports=a},2333:e=>{var t=Object.prototype.toString;e.exports=function(e){return t.call(e)}},5569:e=>{e.exports=function(e,t){return function(n){return e(t(n))}}},45357:(e,t,n)=>{var r=n(96874),o=Math.max;e.exports=function(e,t,n){return t=o(void 0===t?e.length-1:t,0),function(){for(var s=arguments,i=-1,a=o(s.length-t,0),l=Array(a);++i{var r=n(97786),o=n(14259);e.exports=function(e,t){return t.length<2?e:r(e,o(t,0,-1))}},52060:e=>{e.exports={}},90451:(e,t,n)=>{var r=n(278),o=n(65776),s=Math.min;e.exports=function(e,t){for(var n=e.length,i=s(t.length,n),a=r(e);i--;){var l=t[i];e[i]=o(l,n)?a[l]:void 0}return e}},46460:e=>{var t="__lodash_placeholder__";e.exports=function(e,n){for(var r=-1,o=e.length,s=0,i=[];++r{var r=n(31957),o="object"==typeof self&&self&&self.Object===Object&&self,s=r||o||Function("return this")();e.exports=s},36390:e=>{e.exports=function(e,t){if(("constructor"!==t||"function"!=typeof e[t])&&"__proto__"!=t)return e[t]}},90619:e=>{e.exports=function(e){return this.__data__.set(e,"__lodash_hash_undefined__"),this}},72385:e=>{e.exports=function(e){return this.__data__.has(e)}},258:(e,t,n)=>{var r=n(28045),o=n(21275)(r);e.exports=o},21814:e=>{e.exports=function(e){var t=-1,n=Array(e.size);return e.forEach((function(e){n[++t]=e})),n}},30061:(e,t,n)=>{var r=n(56560),o=n(21275)(r);e.exports=o},69255:(e,t,n)=>{var r=n(58775),o=n(83112),s=n(30061),i=n(87241);e.exports=function(e,t,n){var a=t+"";return s(e,o(a,i(r(a),n)))}},21275:e=>{var t=Date.now;e.exports=function(e){var n=0,r=0;return function(){var o=t(),s=16-(o-r);if(r=o,s>0){if(++n>=800)return arguments[0]}else n=0;return e.apply(void 0,arguments)}}},37465:(e,t,n)=>{var r=n(38407);e.exports=function(){this.__data__=new r,this.size=0}},63779:e=>{e.exports=function(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n}},67599:e=>{e.exports=function(e){return this.__data__.get(e)}},44758:e=>{e.exports=function(e){return this.__data__.has(e)}},34309:(e,t,n)=>{var r=n(38407),o=n(57071),s=n(83369);e.exports=function(e,t){var n=this.__data__;if(n instanceof r){var i=n.__data__;if(!o||i.length<199)return i.push([e,t]),this.size=++n.size,this;n=this.__data__=new s(i)}return n.set(e,t),this.size=n.size,this}},42351:e=>{e.exports=function(e,t,n){for(var r=n-1,o=e.length;++r{var r=n(44286),o=n(62689),s=n(676);e.exports=function(e){return o(e)?s(e):r(e)}},55514:(e,t,n)=>{var r=n(24523),o=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,s=/\\(\\)?/g,i=r((function(e){var t=[];return 46===e.charCodeAt(0)&&t.push(""),e.replace(o,(function(e,n,r,o){t.push(r?o.replace(s,"$1"):n||e)})),t}));e.exports=i},40327:(e,t,n)=>{var r=n(33448);e.exports=function(e){if("string"==typeof e||r(e))return e;var t=e+"";return"0"==t&&1/e==-Infinity?"-0":t}},80346:e=>{var t=Function.prototype.toString;e.exports=function(e){if(null!=e){try{return t.call(e)}catch(e){}try{return e+""}catch(e){}}return""}},67990:e=>{var t=/\s/;e.exports=function(e){for(var n=e.length;n--&&t.test(e.charAt(n)););return n}},676:e=>{var t="\\ud800-\\udfff",n="["+t+"]",r="[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]",o="\\ud83c[\\udffb-\\udfff]",s="[^"+t+"]",i="(?:\\ud83c[\\udde6-\\uddff]){2}",a="[\\ud800-\\udbff][\\udc00-\\udfff]",l="(?:"+r+"|"+o+")"+"?",c="[\\ufe0e\\ufe0f]?",u=c+l+("(?:\\u200d(?:"+[s,i,a].join("|")+")"+c+l+")*"),p="(?:"+[s+r+"?",r,i,a,n].join("|")+")",h=RegExp(o+"(?="+o+")|"+p+u,"g");e.exports=function(e){return e.match(h)||[]}},2757:e=>{var t="\\ud800-\\udfff",n="\\u2700-\\u27bf",r="a-z\\xdf-\\xf6\\xf8-\\xff",o="A-Z\\xc0-\\xd6\\xd8-\\xde",s="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",i="["+s+"]",a="\\d+",l="["+n+"]",c="["+r+"]",u="[^"+t+s+a+n+r+o+"]",p="(?:\\ud83c[\\udde6-\\uddff]){2}",h="[\\ud800-\\udbff][\\udc00-\\udfff]",f="["+o+"]",d="(?:"+c+"|"+u+")",m="(?:"+f+"|"+u+")",g="(?:['โ€™](?:d|ll|m|re|s|t|ve))?",y="(?:['โ€™](?:D|LL|M|RE|S|T|VE))?",v="(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?",b="[\\ufe0e\\ufe0f]?",w=b+v+("(?:\\u200d(?:"+["[^"+t+"]",p,h].join("|")+")"+b+v+")*"),E="(?:"+[l,p,h].join("|")+")"+w,x=RegExp([f+"?"+c+"+"+g+"(?="+[i,f,"$"].join("|")+")",m+"+"+y+"(?="+[i,f+d,"$"].join("|")+")",f+"?"+d+"+"+g,f+"+"+y,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",a,E].join("|"),"g");e.exports=function(e){return e.match(x)||[]}},87241:(e,t,n)=>{var r=n(77412),o=n(47443),s=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]];e.exports=function(e,t){return r(s,(function(n){var r="_."+n[0];t&n[1]&&!o(e,r)&&e.push(r)})),e.sort()}},21913:(e,t,n)=>{var r=n(96425),o=n(7548),s=n(278);e.exports=function(e){if(e instanceof r)return e.clone();var t=new o(e.__wrapped__,e.__chain__);return t.__actions__=s(e.__actions__),t.__index__=e.__index__,t.__values__=e.__values__,t}},39514:(e,t,n)=>{var r=n(97727);e.exports=function(e,t,n){return t=n?void 0:t,t=e&&null==t?e.length:t,r(e,128,void 0,void 0,void 0,void 0,t)}},68929:(e,t,n)=>{var r=n(48403),o=n(35393)((function(e,t,n){return t=t.toLowerCase(),e+(n?r(t):t)}));e.exports=o},48403:(e,t,n)=>{var r=n(79833),o=n(11700);e.exports=function(e){return o(r(e).toLowerCase())}},66678:(e,t,n)=>{var r=n(85990);e.exports=function(e){return r(e,4)}},75703:e=>{e.exports=function(e){return function(){return e}}},40087:(e,t,n)=>{var r=n(97727);function o(e,t,n){var s=r(e,8,void 0,void 0,void 0,void 0,void 0,t=n?void 0:t);return s.placeholder=o.placeholder,s}o.placeholder={},e.exports=o},23279:(e,t,n)=>{var r=n(13218),o=n(7771),s=n(14841),i=Math.max,a=Math.min;e.exports=function(e,t,n){var l,c,u,p,h,f,d=0,m=!1,g=!1,y=!0;if("function"!=typeof e)throw new TypeError("Expected a function");function v(t){var n=l,r=c;return l=c=void 0,d=t,p=e.apply(r,n)}function b(e){var n=e-f;return void 0===f||n>=t||n<0||g&&e-d>=u}function w(){var e=o();if(b(e))return E(e);h=setTimeout(w,function(e){var n=t-(e-f);return g?a(n,u-(e-d)):n}(e))}function E(e){return h=void 0,y&&l?v(e):(l=c=void 0,p)}function x(){var e=o(),n=b(e);if(l=arguments,c=this,f=e,n){if(void 0===h)return function(e){return d=e,h=setTimeout(w,t),m?v(e):p}(f);if(g)return clearTimeout(h),h=setTimeout(w,t),v(f)}return void 0===h&&(h=setTimeout(w,t)),p}return t=s(t)||0,r(n)&&(m=!!n.leading,u=(g="maxWait"in n)?i(s(n.maxWait)||0,t):u,y="trailing"in n?!!n.trailing:y),x.cancel=function(){void 0!==h&&clearTimeout(h),d=0,l=f=c=h=void 0},x.flush=function(){return void 0===h?p:E(o())},x}},53816:(e,t,n)=>{var r=n(69389),o=n(79833),s=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,i=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");e.exports=function(e){return(e=o(e))&&e.replace(s,r).replace(i,"")}},77813:e=>{e.exports=function(e,t){return e===t||e!=e&&t!=t}},13311:(e,t,n)=>{var r=n(67740)(n(30998));e.exports=r},30998:(e,t,n)=>{var r=n(41848),o=n(67206),s=n(40554),i=Math.max;e.exports=function(e,t,n){var a=null==e?0:e.length;if(!a)return-1;var l=null==n?0:s(n);return l<0&&(l=i(a+l,0)),r(e,o(t,3),l)}},85564:(e,t,n)=>{var r=n(21078);e.exports=function(e){return(null==e?0:e.length)?r(e,1):[]}},84599:(e,t,n)=>{var r=n(68836),o=n(69306),s=Array.prototype.push;function i(e,t){return 2==t?function(t,n){return e(t,n)}:function(t){return e(t)}}function a(e){for(var t=e?e.length:0,n=Array(t);t--;)n[t]=e[t];return n}function l(e,t){return function(){var n=arguments.length;if(n){for(var r=Array(n);n--;)r[n]=arguments[n];var o=r[0]=t.apply(void 0,r);return e.apply(void 0,r),o}}}e.exports=function e(t,n,c,u){var p="function"==typeof n,h=n===Object(n);if(h&&(u=c,c=n,n=void 0),null==c)throw new TypeError;u||(u={});var f={cap:!("cap"in u)||u.cap,curry:!("curry"in u)||u.curry,fixed:!("fixed"in u)||u.fixed,immutable:!("immutable"in u)||u.immutable,rearg:!("rearg"in u)||u.rearg},d=p?c:o,m="curry"in u&&u.curry,g="fixed"in u&&u.fixed,y="rearg"in u&&u.rearg,v=p?c.runInContext():void 0,b=p?c:{ary:t.ary,assign:t.assign,clone:t.clone,curry:t.curry,forEach:t.forEach,isArray:t.isArray,isError:t.isError,isFunction:t.isFunction,isWeakMap:t.isWeakMap,iteratee:t.iteratee,keys:t.keys,rearg:t.rearg,toInteger:t.toInteger,toPath:t.toPath},w=b.ary,E=b.assign,x=b.clone,S=b.curry,_=b.forEach,j=b.isArray,O=b.isError,k=b.isFunction,A=b.isWeakMap,C=b.keys,P=b.rearg,N=b.toInteger,I=b.toPath,T=C(r.aryMethod),R={castArray:function(e){return function(){var t=arguments[0];return j(t)?e(a(t)):e.apply(void 0,arguments)}},iteratee:function(e){return function(){var t=arguments[1],n=e(arguments[0],t),r=n.length;return f.cap&&"number"==typeof t?(t=t>2?t-2:1,r&&r<=t?n:i(n,t)):n}},mixin:function(e){return function(t){var n=this;if(!k(n))return e(n,Object(t));var r=[];return _(C(t),(function(e){k(t[e])&&r.push([e,n.prototype[e]])})),e(n,Object(t)),_(r,(function(e){var t=e[1];k(t)?n.prototype[e[0]]=t:delete n.prototype[e[0]]})),n}},nthArg:function(e){return function(t){var n=t<0?1:N(t)+1;return S(e(t),n)}},rearg:function(e){return function(t,n){var r=n?n.length:0;return S(e(t,n),r)}},runInContext:function(n){return function(r){return e(t,n(r),u)}}};function M(e,t){if(f.cap){var n=r.iterateeRearg[e];if(n)return function(e,t){return $(e,(function(e){var n=t.length;return function(e,t){return 2==t?function(t,n){return e.apply(void 0,arguments)}:function(t){return e.apply(void 0,arguments)}}(P(i(e,n),t),n)}))}(t,n);var o=!p&&r.iterateeAry[e];if(o)return function(e,t){return $(e,(function(e){return"function"==typeof e?i(e,t):e}))}(t,o)}return t}function D(e,t,n){if(f.fixed&&(g||!r.skipFixed[e])){var o=r.methodSpread[e],i=o&&o.start;return void 0===i?w(t,n):function(e,t){return function(){for(var n=arguments.length,r=n-1,o=Array(n);n--;)o[n]=arguments[n];var i=o[t],a=o.slice(0,t);return i&&s.apply(a,i),t!=r&&s.apply(a,o.slice(t+1)),e.apply(this,a)}}(t,i)}return t}function F(e,t,n){return f.rearg&&n>1&&(y||!r.skipRearg[e])?P(t,r.methodRearg[e]||r.aryRearg[n]):t}function L(e,t){for(var n=-1,r=(t=I(t)).length,o=r-1,s=x(Object(e)),i=s;null!=i&&++n1?S(t,n):t}(0,o=M(s,o),e),!1}})),!o})),o||(o=i),o==t&&(o=m?S(o,1):function(){return t.apply(this,arguments)}),o.convert=B(s,t),o.placeholder=t.placeholder=n,o}if(!h)return q(n,c,d);var U=c,z=[];return _(T,(function(e){_(r.aryMethod[e],(function(e){var t=U[r.remap[e]||e];t&&z.push([e,q(e,t,U)])}))})),_(C(U),(function(e){var t=U[e];if("function"==typeof t){for(var n=z.length;n--;)if(z[n][0]==e)return;t.convert=B(e,t),z.push([e,t])}})),_(z,(function(e){U[e[0]]=e[1]})),U.convert=function(e){return U.runInContext.convert(e)(void 0)},U.placeholder=U,_(C(U),(function(e){_(r.realToAlias[e]||[],(function(t){U[t]=U[e]}))})),U}},68836:(e,t)=>{t.aliasToReal={each:"forEach",eachRight:"forEachRight",entries:"toPairs",entriesIn:"toPairsIn",extend:"assignIn",extendAll:"assignInAll",extendAllWith:"assignInAllWith",extendWith:"assignInWith",first:"head",conforms:"conformsTo",matches:"isMatch",property:"get",__:"placeholder",F:"stubFalse",T:"stubTrue",all:"every",allPass:"overEvery",always:"constant",any:"some",anyPass:"overSome",apply:"spread",assoc:"set",assocPath:"set",complement:"negate",compose:"flowRight",contains:"includes",dissoc:"unset",dissocPath:"unset",dropLast:"dropRight",dropLastWhile:"dropRightWhile",equals:"isEqual",identical:"eq",indexBy:"keyBy",init:"initial",invertObj:"invert",juxt:"over",omitAll:"omit",nAry:"ary",path:"get",pathEq:"matchesProperty",pathOr:"getOr",paths:"at",pickAll:"pick",pipe:"flow",pluck:"map",prop:"get",propEq:"matchesProperty",propOr:"getOr",props:"at",symmetricDifference:"xor",symmetricDifferenceBy:"xorBy",symmetricDifferenceWith:"xorWith",takeLast:"takeRight",takeLastWhile:"takeRightWhile",unapply:"rest",unnest:"flatten",useWith:"overArgs",where:"conformsTo",whereEq:"isMatch",zipObj:"zipObject"},t.aryMethod={1:["assignAll","assignInAll","attempt","castArray","ceil","create","curry","curryRight","defaultsAll","defaultsDeepAll","floor","flow","flowRight","fromPairs","invert","iteratee","memoize","method","mergeAll","methodOf","mixin","nthArg","over","overEvery","overSome","rest","reverse","round","runInContext","spread","template","trim","trimEnd","trimStart","uniqueId","words","zipAll"],2:["add","after","ary","assign","assignAllWith","assignIn","assignInAllWith","at","before","bind","bindAll","bindKey","chunk","cloneDeepWith","cloneWith","concat","conformsTo","countBy","curryN","curryRightN","debounce","defaults","defaultsDeep","defaultTo","delay","difference","divide","drop","dropRight","dropRightWhile","dropWhile","endsWith","eq","every","filter","find","findIndex","findKey","findLast","findLastIndex","findLastKey","flatMap","flatMapDeep","flattenDepth","forEach","forEachRight","forIn","forInRight","forOwn","forOwnRight","get","groupBy","gt","gte","has","hasIn","includes","indexOf","intersection","invertBy","invoke","invokeMap","isEqual","isMatch","join","keyBy","lastIndexOf","lt","lte","map","mapKeys","mapValues","matchesProperty","maxBy","meanBy","merge","mergeAllWith","minBy","multiply","nth","omit","omitBy","overArgs","pad","padEnd","padStart","parseInt","partial","partialRight","partition","pick","pickBy","propertyOf","pull","pullAll","pullAt","random","range","rangeRight","rearg","reject","remove","repeat","restFrom","result","sampleSize","some","sortBy","sortedIndex","sortedIndexOf","sortedLastIndex","sortedLastIndexOf","sortedUniqBy","split","spreadFrom","startsWith","subtract","sumBy","take","takeRight","takeRightWhile","takeWhile","tap","throttle","thru","times","trimChars","trimCharsEnd","trimCharsStart","truncate","union","uniqBy","uniqWith","unset","unzipWith","without","wrap","xor","zip","zipObject","zipObjectDeep"],3:["assignInWith","assignWith","clamp","differenceBy","differenceWith","findFrom","findIndexFrom","findLastFrom","findLastIndexFrom","getOr","includesFrom","indexOfFrom","inRange","intersectionBy","intersectionWith","invokeArgs","invokeArgsMap","isEqualWith","isMatchWith","flatMapDepth","lastIndexOfFrom","mergeWith","orderBy","padChars","padCharsEnd","padCharsStart","pullAllBy","pullAllWith","rangeStep","rangeStepRight","reduce","reduceRight","replace","set","slice","sortedIndexBy","sortedLastIndexBy","transform","unionBy","unionWith","update","xorBy","xorWith","zipWith"],4:["fill","setWith","updateWith"]},t.aryRearg={2:[1,0],3:[2,0,1],4:[3,2,0,1]},t.iterateeAry={dropRightWhile:1,dropWhile:1,every:1,filter:1,find:1,findFrom:1,findIndex:1,findIndexFrom:1,findKey:1,findLast:1,findLastFrom:1,findLastIndex:1,findLastIndexFrom:1,findLastKey:1,flatMap:1,flatMapDeep:1,flatMapDepth:1,forEach:1,forEachRight:1,forIn:1,forInRight:1,forOwn:1,forOwnRight:1,map:1,mapKeys:1,mapValues:1,partition:1,reduce:2,reduceRight:2,reject:1,remove:1,some:1,takeRightWhile:1,takeWhile:1,times:1,transform:2},t.iterateeRearg={mapKeys:[1],reduceRight:[1,0]},t.methodRearg={assignInAllWith:[1,0],assignInWith:[1,2,0],assignAllWith:[1,0],assignWith:[1,2,0],differenceBy:[1,2,0],differenceWith:[1,2,0],getOr:[2,1,0],intersectionBy:[1,2,0],intersectionWith:[1,2,0],isEqualWith:[1,2,0],isMatchWith:[2,1,0],mergeAllWith:[1,0],mergeWith:[1,2,0],padChars:[2,1,0],padCharsEnd:[2,1,0],padCharsStart:[2,1,0],pullAllBy:[2,1,0],pullAllWith:[2,1,0],rangeStep:[1,2,0],rangeStepRight:[1,2,0],setWith:[3,1,2,0],sortedIndexBy:[2,1,0],sortedLastIndexBy:[2,1,0],unionBy:[1,2,0],unionWith:[1,2,0],updateWith:[3,1,2,0],xorBy:[1,2,0],xorWith:[1,2,0],zipWith:[1,2,0]},t.methodSpread={assignAll:{start:0},assignAllWith:{start:0},assignInAll:{start:0},assignInAllWith:{start:0},defaultsAll:{start:0},defaultsDeepAll:{start:0},invokeArgs:{start:2},invokeArgsMap:{start:2},mergeAll:{start:0},mergeAllWith:{start:0},partial:{start:1},partialRight:{start:1},without:{start:1},zipAll:{start:0}},t.mutate={array:{fill:!0,pull:!0,pullAll:!0,pullAllBy:!0,pullAllWith:!0,pullAt:!0,remove:!0,reverse:!0},object:{assign:!0,assignAll:!0,assignAllWith:!0,assignIn:!0,assignInAll:!0,assignInAllWith:!0,assignInWith:!0,assignWith:!0,defaults:!0,defaultsAll:!0,defaultsDeep:!0,defaultsDeepAll:!0,merge:!0,mergeAll:!0,mergeAllWith:!0,mergeWith:!0},set:{set:!0,setWith:!0,unset:!0,update:!0,updateWith:!0}},t.realToAlias=function(){var e=Object.prototype.hasOwnProperty,n=t.aliasToReal,r={};for(var o in n){var s=n[o];e.call(r,s)?r[s].push(o):r[s]=[o]}return r}(),t.remap={assignAll:"assign",assignAllWith:"assignWith",assignInAll:"assignIn",assignInAllWith:"assignInWith",curryN:"curry",curryRightN:"curryRight",defaultsAll:"defaults",defaultsDeepAll:"defaultsDeep",findFrom:"find",findIndexFrom:"findIndex",findLastFrom:"findLast",findLastIndexFrom:"findLastIndex",getOr:"get",includesFrom:"includes",indexOfFrom:"indexOf",invokeArgs:"invoke",invokeArgsMap:"invokeMap",lastIndexOfFrom:"lastIndexOf",mergeAll:"merge",mergeAllWith:"mergeWith",padChars:"pad",padCharsEnd:"padEnd",padCharsStart:"padStart",propertyOf:"get",rangeStep:"range",rangeStepRight:"rangeRight",restFrom:"rest",spreadFrom:"spread",trimChars:"trim",trimCharsEnd:"trimEnd",trimCharsStart:"trimStart",zipAll:"zip"},t.skipFixed={castArray:!0,flow:!0,flowRight:!0,iteratee:!0,mixin:!0,rearg:!0,runInContext:!0},t.skipRearg={add:!0,assign:!0,assignIn:!0,bind:!0,bindKey:!0,concat:!0,difference:!0,divide:!0,eq:!0,gt:!0,gte:!0,isEqual:!0,lt:!0,lte:!0,matchesProperty:!0,merge:!0,multiply:!0,overArgs:!0,partial:!0,partialRight:!0,propertyOf:!0,random:!0,range:!0,rangeRight:!0,subtract:!0,zip:!0,zipObject:!0,zipObjectDeep:!0}},4269:(e,t,n)=>{e.exports={ary:n(39514),assign:n(44037),clone:n(66678),curry:n(40087),forEach:n(77412),isArray:n(1469),isError:n(64647),isFunction:n(23560),isWeakMap:n(81018),iteratee:n(72594),keys:n(280),rearg:n(4963),toInteger:n(40554),toPath:n(30084)}},72700:(e,t,n)=>{e.exports=n(28252)},92822:(e,t,n)=>{var r=n(84599),o=n(4269);e.exports=function(e,t,n){return r(o,e,t,n)}},69306:e=>{e.exports={}},28252:(e,t,n)=>{var r=n(92822)("set",n(36968));r.placeholder=n(69306),e.exports=r},27361:(e,t,n)=>{var r=n(97786);e.exports=function(e,t,n){var o=null==e?void 0:r(e,t);return void 0===o?n:o}},79095:(e,t,n)=>{var r=n(13),o=n(222);e.exports=function(e,t){return null!=e&&o(e,t,r)}},6557:e=>{e.exports=function(e){return e}},35694:(e,t,n)=>{var r=n(9454),o=n(37005),s=Object.prototype,i=s.hasOwnProperty,a=s.propertyIsEnumerable,l=r(function(){return arguments}())?r:function(e){return o(e)&&i.call(e,"callee")&&!a.call(e,"callee")};e.exports=l},1469:e=>{var t=Array.isArray;e.exports=t},98612:(e,t,n)=>{var r=n(23560),o=n(41780);e.exports=function(e){return null!=e&&o(e.length)&&!r(e)}},29246:(e,t,n)=>{var r=n(98612),o=n(37005);e.exports=function(e){return o(e)&&r(e)}},51584:(e,t,n)=>{var r=n(44239),o=n(37005);e.exports=function(e){return!0===e||!1===e||o(e)&&"[object Boolean]"==r(e)}},44144:(e,t,n)=>{e=n.nmd(e);var r=n(55639),o=n(95062),s=t&&!t.nodeType&&t,i=s&&e&&!e.nodeType&&e,a=i&&i.exports===s?r.Buffer:void 0,l=(a?a.isBuffer:void 0)||o;e.exports=l},41609:(e,t,n)=>{var r=n(280),o=n(98882),s=n(35694),i=n(1469),a=n(98612),l=n(44144),c=n(25726),u=n(36719),p=Object.prototype.hasOwnProperty;e.exports=function(e){if(null==e)return!0;if(a(e)&&(i(e)||"string"==typeof e||"function"==typeof e.splice||l(e)||u(e)||s(e)))return!e.length;var t=o(e);if("[object Map]"==t||"[object Set]"==t)return!e.size;if(c(e))return!r(e).length;for(var n in e)if(p.call(e,n))return!1;return!0}},18446:(e,t,n)=>{var r=n(90939);e.exports=function(e,t){return r(e,t)}},64647:(e,t,n)=>{var r=n(44239),o=n(37005),s=n(68630);e.exports=function(e){if(!o(e))return!1;var t=r(e);return"[object Error]"==t||"[object DOMException]"==t||"string"==typeof e.message&&"string"==typeof e.name&&!s(e)}},23560:(e,t,n)=>{var r=n(44239),o=n(13218);e.exports=function(e){if(!o(e))return!1;var t=r(e);return"[object Function]"==t||"[object GeneratorFunction]"==t||"[object AsyncFunction]"==t||"[object Proxy]"==t}},41780:e=>{e.exports=function(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=9007199254740991}},56688:(e,t,n)=>{var r=n(25588),o=n(7518),s=n(31167),i=s&&s.isMap,a=i?o(i):r;e.exports=a},45220:e=>{e.exports=function(e){return null===e}},81763:(e,t,n)=>{var r=n(44239),o=n(37005);e.exports=function(e){return"number"==typeof e||o(e)&&"[object Number]"==r(e)}},13218:e=>{e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},37005:e=>{e.exports=function(e){return null!=e&&"object"==typeof e}},68630:(e,t,n)=>{var r=n(44239),o=n(85924),s=n(37005),i=Function.prototype,a=Object.prototype,l=i.toString,c=a.hasOwnProperty,u=l.call(Object);e.exports=function(e){if(!s(e)||"[object Object]"!=r(e))return!1;var t=o(e);if(null===t)return!0;var n=c.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&l.call(n)==u}},72928:(e,t,n)=>{var r=n(29221),o=n(7518),s=n(31167),i=s&&s.isSet,a=i?o(i):r;e.exports=a},47037:(e,t,n)=>{var r=n(44239),o=n(1469),s=n(37005);e.exports=function(e){return"string"==typeof e||!o(e)&&s(e)&&"[object String]"==r(e)}},33448:(e,t,n)=>{var r=n(44239),o=n(37005);e.exports=function(e){return"symbol"==typeof e||o(e)&&"[object Symbol]"==r(e)}},36719:(e,t,n)=>{var r=n(38749),o=n(7518),s=n(31167),i=s&&s.isTypedArray,a=i?o(i):r;e.exports=a},81018:(e,t,n)=>{var r=n(98882),o=n(37005);e.exports=function(e){return o(e)&&"[object WeakMap]"==r(e)}},72594:(e,t,n)=>{var r=n(85990),o=n(67206);e.exports=function(e){return o("function"==typeof e?e:r(e,1))}},3674:(e,t,n)=>{var r=n(14636),o=n(280),s=n(98612);e.exports=function(e){return s(e)?r(e):o(e)}},81704:(e,t,n)=>{var r=n(14636),o=n(10313),s=n(98612);e.exports=function(e){return s(e)?r(e,!0):o(e)}},10928:e=>{e.exports=function(e){var t=null==e?0:e.length;return t?e[t-1]:void 0}},88306:(e,t,n)=>{var r=n(83369);function o(e,t){if("function"!=typeof e||null!=t&&"function"!=typeof t)throw new TypeError("Expected a function");var n=function(){var r=arguments,o=t?t.apply(this,r):r[0],s=n.cache;if(s.has(o))return s.get(o);var i=e.apply(this,r);return n.cache=s.set(o,i)||s,i};return n.cache=new(o.Cache||r),n}o.Cache=r,e.exports=o},82492:(e,t,n)=>{var r=n(42980),o=n(21463)((function(e,t,n){r(e,t,n)}));e.exports=o},94885:e=>{e.exports=function(e){if("function"!=typeof e)throw new TypeError("Expected a function");return function(){var t=arguments;switch(t.length){case 0:return!e.call(this);case 1:return!e.call(this,t[0]);case 2:return!e.call(this,t[0],t[1]);case 3:return!e.call(this,t[0],t[1],t[2])}return!e.apply(this,t)}}},50308:e=>{e.exports=function(){}},7771:(e,t,n)=>{var r=n(55639);e.exports=function(){return r.Date.now()}},57557:(e,t,n)=>{var r=n(29932),o=n(85990),s=n(57406),i=n(71811),a=n(98363),l=n(60696),c=n(99021),u=n(46904),p=c((function(e,t){var n={};if(null==e)return n;var c=!1;t=r(t,(function(t){return t=i(t,e),c||(c=t.length>1),t})),a(e,u(e),n),c&&(n=o(n,7,l));for(var p=t.length;p--;)s(n,t[p]);return n}));e.exports=p},39601:(e,t,n)=>{var r=n(40371),o=n(79152),s=n(15403),i=n(40327);e.exports=function(e){return s(e)?r(i(e)):o(e)}},4963:(e,t,n)=>{var r=n(97727),o=n(99021),s=o((function(e,t){return r(e,256,void 0,void 0,void 0,t)}));e.exports=s},54061:(e,t,n)=>{var r=n(62663),o=n(89881),s=n(67206),i=n(10107),a=n(1469);e.exports=function(e,t,n){var l=a(e)?r:i,c=arguments.length<3;return l(e,s(t,4),n,c,o)}},36968:(e,t,n)=>{var r=n(10611);e.exports=function(e,t,n){return null==e?e:r(e,t,n)}},59704:(e,t,n)=>{var r=n(82908),o=n(67206),s=n(5076),i=n(1469),a=n(16612);e.exports=function(e,t,n){var l=i(e)?r:s;return n&&a(e,t,n)&&(t=void 0),l(e,o(t,3))}},70479:e=>{e.exports=function(){return[]}},95062:e=>{e.exports=function(){return!1}},18601:(e,t,n)=>{var r=n(14841),o=1/0;e.exports=function(e){return e?(e=r(e))===o||e===-1/0?17976931348623157e292*(e<0?-1:1):e==e?e:0:0===e?e:0}},40554:(e,t,n)=>{var r=n(18601);e.exports=function(e){var t=r(e),n=t%1;return t==t?n?t-n:t:0}},7334:(e,t,n)=>{var r=n(79833);e.exports=function(e){return r(e).toLowerCase()}},14841:(e,t,n)=>{var r=n(27561),o=n(13218),s=n(33448),i=/^[-+]0x[0-9a-f]+$/i,a=/^0b[01]+$/i,l=/^0o[0-7]+$/i,c=parseInt;e.exports=function(e){if("number"==typeof e)return e;if(s(e))return NaN;if(o(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=o(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=r(e);var n=a.test(e);return n||l.test(e)?c(e.slice(2),n?2:8):i.test(e)?NaN:+e}},30084:(e,t,n)=>{var r=n(29932),o=n(278),s=n(1469),i=n(33448),a=n(55514),l=n(40327),c=n(79833);e.exports=function(e){return s(e)?r(e,l):i(e)?[e]:o(a(c(e)))}},59881:(e,t,n)=>{var r=n(98363),o=n(81704);e.exports=function(e){return r(e,o(e))}},79833:(e,t,n)=>{var r=n(80531);e.exports=function(e){return null==e?"":r(e)}},11700:(e,t,n)=>{var r=n(98805)("toUpperCase");e.exports=r},58748:(e,t,n)=>{var r=n(49029),o=n(93157),s=n(79833),i=n(2757);e.exports=function(e,t,n){return e=s(e),void 0===(t=n?void 0:t)?o(e)?i(e):r(e):e.match(t)||[]}},8111:(e,t,n)=>{var r=n(96425),o=n(7548),s=n(9435),i=n(1469),a=n(37005),l=n(21913),c=Object.prototype.hasOwnProperty;function u(e){if(a(e)&&!i(e)&&!(e instanceof r)){if(e instanceof o)return e;if(c.call(e,"__wrapped__"))return l(e)}return new o(e)}u.prototype=s.prototype,u.prototype.constructor=u,e.exports=u},7287:(e,t,n)=>{var r=n(34865),o=n(1757);e.exports=function(e,t){return o(e||[],t||[],r)}},96470:(e,t,n)=>{"use strict";var r=n(47802),o=n(21102);t.highlight=i,t.highlightAuto=function(e,t){var n,a,l,c,u=t||{},p=u.subset||r.listLanguages(),h=u.prefix,f=p.length,d=-1;null==h&&(h=s);if("string"!=typeof e)throw o("Expected `string` for value, got `%s`",e);a={relevance:0,language:null,value:[]},n={relevance:0,language:null,value:[]};for(;++da.relevance&&(a=l),l.relevance>n.relevance&&(a=n,n=l));a.language&&(n.secondBest=a);return n},t.registerLanguage=function(e,t){r.registerLanguage(e,t)},t.listLanguages=function(){return r.listLanguages()},t.registerAlias=function(e,t){var n,o=e;t&&((o={})[e]=t);for(n in o)r.registerAliases(o[n],{languageName:n})},a.prototype.addText=function(e){var t,n,r=this.stack;if(""===e)return;t=r[r.length-1],(n=t.children[t.children.length-1])&&"text"===n.type?n.value+=e:t.children.push({type:"text",value:e})},a.prototype.addKeyword=function(e,t){this.openNode(t),this.addText(e),this.closeNode()},a.prototype.addSublanguage=function(e,t){var n=this.stack,r=n[n.length-1],o=e.rootNode.children,s=t?{type:"element",tagName:"span",properties:{className:[t]},children:o}:o;r.children=r.children.concat(s)},a.prototype.openNode=function(e){var t=this.stack,n=this.options.classPrefix+e,r=t[t.length-1],o={type:"element",tagName:"span",properties:{className:[n]},children:[]};r.children.push(o),t.push(o)},a.prototype.closeNode=function(){this.stack.pop()},a.prototype.closeAllNodes=l,a.prototype.finalize=l,a.prototype.toHTML=function(){return""};var s="hljs-";function i(e,t,n){var i,l=r.configure({}),c=(n||{}).prefix;if("string"!=typeof e)throw o("Expected `string` for name, got `%s`",e);if(!r.getLanguage(e))throw o("Unknown language: `%s` is not registered",e);if("string"!=typeof t)throw o("Expected `string` for value, got `%s`",t);if(null==c&&(c=s),r.configure({__emitter:a,classPrefix:c}),i=r.highlight(t,{language:e,ignoreIllegals:!0}),r.configure(l||{}),i.errorRaised)throw i.errorRaised;return{relevance:i.relevance,language:i.language,value:i.emitter.rootNode.children}}function a(e){this.options=e,this.rootNode={children:[]},this.stack=[this.rootNode]}function l(){}},42566:(e,t,n)=>{const r=n(94885);function o(e){return"string"==typeof e?t=>t.element===e:e.constructor&&e.extend?t=>t instanceof e:e}class s{constructor(e){this.elements=e||[]}toValue(){return this.elements.map((e=>e.toValue()))}map(e,t){return this.elements.map(e,t)}flatMap(e,t){return this.map(e,t).reduce(((e,t)=>e.concat(t)),[])}compactMap(e,t){const n=[];return this.forEach((r=>{const o=e.bind(t)(r);o&&n.push(o)})),n}filter(e,t){return e=o(e),new s(this.elements.filter(e,t))}reject(e,t){return e=o(e),new s(this.elements.filter(r(e),t))}find(e,t){return e=o(e),this.elements.find(e,t)}forEach(e,t){this.elements.forEach(e,t)}reduce(e,t){return this.elements.reduce(e,t)}includes(e){return this.elements.some((t=>t.equals(e)))}shift(){return this.elements.shift()}unshift(e){this.elements.unshift(this.refract(e))}push(e){return this.elements.push(this.refract(e)),this}add(e){this.push(e)}get(e){return this.elements[e]}getValue(e){const t=this.elements[e];if(t)return t.toValue()}get length(){return this.elements.length}get isEmpty(){return 0===this.elements.length}get first(){return this.elements[0]}}"undefined"!=typeof Symbol&&(s.prototype[Symbol.iterator]=function(){return this.elements[Symbol.iterator]()}),e.exports=s},17645:e=>{class t{constructor(e,t){this.key=e,this.value=t}clone(){const e=new t;return this.key&&(e.key=this.key.clone()),this.value&&(e.value=this.value.clone()),e}}e.exports=t},78520:(e,t,n)=>{const r=n(45220),o=n(47037),s=n(81763),i=n(51584),a=n(13218),l=n(28219),c=n(99829);class u{constructor(e){this.elementMap={},this.elementDetection=[],this.Element=c.Element,this.KeyValuePair=c.KeyValuePair,e&&e.noDefault||this.useDefault(),this._attributeElementKeys=[],this._attributeElementArrayKeys=[]}use(e){return e.namespace&&e.namespace({base:this}),e.load&&e.load({base:this}),this}useDefault(){return this.register("null",c.NullElement).register("string",c.StringElement).register("number",c.NumberElement).register("boolean",c.BooleanElement).register("array",c.ArrayElement).register("object",c.ObjectElement).register("member",c.MemberElement).register("ref",c.RefElement).register("link",c.LinkElement),this.detect(r,c.NullElement,!1).detect(o,c.StringElement,!1).detect(s,c.NumberElement,!1).detect(i,c.BooleanElement,!1).detect(Array.isArray,c.ArrayElement,!1).detect(a,c.ObjectElement,!1),this}register(e,t){return this._elements=void 0,this.elementMap[e]=t,this}unregister(e){return this._elements=void 0,delete this.elementMap[e],this}detect(e,t,n){return void 0===n||n?this.elementDetection.unshift([e,t]):this.elementDetection.push([e,t]),this}toElement(e){if(e instanceof this.Element)return e;let t;for(let n=0;n{const t=e[0].toUpperCase()+e.substr(1);this._elements[t]=this.elementMap[e]}))),this._elements}get serialiser(){return new l(this)}}l.prototype.Namespace=u,e.exports=u},87526:(e,t,n)=>{const r=n(94885),o=n(42566);class s extends o{map(e,t){return this.elements.map((n=>e.bind(t)(n.value,n.key,n)))}filter(e,t){return new s(this.elements.filter((n=>e.bind(t)(n.value,n.key,n))))}reject(e,t){return this.filter(r(e.bind(t)))}forEach(e,t){return this.elements.forEach(((n,r)=>{e.bind(t)(n.value,n.key,n,r)}))}keys(){return this.map(((e,t)=>t.toValue()))}values(){return this.map((e=>e.toValue()))}}e.exports=s},99829:(e,t,n)=>{const r=n(3079),o=n(96295),s=n(16036),i=n(91090),a=n(18866),l=n(35804),c=n(5946),u=n(76735),p=n(59964),h=n(38588),f=n(42566),d=n(87526),m=n(17645);function g(e){if(e instanceof r)return e;if("string"==typeof e)return new s(e);if("number"==typeof e)return new i(e);if("boolean"==typeof e)return new a(e);if(null===e)return new o;if(Array.isArray(e))return new l(e.map(g));if("object"==typeof e){return new u(e)}return e}r.prototype.ObjectElement=u,r.prototype.RefElement=h,r.prototype.MemberElement=c,r.prototype.refract=g,f.prototype.refract=g,e.exports={Element:r,NullElement:o,StringElement:s,NumberElement:i,BooleanElement:a,ArrayElement:l,MemberElement:c,ObjectElement:u,LinkElement:p,RefElement:h,refract:g,ArraySlice:f,ObjectSlice:d,KeyValuePair:m}},59964:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e||[],t,n),this.element="link"}get relation(){return this.attributes.get("relation")}set relation(e){this.attributes.set("relation",e)}get href(){return this.attributes.get("href")}set href(e){this.attributes.set("href",e)}}},38588:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e||[],t,n),this.element="ref",this.path||(this.path="element")}get path(){return this.attributes.get("path")}set path(e){this.attributes.set("path",e)}}},43500:(e,t,n)=>{const r=n(78520),o=n(99829);t.lS=r,n(17645),t.O4=o.ArraySlice,o.ObjectSlice,t.W_=o.Element,t.RP=o.StringElement,t.VL=o.NumberElement,t.hh=o.BooleanElement,t.zr=o.NullElement,t.ON=o.ArrayElement,t.Sb=o.ObjectElement,t.c6=o.MemberElement,t.tK=o.RefElement,t.EA=o.LinkElement,t.Qc=o.refract,n(28219),n(3414)},35804:(e,t,n)=>{const r=n(94885),o=n(3079),s=n(42566);class i extends o{constructor(e,t,n){super(e||[],t,n),this.element="array"}primitive(){return"array"}get(e){return this.content[e]}getValue(e){const t=this.get(e);if(t)return t.toValue()}getIndex(e){return this.content[e]}set(e,t){return this.content[e]=this.refract(t),this}remove(e){const t=this.content.splice(e,1);return t.length?t[0]:null}map(e,t){return this.content.map(e,t)}flatMap(e,t){return this.map(e,t).reduce(((e,t)=>e.concat(t)),[])}compactMap(e,t){const n=[];return this.forEach((r=>{const o=e.bind(t)(r);o&&n.push(o)})),n}filter(e,t){return new s(this.content.filter(e,t))}reject(e,t){return this.filter(r(e),t)}reduce(e,t){let n,r;void 0!==t?(n=0,r=this.refract(t)):(n=1,r="object"===this.primitive()?this.first.value:this.first);for(let t=n;t{e.bind(t)(n,this.refract(r))}))}shift(){return this.content.shift()}unshift(e){this.content.unshift(this.refract(e))}push(e){return this.content.push(this.refract(e)),this}add(e){this.push(e)}findElements(e,t){const n=t||{},r=!!n.recursive,o=void 0===n.results?[]:n.results;return this.forEach(((t,n,s)=>{r&&void 0!==t.findElements&&t.findElements(e,{results:o,recursive:r}),e(t,n,s)&&o.push(t)})),o}find(e){return new s(this.findElements(e,{recursive:!0}))}findByElement(e){return this.find((t=>t.element===e))}findByClass(e){return this.find((t=>t.classes.includes(e)))}getById(e){return this.find((t=>t.id.toValue()===e)).first}includes(e){return this.content.some((t=>t.equals(e)))}contains(e){return this.includes(e)}empty(){return new this.constructor([])}"fantasy-land/empty"(){return this.empty()}concat(e){return new this.constructor(this.content.concat(e.content))}"fantasy-land/concat"(e){return this.concat(e)}"fantasy-land/map"(e){return new this.constructor(this.map(e))}"fantasy-land/chain"(e){return this.map((t=>e(t)),this).reduce(((e,t)=>e.concat(t)),this.empty())}"fantasy-land/filter"(e){return new this.constructor(this.content.filter(e))}"fantasy-land/reduce"(e,t){return this.content.reduce(e,t)}get length(){return this.content.length}get isEmpty(){return 0===this.content.length}get first(){return this.getIndex(0)}get second(){return this.getIndex(1)}get last(){return this.getIndex(this.length-1)}}i.empty=function(){return new this},i["fantasy-land/empty"]=i.empty,"undefined"!=typeof Symbol&&(i.prototype[Symbol.iterator]=function(){return this.content[Symbol.iterator]()}),e.exports=i},18866:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e,t,n),this.element="boolean"}primitive(){return"boolean"}}},3079:(e,t,n)=>{const r=n(18446),o=n(17645),s=n(42566);class i{constructor(e,t,n){t&&(this.meta=t),n&&(this.attributes=n),this.content=e}freeze(){Object.isFrozen(this)||(this._meta&&(this.meta.parent=this,this.meta.freeze()),this._attributes&&(this.attributes.parent=this,this.attributes.freeze()),this.children.forEach((e=>{e.parent=this,e.freeze()}),this),this.content&&Array.isArray(this.content)&&Object.freeze(this.content),Object.freeze(this))}primitive(){}clone(){const e=new this.constructor;return e.element=this.element,this.meta.length&&(e._meta=this.meta.clone()),this.attributes.length&&(e._attributes=this.attributes.clone()),this.content?this.content.clone?e.content=this.content.clone():Array.isArray(this.content)?e.content=this.content.map((e=>e.clone())):e.content=this.content:e.content=this.content,e}toValue(){return this.content instanceof i?this.content.toValue():this.content instanceof o?{key:this.content.key.toValue(),value:this.content.value?this.content.value.toValue():void 0}:this.content&&this.content.map?this.content.map((e=>e.toValue()),this):this.content}toRef(e){if(""===this.id.toValue())throw Error("Cannot create reference to an element that does not contain an ID");const t=new this.RefElement(this.id.toValue());return e&&(t.path=e),t}findRecursive(...e){if(arguments.length>1&&!this.isFrozen)throw new Error("Cannot find recursive with multiple element names without first freezing the element. Call `element.freeze()`");const t=e.pop();let n=new s;const r=(e,t)=>(e.push(t),e),i=(e,n)=>{n.element===t&&e.push(n);const s=n.findRecursive(t);return s&&s.reduce(r,e),n.content instanceof o&&(n.content.key&&i(e,n.content.key),n.content.value&&i(e,n.content.value)),e};return this.content&&(this.content.element&&i(n,this.content),Array.isArray(this.content)&&this.content.reduce(i,n)),e.isEmpty||(n=n.filter((t=>{let n=t.parents.map((e=>e.element));for(const t in e){const r=e[t],o=n.indexOf(r);if(-1===o)return!1;n=n.splice(0,o)}return!0}))),n}set(e){return this.content=e,this}equals(e){return r(this.toValue(),e)}getMetaProperty(e,t){if(!this.meta.hasKey(e)){if(this.isFrozen){const e=this.refract(t);return e.freeze(),e}this.meta.set(e,t)}return this.meta.get(e)}setMetaProperty(e,t){this.meta.set(e,t)}get element(){return this._storedElement||"element"}set element(e){this._storedElement=e}get content(){return this._content}set content(e){if(e instanceof i)this._content=e;else if(e instanceof s)this.content=e.elements;else if("string"==typeof e||"number"==typeof e||"boolean"==typeof e||"null"===e||null==e)this._content=e;else if(e instanceof o)this._content=e;else if(Array.isArray(e))this._content=e.map(this.refract);else{if("object"!=typeof e)throw new Error("Cannot set content to given value");this._content=Object.keys(e).map((t=>new this.MemberElement(t,e[t])))}}get meta(){if(!this._meta){if(this.isFrozen){const e=new this.ObjectElement;return e.freeze(),e}this._meta=new this.ObjectElement}return this._meta}set meta(e){e instanceof this.ObjectElement?this._meta=e:this.meta.set(e||{})}get attributes(){if(!this._attributes){if(this.isFrozen){const e=new this.ObjectElement;return e.freeze(),e}this._attributes=new this.ObjectElement}return this._attributes}set attributes(e){e instanceof this.ObjectElement?this._attributes=e:this.attributes.set(e||{})}get id(){return this.getMetaProperty("id","")}set id(e){this.setMetaProperty("id",e)}get classes(){return this.getMetaProperty("classes",[])}set classes(e){this.setMetaProperty("classes",e)}get title(){return this.getMetaProperty("title","")}set title(e){this.setMetaProperty("title",e)}get description(){return this.getMetaProperty("description","")}set description(e){this.setMetaProperty("description",e)}get links(){return this.getMetaProperty("links",[])}set links(e){this.setMetaProperty("links",e)}get isFrozen(){return Object.isFrozen(this)}get parents(){let{parent:e}=this;const t=new s;for(;e;)t.push(e),e=e.parent;return t}get children(){if(Array.isArray(this.content))return new s(this.content);if(this.content instanceof o){const e=new s([this.content.key]);return this.content.value&&e.push(this.content.value),e}return this.content instanceof i?new s([this.content]):new s}get recursiveChildren(){const e=new s;return this.children.forEach((t=>{e.push(t),t.recursiveChildren.forEach((t=>{e.push(t)}))})),e}}e.exports=i},5946:(e,t,n)=>{const r=n(17645),o=n(3079);e.exports=class extends o{constructor(e,t,n,o){super(new r,n,o),this.element="member",this.key=e,this.value=t}get key(){return this.content.key}set key(e){this.content.key=this.refract(e)}get value(){return this.content.value}set value(e){this.content.value=this.refract(e)}}},96295:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e||null,t,n),this.element="null"}primitive(){return"null"}set(){return new Error("Cannot set the value of null")}}},91090:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e,t,n),this.element="number"}primitive(){return"number"}}},76735:(e,t,n)=>{const r=n(94885),o=n(13218),s=n(35804),i=n(5946),a=n(87526);e.exports=class extends s{constructor(e,t,n){super(e||[],t,n),this.element="object"}primitive(){return"object"}toValue(){return this.content.reduce(((e,t)=>(e[t.key.toValue()]=t.value?t.value.toValue():void 0,e)),{})}get(e){const t=this.getMember(e);if(t)return t.value}getMember(e){if(void 0!==e)return this.content.find((t=>t.key.toValue()===e))}remove(e){let t=null;return this.content=this.content.filter((n=>n.key.toValue()!==e||(t=n,!1))),t}getKey(e){const t=this.getMember(e);if(t)return t.key}set(e,t){if(o(e))return Object.keys(e).forEach((t=>{this.set(t,e[t])})),this;const n=e,r=this.getMember(n);return r?r.value=t:this.content.push(new i(n,t)),this}keys(){return this.content.map((e=>e.key.toValue()))}values(){return this.content.map((e=>e.value.toValue()))}hasKey(e){return this.content.some((t=>t.key.equals(e)))}items(){return this.content.map((e=>[e.key.toValue(),e.value.toValue()]))}map(e,t){return this.content.map((n=>e.bind(t)(n.value,n.key,n)))}compactMap(e,t){const n=[];return this.forEach(((r,o,s)=>{const i=e.bind(t)(r,o,s);i&&n.push(i)})),n}filter(e,t){return new a(this.content).filter(e,t)}reject(e,t){return this.filter(r(e),t)}forEach(e,t){return this.content.forEach((n=>e.bind(t)(n.value,n.key,n)))}}},16036:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e,t,n),this.element="string"}primitive(){return"string"}get length(){return this.content.length}}},3414:(e,t,n)=>{const r=n(28219);e.exports=class extends r{serialise(e){if(!(e instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${e}\` is not an Element instance`);let t;e._attributes&&e.attributes.get("variable")&&(t=e.attributes.get("variable"));const n={element:e.element};e._meta&&e._meta.length>0&&(n.meta=this.serialiseObject(e.meta));const r="enum"===e.element||-1!==e.attributes.keys().indexOf("enumerations");if(r){const t=this.enumSerialiseAttributes(e);t&&(n.attributes=t)}else if(e._attributes&&e._attributes.length>0){let{attributes:r}=e;r.get("metadata")&&(r=r.clone(),r.set("meta",r.get("metadata")),r.remove("metadata")),"member"===e.element&&t&&(r=r.clone(),r.remove("variable")),r.length>0&&(n.attributes=this.serialiseObject(r))}if(r)n.content=this.enumSerialiseContent(e,n);else if(this[`${e.element}SerialiseContent`])n.content=this[`${e.element}SerialiseContent`](e,n);else if(void 0!==e.content){let r;t&&e.content.key?(r=e.content.clone(),r.key.attributes.set("variable",t),r=this.serialiseContent(r)):r=this.serialiseContent(e.content),this.shouldSerialiseContent(e,r)&&(n.content=r)}else this.shouldSerialiseContent(e,e.content)&&e instanceof this.namespace.elements.Array&&(n.content=[]);return n}shouldSerialiseContent(e,t){return"parseResult"===e.element||"httpRequest"===e.element||"httpResponse"===e.element||"category"===e.element||"link"===e.element||void 0!==t&&(!Array.isArray(t)||0!==t.length)}refSerialiseContent(e,t){return delete t.attributes,{href:e.toValue(),path:e.path.toValue()}}sourceMapSerialiseContent(e){return e.toValue()}dataStructureSerialiseContent(e){return[this.serialiseContent(e.content)]}enumSerialiseAttributes(e){const t=e.attributes.clone(),n=t.remove("enumerations")||new this.namespace.elements.Array([]),r=t.get("default");let o=t.get("samples")||new this.namespace.elements.Array([]);if(r&&r.content&&(r.content.attributes&&r.content.attributes.remove("typeAttributes"),t.set("default",new this.namespace.elements.Array([r.content]))),o.forEach((e=>{e.content&&e.content.element&&e.content.attributes.remove("typeAttributes")})),e.content&&0!==n.length&&o.unshift(e.content),o=o.map((e=>e instanceof this.namespace.elements.Array?[e]:new this.namespace.elements.Array([e.content]))),o.length&&t.set("samples",o),t.length>0)return this.serialiseObject(t)}enumSerialiseContent(e){if(e._attributes){const t=e.attributes.get("enumerations");if(t&&t.length>0)return t.content.map((e=>{const t=e.clone();return t.attributes.remove("typeAttributes"),this.serialise(t)}))}if(e.content){const t=e.content.clone();return t.attributes.remove("typeAttributes"),[this.serialise(t)]}return[]}deserialise(e){if("string"==typeof e)return new this.namespace.elements.String(e);if("number"==typeof e)return new this.namespace.elements.Number(e);if("boolean"==typeof e)return new this.namespace.elements.Boolean(e);if(null===e)return new this.namespace.elements.Null;if(Array.isArray(e))return new this.namespace.elements.Array(e.map(this.deserialise,this));const t=this.namespace.getElementClass(e.element),n=new t;n.element!==e.element&&(n.element=e.element),e.meta&&this.deserialiseObject(e.meta,n.meta),e.attributes&&this.deserialiseObject(e.attributes,n.attributes);const r=this.deserialiseContent(e.content);if(void 0===r&&null!==n.content||(n.content=r),"enum"===n.element){n.content&&n.attributes.set("enumerations",n.content);let e=n.attributes.get("samples");if(n.attributes.remove("samples"),e){const r=e;e=new this.namespace.elements.Array,r.forEach((r=>{r.forEach((r=>{const o=new t(r);o.element=n.element,e.push(o)}))}));const o=e.shift();n.content=o?o.content:void 0,n.attributes.set("samples",e)}else n.content=void 0;let r=n.attributes.get("default");if(r&&r.length>0){r=r.get(0);const e=new t(r);e.element=n.element,n.attributes.set("default",e)}}else if("dataStructure"===n.element&&Array.isArray(n.content))[n.content]=n.content;else if("category"===n.element){const e=n.attributes.get("meta");e&&(n.attributes.set("metadata",e),n.attributes.remove("meta"))}else"member"===n.element&&n.key&&n.key._attributes&&n.key._attributes.getValue("variable")&&(n.attributes.set("variable",n.key.attributes.get("variable")),n.key.attributes.remove("variable"));return n}serialiseContent(e){if(e instanceof this.namespace.elements.Element)return this.serialise(e);if(e instanceof this.namespace.KeyValuePair){const t={key:this.serialise(e.key)};return e.value&&(t.value=this.serialise(e.value)),t}return e&&e.map?e.map(this.serialise,this):e}deserialiseContent(e){if(e){if(e.element)return this.deserialise(e);if(e.key){const t=new this.namespace.KeyValuePair(this.deserialise(e.key));return e.value&&(t.value=this.deserialise(e.value)),t}if(e.map)return e.map(this.deserialise,this)}return e}shouldRefract(e){return!!(e._attributes&&e.attributes.keys().length||e._meta&&e.meta.keys().length)||"enum"!==e.element&&(e.element!==e.primitive()||"member"===e.element)}convertKeyToRefract(e,t){return this.shouldRefract(t)?this.serialise(t):"enum"===t.element?this.serialiseEnum(t):"array"===t.element?t.map((t=>this.shouldRefract(t)||"default"===e?this.serialise(t):"array"===t.element||"object"===t.element||"enum"===t.element?t.children.map((e=>this.serialise(e))):t.toValue())):"object"===t.element?(t.content||[]).map(this.serialise,this):t.toValue()}serialiseEnum(e){return e.children.map((e=>this.serialise(e)))}serialiseObject(e){const t={};return e.forEach(((e,n)=>{if(e){const r=n.toValue();t[r]=this.convertKeyToRefract(r,e)}})),t}deserialiseObject(e,t){Object.keys(e).forEach((n=>{t.set(n,this.deserialise(e[n]))}))}}},28219:e=>{e.exports=class{constructor(e){this.namespace=e||new this.Namespace}serialise(e){if(!(e instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${e}\` is not an Element instance`);const t={element:e.element};e._meta&&e._meta.length>0&&(t.meta=this.serialiseObject(e.meta)),e._attributes&&e._attributes.length>0&&(t.attributes=this.serialiseObject(e.attributes));const n=this.serialiseContent(e.content);return void 0!==n&&(t.content=n),t}deserialise(e){if(!e.element)throw new Error("Given value is not an object containing an element name");const t=new(this.namespace.getElementClass(e.element));t.element!==e.element&&(t.element=e.element),e.meta&&this.deserialiseObject(e.meta,t.meta),e.attributes&&this.deserialiseObject(e.attributes,t.attributes);const n=this.deserialiseContent(e.content);return void 0===n&&null!==t.content||(t.content=n),t}serialiseContent(e){if(e instanceof this.namespace.elements.Element)return this.serialise(e);if(e instanceof this.namespace.KeyValuePair){const t={key:this.serialise(e.key)};return e.value&&(t.value=this.serialise(e.value)),t}if(e&&e.map){if(0===e.length)return;return e.map(this.serialise,this)}return e}deserialiseContent(e){if(e){if(e.element)return this.deserialise(e);if(e.key){const t=new this.namespace.KeyValuePair(this.deserialise(e.key));return e.value&&(t.value=this.deserialise(e.value)),t}if(e.map)return e.map(this.deserialise,this)}return e}serialiseObject(e){const t={};if(e.forEach(((e,n)=>{e&&(t[n.toValue()]=this.serialise(e))})),0!==Object.keys(t).length)return t}deserialiseObject(e,t){Object.keys(e).forEach((n=>{t.set(n,this.deserialise(e[n]))}))}}},27418:e=>{"use strict";var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,o){for(var s,i,a=function(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}(e),l=1;l{var r="function"==typeof Map&&Map.prototype,o=Object.getOwnPropertyDescriptor&&r?Object.getOwnPropertyDescriptor(Map.prototype,"size"):null,s=r&&o&&"function"==typeof o.get?o.get:null,i=r&&Map.prototype.forEach,a="function"==typeof Set&&Set.prototype,l=Object.getOwnPropertyDescriptor&&a?Object.getOwnPropertyDescriptor(Set.prototype,"size"):null,c=a&&l&&"function"==typeof l.get?l.get:null,u=a&&Set.prototype.forEach,p="function"==typeof WeakMap&&WeakMap.prototype?WeakMap.prototype.has:null,h="function"==typeof WeakSet&&WeakSet.prototype?WeakSet.prototype.has:null,f="function"==typeof WeakRef&&WeakRef.prototype?WeakRef.prototype.deref:null,d=Boolean.prototype.valueOf,m=Object.prototype.toString,g=Function.prototype.toString,y=String.prototype.match,v=String.prototype.slice,b=String.prototype.replace,w=String.prototype.toUpperCase,E=String.prototype.toLowerCase,x=RegExp.prototype.test,S=Array.prototype.concat,_=Array.prototype.join,j=Array.prototype.slice,O=Math.floor,k="function"==typeof BigInt?BigInt.prototype.valueOf:null,A=Object.getOwnPropertySymbols,C="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?Symbol.prototype.toString:null,P="function"==typeof Symbol&&"object"==typeof Symbol.iterator,N="function"==typeof Symbol&&Symbol.toStringTag&&(typeof Symbol.toStringTag===P||"symbol")?Symbol.toStringTag:null,I=Object.prototype.propertyIsEnumerable,T=("function"==typeof Reflect?Reflect.getPrototypeOf:Object.getPrototypeOf)||([].__proto__===Array.prototype?function(e){return e.__proto__}:null);function R(e,t){if(e===1/0||e===-1/0||e!=e||e&&e>-1e3&&e<1e3||x.call(/e/,t))return t;var n=/[0-9](?=(?:[0-9]{3})+(?![0-9]))/g;if("number"==typeof e){var r=e<0?-O(-e):O(e);if(r!==e){var o=String(r),s=v.call(t,o.length+1);return b.call(o,n,"$&_")+"."+b.call(b.call(s,/([0-9]{3})/g,"$&_"),/_$/,"")}}return b.call(t,n,"$&_")}var M=n(24654),D=M.custom,F=U(D)?D:null;function L(e,t,n){var r="double"===(n.quoteStyle||t)?'"':"'";return r+e+r}function B(e){return b.call(String(e),/"/g,""")}function $(e){return!("[object Array]"!==W(e)||N&&"object"==typeof e&&N in e)}function q(e){return!("[object RegExp]"!==W(e)||N&&"object"==typeof e&&N in e)}function U(e){if(P)return e&&"object"==typeof e&&e instanceof Symbol;if("symbol"==typeof e)return!0;if(!e||"object"!=typeof e||!C)return!1;try{return C.call(e),!0}catch(e){}return!1}e.exports=function e(t,n,r,o){var a=n||{};if(V(a,"quoteStyle")&&"single"!==a.quoteStyle&&"double"!==a.quoteStyle)throw new TypeError('option "quoteStyle" must be "single" or "double"');if(V(a,"maxStringLength")&&("number"==typeof a.maxStringLength?a.maxStringLength<0&&a.maxStringLength!==1/0:null!==a.maxStringLength))throw new TypeError('option "maxStringLength", if provided, must be a positive integer, Infinity, or `null`');var l=!V(a,"customInspect")||a.customInspect;if("boolean"!=typeof l&&"symbol"!==l)throw new TypeError("option \"customInspect\", if provided, must be `true`, `false`, or `'symbol'`");if(V(a,"indent")&&null!==a.indent&&"\t"!==a.indent&&!(parseInt(a.indent,10)===a.indent&&a.indent>0))throw new TypeError('option "indent" must be "\\t", an integer > 0, or `null`');if(V(a,"numericSeparator")&&"boolean"!=typeof a.numericSeparator)throw new TypeError('option "numericSeparator", if provided, must be `true` or `false`');var m=a.numericSeparator;if(void 0===t)return"undefined";if(null===t)return"null";if("boolean"==typeof t)return t?"true":"false";if("string"==typeof t)return K(t,a);if("number"==typeof t){if(0===t)return 1/0/t>0?"0":"-0";var w=String(t);return m?R(t,w):w}if("bigint"==typeof t){var x=String(t)+"n";return m?R(t,x):x}var O=void 0===a.depth?5:a.depth;if(void 0===r&&(r=0),r>=O&&O>0&&"object"==typeof t)return $(t)?"[Array]":"[Object]";var A=function(e,t){var n;if("\t"===e.indent)n="\t";else{if(!("number"==typeof e.indent&&e.indent>0))return null;n=_.call(Array(e.indent+1)," ")}return{base:n,prev:_.call(Array(t+1),n)}}(a,r);if(void 0===o)o=[];else if(J(o,t)>=0)return"[Circular]";function D(t,n,s){if(n&&(o=j.call(o)).push(n),s){var i={depth:a.depth};return V(a,"quoteStyle")&&(i.quoteStyle=a.quoteStyle),e(t,i,r+1,o)}return e(t,a,r+1,o)}if("function"==typeof t&&!q(t)){var z=function(e){if(e.name)return e.name;var t=y.call(g.call(e),/^function\s*([\w$]+)/);if(t)return t[1];return null}(t),H=Q(t,D);return"[Function"+(z?": "+z:" (anonymous)")+"]"+(H.length>0?" { "+_.call(H,", ")+" }":"")}if(U(t)){var ee=P?b.call(String(t),/^(Symbol\(.*\))_[^)]*$/,"$1"):C.call(t);return"object"!=typeof t||P?ee:G(ee)}if(function(e){if(!e||"object"!=typeof e)return!1;if("undefined"!=typeof HTMLElement&&e instanceof HTMLElement)return!0;return"string"==typeof e.nodeName&&"function"==typeof e.getAttribute}(t)){for(var te="<"+E.call(String(t.nodeName)),ne=t.attributes||[],re=0;re"}if($(t)){if(0===t.length)return"[]";var oe=Q(t,D);return A&&!function(e){for(var t=0;t=0)return!1;return!0}(oe)?"["+X(oe,A)+"]":"[ "+_.call(oe,", ")+" ]"}if(function(e){return!("[object Error]"!==W(e)||N&&"object"==typeof e&&N in e)}(t)){var se=Q(t,D);return"cause"in Error.prototype||!("cause"in t)||I.call(t,"cause")?0===se.length?"["+String(t)+"]":"{ ["+String(t)+"] "+_.call(se,", ")+" }":"{ ["+String(t)+"] "+_.call(S.call("[cause]: "+D(t.cause),se),", ")+" }"}if("object"==typeof t&&l){if(F&&"function"==typeof t[F]&&M)return M(t,{depth:O-r});if("symbol"!==l&&"function"==typeof t.inspect)return t.inspect()}if(function(e){if(!s||!e||"object"!=typeof e)return!1;try{s.call(e);try{c.call(e)}catch(e){return!0}return e instanceof Map}catch(e){}return!1}(t)){var ie=[];return i&&i.call(t,(function(e,n){ie.push(D(n,t,!0)+" => "+D(e,t))})),Y("Map",s.call(t),ie,A)}if(function(e){if(!c||!e||"object"!=typeof e)return!1;try{c.call(e);try{s.call(e)}catch(e){return!0}return e instanceof Set}catch(e){}return!1}(t)){var ae=[];return u&&u.call(t,(function(e){ae.push(D(e,t))})),Y("Set",c.call(t),ae,A)}if(function(e){if(!p||!e||"object"!=typeof e)return!1;try{p.call(e,p);try{h.call(e,h)}catch(e){return!0}return e instanceof WeakMap}catch(e){}return!1}(t))return Z("WeakMap");if(function(e){if(!h||!e||"object"!=typeof e)return!1;try{h.call(e,h);try{p.call(e,p)}catch(e){return!0}return e instanceof WeakSet}catch(e){}return!1}(t))return Z("WeakSet");if(function(e){if(!f||!e||"object"!=typeof e)return!1;try{return f.call(e),!0}catch(e){}return!1}(t))return Z("WeakRef");if(function(e){return!("[object Number]"!==W(e)||N&&"object"==typeof e&&N in e)}(t))return G(D(Number(t)));if(function(e){if(!e||"object"!=typeof e||!k)return!1;try{return k.call(e),!0}catch(e){}return!1}(t))return G(D(k.call(t)));if(function(e){return!("[object Boolean]"!==W(e)||N&&"object"==typeof e&&N in e)}(t))return G(d.call(t));if(function(e){return!("[object String]"!==W(e)||N&&"object"==typeof e&&N in e)}(t))return G(D(String(t)));if(!function(e){return!("[object Date]"!==W(e)||N&&"object"==typeof e&&N in e)}(t)&&!q(t)){var le=Q(t,D),ce=T?T(t)===Object.prototype:t instanceof Object||t.constructor===Object,ue=t instanceof Object?"":"null prototype",pe=!ce&&N&&Object(t)===t&&N in t?v.call(W(t),8,-1):ue?"Object":"",he=(ce||"function"!=typeof t.constructor?"":t.constructor.name?t.constructor.name+" ":"")+(pe||ue?"["+_.call(S.call([],pe||[],ue||[]),": ")+"] ":"");return 0===le.length?he+"{}":A?he+"{"+X(le,A)+"}":he+"{ "+_.call(le,", ")+" }"}return String(t)};var z=Object.prototype.hasOwnProperty||function(e){return e in this};function V(e,t){return z.call(e,t)}function W(e){return m.call(e)}function J(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0,r=e.length;nt.maxStringLength){var n=e.length-t.maxStringLength,r="... "+n+" more character"+(n>1?"s":"");return K(v.call(e,0,t.maxStringLength),t)+r}return L(b.call(b.call(e,/(['\\])/g,"\\$1"),/[\x00-\x1f]/g,H),"single",t)}function H(e){var t=e.charCodeAt(0),n={8:"b",9:"t",10:"n",12:"f",13:"r"}[t];return n?"\\"+n:"\\x"+(t<16?"0":"")+w.call(t.toString(16))}function G(e){return"Object("+e+")"}function Z(e){return e+" { ? }"}function Y(e,t,n,r){return e+" ("+t+") {"+(r?X(n,r):_.call(n,", "))+"}"}function X(e,t){if(0===e.length)return"";var n="\n"+t.prev+t.base;return n+_.call(e,","+n)+"\n"+t.prev}function Q(e,t){var n=$(e),r=[];if(n){r.length=e.length;for(var o=0;o{var t,n,r=e.exports={};function o(){throw new Error("setTimeout has not been defined")}function s(){throw new Error("clearTimeout has not been defined")}function i(e){if(t===setTimeout)return setTimeout(e,0);if((t===o||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(n){try{return t.call(null,e,0)}catch(n){return t.call(this,e,0)}}}!function(){try{t="function"==typeof setTimeout?setTimeout:o}catch(e){t=o}try{n="function"==typeof clearTimeout?clearTimeout:s}catch(e){n=s}}();var a,l=[],c=!1,u=-1;function p(){c&&a&&(c=!1,a.length?l=a.concat(l):u=-1,l.length&&h())}function h(){if(!c){var e=i(p);c=!0;for(var t=l.length;t;){for(a=l,l=[];++u1)for(var n=1;n{"use strict";var r=n(50414);function o(){}function s(){}s.resetWarningCache=o,e.exports=function(){function e(e,t,n,o,s,i){if(i!==r){var a=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw a.name="Invariant Violation",a}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:s,resetWarningCache:o};return n.PropTypes=n,n}},45697:(e,t,n)=>{e.exports=n(92703)()},50414:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},55798:e=>{"use strict";var t=String.prototype.replace,n=/%20/g,r="RFC1738",o="RFC3986";e.exports={default:o,formatters:{RFC1738:function(e){return t.call(e,n,"+")},RFC3986:function(e){return String(e)}},RFC1738:r,RFC3986:o}},80129:(e,t,n)=>{"use strict";var r=n(58261),o=n(55235),s=n(55798);e.exports={formats:s,parse:o,stringify:r}},55235:(e,t,n)=>{"use strict";var r=n(12769),o=Object.prototype.hasOwnProperty,s=Array.isArray,i={allowDots:!1,allowPrototypes:!1,allowSparse:!1,arrayLimit:20,charset:"utf-8",charsetSentinel:!1,comma:!1,decoder:r.decode,delimiter:"&",depth:5,ignoreQueryPrefix:!1,interpretNumericEntities:!1,parameterLimit:1e3,parseArrays:!0,plainObjects:!1,strictNullHandling:!1},a=function(e){return e.replace(/&#(\d+);/g,(function(e,t){return String.fromCharCode(parseInt(t,10))}))},l=function(e,t){return e&&"string"==typeof e&&t.comma&&e.indexOf(",")>-1?e.split(","):e},c=function(e,t,n,r){if(e){var s=n.allowDots?e.replace(/\.([^.[]+)/g,"[$1]"):e,i=/(\[[^[\]]*])/g,a=n.depth>0&&/(\[[^[\]]*])/.exec(s),c=a?s.slice(0,a.index):s,u=[];if(c){if(!n.plainObjects&&o.call(Object.prototype,c)&&!n.allowPrototypes)return;u.push(c)}for(var p=0;n.depth>0&&null!==(a=i.exec(s))&&p=0;--s){var i,a=e[s];if("[]"===a&&n.parseArrays)i=[].concat(o);else{i=n.plainObjects?Object.create(null):{};var c="["===a.charAt(0)&&"]"===a.charAt(a.length-1)?a.slice(1,-1):a,u=parseInt(c,10);n.parseArrays||""!==c?!isNaN(u)&&a!==c&&String(u)===c&&u>=0&&n.parseArrays&&u<=n.arrayLimit?(i=[])[u]=o:"__proto__"!==c&&(i[c]=o):i={0:o}}o=i}return o}(u,t,n,r)}};e.exports=function(e,t){var n=function(e){if(!e)return i;if(null!==e.decoder&&void 0!==e.decoder&&"function"!=typeof e.decoder)throw new TypeError("Decoder has to be a function.");if(void 0!==e.charset&&"utf-8"!==e.charset&&"iso-8859-1"!==e.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");var t=void 0===e.charset?i.charset:e.charset;return{allowDots:void 0===e.allowDots?i.allowDots:!!e.allowDots,allowPrototypes:"boolean"==typeof e.allowPrototypes?e.allowPrototypes:i.allowPrototypes,allowSparse:"boolean"==typeof e.allowSparse?e.allowSparse:i.allowSparse,arrayLimit:"number"==typeof e.arrayLimit?e.arrayLimit:i.arrayLimit,charset:t,charsetSentinel:"boolean"==typeof e.charsetSentinel?e.charsetSentinel:i.charsetSentinel,comma:"boolean"==typeof e.comma?e.comma:i.comma,decoder:"function"==typeof e.decoder?e.decoder:i.decoder,delimiter:"string"==typeof e.delimiter||r.isRegExp(e.delimiter)?e.delimiter:i.delimiter,depth:"number"==typeof e.depth||!1===e.depth?+e.depth:i.depth,ignoreQueryPrefix:!0===e.ignoreQueryPrefix,interpretNumericEntities:"boolean"==typeof e.interpretNumericEntities?e.interpretNumericEntities:i.interpretNumericEntities,parameterLimit:"number"==typeof e.parameterLimit?e.parameterLimit:i.parameterLimit,parseArrays:!1!==e.parseArrays,plainObjects:"boolean"==typeof e.plainObjects?e.plainObjects:i.plainObjects,strictNullHandling:"boolean"==typeof e.strictNullHandling?e.strictNullHandling:i.strictNullHandling}}(t);if(""===e||null==e)return n.plainObjects?Object.create(null):{};for(var u="string"==typeof e?function(e,t){var n,c={},u=t.ignoreQueryPrefix?e.replace(/^\?/,""):e,p=t.parameterLimit===1/0?void 0:t.parameterLimit,h=u.split(t.delimiter,p),f=-1,d=t.charset;if(t.charsetSentinel)for(n=0;n-1&&(g=s(g)?[g]:g),o.call(c,m)?c[m]=r.combine(c[m],g):c[m]=g}return c}(e,n):e,p=n.plainObjects?Object.create(null):{},h=Object.keys(u),f=0;f{"use strict";var r=n(37478),o=n(12769),s=n(55798),i=Object.prototype.hasOwnProperty,a={brackets:function(e){return e+"[]"},comma:"comma",indices:function(e,t){return e+"["+t+"]"},repeat:function(e){return e}},l=Array.isArray,c=String.prototype.split,u=Array.prototype.push,p=function(e,t){u.apply(e,l(t)?t:[t])},h=Date.prototype.toISOString,f=s.default,d={addQueryPrefix:!1,allowDots:!1,charset:"utf-8",charsetSentinel:!1,delimiter:"&",encode:!0,encoder:o.encode,encodeValuesOnly:!1,format:f,formatter:s.formatters[f],indices:!1,serializeDate:function(e){return h.call(e)},skipNulls:!1,strictNullHandling:!1},m={},g=function e(t,n,s,i,a,u,h,f,g,y,v,b,w,E,x,S){for(var _,j=t,O=S,k=0,A=!1;void 0!==(O=O.get(m))&&!A;){var C=O.get(t);if(k+=1,void 0!==C){if(C===k)throw new RangeError("Cyclic object value");A=!0}void 0===O.get(m)&&(k=0)}if("function"==typeof f?j=f(n,j):j instanceof Date?j=v(j):"comma"===s&&l(j)&&(j=o.maybeMap(j,(function(e){return e instanceof Date?v(e):e}))),null===j){if(a)return h&&!E?h(n,d.encoder,x,"key",b):n;j=""}if("string"==typeof(_=j)||"number"==typeof _||"boolean"==typeof _||"symbol"==typeof _||"bigint"==typeof _||o.isBuffer(j)){if(h){var P=E?n:h(n,d.encoder,x,"key",b);if("comma"===s&&E){for(var N=c.call(String(j),","),I="",T=0;T0?j.join(",")||null:void 0}];else if(l(f))R=f;else{var D=Object.keys(j);R=g?D.sort(g):D}for(var F=i&&l(j)&&1===j.length?n+"[]":n,L=0;L0?E+w:""}},12769:(e,t,n)=>{"use strict";var r=n(55798),o=Object.prototype.hasOwnProperty,s=Array.isArray,i=function(){for(var e=[],t=0;t<256;++t)e.push("%"+((t<16?"0":"")+t.toString(16)).toUpperCase());return e}(),a=function(e,t){for(var n=t&&t.plainObjects?Object.create(null):{},r=0;r1;){var t=e.pop(),n=t.obj[t.prop];if(s(n)){for(var r=[],o=0;o=48&&u<=57||u>=65&&u<=90||u>=97&&u<=122||s===r.RFC1738&&(40===u||41===u)?l+=a.charAt(c):u<128?l+=i[u]:u<2048?l+=i[192|u>>6]+i[128|63&u]:u<55296||u>=57344?l+=i[224|u>>12]+i[128|u>>6&63]+i[128|63&u]:(c+=1,u=65536+((1023&u)<<10|1023&a.charCodeAt(c)),l+=i[240|u>>18]+i[128|u>>12&63]+i[128|u>>6&63]+i[128|63&u])}return l},isBuffer:function(e){return!(!e||"object"!=typeof e)&&!!(e.constructor&&e.constructor.isBuffer&&e.constructor.isBuffer(e))},isRegExp:function(e){return"[object RegExp]"===Object.prototype.toString.call(e)},maybeMap:function(e,t){if(s(e)){for(var n=[],r=0;r{"use strict";var n=Object.prototype.hasOwnProperty;function r(e){try{return decodeURIComponent(e.replace(/\+/g," "))}catch(e){return null}}function o(e){try{return encodeURIComponent(e)}catch(e){return null}}t.stringify=function(e,t){t=t||"";var r,s,i=[];for(s in"string"!=typeof t&&(t="?"),e)if(n.call(e,s)){if((r=e[s])||null!=r&&!isNaN(r)||(r=""),s=o(s),r=o(r),null===s||null===r)continue;i.push(s+"="+r)}return i.length?t+i.join("&"):""},t.parse=function(e){for(var t,n=/([^=?#&]+)=?([^&]*)/g,o={};t=n.exec(e);){var s=r(t[1]),i=r(t[2]);null===s||null===i||s in o||(o[s]=i)}return o}},14419:(e,t,n)=>{const r=n(60697),o=n(69450),s=r.types;e.exports=class e{constructor(e,t){if(this._setDefaults(e),e instanceof RegExp)this.ignoreCase=e.ignoreCase,this.multiline=e.multiline,e=e.source;else{if("string"!=typeof e)throw new Error("Expected a regexp or string");this.ignoreCase=t&&-1!==t.indexOf("i"),this.multiline=t&&-1!==t.indexOf("m")}this.tokens=r(e)}_setDefaults(t){this.max=null!=t.max?t.max:null!=e.prototype.max?e.prototype.max:100,this.defaultRange=t.defaultRange?t.defaultRange:this.defaultRange.clone(),t.randInt&&(this.randInt=t.randInt)}gen(){return this._gen(this.tokens,[])}_gen(e,t){var n,r,o,i,a;switch(e.type){case s.ROOT:case s.GROUP:if(e.followedBy||e.notFollowedBy)return"";for(e.remember&&void 0===e.groupNumber&&(e.groupNumber=t.push(null)-1),r="",i=0,a=(n=e.options?this._randSelect(e.options):e.stack).length;i{"use strict";var r=n(34155),o=65536,s=4294967295;var i=n(89509).Buffer,a=n.g.crypto||n.g.msCrypto;a&&a.getRandomValues?e.exports=function(e,t){if(e>s)throw new RangeError("requested too many random bytes");var n=i.allocUnsafe(e);if(e>0)if(e>o)for(var l=0;l{"use strict";function r(e){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.CopyToClipboard=void 0;var o=a(n(67294)),s=a(n(20640)),i=["text","onCopy","options","children"];function a(e){return e&&e.__esModule?e:{default:e}}function l(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function c(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function p(e,t){for(var n=0;n{"use strict";var r=n(74300).CopyToClipboard;r.CopyToClipboard=r,e.exports=r},53441:(e,t,n)=>{"use strict";function r(e){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.DebounceInput=void 0;var o=a(n(67294)),s=a(n(91296)),i=["element","onChange","value","minLength","debounceTimeout","forceNotifyByEnter","forceNotifyOnBlur","onKeyDown","onBlur","inputRef"];function a(e){return e&&e.__esModule?e:{default:e}}function l(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},s=Object.keys(e);for(r=0;r=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function c(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function u(e){for(var t=1;t=r?t.notify(e):n.length>o.length&&t.notify(u(u({},e),{},{target:u(u({},e.target),{},{value:""})}))}))})),g(d(t),"onKeyDown",(function(e){"Enter"===e.key&&t.forceNotify(e);var n=t.props.onKeyDown;n&&(e.persist(),n(e))})),g(d(t),"onBlur",(function(e){t.forceNotify(e);var n=t.props.onBlur;n&&(e.persist(),n(e))})),g(d(t),"createNotifier",(function(e){if(e<0)t.notify=function(){return null};else if(0===e)t.notify=t.doNotify;else{var n=(0,s.default)((function(e){t.isDebouncing=!1,t.doNotify(e)}),e);t.notify=function(e){t.isDebouncing=!0,n(e)},t.flush=function(){return n.flush()},t.cancel=function(){t.isDebouncing=!1,n.cancel()}}})),g(d(t),"doNotify",(function(){t.props.onChange.apply(void 0,arguments)})),g(d(t),"forceNotify",(function(e){var n=t.props.debounceTimeout;if(t.isDebouncing||!(n>0)){t.cancel&&t.cancel();var r=t.state.value,o=t.props.minLength;r.length>=o?t.doNotify(e):t.doNotify(u(u({},e),{},{target:u(u({},e.target),{},{value:r})}))}})),t.isDebouncing=!1,t.state={value:void 0===e.value||null===e.value?"":e.value};var n=t.props.debounceTimeout;return t.createNotifier(n),t}return t=c,(n=[{key:"componentDidUpdate",value:function(e){if(!this.isDebouncing){var t=this.props,n=t.value,r=t.debounceTimeout,o=e.debounceTimeout,s=e.value,i=this.state.value;void 0!==n&&s!==n&&i!==n&&this.setState({value:n}),r!==o&&this.createNotifier(r)}}},{key:"componentWillUnmount",value:function(){this.flush&&this.flush()}},{key:"render",value:function(){var e,t,n=this.props,r=n.element,s=(n.onChange,n.value,n.minLength,n.debounceTimeout,n.forceNotifyByEnter),a=n.forceNotifyOnBlur,c=n.onKeyDown,p=n.onBlur,h=n.inputRef,f=l(n,i),d=this.state.value;e=s?{onKeyDown:this.onKeyDown}:c?{onKeyDown:c}:{},t=a?{onBlur:this.onBlur}:p?{onBlur:p}:{};var m=h?{ref:h}:{};return o.default.createElement(r,u(u(u(u({},f),{},{onChange:this.onChange,value:d},e),t),m))}}])&&p(t.prototype,n),r&&p(t,r),Object.defineProperty(t,"prototype",{writable:!1}),c}(o.default.PureComponent);t.DebounceInput=y,g(y,"defaultProps",{element:"input",type:"text",onKeyDown:void 0,onBlur:void 0,value:void 0,minLength:0,debounceTimeout:100,forceNotifyByEnter:!0,forceNotifyOnBlur:!0,inputRef:void 0})},775:(e,t,n)=>{"use strict";var r=n(53441).DebounceInput;r.DebounceInput=r,e.exports=r},64448:(e,t,n)=>{"use strict";var r=n(67294),o=n(27418),s=n(63840);function i(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n
\n \n )\n}\n\nAccordion.propTypes = {\n expanded: PropTypes.bool,\n children: PropTypes.node.isRequired,\n onChange: PropTypes.func.isRequired,\n}\n\nAccordion.defaultProps = {\n expanded: false,\n}\n\nexport default Accordion\n","/**\n * @prettier\n */\nimport React, { useCallback } from \"react\"\nimport PropTypes from \"prop-types\"\n\nconst ExpandDeepButton = ({ expanded, onClick }) => {\n const handleExpansion = useCallback(\n (event) => {\n onClick(event, !expanded)\n },\n [expanded, onClick]\n )\n\n return (\n \n {expanded ? \"Collapse all\" : \"Expand all\"}\n \n )\n}\n\nExpandDeepButton.propTypes = {\n expanded: PropTypes.bool.isRequired,\n onClick: PropTypes.func.isRequired,\n}\n\nexport default ExpandDeepButton\n","/**\n * @prettier\n */\nimport React, { forwardRef, useState, useCallback, useEffect } from \"react\"\nimport PropTypes from \"prop-types\"\nimport classNames from \"classnames\"\n\nimport * as propTypes from \"../../prop-types\"\nimport {\n useComponent,\n useLevel,\n useFn,\n useIsEmbedded,\n useIsExpanded,\n useIsExpandedDeeply,\n useIsCircular,\n useRenderedSchemas,\n} from \"../../hooks\"\nimport {\n JSONSchemaLevelContext,\n JSONSchemaDeepExpansionContext,\n JSONSchemaCyclesContext,\n} from \"../../context\"\n\nconst JSONSchema = forwardRef(\n ({ schema, name, dependentRequired, onExpand }, ref) => {\n const fn = useFn()\n const isExpanded = useIsExpanded()\n const isExpandedDeeply = useIsExpandedDeeply()\n const [expanded, setExpanded] = useState(isExpanded || isExpandedDeeply)\n const [expandedDeeply, setExpandedDeeply] = useState(isExpandedDeeply)\n const [level, nextLevel] = useLevel()\n const isEmbedded = useIsEmbedded()\n const isExpandable = fn.isExpandable(schema) || dependentRequired.length > 0\n const isCircular = useIsCircular(schema)\n const renderedSchemas = useRenderedSchemas(schema)\n const constraints = fn.stringifyConstraints(schema)\n const Accordion = useComponent(\"Accordion\")\n const Keyword$schema = useComponent(\"Keyword$schema\")\n const Keyword$vocabulary = useComponent(\"Keyword$vocabulary\")\n const Keyword$id = useComponent(\"Keyword$id\")\n const Keyword$anchor = useComponent(\"Keyword$anchor\")\n const Keyword$dynamicAnchor = useComponent(\"Keyword$dynamicAnchor\")\n const Keyword$ref = useComponent(\"Keyword$ref\")\n const Keyword$dynamicRef = useComponent(\"Keyword$dynamicRef\")\n const Keyword$defs = useComponent(\"Keyword$defs\")\n const Keyword$comment = useComponent(\"Keyword$comment\")\n const KeywordAllOf = useComponent(\"KeywordAllOf\")\n const KeywordAnyOf = useComponent(\"KeywordAnyOf\")\n const KeywordOneOf = useComponent(\"KeywordOneOf\")\n const KeywordNot = useComponent(\"KeywordNot\")\n const KeywordIf = useComponent(\"KeywordIf\")\n const KeywordThen = useComponent(\"KeywordThen\")\n const KeywordElse = useComponent(\"KeywordElse\")\n const KeywordDependentSchemas = useComponent(\"KeywordDependentSchemas\")\n const KeywordPrefixItems = useComponent(\"KeywordPrefixItems\")\n const KeywordItems = useComponent(\"KeywordItems\")\n const KeywordContains = useComponent(\"KeywordContains\")\n const KeywordProperties = useComponent(\"KeywordProperties\")\n const KeywordPatternProperties = useComponent(\"KeywordPatternProperties\")\n const KeywordAdditionalProperties = useComponent(\n \"KeywordAdditionalProperties\"\n )\n const KeywordPropertyNames = useComponent(\"KeywordPropertyNames\")\n const KeywordUnevaluatedItems = useComponent(\"KeywordUnevaluatedItems\")\n const KeywordUnevaluatedProperties = useComponent(\n \"KeywordUnevaluatedProperties\"\n )\n const KeywordType = useComponent(\"KeywordType\")\n const KeywordEnum = useComponent(\"KeywordEnum\")\n const KeywordConst = useComponent(\"KeywordConst\")\n const KeywordConstraint = useComponent(\"KeywordConstraint\")\n const KeywordDependentRequired = useComponent(\"KeywordDependentRequired\")\n const KeywordContentSchema = useComponent(\"KeywordContentSchema\")\n const KeywordTitle = useComponent(\"KeywordTitle\")\n const KeywordDescription = useComponent(\"KeywordDescription\")\n const KeywordDefault = useComponent(\"KeywordDefault\")\n const KeywordDeprecated = useComponent(\"KeywordDeprecated\")\n const KeywordReadOnly = useComponent(\"KeywordReadOnly\")\n const KeywordWriteOnly = useComponent(\"KeywordWriteOnly\")\n const ExpandDeepButton = useComponent(\"ExpandDeepButton\")\n\n /**\n * Effects handlers.\n */\n useEffect(() => {\n setExpandedDeeply(isExpandedDeeply)\n }, [isExpandedDeeply])\n\n useEffect(() => {\n setExpandedDeeply(expandedDeeply)\n }, [expandedDeeply])\n\n /**\n * Event handlers.\n */\n const handleExpansion = useCallback(\n (e, expandedNew) => {\n setExpanded(expandedNew)\n !expandedNew && setExpandedDeeply(false)\n onExpand(e, expandedNew, false)\n },\n [onExpand]\n )\n const handleExpansionDeep = useCallback(\n (e, expandedDeepNew) => {\n setExpanded(expandedDeepNew)\n setExpandedDeeply(expandedDeepNew)\n onExpand(e, expandedDeepNew, true)\n },\n [onExpand]\n )\n\n return (\n \n \n \n \n
\n {isExpandable && !isCircular ? (\n <>\n \n \n \n \n \n ) : (\n \n )}\n \n \n \n \n {constraints.length > 0 &&\n constraints.map((constraint) => (\n \n ))}\n
\n \n {expanded && (\n <>\n \n {!isCircular && isExpandable && (\n <>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n )}\n \n \n \n \n \n \n \n \n \n \n {!isCircular && isExpandable && (\n \n )}\n \n \n \n )}\n
\n \n \n \n \n )\n }\n)\n\nJSONSchema.propTypes = {\n name: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),\n schema: propTypes.schema.isRequired,\n dependentRequired: PropTypes.arrayOf(PropTypes.string),\n onExpand: PropTypes.func,\n}\n\nJSONSchema.defaultProps = {\n name: \"\",\n dependentRequired: [],\n onExpand: () => {},\n}\n\nexport default JSONSchema\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nconst ChevronRight = () => (\n \n \n \n)\n\nexport default ChevronRight\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\n\nconst $anchor = ({ schema }) => {\n if (!schema?.$anchor) return null\n\n return (\n
\n \n $anchor\n \n \n {schema.$anchor}\n \n
\n )\n}\n\n$anchor.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default $anchor\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\n\nconst $comment = ({ schema }) => {\n if (!schema?.$comment) return null\n\n return (\n
\n \n $comment\n \n \n {schema.$comment}\n \n
\n )\n}\n\n$comment.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default $comment\n","/**\n * @prettier\n */\nimport React, { useCallback, useState } from \"react\"\nimport classNames from \"classnames\"\n\nimport { schema } from \"../../prop-types\"\nimport { useComponent, useIsExpandedDeeply } from \"../../hooks\"\nimport { JSONSchemaDeepExpansionContext } from \"../../context\"\n\nconst $defs = ({ schema }) => {\n const $defs = schema?.$defs || {}\n const isExpandedDeeply = useIsExpandedDeeply()\n const [expanded, setExpanded] = useState(isExpandedDeeply)\n const [expandedDeeply, setExpandedDeeply] = useState(false)\n const Accordion = useComponent(\"Accordion\")\n const ExpandDeepButton = useComponent(\"ExpandDeepButton\")\n const JSONSchema = useComponent(\"JSONSchema\")\n\n /**\n * Event handlers.\n */\n const handleExpansion = useCallback(() => {\n setExpanded((prev) => !prev)\n }, [])\n const handleExpansionDeep = useCallback((e, expandedDeepNew) => {\n setExpanded(expandedDeepNew)\n setExpandedDeeply(expandedDeepNew)\n }, [])\n\n /**\n * Rendering.\n */\n if (Object.keys($defs).length === 0) {\n return null\n }\n\n return (\n \n
\n \n \n $defs\n \n \n \n \n object\n \n \n {expanded && (\n <>\n {Object.entries($defs).map(([schemaName, schema]) => (\n
  • \n \n
  • \n ))}\n \n )}\n \n
    \n
    \n )\n}\n\n$defs.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default $defs\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\n\nconst $dynamicAnchor = ({ schema }) => {\n if (!schema?.$dynamicAnchor) return null\n\n return (\n
    \n \n $dynamicAnchor\n \n \n {schema.$dynamicAnchor}\n \n
    \n )\n}\n\n$dynamicAnchor.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default $dynamicAnchor\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\n\nconst $dynamicRef = ({ schema }) => {\n if (!schema?.$dynamicRef) return null\n\n return (\n
    \n \n $dynamicRef\n \n \n {schema.$dynamicRef}\n \n
    \n )\n}\n\n$dynamicRef.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default $dynamicRef\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\n\nconst $id = ({ schema }) => {\n if (!schema?.$id) return null\n\n return (\n
    \n \n $id\n \n \n {schema.$id}\n \n
    \n )\n}\n\n$id.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default $id\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\n\nconst $ref = ({ schema }) => {\n if (!schema?.$ref) return null\n\n return (\n
    \n \n $ref\n \n \n {schema.$ref}\n \n
    \n )\n}\n\n$ref.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default $ref\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\n\nconst $schema = ({ schema }) => {\n if (!schema?.$schema) return null\n\n return (\n
    \n \n $schema\n \n \n {schema.$schema}\n \n
    \n )\n}\n\n$schema.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default $schema\n","/**\n * @prettier\n */\nimport React, { useCallback, useState } from \"react\"\nimport classNames from \"classnames\"\n\nimport { schema } from \"../../../prop-types\"\nimport { useComponent, useIsExpandedDeeply } from \"../../../hooks\"\n\nconst $vocabulary = ({ schema }) => {\n const isExpandedDeeply = useIsExpandedDeeply()\n const [expanded, setExpanded] = useState(isExpandedDeeply)\n const Accordion = useComponent(\"Accordion\")\n\n const handleExpansion = useCallback(() => {\n setExpanded((prev) => !prev)\n }, [])\n\n /**\n * Rendering.\n */\n if (!schema?.$vocabulary) return null\n if (typeof schema.$vocabulary !== \"object\") return null\n\n return (\n
    \n \n \n $vocabulary\n \n \n \n object\n \n
      \n {expanded &&\n Object.entries(schema.$vocabulary).map(([uri, enabled]) => (\n \n \n {uri}\n \n \n ))}\n
    \n
    \n )\n}\n\n$vocabulary.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default $vocabulary\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent } from \"../../hooks\"\n\nconst AdditionalProperties = ({ schema }) => {\n const fn = useFn()\n const { additionalProperties } = schema\n const JSONSchema = useComponent(\"JSONSchema\")\n\n if (!fn.hasKeyword(schema, \"additionalProperties\")) return null\n\n /**\n * Rendering.\n */\n const name = (\n \n Additional properties\n \n )\n\n return (\n
    \n {additionalProperties === true ? (\n <>\n {name}\n \n allowed\n \n \n ) : additionalProperties === false ? (\n <>\n {name}\n \n forbidden\n \n \n ) : (\n \n )}\n
    \n )\n}\n\nAdditionalProperties.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default AdditionalProperties\n","/**\n * @prettier\n */\nimport React, { useCallback, useState } from \"react\"\nimport classNames from \"classnames\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent, useIsExpandedDeeply } from \"../../hooks\"\nimport { JSONSchemaDeepExpansionContext } from \"../../context\"\n\nconst AllOf = ({ schema }) => {\n const allOf = schema?.allOf || []\n const fn = useFn()\n const isExpandedDeeply = useIsExpandedDeeply()\n const [expanded, setExpanded] = useState(isExpandedDeeply)\n const [expandedDeeply, setExpandedDeeply] = useState(false)\n const Accordion = useComponent(\"Accordion\")\n const ExpandDeepButton = useComponent(\"ExpandDeepButton\")\n const JSONSchema = useComponent(\"JSONSchema\")\n const KeywordType = useComponent(\"KeywordType\")\n\n /**\n * Event handlers.\n */\n const handleExpansion = useCallback(() => {\n setExpanded((prev) => !prev)\n }, [])\n const handleExpansionDeep = useCallback((e, expandedDeepNew) => {\n setExpanded(expandedDeepNew)\n setExpandedDeeply(expandedDeepNew)\n }, [])\n\n /**\n * Rendering.\n */\n if (!Array.isArray(allOf) || allOf.length === 0) {\n return null\n }\n\n return (\n \n
    \n \n \n All of\n \n \n \n \n \n {expanded && (\n <>\n {allOf.map((schema, index) => (\n
  • \n \n
  • \n ))}\n \n )}\n \n
    \n
    \n )\n}\n\nAllOf.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default AllOf\n","/**\n * @prettier\n */\nimport React, { useCallback, useState } from \"react\"\nimport classNames from \"classnames\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent, useIsExpandedDeeply } from \"../../hooks\"\nimport { JSONSchemaDeepExpansionContext } from \"../../context\"\n\nconst AnyOf = ({ schema }) => {\n const anyOf = schema?.anyOf || []\n const fn = useFn()\n const isExpandedDeeply = useIsExpandedDeeply()\n const [expanded, setExpanded] = useState(isExpandedDeeply)\n const [expandedDeeply, setExpandedDeeply] = useState(false)\n const Accordion = useComponent(\"Accordion\")\n const ExpandDeepButton = useComponent(\"ExpandDeepButton\")\n const JSONSchema = useComponent(\"JSONSchema\")\n const KeywordType = useComponent(\"KeywordType\")\n\n /**\n * Event handlers.\n */\n const handleExpansion = useCallback(() => {\n setExpanded((prev) => !prev)\n }, [])\n const handleExpansionDeep = useCallback((e, expandedDeepNew) => {\n setExpanded(expandedDeepNew)\n setExpandedDeeply(expandedDeepNew)\n }, [])\n\n /**\n * Rendering.\n */\n if (!Array.isArray(anyOf) || anyOf.length === 0) {\n return null\n }\n\n return (\n \n
    \n \n \n Any of\n \n \n \n \n \n {expanded && (\n <>\n {anyOf.map((schema, index) => (\n
  • \n \n
  • \n ))}\n \n )}\n \n
    \n
    \n )\n}\n\nAnyOf.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default AnyOf\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn } from \"../../hooks\"\n\nconst Const = ({ schema }) => {\n const fn = useFn()\n\n if (!fn.hasKeyword(schema, \"const\")) return null\n\n return (\n
    \n \n Const\n \n \n {fn.stringify(schema.const)}\n \n
    \n )\n}\n\nConst.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default Const\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\n\n/**\n * This component represents various constraint keywords\n * from JSON Schema 2020-12 validation vocabulary.\n */\nconst Constraint = ({ constraint }) => (\n \n {constraint.value}\n \n)\n\nConstraint.propTypes = {\n constraint: PropTypes.shape({\n scope: PropTypes.oneOf([\"number\", \"string\", \"array\", \"object\"]).isRequired,\n value: PropTypes.string.isRequired,\n }).isRequired,\n}\n\nexport default React.memo(Constraint)\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent } from \"../../hooks\"\n\nconst Contains = ({ schema }) => {\n const fn = useFn()\n const JSONSchema = useComponent(\"JSONSchema\")\n\n /**\n * Rendering.\n */\n if (!fn.hasKeyword(schema, \"contains\")) return null\n\n const name = (\n \n Contains\n \n )\n\n return (\n
    \n \n
    \n )\n}\n\nContains.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default Contains\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent } from \"../../hooks\"\n\nconst ContentSchema = ({ schema }) => {\n const fn = useFn()\n const JSONSchema = useComponent(\"JSONSchema\")\n\n /**\n * Rendering.\n */\n if (!fn.hasKeyword(schema, \"contentSchema\")) return null\n\n const name = (\n \n Content schema\n \n )\n\n return (\n
    \n \n
    \n )\n}\n\nContentSchema.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default ContentSchema\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn } from \"../../hooks\"\n\nconst Default = ({ schema }) => {\n const fn = useFn()\n\n if (!fn.hasKeyword(schema, \"default\")) return null\n\n return (\n
    \n \n Default\n \n \n {fn.stringify(schema.default)}\n \n
    \n )\n}\n\nDefault.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default Default\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\n\nimport * as propTypes from \"../../../prop-types\"\n\nconst DependentRequired = ({ dependentRequired }) => {\n if (dependentRequired.length === 0) return null\n\n return (\n
    \n \n Required when defined\n \n
      \n {dependentRequired.map((propertyName) => (\n
    • \n \n {propertyName}\n \n
    • \n ))}\n
    \n
    \n )\n}\n\nDependentRequired.propTypes = {\n schema: propTypes.schema.isRequired,\n dependentRequired: PropTypes.arrayOf(PropTypes.string).isRequired,\n}\n\nexport default DependentRequired\n","/**\n * @prettier\n */\nimport React, { useCallback, useState } from \"react\"\nimport classNames from \"classnames\"\n\nimport { schema } from \"../../prop-types\"\nimport { useComponent, useIsExpandedDeeply } from \"../../hooks\"\nimport { JSONSchemaDeepExpansionContext } from \"../../context\"\n\nconst DependentSchemas = ({ schema }) => {\n const dependentSchemas = schema?.dependentSchemas || []\n const isExpandedDeeply = useIsExpandedDeeply()\n const [expanded, setExpanded] = useState(isExpandedDeeply)\n const [expandedDeeply, setExpandedDeeply] = useState(false)\n const Accordion = useComponent(\"Accordion\")\n const ExpandDeepButton = useComponent(\"ExpandDeepButton\")\n const JSONSchema = useComponent(\"JSONSchema\")\n\n /**\n * Event handlers.\n */\n const handleExpansion = useCallback(() => {\n setExpanded((prev) => !prev)\n }, [])\n const handleExpansionDeep = useCallback((e, expandedDeepNew) => {\n setExpanded(expandedDeepNew)\n setExpandedDeeply(expandedDeepNew)\n }, [])\n\n /**\n * Rendering.\n */\n if (typeof dependentSchemas !== \"object\") return null\n if (Object.keys(dependentSchemas).length === 0) return null\n\n return (\n \n
    \n \n \n Dependent schemas\n \n \n \n \n object\n \n \n {expanded && (\n <>\n {Object.entries(dependentSchemas).map(([schemaName, schema]) => (\n
  • \n \n
  • \n ))}\n \n )}\n \n
    \n
    \n )\n}\n\nDependentSchemas.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default DependentSchemas\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\n\nconst Deprecated = ({ schema }) => {\n if (schema?.deprecated !== true) return null\n\n return (\n \n deprecated\n \n )\n}\n\nDeprecated.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default Deprecated\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../../prop-types\"\n\nconst Description = ({ schema }) => {\n if (!schema?.description) return null\n\n return (\n
    \n
    \n {schema.description}\n
    \n
    \n )\n}\n\nDescription.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default Description\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent } from \"../../hooks\"\n\nconst Else = ({ schema }) => {\n const fn = useFn()\n const JSONSchema = useComponent(\"JSONSchema\")\n\n /**\n * Rendering.\n */\n if (!fn.hasKeyword(schema, \"else\")) return null\n\n const name = (\n \n Else\n \n )\n\n return (\n
    \n \n
    \n )\n}\n\nElse.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default Else\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../../prop-types\"\nimport { useFn } from \"../../../hooks\"\n\nconst Enum = ({ schema }) => {\n const fn = useFn()\n\n if (!Array.isArray(schema?.enum)) return null\n\n return (\n
    \n \n Allowed values\n \n
      \n {schema.enum.map((element) => {\n const strigifiedElement = fn.stringify(element)\n\n return (\n
    • \n \n {strigifiedElement}\n \n
    • \n )\n })}\n
    \n
    \n )\n}\n\nEnum.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default Enum\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent } from \"../../hooks\"\n\nconst If = ({ schema }) => {\n const fn = useFn()\n const JSONSchema = useComponent(\"JSONSchema\")\n\n /**\n * Rendering.\n */\n if (!fn.hasKeyword(schema, \"if\")) return null\n\n const name = (\n \n If\n \n )\n\n return (\n
    \n \n
    \n )\n}\n\nIf.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default If\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent } from \"../../hooks\"\n\nconst Items = ({ schema }) => {\n const fn = useFn()\n const JSONSchema = useComponent(\"JSONSchema\")\n\n /**\n * Rendering.\n */\n if (!fn.hasKeyword(schema, \"items\")) return null\n\n const name = (\n \n Items\n \n )\n\n return (\n
    \n \n
    \n )\n}\n\nItems.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default Items\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent } from \"../../hooks\"\n\nconst Not = ({ schema }) => {\n const fn = useFn()\n const JSONSchema = useComponent(\"JSONSchema\")\n\n /**\n * Rendering.\n */\n if (!fn.hasKeyword(schema, \"not\")) return null\n\n const name = (\n \n Not\n \n )\n\n return (\n
    \n \n
    \n )\n}\n\nNot.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default Not\n","/**\n * @prettier\n */\nimport React, { useCallback, useState } from \"react\"\nimport classNames from \"classnames\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent, useIsExpandedDeeply } from \"../../hooks\"\nimport { JSONSchemaDeepExpansionContext } from \"../../context\"\n\nconst OneOf = ({ schema }) => {\n const oneOf = schema?.oneOf || []\n const fn = useFn()\n const isExpandedDeeply = useIsExpandedDeeply()\n const [expanded, setExpanded] = useState(isExpandedDeeply)\n const [expandedDeeply, setExpandedDeeply] = useState(false)\n const Accordion = useComponent(\"Accordion\")\n const ExpandDeepButton = useComponent(\"ExpandDeepButton\")\n const JSONSchema = useComponent(\"JSONSchema\")\n const KeywordType = useComponent(\"KeywordType\")\n\n /**\n * Event handlers.\n */\n const handleExpansion = useCallback(() => {\n setExpanded((prev) => !prev)\n }, [])\n const handleExpansionDeep = useCallback((e, expandedDeepNew) => {\n setExpanded(expandedDeepNew)\n setExpandedDeeply(expandedDeepNew)\n }, [])\n\n /**\n * Rendering.\n */\n if (!Array.isArray(oneOf) || oneOf.length === 0) {\n return null\n }\n\n return (\n \n
    \n \n \n One of\n \n \n \n \n \n {expanded && (\n <>\n {oneOf.map((schema, index) => (\n
  • \n \n
  • \n ))}\n \n )}\n \n
    \n
    \n )\n}\n\nOneOf.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default OneOf\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../../prop-types\"\nimport { useComponent } from \"../../../hooks\"\n\nconst PatternProperties = ({ schema }) => {\n const patternProperties = schema?.patternProperties || {}\n const JSONSchema = useComponent(\"JSONSchema\")\n\n /**\n * Rendering.\n */\n if (Object.keys(patternProperties).length === 0) {\n return null\n }\n\n return (\n
    \n
      \n {Object.entries(patternProperties).map(([propertyName, schema]) => (\n
    • \n \n
    • \n ))}\n
    \n
    \n )\n}\n\nPatternProperties.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default PatternProperties\n","/**\n * @prettier\n */\nimport React, { useCallback, useState } from \"react\"\nimport classNames from \"classnames\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent, useIsExpandedDeeply } from \"../../hooks\"\nimport { JSONSchemaDeepExpansionContext } from \"../../context\"\n\nconst PrefixItems = ({ schema }) => {\n const prefixItems = schema?.prefixItems || []\n const fn = useFn()\n const isExpandedDeeply = useIsExpandedDeeply()\n const [expanded, setExpanded] = useState(isExpandedDeeply)\n const [expandedDeeply, setExpandedDeeply] = useState(false)\n const Accordion = useComponent(\"Accordion\")\n const ExpandDeepButton = useComponent(\"ExpandDeepButton\")\n const JSONSchema = useComponent(\"JSONSchema\")\n const KeywordType = useComponent(\"KeywordType\")\n\n /**\n * Event handlers.\n */\n const handleExpansion = useCallback(() => {\n setExpanded((prev) => !prev)\n }, [])\n const handleExpansionDeep = useCallback((e, expandedDeepNew) => {\n setExpanded(expandedDeepNew)\n setExpandedDeeply(expandedDeepNew)\n }, [])\n\n /**\n * Rendering.\n */\n if (!Array.isArray(prefixItems) || prefixItems.length === 0) {\n return null\n }\n\n return (\n \n
    \n \n \n Prefix items\n \n \n \n \n \n {expanded && (\n <>\n {prefixItems.map((schema, index) => (\n
  • \n \n
  • \n ))}\n \n )}\n \n
    \n
    \n )\n}\n\nPrefixItems.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default PrefixItems\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport classNames from \"classnames\"\n\nimport { schema } from \"../../../prop-types\"\nimport { useFn, useComponent } from \"../../../hooks\"\n\nconst Properties = ({ schema }) => {\n const fn = useFn()\n const properties = schema?.properties || {}\n const required = Array.isArray(schema?.required) ? schema.required : []\n const JSONSchema = useComponent(\"JSONSchema\")\n\n /**\n * Rendering.\n */\n if (Object.keys(properties).length === 0) {\n return null\n }\n\n return (\n
    \n
      \n {Object.entries(properties).map(([propertyName, propertySchema]) => {\n const isRequired = required.includes(propertyName)\n const dependentRequired = fn.getDependentRequired(\n propertyName,\n schema\n )\n\n return (\n \n \n \n )\n })}\n
    \n
    \n )\n}\n\nProperties.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default Properties\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent } from \"../../hooks\"\n\nconst PropertyNames = ({ schema }) => {\n const fn = useFn()\n const { propertyNames } = schema\n const JSONSchema = useComponent(\"JSONSchema\")\n const name = (\n \n Property names\n \n )\n\n /**\n * Rendering.\n */\n if (!fn.hasKeyword(schema, \"propertyNames\")) return null\n\n return (\n
    \n \n
    \n )\n}\n\nPropertyNames.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default PropertyNames\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\n\nconst ReadOnly = ({ schema }) => {\n if (schema?.readOnly !== true) return null\n\n return (\n \n read-only\n \n )\n}\n\nReadOnly.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default ReadOnly\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent } from \"../../hooks\"\n\nconst Then = ({ schema }) => {\n const fn = useFn()\n const JSONSchema = useComponent(\"JSONSchema\")\n\n /**\n * Rendering.\n */\n if (!fn.hasKeyword(schema, \"then\")) return null\n\n const name = (\n \n Then\n \n )\n\n return (\n
    \n \n
    \n )\n}\n\nThen.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default Then\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\n\nimport { schema } from \"../../../prop-types\"\nimport { useFn } from \"../../../hooks\"\n\nconst Title = ({ title, schema }) => {\n const fn = useFn()\n const renderedTitle = title || fn.getTitle(schema)\n\n if (!renderedTitle) return null\n\n return (\n
    \n {title || fn.getTitle(schema)}\n
    \n )\n}\n\nTitle.propTypes = {\n title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),\n schema: schema.isRequired,\n}\n\nTitle.defaultProps = {\n title: \"\",\n}\n\nexport default Title\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn } from \"../../hooks\"\n\nconst Type = ({ schema, isCircular }) => {\n const fn = useFn()\n const type = fn.getType(schema)\n const circularSuffix = isCircular ? \" [circular]\" : \"\"\n\n return (\n \n {`${type}${circularSuffix}`}\n \n )\n}\n\nType.propTypes = {\n schema: schema.isRequired,\n isCircular: PropTypes.bool,\n}\n\nType.defaultProps = {\n isCircular: false,\n}\n\nexport default Type\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent } from \"../../hooks\"\n\nconst UnevaluatedItems = ({ schema }) => {\n const fn = useFn()\n const { unevaluatedItems } = schema\n const JSONSchema = useComponent(\"JSONSchema\")\n\n /**\n * Rendering.\n */\n if (!fn.hasKeyword(schema, \"unevaluatedItems\")) return null\n\n const name = (\n \n Unevaluated items\n \n )\n\n return (\n
    \n \n
    \n )\n}\n\nUnevaluatedItems.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default UnevaluatedItems\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\nimport { useFn, useComponent } from \"../../hooks\"\n\nconst UnevaluatedProperties = ({ schema }) => {\n const fn = useFn()\n const { unevaluatedProperties } = schema\n const JSONSchema = useComponent(\"JSONSchema\")\n\n /**\n * Rendering.\n */\n if (!fn.hasKeyword(schema, \"unevaluatedProperties\")) return null\n\n const name = (\n \n Unevaluated properties\n \n )\n\n return (\n
    \n \n
    \n )\n}\n\nUnevaluatedProperties.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default UnevaluatedProperties\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { schema } from \"../../prop-types\"\n\nconst WriteOnly = ({ schema }) => {\n if (schema?.writeOnly !== true) return null\n\n return (\n \n write-only\n \n )\n}\n\nWriteOnly.propTypes = {\n schema: schema.isRequired,\n}\n\nexport default WriteOnly\n","/**\n * @prettier\n */\nimport { createContext } from \"react\"\n\nexport const JSONSchemaContext = createContext(null)\nJSONSchemaContext.displayName = \"JSONSchemaContext\"\n\nexport const JSONSchemaLevelContext = createContext(0)\nJSONSchemaLevelContext.displayName = \"JSONSchemaLevelContext\"\n\nexport const JSONSchemaDeepExpansionContext = createContext(false)\nJSONSchemaDeepExpansionContext.displayName = \"JSONSchemaDeepExpansionContext\"\n\nexport const JSONSchemaCyclesContext = createContext(new Set())\n","/**\n * @prettier\n */\nimport { useFn } from \"./hooks\"\n\nexport const upperFirst = (value) => {\n if (typeof value === \"string\") {\n return `${value.charAt(0).toUpperCase()}${value.slice(1)}`\n }\n return value\n}\n\nexport const getTitle = (schema) => {\n const fn = useFn()\n\n if (schema?.title) return fn.upperFirst(schema.title)\n if (schema?.$anchor) return fn.upperFirst(schema.$anchor)\n if (schema?.$id) return schema.$id\n\n return \"\"\n}\n\nexport const getType = (schema, processedSchemas = new WeakSet()) => {\n const fn = useFn()\n\n if (schema == null) {\n return \"any\"\n }\n\n if (fn.isBooleanJSONSchema(schema)) {\n return schema ? \"any\" : \"never\"\n }\n\n if (typeof schema !== \"object\") {\n return \"any\"\n }\n\n if (processedSchemas.has(schema)) {\n return \"any\" // detect a cycle\n }\n processedSchemas.add(schema)\n\n const { type, prefixItems, items } = schema\n\n const getArrayType = () => {\n if (Array.isArray(prefixItems)) {\n const prefixItemsTypes = prefixItems.map((itemSchema) =>\n getType(itemSchema, processedSchemas)\n )\n const itemsType = items ? getType(items, processedSchemas) : \"any\"\n return `array<[${prefixItemsTypes.join(\", \")}], ${itemsType}>`\n } else if (items) {\n const itemsType = getType(items, processedSchemas)\n return `array<${itemsType}>`\n } else {\n return \"array\"\n }\n }\n\n const inferType = () => {\n if (\n Object.hasOwn(schema, \"prefixItems\") ||\n Object.hasOwn(schema, \"items\") ||\n Object.hasOwn(schema, \"contains\")\n ) {\n return getArrayType()\n } else if (\n Object.hasOwn(schema, \"properties\") ||\n Object.hasOwn(schema, \"additionalProperties\") ||\n Object.hasOwn(schema, \"patternProperties\")\n ) {\n return \"object\"\n } else if ([\"int32\", \"int64\"].includes(schema.format)) {\n // OpenAPI 3.1.0 integer custom formats\n return \"integer\"\n } else if ([\"float\", \"double\"].includes(schema.format)) {\n // OpenAPI 3.1.0 number custom formats\n return \"number\"\n } else if (\n Object.hasOwn(schema, \"minimum\") ||\n Object.hasOwn(schema, \"maximum\") ||\n Object.hasOwn(schema, \"exclusiveMinimum\") ||\n Object.hasOwn(schema, \"exclusiveMaximum\") ||\n Object.hasOwn(schema, \"multipleOf\")\n ) {\n return \"number | integer\"\n } else if (\n Object.hasOwn(schema, \"pattern\") ||\n Object.hasOwn(schema, \"format\") ||\n Object.hasOwn(schema, \"minLength\") ||\n Object.hasOwn(schema, \"maxLength\")\n ) {\n return \"string\"\n } else if (typeof schema.const !== \"undefined\") {\n if (schema.const === null) {\n return \"null\"\n } else if (typeof schema.const === \"boolean\") {\n return \"boolean\"\n } else if (typeof schema.const === \"number\") {\n return Number.isInteger(schema.const) ? \"integer\" : \"number\"\n } else if (typeof schema.const === \"string\") {\n return \"string\"\n } else if (Array.isArray(schema.const)) {\n return \"array\"\n } else if (typeof schema.const === \"object\") {\n return \"object\"\n }\n }\n return null\n }\n\n if (schema.not && getType(schema.not) === \"any\") {\n return \"never\"\n }\n\n const typeString = Array.isArray(type)\n ? type.map((t) => (t === \"array\" ? getArrayType() : t)).join(\" | \")\n : type === \"array\"\n ? getArrayType()\n : [\"null\", \"boolean\", \"object\", \"array\", \"number\", \"string\"].includes(type)\n ? type\n : inferType()\n\n const handleCombiningKeywords = (keyword, separator) => {\n if (Array.isArray(schema[keyword])) {\n const combinedTypes = schema[keyword].map((subSchema) =>\n getType(subSchema, processedSchemas)\n )\n return `(${combinedTypes.join(separator)})`\n }\n return null\n }\n\n const oneOfString = handleCombiningKeywords(\"oneOf\", \" | \")\n const anyOfString = handleCombiningKeywords(\"anyOf\", \" | \")\n const allOfString = handleCombiningKeywords(\"allOf\", \" & \")\n\n const combinedStrings = [typeString, oneOfString, anyOfString, allOfString]\n .filter(Boolean)\n .join(\" | \")\n\n processedSchemas.delete(schema)\n\n return combinedStrings || \"any\"\n}\n\nexport const isBooleanJSONSchema = (schema) => typeof schema === \"boolean\"\n\nexport const hasKeyword = (schema, keyword) =>\n schema !== null &&\n typeof schema === \"object\" &&\n Object.hasOwn(schema, keyword)\n\nexport const isExpandable = (schema) => {\n const fn = useFn()\n\n return (\n schema?.$schema ||\n schema?.$vocabulary ||\n schema?.$id ||\n schema?.$anchor ||\n schema?.$dynamicAnchor ||\n schema?.$ref ||\n schema?.$dynamicRef ||\n schema?.$defs ||\n schema?.$comment ||\n schema?.allOf ||\n schema?.anyOf ||\n schema?.oneOf ||\n fn.hasKeyword(schema, \"not\") ||\n fn.hasKeyword(schema, \"if\") ||\n fn.hasKeyword(schema, \"then\") ||\n fn.hasKeyword(schema, \"else\") ||\n schema?.dependentSchemas ||\n schema?.prefixItems ||\n fn.hasKeyword(schema, \"items\") ||\n fn.hasKeyword(schema, \"contains\") ||\n schema?.properties ||\n schema?.patternProperties ||\n fn.hasKeyword(schema, \"additionalProperties\") ||\n fn.hasKeyword(schema, \"propertyNames\") ||\n fn.hasKeyword(schema, \"unevaluatedItems\") ||\n fn.hasKeyword(schema, \"unevaluatedProperties\") ||\n schema?.description ||\n schema?.enum ||\n fn.hasKeyword(schema, \"const\") ||\n fn.hasKeyword(schema, \"contentSchema\") ||\n fn.hasKeyword(schema, \"default\")\n )\n}\n\nexport const stringify = (value) => {\n if (\n value === null ||\n [\"number\", \"bigint\", \"boolean\"].includes(typeof value)\n ) {\n return String(value)\n }\n\n if (Array.isArray(value)) {\n return `[${value.map(stringify).join(\", \")}]`\n }\n\n return JSON.stringify(value)\n}\n\nconst stringifyConstraintMultipleOf = (schema) => {\n if (typeof schema?.multipleOf !== \"number\") return null\n if (schema.multipleOf <= 0) return null\n if (schema.multipleOf === 1) return null\n\n const { multipleOf } = schema\n\n if (Number.isInteger(multipleOf)) {\n return `multiple of ${multipleOf}`\n }\n\n const decimalPlaces = multipleOf.toString().split(\".\")[1].length\n const factor = 10 ** decimalPlaces\n const numerator = multipleOf * factor\n const denominator = factor\n return `multiple of ${numerator}/${denominator}`\n}\n\nconst stringifyConstraintNumberRange = (schema) => {\n const minimum = schema?.minimum\n const maximum = schema?.maximum\n const exclusiveMinimum = schema?.exclusiveMinimum\n const exclusiveMaximum = schema?.exclusiveMaximum\n const hasMinimum = typeof minimum === \"number\"\n const hasMaximum = typeof maximum === \"number\"\n const hasExclusiveMinimum = typeof exclusiveMinimum === \"number\"\n const hasExclusiveMaximum = typeof exclusiveMaximum === \"number\"\n const isMinExclusive = hasExclusiveMinimum && minimum < exclusiveMinimum\n const isMaxExclusive = hasExclusiveMaximum && maximum > exclusiveMaximum\n\n if (hasMinimum && hasMaximum) {\n const minSymbol = isMinExclusive ? \"(\" : \"[\"\n const maxSymbol = isMaxExclusive ? \")\" : \"]\"\n const minValue = isMinExclusive ? exclusiveMinimum : minimum\n const maxValue = isMaxExclusive ? exclusiveMaximum : maximum\n return `${minSymbol}${minValue}, ${maxValue}${maxSymbol}`\n }\n if (hasMinimum) {\n const minSymbol = isMinExclusive ? \">\" : \"โ‰ฅ\"\n const minValue = isMinExclusive ? exclusiveMinimum : minimum\n return `${minSymbol} ${minValue}`\n }\n if (hasMaximum) {\n const maxSymbol = isMaxExclusive ? \"<\" : \"โ‰ค\"\n const maxValue = isMaxExclusive ? exclusiveMaximum : maximum\n return `${maxSymbol} ${maxValue}`\n }\n\n return null\n}\n\nconst stringifyConstraintRange = (label, min, max) => {\n const hasMin = typeof min === \"number\"\n const hasMax = typeof max === \"number\"\n\n if (hasMin && hasMax) {\n if (min === max) {\n return `${min} ${label}`\n } else {\n return `[${min}, ${max}] ${label}`\n }\n }\n if (hasMin) {\n return `>= ${min} ${label}`\n }\n if (hasMax) {\n return `<= ${max} ${label}`\n }\n\n return null\n}\n\nexport const stringifyConstraints = (schema) => {\n const constraints = []\n\n // validation Keywords for Numeric Instances (number and integer)\n const multipleOf = stringifyConstraintMultipleOf(schema)\n if (multipleOf !== null) {\n constraints.push({ scope: \"number\", value: multipleOf })\n }\n const numberRange = stringifyConstraintNumberRange(schema)\n if (numberRange !== null) {\n constraints.push({ scope: \"number\", value: numberRange })\n }\n\n // vocabularies for Semantic Content With \"format\"\n if (schema?.format) {\n constraints.push({ scope: \"string\", value: schema.format })\n }\n\n // validation Keywords for Strings\n const stringRange = stringifyConstraintRange(\n \"characters\",\n schema?.minLength,\n schema?.maxLength\n )\n if (stringRange !== null) {\n constraints.push({ scope: \"string\", value: stringRange })\n }\n if (schema?.pattern) {\n constraints.push({ scope: \"string\", value: `matches ${schema?.pattern}` })\n }\n\n // vocabulary for the Contents of String-Encoded Data\n if (schema?.contentMediaType) {\n constraints.push({\n scope: \"string\",\n value: `media type: ${schema.contentMediaType}`,\n })\n }\n if (schema?.contentEncoding) {\n constraints.push({\n scope: \"string\",\n value: `encoding: ${schema.contentEncoding}`,\n })\n }\n\n // validation Keywords for Arrays\n const arrayRange = stringifyConstraintRange(\n schema?.hasUniqueItems ? \"unique items\" : \"items\",\n schema?.minItems,\n schema?.maxItems\n )\n if (arrayRange !== null) {\n constraints.push({ scope: \"array\", value: arrayRange })\n }\n const containsRange = stringifyConstraintRange(\n \"contained items\",\n schema?.minContains,\n schema?.maxContains\n )\n if (containsRange !== null) {\n constraints.push({ scope: \"array\", value: containsRange })\n }\n\n // validation Keywords for Objects\n const objectRange = stringifyConstraintRange(\n \"properties\",\n schema?.minProperties,\n schema?.maxProperties\n )\n if (objectRange !== null) {\n constraints.push({ scope: \"object\", value: objectRange })\n }\n\n return constraints\n}\n\nexport const getDependentRequired = (propertyName, schema) => {\n if (!schema?.dependentRequired) return []\n\n return Array.from(\n Object.entries(schema.dependentRequired).reduce((acc, [prop, list]) => {\n if (!Array.isArray(list)) return acc\n if (!list.includes(propertyName)) return acc\n\n acc.add(prop)\n\n return acc\n }, new Set())\n )\n}\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport JSONSchema from \"./components/JSONSchema/JSONSchema\"\nimport Keyword$schema from \"./components/keywords/$schema\"\nimport Keyword$vocabulary from \"./components/keywords/$vocabulary/$vocabulary\"\nimport Keyword$id from \"./components/keywords/$id\"\nimport Keyword$anchor from \"./components/keywords/$anchor\"\nimport Keyword$dynamicAnchor from \"./components/keywords/$dynamicAnchor\"\nimport Keyword$ref from \"./components/keywords/$ref\"\nimport Keyword$dynamicRef from \"./components/keywords/$dynamicRef\"\nimport Keyword$defs from \"./components/keywords/$defs\"\nimport Keyword$comment from \"./components/keywords/$comment\"\nimport KeywordAllOf from \"./components/keywords/AllOf\"\nimport KeywordAnyOf from \"./components/keywords/AnyOf\"\nimport KeywordOneOf from \"./components/keywords/OneOf\"\nimport KeywordNot from \"./components/keywords/Not\"\nimport KeywordIf from \"./components/keywords/If\"\nimport KeywordThen from \"./components/keywords/Then\"\nimport KeywordElse from \"./components/keywords/Else\"\nimport KeywordDependentSchemas from \"./components/keywords/DependentSchemas\"\nimport KeywordPrefixItems from \"./components/keywords/PrefixItems\"\nimport KeywordItems from \"./components/keywords/Items\"\nimport KeywordContains from \"./components/keywords/Contains\"\nimport KeywordProperties from \"./components/keywords/Properties/Properties\"\nimport KeywordPatternProperties from \"./components/keywords/PatternProperties/PatternProperties\"\nimport KeywordAdditionalProperties from \"./components/keywords/AdditionalProperties\"\nimport KeywordPropertyNames from \"./components/keywords/PropertyNames\"\nimport KeywordUnevaluatedItems from \"./components/keywords/UnevaluatedItems\"\nimport KeywordUnevaluatedProperties from \"./components/keywords/UnevaluatedProperties\"\nimport KeywordType from \"./components/keywords/Type\"\nimport KeywordEnum from \"./components/keywords/Enum/Enum\"\nimport KeywordConst from \"./components/keywords/Const\"\nimport KeywordConstraint from \"./components/keywords/Constraint/Constraint\"\nimport KeywordDependentRequired from \"./components/keywords/DependentRequired/DependentRequired\"\nimport KeywordContentSchema from \"./components/keywords/ContentSchema\"\nimport KeywordTitle from \"./components/keywords/Title/Title\"\nimport KeywordDescription from \"./components/keywords/Description/Description\"\nimport KeywordDefault from \"./components/keywords/Default\"\nimport KeywordDeprecated from \"./components/keywords/Deprecated\"\nimport KeywordReadOnly from \"./components/keywords/ReadOnly\"\nimport KeywordWriteOnly from \"./components/keywords/WriteOnly\"\nimport Accordion from \"./components/Accordion/Accordion\"\nimport ExpandDeepButton from \"./components/ExpandDeepButton/ExpandDeepButton\"\nimport ChevronRightIcon from \"./components/icons/ChevronRight\"\nimport { JSONSchemaContext } from \"./context\"\nimport {\n getTitle,\n isBooleanJSONSchema,\n upperFirst,\n getType,\n hasKeyword,\n isExpandable,\n stringify,\n stringifyConstraints,\n getDependentRequired,\n} from \"./fn\"\n\nexport const withJSONSchemaContext = (Component, overrides = {}) => {\n const value = {\n components: {\n JSONSchema,\n Keyword$schema,\n Keyword$vocabulary,\n Keyword$id,\n Keyword$anchor,\n Keyword$dynamicAnchor,\n Keyword$ref,\n Keyword$dynamicRef,\n Keyword$defs,\n Keyword$comment,\n KeywordAllOf,\n KeywordAnyOf,\n KeywordOneOf,\n KeywordNot,\n KeywordIf,\n KeywordThen,\n KeywordElse,\n KeywordDependentSchemas,\n KeywordPrefixItems,\n KeywordItems,\n KeywordContains,\n KeywordProperties,\n KeywordPatternProperties,\n KeywordAdditionalProperties,\n KeywordPropertyNames,\n KeywordUnevaluatedItems,\n KeywordUnevaluatedProperties,\n KeywordType,\n KeywordEnum,\n KeywordConst,\n KeywordConstraint,\n KeywordDependentRequired,\n KeywordContentSchema,\n KeywordTitle,\n KeywordDescription,\n KeywordDefault,\n KeywordDeprecated,\n KeywordReadOnly,\n KeywordWriteOnly,\n Accordion,\n ExpandDeepButton,\n ChevronRightIcon,\n ...overrides.components,\n },\n config: {\n default$schema: \"https://json-schema.org/draft/2020-12/schema\",\n /**\n * Defines an upper exclusive boundary of the level range for automatic expansion.\n *\n * 0 -> do nothing\n * 1 -> [0]...(1)\n * 2 -> [0]...(2)\n * 3 -> [0]...(3)\n */\n defaultExpandedLevels: 0, // 2 = 0...2\n ...overrides.config,\n },\n fn: {\n upperFirst,\n getTitle,\n getType,\n isBooleanJSONSchema,\n hasKeyword,\n isExpandable,\n stringify,\n stringifyConstraints,\n getDependentRequired,\n ...overrides.fn,\n },\n }\n\n const HOC = (props) => (\n \n \n \n )\n HOC.contexts = {\n JSONSchemaContext,\n }\n HOC.displayName = Component.displayName\n\n return HOC\n}\n","/**\n * @prettier\n */\nimport { useContext } from \"react\"\n\nimport {\n JSONSchemaContext,\n JSONSchemaLevelContext,\n JSONSchemaDeepExpansionContext,\n JSONSchemaCyclesContext,\n} from \"./context\"\n\nexport const useConfig = () => {\n const { config } = useContext(JSONSchemaContext)\n return config\n}\n\nexport const useComponent = (componentName) => {\n const { components } = useContext(JSONSchemaContext)\n return components[componentName] || null\n}\n\nexport const useFn = (fnName = undefined) => {\n const { fn } = useContext(JSONSchemaContext)\n\n return typeof fnName !== \"undefined\" ? fn[fnName] : fn\n}\n\nexport const useLevel = () => {\n const level = useContext(JSONSchemaLevelContext)\n\n return [level, level + 1]\n}\n\nexport const useIsEmbedded = () => {\n const [level] = useLevel()\n\n return level > 0\n}\n\nexport const useIsExpanded = () => {\n const [level] = useLevel()\n const { defaultExpandedLevels } = useConfig()\n\n return defaultExpandedLevels - level > 0\n}\n\nexport const useIsExpandedDeeply = () => {\n return useContext(JSONSchemaDeepExpansionContext)\n}\n\nexport const useRenderedSchemas = (schema = undefined) => {\n if (typeof schema === \"undefined\") {\n return useContext(JSONSchemaCyclesContext)\n }\n\n const renderedSchemas = useContext(JSONSchemaCyclesContext)\n return new Set([...renderedSchemas, schema])\n}\nexport const useIsCircular = (schema) => {\n const renderedSchemas = useRenderedSchemas()\n return renderedSchemas.has(schema)\n}\n","/**\n * @prettier\n */\nimport JSONSchema from \"./components/JSONSchema/JSONSchema\"\nimport Keyword$schema from \"./components/keywords/$schema\"\nimport Keyword$vocabulary from \"./components/keywords/$vocabulary/$vocabulary\"\nimport Keyword$id from \"./components/keywords/$id\"\nimport Keyword$anchor from \"./components/keywords/$anchor\"\nimport Keyword$dynamicAnchor from \"./components/keywords/$dynamicAnchor\"\nimport Keyword$ref from \"./components/keywords/$ref\"\nimport Keyword$dynamicRef from \"./components/keywords/$dynamicRef\"\nimport Keyword$defs from \"./components/keywords/$defs\"\nimport Keyword$comment from \"./components/keywords/$comment\"\nimport KeywordAllOf from \"./components/keywords/AllOf\"\nimport KeywordAnyOf from \"./components/keywords/AnyOf\"\nimport KeywordOneOf from \"./components/keywords/OneOf\"\nimport KeywordNot from \"./components/keywords/Not\"\nimport KeywordIf from \"./components/keywords/If\"\nimport KeywordThen from \"./components/keywords/Then\"\nimport KeywordElse from \"./components/keywords/Else\"\nimport KeywordDependentSchemas from \"./components/keywords/DependentSchemas\"\nimport KeywordPrefixItems from \"./components/keywords/PrefixItems\"\nimport KeywordItems from \"./components/keywords/Items\"\nimport KeywordContains from \"./components/keywords/Contains\"\nimport KeywordProperties from \"./components/keywords/Properties/Properties\"\nimport KeywordPatternProperties from \"./components/keywords/PatternProperties/PatternProperties\"\nimport KeywordAdditionalProperties from \"./components/keywords/AdditionalProperties\"\nimport KeywordPropertyNames from \"./components/keywords/PropertyNames\"\nimport KeywordUnevaluatedItems from \"./components/keywords/UnevaluatedItems\"\nimport KeywordUnevaluatedProperties from \"./components/keywords/UnevaluatedProperties\"\nimport KeywordType from \"./components/keywords/Type\"\nimport KeywordEnum from \"./components/keywords/Enum/Enum\"\nimport KeywordConst from \"./components/keywords/Const\"\nimport KeywordConstraint from \"./components/keywords/Constraint/Constraint\"\nimport KeywordDependentRequired from \"./components/keywords/DependentRequired/DependentRequired\"\nimport KeywordContentSchema from \"./components/keywords/ContentSchema\"\nimport KeywordTitle from \"./components/keywords/Title/Title\"\nimport KeywordDescription from \"./components/keywords/Description/Description\"\nimport KeywordDefault from \"./components/keywords/Default\"\nimport KeywordDeprecated from \"./components/keywords/Deprecated\"\nimport KeywordReadOnly from \"./components/keywords/ReadOnly\"\nimport KeywordWriteOnly from \"./components/keywords/WriteOnly\"\nimport Accordion from \"./components/Accordion/Accordion\"\nimport ExpandDeepButton from \"./components/ExpandDeepButton/ExpandDeepButton\"\nimport ChevronRightIcon from \"./components/icons/ChevronRight\"\nimport { upperFirst, hasKeyword, isExpandable } from \"./fn\"\nimport {\n sampleFromSchema,\n sampleFromSchemaGeneric,\n createXMLExample,\n memoizedSampleFromSchema,\n memoizedCreateXMLExample,\n encoderAPI,\n mediaTypeAPI,\n formatAPI,\n} from \"./samples-extensions/fn/index\"\nimport { JSONSchemaDeepExpansionContext } from \"./context\"\nimport { useFn, useConfig, useComponent, useIsExpandedDeeply } from \"./hooks\"\nimport { withJSONSchemaContext } from \"./hoc\"\n\nconst JSONSchema202012Plugin = () => ({\n components: {\n JSONSchema202012: JSONSchema,\n JSONSchema202012Keyword$schema: Keyword$schema,\n JSONSchema202012Keyword$vocabulary: Keyword$vocabulary,\n JSONSchema202012Keyword$id: Keyword$id,\n JSONSchema202012Keyword$anchor: Keyword$anchor,\n JSONSchema202012Keyword$dynamicAnchor: Keyword$dynamicAnchor,\n JSONSchema202012Keyword$ref: Keyword$ref,\n JSONSchema202012Keyword$dynamicRef: Keyword$dynamicRef,\n JSONSchema202012Keyword$defs: Keyword$defs,\n JSONSchema202012Keyword$comment: Keyword$comment,\n JSONSchema202012KeywordAllOf: KeywordAllOf,\n JSONSchema202012KeywordAnyOf: KeywordAnyOf,\n JSONSchema202012KeywordOneOf: KeywordOneOf,\n JSONSchema202012KeywordNot: KeywordNot,\n JSONSchema202012KeywordIf: KeywordIf,\n JSONSchema202012KeywordThen: KeywordThen,\n JSONSchema202012KeywordElse: KeywordElse,\n JSONSchema202012KeywordDependentSchemas: KeywordDependentSchemas,\n JSONSchema202012KeywordPrefixItems: KeywordPrefixItems,\n JSONSchema202012KeywordItems: KeywordItems,\n JSONSchema202012KeywordContains: KeywordContains,\n JSONSchema202012KeywordProperties: KeywordProperties,\n JSONSchema202012KeywordPatternProperties: KeywordPatternProperties,\n JSONSchema202012KeywordAdditionalProperties: KeywordAdditionalProperties,\n JSONSchema202012KeywordPropertyNames: KeywordPropertyNames,\n JSONSchema202012KeywordUnevaluatedItems: KeywordUnevaluatedItems,\n JSONSchema202012KeywordUnevaluatedProperties: KeywordUnevaluatedProperties,\n JSONSchema202012KeywordType: KeywordType,\n JSONSchema202012KeywordEnum: KeywordEnum,\n JSONSchema202012KeywordConst: KeywordConst,\n JSONSchema202012KeywordConstraint: KeywordConstraint,\n JSONSchema202012KeywordDependentRequired: KeywordDependentRequired,\n JSONSchema202012KeywordContentSchema: KeywordContentSchema,\n JSONSchema202012KeywordTitle: KeywordTitle,\n JSONSchema202012KeywordDescription: KeywordDescription,\n JSONSchema202012KeywordDefault: KeywordDefault,\n JSONSchema202012KeywordDeprecated: KeywordDeprecated,\n JSONSchema202012KeywordReadOnly: KeywordReadOnly,\n JSONSchema202012KeywordWriteOnly: KeywordWriteOnly,\n JSONSchema202012Accordion: Accordion,\n JSONSchema202012ExpandDeepButton: ExpandDeepButton,\n JSONSchema202012ChevronRightIcon: ChevronRightIcon,\n withJSONSchema202012Context: withJSONSchemaContext,\n JSONSchema202012DeepExpansionContext: () => JSONSchemaDeepExpansionContext,\n },\n fn: {\n upperFirst,\n jsonSchema202012: {\n isExpandable,\n hasKeyword,\n useFn,\n useConfig,\n useComponent,\n useIsExpandedDeeply,\n sampleFromSchema,\n sampleFromSchemaGeneric,\n sampleEncoderAPI: encoderAPI,\n sampleFormatAPI: formatAPI,\n sampleMediaTypeAPI: mediaTypeAPI,\n createXMLExample,\n memoizedSampleFromSchema,\n memoizedCreateXMLExample,\n },\n },\n})\n\nexport default JSONSchema202012Plugin\n","/**\n * @prettier\n */\nimport PropTypes from \"prop-types\"\n\nexport const objectSchema = PropTypes.object\n\nexport const booleanSchema = PropTypes.bool\n\nexport const schema = PropTypes.oneOfType([objectSchema, booleanSchema])\n","/**\n * @prettier\n */\n\nimport EncoderRegistry from \"core/plugins/json-schema-2020-12/samples-extensions/fn/class/EncoderRegistry\"\n\nconst registry = new EncoderRegistry()\n\nconst encoderAPI = (encodingName, encoder) => {\n if (typeof encoder === \"function\") {\n return registry.register(encodingName, encoder)\n } else if (encoder === null) {\n return registry.unregister(encodingName)\n }\n\n return registry.get(encodingName)\n}\nencoderAPI.getDefaults = () => registry.defaults\n\nexport default encoderAPI\n","/**\n * @prettier\n */\n\nimport Registry from \"../class/Registry\"\n\nconst registry = new Registry()\n\nconst formatAPI = (format, generator) => {\n if (typeof generator === \"function\") {\n return registry.register(format, generator)\n } else if (generator === null) {\n return registry.unregister(format)\n }\n\n return registry.get(format)\n}\n\nexport default formatAPI\n","/**\n * @prettier\n */\n\nimport MediaTypeRegistry from \"../class/MediaTypeRegistry\"\n\nconst registry = new MediaTypeRegistry()\n\nconst mediaTypeAPI = (mediaType, generator) => {\n if (typeof generator === \"function\") {\n return registry.register(mediaType, generator)\n } else if (generator === null) {\n return registry.unregister(mediaType)\n }\n\n const mediaTypeNoParams = mediaType.split(\";\").at(0)\n const topLevelMediaType = `${mediaTypeNoParams.split(\"/\").at(0)}/*`\n\n return (\n registry.get(mediaType) ||\n registry.get(mediaTypeNoParams) ||\n registry.get(topLevelMediaType)\n )\n}\nmediaTypeAPI.getDefaults = () => registry.defaults\n\nexport default mediaTypeAPI\n","/**\n * @prettier\n */\nimport Registry from \"./Registry\"\nimport encode7bit from \"../encoders/7bit\"\nimport encode8bit from \"../encoders/8bit\"\nimport encodeBinary from \"../encoders/binary\"\nimport encodeQuotedPrintable from \"../encoders/quoted-printable\"\nimport encodeBase16 from \"../encoders/base16\"\nimport encodeBase32 from \"../encoders/base32\"\nimport encodeBase64 from \"../encoders/base64\"\n\nclass EncoderRegistry extends Registry {\n #defaults = {\n \"7bit\": encode7bit,\n \"8bit\": encode8bit,\n binary: encodeBinary,\n \"quoted-printable\": encodeQuotedPrintable,\n base16: encodeBase16,\n base32: encodeBase32,\n base64: encodeBase64,\n }\n\n data = { ...this.#defaults }\n\n get defaults() {\n return { ...this.#defaults }\n }\n}\n\nexport default EncoderRegistry\n","/**\n * @prettier\n */\nimport Registry from \"./Registry\"\nimport textMediaTypesGenerators from \"../generators/media-types/text\"\nimport imageMediaTypesGenerators from \"../generators/media-types/image\"\nimport audioMediaTypesGenerators from \"../generators/media-types/audio\"\nimport videoMediaTypesGenerators from \"../generators/media-types/video\"\nimport applicationMediaTypesGenerators from \"../generators/media-types/application\"\n\nclass MediaTypeRegistry extends Registry {\n #defaults = {\n ...textMediaTypesGenerators,\n ...imageMediaTypesGenerators,\n ...audioMediaTypesGenerators,\n ...videoMediaTypesGenerators,\n ...applicationMediaTypesGenerators,\n }\n\n data = { ...this.#defaults }\n\n get defaults() {\n return { ...this.#defaults }\n }\n}\n\nexport default MediaTypeRegistry\n","/**\n * @prettier\n */\nclass Registry {\n data = {}\n\n register(name, value) {\n this.data[name] = value\n }\n\n unregister(name) {\n if (typeof name === \"undefined\") {\n this.data = {}\n } else {\n delete this.data[name]\n }\n }\n\n get(name) {\n return this.data[name]\n }\n}\n\nexport default Registry\n","/**\n * @prettier\n */\nexport const SCALAR_TYPES = [\"number\", \"integer\", \"string\", \"boolean\", \"null\"]\n\nexport const ALL_TYPES = [\"array\", \"object\", ...SCALAR_TYPES]\n","/**\n * @prettier\n */\nimport { isJSONSchemaObject } from \"./predicates\"\n\n/**\n * Precedence of keywords that provides author defined values (top of the list = higher priority)\n *\n * ### examples\n * Array containing example values for the item defined by the schema.\n * Not guaranteed to be valid or invalid against the schema\n *\n * ### default\n * Default value for an item defined by the schema.\n * Is expected to be a valid instance of the schema.\n *\n * ### example\n * Deprecated. Part of OpenAPI 3.1.0 Schema Object dialect.\n * Represents single example. Equivalent of `examples` keywords\n * with single item.\n */\n\nexport const hasExample = (schema) => {\n if (!isJSONSchemaObject(schema)) return false\n\n const { examples, example, default: defaultVal } = schema\n\n if (Array.isArray(examples) && examples.length >= 1) {\n return true\n }\n\n if (typeof defaultVal !== \"undefined\") {\n return true\n }\n\n return typeof example !== \"undefined\"\n}\n\nexport const extractExample = (schema) => {\n if (!isJSONSchemaObject(schema)) return null\n\n const { examples, example, default: defaultVal } = schema\n\n if (Array.isArray(examples) && examples.length >= 1) {\n return examples.at(0)\n }\n\n if (typeof defaultVal !== \"undefined\") {\n return defaultVal\n }\n\n if (typeof example !== \"undefined\") {\n return example\n }\n\n return undefined\n}\n","/**\n * @prettier\n */\nimport { normalizeArray as ensureArray } from \"core/utils\"\nimport { isBooleanJSONSchema, isJSONSchema } from \"./predicates\"\n\nconst merge = (target, source, config = {}) => {\n if (isBooleanJSONSchema(target) && target === true) return true\n if (isBooleanJSONSchema(target) && target === false) return false\n if (isBooleanJSONSchema(source) && source === true) return true\n if (isBooleanJSONSchema(source) && source === false) return false\n\n if (!isJSONSchema(target)) return source\n if (!isJSONSchema(source)) return target\n\n /**\n * Merging properties from the source object into the target object\n * only if they do not already exist in the target object.\n */\n const merged = { ...source, ...target }\n\n // merging the type keyword\n if (source.type && target.type) {\n if (Array.isArray(source.type) && typeof source.type === \"string\") {\n const mergedType = ensureArray(source.type).concat(target.type)\n merged.type = Array.from(new Set(mergedType))\n }\n }\n\n // merging required keyword\n if (Array.isArray(source.required) && Array.isArray(target.required)) {\n merged.required = [...new Set([...target.required, ...source.required])]\n }\n\n // merging properties keyword\n if (source.properties && target.properties) {\n const allPropertyNames = new Set([\n ...Object.keys(source.properties),\n ...Object.keys(target.properties),\n ])\n\n merged.properties = {}\n for (const name of allPropertyNames) {\n const sourceProperty = source.properties[name] || {}\n const targetProperty = target.properties[name] || {}\n\n if (\n (sourceProperty.readOnly && !config.includeReadOnly) ||\n (sourceProperty.writeOnly && !config.includeWriteOnly)\n ) {\n merged.required = (merged.required || []).filter((p) => p !== name)\n } else {\n merged.properties[name] = merge(targetProperty, sourceProperty, config)\n }\n }\n }\n\n // merging items keyword\n if (isJSONSchema(source.items) && isJSONSchema(target.items)) {\n merged.items = merge(target.items, source.items, config)\n }\n\n // merging contains keyword\n if (isJSONSchema(source.contains) && isJSONSchema(target.contains)) {\n merged.contains = merge(target.contains, source.contains, config)\n }\n\n // merging contentSchema keyword\n if (\n isJSONSchema(source.contentSchema) &&\n isJSONSchema(target.contentSchema)\n ) {\n merged.contentSchema = merge(\n target.contentSchema,\n source.contentSchema,\n config\n )\n }\n\n return merged\n}\n\nexport default merge\n","/**\n * @prettier\n */\nimport isPlainObject from \"lodash/isPlainObject\"\n\nexport const isBooleanJSONSchema = (schema) => {\n return typeof schema === \"boolean\"\n}\n\nexport const isJSONSchemaObject = (schema) => {\n return isPlainObject(schema)\n}\n\nexport const isJSONSchema = (schema) => {\n return isBooleanJSONSchema(schema) || isJSONSchemaObject(schema)\n}\n","/**\n * @prettier\n */\nimport randomBytes from \"randombytes\"\nimport RandExp from \"randexp\"\n\n/**\n * Some of the functions returns constants. This is due to the nature\n * of SwaggerUI expectations - provide as stable data as possible.\n *\n * In future, we may decide to randomize these function and provide\n * true random values.\n */\n\nexport const bytes = (length) => randomBytes(length)\n\nexport const randexp = (pattern) => {\n try {\n const randexpInstance = new RandExp(pattern)\n return randexpInstance.gen()\n } catch {\n // invalid regex should not cause a crash (regex syntax varies across languages)\n return \"string\"\n }\n}\n\nexport const pick = (list) => {\n return list.at(0)\n}\n\nexport const string = () => \"string\"\n\nexport const number = () => 0\n\nexport const integer = () => 0\n","/**\n * @prettier\n */\nimport { ALL_TYPES } from \"./constants\"\nimport { isJSONSchemaObject } from \"./predicates\"\nimport { pick as randomPick } from \"./random\"\nimport { hasExample, extractExample } from \"./example\"\n\nconst inferringKeywords = {\n array: [\n \"items\",\n \"prefixItems\",\n \"contains\",\n \"maxContains\",\n \"minContains\",\n \"maxItems\",\n \"minItems\",\n \"uniqueItems\",\n \"unevaluatedItems\",\n ],\n object: [\n \"properties\",\n \"additionalProperties\",\n \"patternProperties\",\n \"propertyNames\",\n \"minProperties\",\n \"maxProperties\",\n \"required\",\n \"dependentSchemas\",\n \"dependentRequired\",\n \"unevaluatedProperties\",\n ],\n string: [\n \"pattern\",\n \"format\",\n \"minLength\",\n \"maxLength\",\n \"contentEncoding\",\n \"contentMediaType\",\n \"contentSchema\",\n ],\n integer: [\n \"minimum\",\n \"maximum\",\n \"exclusiveMinimum\",\n \"exclusiveMaximum\",\n \"multipleOf\",\n ],\n}\ninferringKeywords.number = inferringKeywords.integer\n\nconst fallbackType = \"string\"\n\nconst inferTypeFromValue = (value) => {\n if (typeof value === \"undefined\") return null\n if (value === null) return \"null\"\n if (Array.isArray(value)) return \"array\"\n if (Number.isInteger(value)) return \"integer\"\n\n return typeof value\n}\n\nexport const foldType = (type) => {\n if (Array.isArray(type) && type.length >= 1) {\n if (type.includes(\"array\")) {\n return \"array\"\n } else if (type.includes(\"object\")) {\n return \"object\"\n } else {\n const pickedType = randomPick(type)\n if (ALL_TYPES.includes(pickedType)) {\n return pickedType\n }\n }\n }\n\n if (ALL_TYPES.includes(type)) {\n return type\n }\n\n return null\n}\n\nexport const inferType = (schema, processedSchemas = new WeakSet()) => {\n if (!isJSONSchemaObject(schema)) return fallbackType\n if (processedSchemas.has(schema)) return fallbackType\n\n processedSchemas.add(schema)\n\n let { type, const: constant } = schema\n type = foldType(type)\n\n // inferring type from inferring keywords\n if (typeof type !== \"string\") {\n const inferringTypes = Object.keys(inferringKeywords)\n\n interrupt: for (let i = 0; i < inferringTypes.length; i += 1) {\n const inferringType = inferringTypes[i]\n const inferringTypeKeywords = inferringKeywords[inferringType]\n\n for (let j = 0; j < inferringTypeKeywords.length; j += 1) {\n const inferringKeyword = inferringTypeKeywords[j]\n if (Object.hasOwn(schema, inferringKeyword)) {\n type = inferringType\n break interrupt\n }\n }\n }\n }\n\n // inferring type from const keyword\n if (typeof type !== \"string\" && typeof constant !== \"undefined\") {\n const constType = inferTypeFromValue(constant)\n type = typeof constType === \"string\" ? constType : type\n }\n\n // inferring type from combining schemas\n if (typeof type !== \"string\") {\n const combineTypes = (keyword) => {\n if (Array.isArray(schema[keyword])) {\n const combinedTypes = schema[keyword].map((subSchema) =>\n inferType(subSchema, processedSchemas)\n )\n return foldType(combinedTypes)\n }\n return null\n }\n\n const allOf = combineTypes(\"allOf\")\n const anyOf = combineTypes(\"anyOf\")\n const oneOf = combineTypes(\"oneOf\")\n const not = schema.not ? inferType(schema.not, processedSchemas) : null\n\n if (allOf || anyOf || oneOf || not) {\n type = foldType([allOf, anyOf, oneOf, not].filter(Boolean))\n }\n }\n\n // inferring type from example\n if (typeof type !== \"string\" && hasExample(schema)) {\n const example = extractExample(schema)\n const exampleType = inferTypeFromValue(example)\n type = typeof exampleType === \"string\" ? exampleType : type\n }\n\n processedSchemas.delete(schema)\n\n return type || fallbackType\n}\n\nexport const getType = (schema) => {\n return inferType(schema)\n}\n","/**\n * @prettier\n */\nimport { isBooleanJSONSchema, isJSONSchemaObject } from \"./predicates\"\n\nexport const fromJSONBooleanSchema = (schema) => {\n if (schema === false) {\n return { not: {} }\n }\n\n return {}\n}\n\nexport const typeCast = (schema) => {\n if (isBooleanJSONSchema(schema)) {\n return fromJSONBooleanSchema(schema)\n }\n if (!isJSONSchemaObject(schema)) {\n return {}\n }\n\n return schema\n}\n","/**\n * @prettier\n */\nconst encode7bit = (content) => Buffer.from(content).toString(\"ascii\")\n\nexport default encode7bit\n","/**\n * @prettier\n */\nconst encode8bit = (content) => Buffer.from(content).toString(\"utf8\")\n\nexport default encode8bit\n","/**\n * @prettier\n */\nconst encodeBase16 = (content) => Buffer.from(content).toString(\"hex\")\n\nexport default encodeBase16\n","/**\n * @prettier\n */\nconst encodeBase32 = (content) => {\n const utf8Value = Buffer.from(content).toString(\"utf8\")\n const base32Alphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\"\n let paddingCount = 0\n let base32Str = \"\"\n let buffer = 0\n let bufferLength = 0\n\n for (let i = 0; i < utf8Value.length; i++) {\n buffer = (buffer << 8) | utf8Value.charCodeAt(i)\n bufferLength += 8\n\n while (bufferLength >= 5) {\n base32Str += base32Alphabet.charAt((buffer >>> (bufferLength - 5)) & 31)\n bufferLength -= 5\n }\n }\n\n if (bufferLength > 0) {\n base32Str += base32Alphabet.charAt((buffer << (5 - bufferLength)) & 31)\n paddingCount = (8 - ((utf8Value.length * 8) % 5)) % 5\n }\n\n for (let i = 0; i < paddingCount; i++) {\n base32Str += \"=\"\n }\n\n return base32Str\n}\n\nexport default encodeBase32\n","/**\n * @prettier\n */\nconst encodeBase64 = (content) => Buffer.from(content).toString(\"base64\")\n\nexport default encodeBase64\n","/**\n * @prettier\n */\nconst encodeBinary = (content) => Buffer.from(content).toString(\"binary\")\n\nexport default encodeBinary\n","/**\n * @prettier\n */\nconst encodeQuotedPrintable = (content) => {\n let quotedPrintable = \"\"\n\n for (let i = 0; i < content.length; i++) {\n const charCode = content.charCodeAt(i)\n\n if (charCode === 61) {\n // ASCII content of \"=\"\n quotedPrintable += \"=3D\"\n } else if (\n (charCode >= 33 && charCode <= 60) ||\n (charCode >= 62 && charCode <= 126) ||\n charCode === 9 ||\n charCode === 32\n ) {\n quotedPrintable += content.charAt(i)\n } else if (charCode === 13 || charCode === 10) {\n quotedPrintable += \"\\r\\n\"\n } else if (charCode > 126) {\n // convert non-ASCII characters to UTF-8 and encode each byte\n const utf8 = unescape(encodeURIComponent(content.charAt(i)))\n for (let j = 0; j < utf8.length; j++) {\n quotedPrintable +=\n \"=\" + (\"0\" + utf8.charCodeAt(j).toString(16)).slice(-2).toUpperCase()\n }\n } else {\n quotedPrintable +=\n \"=\" + (\"0\" + charCode.toString(16)).slice(-2).toUpperCase()\n }\n }\n\n return quotedPrintable\n}\n\nexport default encodeQuotedPrintable\n","/**\n * @prettier\n */\nconst dateTimeGenerator = () => new Date().toISOString()\n\nexport default dateTimeGenerator\n","/**\n * @prettier\n */\nconst dateGenerator = () => new Date().toISOString().substring(0, 10)\n\nexport default dateGenerator\n","/**\n * @prettier\n */\nconst doubleGenerator = () => 0.1\n\nexport default doubleGenerator\n","/**\n * @prettier\n */\nconst durationGenerator = () => \"P3D\" // expresses a duration of 3 days\n\nexport default durationGenerator\n","/**\n * @prettier\n */\nconst emailGenerator = () => \"user@example.com\"\n\nexport default emailGenerator\n","/**\n * @prettier\n */\nconst floatGenerator = () => 0.1\n\nexport default floatGenerator\n","/**\n * @prettier\n */\nconst hostnameGenerator = () => \"example.com\"\n\nexport default hostnameGenerator\n","/**\n * @prettier\n */\nconst idnEmailGenerator = () => \"์‹ค๋ก€@example.com\"\n\nexport default idnEmailGenerator\n","/**\n * @prettier\n */\nconst idnHostnameGenerator = () => \"์‹ค๋ก€.com\"\n\nexport default idnHostnameGenerator\n","/**\n * @prettier\n */\nconst int32Generator = () => (2 ** 30) >>> 0\n\nexport default int32Generator\n","/**\n * @prettier\n */\nconst int64Generator = () => 2 ** 53 - 1\n\nexport default int64Generator\n","/**\n * @prettier\n */\nconst ipv4Generator = () => \"198.51.100.42\"\n\nexport default ipv4Generator\n","/**\n * @prettier\n */\nconst ipv6Generator = () => \"2001:0db8:5b96:0000:0000:426f:8e17:642a\"\n\nexport default ipv6Generator\n","/**\n * @prettier\n */\nconst iriReferenceGenerator = () => \"path/์‹ค๋ก€.html\"\n\nexport default iriReferenceGenerator\n","/**\n * @prettier\n */\nconst iriGenerator = () => \"https://์‹ค๋ก€.com/\"\n\nexport default iriGenerator\n","/**\n * @prettier\n */\nconst jsonPointerGenerator = () => \"/a/b/c\"\n\nexport default jsonPointerGenerator\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_string_raw_bd16f4a0__[\"default\"] });","/**\n * @prettier\n */\nimport { bytes } from \"../../core/random\"\n\n// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types\nconst applicationMediaTypesGenerators = {\n \"application/json\": () => '{\"key\":\"value\"}',\n \"application/ld+json\": () => '{\"name\": \"John Doe\"}',\n \"application/x-httpd-php\": () => \"Hello World!

    '; ?>\",\n \"application/rtf\": () => String.raw`{\\rtf1\\adeflang1025\\ansi\\ansicpg1252\\uc1`,\n \"application/x-sh\": () => 'echo \"Hello World!\"',\n \"application/xhtml+xml\": () => \"

    content

    \",\n \"application/*\": () => bytes(25).toString(\"binary\"),\n}\n\nexport default applicationMediaTypesGenerators\n","/**\n * @prettier\n */\nimport { bytes } from \"../../core/random\"\n\nconst audioMediaTypesGenerators = {\n \"audio/*\": () => bytes(25).toString(\"binary\"),\n}\n\nexport default audioMediaTypesGenerators\n","/**\n * @prettier\n */\nimport { bytes } from \"../../core/random\"\n\nconst imageMediaTypesGenerators = {\n \"image/*\": () => bytes(25).toString(\"binary\"),\n}\n\nexport default imageMediaTypesGenerators\n","/**\n * @prettier\n */\n\n// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types\nconst textMediaTypesGenerators = {\n \"text/plain\": () => \"string\",\n \"text/css\": () => \".selector { border: 1px solid red }\",\n \"text/csv\": () => \"value1,value2,value3\",\n \"text/html\": () => \"

    content

    \",\n \"text/calendar\": () => \"BEGIN:VCALENDAR\",\n \"text/javascript\": () => \"console.dir('Hello world!');\",\n \"text/xml\": () => 'John Doe',\n \"text/*\": () => \"string\",\n}\n\nexport default textMediaTypesGenerators\n","/**\n * @prettier\n */\nimport { bytes } from \"../../core/random\"\n\nconst videoMediaTypesGenerators = {\n \"video/*\": () => bytes(25).toString(\"binary\"),\n}\n\nexport default videoMediaTypesGenerators\n","/**\n * @prettier\n */\nconst passwordGenerator = () => \"********\"\n\nexport default passwordGenerator\n","/**\n * @prettier\n */\nconst regexGenerator = () => \"^[a-z]+$\"\n\nexport default regexGenerator\n","/**\n * @prettier\n */\nconst relativeJsonPointerGenerator = () => \"1/0\"\n\nexport default relativeJsonPointerGenerator\n","/**\n * @prettier\n */\nconst timeGenerator = () => new Date().toISOString().substring(11)\n\nexport default timeGenerator\n","/**\n * @prettier\n */\nconst uriReferenceGenerator = () => \"path/index.html\"\n\nexport default uriReferenceGenerator\n","/**\n * @prettier\n */\nconst uriTemplateGenerator = () =>\n \"https://example.com/dictionary/{term:1}/{term}\"\n\nexport default uriTemplateGenerator\n","/**\n * @prettier\n */\nconst uriGenerator = () => \"https://example.com/\"\n\nexport default uriGenerator\n","/**\n * @prettier\n */\nconst uuidGenerator = () => \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\n\nexport default uuidGenerator\n","/**\n * @prettier\n */\nimport XML from \"xml\"\nimport isEmpty from \"lodash/isEmpty\"\nimport isPlainObject from \"lodash/isPlainObject\"\n\nimport { objectify, normalizeArray } from \"core/utils\"\nimport memoizeN from \"../../../../../helpers/memoizeN\"\nimport typeMap from \"./types/index\"\nimport { getType } from \"./core/type\"\nimport { typeCast } from \"./core/utils\"\nimport { hasExample, extractExample } from \"./core/example\"\nimport { pick as randomPick } from \"./core/random\"\nimport merge from \"./core/merge\"\nimport { isBooleanJSONSchema, isJSONSchemaObject } from \"./core/predicates\"\n\nexport const sampleFromSchemaGeneric = (\n schema,\n config = {},\n exampleOverride = undefined,\n respectXML = false\n) => {\n if (typeof schema?.toJS === \"function\") schema = schema.toJS()\n schema = typeCast(schema)\n\n let usePlainValue = exampleOverride !== undefined || hasExample(schema)\n // first check if there is the need of combining this schema with others required by allOf\n const hasOneOf =\n !usePlainValue && Array.isArray(schema.oneOf) && schema.oneOf.length > 0\n const hasAnyOf =\n !usePlainValue && Array.isArray(schema.anyOf) && schema.anyOf.length > 0\n if (!usePlainValue && (hasOneOf || hasAnyOf)) {\n const schemaToAdd = typeCast(\n hasOneOf ? randomPick(schema.oneOf) : randomPick(schema.anyOf)\n )\n schema = merge(schema, schemaToAdd, config)\n if (!schema.xml && schemaToAdd.xml) {\n schema.xml = schemaToAdd.xml\n }\n if (hasExample(schema) && hasExample(schemaToAdd)) {\n usePlainValue = true\n }\n }\n const _attr = {}\n let { xml, properties, additionalProperties, items, contains } = schema || {}\n let type = getType(schema)\n let { includeReadOnly, includeWriteOnly } = config\n xml = xml || {}\n let { name, prefix, namespace } = xml\n let displayName\n let res = {}\n\n if (!Object.hasOwn(schema, \"type\")) {\n schema.type = type\n }\n\n // set xml naming and attributes\n if (respectXML) {\n name = name || \"notagname\"\n // add prefix to name if exists\n displayName = (prefix ? `${prefix}:` : \"\") + name\n if (namespace) {\n //add prefix to namespace if exists\n let namespacePrefix = prefix ? `xmlns:${prefix}` : \"xmlns\"\n _attr[namespacePrefix] = namespace\n }\n }\n\n // init xml default response sample obj\n if (respectXML) {\n res[displayName] = []\n }\n\n // add to result helper init for xml or json\n const props = objectify(properties)\n let addPropertyToResult\n let propertyAddedCounter = 0\n\n const hasExceededMaxProperties = () =>\n Number.isInteger(schema.maxProperties) &&\n schema.maxProperties > 0 &&\n propertyAddedCounter >= schema.maxProperties\n\n const requiredPropertiesToAdd = () => {\n if (!Array.isArray(schema.required) || schema.required.length === 0) {\n return 0\n }\n let addedCount = 0\n if (respectXML) {\n schema.required.forEach(\n (key) => (addedCount += res[key] === undefined ? 0 : 1)\n )\n } else {\n schema.required.forEach((key) => {\n addedCount +=\n res[displayName]?.find((x) => x[key] !== undefined) === undefined\n ? 0\n : 1\n })\n }\n return schema.required.length - addedCount\n }\n\n const isOptionalProperty = (propName) => {\n if (!Array.isArray(schema.required)) return true\n if (schema.required.length === 0) return true\n\n return !schema.required.includes(propName)\n }\n\n const canAddProperty = (propName) => {\n if (!(Number.isInteger(schema.maxProperties) && schema.maxProperties > 0)) {\n return true\n }\n if (hasExceededMaxProperties()) {\n return false\n }\n if (!isOptionalProperty(propName)) {\n return true\n }\n return (\n schema.maxProperties - propertyAddedCounter - requiredPropertiesToAdd() >\n 0\n )\n }\n\n if (respectXML) {\n addPropertyToResult = (propName, overrideE = undefined) => {\n if (schema && props[propName]) {\n // case it is a xml attribute\n props[propName].xml = props[propName].xml || {}\n\n if (props[propName].xml.attribute) {\n const enumAttrVal = Array.isArray(props[propName].enum)\n ? randomPick(props[propName].enum)\n : undefined\n if (hasExample(props[propName])) {\n _attr[props[propName].xml.name || propName] = extractExample(\n props[propName]\n )\n } else if (enumAttrVal !== undefined) {\n _attr[props[propName].xml.name || propName] = enumAttrVal\n } else {\n const propSchema = typeCast(props[propName])\n const propSchemaType = getType(propSchema)\n const attrName = props[propName].xml.name || propName\n _attr[attrName] = typeMap[propSchemaType](propSchema)\n }\n\n return\n }\n props[propName].xml.name = props[propName].xml.name || propName\n } else if (!props[propName] && additionalProperties !== false) {\n // case only additionalProperty that is not defined in schema\n props[propName] = {\n xml: {\n name: propName,\n },\n }\n }\n\n let t = sampleFromSchemaGeneric(\n props[propName],\n config,\n overrideE,\n respectXML\n )\n if (!canAddProperty(propName)) {\n return\n }\n\n propertyAddedCounter++\n if (Array.isArray(t)) {\n res[displayName] = res[displayName].concat(t)\n } else {\n res[displayName].push(t)\n }\n }\n } else {\n addPropertyToResult = (propName, overrideE) => {\n if (!canAddProperty(propName)) {\n return\n }\n if (\n isPlainObject(schema.discriminator?.mapping) &&\n schema.discriminator.propertyName === propName &&\n typeof schema.$$ref === \"string\"\n ) {\n for (const pair in schema.discriminator.mapping) {\n if (schema.$$ref.search(schema.discriminator.mapping[pair]) !== -1) {\n res[propName] = pair\n break\n }\n }\n } else {\n res[propName] = sampleFromSchemaGeneric(\n props[propName],\n config,\n overrideE,\n respectXML\n )\n }\n propertyAddedCounter++\n }\n }\n\n // check for plain value and if found use it to generate sample from it\n if (usePlainValue) {\n let sample\n if (exampleOverride !== undefined) {\n sample = exampleOverride\n } else {\n sample = extractExample(schema)\n }\n\n // if json just return\n if (!respectXML) {\n // spacial case yaml parser can not know about\n if (typeof sample === \"number\" && type === \"string\") {\n return `${sample}`\n }\n // return if sample does not need any parsing\n if (typeof sample !== \"string\" || type === \"string\") {\n return sample\n }\n // check if sample is parsable or just a plain string\n try {\n return JSON.parse(sample)\n } catch {\n // sample is just plain string return it\n return sample\n }\n }\n\n // generate xml sample recursively for array case\n if (type === \"array\") {\n if (!Array.isArray(sample)) {\n if (typeof sample === \"string\") {\n return sample\n }\n sample = [sample]\n }\n\n let itemSamples = []\n\n if (isJSONSchemaObject(items)) {\n items.xml = items.xml || xml || {}\n items.xml.name = items.xml.name || xml.name\n itemSamples = sample.map((s) =>\n sampleFromSchemaGeneric(items, config, s, respectXML)\n )\n }\n\n if (isJSONSchemaObject(contains)) {\n contains.xml = contains.xml || xml || {}\n contains.xml.name = contains.xml.name || xml.name\n itemSamples = [\n sampleFromSchemaGeneric(contains, config, undefined, respectXML),\n ...itemSamples,\n ]\n }\n\n itemSamples = typeMap.array(schema, { sample: itemSamples })\n if (xml.wrapped) {\n res[displayName] = itemSamples\n if (!isEmpty(_attr)) {\n res[displayName].push({ _attr: _attr })\n }\n } else {\n res = itemSamples\n }\n return res\n }\n\n // generate xml sample recursively for object case\n if (type === \"object\") {\n // case literal example\n if (typeof sample === \"string\") {\n return sample\n }\n for (const propName in sample) {\n if (!Object.hasOwn(sample, propName)) {\n continue\n }\n if (props[propName]?.readOnly && !includeReadOnly) {\n continue\n }\n if (props[propName]?.writeOnly && !includeWriteOnly) {\n continue\n }\n if (props[propName]?.xml?.attribute) {\n _attr[props[propName].xml.name || propName] = sample[propName]\n continue\n }\n addPropertyToResult(propName, sample[propName])\n }\n if (!isEmpty(_attr)) {\n res[displayName].push({ _attr: _attr })\n }\n\n return res\n }\n\n res[displayName] = !isEmpty(_attr) ? [{ _attr: _attr }, sample] : sample\n return res\n }\n\n // use schema to generate sample\n if (type === \"array\") {\n let sampleArray = []\n\n if (isJSONSchemaObject(contains)) {\n if (respectXML) {\n contains.xml = contains.xml || schema.xml || {}\n contains.xml.name = contains.xml.name || xml.name\n }\n\n if (Array.isArray(contains.anyOf)) {\n sampleArray.push(\n ...contains.anyOf.map((anyOfSchema) =>\n sampleFromSchemaGeneric(\n merge(anyOfSchema, contains, config),\n config,\n undefined,\n respectXML\n )\n )\n )\n } else if (Array.isArray(contains.oneOf)) {\n sampleArray.push(\n ...contains.oneOf.map((oneOfSchema) =>\n sampleFromSchemaGeneric(\n merge(oneOfSchema, contains, config),\n config,\n undefined,\n respectXML\n )\n )\n )\n } else if (!respectXML || (respectXML && xml.wrapped)) {\n sampleArray.push(\n sampleFromSchemaGeneric(contains, config, undefined, respectXML)\n )\n } else {\n return sampleFromSchemaGeneric(contains, config, undefined, respectXML)\n }\n }\n\n if (isJSONSchemaObject(items)) {\n if (respectXML) {\n items.xml = items.xml || schema.xml || {}\n items.xml.name = items.xml.name || xml.name\n }\n\n if (Array.isArray(items.anyOf)) {\n sampleArray.push(\n ...items.anyOf.map((i) =>\n sampleFromSchemaGeneric(\n merge(i, items, config),\n config,\n undefined,\n respectXML\n )\n )\n )\n } else if (Array.isArray(items.oneOf)) {\n sampleArray.push(\n ...items.oneOf.map((i) =>\n sampleFromSchemaGeneric(\n merge(i, items, config),\n config,\n undefined,\n respectXML\n )\n )\n )\n } else if (!respectXML || (respectXML && xml.wrapped)) {\n sampleArray.push(\n sampleFromSchemaGeneric(items, config, undefined, respectXML)\n )\n } else {\n return sampleFromSchemaGeneric(items, config, undefined, respectXML)\n }\n }\n\n sampleArray = typeMap.array(schema, { sample: sampleArray })\n if (respectXML && xml.wrapped) {\n res[displayName] = sampleArray\n if (!isEmpty(_attr)) {\n res[displayName].push({ _attr: _attr })\n }\n return res\n }\n\n return sampleArray\n }\n\n if (type === \"object\") {\n for (let propName in props) {\n if (!Object.hasOwn(props, propName)) {\n continue\n }\n if (props[propName]?.deprecated) {\n continue\n }\n if (props[propName]?.readOnly && !includeReadOnly) {\n continue\n }\n if (props[propName]?.writeOnly && !includeWriteOnly) {\n continue\n }\n addPropertyToResult(propName)\n }\n if (respectXML && _attr) {\n res[displayName].push({ _attr: _attr })\n }\n\n if (hasExceededMaxProperties()) {\n return res\n }\n\n if (isBooleanJSONSchema(additionalProperties)) {\n if (respectXML) {\n res[displayName].push({ additionalProp: \"Anything can be here\" })\n } else {\n res.additionalProp1 = {}\n }\n propertyAddedCounter++\n } else if (isJSONSchemaObject(additionalProperties)) {\n const additionalProps = additionalProperties\n const additionalPropSample = sampleFromSchemaGeneric(\n additionalProps,\n config,\n undefined,\n respectXML\n )\n\n if (\n respectXML &&\n typeof additionalProps?.xml?.name === \"string\" &&\n additionalProps?.xml?.name !== \"notagname\"\n ) {\n res[displayName].push(additionalPropSample)\n } else {\n const toGenerateCount =\n Number.isInteger(schema.minProperties) &&\n schema.minProperties > 0 &&\n propertyAddedCounter < schema.minProperties\n ? schema.minProperties - propertyAddedCounter\n : 3\n for (let i = 1; i <= toGenerateCount; i++) {\n if (hasExceededMaxProperties()) {\n return res\n }\n if (respectXML) {\n const temp = {}\n temp[\"additionalProp\" + i] = additionalPropSample[\"notagname\"]\n res[displayName].push(temp)\n } else {\n res[\"additionalProp\" + i] = additionalPropSample\n }\n propertyAddedCounter++\n }\n }\n }\n return res\n }\n\n let value\n if (typeof schema.const !== \"undefined\") {\n // display const value\n value = schema.const\n } else if (schema && Array.isArray(schema.enum)) {\n //display enum first value\n value = randomPick(normalizeArray(schema.enum))\n } else {\n // display schema default\n const contentSample = isJSONSchemaObject(schema.contentSchema)\n ? sampleFromSchemaGeneric(\n schema.contentSchema,\n config,\n undefined,\n respectXML\n )\n : undefined\n value = typeMap[type](schema, { sample: contentSample })\n }\n\n if (respectXML) {\n res[displayName] = !isEmpty(_attr) ? [{ _attr: _attr }, value] : value\n return res\n }\n\n return value\n}\n\nexport const createXMLExample = (schema, config, o) => {\n const json = sampleFromSchemaGeneric(schema, config, o, true)\n if (!json) {\n return\n }\n if (typeof json === \"string\") {\n return json\n }\n return XML(json, { declaration: true, indent: \"\\t\" })\n}\n\nexport const sampleFromSchema = (schema, config, o) => {\n return sampleFromSchemaGeneric(schema, config, o, false)\n}\n\nconst resolver = (arg1, arg2, arg3) => [\n arg1,\n JSON.stringify(arg2),\n JSON.stringify(arg3),\n]\n\nexport const memoizedCreateXMLExample = memoizeN(createXMLExample, resolver)\n\nexport const memoizedSampleFromSchema = memoizeN(sampleFromSchema, resolver)\n","/**\n * @prettier\n */\n\nexport const applyArrayConstraints = (array, constraints = {}) => {\n const { minItems, maxItems, uniqueItems } = constraints\n const { contains, minContains, maxContains } = constraints\n let constrainedArray = [...array]\n\n if (contains != null && typeof contains === \"object\") {\n if (Number.isInteger(minContains) && minContains > 1) {\n const containsItem = constrainedArray.at(0)\n for (let i = 1; i < minContains; i += 1) {\n constrainedArray.unshift(containsItem)\n }\n }\n if (Number.isInteger(maxContains) && maxContains > 0) {\n /**\n * This is noop. `minContains` already generate minimum required\n * number of items that satisfies `contains`. `maxContains` would\n * have no effect.\n */\n }\n }\n\n if (Number.isInteger(maxItems) && maxItems > 0) {\n constrainedArray = array.slice(0, maxItems)\n }\n if (Number.isInteger(minItems) && minItems > 0) {\n for (let i = 0; constrainedArray.length < minItems; i += 1) {\n constrainedArray.push(constrainedArray[i % constrainedArray.length])\n }\n }\n\n if (uniqueItems === true) {\n /**\n * If uniqueItems is true, it implies that every item in the array must be unique.\n * This overrides any minItems constraint that cannot be satisfied with unique items.\n * So if minItems is greater than the number of unique items,\n * it should be reduced to the number of unique items.\n */\n constrainedArray = Array.from(new Set(constrainedArray))\n }\n\n return constrainedArray\n}\n\nconst arrayType = (schema, { sample }) => {\n return applyArrayConstraints(sample, schema)\n}\n\nexport default arrayType\n","/**\n * @prettier\n */\n\nconst booleanType = (schema) => {\n return typeof schema.default === \"boolean\" ? schema.default : true\n}\n\nexport default booleanType\n","/**\n * @prettier\n */\nimport arrayType from \"./array\"\nimport objectType from \"./object\"\nimport stringType from \"./string\"\nimport numberType from \"./number\"\nimport integerType from \"./integer\"\nimport booleanType from \"./boolean\"\nimport nullType from \"./null\"\n\nconst typeMap = {\n array: arrayType,\n object: objectType,\n string: stringType,\n number: numberType,\n integer: integerType,\n boolean: booleanType,\n null: nullType,\n}\n\nexport default new Proxy(typeMap, {\n get(target, prop) {\n if (typeof prop === \"string\" && Object.hasOwn(target, prop)) {\n return target[prop]\n }\n\n return () => `Unknown Type: ${prop}`\n },\n})\n","/**\n * @prettier\n */\nimport { integer as randomInteger } from \"../core/random\"\nimport formatAPI from \"../api/formatAPI\"\nimport int32Generator from \"../generators/int32\"\nimport int64Generator from \"../generators/int64\"\n\nconst generateFormat = (schema) => {\n const { format } = schema\n\n const formatGenerator = formatAPI(format)\n if (typeof formatGenerator === \"function\") {\n return formatGenerator(schema)\n }\n\n switch (format) {\n case \"int32\": {\n return int32Generator()\n }\n case \"int64\": {\n return int64Generator()\n }\n }\n\n return randomInteger()\n}\nconst integerType = (schema) => {\n const { format } = schema\n\n if (typeof format === \"string\") {\n return generateFormat(schema)\n }\n\n return randomInteger()\n}\n\nexport default integerType\n","/**\n * @prettier\n */\n\nconst nullType = () => {\n return null\n}\n\nexport default nullType\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_number_epsilon_c9a81fe6__[\"default\"] });","/**\n * @prettier\n */\nimport { number as randomNumber } from \"../core/random\"\nimport formatAPI from \"../api/formatAPI\"\nimport floatGenerator from \"../generators/float\"\nimport doubleGenerator from \"../generators/double\"\n\nconst generateFormat = (schema) => {\n const { format } = schema\n\n const formatGenerator = formatAPI(format)\n if (typeof formatGenerator === \"function\") {\n return formatGenerator(schema)\n }\n\n switch (format) {\n case \"float\": {\n return floatGenerator()\n }\n case \"double\": {\n return doubleGenerator()\n }\n }\n\n return randomNumber()\n}\n\nconst applyNumberConstraints = (number, constraints = {}) => {\n const { minimum, maximum, exclusiveMinimum, exclusiveMaximum } = constraints\n const { multipleOf } = constraints\n const epsilon = Number.isInteger(number) ? 1 : Number.EPSILON\n let minValue = typeof minimum === \"number\" ? minimum : null\n let maxValue = typeof maximum === \"number\" ? maximum : null\n let constrainedNumber = number\n\n if (typeof exclusiveMinimum === \"number\") {\n minValue =\n minValue !== null\n ? Math.max(minValue, exclusiveMinimum + epsilon)\n : exclusiveMinimum + epsilon\n }\n if (typeof exclusiveMaximum === \"number\") {\n maxValue =\n maxValue !== null\n ? Math.min(maxValue, exclusiveMaximum - epsilon)\n : exclusiveMaximum - epsilon\n }\n constrainedNumber =\n (minValue > maxValue && number) || minValue || maxValue || constrainedNumber\n\n if (typeof multipleOf === \"number\" && multipleOf > 0) {\n const remainder = constrainedNumber % multipleOf\n constrainedNumber =\n remainder === 0\n ? constrainedNumber\n : constrainedNumber + multipleOf - remainder\n }\n\n return constrainedNumber\n}\n\nconst numberType = (schema) => {\n const { format } = schema\n let generatedNumber\n\n if (typeof format === \"string\") {\n generatedNumber = generateFormat(schema)\n } else {\n generatedNumber = randomNumber()\n }\n\n return applyNumberConstraints(generatedNumber, schema)\n}\n\nexport default numberType\n","/**\n * @prettier\n */\n\nconst objectType = () => {\n throw new Error(\"Not implemented\")\n}\n\nexport default objectType\n","/**\n * @prettier\n */\nimport identity from \"lodash/identity\"\n\nimport { string as randomString, randexp } from \"../core/random\"\nimport { isJSONSchema } from \"../core/predicates\"\nimport emailGenerator from \"../generators/email\"\nimport idnEmailGenerator from \"../generators/idn-email\"\nimport hostnameGenerator from \"../generators/hostname\"\nimport idnHostnameGenerator from \"../generators/idn-hostname\"\nimport ipv4Generator from \"../generators/ipv4\"\nimport ipv6Generator from \"../generators/ipv6\"\nimport uriGenerator from \"../generators/uri\"\nimport uriReferenceGenerator from \"../generators/uri-reference\"\nimport iriGenerator from \"../generators/iri\"\nimport iriReferenceGenerator from \"../generators/iri-reference\"\nimport uuidGenerator from \"../generators/uuid\"\nimport uriTemplateGenerator from \"../generators/uri-template\"\nimport jsonPointerGenerator from \"../generators/json-pointer\"\nimport relativeJsonPointerGenerator from \"../generators/relative-json-pointer\"\nimport dateTimeGenerator from \"../generators/date-time\"\nimport dateGenerator from \"../generators/date\"\nimport timeGenerator from \"../generators/time\"\nimport durationGenerator from \"../generators/duration\"\nimport passwordGenerator from \"../generators/password\"\nimport regexGenerator from \"../generators/regex\"\nimport formatAPI from \"../api/formatAPI\"\nimport encoderAPI from \"../api/encoderAPI\"\nimport mediaTypeAPI from \"../api/mediaTypeAPI\"\n\nconst generateFormat = (schema) => {\n const { format } = schema\n\n const formatGenerator = formatAPI(format)\n if (typeof formatGenerator === \"function\") {\n return formatGenerator(schema)\n }\n\n switch (format) {\n case \"email\": {\n return emailGenerator()\n }\n case \"idn-email\": {\n return idnEmailGenerator()\n }\n case \"hostname\": {\n return hostnameGenerator()\n }\n case \"idn-hostname\": {\n return idnHostnameGenerator()\n }\n case \"ipv4\": {\n return ipv4Generator()\n }\n case \"ipv6\": {\n return ipv6Generator()\n }\n case \"uri\": {\n return uriGenerator()\n }\n case \"uri-reference\": {\n return uriReferenceGenerator()\n }\n case \"iri\": {\n return iriGenerator()\n }\n case \"iri-reference\": {\n return iriReferenceGenerator()\n }\n case \"uuid\": {\n return uuidGenerator()\n }\n case \"uri-template\": {\n return uriTemplateGenerator()\n }\n case \"json-pointer\": {\n return jsonPointerGenerator()\n }\n case \"relative-json-pointer\": {\n return relativeJsonPointerGenerator()\n }\n case \"date-time\": {\n return dateTimeGenerator()\n }\n case \"date\": {\n return dateGenerator()\n }\n case \"time\": {\n return timeGenerator()\n }\n case \"duration\": {\n return durationGenerator()\n }\n case \"password\": {\n return passwordGenerator()\n }\n case \"regex\": {\n return regexGenerator()\n }\n }\n\n return randomString()\n}\n\nconst applyStringConstraints = (string, constraints = {}) => {\n const { maxLength, minLength } = constraints\n let constrainedString = string\n\n if (Number.isInteger(maxLength) && maxLength > 0) {\n constrainedString = constrainedString.slice(0, maxLength)\n }\n if (Number.isInteger(minLength) && minLength > 0) {\n let i = 0\n while (constrainedString.length < minLength) {\n constrainedString += constrainedString[i++ % constrainedString.length]\n }\n }\n\n return constrainedString\n}\nconst stringType = (schema, { sample } = {}) => {\n const { contentEncoding, contentMediaType, contentSchema } = schema\n const { pattern, format } = schema\n const encode = encoderAPI(contentEncoding) || identity\n let generatedString\n\n if (typeof pattern === \"string\") {\n generatedString = randexp(pattern)\n } else if (typeof format === \"string\") {\n generatedString = generateFormat(schema)\n } else if (\n isJSONSchema(contentSchema) &&\n typeof contentMediaType === \"string\" &&\n typeof sample !== \"undefined\"\n ) {\n if (Array.isArray(sample) || typeof sample === \"object\") {\n generatedString = JSON.stringify(sample)\n } else {\n generatedString = String(sample)\n }\n } else if (typeof contentMediaType === \"string\") {\n const mediaTypeGenerator = mediaTypeAPI(contentMediaType)\n if (typeof mediaTypeGenerator === \"function\") {\n generatedString = mediaTypeGenerator(schema)\n }\n } else {\n generatedString = randomString()\n }\n\n return encode(applyStringConstraints(generatedString, schema))\n}\n\nexport default stringType\n","import { normalizeArray } from \"core/utils\"\n\nexport const UPDATE_LAYOUT = \"layout_update_layout\"\nexport const UPDATE_FILTER = \"layout_update_filter\"\nexport const UPDATE_MODE = \"layout_update_mode\"\nexport const SHOW = \"layout_show\"\n\n// export const ONLY_SHOW = \"layout_only_show\"\n\nexport function updateLayout(layout) {\n return {\n type: UPDATE_LAYOUT,\n payload: layout\n }\n}\n\nexport function updateFilter(filter) {\n return {\n type: UPDATE_FILTER,\n payload: filter\n }\n}\n\nexport function show(thing, shown=true) {\n thing = normalizeArray(thing)\n return {\n type: SHOW,\n payload: {thing, shown}\n }\n}\n\n// Simple string key-store, used for\nexport function changeMode(thing, mode=\"\") {\n thing = normalizeArray(thing)\n return {\n type: UPDATE_MODE,\n payload: {thing, mode}\n }\n}\n","import reducers from \"./reducers\"\nimport * as actions from \"./actions\"\nimport * as selectors from \"./selectors\"\nimport * as wrapSelectors from \"./spec-extensions/wrap-selector\"\n\nexport default function() {\n return {\n statePlugins: {\n layout: {\n reducers,\n actions,\n selectors\n },\n spec: {\n wrapSelectors\n }\n }\n }\n}\n","import { fromJS } from \"immutable\"\nimport {\n UPDATE_LAYOUT,\n UPDATE_FILTER,\n UPDATE_MODE,\n SHOW\n} from \"./actions\"\n\nexport default {\n\n [UPDATE_LAYOUT]: (state, action) => state.set(\"layout\", action.payload),\n\n [UPDATE_FILTER]: (state, action) => state.set(\"filter\", action.payload),\n\n [SHOW]: (state, action) => {\n const isShown = action.payload.shown\n // This is one way to serialize an array, another (preferred) is to convert to json-pointer\n // TODO: use json-pointer serilization instead of fromJS(...), for performance\n const thingToShow = fromJS(action.payload.thing)\n // This is a map of paths to bools\n // eg: [one, two] => true\n // eg: [one] => false\n return state.update(\"shown\", fromJS({}), a => a.set(thingToShow, isShown))\n },\n\n [UPDATE_MODE]: (state, action) => {\n let thing = action.payload.thing\n let mode = action.payload.mode\n return state.setIn([\"modes\"].concat(thing), (mode || \"\") + \"\")\n }\n\n}\n","import { createSelector } from \"reselect\"\nimport { normalizeArray } from \"core/utils\"\nimport { fromJS } from \"immutable\"\n\nconst state = state => state\n\nexport const current = state => state.get(\"layout\")\n\nexport const currentFilter = state => state.get(\"filter\")\n\nexport const isShown = (state, thing, def) => {\n thing = normalizeArray(thing)\n return state.get(\"shown\", fromJS({})).get(fromJS(thing), def)\n}\n\nexport const whatMode = (state, thing, def=\"\") => {\n thing = normalizeArray(thing)\n return state.getIn([\"modes\", ...thing], def)\n}\n\nexport const showSummary = createSelector(\n state,\n state => !isShown(state, \"editor\")\n)\n","\nexport const taggedOperations = (oriSelector, system) => (state, ...args) => {\n let taggedOps = oriSelector(state, ...args)\n\n const { fn, layoutSelectors, getConfigs } = system.getSystem()\n const configs = getConfigs()\n const { maxDisplayedTags } = configs\n\n // Filter, if requested\n let filter = layoutSelectors.currentFilter()\n if (filter) {\n if (filter !== true && filter !== \"true\" && filter !== \"false\") {\n taggedOps = fn.opsFilter(taggedOps, filter)\n }\n }\n // Limit to [max] items, if specified\n if (maxDisplayedTags && !isNaN(maxDisplayedTags) && maxDisplayedTags >= 0) {\n taggedOps = taggedOps.slice(0, maxDisplayedTags)\n }\n\n return taggedOps\n}\n","export default function ({configs}) {\n\n const levels = {\n \"debug\": 0,\n \"info\": 1,\n \"log\": 2,\n \"warn\": 3,\n \"error\": 4\n }\n\n const getLevel = (level) => levels[level] || -1\n\n let { logLevel } = configs\n let logLevelInt = getLevel(logLevel)\n\n function log(level, ...args) {\n if(getLevel(level) >= logLevelInt)\n // eslint-disable-next-line no-console\n console[level](...args)\n }\n\n log.warn = log.bind(null, \"warn\")\n log.error = log.bind(null, \"error\")\n log.info = log.bind(null, \"info\")\n log.debug = log.bind(null, \"debug\")\n\n return { rootInjects: { log } }\n}\n","// Actions conform to FSA (flux-standard-actions)\n// {type: string,payload: Any|Error, meta: obj, error: bool}\n\nexport const UPDATE_SELECTED_SERVER = \"oas3_set_servers\"\nexport const UPDATE_REQUEST_BODY_VALUE = \"oas3_set_request_body_value\"\nexport const UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG = \"oas3_set_request_body_retain_flag\"\nexport const UPDATE_REQUEST_BODY_INCLUSION = \"oas3_set_request_body_inclusion\"\nexport const UPDATE_ACTIVE_EXAMPLES_MEMBER = \"oas3_set_active_examples_member\"\nexport const UPDATE_REQUEST_CONTENT_TYPE = \"oas3_set_request_content_type\"\nexport const UPDATE_RESPONSE_CONTENT_TYPE = \"oas3_set_response_content_type\"\nexport const UPDATE_SERVER_VARIABLE_VALUE = \"oas3_set_server_variable_value\"\nexport const SET_REQUEST_BODY_VALIDATE_ERROR = \"oas3_set_request_body_validate_error\"\nexport const CLEAR_REQUEST_BODY_VALIDATE_ERROR = \"oas3_clear_request_body_validate_error\"\nexport const CLEAR_REQUEST_BODY_VALUE = \"oas3_clear_request_body_value\"\n\nexport function setSelectedServer (selectedServerUrl, namespace) {\n return {\n type: UPDATE_SELECTED_SERVER,\n payload: {selectedServerUrl, namespace}\n }\n}\n\nexport function setRequestBodyValue ({ value, pathMethod }) {\n return {\n type: UPDATE_REQUEST_BODY_VALUE,\n payload: { value, pathMethod }\n }\n}\n\nexport const setRetainRequestBodyValueFlag = ({ value, pathMethod }) => {\n return {\n type: UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG,\n payload: { value, pathMethod }\n }\n}\n\n\nexport function setRequestBodyInclusion ({ value, pathMethod, name }) {\n return {\n type: UPDATE_REQUEST_BODY_INCLUSION,\n payload: { value, pathMethod, name }\n }\n}\n\nexport function setActiveExamplesMember ({ name, pathMethod, contextType, contextName }) {\n return {\n type: UPDATE_ACTIVE_EXAMPLES_MEMBER,\n payload: { name, pathMethod, contextType, contextName }\n }\n}\n\nexport function setRequestContentType ({ value, pathMethod }) {\n return {\n type: UPDATE_REQUEST_CONTENT_TYPE,\n payload: { value, pathMethod }\n }\n}\n\nexport function setResponseContentType ({ value, path, method }) {\n return {\n type: UPDATE_RESPONSE_CONTENT_TYPE,\n payload: { value, path, method }\n }\n}\n\nexport function setServerVariableValue ({ server, namespace, key, val }) {\n return {\n type: UPDATE_SERVER_VARIABLE_VALUE,\n payload: { server, namespace, key, val }\n }\n}\n\nexport const setRequestBodyValidateError = ({ path, method, validationErrors }) => {\n return {\n type: SET_REQUEST_BODY_VALIDATE_ERROR,\n payload: { path, method, validationErrors }\n }\n}\n\nexport const clearRequestBodyValidateError = ({ path, method }) => {\n return {\n type: CLEAR_REQUEST_BODY_VALIDATE_ERROR,\n payload: { path, method }\n }\n}\n\nexport const initRequestBodyValidateError = ({ pathMethod } ) => {\n return {\n type: CLEAR_REQUEST_BODY_VALIDATE_ERROR,\n payload: { path: pathMethod[0], method: pathMethod[1] }\n }\n}\n\nexport const clearRequestBodyValue = ({ pathMethod }) => {\n return {\n type: CLEAR_REQUEST_BODY_VALUE,\n payload: { pathMethod }\n }\n}\n","import { createSelector } from \"reselect\"\nimport { List, Map, fromJS } from \"immutable\"\n\n\n// Helpers\n\nconst state = state => state\n\nfunction onlyOAS3(selector) {\n return (ori, system) => (...args) => {\n if(system.getSystem().specSelectors.isOAS3()) {\n // Pass the spec plugin state to Reselect to trigger on securityDefinitions update\n let resolvedSchemes = system.getState().getIn([\"spec\", \"resolvedSubtrees\",\n \"components\", \"securitySchemes\"])\n return selector(system, resolvedSchemes, ...args)\n } else {\n return ori(...args)\n }\n }\n}\n\nexport const definitionsToAuthorize = onlyOAS3(createSelector(\n state,\n ({specSelectors}) => specSelectors.securityDefinitions(),\n (system, definitions) => {\n // Coerce our OpenAPI 3.0 definitions into monoflow definitions\n // that look like Swagger2 definitions.\n let list = List()\n\n if(!definitions) {\n return list\n }\n\n definitions.entrySeq().forEach( ([ defName, definition ]) => {\n const type = definition.get(\"type\")\n\n if(type === \"oauth2\") {\n definition.get(\"flows\").entrySeq().forEach(([flowKey, flowVal]) => {\n let translatedDef = fromJS({\n flow: flowKey,\n authorizationUrl: flowVal.get(\"authorizationUrl\"),\n tokenUrl: flowVal.get(\"tokenUrl\"),\n scopes: flowVal.get(\"scopes\"),\n type: definition.get(\"type\"),\n description: definition.get(\"description\")\n })\n\n list = list.push(new Map({\n [defName]: translatedDef.filter((v) => {\n // filter out unset values, sometimes `authorizationUrl`\n // and `tokenUrl` come out as `undefined` in the data\n return v !== undefined\n })\n }))\n })\n }\n if(type === \"http\" || type === \"apiKey\") {\n list = list.push(new Map({\n [defName]: definition\n }))\n }\n if(type === \"openIdConnect\" && definition.get(\"openIdConnectData\")) {\n let oidcData = definition.get(\"openIdConnectData\")\n let grants = oidcData.get(\"grant_types_supported\") || [\"authorization_code\", \"implicit\"]\n grants.forEach((grant) => {\n // Convert from OIDC list of scopes to the OAS-style map with empty descriptions\n let translatedScopes = oidcData.get(\"scopes_supported\") &&\n oidcData.get(\"scopes_supported\").reduce((acc, cur) => acc.set(cur, \"\"), new Map())\n\n let translatedDef = fromJS({\n flow: grant,\n authorizationUrl: oidcData.get(\"authorization_endpoint\"),\n tokenUrl: oidcData.get(\"token_endpoint\"),\n scopes: translatedScopes,\n type: \"oauth2\",\n openIdConnectUrl: definition.get(\"openIdConnectUrl\")\n })\n\n list = list.push(new Map({\n [defName]: translatedDef.filter((v) => {\n // filter out unset values, sometimes `authorizationUrl`\n // and `tokenUrl` come out as `undefined` in the data\n return v !== undefined\n })\n }))\n })\n }\n })\n\n return list\n }\n))\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nconst Callbacks = ({ callbacks, specPath, specSelectors, getComponent }) => {\n const operationDTOs = specSelectors.callbacksOperations({\n callbacks,\n specPath,\n })\n const callbackNames = Object.keys(operationDTOs)\n\n const OperationContainer = getComponent(\"OperationContainer\", true)\n\n if (callbackNames.length === 0) return No callbacks\n\n return (\n
    \n {callbackNames.map((callbackName) => (\n
    \n

    {callbackName}

    \n\n {operationDTOs[callbackName].map((operationDTO) => (\n \n ))}\n
    \n ))}\n
    \n )\n}\n\nCallbacks.propTypes = {\n getComponent: PropTypes.func.isRequired,\n specSelectors: PropTypes.shape({\n callbacksOperations: PropTypes.func.isRequired,\n }).isRequired,\n callbacks: ImPropTypes.iterable.isRequired,\n specPath: ImPropTypes.list.isRequired,\n}\n\nexport default Callbacks\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class HttpAuth extends React.Component {\n static propTypes = {\n authorized: PropTypes.object,\n getComponent: PropTypes.func.isRequired,\n errSelectors: PropTypes.object.isRequired,\n schema: PropTypes.object.isRequired,\n name: PropTypes.string.isRequired,\n onChange: PropTypes.func\n }\n\n constructor(props, context) {\n super(props, context)\n let { name, schema } = this.props\n let value = this.getValue()\n\n this.state = {\n name: name,\n schema: schema,\n value: value\n }\n }\n\n getValue () {\n let { name, authorized } = this.props\n\n return authorized && authorized.getIn([name, \"value\"])\n }\n\n onChange =(e) => {\n let { onChange } = this.props\n let { value, name } = e.target\n\n let newValue = Object.assign({}, this.state.value)\n\n if(name) {\n newValue[name] = value\n } else {\n newValue = value\n }\n\n this.setState({ value: newValue }, () => onChange(this.state))\n\n }\n\n render() {\n let { schema, getComponent, errSelectors, name } = this.props\n const Input = getComponent(\"Input\")\n const Row = getComponent(\"Row\")\n const Col = getComponent(\"Col\")\n const AuthError = getComponent(\"authError\")\n const Markdown = getComponent(\"Markdown\", true)\n const JumpToPath = getComponent(\"JumpToPath\", true)\n\n const scheme = (schema.get(\"scheme\") || \"\").toLowerCase()\n let value = this.getValue()\n let errors = errSelectors.allErrors().filter( err => err.get(\"authId\") === name)\n\n if(scheme === \"basic\") {\n let username = value ? value.get(\"username\") : null\n return
    \n

    \n { name || schema.get(\"name\") } \n (http, Basic)\n \n

    \n { username &&
    Authorized
    }\n \n \n \n \n \n {\n username ? { username } \n : \n }\n \n \n \n {\n username ? ****** \n : \n }\n \n {\n errors.valueSeq().map( (error, key) => {\n return \n } )\n }\n
    \n }\n\n if(scheme === \"bearer\") {\n return (\n
    \n

    \n { name || schema.get(\"name\") } \n (http, Bearer)\n \n

    \n { value &&
    Authorized
    }\n \n \n \n \n \n {\n value ? ****** \n : \n }\n \n {\n errors.valueSeq().map( (error, key) => {\n return \n } )\n }\n
    \n )\n }\n return
    \n {name} HTTP authentication: unsupported scheme {`'${scheme}'`}\n
    \n }\n}\n","import Callbacks from \"./callbacks\"\nimport RequestBody from \"./request-body\"\nimport OperationLink from \"./operation-link\"\nimport Servers from \"./servers\"\nimport ServersContainer from \"./servers-container\"\nimport RequestBodyEditor from \"./request-body-editor\"\nimport HttpAuth from \"./http-auth\"\nimport OperationServers from \"./operation-servers\"\n\nexport default {\n Callbacks,\n HttpAuth,\n RequestBody,\n Servers,\n ServersContainer,\n RequestBodyEditor,\n OperationServers,\n operationLink: OperationLink,\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nclass OperationLink extends Component {\n render() {\n const { link, name, getComponent } = this.props\n\n const Markdown = getComponent(\"Markdown\", true)\n\n let targetOp = link.get(\"operationId\") || link.get(\"operationRef\")\n let parameters = link.get(\"parameters\") && link.get(\"parameters\").toJS()\n let description = link.get(\"description\")\n\n return
    \n
    \n {name}\n { description ? : null }\n
    \n
    \n        Operation `{targetOp}`

    \n Parameters {padString(0, JSON.stringify(parameters, null, 2)) || \"{}\"}
    \n
    \n
    \n }\n\n}\n\nfunction padString(n, string) {\n if(typeof string !== \"string\") { return \"\" }\n return string\n .split(\"\\n\")\n .map((line, i) => i > 0 ? Array(n + 1).join(\" \") + line : line)\n .join(\"\\n\")\n}\n\nOperationLink.propTypes = {\n getComponent: PropTypes.func.isRequired,\n link: ImPropTypes.orderedMap.isRequired,\n name: PropTypes.String\n}\n\nexport default OperationLink\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class OperationServers extends React.Component {\n static propTypes = {\n // for self\n path: PropTypes.string.isRequired,\n method: PropTypes.string.isRequired,\n operationServers: ImPropTypes.list,\n pathServers: ImPropTypes.list,\n setSelectedServer: PropTypes.func.isRequired,\n setServerVariableValue: PropTypes.func.isRequired,\n getSelectedServer: PropTypes.func.isRequired,\n getServerVariable: PropTypes.func.isRequired,\n getEffectiveServerValue: PropTypes.func.isRequired,\n\n // utils\n getComponent: PropTypes.func.isRequired\n }\n\n setSelectedServer = (server) => {\n const { path, method } = this.props\n // FIXME: we should be keeping up with this in props/state upstream of us\n // instead of cheatingโ„ข with `forceUpdate`\n this.forceUpdate()\n return this.props.setSelectedServer(server, `${path}:${method}`)\n }\n\n setServerVariableValue = (obj) => {\n const { path, method } = this.props\n // FIXME: we should be keeping up with this in props/state upstream of us\n // instead of cheatingโ„ข with `forceUpdate`\n this.forceUpdate()\n return this.props.setServerVariableValue({\n ...obj,\n namespace: `${path}:${method}`\n })\n }\n\n getSelectedServer = () => {\n const { path, method } = this.props\n return this.props.getSelectedServer(`${path}:${method}`)\n }\n\n getServerVariable = (server, key) => {\n const { path, method } = this.props\n return this.props.getServerVariable({\n namespace: `${path}:${method}`,\n server\n }, key)\n }\n\n getEffectiveServerValue = (server) => {\n const { path, method } = this.props\n return this.props.getEffectiveServerValue({\n server,\n namespace: `${path}:${method}`\n })\n }\n\n render() {\n const {\n // for self\n operationServers,\n pathServers,\n\n // util\n getComponent\n } = this.props\n\n if(!operationServers && !pathServers) {\n return null\n }\n\n const Servers = getComponent(\"Servers\")\n\n const serversToDisplay = operationServers || pathServers\n const displaying = operationServers ? \"operation\" : \"path\"\n\n return
    \n
    \n
    \n

    Servers

    \n
    \n
    \n
    \n

    \n These {displaying}-level options override the global server options.\n

    \n \n
    \n
    \n }\n}\n","import React, { PureComponent } from \"react\"\nimport PropTypes from \"prop-types\"\nimport cx from \"classnames\"\nimport { stringify } from \"core/utils\"\n\nconst NOOP = Function.prototype\n\nexport default class RequestBodyEditor extends PureComponent {\n\n static propTypes = {\n onChange: PropTypes.func,\n getComponent: PropTypes.func.isRequired,\n value: PropTypes.string,\n defaultValue: PropTypes.string,\n errors: PropTypes.array,\n }\n\n static defaultProps = {\n onChange: NOOP,\n userHasEditedBody: false,\n }\n\n constructor(props, context) {\n super(props, context)\n\n this.state = {\n value: stringify(props.value) || props.defaultValue\n }\n\n // this is the glue that makes sure our initial value gets set as the\n // current request body value\n // TODO: achieve this in a selector instead\n props.onChange(props.value)\n }\n\n applyDefaultValue = (nextProps) => {\n const { onChange, defaultValue } = (nextProps ? nextProps : this.props)\n\n this.setState({\n value: defaultValue\n })\n\n return onChange(defaultValue)\n }\n\n onChange = (value) => {\n this.props.onChange(stringify(value))\n }\n\n onDomChange = e => {\n const inputValue = e.target.value\n\n this.setState({\n value: inputValue,\n }, () => this.onChange(inputValue))\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n if(\n this.props.value !== nextProps.value &&\n nextProps.value !== this.state.value\n ) {\n\n this.setState({\n value: stringify(nextProps.value)\n })\n }\n\n\n\n if(!nextProps.value && nextProps.defaultValue && !!this.state.value) {\n // if new value is falsy, we have a default, AND the falsy value didn't\n // come from us originally\n this.applyDefaultValue(nextProps)\n }\n }\n\n render() {\n let {\n getComponent,\n errors,\n } = this.props\n\n let {\n value\n } = this.state\n\n let isInvalid = errors.size > 0 ? true : false\n const TextArea = getComponent(\"TextArea\")\n\n return (\n
    \n \n
    \n )\n\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { Map, OrderedMap, List } from \"immutable\"\nimport { getCommonExtensions, stringify, isEmptyValue } from \"core/utils\"\nimport { getKnownSyntaxHighlighterLanguage } from \"core/utils/jsonParse\"\n\nexport const getDefaultRequestBodyValue = (requestBody, mediaType, activeExamplesKey, fn) => {\n const mediaTypeValue = requestBody.getIn([\"content\", mediaType])\n const schema = mediaTypeValue.get(\"schema\").toJS()\n\n const hasExamplesKey = mediaTypeValue.get(\"examples\") !== undefined\n const exampleSchema = mediaTypeValue.get(\"example\")\n const mediaTypeExample = hasExamplesKey\n ? mediaTypeValue.getIn([\n \"examples\",\n activeExamplesKey,\n \"value\"\n ])\n : exampleSchema\n\n const exampleValue = fn.getSampleSchema(\n schema,\n mediaType,\n {\n includeWriteOnly: true\n },\n mediaTypeExample\n )\n return stringify(exampleValue)\n}\n\n\n\nconst RequestBody = ({\n userHasEditedBody,\n requestBody,\n requestBodyValue,\n requestBodyInclusionSetting,\n requestBodyErrors,\n getComponent,\n getConfigs,\n specSelectors,\n fn,\n contentType,\n isExecute,\n specPath,\n onChange,\n onChangeIncludeEmpty,\n activeExamplesKey,\n updateActiveExamplesKey,\n setRetainRequestBodyValueFlag\n}) => {\n const handleFile = (e) => {\n onChange(e.target.files[0])\n }\n const setIsIncludedOptions = (key) => {\n let options = {\n key,\n shouldDispatchInit: false,\n defaultValue: true\n }\n let currentInclusion = requestBodyInclusionSetting.get(key, \"no value\")\n if (currentInclusion === \"no value\") {\n options.shouldDispatchInit = true\n // future: can get/set defaultValue from a config setting\n }\n return options\n }\n\n const Markdown = getComponent(\"Markdown\", true)\n const ModelExample = getComponent(\"modelExample\")\n const RequestBodyEditor = getComponent(\"RequestBodyEditor\")\n const HighlightCode = getComponent(\"highlightCode\")\n const ExamplesSelectValueRetainer = getComponent(\"ExamplesSelectValueRetainer\")\n const Example = getComponent(\"Example\")\n const ParameterIncludeEmpty = getComponent(\"ParameterIncludeEmpty\")\n\n const { showCommonExtensions } = getConfigs()\n\n const requestBodyDescription = (requestBody && requestBody.get(\"description\")) || null\n const requestBodyContent = (requestBody && requestBody.get(\"content\")) || new OrderedMap()\n contentType = contentType || requestBodyContent.keySeq().first() || \"\"\n\n const mediaTypeValue = requestBodyContent.get(contentType, OrderedMap())\n const schemaForMediaType = mediaTypeValue.get(\"schema\", OrderedMap())\n const rawExamplesOfMediaType = mediaTypeValue.get(\"examples\", null)\n const sampleForMediaType = rawExamplesOfMediaType?.map((container, key) => {\n const val = container?.get(\"value\", null)\n if(val) {\n container = container.set(\"value\", getDefaultRequestBodyValue(\n requestBody,\n contentType,\n key,\n fn,\n ), val)\n }\n return container\n })\n\n const handleExamplesSelect = (key /*, { isSyntheticChange } */) => {\n updateActiveExamplesKey(key)\n }\n requestBodyErrors = List.isList(requestBodyErrors) ? requestBodyErrors : List()\n\n if(!mediaTypeValue.size) {\n return null\n }\n\n const isObjectContent = mediaTypeValue.getIn([\"schema\", \"type\"]) === \"object\"\n const isBinaryFormat = mediaTypeValue.getIn([\"schema\", \"format\"]) === \"binary\"\n const isBase64Format = mediaTypeValue.getIn([\"schema\", \"format\"]) === \"base64\"\n\n if(\n contentType === \"application/octet-stream\"\n || contentType.indexOf(\"image/\") === 0\n || contentType.indexOf(\"audio/\") === 0\n || contentType.indexOf(\"video/\") === 0\n || isBinaryFormat\n || isBase64Format\n ) {\n const Input = getComponent(\"Input\")\n\n if(!isExecute) {\n return \n Example values are not available for {contentType} media types.\n \n }\n\n return \n }\n\n if (\n isObjectContent &&\n (\n contentType === \"application/x-www-form-urlencoded\" ||\n contentType.indexOf(\"multipart/\") === 0\n ) &&\n schemaForMediaType.get(\"properties\", OrderedMap()).size > 0\n ) {\n const JsonSchemaForm = getComponent(\"JsonSchemaForm\")\n const ParameterExt = getComponent(\"ParameterExt\")\n const bodyProperties = schemaForMediaType.get(\"properties\", OrderedMap())\n requestBodyValue = Map.isMap(requestBodyValue) ? requestBodyValue : OrderedMap()\n\n return
    \n { requestBodyDescription &&\n \n }\n \n \n {\n Map.isMap(bodyProperties) && bodyProperties.entrySeq().map(([key, prop]) => {\n if (prop.get(\"readOnly\")) return\n\n let commonExt = showCommonExtensions ? getCommonExtensions(prop) : null\n const required = schemaForMediaType.get(\"required\", List()).includes(key)\n const type = prop.get(\"type\")\n const format = prop.get(\"format\")\n const description = prop.get(\"description\")\n const currentValue = requestBodyValue.getIn([key, \"value\"])\n const currentErrors = requestBodyValue.getIn([key, \"errors\"]) || requestBodyErrors\n const included = requestBodyInclusionSetting.get(key) || false\n\n const useInitialValFromSchemaSamples = prop.has(\"default\")\n || prop.has(\"example\")\n || prop.hasIn([\"items\", \"example\"])\n || prop.hasIn([\"items\", \"default\"])\n const useInitialValFromEnum = prop.has(\"enum\") && (prop.get(\"enum\").size === 1 || required)\n const useInitialValue = useInitialValFromSchemaSamples || useInitialValFromEnum\n\n let initialValue = \"\"\n if (type === \"array\" && !useInitialValue) {\n initialValue = []\n }\n if (type === \"object\" || useInitialValue) {\n // TODO: what about example or examples from requestBody could be passed as exampleOverride\n initialValue = fn.getSampleSchema(prop, false, {\n includeWriteOnly: true\n })\n }\n\n if (typeof initialValue !== \"string\" && type === \"object\") {\n initialValue = stringify(initialValue)\n }\n if (typeof initialValue === \"string\" && type === \"array\") {\n initialValue = JSON.parse(initialValue)\n }\n\n const isFile = type === \"string\" && (format === \"binary\" || format === \"base64\")\n\n return \n \n \n \n })\n }\n \n
    \n
    \n { key }\n { !required ? null :  * }\n
    \n
    \n { type }\n { format && (${format})}\n {!showCommonExtensions || !commonExt.size ? null : commonExt.entrySeq().map(([key, v]) => )}\n
    \n
    \n { prop.get(\"deprecated\") ? \"deprecated\": null }\n
    \n
    \n \n {isExecute ?
    \n {\n onChange(value, [key])\n }}\n />\n {required ? null : (\n onChangeIncludeEmpty(key, value)}\n isIncluded={included}\n isIncludedOptions={setIsIncludedOptions(key)}\n isDisabled={Array.isArray(currentValue) ? currentValue.length !== 0 : !isEmptyValue(currentValue)}\n />\n )}\n
    : null }\n
    \n
    \n }\n\n const sampleRequestBody = getDefaultRequestBodyValue(\n requestBody,\n contentType,\n activeExamplesKey,\n fn,\n )\n let language = null\n let testValueForJson = getKnownSyntaxHighlighterLanguage(sampleRequestBody)\n if (testValueForJson) {\n language = \"json\"\n }\n\n return
    \n { requestBodyDescription &&\n \n }\n {\n sampleForMediaType ? (\n \n ) : null\n }\n {\n isExecute ? (\n
    \n \n
    \n ) : (\n \n }\n includeWriteOnly={true}\n />\n )\n }\n {\n sampleForMediaType ? (\n \n ) : null\n }\n
    \n}\n\nRequestBody.propTypes = {\n userHasEditedBody: PropTypes.bool.isRequired,\n requestBody: ImPropTypes.orderedMap.isRequired,\n requestBodyValue: ImPropTypes.orderedMap.isRequired,\n requestBodyInclusionSetting: ImPropTypes.map.isRequired,\n requestBodyErrors: ImPropTypes.list.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n fn: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n contentType: PropTypes.string,\n isExecute: PropTypes.bool.isRequired,\n onChange: PropTypes.func.isRequired,\n onChangeIncludeEmpty: PropTypes.func.isRequired,\n specPath: PropTypes.array.isRequired,\n activeExamplesKey: PropTypes.string,\n updateActiveExamplesKey: PropTypes.func,\n setRetainRequestBodyValueFlag: PropTypes.func,\n oas3Actions: PropTypes.object.isRequired\n}\n\nexport default RequestBody\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class ServersContainer extends React.Component {\n\n static propTypes = {\n specSelectors: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n }\n\n render () {\n const {specSelectors, oas3Selectors, oas3Actions, getComponent} = this.props\n\n const servers = specSelectors.servers()\n\n const Servers = getComponent(\"Servers\")\n\n return servers && servers.size ? (\n
    \n Servers\n \n
    ) : null\n }\n}","import React from \"react\"\nimport { OrderedMap } from \"immutable\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class Servers extends React.Component {\n\n static propTypes = {\n servers: ImPropTypes.list.isRequired,\n currentServer: PropTypes.string.isRequired,\n setSelectedServer: PropTypes.func.isRequired,\n setServerVariableValue: PropTypes.func.isRequired,\n getServerVariable: PropTypes.func.isRequired,\n getEffectiveServerValue: PropTypes.func.isRequired\n }\n\n componentDidMount() {\n let { servers, currentServer } = this.props\n\n if(currentServer) {\n return\n }\n\n // fire 'change' event to set default 'value' of select\n this.setServer(servers.first()?.get(\"url\"))\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n let {\n servers,\n setServerVariableValue,\n getServerVariable\n } = nextProps\n if (this.props.currentServer !== nextProps.currentServer || this.props.servers !== nextProps.servers) {\n // Server has changed, we may need to set default values\n let currentServerDefinition = servers\n .find(v => v.get(\"url\") === nextProps.currentServer)\n let prevServerDefinition = this.props.servers\n .find(v => v.get(\"url\") === this.props.currentServer) || OrderedMap()\n \n if(!currentServerDefinition) {\n return this.setServer(servers.first().get(\"url\"))\n }\n \n let prevServerVariableDefs = prevServerDefinition.get(\"variables\") || OrderedMap()\n let prevServerVariableDefaultKey = prevServerVariableDefs.find(v => v.get(\"default\")) || OrderedMap()\n let prevServerVariableDefaultValue = prevServerVariableDefaultKey.get(\"default\")\n \n let currentServerVariableDefs = currentServerDefinition.get(\"variables\") || OrderedMap()\n let currentServerVariableDefaultKey = currentServerVariableDefs.find(v => v.get(\"default\")) || OrderedMap()\n let currentServerVariableDefaultValue = currentServerVariableDefaultKey.get(\"default\")\n \n currentServerVariableDefs.map((val, key) => {\n let currentValue = getServerVariable(nextProps.currentServer, key)\n \n // note: it is possible for both key/val to be the same across definitions,\n // but we will try to detect a change in default values between definitions\n // only set the default value if the user hasn't set one yet\n // or if the definition appears to have changed\n if (!currentValue || prevServerVariableDefaultValue !== currentServerVariableDefaultValue) {\n setServerVariableValue({\n server: nextProps.currentServer,\n key,\n val: val.get(\"default\") || \"\"\n })\n }\n })\n }\n }\n\n onServerChange =( e ) => {\n this.setServer( e.target.value )\n\n // set default variable values\n }\n\n onServerVariableValueChange = ( e ) => {\n let {\n setServerVariableValue,\n currentServer\n } = this.props\n\n let variableName = e.target.getAttribute(\"data-variable\")\n let newVariableValue = e.target.value\n\n if(typeof setServerVariableValue === \"function\") {\n setServerVariableValue({\n server: currentServer,\n key: variableName,\n val: newVariableValue\n })\n }\n }\n\n setServer = ( value ) => {\n let { setSelectedServer } = this.props\n\n setSelectedServer(value)\n }\n\n render() {\n let { servers,\n currentServer,\n getServerVariable,\n getEffectiveServerValue\n } = this.props\n\n\n let currentServerDefinition = servers.find(s => s.get(\"url\") === currentServer) || OrderedMap()\n\n let currentServerVariableDefs = currentServerDefinition.get(\"variables\") || OrderedMap()\n\n let shouldShowVariableUI = currentServerVariableDefs.size !== 0\n\n return (\n
    \n \n { shouldShowVariableUI ?\n
    \n\n
    \n Computed URL:\n \n {getEffectiveServerValue(currentServer)}\n \n
    \n

    Server variables

    \n \n \n {\n currentServerVariableDefs.entrySeq().map(([name, val]) => {\n return \n \n \n \n })\n }\n \n
    {name}\n { val.get(\"enum\") ?\n :\n \n }\n
    \n
    : null\n }\n
    \n )\n }\n}\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nexport function isOAS30(jsSpec) {\n const oasVersion = jsSpec.get(\"openapi\")\n\n return (\n typeof oasVersion === \"string\" &&\n /^3\\.0\\.([0123])(?:-rc[012])?$/.test(oasVersion)\n )\n}\n\nexport function isSwagger2(jsSpec) {\n const swaggerVersion = jsSpec.get(\"swagger\")\n\n return typeof swaggerVersion === \"string\" && swaggerVersion === \"2.0\"\n}\n\nexport function OAS3ComponentWrapFactory(Component) {\n return (Ori, system) => (props) => {\n if (typeof system.specSelectors?.isOAS3 === \"function\") {\n if (system.specSelectors.isOAS3()) {\n return \n } else {\n return \n }\n } else {\n console.warn(\"OAS3 wrapper: couldn't get spec\")\n return null\n }\n }\n}\n\nexport function OAS30ComponentWrapFactory(Component) {\n return (Ori, system) => (props) => {\n if (typeof system.specSelectors?.isOAS30 === \"function\") {\n if (system.specSelectors.isOAS30()) {\n return \n } else {\n return \n }\n } else {\n console.warn(\"OAS30 wrapper: couldn't get spec\")\n return null\n }\n }\n}\n","/**\n * @prettier\n */\nimport * as specWrapSelectors from \"./spec-extensions/wrap-selectors\"\nimport * as authWrapSelectors from \"./auth-extensions/wrap-selectors\"\nimport * as specSelectors from \"./spec-extensions/selectors\"\nimport components from \"./components\"\nimport wrapComponents from \"./wrap-components\"\nimport * as actions from \"./actions\"\nimport * as selectors from \"./selectors\"\nimport reducers from \"./reducers\"\n\nexport default function () {\n return {\n components,\n wrapComponents,\n statePlugins: {\n spec: {\n wrapSelectors: specWrapSelectors,\n selectors: specSelectors,\n },\n auth: {\n wrapSelectors: authWrapSelectors,\n },\n oas3: {\n actions,\n reducers,\n selectors,\n },\n },\n }\n}\n","import { fromJS, Map } from \"immutable\"\n\nimport {\n UPDATE_SELECTED_SERVER,\n UPDATE_REQUEST_BODY_VALUE,\n UPDATE_REQUEST_BODY_INCLUSION,\n UPDATE_ACTIVE_EXAMPLES_MEMBER,\n UPDATE_REQUEST_CONTENT_TYPE,\n UPDATE_SERVER_VARIABLE_VALUE,\n UPDATE_RESPONSE_CONTENT_TYPE,\n SET_REQUEST_BODY_VALIDATE_ERROR,\n CLEAR_REQUEST_BODY_VALIDATE_ERROR,\n CLEAR_REQUEST_BODY_VALUE, UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG,\n} from \"./actions\"\n\nexport default {\n [UPDATE_SELECTED_SERVER]: (state, { payload: { selectedServerUrl, namespace } } ) =>{\n const path = namespace ? [ namespace, \"selectedServer\"] : [ \"selectedServer\"]\n return state.setIn( path, selectedServerUrl)\n },\n [UPDATE_REQUEST_BODY_VALUE]: (state, { payload: { value, pathMethod } } ) =>{\n let [path, method] = pathMethod\n if (!Map.isMap(value)) {\n // context: application/json is always a String (instead of Map)\n return state.setIn( [ \"requestData\", path, method, \"bodyValue\" ], value)\n }\n let currentVal = state.getIn([\"requestData\", path, method, \"bodyValue\"]) || Map()\n if (!Map.isMap(currentVal)) {\n // context: user switch from application/json to application/x-www-form-urlencoded\n currentVal = Map()\n }\n let newVal\n const [...valueKeys] = value.keys()\n valueKeys.forEach((valueKey) => {\n let valueKeyVal = value.getIn([valueKey])\n if (!currentVal.has(valueKey)) {\n newVal = currentVal.setIn([valueKey, \"value\"], valueKeyVal)\n } else if (!Map.isMap(valueKeyVal)) {\n // context: user input will be received as String\n newVal = currentVal.setIn([valueKey, \"value\"], valueKeyVal)\n }\n })\n return state.setIn([\"requestData\", path, method, \"bodyValue\"], newVal)\n },\n [UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG]: (state, { payload: { value, pathMethod } } ) =>{\n let [path, method] = pathMethod\n return state.setIn([\"requestData\", path, method, \"retainBodyValue\"], value)\n },\n [UPDATE_REQUEST_BODY_INCLUSION]: (state, { payload: { value, pathMethod, name } } ) =>{\n let [path, method] = pathMethod\n return state.setIn( [ \"requestData\", path, method, \"bodyInclusion\", name ], value)\n },\n [UPDATE_ACTIVE_EXAMPLES_MEMBER]: (state, { payload: { name, pathMethod, contextType, contextName } } ) =>{\n let [path, method] = pathMethod\n return state.setIn( [ \"examples\", path, method, contextType, contextName, \"activeExample\" ], name)\n },\n [UPDATE_REQUEST_CONTENT_TYPE]: (state, { payload: { value, pathMethod } } ) =>{\n let [path, method] = pathMethod\n return state.setIn( [ \"requestData\", path, method, \"requestContentType\" ], value)\n },\n [UPDATE_RESPONSE_CONTENT_TYPE]: (state, { payload: { value, path, method } } ) =>{\n return state.setIn( [ \"requestData\", path, method, \"responseContentType\" ], value)\n },\n [UPDATE_SERVER_VARIABLE_VALUE]: (state, { payload: { server, namespace, key, val } } ) =>{\n const path = namespace ? [ namespace, \"serverVariableValues\", server, key ] : [ \"serverVariableValues\", server, key ]\n return state.setIn(path, val)\n },\n [SET_REQUEST_BODY_VALIDATE_ERROR]: (state, { payload: { path, method, validationErrors } } ) => {\n let errors = []\n errors.push(\"Required field is not provided\")\n if (validationErrors.missingBodyValue) {\n // context: is application/json or application/xml, where typeof (missing) bodyValue = String\n return state.setIn([\"requestData\", path, method, \"errors\"], fromJS(errors))\n }\n if (validationErrors.missingRequiredKeys && validationErrors.missingRequiredKeys.length > 0) {\n // context: is application/x-www-form-urlencoded, with list of missing keys\n const { missingRequiredKeys } = validationErrors\n return state.updateIn([\"requestData\", path, method, \"bodyValue\"], fromJS({}), missingKeyValues => {\n return missingRequiredKeys.reduce((bodyValue, currentMissingKey) => {\n return bodyValue.setIn([currentMissingKey, \"errors\"], fromJS(errors))\n }, missingKeyValues)\n })\n }\n console.warn(\"unexpected result: SET_REQUEST_BODY_VALIDATE_ERROR\")\n return state\n },\n [CLEAR_REQUEST_BODY_VALIDATE_ERROR]: (state, { payload: { path, method } }) => {\n const requestBodyValue = state.getIn([\"requestData\", path, method, \"bodyValue\"])\n if (!Map.isMap(requestBodyValue)) {\n return state.setIn([\"requestData\", path, method, \"errors\"], fromJS([]))\n }\n const [...valueKeys] = requestBodyValue.keys()\n if (!valueKeys) {\n return state\n }\n return state.updateIn([\"requestData\", path, method, \"bodyValue\"], fromJS({}), bodyValues => {\n return valueKeys.reduce((bodyValue, curr) => {\n return bodyValue.setIn([curr, \"errors\"], fromJS([]))\n }, bodyValues)\n })\n },\n [CLEAR_REQUEST_BODY_VALUE]: (state, { payload: { pathMethod }}) => {\n let [path, method] = pathMethod\n const requestBodyValue = state.getIn([\"requestData\", path, method, \"bodyValue\"])\n if (!requestBodyValue) {\n return state\n }\n if (!Map.isMap(requestBodyValue)) {\n return state.setIn([\"requestData\", path, method, \"bodyValue\"], \"\")\n }\n return state.setIn([\"requestData\", path, method, \"bodyValue\"], Map())\n }\n}\n","/**\n * @prettier\n */\nimport { OrderedMap, Map, List } from \"immutable\"\nimport { createSelector } from \"reselect\"\n\nimport { getDefaultRequestBodyValue } from \"./components/request-body\"\nimport { stringify } from \"../../utils\"\n\n// Helpers\n\nconst onlyOAS3 =\n (selector) =>\n (state, ...args) =>\n (system) => {\n if (system.getSystem().specSelectors.isOAS3()) {\n const selectedValue = selector(state, ...args)\n return typeof selectedValue === \"function\"\n ? selectedValue(system)\n : selectedValue\n } else {\n return null\n }\n }\n\nfunction validateRequestBodyIsRequired(selector) {\n return (...args) =>\n (system) => {\n const specJson = system.getSystem().specSelectors.specJson()\n const argsList = [...args]\n // expect argsList[0] = state\n let pathMethod = argsList[1] || []\n let isOas3RequestBodyRequired = specJson.getIn([\n \"paths\",\n ...pathMethod,\n \"requestBody\",\n \"required\",\n ])\n\n if (isOas3RequestBodyRequired) {\n return selector(...args)\n } else {\n // validation pass b/c not required\n return true\n }\n }\n}\n\nconst validateRequestBodyValueExists = (state, pathMethod) => {\n pathMethod = pathMethod || []\n let oas3RequestBodyValue = state.getIn([\n \"requestData\",\n ...pathMethod,\n \"bodyValue\",\n ])\n // context: bodyValue can be a String, or a Map\n if (!oas3RequestBodyValue) {\n return false\n }\n // validation pass if String is not empty, or if Map exists\n return true\n}\n\nexport const selectedServer = onlyOAS3((state, namespace) => {\n const path = namespace ? [namespace, \"selectedServer\"] : [\"selectedServer\"]\n return state.getIn(path) || \"\"\n})\n\nexport const requestBodyValue = onlyOAS3((state, path, method) => {\n return state.getIn([\"requestData\", path, method, \"bodyValue\"]) || null\n})\n\nexport const shouldRetainRequestBodyValue = onlyOAS3((state, path, method) => {\n return state.getIn([\"requestData\", path, method, \"retainBodyValue\"]) || false\n})\n\nexport const selectDefaultRequestBodyValue =\n (state, path, method) => (system) => {\n const { oas3Selectors, specSelectors, fn } = system.getSystem()\n\n if (specSelectors.isOAS3()) {\n const currentMediaType = oas3Selectors.requestContentType(path, method)\n if (currentMediaType) {\n return getDefaultRequestBodyValue(\n specSelectors.specResolvedSubtree([\n \"paths\",\n path,\n method,\n \"requestBody\",\n ]),\n currentMediaType,\n oas3Selectors.activeExamplesMember(\n path,\n method,\n \"requestBody\",\n \"requestBody\"\n ),\n fn\n )\n }\n }\n return null\n }\n\nexport const hasUserEditedBody = onlyOAS3((state, path, method) => (system) => {\n const { oas3Selectors, specSelectors, fn } = system\n\n let userHasEditedBody = false\n const currentMediaType = oas3Selectors.requestContentType(path, method)\n let userEditedRequestBody = oas3Selectors.requestBodyValue(path, method)\n const requestBody = specSelectors.specResolvedSubtree([\n \"paths\",\n path,\n method,\n \"requestBody\",\n ])\n\n /**\n * The only request body that can currently be edited is for Path Items that are direct values of OpenAPI.paths.\n * Path Item contained within the Callback Object or OpenAPI.webhooks (OpenAPI 3.1.0) have `Try it out`\n * disabled and thus body cannot be edited.\n */\n if (!requestBody) {\n return false\n }\n\n if (Map.isMap(userEditedRequestBody)) {\n // context is not application/json media-type\n userEditedRequestBody = stringify(\n userEditedRequestBody\n .mapEntries((kv) =>\n Map.isMap(kv[1]) ? [kv[0], kv[1].get(\"value\")] : kv\n )\n .toJS()\n )\n }\n if (List.isList(userEditedRequestBody)) {\n userEditedRequestBody = stringify(userEditedRequestBody)\n }\n\n if (currentMediaType) {\n const currentMediaTypeDefaultBodyValue = getDefaultRequestBodyValue(\n requestBody,\n currentMediaType,\n oas3Selectors.activeExamplesMember(\n path,\n method,\n \"requestBody\",\n \"requestBody\"\n ),\n fn\n )\n userHasEditedBody =\n !!userEditedRequestBody &&\n userEditedRequestBody !== currentMediaTypeDefaultBodyValue\n }\n return userHasEditedBody\n})\n\nexport const requestBodyInclusionSetting = onlyOAS3((state, path, method) => {\n return state.getIn([\"requestData\", path, method, \"bodyInclusion\"]) || Map()\n})\n\nexport const requestBodyErrors = onlyOAS3((state, path, method) => {\n return state.getIn([\"requestData\", path, method, \"errors\"]) || null\n})\n\nexport const activeExamplesMember = onlyOAS3(\n (state, path, method, type, name) => {\n return (\n state.getIn([\"examples\", path, method, type, name, \"activeExample\"]) ||\n null\n )\n }\n)\n\nexport const requestContentType = onlyOAS3((state, path, method) => {\n return (\n state.getIn([\"requestData\", path, method, \"requestContentType\"]) || null\n )\n})\n\nexport const responseContentType = onlyOAS3((state, path, method) => {\n return (\n state.getIn([\"requestData\", path, method, \"responseContentType\"]) || null\n )\n})\n\nexport const serverVariableValue = onlyOAS3((state, locationData, key) => {\n let path\n\n // locationData may take one of two forms, for backwards compatibility\n // Object: ({server, namespace?}) or String:(server)\n if (typeof locationData !== \"string\") {\n const { server, namespace } = locationData\n if (namespace) {\n path = [namespace, \"serverVariableValues\", server, key]\n } else {\n path = [\"serverVariableValues\", server, key]\n }\n } else {\n const server = locationData\n path = [\"serverVariableValues\", server, key]\n }\n\n return state.getIn(path) || null\n})\n\nexport const serverVariables = onlyOAS3((state, locationData) => {\n let path\n\n // locationData may take one of two forms, for backwards compatibility\n // Object: ({server, namespace?}) or String:(server)\n if (typeof locationData !== \"string\") {\n const { server, namespace } = locationData\n if (namespace) {\n path = [namespace, \"serverVariableValues\", server]\n } else {\n path = [\"serverVariableValues\", server]\n }\n } else {\n const server = locationData\n path = [\"serverVariableValues\", server]\n }\n\n return state.getIn(path) || OrderedMap()\n})\n\nexport const serverEffectiveValue = onlyOAS3((state, locationData) => {\n var varValues, serverValue\n\n // locationData may take one of two forms, for backwards compatibility\n // Object: ({server, namespace?}) or String:(server)\n if (typeof locationData !== \"string\") {\n const { server, namespace } = locationData\n serverValue = server\n if (namespace) {\n varValues = state.getIn([namespace, \"serverVariableValues\", serverValue])\n } else {\n varValues = state.getIn([\"serverVariableValues\", serverValue])\n }\n } else {\n serverValue = locationData\n varValues = state.getIn([\"serverVariableValues\", serverValue])\n }\n\n varValues = varValues || OrderedMap()\n let str = serverValue\n\n varValues.map((val, key) => {\n str = str.replace(new RegExp(`{${key}}`, \"g\"), val)\n })\n\n return str\n})\n\nexport const validateBeforeExecute = validateRequestBodyIsRequired(\n (state, pathMethod) => validateRequestBodyValueExists(state, pathMethod)\n)\n\nexport const validateShallowRequired = (\n state,\n {\n oas3RequiredRequestBodyContentType,\n oas3RequestContentType,\n oas3RequestBodyValue,\n }\n) => {\n let missingRequiredKeys = []\n // context: json => String; urlencoded, form-data => Map\n if (!Map.isMap(oas3RequestBodyValue)) {\n return missingRequiredKeys\n }\n let requiredKeys = []\n // Cycle through list of possible contentTypes for matching contentType and defined requiredKeys\n Object.keys(oas3RequiredRequestBodyContentType.requestContentType).forEach(\n (contentType) => {\n if (contentType === oas3RequestContentType) {\n let contentTypeVal =\n oas3RequiredRequestBodyContentType.requestContentType[contentType]\n contentTypeVal.forEach((requiredKey) => {\n if (requiredKeys.indexOf(requiredKey) < 0) {\n requiredKeys.push(requiredKey)\n }\n })\n }\n }\n )\n requiredKeys.forEach((key) => {\n let requiredKeyValue = oas3RequestBodyValue.getIn([key, \"value\"])\n if (!requiredKeyValue) {\n missingRequiredKeys.push(key)\n }\n })\n return missingRequiredKeys\n}\n\nexport const validOperationMethods = createSelector(() => [\n \"get\",\n \"put\",\n \"post\",\n \"delete\",\n \"options\",\n \"head\",\n \"patch\",\n \"trace\",\n])\n","/**\n * @prettier\n */\nimport { List, Map } from \"immutable\"\n\nimport {\n isSwagger2 as isSwagger2Helper,\n isOAS30 as isOAS30Helper,\n} from \"../helpers\"\n\n/**\n * Helpers\n */\n\nconst map = Map()\n\nexport const isSwagger2 = () => (system) => {\n const spec = system.getSystem().specSelectors.specJson()\n return isSwagger2Helper(spec)\n}\n\nexport const isOAS30 = () => (system) => {\n const spec = system.getSystem().specSelectors.specJson()\n return isOAS30Helper(spec)\n}\n\nexport const isOAS3 = () => (system) => {\n return system.getSystem().specSelectors.isOAS30()\n}\n\nfunction onlyOAS3(selector) {\n return (state, ...args) =>\n (system) => {\n if (system.specSelectors.isOAS3()) {\n const selectedValue = selector(state, ...args)\n return typeof selectedValue === \"function\"\n ? selectedValue(system)\n : selectedValue\n } else {\n return null\n }\n }\n}\n\nexport const servers = onlyOAS3(() => (system) => {\n const spec = system.specSelectors.specJson()\n return spec.get(\"servers\", map)\n})\n\nexport const callbacksOperations = onlyOAS3(\n (state, { callbacks, specPath }) =>\n (system) => {\n const validOperationMethods = system.specSelectors.validOperationMethods()\n\n if (!Map.isMap(callbacks)) return {}\n\n return callbacks\n .reduce((allOperations, callback, callbackName) => {\n if (!Map.isMap(callback)) return allOperations\n\n return callback.reduce((callbackOperations, pathItem, expression) => {\n if (!Map.isMap(pathItem)) return callbackOperations\n\n const pathItemOperations = pathItem\n .entrySeq()\n .filter(([key]) => validOperationMethods.includes(key))\n .map(([method, operation]) => ({\n operation: Map({ operation }),\n method,\n path: expression,\n callbackName,\n specPath: specPath.concat([callbackName, expression, method]),\n }))\n\n return callbackOperations.concat(pathItemOperations)\n }, List())\n }, List())\n .groupBy((operationDTO) => operationDTO.callbackName)\n .map((operations) => operations.toArray())\n .toObject()\n }\n)\n","/**\n * @prettier\n */\nimport { createSelector } from \"reselect\"\nimport { specJsonWithResolvedSubtrees } from \"../../spec/selectors\"\nimport { Map } from \"immutable\"\n\n/**\n * Helpers\n */\n\nconst map = Map()\n\nfunction onlyOAS3(selector) {\n return (ori, system) =>\n (...args) => {\n if (system.getSystem().specSelectors.isOAS3()) {\n const result = selector(...args)\n return typeof result === \"function\" ? result(system) : result\n } else {\n return ori(...args)\n }\n }\n}\n\nconst nullSelector = createSelector(() => null)\n\nconst OAS3NullSelector = onlyOAS3(nullSelector)\n\n/**\n * Wrappers\n */\n\nexport const definitions = onlyOAS3(() => (system) => {\n const spec = system.getSystem().specSelectors.specJson()\n const schemas = spec.getIn([\"components\", \"schemas\"])\n return Map.isMap(schemas) ? schemas : map\n})\n\nexport const hasHost = onlyOAS3(() => (system) => {\n const spec = system.getSystem().specSelectors.specJson()\n return spec.hasIn([\"servers\", 0])\n})\n\nexport const securityDefinitions = onlyOAS3(\n createSelector(\n specJsonWithResolvedSubtrees,\n (spec) => spec.getIn([\"components\", \"securitySchemes\"]) || null\n )\n)\n\nexport const validOperationMethods =\n (oriSelector, system) =>\n (state, ...args) => {\n if (system.specSelectors.isOAS3()) {\n return system.oas3Selectors.validOperationMethods()\n }\n\n return oriSelector(...args)\n }\n\nexport const host = OAS3NullSelector\nexport const basePath = OAS3NullSelector\nexport const consumes = OAS3NullSelector\nexport const produces = OAS3NullSelector\nexport const schemes = OAS3NullSelector\n","import React from \"react\"\nimport { OAS3ComponentWrapFactory } from \"../helpers\"\n\nexport default OAS3ComponentWrapFactory(({ Ori, ...props }) => {\n const {\n schema, getComponent, errSelectors, authorized, onAuthChange, name\n } = props\n\n const HttpAuth = getComponent(\"HttpAuth\")\n const type = schema.get(\"type\")\n\n if(type === \"http\") {\n return \n } else {\n return \n }\n})\n","import Markdown from \"./markdown\"\nimport AuthItem from \"./auth-item\"\nimport VersionStamp from \"./version-stamp\"\nimport OnlineValidatorBadge from \"./online-validator-badge\"\nimport Model from \"./model\"\nimport JsonSchema_string from \"./json-schema-string\"\n\nexport default {\n Markdown,\n AuthItem,\n JsonSchema_string,\n VersionStamp,\n model: Model,\n onlineValidatorBadge: OnlineValidatorBadge,\n}\n","import React from \"react\"\nimport { OAS3ComponentWrapFactory } from \"../helpers\"\n\nexport default OAS3ComponentWrapFactory(({ Ori, ...props }) => {\n const {\n schema,\n getComponent,\n errors,\n onChange\n } = props\n\n const format = schema && schema.get ? schema.get(\"format\") : null\n const type = schema && schema.get ? schema.get(\"type\") : null\n const Input = getComponent(\"Input\")\n\n if(type && type === \"string\" && (format && (format === \"binary\" || format === \"base64\"))) {\n return {\n onChange(e.target.files[0])\n }}\n disabled={Ori.isDisabled}/>\n } else {\n return \n }\n})\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport cx from \"classnames\"\nimport { Remarkable } from \"remarkable\"\nimport { OAS3ComponentWrapFactory } from \"../helpers\"\nimport { sanitizer } from \"core/components/providers/markdown\"\n\nconst parser = new Remarkable(\"commonmark\")\nparser.block.ruler.enable([\"table\"])\nparser.set({ linkTarget: \"_blank\" })\n\nexport const Markdown = ({ source, className = \"\", getConfigs }) => {\n if(typeof source !== \"string\") {\n return null\n }\n\n if ( source ) {\n const { useUnsafeMarkdown } = getConfigs()\n const html = parser.render(source)\n const sanitized = sanitizer(html, { useUnsafeMarkdown })\n\n let trimmed\n\n if(typeof sanitized === \"string\") {\n trimmed = sanitized.trim()\n }\n\n return (\n \n )\n }\n return null\n}\nMarkdown.propTypes = {\n source: PropTypes.string,\n className: PropTypes.string,\n getConfigs: PropTypes.func,\n}\n\nMarkdown.defaultProps = {\n getConfigs: () => ({ useUnsafeMarkdown: false }),\n}\n\nexport default OAS3ComponentWrapFactory(Markdown)\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { OAS3ComponentWrapFactory } from \"../helpers\"\nimport Model from \"core/components/model\"\n\nclass ModelComponent extends Component {\n static propTypes = {\n schema: PropTypes.object.isRequired,\n name: PropTypes.string,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n expandDepth: PropTypes.number,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n }\n\n render(){\n let { getConfigs, schema } = this.props\n let classes = [\"model-box\"]\n let isDeprecated = schema.get(\"deprecated\") === true\n let message = null\n\n if(isDeprecated) {\n classes.push(\"deprecated\")\n message = Deprecated:\n }\n\n return
    \n {message}\n \n
    \n }\n}\n\nexport default OAS3ComponentWrapFactory(ModelComponent)\n","import { OAS3ComponentWrapFactory } from \"../helpers\"\nimport OnlineValidatorBadge from \"core/components/online-validator-badge\"\n\n// OAS3 spec is now supported by the online validator.\nexport default OAS3ComponentWrapFactory(OnlineValidatorBadge)\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { OAS30ComponentWrapFactory } from \"../helpers\"\n\nexport default OAS30ComponentWrapFactory((props) => {\n const { Ori } = props\n\n return (\n \n \n \n
    OAS 3.0
    \n
    \n
    \n )\n})\n","/**\n * @prettier\n */\nimport {\n makeIsExpandable,\n getProperties,\n} from \"./json-schema-2020-12-extensions/fn\"\nimport { wrapOAS31Fn } from \"./fn\"\n\nfunction afterLoad({ fn, getSystem }) {\n // overrides for fn.jsonSchema202012\n if (fn.jsonSchema202012) {\n const isExpandable = makeIsExpandable(\n fn.jsonSchema202012.isExpandable,\n getSystem\n )\n\n Object.assign(this.fn.jsonSchema202012, { isExpandable, getProperties })\n }\n\n // wraps schema generators from samples plugin and make them specific to OpenAPI 3.1 version\n if (typeof fn.sampleFromSchema === \"function\" && fn.jsonSchema202012) {\n const wrappedFns = wrapOAS31Fn(\n {\n sampleFromSchema: fn.jsonSchema202012.sampleFromSchema,\n sampleFromSchemaGeneric: fn.jsonSchema202012.sampleFromSchemaGeneric,\n createXMLExample: fn.jsonSchema202012.createXMLExample,\n memoizedSampleFromSchema: fn.jsonSchema202012.memoizedSampleFromSchema,\n memoizedCreateXMLExample: fn.jsonSchema202012.memoizedCreateXMLExample,\n },\n getSystem()\n )\n\n Object.assign(this.fn, wrappedFns)\n }\n}\n\nexport default afterLoad\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\n\nimport { sanitizeUrl } from \"core/utils\"\n\nconst Contact = ({ getComponent, specSelectors }) => {\n const name = specSelectors.selectContactNameField()\n const url = specSelectors.selectContactUrl()\n const email = specSelectors.selectContactEmailField()\n\n const Link = getComponent(\"Link\")\n\n return (\n
    \n {url && (\n
    \n \n {name} - Website\n \n
    \n )}\n {email && (\n \n {url ? `Send email to ${name}` : `Contact ${name}`}\n \n )}\n
    \n )\n}\n\nContact.propTypes = {\n getComponent: PropTypes.func.isRequired,\n specSelectors: PropTypes.shape({\n selectContactNameField: PropTypes.func.isRequired,\n selectContactUrl: PropTypes.func.isRequired,\n selectContactEmailField: PropTypes.func.isRequired,\n }).isRequired,\n}\n\nexport default Contact\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\n\nimport { sanitizeUrl } from \"core/utils\"\n\nconst Info = ({ getComponent, specSelectors }) => {\n const version = specSelectors.version()\n const url = specSelectors.url()\n const basePath = specSelectors.basePath()\n const host = specSelectors.host()\n const summary = specSelectors.selectInfoSummaryField()\n const description = specSelectors.selectInfoDescriptionField()\n const title = specSelectors.selectInfoTitleField()\n const termsOfServiceUrl = specSelectors.selectInfoTermsOfServiceUrl()\n const externalDocsUrl = specSelectors.selectExternalDocsUrl()\n const externalDocsDesc = specSelectors.selectExternalDocsDescriptionField()\n const contact = specSelectors.contact()\n const license = specSelectors.license()\n\n const Markdown = getComponent(\"Markdown\", true)\n const Link = getComponent(\"Link\")\n const VersionStamp = getComponent(\"VersionStamp\")\n const InfoUrl = getComponent(\"InfoUrl\")\n const InfoBasePath = getComponent(\"InfoBasePath\")\n const License = getComponent(\"License\", true)\n const Contact = getComponent(\"Contact\", true)\n const JsonSchemaDialect = getComponent(\"JsonSchemaDialect\", true)\n\n return (\n
    \n
    \n

    \n {title}\n {version && }\n

    \n\n {(host || basePath) && }\n {url && }\n
    \n\n {summary &&

    {summary}

    }\n\n
    \n \n
    \n\n {termsOfServiceUrl && (\n
    \n \n Terms of service\n \n
    \n )}\n\n {contact.size > 0 && }\n\n {license.size > 0 && }\n\n {externalDocsUrl && (\n \n {externalDocsDesc || externalDocsUrl}\n \n )}\n\n \n
    \n )\n}\n\nInfo.propTypes = {\n getComponent: PropTypes.func.isRequired,\n specSelectors: PropTypes.shape({\n version: PropTypes.func.isRequired,\n url: PropTypes.func.isRequired,\n basePath: PropTypes.func.isRequired,\n host: PropTypes.func.isRequired,\n selectInfoSummaryField: PropTypes.func.isRequired,\n selectInfoDescriptionField: PropTypes.func.isRequired,\n selectInfoTitleField: PropTypes.func.isRequired,\n selectInfoTermsOfServiceUrl: PropTypes.func.isRequired,\n selectExternalDocsUrl: PropTypes.func.isRequired,\n selectExternalDocsDescriptionField: PropTypes.func.isRequired,\n contact: PropTypes.func.isRequired,\n license: PropTypes.func.isRequired,\n }).isRequired,\n}\n\nexport default Info\n","/**\n * @prettier\n */\n\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\n\nimport { sanitizeUrl } from \"core/utils\"\n\nconst JsonSchemaDialect = ({ getComponent, specSelectors }) => {\n const jsonSchemaDialect = specSelectors.selectJsonSchemaDialectField()\n const jsonSchemaDialectDefault = specSelectors.selectJsonSchemaDialectDefault() // prettier-ignore\n\n const Link = getComponent(\"Link\")\n\n return (\n <>\n {jsonSchemaDialect && jsonSchemaDialect === jsonSchemaDialectDefault && (\n

    \n JSON Schema dialect:{\" \"}\n \n {jsonSchemaDialect}\n \n

    \n )}\n\n {jsonSchemaDialect && jsonSchemaDialect !== jsonSchemaDialectDefault && (\n
    \n
    \n
    \n
    \n

    Warning

    \n

    \n OpenAPI.jsonSchemaDialect field contains a\n value different from the default value of{\" \"}\n \n {jsonSchemaDialectDefault}\n \n . Values different from the default one are currently not\n supported. Please either omit the field or provide it with the\n default value.\n

    \n
    \n
    \n
    \n
    \n )}\n \n )\n}\n\nJsonSchemaDialect.propTypes = {\n getComponent: PropTypes.func.isRequired,\n specSelectors: PropTypes.shape({\n selectJsonSchemaDialectField: PropTypes.func.isRequired,\n selectJsonSchemaDialectDefault: PropTypes.func.isRequired,\n }).isRequired,\n}\n\nexport default JsonSchemaDialect\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\n\nimport { sanitizeUrl } from \"core/utils\"\n\nconst License = ({ getComponent, specSelectors }) => {\n const name = specSelectors.selectLicenseNameField()\n const url = specSelectors.selectLicenseUrl()\n\n const Link = getComponent(\"Link\")\n\n return (\n
    \n {url ? (\n
    \n \n {name}\n \n
    \n ) : (\n {name}\n )}\n
    \n )\n}\n\nLicense.propTypes = {\n getComponent: PropTypes.func.isRequired,\n specSelectors: PropTypes.shape({\n selectLicenseNameField: PropTypes.func.isRequired,\n selectLicenseUrl: PropTypes.func.isRequired,\n }).isRequired,\n}\n\nexport default License\n","/**\n * @prettier\n */\nimport React, { forwardRef, useCallback } from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nconst decodeRefName = (uri) => {\n const unescaped = uri.replace(/~1/g, \"/\").replace(/~0/g, \"~\")\n try {\n return decodeURIComponent(unescaped)\n } catch {\n return unescaped\n }\n}\nconst getModelName = (uri) => {\n if (typeof uri === \"string\" && uri.includes(\"#/components/schemas/\")) {\n return decodeRefName(uri.replace(/^.*#\\/components\\/schemas\\//, \"\"))\n }\n return null\n}\n\nconst Model = forwardRef(({ schema, getComponent, onToggle }, ref) => {\n const JSONSchema202012 = getComponent(\"JSONSchema202012\")\n const name = getModelName(schema.get(\"$$ref\"))\n\n const handleExpand = useCallback(\n (e, expanded) => {\n onToggle(name, expanded)\n },\n [name, onToggle]\n )\n\n return (\n \n )\n})\n\nModel.propTypes = {\n schema: ImPropTypes.map.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n specPath: ImPropTypes.list.isRequired,\n name: PropTypes.string,\n displayName: PropTypes.string,\n isRef: PropTypes.bool,\n required: PropTypes.bool,\n expandDepth: PropTypes.number,\n depth: PropTypes.number,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n onToggle: PropTypes.func,\n}\n\nModel.defaultProps = {\n name: \"\",\n displayName: \"\",\n isRef: false,\n required: false,\n expandDepth: 0,\n depth: 1,\n includeReadOnly: false,\n includeWriteOnly: false,\n onToggle: () => {},\n}\n\nexport default Model\n","/**\n * @prettier\n */\nimport React, { useCallback, useEffect } from \"react\"\nimport PropTypes from \"prop-types\"\nimport classNames from \"classnames\"\n\nconst Models = ({\n specActions,\n specSelectors,\n layoutSelectors,\n layoutActions,\n getComponent,\n getConfigs,\n}) => {\n const schemas = specSelectors.selectSchemas()\n const hasSchemas = Object.keys(schemas).length > 0\n const schemasPath = [\"components\", \"schemas\"]\n const { docExpansion, defaultModelsExpandDepth } = getConfigs()\n const isOpenDefault = defaultModelsExpandDepth > 0 && docExpansion !== \"none\"\n const isOpen = layoutSelectors.isShown(schemasPath, isOpenDefault)\n const Collapse = getComponent(\"Collapse\")\n const JSONSchema202012 = getComponent(\"JSONSchema202012\")\n\n /**\n * Effects.\n */\n useEffect(() => {\n const isOpenAndExpanded = isOpen && defaultModelsExpandDepth > 1\n const isResolved = specSelectors.specResolvedSubtree(schemasPath) != null\n if (isOpenAndExpanded && !isResolved) {\n specActions.requestResolvedSubtree(schemasPath)\n }\n }, [isOpen, defaultModelsExpandDepth])\n\n /**\n * Event handlers.\n */\n\n const handleModelsExpand = useCallback(() => {\n layoutActions.show(schemasPath, !isOpen)\n }, [isOpen])\n const handleModelsRef = useCallback((node) => {\n if (node !== null) {\n layoutActions.readyToScroll(schemasPath, node)\n }\n }, [])\n const handleJSONSchema202012Ref = (schemaName) => (node) => {\n if (node !== null) {\n layoutActions.readyToScroll([...schemasPath, schemaName], node)\n }\n }\n const handleJSONSchema202012Expand = (schemaName) => (e, expanded) => {\n if (expanded) {\n const schemaPath = [...schemasPath, schemaName]\n const isResolved = specSelectors.specResolvedSubtree(schemaPath) != null\n if (!isResolved) {\n specActions.requestResolvedSubtree([...schemasPath, schemaName])\n }\n }\n }\n\n /**\n * Rendering.\n */\n\n if (!hasSchemas || defaultModelsExpandDepth < 0) {\n return null\n }\n\n return (\n \n

    \n \n Schemas\n \n \n \n \n

    \n \n {Object.entries(schemas).map(([schemaName, schema]) => (\n \n ))}\n \n \n )\n}\n\nModels.propTypes = {\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.shape({\n selectSchemas: PropTypes.func.isRequired,\n specResolvedSubtree: PropTypes.func.isRequired,\n }).isRequired,\n specActions: PropTypes.shape({\n requestResolvedSubtree: PropTypes.func.isRequired,\n }).isRequired,\n layoutSelectors: PropTypes.shape({\n isShown: PropTypes.func.isRequired,\n }).isRequired,\n layoutActions: PropTypes.shape({\n show: PropTypes.func.isRequired,\n readyToScroll: PropTypes.func.isRequired,\n }).isRequired,\n}\n\nexport default Models\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nconst VersionPragmaFilter = ({\n bypass,\n isSwagger2,\n isOAS3,\n isOAS31,\n alsoShow,\n children,\n}) => {\n if (bypass) {\n return
    {children}
    \n }\n\n if (isSwagger2 && (isOAS3 || isOAS31)) {\n return (\n
    \n {alsoShow}\n
    \n
    \n

    Unable to render this definition

    \n

    \n swagger and openapi fields cannot be\n present in the same Swagger or OpenAPI definition. Please remove\n one of the fields.\n

    \n

    \n Supported version fields are swagger: "2.0" and\n those that match openapi: 3.x.y (for example,{\" \"}\n openapi: 3.1.0).\n

    \n
    \n
    \n
    \n )\n }\n\n if (!isSwagger2 && !isOAS3 && !isOAS31) {\n return (\n
    \n {alsoShow}\n
    \n
    \n

    Unable to render this definition

    \n

    \n The provided definition does not specify a valid version field.\n

    \n

    \n Please indicate a valid Swagger or OpenAPI version field.\n Supported version fields are swagger: "2.0" and\n those that match openapi: 3.x.y (for example,{\" \"}\n openapi: 3.1.0).\n

    \n
    \n
    \n
    \n )\n }\n\n return
    {children}
    \n}\n\nVersionPragmaFilter.propTypes = {\n isSwagger2: PropTypes.bool.isRequired,\n isOAS3: PropTypes.bool.isRequired,\n isOAS31: PropTypes.bool.isRequired,\n bypass: PropTypes.bool,\n alsoShow: PropTypes.element,\n children: PropTypes.any,\n}\n\nexport default VersionPragmaFilter\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\n\nconst Webhooks = ({ specSelectors, getComponent }) => {\n const operationDTOs = specSelectors.selectWebhooksOperations()\n const pathItemNames = Object.keys(operationDTOs)\n\n const OperationContainer = getComponent(\"OperationContainer\", true)\n\n if (pathItemNames.length === 0) return null\n\n return (\n
    \n

    Webhooks

    \n\n {pathItemNames.map((pathItemName) => (\n
    \n {operationDTOs[pathItemName].map((operationDTO) => (\n \n ))}\n
    \n ))}\n
    \n )\n}\n\nWebhooks.propTypes = {\n specSelectors: PropTypes.shape({\n selectWebhooksOperations: PropTypes.func.isRequired,\n }).isRequired,\n getComponent: PropTypes.func.isRequired,\n}\n\nexport default Webhooks\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nexport const isOAS31 = (jsSpec) => {\n const oasVersion = jsSpec.get(\"openapi\")\n\n return (\n typeof oasVersion === \"string\" && /^3\\.1\\.(?:[1-9]\\d*|0)$/.test(oasVersion)\n )\n}\n\n/**\n * Creates selector that returns value of the passed\n * selector when spec is OpenAPI 3.1.0., null otherwise.\n *\n * @param selector\n * @returns {function(*, ...[*]): function(*): (*|null)}\n */\nexport const createOnlyOAS31Selector =\n (selector) =>\n (state, ...args) =>\n (system) => {\n if (system.getSystem().specSelectors.isOAS31()) {\n const selectedValue = selector(state, ...args)\n return typeof selectedValue === \"function\"\n ? selectedValue(system)\n : selectedValue\n } else {\n return null\n }\n }\n\n/**\n * Creates selector wrapper that returns value of the passed\n * selector when spec is OpenAPI 3.1.0., calls original selector otherwise.\n *\n *\n * @param selector\n * @returns {function(*, *): function(*, ...[*]): (*)}\n */\nexport const createOnlyOAS31SelectorWrapper =\n (selector) =>\n (oriSelector, system) =>\n (state, ...args) => {\n if (system.getSystem().specSelectors.isOAS31()) {\n const selectedValue = selector(state, ...args)\n return typeof selectedValue === \"function\"\n ? selectedValue(oriSelector, system)\n : selectedValue\n } else {\n return oriSelector(...args)\n }\n }\n\n/**\n * Creates selector that provides system as the\n * second argument. This allows to create memoized\n * composed selectors from different plugins.\n *\n * @param selector\n * @returns {function(*, ...[*]): function(*): *}\n */\nexport const createSystemSelector =\n (selector) =>\n (state, ...args) =>\n (system) => {\n const selectedValue = selector(state, system, ...args)\n return typeof selectedValue === \"function\"\n ? selectedValue(system)\n : selectedValue\n }\n\n/* eslint-disable react/jsx-filename-extension */\n/**\n * Creates component wrapper that only wraps the component\n * when spec is OpenAPI 3.1.0. Otherwise, returns original\n * component with passed props.\n *\n * @param Component\n * @returns {function(*, *): function(*): *}\n */\nexport const createOnlyOAS31ComponentWrapper =\n (Component) => (Original, system) => (props) => {\n if (system.specSelectors.isOAS31()) {\n return (\n \n )\n }\n\n return \n }\n/* eslint-enable react/jsx-filename-extension */\n\n/**\n * Runs the fn replacement implementation when spec is OpenAPI 3.1.\n * Runs the fn original implementation otherwise.\n *\n * @param fn\n * @param system\n * @returns {{[p: string]: function(...[*]): *}}\n */\nexport const wrapOAS31Fn = (fn, system) => {\n const { fn: systemFn, specSelectors } = system\n\n return Object.fromEntries(\n Object.entries(fn).map(([name, newImpl]) => {\n const oriImpl = systemFn[name]\n const impl = (...args) =>\n specSelectors.isOAS31()\n ? newImpl(...args)\n : typeof oriImpl === \"function\"\n ? oriImpl(...args)\n : undefined\n\n return [name, impl]\n })\n )\n}\n","/**\n * @prettier\n */\nimport Webhooks from \"./components/webhooks\"\nimport License from \"./components/license\"\nimport Contact from \"./components/contact\"\nimport Info from \"./components/info\"\nimport JsonSchemaDialect from \"./components/json-schema-dialect\"\nimport VersionPragmaFilter from \"./components/version-pragma-filter\"\nimport Model from \"./components/model/model\"\nimport Models from \"./components/models/models\"\nimport LicenseWrapper from \"./wrap-components/license\"\nimport ContactWrapper from \"./wrap-components/contact\"\nimport InfoWrapper from \"./wrap-components/info\"\nimport ModelWrapper from \"./wrap-components/model\"\nimport ModelsWrapper from \"./wrap-components/models\"\nimport VersionPragmaFilterWrapper from \"./wrap-components/version-pragma-filter\"\nimport VersionStampWrapper from \"./wrap-components/version-stamp\"\nimport {\n isOAS31 as isOAS31Fn,\n createOnlyOAS31Selector as createOnlyOAS31SelectorFn,\n createSystemSelector as createSystemSelectorFn,\n} from \"./fn\"\nimport {\n license as selectLicense,\n contact as selectContact,\n webhooks as selectWebhooks,\n selectLicenseNameField,\n selectLicenseUrlField,\n selectLicenseIdentifierField,\n selectContactNameField,\n selectContactEmailField,\n selectContactUrlField,\n selectContactUrl,\n isOAS31 as selectIsOAS31,\n selectLicenseUrl,\n selectInfoTitleField,\n selectInfoSummaryField,\n selectInfoDescriptionField,\n selectInfoTermsOfServiceField,\n selectInfoTermsOfServiceUrl,\n selectExternalDocsDescriptionField,\n selectExternalDocsUrlField,\n selectExternalDocsUrl,\n selectWebhooksOperations,\n selectJsonSchemaDialectField,\n selectJsonSchemaDialectDefault,\n selectSchemas,\n} from \"./spec-extensions/selectors\"\nimport {\n isOAS3 as isOAS3SelectorWrapper,\n selectLicenseUrl as selectLicenseUrlWrapper,\n} from \"./spec-extensions/wrap-selectors\"\nimport { selectLicenseUrl as selectOAS31LicenseUrl } from \"./selectors\"\nimport JSONSchema202012KeywordExample from \"./json-schema-2020-12-extensions/components/keywords/Example\"\nimport JSONSchema202012KeywordXml from \"./json-schema-2020-12-extensions/components/keywords/Xml\"\nimport JSONSchema202012KeywordDiscriminator from \"./json-schema-2020-12-extensions/components/keywords/Discriminator/Discriminator\"\nimport JSONSchema202012KeywordExternalDocs from \"./json-schema-2020-12-extensions/components/keywords/ExternalDocs\"\nimport JSONSchema202012KeywordDescriptionWrapper from \"./json-schema-2020-12-extensions/wrap-components/keywords/Description\"\nimport JSONSchema202012KeywordDefaultWrapper from \"./json-schema-2020-12-extensions/wrap-components/keywords/Default\"\nimport JSONSchema202012KeywordPropertiesWrapper from \"./json-schema-2020-12-extensions/wrap-components/keywords/Properties\"\nimport afterLoad from \"./after-load\"\n\nconst OAS31Plugin = ({ fn }) => {\n const createSystemSelector = fn.createSystemSelector || createSystemSelectorFn\n const createOnlyOAS31Selector = fn.createOnlyOAS31Selector || createOnlyOAS31SelectorFn // prettier-ignore\n\n return {\n afterLoad,\n fn: {\n isOAS31: isOAS31Fn,\n createSystemSelector: createSystemSelectorFn,\n createOnlyOAS31Selector: createOnlyOAS31SelectorFn,\n },\n components: {\n Webhooks,\n JsonSchemaDialect,\n OAS31Info: Info,\n OAS31License: License,\n OAS31Contact: Contact,\n OAS31VersionPragmaFilter: VersionPragmaFilter,\n OAS31Model: Model,\n OAS31Models: Models,\n JSONSchema202012KeywordExample,\n JSONSchema202012KeywordXml,\n JSONSchema202012KeywordDiscriminator,\n JSONSchema202012KeywordExternalDocs,\n },\n wrapComponents: {\n InfoContainer: InfoWrapper,\n License: LicenseWrapper,\n Contact: ContactWrapper,\n VersionPragmaFilter: VersionPragmaFilterWrapper,\n VersionStamp: VersionStampWrapper,\n Model: ModelWrapper,\n Models: ModelsWrapper,\n JSONSchema202012KeywordDescription:\n JSONSchema202012KeywordDescriptionWrapper,\n JSONSchema202012KeywordDefault: JSONSchema202012KeywordDefaultWrapper,\n JSONSchema202012KeywordProperties:\n JSONSchema202012KeywordPropertiesWrapper,\n },\n statePlugins: {\n spec: {\n selectors: {\n isOAS31: createSystemSelector(selectIsOAS31),\n\n license: selectLicense,\n selectLicenseNameField,\n selectLicenseUrlField,\n selectLicenseIdentifierField: createOnlyOAS31Selector(selectLicenseIdentifierField), // prettier-ignore\n selectLicenseUrl: createSystemSelector(selectLicenseUrl),\n\n contact: selectContact,\n selectContactNameField,\n selectContactEmailField,\n selectContactUrlField,\n selectContactUrl: createSystemSelector(selectContactUrl),\n\n selectInfoTitleField,\n selectInfoSummaryField: createOnlyOAS31Selector(selectInfoSummaryField), // prettier-ignore\n selectInfoDescriptionField,\n selectInfoTermsOfServiceField,\n selectInfoTermsOfServiceUrl: createSystemSelector(selectInfoTermsOfServiceUrl), // prettier-ignore\n\n selectExternalDocsDescriptionField,\n selectExternalDocsUrlField,\n selectExternalDocsUrl: createSystemSelector(selectExternalDocsUrl),\n\n webhooks: createOnlyOAS31Selector(selectWebhooks),\n selectWebhooksOperations: createOnlyOAS31Selector(createSystemSelector(selectWebhooksOperations)), // prettier-ignore\n\n selectJsonSchemaDialectField,\n selectJsonSchemaDialectDefault,\n\n selectSchemas: createSystemSelector(selectSchemas),\n },\n wrapSelectors: {\n isOAS3: isOAS3SelectorWrapper,\n selectLicenseUrl: selectLicenseUrlWrapper,\n },\n },\n oas31: {\n selectors: {\n selectLicenseUrl: createOnlyOAS31Selector(createSystemSelector(selectOAS31LicenseUrl)), // prettier-ignore\n },\n },\n },\n }\n}\n\nexport default OAS31Plugin\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\n\nconst Description = ({ schema, getSystem }) => {\n if (!schema?.description) return null\n\n const { getComponent } = getSystem()\n const MarkDown = getComponent(\"Markdown\")\n\n return (\n
    \n
    \n \n
    \n
    \n )\n}\n\nDescription.propTypes = {\n schema: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired,\n getSystem: PropTypes.func.isRequired,\n}\n\nexport default Description\n","/**\n * @prettier\n */\nimport React, { useCallback, useState } from \"react\"\nimport PropTypes from \"prop-types\"\nimport classNames from \"classnames\"\n\nimport DiscriminatorMapping from \"./DiscriminatorMapping\"\n\nconst Discriminator = ({ schema, getSystem }) => {\n const discriminator = schema?.discriminator || {}\n const { fn, getComponent } = getSystem()\n const { useIsExpandedDeeply, useComponent } = fn.jsonSchema202012\n const isExpandedDeeply = useIsExpandedDeeply()\n const isExpandable = !!discriminator.mapping\n const [expanded, setExpanded] = useState(isExpandedDeeply)\n const [expandedDeeply, setExpandedDeeply] = useState(false)\n const Accordion = useComponent(\"Accordion\")\n const ExpandDeepButton = useComponent(\"ExpandDeepButton\")\n const JSONSchemaDeepExpansionContext = getComponent(\n \"JSONSchema202012DeepExpansionContext\"\n )()\n\n /**\n * Event handlers.\n */\n const handleExpansion = useCallback(() => {\n setExpanded((prev) => !prev)\n }, [])\n const handleExpansionDeep = useCallback((e, expandedDeepNew) => {\n setExpanded(expandedDeepNew)\n setExpandedDeeply(expandedDeepNew)\n }, [])\n\n /**\n * Rendering.\n */\n if (Object.keys(discriminator).length === 0) {\n return null\n }\n\n return (\n \n
    \n {isExpandable ? (\n <>\n \n \n Discriminator\n \n \n \n \n ) : (\n \n Discriminator\n \n )}\n\n {discriminator.propertyName && (\n \n {discriminator.propertyName}\n \n )}\n \n object\n \n \n {expanded && (\n
  • \n \n
  • \n )}\n \n
    \n
    \n )\n}\n\nDiscriminator.propTypes = {\n schema: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired,\n getSystem: PropTypes.func.isRequired,\n}\n\nexport default Discriminator\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\n\nconst DiscriminatorMapping = ({ discriminator }) => {\n const mapping = discriminator?.mapping || {}\n\n if (Object.keys(mapping).length === 0) {\n return null\n }\n\n return Object.entries(mapping).map(([key, value]) => (\n
    \n \n {key}\n \n \n {value}\n \n
    \n ))\n}\n\nDiscriminatorMapping.propTypes = {\n discriminator: PropTypes.shape({\n mapping: PropTypes.any,\n }),\n}\n\nDiscriminatorMapping.defaultProps = {\n mapping: undefined,\n}\n\nexport default DiscriminatorMapping\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\n\nconst Example = ({ schema, getSystem }) => {\n const { fn } = getSystem()\n const { hasKeyword, stringify } = fn.jsonSchema202012.useFn()\n\n if (!hasKeyword(schema, \"example\")) return null\n\n return (\n
    \n \n Example\n \n \n {stringify(schema.example)}\n \n
    \n )\n}\n\nExample.propTypes = {\n schema: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired,\n getSystem: PropTypes.func.isRequired,\n}\n\nexport default Example\n","/**\n * @prettier\n */\nimport React, { useCallback, useState } from \"react\"\nimport PropTypes from \"prop-types\"\nimport classNames from \"classnames\"\n\nimport { sanitizeUrl } from \"core/utils\"\n\nconst ExternalDocs = ({ schema, getSystem }) => {\n const externalDocs = schema?.externalDocs || {}\n const { fn, getComponent } = getSystem()\n const { useIsExpandedDeeply, useComponent } = fn.jsonSchema202012\n const isExpandedDeeply = useIsExpandedDeeply()\n const isExpandable = !!(externalDocs.description || externalDocs.url)\n const [expanded, setExpanded] = useState(isExpandedDeeply)\n const [expandedDeeply, setExpandedDeeply] = useState(false)\n const Accordion = useComponent(\"Accordion\")\n const ExpandDeepButton = useComponent(\"ExpandDeepButton\")\n const KeywordDescription = getComponent(\"JSONSchema202012KeywordDescription\")\n const Link = getComponent(\"Link\")\n const JSONSchemaDeepExpansionContext = getComponent(\n \"JSONSchema202012DeepExpansionContext\"\n )()\n\n /**\n * Event handlers.\n */\n const handleExpansion = useCallback(() => {\n setExpanded((prev) => !prev)\n }, [])\n const handleExpansionDeep = useCallback((e, expandedDeepNew) => {\n setExpanded(expandedDeepNew)\n setExpandedDeeply(expandedDeepNew)\n }, [])\n\n /**\n * Rendering.\n */\n if (Object.keys(externalDocs).length === 0) {\n return null\n }\n\n return (\n \n
    \n {isExpandable ? (\n <>\n \n \n External documentation\n \n \n \n \n ) : (\n \n External documentation\n \n )}\n \n object\n \n \n {expanded && (\n <>\n {externalDocs.description && (\n
  • \n \n
  • \n )}\n\n {externalDocs.url && (\n
  • \n
    \n \n url\n \n \n \n {externalDocs.url}\n \n \n
    \n
  • \n )}\n \n )}\n \n
    \n
    \n )\n}\n\nExternalDocs.propTypes = {\n schema: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired,\n getSystem: PropTypes.func.isRequired,\n}\n\nexport default ExternalDocs\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\nimport classNames from \"classnames\"\n\nconst Properties = ({ schema, getSystem }) => {\n const { fn } = getSystem()\n const { useComponent } = fn.jsonSchema202012\n const { getDependentRequired, getProperties } = fn.jsonSchema202012.useFn()\n const config = fn.jsonSchema202012.useConfig()\n const required = Array.isArray(schema?.required) ? schema.required : []\n const JSONSchema = useComponent(\"JSONSchema\")\n const properties = getProperties(schema, config)\n\n /**\n * Rendering.\n */\n if (Object.keys(properties).length === 0) {\n return null\n }\n\n return (\n
    \n
      \n {Object.entries(properties).map(([propertyName, propertySchema]) => {\n const isRequired = required.includes(propertyName)\n const dependentRequired = getDependentRequired(propertyName, schema)\n\n return (\n \n \n \n )\n })}\n
    \n
    \n )\n}\n\nProperties.propTypes = {\n schema: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired,\n getSystem: PropTypes.func.isRequired,\n}\n\nexport default Properties\n","/**\n * @prettier\n */\nimport React, { useCallback, useState } from \"react\"\nimport PropTypes from \"prop-types\"\nimport classNames from \"classnames\"\n\nconst Xml = ({ schema, getSystem }) => {\n const xml = schema?.xml || {}\n const { fn, getComponent } = getSystem()\n const { useIsExpandedDeeply, useComponent } = fn.jsonSchema202012\n const isExpandedDeeply = useIsExpandedDeeply()\n const isExpandable = !!(xml.name || xml.namespace || xml.prefix)\n const [expanded, setExpanded] = useState(isExpandedDeeply)\n const [expandedDeeply, setExpandedDeeply] = useState(false)\n const Accordion = useComponent(\"Accordion\")\n const ExpandDeepButton = useComponent(\"ExpandDeepButton\")\n const JSONSchemaDeepExpansionContext = getComponent(\n \"JSONSchema202012DeepExpansionContext\"\n )()\n\n /**\n * Event handlers.\n */\n const handleExpansion = useCallback(() => {\n setExpanded((prev) => !prev)\n }, [])\n const handleExpansionDeep = useCallback((e, expandedDeepNew) => {\n setExpanded(expandedDeepNew)\n setExpandedDeeply(expandedDeepNew)\n }, [])\n\n /**\n * Rendering.\n */\n if (Object.keys(xml).length === 0) {\n return null\n }\n\n return (\n \n
    \n {isExpandable ? (\n <>\n \n \n XML\n \n \n \n \n ) : (\n \n XML\n \n )}\n {xml.attribute === true && (\n \n attribute\n \n )}\n {xml.wrapped === true && (\n \n wrapped\n \n )}\n \n object\n \n \n {expanded && (\n <>\n {xml.name && (\n
  • \n
    \n \n name\n \n \n {xml.name}\n \n
    \n
  • \n )}\n\n {xml.namespace && (\n
  • \n
    \n \n namespace\n \n \n {xml.namespace}\n \n
    \n
  • \n )}\n\n {xml.prefix && (\n
  • \n
    \n \n prefix\n \n \n {xml.prefix}\n \n
    \n
  • \n )}\n \n )}\n \n
    \n
    \n )\n}\n\nXml.propTypes = {\n schema: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired,\n getSystem: PropTypes.func.isRequired,\n}\n\nexport default Xml\n","/**\n * @prettier\n */\nexport const makeIsExpandable = (original, getSystem) => {\n const { fn } = getSystem()\n\n if (typeof original !== \"function\") {\n return null\n }\n\n const { hasKeyword } = fn.jsonSchema202012\n\n return (schema) =>\n original(schema) ||\n hasKeyword(schema, \"example\") ||\n schema?.xml ||\n schema?.discriminator ||\n schema?.externalDocs\n}\n\nexport const getProperties = (\n schema,\n { includeReadOnly, includeWriteOnly }\n) => {\n // shortcut\n if (!schema?.properties) return {}\n\n const properties = Object.entries(schema.properties)\n const filteredProperties = properties.filter(([, value]) => {\n const isReadOnly = value?.readOnly === true\n const isWriteOnly = value?.writeOnly === true\n\n return (\n (!isReadOnly || includeReadOnly) && (!isWriteOnly || includeWriteOnly)\n )\n })\n\n return Object.fromEntries(filteredProperties)\n}\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport { createOnlyOAS31ComponentWrapper } from \"../../../fn\"\n\nconst DefaultWrapper = createOnlyOAS31ComponentWrapper(\n ({ schema, getSystem, originalComponent: KeywordDefault }) => {\n const { getComponent } = getSystem()\n const KeywordDiscriminator = getComponent(\n \"JSONSchema202012KeywordDiscriminator\"\n )\n const KeywordXml = getComponent(\"JSONSchema202012KeywordXml\")\n const KeywordExample = getComponent(\"JSONSchema202012KeywordExample\")\n const KeywordExternalDocs = getComponent(\n \"JSONSchema202012KeywordExternalDocs\"\n )\n\n return (\n <>\n \n \n \n \n \n \n )\n }\n)\n\nexport default DefaultWrapper\n","/**\n * @prettier\n */\nimport DescriptionKeyword from \"../../components/keywords/Description\"\nimport { createOnlyOAS31ComponentWrapper } from \"../../../fn\"\n\nconst DescriptionWrapper = createOnlyOAS31ComponentWrapper(DescriptionKeyword)\n\nexport default DescriptionWrapper\n","/**\n * @prettier\n */\nimport PropertiesKeyword from \"../../components/keywords/Properties\"\nimport { createOnlyOAS31ComponentWrapper } from \"../../../fn\"\n\nconst PropertiesWrapper = createOnlyOAS31ComponentWrapper(PropertiesKeyword)\n\nexport default PropertiesWrapper\n","/**\n * @prettier\n */\nimport { createSelector } from \"reselect\"\n\nimport { safeBuildUrl } from \"core/utils/url\"\n\nexport const selectLicenseUrl = createSelector(\n (state, system) => system.specSelectors.url(),\n (state, system) => system.oas3Selectors.selectedServer(),\n (state, system) => system.specSelectors.selectLicenseUrlField(),\n (state, system) => system.specSelectors.selectLicenseIdentifierField(),\n (specUrl, selectedServer, url, identifier) => {\n if (url) {\n return safeBuildUrl(url, specUrl, { selectedServer })\n }\n\n if (identifier) {\n return `https://spdx.org/licenses/${identifier}.html`\n }\n\n return undefined\n }\n)\n","/**\n * @prettier\n */\nimport { List, Map } from \"immutable\"\nimport { createSelector } from \"reselect\"\n\nimport { safeBuildUrl } from \"core/utils/url\"\nimport { isOAS31 as isOAS31Fn } from \"../fn\"\n\nconst map = Map()\n\nexport const isOAS31 = createSelector(\n (state, system) => system.specSelectors.specJson(),\n isOAS31Fn\n)\n\nexport const webhooks = () => (system) => {\n return system.specSelectors.specJson().get(\"webhooks\", map)\n}\n\n/**\n * `specResolvedSubtree` selector is needed as input selector,\n * so that we regenerate the selected result whenever the lazy\n * resolution happens.\n */\nexport const selectWebhooksOperations = createSelector(\n (state, system) => system.specSelectors.webhooks(),\n (state, system) => system.specSelectors.validOperationMethods(),\n (state, system) => system.specSelectors.specResolvedSubtree([\"webhooks\"]),\n (webhooks, validOperationMethods) => {\n if (!Map.isMap(webhooks)) return {}\n\n return webhooks\n .reduce((allOperations, pathItem, pathItemName) => {\n if (!Map.isMap(pathItem)) return allOperations\n\n const pathItemOperations = pathItem\n .entrySeq()\n .filter(([key]) => validOperationMethods.includes(key))\n .map(([method, operation]) => ({\n operation: Map({ operation }),\n method,\n path: pathItemName,\n specPath: List([\"webhooks\", pathItemName, method]),\n }))\n\n return allOperations.concat(pathItemOperations)\n }, List())\n .groupBy((operationDTO) => operationDTO.path)\n .map((operations) => operations.toArray())\n .toObject()\n }\n)\n\nexport const license = () => (system) => {\n return system.specSelectors.info().get(\"license\", map)\n}\n\nexport const selectLicenseNameField = () => (system) => {\n return system.specSelectors.license().get(\"name\", \"License\")\n}\n\nexport const selectLicenseUrlField = () => (system) => {\n return system.specSelectors.license().get(\"url\")\n}\n\nexport const selectLicenseUrl = createSelector(\n (state, system) => system.specSelectors.url(),\n (state, system) => system.oas3Selectors.selectedServer(),\n (state, system) => system.specSelectors.selectLicenseUrlField(),\n (specUrl, selectedServer, url) => {\n if (url) {\n return safeBuildUrl(url, specUrl, { selectedServer })\n }\n\n return undefined\n }\n)\n\nexport const selectLicenseIdentifierField = () => (system) => {\n return system.specSelectors.license().get(\"identifier\")\n}\n\nexport const contact = () => (system) => {\n return system.specSelectors.info().get(\"contact\", map)\n}\n\nexport const selectContactNameField = () => (system) => {\n return system.specSelectors.contact().get(\"name\", \"the developer\")\n}\n\nexport const selectContactEmailField = () => (system) => {\n return system.specSelectors.contact().get(\"email\")\n}\n\nexport const selectContactUrlField = () => (system) => {\n return system.specSelectors.contact().get(\"url\")\n}\n\nexport const selectContactUrl = createSelector(\n (state, system) => system.specSelectors.url(),\n (state, system) => system.oas3Selectors.selectedServer(),\n (state, system) => system.specSelectors.selectContactUrlField(),\n (specUrl, selectedServer, url) => {\n if (url) {\n return safeBuildUrl(url, specUrl, { selectedServer })\n }\n\n return undefined\n }\n)\n\nexport const selectInfoTitleField = () => (system) => {\n return system.specSelectors.info().get(\"title\")\n}\n\nexport const selectInfoSummaryField = () => (system) => {\n return system.specSelectors.info().get(\"summary\")\n}\n\nexport const selectInfoDescriptionField = () => (system) => {\n return system.specSelectors.info().get(\"description\")\n}\n\nexport const selectInfoTermsOfServiceField = () => (system) => {\n return system.specSelectors.info().get(\"termsOfService\")\n}\n\nexport const selectInfoTermsOfServiceUrl = createSelector(\n (state, system) => system.specSelectors.url(),\n (state, system) => system.oas3Selectors.selectedServer(),\n (state, system) => system.specSelectors.selectInfoTermsOfServiceField(),\n (specUrl, selectedServer, termsOfService) => {\n if (termsOfService) {\n return safeBuildUrl(termsOfService, specUrl, { selectedServer })\n }\n\n return undefined\n }\n)\n\nexport const selectExternalDocsDescriptionField = () => (system) => {\n return system.specSelectors.externalDocs().get(\"description\")\n}\n\nexport const selectExternalDocsUrlField = () => (system) => {\n return system.specSelectors.externalDocs().get(\"url\")\n}\n\nexport const selectExternalDocsUrl = createSelector(\n (state, system) => system.specSelectors.url(),\n (state, system) => system.oas3Selectors.selectedServer(),\n (state, system) => system.specSelectors.selectExternalDocsUrlField(),\n (specUrl, selectedServer, url) => {\n if (url) {\n return safeBuildUrl(url, specUrl, { selectedServer })\n }\n\n return undefined\n }\n)\n\nexport const selectJsonSchemaDialectField = () => (system) => {\n return system.specSelectors.specJson().get(\"jsonSchemaDialect\")\n}\n\nexport const selectJsonSchemaDialectDefault = () =>\n \"https://spec.openapis.org/oas/3.1/dialect/base\"\n\nexport const selectSchemas = createSelector(\n (state, system) => system.specSelectors.definitions(),\n (state, system) =>\n system.specSelectors.specResolvedSubtree([\"components\", \"schemas\"]),\n\n (rawSchemas, resolvedSchemas) => {\n if (!Map.isMap(rawSchemas)) return {}\n if (!Map.isMap(resolvedSchemas)) return rawSchemas.toJS()\n\n return Object.entries(rawSchemas.toJS()).reduce(\n (acc, [schemaName, rawSchema]) => {\n const resolvedSchema = resolvedSchemas.get(schemaName)\n acc[schemaName] = resolvedSchema?.toJS() || rawSchema\n return acc\n },\n {}\n )\n }\n)\n","/**\n * @prettier\n */\n\nimport { createOnlyOAS31SelectorWrapper } from \"../fn\"\n\nexport const isOAS3 =\n (oriSelector, system) =>\n (state, ...args) => {\n const isOAS31 = system.specSelectors.isOAS31()\n return isOAS31 || oriSelector(...args)\n }\n\nexport const selectLicenseUrl = createOnlyOAS31SelectorWrapper(\n () => (oriSelector, system) => {\n return system.oas31Selectors.selectLicenseUrl()\n }\n)\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { createOnlyOAS31ComponentWrapper } from \"../fn\"\n\nconst ContactWrapper = createOnlyOAS31ComponentWrapper(({ getSystem }) => {\n const system = getSystem()\n const OAS31Contact = system.getComponent(\"OAS31Contact\", true)\n\n return \n})\n\nexport default ContactWrapper\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { createOnlyOAS31ComponentWrapper } from \"../fn\"\n\nconst InfoWrapper = createOnlyOAS31ComponentWrapper(({ getSystem }) => {\n const system = getSystem()\n const OAS31Info = system.getComponent(\"OAS31Info\", true)\n\n return \n})\n\nexport default InfoWrapper\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { createOnlyOAS31ComponentWrapper } from \"../fn\"\n\nconst LicenseWrapper = createOnlyOAS31ComponentWrapper(({ getSystem }) => {\n const system = getSystem()\n const OAS31License = system.getComponent(\"OAS31License\", true)\n\n return \n})\n\nexport default LicenseWrapper\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { createOnlyOAS31ComponentWrapper } from \"../fn\"\nimport {\n makeIsExpandable,\n getProperties,\n} from \"../json-schema-2020-12-extensions/fn\"\n\nconst ModelWrapper = createOnlyOAS31ComponentWrapper(\n ({ getSystem, ...props }) => {\n const system = getSystem()\n const { getComponent, fn, getConfigs } = system\n const configs = getConfigs()\n\n const Model = getComponent(\"OAS31Model\")\n const JSONSchema = getComponent(\"JSONSchema202012\")\n const Keyword$schema = getComponent(\"JSONSchema202012Keyword$schema\")\n const Keyword$vocabulary = getComponent(\n \"JSONSchema202012Keyword$vocabulary\"\n )\n const Keyword$id = getComponent(\"JSONSchema202012Keyword$id\")\n const Keyword$anchor = getComponent(\"JSONSchema202012Keyword$anchor\")\n const Keyword$dynamicAnchor = getComponent(\n \"JSONSchema202012Keyword$dynamicAnchor\"\n )\n const Keyword$ref = getComponent(\"JSONSchema202012Keyword$ref\")\n const Keyword$dynamicRef = getComponent(\n \"JSONSchema202012Keyword$dynamicRef\"\n )\n const Keyword$defs = getComponent(\"JSONSchema202012Keyword$defs\")\n const Keyword$comment = getComponent(\"JSONSchema202012Keyword$comment\")\n const KeywordAllOf = getComponent(\"JSONSchema202012KeywordAllOf\")\n const KeywordAnyOf = getComponent(\"JSONSchema202012KeywordAnyOf\")\n const KeywordOneOf = getComponent(\"JSONSchema202012KeywordOneOf\")\n const KeywordNot = getComponent(\"JSONSchema202012KeywordNot\")\n const KeywordIf = getComponent(\"JSONSchema202012KeywordIf\")\n const KeywordThen = getComponent(\"JSONSchema202012KeywordThen\")\n const KeywordElse = getComponent(\"JSONSchema202012KeywordElse\")\n const KeywordDependentSchemas = getComponent(\n \"JSONSchema202012KeywordDependentSchemas\"\n )\n const KeywordPrefixItems = getComponent(\n \"JSONSchema202012KeywordPrefixItems\"\n )\n const KeywordItems = getComponent(\"JSONSchema202012KeywordItems\")\n const KeywordContains = getComponent(\"JSONSchema202012KeywordContains\")\n const KeywordProperties = getComponent(\"JSONSchema202012KeywordProperties\")\n const KeywordPatternProperties = getComponent(\n \"JSONSchema202012KeywordPatternProperties\"\n )\n const KeywordAdditionalProperties = getComponent(\n \"JSONSchema202012KeywordAdditionalProperties\"\n )\n const KeywordPropertyNames = getComponent(\n \"JSONSchema202012KeywordPropertyNames\"\n )\n const KeywordUnevaluatedItems = getComponent(\n \"JSONSchema202012KeywordUnevaluatedItems\"\n )\n const KeywordUnevaluatedProperties = getComponent(\n \"JSONSchema202012KeywordUnevaluatedProperties\"\n )\n const KeywordType = getComponent(\"JSONSchema202012KeywordType\")\n const KeywordEnum = getComponent(\"JSONSchema202012KeywordEnum\")\n const KeywordConst = getComponent(\"JSONSchema202012KeywordConst\")\n const KeywordConstraint = getComponent(\"JSONSchema202012KeywordConstraint\")\n const KeywordDependentRequired = getComponent(\n \"JSONSchema202012KeywordDependentRequired\"\n )\n const KeywordContentSchema = getComponent(\n \"JSONSchema202012KeywordContentSchema\"\n )\n const KeywordTitle = getComponent(\"JSONSchema202012KeywordTitle\")\n const KeywordDescription = getComponent(\n \"JSONSchema202012KeywordDescription\"\n )\n const KeywordDefault = getComponent(\"JSONSchema202012KeywordDefault\")\n const KeywordDeprecated = getComponent(\"JSONSchema202012KeywordDeprecated\")\n const KeywordReadOnly = getComponent(\"JSONSchema202012KeywordReadOnly\")\n const KeywordWriteOnly = getComponent(\"JSONSchema202012KeywordWriteOnly\")\n const Accordion = getComponent(\"JSONSchema202012Accordion\")\n const ExpandDeepButton = getComponent(\"JSONSchema202012ExpandDeepButton\")\n const ChevronRightIcon = getComponent(\"JSONSchema202012ChevronRightIcon\")\n const withSchemaContext = getComponent(\"withJSONSchema202012Context\")\n\n const ModelWithJSONSchemaContext = withSchemaContext(Model, {\n config: {\n default$schema: \"https://spec.openapis.org/oas/3.1/dialect/base\",\n defaultExpandedLevels: configs.defaultModelExpandDepth,\n includeReadOnly: Boolean(props.includeReadOnly),\n includeWriteOnly: Boolean(props.includeWriteOnly),\n },\n components: {\n JSONSchema,\n Keyword$schema,\n Keyword$vocabulary,\n Keyword$id,\n Keyword$anchor,\n Keyword$dynamicAnchor,\n Keyword$ref,\n Keyword$dynamicRef,\n Keyword$defs,\n Keyword$comment,\n KeywordAllOf,\n KeywordAnyOf,\n KeywordOneOf,\n KeywordNot,\n KeywordIf,\n KeywordThen,\n KeywordElse,\n KeywordDependentSchemas,\n KeywordPrefixItems,\n KeywordItems,\n KeywordContains,\n KeywordProperties,\n KeywordPatternProperties,\n KeywordAdditionalProperties,\n KeywordPropertyNames,\n KeywordUnevaluatedItems,\n KeywordUnevaluatedProperties,\n KeywordType,\n KeywordEnum,\n KeywordConst,\n KeywordConstraint,\n KeywordDependentRequired,\n KeywordContentSchema,\n KeywordTitle,\n KeywordDescription,\n KeywordDefault,\n KeywordDeprecated,\n KeywordReadOnly,\n KeywordWriteOnly,\n Accordion,\n ExpandDeepButton,\n ChevronRightIcon,\n },\n fn: {\n upperFirst: fn.upperFirst,\n isExpandable: makeIsExpandable(\n fn.jsonSchema202012.isExpandable,\n getSystem\n ),\n getProperties,\n },\n })\n\n return \n }\n)\n\nexport default ModelWrapper\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { createOnlyOAS31ComponentWrapper } from \"../fn\"\n\nconst ModelsWrapper = createOnlyOAS31ComponentWrapper(({ getSystem }) => {\n const { getComponent, fn, getConfigs } = getSystem()\n const configs = getConfigs()\n\n if (ModelsWrapper.ModelsWithJSONSchemaContext) {\n return \n }\n\n const Models = getComponent(\"OAS31Models\", true)\n const JSONSchema = getComponent(\"JSONSchema202012\")\n const Keyword$schema = getComponent(\"JSONSchema202012Keyword$schema\")\n const Keyword$vocabulary = getComponent(\"JSONSchema202012Keyword$vocabulary\")\n const Keyword$id = getComponent(\"JSONSchema202012Keyword$id\")\n const Keyword$anchor = getComponent(\"JSONSchema202012Keyword$anchor\")\n const Keyword$dynamicAnchor = getComponent(\n \"JSONSchema202012Keyword$dynamicAnchor\"\n )\n const Keyword$ref = getComponent(\"JSONSchema202012Keyword$ref\")\n const Keyword$dynamicRef = getComponent(\"JSONSchema202012Keyword$dynamicRef\")\n const Keyword$defs = getComponent(\"JSONSchema202012Keyword$defs\")\n const Keyword$comment = getComponent(\"JSONSchema202012Keyword$comment\")\n const KeywordAllOf = getComponent(\"JSONSchema202012KeywordAllOf\")\n const KeywordAnyOf = getComponent(\"JSONSchema202012KeywordAnyOf\")\n const KeywordOneOf = getComponent(\"JSONSchema202012KeywordOneOf\")\n const KeywordNot = getComponent(\"JSONSchema202012KeywordNot\")\n const KeywordIf = getComponent(\"JSONSchema202012KeywordIf\")\n const KeywordThen = getComponent(\"JSONSchema202012KeywordThen\")\n const KeywordElse = getComponent(\"JSONSchema202012KeywordElse\")\n const KeywordDependentSchemas = getComponent(\n \"JSONSchema202012KeywordDependentSchemas\"\n )\n const KeywordPrefixItems = getComponent(\"JSONSchema202012KeywordPrefixItems\")\n const KeywordItems = getComponent(\"JSONSchema202012KeywordItems\")\n const KeywordContains = getComponent(\"JSONSchema202012KeywordContains\")\n const KeywordProperties = getComponent(\"JSONSchema202012KeywordProperties\")\n const KeywordPatternProperties = getComponent(\n \"JSONSchema202012KeywordPatternProperties\"\n )\n const KeywordAdditionalProperties = getComponent(\n \"JSONSchema202012KeywordAdditionalProperties\"\n )\n const KeywordPropertyNames = getComponent(\n \"JSONSchema202012KeywordPropertyNames\"\n )\n const KeywordUnevaluatedItems = getComponent(\n \"JSONSchema202012KeywordUnevaluatedItems\"\n )\n const KeywordUnevaluatedProperties = getComponent(\n \"JSONSchema202012KeywordUnevaluatedProperties\"\n )\n const KeywordType = getComponent(\"JSONSchema202012KeywordType\")\n const KeywordEnum = getComponent(\"JSONSchema202012KeywordEnum\")\n const KeywordConst = getComponent(\"JSONSchema202012KeywordConst\")\n const KeywordConstraint = getComponent(\"JSONSchema202012KeywordConstraint\")\n const KeywordDependentRequired = getComponent(\n \"JSONSchema202012KeywordDependentRequired\"\n )\n const KeywordContentSchema = getComponent(\n \"JSONSchema202012KeywordContentSchema\"\n )\n const KeywordTitle = getComponent(\"JSONSchema202012KeywordTitle\")\n const KeywordDescription = getComponent(\"JSONSchema202012KeywordDescription\")\n const KeywordDefault = getComponent(\"JSONSchema202012KeywordDefault\")\n const KeywordDeprecated = getComponent(\"JSONSchema202012KeywordDeprecated\")\n const KeywordReadOnly = getComponent(\"JSONSchema202012KeywordReadOnly\")\n const KeywordWriteOnly = getComponent(\"JSONSchema202012KeywordWriteOnly\")\n const Accordion = getComponent(\"JSONSchema202012Accordion\")\n const ExpandDeepButton = getComponent(\"JSONSchema202012ExpandDeepButton\")\n const ChevronRightIcon = getComponent(\"JSONSchema202012ChevronRightIcon\")\n const withSchemaContext = getComponent(\"withJSONSchema202012Context\")\n\n // we cache the HOC as recreating it with every re-render is quite expensive\n ModelsWrapper.ModelsWithJSONSchemaContext = withSchemaContext(Models, {\n config: {\n default$schema: \"https://spec.openapis.org/oas/3.1/dialect/base\",\n defaultExpandedLevels: configs.defaultModelsExpandDepth - 1,\n includeReadOnly: true,\n includeWriteOnly: true,\n },\n components: {\n JSONSchema,\n Keyword$schema,\n Keyword$vocabulary,\n Keyword$id,\n Keyword$anchor,\n Keyword$dynamicAnchor,\n Keyword$ref,\n Keyword$dynamicRef,\n Keyword$defs,\n Keyword$comment,\n KeywordAllOf,\n KeywordAnyOf,\n KeywordOneOf,\n KeywordNot,\n KeywordIf,\n KeywordThen,\n KeywordElse,\n KeywordDependentSchemas,\n KeywordPrefixItems,\n KeywordItems,\n KeywordContains,\n KeywordProperties,\n KeywordPatternProperties,\n KeywordAdditionalProperties,\n KeywordPropertyNames,\n KeywordUnevaluatedItems,\n KeywordUnevaluatedProperties,\n KeywordType,\n KeywordEnum,\n KeywordConst,\n KeywordConstraint,\n KeywordDependentRequired,\n KeywordContentSchema,\n KeywordTitle,\n KeywordDescription,\n KeywordDefault,\n KeywordDeprecated,\n KeywordReadOnly,\n KeywordWriteOnly,\n Accordion,\n ExpandDeepButton,\n ChevronRightIcon,\n },\n fn: {\n upperFirst: fn.upperFirst,\n isExpandable: fn.jsonSchema202012.isExpandable,\n getProperties: fn.jsonSchema202012.getProperties,\n },\n })\n\n return \n})\n\nModelsWrapper.ModelsWithJSONSchemaContext = null\n\nexport default ModelsWrapper\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nconst VersionPragmaFilterWrapper = (Original, system) => (props) => {\n const isOAS31 = system.specSelectors.isOAS31()\n\n const OAS31VersionPragmaFilter = system.getComponent(\n \"OAS31VersionPragmaFilter\"\n )\n\n return \n}\n\nexport default VersionPragmaFilterWrapper\n","/**\n * @prettier\n */\nimport React from \"react\"\n\nimport { createOnlyOAS31ComponentWrapper } from \"../fn\"\n\nconst VersionStampWrapper = createOnlyOAS31ComponentWrapper(\n ({ originalComponent: Original, ...restProps }) => (\n \n \n \n
    OAS 3.1
    \n
    \n
    \n )\n)\n\nexport default VersionStampWrapper\n","let engaged = false\n\nexport default function() {\n\n return {\n statePlugins: {\n spec: {\n wrapActions: {\n updateSpec: (ori) => (...args) => {\n engaged = true\n return ori(...args)\n },\n updateJsonSpec: (ori, system) => (...args) => {\n const cb = system.getConfigs().onComplete\n if(engaged && typeof cb === \"function\") {\n // call `onComplete` on next tick, which allows React to\n // reconcile the DOM before we notify the user\n setTimeout(cb, 0)\n engaged = false\n }\n\n return ori(...args)\n }\n }\n }\n }\n }\n}\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_repeat_18ab8b74__[\"default\"] });","import win from \"../../window\"\nimport { Map } from \"immutable\"\n\n/**\n * if duplicate key name existed from FormData entries,\n * we mutated the key name by appending a hashIdx\n * @param {String} k - possibly mutated key name\n * @return {String} - src key name\n */\nconst extractKey = (k) => {\n const hashIdx = \"_**[]\"\n if (k.indexOf(hashIdx) < 0) {\n return k\n }\n return k.split(hashIdx)[0].trim()\n}\n\nconst escapeShell = (str) => {\n if (str === \"-d \") {\n return str\n }\n // eslint-disable-next-line no-useless-escape\n if (!/^[_\\/-]/g.test(str))\n return (\"'\" + str\n .replace(/'/g, \"'\\\\''\") + \"'\")\n else\n return str\n}\n\nconst escapeCMD = (str) => {\n str = str\n .replace(/\\^/g, \"^^\")\n .replace(/\\\\\"/g, \"\\\\\\\\\\\"\")\n .replace(/\"/g, \"\\\"\\\"\")\n .replace(/\\n/g, \"^\\n\")\n if (str === \"-d \") {\n return str\n .replace(/-d /g, \"-d ^\\n\")\n }\n // eslint-disable-next-line no-useless-escape\n if (!/^[_\\/-]/g.test(str))\n return \"\\\"\" + str + \"\\\"\"\n else\n return str\n}\n\nconst escapePowershell = (str) => {\n if (str === \"-d \") {\n return str\n }\n if (/\\n/.test(str)) {\n return \"@\\\"\\n\" + str.replace(/\"/g, \"\\\\\\\"\").replace(/`/g, \"``\").replace(/\\$/, \"`$\") + \"\\n\\\"@\"\n }\n // eslint-disable-next-line no-useless-escape\n if (!/^[_\\/-]/g.test(str))\n return \"'\" + str\n .replace(/\"/g, \"\\\"\\\"\")\n .replace(/'/g, \"''\") + \"'\"\n else\n return str\n}\n\nfunction getStringBodyOfMap(request) {\n let curlifyToJoin = []\n for (let [k, v] of request.get(\"body\").entrySeq()) {\n let extractedKey = extractKey(k)\n if (v instanceof win.File) {\n curlifyToJoin.push(` \"${extractedKey}\": {\\n \"name\": \"${v.name}\"${v.type ? `,\\n \"type\": \"${v.type}\"` : \"\"}\\n }`)\n } else {\n curlifyToJoin.push(` \"${extractedKey}\": ${JSON.stringify(v, null, 2).replace(/(\\r\\n|\\r|\\n)/g, \"\\n \")}`)\n }\n }\n return `{\\n${curlifyToJoin.join(\",\\n\")}\\n}`\n}\n\nconst curlify = (request, escape, newLine, ext = \"\") => {\n let isMultipartFormDataRequest = false\n let curlified = \"\"\n const addWords = (...args) => curlified += \" \" + args.map(escape).join(\" \")\n const addWordsWithoutLeadingSpace = (...args) => curlified += args.map(escape).join(\" \")\n const addNewLine = () => curlified += ` ${newLine}`\n const addIndent = (level = 1) => curlified += \" \".repeat(level)\n let headers = request.get(\"headers\")\n curlified += \"curl\" + ext\n\n if (request.has(\"curlOptions\")) {\n addWords(...request.get(\"curlOptions\"))\n }\n\n addWords(\"-X\", request.get(\"method\"))\n\n addNewLine()\n addIndent()\n addWordsWithoutLeadingSpace(`${request.get(\"url\")}`)\n\n if (headers && headers.size) {\n for (let p of request.get(\"headers\").entries()) {\n addNewLine()\n addIndent()\n let [h, v] = p\n addWordsWithoutLeadingSpace(\"-H\", `${h}: ${v}`)\n isMultipartFormDataRequest = isMultipartFormDataRequest || /^content-type$/i.test(h) && /^multipart\\/form-data$/i.test(v)\n }\n }\n\n const body = request.get(\"body\")\n if (body) {\n if (isMultipartFormDataRequest && [\"POST\", \"PUT\", \"PATCH\"].includes(request.get(\"method\"))) {\n for (let [k, v] of body.entrySeq()) {\n let extractedKey = extractKey(k)\n addNewLine()\n addIndent()\n addWordsWithoutLeadingSpace(\"-F\")\n if (v instanceof win.File) {\n addWords(`${extractedKey}=@${v.name}${v.type ? `;type=${v.type}` : \"\"}`)\n } else {\n addWords(`${extractedKey}=${v}`)\n }\n }\n } else if(body instanceof win.File) {\n addNewLine()\n addIndent()\n addWordsWithoutLeadingSpace(`--data-binary '@${body.name}'`)\n } else {\n addNewLine()\n addIndent()\n addWordsWithoutLeadingSpace(\"-d \")\n let reqBody = body\n if (!Map.isMap(reqBody)) {\n if (typeof reqBody !== \"string\") {\n reqBody = JSON.stringify(reqBody)\n }\n addWordsWithoutLeadingSpace(reqBody)\n } else {\n addWordsWithoutLeadingSpace(getStringBodyOfMap(request))\n }\n }\n } else if (!body && request.get(\"method\") === \"POST\") {\n addNewLine()\n addIndent()\n addWordsWithoutLeadingSpace(\"-d ''\")\n }\n\n return curlified\n}\n\n// eslint-disable-next-line camelcase\nexport const requestSnippetGenerator_curl_powershell = (request) => {\n return curlify(request, escapePowershell, \"`\\n\", \".exe\")\n}\n\n// eslint-disable-next-line camelcase\nexport const requestSnippetGenerator_curl_bash = (request) => {\n return curlify(request, escapeShell, \"\\\\\\n\")\n}\n\n// eslint-disable-next-line camelcase\nexport const requestSnippetGenerator_curl_cmd = (request) => {\n return curlify(request, escapeCMD, \"^\\n\")\n}\n","import * as fn from \"./fn\"\nimport * as selectors from \"./selectors\"\nimport RequestSnippets from \"./request-snippets\"\nexport default () => {\n return {\n components: {\n RequestSnippets\n },\n fn,\n statePlugins: {\n requestSnippets: {\n selectors\n }\n }\n }\n}\n","import React, { useRef, useEffect, useState } from \"react\"\nimport PropTypes from \"prop-types\"\nimport get from \"lodash/get\"\nimport isFunction from \"lodash/isFunction\"\nimport { CopyToClipboard } from \"react-copy-to-clipboard\"\nimport { SyntaxHighlighter, getStyle } from \"core/syntax-highlighting\"\n\nconst style = {\n cursor: \"pointer\",\n lineHeight: 1,\n display: \"inline-flex\",\n backgroundColor: \"rgb(250, 250, 250)\",\n paddingBottom: \"0\",\n paddingTop: \"0\",\n border: \"1px solid rgb(51, 51, 51)\",\n borderRadius: \"4px 4px 0 0\",\n boxShadow: \"none\",\n borderBottom: \"none\"\n}\n\nconst activeStyle = {\n cursor: \"pointer\",\n lineHeight: 1,\n display: \"inline-flex\",\n backgroundColor: \"rgb(51, 51, 51)\",\n boxShadow: \"none\",\n border: \"1px solid rgb(51, 51, 51)\",\n paddingBottom: \"0\",\n paddingTop: \"0\",\n borderRadius: \"4px 4px 0 0\",\n marginTop: \"-5px\",\n marginRight: \"-5px\",\n marginLeft: \"-5px\",\n zIndex: \"9999\",\n borderBottom: \"none\"\n}\n\nconst RequestSnippets = ({ request, requestSnippetsSelectors, getConfigs }) => {\n const config = isFunction(getConfigs) ? getConfigs() : null\n const canSyntaxHighlight = get(config, \"syntaxHighlight\") !== false && get(config, \"syntaxHighlight.activated\", true)\n const rootRef = useRef(null)\n\n const [activeLanguage, setActiveLanguage] = useState(requestSnippetsSelectors.getSnippetGenerators()?.keySeq().first())\n const [isExpanded, setIsExpanded] = useState(requestSnippetsSelectors?.getDefaultExpanded())\n useEffect(() => {\n const doIt = () => {\n\n }\n doIt()\n }, [])\n useEffect(() => {\n const childNodes = Array\n .from(rootRef.current.childNodes)\n .filter(node => !!node.nodeType && node.classList?.contains(\"curl-command\"))\n // eslint-disable-next-line no-use-before-define\n childNodes.forEach(node => node.addEventListener(\"mousewheel\", handlePreventYScrollingBeyondElement, { passive: false }))\n\n return () => {\n // eslint-disable-next-line no-use-before-define\n childNodes.forEach(node => node.removeEventListener(\"mousewheel\", handlePreventYScrollingBeyondElement))\n }\n }, [request])\n\n const snippetGenerators = requestSnippetsSelectors.getSnippetGenerators()\n const activeGenerator = snippetGenerators.get(activeLanguage)\n const snippet = activeGenerator.get(\"fn\")(request)\n\n const handleGenChange = (key) => {\n const needsChange = activeLanguage !== key\n if (needsChange) {\n setActiveLanguage(key)\n }\n }\n\n const handleSetIsExpanded = () => {\n setIsExpanded(!isExpanded)\n }\n\n const handleGetBtnStyle = (key) => {\n if (key === activeLanguage) {\n return activeStyle\n }\n return style\n }\n\n const handlePreventYScrollingBeyondElement = (e) => {\n const { target, deltaY } = e\n const { scrollHeight: contentHeight, offsetHeight: visibleHeight, scrollTop } = target\n const scrollOffset = visibleHeight + scrollTop\n const isElementScrollable = contentHeight > visibleHeight\n const isScrollingPastTop = scrollTop === 0 && deltaY < 0\n const isScrollingPastBottom = scrollOffset >= contentHeight && deltaY > 0\n\n if (isElementScrollable && (isScrollingPastTop || isScrollingPastBottom)) {\n e.preventDefault()\n }\n }\n\n const SnippetComponent = canSyntaxHighlight\n ? \n {snippet}\n \n :\n \n\n return (\n
    \n
    \n handleSetIsExpanded()}\n style={{ cursor: \"pointer\" }}\n >Snippets\n handleSetIsExpanded()}\n style={{ border: \"none\", background: \"none\" }}\n title={isExpanded ? \"Collapse operation\" : \"Expand operation\"}\n >\n \n \n \n \n
    \n {\n isExpanded &&
    \n
    \n {\n snippetGenerators.entrySeq().map(([key, gen]) => {\n return (
    handleGenChange(key)}>\n

    {gen.get(\"title\")}

    \n
    )\n })\n }\n
    \n
    \n \n
    \n
    \n {SnippetComponent}\n
    \n
    \n }\n
    \n ) \n}\n\nRequestSnippets.propTypes = {\n request: PropTypes.object.isRequired,\n requestSnippetsSelectors: PropTypes.object.isRequired,\n getConfigs: PropTypes.object.isRequired,\n requestSnippetsActions: PropTypes.object,\n}\n\nexport default RequestSnippets\n","import { createSelector } from \"reselect\"\nimport { Map } from \"immutable\"\n\nconst state = state => state || Map()\n\nexport const getGenerators = createSelector(\n state,\n state => {\n const languageKeys = state\n .get(\"languages\")\n const generators = state\n .get(\"generators\", Map())\n if(!languageKeys || languageKeys.isEmpty()) {\n return generators\n }\n return generators\n .filter((v, key) => languageKeys.includes(key))\n }\n)\n\nexport const getSnippetGenerators = (state) => ({ fn }) => {\n const getGenFn = (key) => fn[`requestSnippetGenerator_${key}`]\n return getGenerators(state)\n .map((gen, key) => {\n const genFn = getGenFn(key)\n if(typeof genFn !== \"function\") {\n return null\n }\n\n return gen.set(\"fn\", genFn)\n })\n .filter(v => v)\n}\n\nexport const getActiveLanguage = createSelector(\n state,\n state => state\n .get(\"activeLanguage\")\n)\n\nexport const getDefaultExpanded = createSelector(\n state,\n state => state\n .get(\"defaultExpanded\")\n)\n","import PropTypes from \"prop-types\"\nimport React, { Component } from \"react\"\n\nimport { componentDidCatch } from \"../fn\"\nimport Fallback from \"./fallback\"\n\nexport class ErrorBoundary extends Component {\n static getDerivedStateFromError(error) {\n return { hasError: true, error }\n }\n\n constructor(...args) {\n super(...args)\n this.state = { hasError: false, error: null }\n }\n\n componentDidCatch(error, errorInfo) {\n this.props.fn.componentDidCatch(error, errorInfo)\n }\n\n render() {\n const { getComponent, targetName, children } = this.props\n\n if (this.state.hasError) {\n const FallbackComponent = getComponent(\"Fallback\")\n return \n }\n\n return children\n }\n}\nErrorBoundary.propTypes = {\n targetName: PropTypes.string,\n getComponent: PropTypes.func,\n fn: PropTypes.object,\n children: PropTypes.oneOfType([\n PropTypes.arrayOf(PropTypes.node),\n PropTypes.node,\n ])\n}\nErrorBoundary.defaultProps = {\n targetName: \"this component\",\n getComponent: () => Fallback,\n fn: {\n componentDidCatch,\n },\n children: null,\n}\n\nexport default ErrorBoundary\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nconst Fallback = ({ name }) => (\n
    \n ๐Ÿ˜ฑ Could not render { name === \"t\" ? \"this component\" : name }, see the console.\n
    \n)\nFallback.propTypes = {\n name: PropTypes.string.isRequired,\n}\n\nexport default Fallback\n","import React, { Component } from \"react\"\n\nexport const componentDidCatch = console.error\n\nconst isClassComponent = component => component.prototype && component.prototype.isReactComponent\n\nexport const withErrorBoundary = (getSystem) => (WrappedComponent) => {\n const { getComponent, fn } = getSystem()\n const ErrorBoundary = getComponent(\"ErrorBoundary\")\n const targetName = fn.getDisplayName(WrappedComponent)\n\n class WithErrorBoundary extends Component {\n render() {\n return (\n \n \n \n )\n }\n }\n WithErrorBoundary.displayName = `WithErrorBoundary(${targetName})`\n if (isClassComponent(WrappedComponent)) {\n /**\n * We need to handle case of class components defining a `mapStateToProps` public method.\n * Components with `mapStateToProps` public method cannot be wrapped.\n */\n WithErrorBoundary.prototype.mapStateToProps = WrappedComponent.prototype.mapStateToProps\n }\n\n return WithErrorBoundary\n}\n\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_fill_07ef3114__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_zipObject_c74f1c14__[\"default\"] });","import zipObject from \"lodash/zipObject\"\n\nimport ErrorBoundary from \"./components/error-boundary\"\nimport Fallback from \"./components/fallback\"\nimport { componentDidCatch, withErrorBoundary } from \"./fn\"\n\nconst safeRenderPlugin = ({componentList = [], fullOverride = false} = {}) => ({ getSystem }) => {\n const defaultComponentList = [\n \"App\",\n \"BaseLayout\",\n \"VersionPragmaFilter\",\n \"InfoContainer\",\n \"ServersContainer\",\n \"SchemesContainer\",\n \"AuthorizeBtnContainer\",\n \"FilterContainer\",\n \"Operations\",\n \"OperationContainer\",\n \"parameters\",\n \"responses\",\n \"OperationServers\",\n \"Models\",\n \"ModelWrapper\",\n ]\n const mergedComponentList = fullOverride ? componentList : [...defaultComponentList, ...componentList]\n const wrapFactory = (Original, { fn }) => fn.withErrorBoundary(Original)\n const wrapComponents = zipObject(mergedComponentList, Array(mergedComponentList.length).fill(wrapFactory))\n\n return {\n fn: {\n componentDidCatch,\n withErrorBoundary: withErrorBoundary(getSystem),\n },\n components: {\n ErrorBoundary,\n Fallback,\n },\n wrapComponents,\n }\n}\n\nexport default safeRenderPlugin\n","/**\n * @prettier\n */\nimport some from \"lodash/some\"\n\nconst shouldStringifyTypesConfig = [\n {\n when: /json/,\n shouldStringifyTypes: [\"string\"],\n },\n]\nconst defaultStringifyTypes = [\"object\"]\nconst makeGetJsonSampleSchema =\n (getSystem) => (schema, config, contentType, exampleOverride) => {\n const { fn } = getSystem()\n const res = fn.memoizedSampleFromSchema(schema, config, exampleOverride)\n const resType = typeof res\n\n const typesToStringify = shouldStringifyTypesConfig.reduce(\n (types, nextConfig) =>\n nextConfig.when.test(contentType)\n ? [...types, ...nextConfig.shouldStringifyTypes]\n : types,\n defaultStringifyTypes\n )\n\n return some(typesToStringify, (x) => x === resType)\n ? JSON.stringify(res, null, 2)\n : res\n }\n\nexport default makeGetJsonSampleSchema\n","/**\n * @prettier\n */\nconst makeGetSampleSchema =\n (getSystem) =>\n (schema, contentType = \"\", config = {}, exampleOverride = undefined) => {\n const { fn } = getSystem()\n\n if (typeof schema?.toJS === \"function\") {\n schema = schema.toJS()\n }\n if (typeof exampleOverride?.toJS === \"function\") {\n exampleOverride = exampleOverride.toJS()\n }\n\n if (/xml/.test(contentType)) {\n return fn.getXmlSampleSchema(schema, config, exampleOverride)\n }\n if (/(yaml|yml)/.test(contentType)) {\n return fn.getYamlSampleSchema(\n schema,\n config,\n contentType,\n exampleOverride\n )\n }\n return fn.getJsonSampleSchema(schema, config, contentType, exampleOverride)\n }\n\nexport default makeGetSampleSchema\n","/**\n * @prettier\n */\nconst makeGetXmlSampleSchema =\n (getSystem) => (schema, config, exampleOverride) => {\n const { fn } = getSystem()\n\n if (schema && !schema.xml) {\n schema.xml = {}\n }\n if (schema && !schema.xml.name) {\n if (\n !schema.$$ref &&\n (schema.type ||\n schema.items ||\n schema.properties ||\n schema.additionalProperties)\n ) {\n // eslint-disable-next-line quotes\n return '\\n'\n }\n if (schema.$$ref) {\n let match = schema.$$ref.match(/\\S*\\/(\\S+)$/)\n schema.xml.name = match[1]\n }\n }\n\n return fn.memoizedCreateXMLExample(schema, config, exampleOverride)\n }\n\nexport default makeGetXmlSampleSchema\n","/**\n * @prettier\n */\nimport YAML, { JSON_SCHEMA } from \"js-yaml\"\n\nconst makeGetYamlSampleSchema =\n (getSystem) => (schema, config, contentType, exampleOverride) => {\n const { fn } = getSystem()\n const jsonExample = fn.getJsonSampleSchema(\n schema,\n config,\n contentType,\n exampleOverride\n )\n let yamlString\n try {\n yamlString = YAML.dump(\n YAML.load(jsonExample),\n {\n lineWidth: -1, // don't generate line folds\n },\n { schema: JSON_SCHEMA }\n )\n if (yamlString[yamlString.length - 1] === \"\\n\") {\n yamlString = yamlString.slice(0, yamlString.length - 1)\n }\n } catch (e) {\n console.error(e)\n return \"error: could not generate yaml example\"\n }\n return yamlString.replace(/\\t/g, \" \")\n }\n\nexport default makeGetYamlSampleSchema\n","import XML from \"xml\"\nimport RandExp from \"randexp\"\nimport isEmpty from \"lodash/isEmpty\"\nimport { objectify, isFunc, normalizeArray, deeplyStripKey } from \"core/utils\"\n\nimport memoizeN from \"../../../../helpers/memoizeN\"\n\nconst generateStringFromRegex = (pattern) => {\n try {\n const randexp = new RandExp(pattern)\n return randexp.gen()\n } catch (e) {\n // Invalid regex should not cause a crash (regex syntax varies across languages)\n return \"string\"\n }\n}\n\nconst primitives = {\n \"string\": (schema) => schema.pattern ? generateStringFromRegex(schema.pattern) : \"string\",\n \"string_email\": () => \"user@example.com\",\n \"string_date-time\": () => new Date().toISOString(),\n \"string_date\": () => new Date().toISOString().substring(0, 10),\n \"string_uuid\": () => \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n \"string_hostname\": () => \"example.com\",\n \"string_ipv4\": () => \"198.51.100.42\",\n \"string_ipv6\": () => \"2001:0db8:5b96:0000:0000:426f:8e17:642a\",\n \"number\": () => 0,\n \"number_float\": () => 0.0,\n \"integer\": () => 0,\n \"boolean\": (schema) => typeof schema.default === \"boolean\" ? schema.default : true\n}\n\nconst primitive = (schema) => {\n schema = objectify(schema)\n let { type, format } = schema\n\n let fn = primitives[`${type}_${format}`] || primitives[type]\n\n if(isFunc(fn))\n return fn(schema)\n\n return \"Unknown Type: \" + schema.type\n}\n\n// do a couple of quick sanity tests to ensure the value\n// looks like a $$ref that swagger-client generates.\nconst sanitizeRef = (value) => deeplyStripKey(value, \"$$ref\", (val) =>\n typeof val === \"string\" && val.indexOf(\"#\") > -1)\n\nconst objectContracts = [\"maxProperties\", \"minProperties\"]\nconst arrayContracts = [\"minItems\", \"maxItems\"]\nconst numberContracts = [\n \"minimum\",\n \"maximum\",\n \"exclusiveMinimum\",\n \"exclusiveMaximum\"\n]\nconst stringContracts = [\"minLength\", \"maxLength\"]\n\nconst liftSampleHelper = (oldSchema, target, config = {}) => {\n const setIfNotDefinedInTarget = (key) => {\n if(target[key] === undefined && oldSchema[key] !== undefined) {\n target[key] = oldSchema[key]\n }\n }\n\n [\n \"example\",\n \"default\",\n \"enum\",\n \"xml\",\n \"type\",\n ...objectContracts,\n ...arrayContracts,\n ...numberContracts,\n ...stringContracts,\n ].forEach(key => setIfNotDefinedInTarget(key))\n\n if(oldSchema.required !== undefined && Array.isArray(oldSchema.required)) {\n if(target.required === undefined || !target.required.length) {\n target.required = []\n }\n oldSchema.required.forEach(key => {\n if(target.required.includes(key)) {\n return\n }\n target.required.push(key)\n })\n }\n if(oldSchema.properties) {\n if(!target.properties) {\n target.properties = {}\n }\n let props = objectify(oldSchema.properties)\n for (let propName in props) {\n if (!Object.prototype.hasOwnProperty.call(props, propName)) {\n continue\n }\n if ( props[propName] && props[propName].deprecated ) {\n continue\n }\n if ( props[propName] && props[propName].readOnly && !config.includeReadOnly ) {\n continue\n }\n if ( props[propName] && props[propName].writeOnly && !config.includeWriteOnly ) {\n continue\n }\n if(!target.properties[propName]) {\n target.properties[propName] = props[propName]\n if(!oldSchema.required && Array.isArray(oldSchema.required) && oldSchema.required.indexOf(propName) !== -1) {\n if(!target.required) {\n target.required = [propName]\n } else {\n target.required.push(propName)\n }\n }\n }\n }\n }\n if(oldSchema.items) {\n if(!target.items) {\n target.items = {}\n }\n target.items = liftSampleHelper(oldSchema.items, target.items, config)\n }\n\n return target\n}\n\nexport const sampleFromSchemaGeneric = (schema, config={}, exampleOverride = undefined, respectXML = false) => {\n if(schema && isFunc(schema.toJS))\n schema = schema.toJS()\n let usePlainValue = exampleOverride !== undefined || schema && schema.example !== undefined || schema && schema.default !== undefined\n // first check if there is the need of combining this schema with others required by allOf\n const hasOneOf = !usePlainValue && schema && schema.oneOf && schema.oneOf.length > 0\n const hasAnyOf = !usePlainValue && schema && schema.anyOf && schema.anyOf.length > 0\n if(!usePlainValue && (hasOneOf || hasAnyOf)) {\n const schemaToAdd = objectify(hasOneOf\n ? schema.oneOf[0]\n : schema.anyOf[0]\n )\n liftSampleHelper(schemaToAdd, schema, config)\n if(!schema.xml && schemaToAdd.xml) {\n schema.xml = schemaToAdd.xml\n }\n if(schema.example !== undefined && schemaToAdd.example !== undefined) {\n usePlainValue = true\n } else if(schemaToAdd.properties) {\n if(!schema.properties) {\n schema.properties = {}\n }\n let props = objectify(schemaToAdd.properties)\n for (let propName in props) {\n if (!Object.prototype.hasOwnProperty.call(props, propName)) {\n continue\n }\n if ( props[propName] && props[propName].deprecated ) {\n continue\n }\n if ( props[propName] && props[propName].readOnly && !config.includeReadOnly ) {\n continue\n }\n if ( props[propName] && props[propName].writeOnly && !config.includeWriteOnly ) {\n continue\n }\n if(!schema.properties[propName]) {\n schema.properties[propName] = props[propName]\n if(!schemaToAdd.required && Array.isArray(schemaToAdd.required) && schemaToAdd.required.indexOf(propName) !== -1) {\n if(!schema.required) {\n schema.required = [propName]\n } else {\n schema.required.push(propName)\n }\n }\n }\n }\n }\n }\n const _attr = {}\n let { xml, type, example, properties, additionalProperties, items } = schema || {}\n let { includeReadOnly, includeWriteOnly } = config\n xml = xml || {}\n let { name, prefix, namespace } = xml\n let displayName\n let res = {}\n\n // set xml naming and attributes\n if(respectXML) {\n name = name || \"notagname\"\n // add prefix to name if exists\n displayName = (prefix ? prefix + \":\" : \"\") + name\n if ( namespace ) {\n //add prefix to namespace if exists\n let namespacePrefix = prefix ? ( \"xmlns:\" + prefix ) : \"xmlns\"\n _attr[namespacePrefix] = namespace\n }\n }\n\n // init xml default response sample obj\n if(respectXML) {\n res[displayName] = []\n }\n\n const schemaHasAny = (keys) => keys.some(key => Object.prototype.hasOwnProperty.call(schema, key))\n // try recover missing type\n if(schema && !type) {\n if(properties || additionalProperties || schemaHasAny(objectContracts)) {\n type = \"object\"\n } else if(items || schemaHasAny(arrayContracts)) {\n type = \"array\"\n } else if(schemaHasAny(numberContracts)) {\n type = \"number\"\n schema.type = \"number\"\n } else if(!usePlainValue && !schema.enum){\n // implicit cover schemaHasAny(stringContracts) or A schema without a type matches any data type is:\n // components:\n // schemas:\n // AnyValue:\n // anyOf:\n // - type: string\n // - type: number\n // - type: integer\n // - type: boolean\n // - type: array\n // items: {}\n // - type: object\n //\n // which would resolve to type: string\n type = \"string\"\n schema.type = \"string\"\n }\n }\n\n const handleMinMaxItems = (sampleArray) => {\n if (schema?.maxItems !== null && schema?.maxItems !== undefined) {\n sampleArray = sampleArray.slice(0, schema?.maxItems)\n }\n if (schema?.minItems !== null && schema?.minItems !== undefined) {\n let i = 0\n while (sampleArray.length < schema?.minItems) {\n sampleArray.push(sampleArray[i++ % sampleArray.length])\n }\n }\n return sampleArray\n }\n\n // add to result helper init for xml or json\n const props = objectify(properties)\n let addPropertyToResult\n let propertyAddedCounter = 0\n\n const hasExceededMaxProperties = () => schema\n && schema.maxProperties !== null && schema.maxProperties !== undefined\n && propertyAddedCounter >= schema.maxProperties\n\n const requiredPropertiesToAdd = () => {\n if(!schema || !schema.required) {\n return 0\n }\n let addedCount = 0\n if(respectXML) {\n schema.required.forEach(key => addedCount +=\n res[key] === undefined\n ? 0\n : 1\n )\n } else {\n schema.required.forEach(key => addedCount +=\n res[displayName]?.find(x => x[key] !== undefined) === undefined\n ? 0\n : 1\n )\n }\n return schema.required.length - addedCount\n }\n\n const isOptionalProperty = (propName) => {\n if(!schema || !schema.required || !schema.required.length) {\n return true\n }\n return !schema.required.includes(propName)\n }\n\n const canAddProperty = (propName) => {\n if(!schema || schema.maxProperties === null || schema.maxProperties === undefined) {\n return true\n }\n if(hasExceededMaxProperties()) {\n return false\n }\n if(!isOptionalProperty(propName)) {\n return true\n }\n return (schema.maxProperties - propertyAddedCounter - requiredPropertiesToAdd()) > 0\n }\n\n if(respectXML) {\n addPropertyToResult = (propName, overrideE = undefined) => {\n if(schema && props[propName]) {\n // case it is an xml attribute\n props[propName].xml = props[propName].xml || {}\n\n if (props[propName].xml.attribute) {\n const enumAttrVal = Array.isArray(props[propName].enum)\n ? props[propName].enum[0]\n : undefined\n const attrExample = props[propName].example\n const attrDefault = props[propName].default\n\n if(attrExample !== undefined) {\n _attr[props[propName].xml.name || propName] = attrExample\n } else if(attrDefault !== undefined) {\n _attr[props[propName].xml.name || propName] = attrDefault\n } else if(enumAttrVal !== undefined) {\n _attr[props[propName].xml.name || propName] = enumAttrVal\n } else {\n _attr[props[propName].xml.name || propName] = primitive(props[propName])\n }\n\n return\n }\n props[propName].xml.name = props[propName].xml.name || propName\n } else if(!props[propName] && additionalProperties !== false) {\n // case only additionalProperty that is not defined in schema\n props[propName] = {\n xml: {\n name: propName\n }\n }\n }\n\n let t = sampleFromSchemaGeneric(schema && props[propName] || undefined, config, overrideE, respectXML)\n if(!canAddProperty(propName)) {\n return\n }\n\n propertyAddedCounter++\n if (Array.isArray(t)) {\n res[displayName] = res[displayName].concat(t)\n } else {\n res[displayName].push(t)\n }\n }\n } else {\n addPropertyToResult = (propName, overrideE) => {\n if(!canAddProperty(propName)) {\n return\n }\n if(Object.prototype.hasOwnProperty.call(schema, \"discriminator\") &&\n schema.discriminator &&\n Object.prototype.hasOwnProperty.call(schema.discriminator, \"mapping\") &&\n schema.discriminator.mapping &&\n Object.prototype.hasOwnProperty.call(schema, \"$$ref\") &&\n schema.$$ref &&\n schema.discriminator.propertyName === propName) {\n for (let pair in schema.discriminator.mapping){\n if (schema.$$ref.search(schema.discriminator.mapping[pair]) !== -1) {\n res[propName] = pair\n break\n }\n }\n } else {\n res[propName] = sampleFromSchemaGeneric(props[propName], config, overrideE, respectXML)\n }\n propertyAddedCounter++\n }\n }\n\n // check for plain value and if found use it to generate sample from it\n if(usePlainValue) {\n let sample\n if(exampleOverride !== undefined) {\n sample = sanitizeRef(exampleOverride)\n } else if(example !== undefined) {\n sample = sanitizeRef(example)\n } else {\n sample = sanitizeRef(schema.default)\n }\n\n // if json just return\n if(!respectXML) {\n // spacial case yaml parser can not know about\n if(typeof sample === \"number\" && type === \"string\") {\n return `${sample}`\n }\n // return if sample does not need any parsing\n if(typeof sample !== \"string\" || type === \"string\") {\n return sample\n }\n // check if sample is parsable or just a plain string\n try {\n return JSON.parse(sample)\n } catch(e) {\n // sample is just plain string return it\n return sample\n }\n }\n\n // recover missing type\n if(!schema) {\n type = Array.isArray(sample) ? \"array\" : typeof sample\n }\n\n // generate xml sample recursively for array case\n if(type === \"array\") {\n if (!Array.isArray(sample)) {\n if(typeof sample === \"string\") {\n return sample\n }\n sample = [sample]\n }\n const itemSchema = schema\n ? schema.items\n : undefined\n if(itemSchema) {\n itemSchema.xml = itemSchema.xml || xml || {}\n itemSchema.xml.name = itemSchema.xml.name || xml.name\n }\n let itemSamples = sample\n .map(s => sampleFromSchemaGeneric(itemSchema, config, s, respectXML))\n itemSamples = handleMinMaxItems(itemSamples)\n if(xml.wrapped) {\n res[displayName] = itemSamples\n if (!isEmpty(_attr)) {\n res[displayName].push({_attr: _attr})\n }\n }\n else {\n res = itemSamples\n }\n return res\n }\n\n // generate xml sample recursively for object case\n if(type === \"object\") {\n // case literal example\n if(typeof sample === \"string\") {\n return sample\n }\n for (let propName in sample) {\n if (!Object.prototype.hasOwnProperty.call(sample, propName)) {\n continue\n }\n if (schema && props[propName] && props[propName].readOnly && !includeReadOnly) {\n continue\n }\n if (schema && props[propName] && props[propName].writeOnly && !includeWriteOnly) {\n continue\n }\n if (schema && props[propName] && props[propName].xml && props[propName].xml.attribute) {\n _attr[props[propName].xml.name || propName] = sample[propName]\n continue\n }\n addPropertyToResult(propName, sample[propName])\n }\n if (!isEmpty(_attr)) {\n res[displayName].push({_attr: _attr})\n }\n\n return res\n }\n\n res[displayName] = !isEmpty(_attr) ? [{_attr: _attr}, sample] : sample\n return res\n }\n\n // use schema to generate sample\n\n if(type === \"object\") {\n for (let propName in props) {\n if (!Object.prototype.hasOwnProperty.call(props, propName)) {\n continue\n }\n if ( props[propName] && props[propName].deprecated ) {\n continue\n }\n if ( props[propName] && props[propName].readOnly && !includeReadOnly ) {\n continue\n }\n if ( props[propName] && props[propName].writeOnly && !includeWriteOnly ) {\n continue\n }\n addPropertyToResult(propName)\n }\n if (respectXML && _attr) {\n res[displayName].push({_attr: _attr})\n }\n\n if(hasExceededMaxProperties()) {\n return res\n }\n\n if ( additionalProperties === true ) {\n if(respectXML) {\n res[displayName].push({additionalProp: \"Anything can be here\"})\n } else {\n res.additionalProp1 = {}\n }\n propertyAddedCounter++\n } else if ( additionalProperties ) {\n const additionalProps = objectify(additionalProperties)\n const additionalPropSample = sampleFromSchemaGeneric(additionalProps, config, undefined, respectXML)\n\n if(respectXML && additionalProps.xml && additionalProps.xml.name && additionalProps.xml.name !== \"notagname\")\n {\n res[displayName].push(additionalPropSample)\n } else {\n const toGenerateCount = schema.minProperties !== null && schema.minProperties !== undefined && propertyAddedCounter < schema.minProperties\n ? schema.minProperties - propertyAddedCounter\n : 3\n for (let i = 1; i <= toGenerateCount; i++) {\n if(hasExceededMaxProperties()) {\n return res\n }\n if(respectXML) {\n const temp = {}\n temp[\"additionalProp\" + i] = additionalPropSample[\"notagname\"]\n res[displayName].push(temp)\n } else {\n res[\"additionalProp\" + i] = additionalPropSample\n }\n propertyAddedCounter++\n }\n }\n }\n return res\n }\n\n if(type === \"array\") {\n if (!items) {\n return\n }\n\n let sampleArray\n if(respectXML) {\n items.xml = items.xml || schema?.xml || {}\n items.xml.name = items.xml.name || xml.name\n }\n\n if(Array.isArray(items.anyOf)) {\n sampleArray = items.anyOf.map(i => sampleFromSchemaGeneric(liftSampleHelper(items, i, config), config, undefined, respectXML))\n } else if(Array.isArray(items.oneOf)) {\n sampleArray = items.oneOf.map(i => sampleFromSchemaGeneric(liftSampleHelper(items, i, config), config, undefined, respectXML))\n } else if(!respectXML || respectXML && xml.wrapped) {\n sampleArray = [sampleFromSchemaGeneric(items, config, undefined, respectXML)]\n } else {\n return sampleFromSchemaGeneric(items, config, undefined, respectXML)\n }\n sampleArray = handleMinMaxItems(sampleArray)\n if(respectXML && xml.wrapped) {\n res[displayName] = sampleArray\n if (!isEmpty(_attr)) {\n res[displayName].push({_attr: _attr})\n }\n return res\n }\n return sampleArray\n }\n\n let value\n if (schema && Array.isArray(schema.enum)) {\n //display enum first value\n value = normalizeArray(schema.enum)[0]\n } else if(schema) {\n // display schema default\n value = primitive(schema)\n if(typeof value === \"number\") {\n let min = schema.minimum\n if(min !== undefined && min !== null) {\n if(schema.exclusiveMinimum) {\n min++\n }\n value = min\n }\n let max = schema.maximum\n if(max !== undefined && max !== null) {\n if(schema.exclusiveMaximum) {\n max--\n }\n value = max\n }\n }\n if(typeof value === \"string\") {\n if (schema.maxLength !== null && schema.maxLength !== undefined) {\n value = value.slice(0, schema.maxLength)\n }\n if (schema.minLength !== null && schema.minLength !== undefined) {\n let i = 0\n while (value.length < schema.minLength) {\n value += value[i++ % value.length]\n }\n }\n }\n } else {\n return\n }\n if (type === \"file\") {\n return\n }\n\n if(respectXML) {\n res[displayName] = !isEmpty(_attr) ? [{_attr: _attr}, value] : value\n return res\n }\n\n return value\n}\n\nexport const inferSchema = (thing) => {\n if(thing.schema)\n thing = thing.schema\n\n if(thing.properties) {\n thing.type = \"object\"\n }\n\n return thing // Hopefully this will have something schema like in it... `type` for example\n}\n\nexport const createXMLExample = (schema, config, o) => {\n const json = sampleFromSchemaGeneric(schema, config, o, true)\n if (!json) { return }\n if(typeof json === \"string\") {\n return json\n }\n return XML(json, { declaration: true, indent: \"\\t\" })\n}\n\nexport const sampleFromSchema = (schema, config, o) =>\n sampleFromSchemaGeneric(schema, config, o, false)\n\nconst resolver = (arg1, arg2, arg3) => [arg1, JSON.stringify(arg2), JSON.stringify(arg3)]\n\nexport const memoizedCreateXMLExample = memoizeN(createXMLExample, resolver)\n\nexport const memoizedSampleFromSchema = memoizeN(sampleFromSchema, resolver)\n","/**\n * @prettier\n */\nimport {\n sampleFromSchema,\n inferSchema,\n sampleFromSchemaGeneric,\n createXMLExample,\n memoizedCreateXMLExample,\n memoizedSampleFromSchema,\n} from \"./fn/index\"\nimport makeGetJsonSampleSchema from \"./fn/get-json-sample-schema\"\nimport makeGetYamlSampleSchema from \"./fn/get-yaml-sample-schema\"\nimport makeGetXmlSampleSchema from \"./fn/get-xml-sample-schema\"\nimport makeGetSampleSchema from \"./fn/get-sample-schema\"\n\nconst SamplesPlugin = ({ getSystem }) => ({\n fn: {\n inferSchema,\n sampleFromSchema,\n sampleFromSchemaGeneric,\n createXMLExample,\n memoizedSampleFromSchema,\n memoizedCreateXMLExample,\n getJsonSampleSchema: makeGetJsonSampleSchema(getSystem),\n getYamlSampleSchema: makeGetYamlSampleSchema(getSystem),\n getXmlSampleSchema: makeGetXmlSampleSchema(getSystem),\n getSampleSchema: makeGetSampleSchema(getSystem),\n },\n})\n\nexport default SamplesPlugin\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_promise_047dc8e8__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_date_now_1bf78713__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_isString_e6fa8a5b__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_debounce_3540babe__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_set_b4b15ee5__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_fp_assocPath_f9d64e33__[\"default\"] });","import YAML, { JSON_SCHEMA } from \"js-yaml\"\nimport { Map } from \"immutable\"\nimport parseUrl from \"url-parse\"\nimport { serializeError } from \"serialize-error\"\nimport isString from \"lodash/isString\"\nimport debounce from \"lodash/debounce\"\nimport set from \"lodash/set\"\nimport assocPath from \"lodash/fp/assocPath\"\n\nimport { paramToValue, isEmptyValue } from \"core/utils\"\n\n// Actions conform to FSA (flux-standard-actions)\n// {type: string,payload: Any|Error, meta: obj, error: bool}\n\nexport const UPDATE_SPEC = \"spec_update_spec\"\nexport const UPDATE_URL = \"spec_update_url\"\nexport const UPDATE_JSON = \"spec_update_json\"\nexport const UPDATE_PARAM = \"spec_update_param\"\nexport const UPDATE_EMPTY_PARAM_INCLUSION = \"spec_update_empty_param_inclusion\"\nexport const VALIDATE_PARAMS = \"spec_validate_param\"\nexport const SET_RESPONSE = \"spec_set_response\"\nexport const SET_REQUEST = \"spec_set_request\"\nexport const SET_MUTATED_REQUEST = \"spec_set_mutated_request\"\nexport const LOG_REQUEST = \"spec_log_request\"\nexport const CLEAR_RESPONSE = \"spec_clear_response\"\nexport const CLEAR_REQUEST = \"spec_clear_request\"\nexport const CLEAR_VALIDATE_PARAMS = \"spec_clear_validate_param\"\nexport const UPDATE_OPERATION_META_VALUE = \"spec_update_operation_meta_value\"\nexport const UPDATE_RESOLVED = \"spec_update_resolved\"\nexport const UPDATE_RESOLVED_SUBTREE = \"spec_update_resolved_subtree\"\nexport const SET_SCHEME = \"set_scheme\"\n\nconst toStr = (str) => isString(str) ? str : \"\"\n\nexport function updateSpec(spec) {\n const cleanSpec = (toStr(spec)).replace(/\\t/g, \" \")\n if(typeof spec === \"string\") {\n return {\n type: UPDATE_SPEC,\n payload: cleanSpec\n }\n }\n}\n\nexport function updateResolved(spec) {\n return {\n type: UPDATE_RESOLVED,\n payload: spec\n }\n}\n\nexport function updateUrl(url) {\n return {type: UPDATE_URL, payload: url}\n}\n\nexport function updateJsonSpec(json) {\n return {type: UPDATE_JSON, payload: json}\n}\n\nexport const parseToJson = (str) => ({specActions, specSelectors, errActions}) => {\n let { specStr } = specSelectors\n\n let json = null\n try {\n str = str || specStr()\n errActions.clear({ source: \"parser\" })\n json = YAML.load(str, { schema: JSON_SCHEMA })\n } catch(e) {\n // TODO: push error to state\n console.error(e)\n return errActions.newSpecErr({\n source: \"parser\",\n level: \"error\",\n message: e.reason,\n line: e.mark && e.mark.line ? e.mark.line + 1 : undefined\n })\n }\n if(json && typeof json === \"object\") {\n return specActions.updateJsonSpec(json)\n }\n return {}\n}\n\nlet hasWarnedAboutResolveSpecDeprecation = false\n\nexport const resolveSpec = (json, url) => ({specActions, specSelectors, errActions, fn: { fetch, resolve, AST = {} }, getConfigs}) => {\n if(!hasWarnedAboutResolveSpecDeprecation) {\n console.warn(`specActions.resolveSpec is deprecated since v3.10.0 and will be removed in v4.0.0; use requestResolvedSubtree instead!`)\n hasWarnedAboutResolveSpecDeprecation = true\n }\n\n const {\n modelPropertyMacro,\n parameterMacro,\n requestInterceptor,\n responseInterceptor\n } = getConfigs()\n\n if(typeof(json) === \"undefined\") {\n json = specSelectors.specJson()\n }\n if(typeof(url) === \"undefined\") {\n url = specSelectors.url()\n }\n\n let getLineNumberForPath = AST.getLineNumberForPath ? AST.getLineNumberForPath : () => undefined\n\n let specStr = specSelectors.specStr()\n\n return resolve({\n fetch,\n spec: json,\n baseDoc: url,\n modelPropertyMacro,\n parameterMacro,\n requestInterceptor,\n responseInterceptor\n }).then( ({spec, errors}) => {\n errActions.clear({\n type: \"thrown\"\n })\n if(Array.isArray(errors) && errors.length > 0) {\n let preparedErrors = errors\n .map(err => {\n console.error(err)\n err.line = err.fullPath ? getLineNumberForPath(specStr, err.fullPath) : null\n err.path = err.fullPath ? err.fullPath.join(\".\") : null\n err.level = \"error\"\n err.type = \"thrown\"\n err.source = \"resolver\"\n Object.defineProperty(err, \"message\", { enumerable: true, value: err.message })\n return err\n })\n errActions.newThrownErrBatch(preparedErrors)\n }\n\n return specActions.updateResolved(spec)\n })\n}\n\nlet requestBatch = []\n\nconst debResolveSubtrees = debounce(async () => {\n const system = requestBatch.system // Just a reference to the \"latest\" system\n\n if(!system) {\n console.error(\"debResolveSubtrees: don't have a system to operate on, aborting.\")\n return\n }\n const {\n errActions,\n errSelectors,\n fn: {\n resolveSubtree,\n fetch,\n AST = {}\n },\n specSelectors,\n specActions,\n } = system\n\n if(!resolveSubtree) {\n console.error(\"Error: Swagger-Client did not provide a `resolveSubtree` method, doing nothing.\")\n return\n }\n\n let getLineNumberForPath = AST.getLineNumberForPath ? AST.getLineNumberForPath : () => undefined\n\n const specStr = specSelectors.specStr()\n\n const {\n modelPropertyMacro,\n parameterMacro,\n requestInterceptor,\n responseInterceptor\n } = system.getConfigs()\n\n try {\n var batchResult = await requestBatch.reduce(async (prev, path) => {\n let { resultMap, specWithCurrentSubtrees } = await prev\n const { errors, spec } = await resolveSubtree(specWithCurrentSubtrees, path, {\n baseDoc: specSelectors.url(),\n modelPropertyMacro,\n parameterMacro,\n requestInterceptor,\n responseInterceptor\n })\n\n if(errSelectors.allErrors().size) {\n errActions.clearBy(err => {\n // keep if...\n return err.get(\"type\") !== \"thrown\" // it's not a thrown error\n || err.get(\"source\") !== \"resolver\" // it's not a resolver error\n || !err.get(\"fullPath\").every((key, i) => key === path[i] || path[i] === undefined) // it's not within the path we're resolving\n })\n }\n\n if(Array.isArray(errors) && errors.length > 0) {\n let preparedErrors = errors\n .map(err => {\n err.line = err.fullPath ? getLineNumberForPath(specStr, err.fullPath) : null\n err.path = err.fullPath ? err.fullPath.join(\".\") : null\n err.level = \"error\"\n err.type = \"thrown\"\n err.source = \"resolver\"\n Object.defineProperty(err, \"message\", { enumerable: true, value: err.message })\n return err\n })\n errActions.newThrownErrBatch(preparedErrors)\n }\n\n if (spec && specSelectors.isOAS3() && path[0] === \"components\" && path[1] === \"securitySchemes\") {\n // Resolve OIDC URLs if present\n await Promise.all(Object.values(spec)\n .filter((scheme) => scheme.type === \"openIdConnect\")\n .map(async (oidcScheme) => {\n const req = {\n url: oidcScheme.openIdConnectUrl,\n requestInterceptor: requestInterceptor,\n responseInterceptor: responseInterceptor\n }\n try {\n const res = await fetch(req)\n if (res instanceof Error || res.status >= 400) {\n console.error(res.statusText + \" \" + req.url)\n } else {\n oidcScheme.openIdConnectData = JSON.parse(res.text)\n }\n } catch (e) {\n console.error(e)\n }\n }))\n }\n set(resultMap, path, spec)\n specWithCurrentSubtrees = assocPath(path, spec, specWithCurrentSubtrees)\n\n return {\n resultMap,\n specWithCurrentSubtrees\n }\n }, Promise.resolve({\n resultMap: (specSelectors.specResolvedSubtree([]) || Map()).toJS(),\n specWithCurrentSubtrees: specSelectors.specJS()\n }))\n\n delete requestBatch.system\n requestBatch = [] // Clear stack\n } catch(e) {\n console.error(e)\n }\n\n specActions.updateResolvedSubtree([], batchResult.resultMap)\n}, 35)\n\nexport const requestResolvedSubtree = path => system => {\n // poor-man's array comparison\n // if this ever inadequate, this should be rewritten to use Im.List\n const isPathAlreadyBatched = requestBatch\n .map(arr => arr.join(\"@@\"))\n .indexOf(path.join(\"@@\")) > -1\n\n if(isPathAlreadyBatched) {\n return\n }\n\n requestBatch.push(path)\n requestBatch.system = system\n debResolveSubtrees()\n}\n\nexport function changeParam( path, paramName, paramIn, value, isXml ){\n return {\n type: UPDATE_PARAM,\n payload:{ path, value, paramName, paramIn, isXml }\n }\n}\n\nexport function changeParamByIdentity( pathMethod, param, value, isXml ){\n return {\n type: UPDATE_PARAM,\n payload:{ path: pathMethod, param, value, isXml }\n }\n}\n\nexport const updateResolvedSubtree = (path, value) => {\n return {\n type: UPDATE_RESOLVED_SUBTREE,\n payload: { path, value }\n }\n}\n\nexport const invalidateResolvedSubtreeCache = () => {\n return {\n type: UPDATE_RESOLVED_SUBTREE,\n payload: {\n path: [],\n value: Map()\n }\n }\n}\n\nexport const validateParams = ( payload, isOAS3 ) =>{\n return {\n type: VALIDATE_PARAMS,\n payload:{\n pathMethod: payload,\n isOAS3\n }\n }\n}\n\nexport const updateEmptyParamInclusion = ( pathMethod, paramName, paramIn, includeEmptyValue ) =>{\n return {\n type: UPDATE_EMPTY_PARAM_INCLUSION,\n payload:{\n pathMethod,\n paramName,\n paramIn,\n includeEmptyValue\n }\n }\n}\n\nexport function clearValidateParams( payload ){\n return {\n type: CLEAR_VALIDATE_PARAMS,\n payload:{ pathMethod: payload }\n }\n}\n\nexport function changeConsumesValue(path, value) {\n return {\n type: UPDATE_OPERATION_META_VALUE,\n payload:{ path, value, key: \"consumes_value\" }\n }\n}\n\nexport function changeProducesValue(path, value) {\n return {\n type: UPDATE_OPERATION_META_VALUE,\n payload:{ path, value, key: \"produces_value\" }\n }\n}\n\nexport const setResponse = ( path, method, res ) => {\n return {\n payload: { path, method, res },\n type: SET_RESPONSE\n }\n}\n\nexport const setRequest = ( path, method, req ) => {\n return {\n payload: { path, method, req },\n type: SET_REQUEST\n }\n}\n\nexport const setMutatedRequest = ( path, method, req ) => {\n return {\n payload: { path, method, req },\n type: SET_MUTATED_REQUEST\n }\n}\n\n// This is for debugging, remove this comment if you depend on this action\nexport const logRequest = (req) => {\n return {\n payload: req,\n type: LOG_REQUEST\n }\n}\n\n// Actually fire the request via fn.execute\n// (For debugging) and ease of testing\nexport const executeRequest = (req) =>\n ({fn, specActions, specSelectors, getConfigs, oas3Selectors}) => {\n let { pathName, method, operation } = req\n let { requestInterceptor, responseInterceptor } = getConfigs()\n\n\n let op = operation.toJS()\n\n // ensure that explicitly-included params are in the request\n\n if (operation && operation.get(\"parameters\")) {\n operation.get(\"parameters\")\n .filter(param => param && param.get(\"allowEmptyValue\") === true)\n .forEach(param => {\n if (specSelectors.parameterInclusionSettingFor([pathName, method], param.get(\"name\"), param.get(\"in\"))) {\n req.parameters = req.parameters || {}\n const paramValue = paramToValue(param, req.parameters)\n\n // if the value is falsy or an empty Immutable iterable...\n if(!paramValue || (paramValue && paramValue.size === 0)) {\n // set it to empty string, so Swagger Client will treat it as\n // present but empty.\n req.parameters[param.get(\"name\")] = \"\"\n }\n }\n })\n }\n\n // if url is relative, parseUrl makes it absolute by inferring from `window.location`\n req.contextUrl = parseUrl(specSelectors.url()).toString()\n\n if(op && op.operationId) {\n req.operationId = op.operationId\n } else if(op && pathName && method) {\n req.operationId = fn.opId(op, pathName, method)\n }\n\n if(specSelectors.isOAS3()) {\n const namespace = `${pathName}:${method}`\n\n req.server = oas3Selectors.selectedServer(namespace) || oas3Selectors.selectedServer()\n\n const namespaceVariables = oas3Selectors.serverVariables({\n server: req.server,\n namespace\n }).toJS()\n const globalVariables = oas3Selectors.serverVariables({ server: req.server }).toJS()\n\n req.serverVariables = Object.keys(namespaceVariables).length ? namespaceVariables : globalVariables\n\n req.requestContentType = oas3Selectors.requestContentType(pathName, method)\n req.responseContentType = oas3Selectors.responseContentType(pathName, method) || \"*/*\"\n const requestBody = oas3Selectors.requestBodyValue(pathName, method)\n const requestBodyInclusionSetting = oas3Selectors.requestBodyInclusionSetting(pathName, method)\n\n if(requestBody && requestBody.toJS) {\n req.requestBody = requestBody\n .map(\n (val) => {\n if (Map.isMap(val)) {\n return val.get(\"value\")\n }\n return val\n }\n )\n .filter(\n (value, key) => (Array.isArray(value)\n ? value.length !== 0\n : !isEmptyValue(value)\n ) || requestBodyInclusionSetting.get(key)\n )\n .toJS()\n } else {\n req.requestBody = requestBody\n }\n }\n\n let parsedRequest = Object.assign({}, req)\n parsedRequest = fn.buildRequest(parsedRequest)\n\n specActions.setRequest(req.pathName, req.method, parsedRequest)\n\n let requestInterceptorWrapper = async (r) => {\n let mutatedRequest = await requestInterceptor.apply(this, [r])\n let parsedMutatedRequest = Object.assign({}, mutatedRequest)\n specActions.setMutatedRequest(req.pathName, req.method, parsedMutatedRequest)\n return mutatedRequest\n }\n\n req.requestInterceptor = requestInterceptorWrapper\n req.responseInterceptor = responseInterceptor\n\n // track duration of request\n const startTime = Date.now()\n\n\n return fn.execute(req)\n .then( res => {\n res.duration = Date.now() - startTime\n specActions.setResponse(req.pathName, req.method, res)\n } )\n .catch(\n err => {\n // console.error(err)\n if(err.message === \"Failed to fetch\") {\n err.name = \"\"\n err.message = \"**Failed to fetch.** \\n**Possible Reasons:** \\n - CORS \\n - Network Failure \\n - URL scheme must be \\\"http\\\" or \\\"https\\\" for CORS request.\"\n }\n specActions.setResponse(req.pathName, req.method, {\n error: true, err: serializeError(err)\n })\n }\n )\n }\n\n\n// I'm using extras as a way to inject properties into the final, `execute` method - It's not great. Anyone have a better idea? @ponelat\nexport const execute = ( { path, method, ...extras }={} ) => (system) => {\n let { fn:{fetch}, specSelectors, specActions } = system\n let spec = specSelectors.specJsonWithResolvedSubtrees().toJS()\n let scheme = specSelectors.operationScheme(path, method)\n let { requestContentType, responseContentType } = specSelectors.contentTypeValues([path, method]).toJS()\n let isXml = /xml/i.test(requestContentType)\n let parameters = specSelectors.parameterValues([path, method], isXml).toJS()\n\n return specActions.executeRequest({\n ...extras,\n fetch,\n spec,\n pathName: path,\n method, parameters,\n requestContentType,\n scheme,\n responseContentType\n })\n}\n\nexport function clearResponse (path, method) {\n return {\n type: CLEAR_RESPONSE,\n payload:{ path, method }\n }\n}\n\nexport function clearRequest (path, method) {\n return {\n type: CLEAR_REQUEST,\n payload:{ path, method }\n }\n}\n\nexport function setScheme (scheme, path, method) {\n return {\n type: SET_SCHEME,\n payload: { scheme, path, method }\n }\n}\n","import reducers from \"./reducers\"\nimport * as actions from \"./actions\"\nimport * as selectors from \"./selectors\"\nimport * as wrapActions from \"./wrap-actions\"\n\nexport default function() {\n return {\n statePlugins: {\n spec: {\n wrapActions,\n reducers,\n actions,\n selectors\n }\n }\n }\n}\n","import { fromJS, List } from \"immutable\"\nimport { fromJSOrdered, validateParam, paramToValue } from \"core/utils\"\nimport win from \"../../window\"\n\n// selector-in-reducer is suboptimal, but `operationWithMeta` is more of a helper\nimport {\n specJsonWithResolvedSubtrees,\n parameterValues,\n parameterInclusionSettingFor,\n} from \"./selectors\"\n\nimport {\n UPDATE_SPEC,\n UPDATE_URL,\n UPDATE_JSON,\n UPDATE_PARAM,\n UPDATE_EMPTY_PARAM_INCLUSION,\n VALIDATE_PARAMS,\n SET_RESPONSE,\n SET_REQUEST,\n SET_MUTATED_REQUEST,\n UPDATE_RESOLVED,\n UPDATE_RESOLVED_SUBTREE,\n UPDATE_OPERATION_META_VALUE,\n CLEAR_RESPONSE,\n CLEAR_REQUEST,\n CLEAR_VALIDATE_PARAMS,\n SET_SCHEME\n} from \"./actions\"\nimport { paramToIdentifier } from \"../../utils\"\n\nexport default {\n\n [UPDATE_SPEC]: (state, action) => {\n return (typeof action.payload === \"string\")\n ? state.set(\"spec\", action.payload)\n : state\n },\n\n [UPDATE_URL]: (state, action) => {\n return state.set(\"url\", action.payload+\"\")\n },\n\n [UPDATE_JSON]: (state, action) => {\n return state.set(\"json\", fromJSOrdered(action.payload))\n },\n\n [UPDATE_RESOLVED]: (state, action) => {\n return state.setIn([\"resolved\"], fromJSOrdered(action.payload))\n },\n\n [UPDATE_RESOLVED_SUBTREE]: (state, action) => {\n const { value, path } = action.payload\n return state.setIn([\"resolvedSubtrees\", ...path], fromJSOrdered(value))\n },\n\n [UPDATE_PARAM]: ( state, {payload} ) => {\n let { path: pathMethod, paramName, paramIn, param, value, isXml } = payload\n\n let paramKey = param ? paramToIdentifier(param) : `${paramIn}.${paramName}`\n\n const valueKey = isXml ? \"value_xml\" : \"value\"\n\n return state.setIn(\n [\"meta\", \"paths\", ...pathMethod, \"parameters\", paramKey, valueKey],\n value\n )\n },\n\n [UPDATE_EMPTY_PARAM_INCLUSION]: ( state, {payload} ) => {\n let { pathMethod, paramName, paramIn, includeEmptyValue } = payload\n\n if(!paramName || !paramIn) {\n console.warn(\"Warning: UPDATE_EMPTY_PARAM_INCLUSION could not generate a paramKey.\")\n return state\n }\n\n const paramKey = `${paramIn}.${paramName}`\n\n return state.setIn(\n [\"meta\", \"paths\", ...pathMethod, \"parameter_inclusions\", paramKey],\n includeEmptyValue\n )\n },\n\n [VALIDATE_PARAMS]: ( state, { payload: { pathMethod, isOAS3 } } ) => {\n const op = specJsonWithResolvedSubtrees(state).getIn([\"paths\", ...pathMethod])\n const paramValues = parameterValues(state, pathMethod).toJS()\n\n return state.updateIn([\"meta\", \"paths\", ...pathMethod, \"parameters\"], fromJS({}), paramMeta => {\n return op.get(\"parameters\", List()).reduce((res, param) => {\n const value = paramToValue(param, paramValues)\n const isEmptyValueIncluded = parameterInclusionSettingFor(state, pathMethod, param.get(\"name\"), param.get(\"in\"))\n const errors = validateParam(param, value, {\n bypassRequiredCheck: isEmptyValueIncluded,\n isOAS3,\n })\n return res.setIn([paramToIdentifier(param), \"errors\"], fromJS(errors))\n }, paramMeta)\n })\n },\n [CLEAR_VALIDATE_PARAMS]: ( state, { payload: { pathMethod } } ) => {\n return state.updateIn( [ \"meta\", \"paths\", ...pathMethod, \"parameters\" ], fromJS([]), parameters => {\n return parameters.map(param => param.set(\"errors\", fromJS([])))\n })\n },\n\n [SET_RESPONSE]: (state, { payload: { res, path, method } } ) =>{\n let result\n if ( res.error ) {\n result = Object.assign({\n error: true,\n name: res.err.name,\n message: res.err.message,\n statusCode: res.err.statusCode\n }, res.err.response)\n } else {\n result = res\n }\n\n // Ensure headers\n result.headers = result.headers || {}\n\n let newState = state.setIn( [ \"responses\", path, method ], fromJSOrdered(result) )\n\n // ImmutableJS messes up Blob. Needs to reset its value.\n if (win.Blob && res.data instanceof win.Blob) {\n newState = newState.setIn( [ \"responses\", path, method, \"text\" ], res.data)\n }\n return newState\n },\n\n [SET_REQUEST]: (state, { payload: { req, path, method } } ) =>{\n return state.setIn( [ \"requests\", path, method ], fromJSOrdered(req))\n },\n\n [SET_MUTATED_REQUEST]: (state, { payload: { req, path, method } } ) =>{\n return state.setIn( [ \"mutatedRequests\", path, method ], fromJSOrdered(req))\n },\n\n [UPDATE_OPERATION_META_VALUE]: (state, { payload: { path, value, key } }) => {\n // path is a pathMethod tuple... can't change the name now.\n let operationPath = [\"paths\", ...path]\n let metaPath = [\"meta\", \"paths\", ...path]\n\n if(\n !state.getIn([\"json\", ...operationPath])\n && !state.getIn([\"resolved\", ...operationPath])\n && !state.getIn([\"resolvedSubtrees\", ...operationPath])\n ) {\n // do nothing if the operation does not exist\n return state\n }\n\n return state.setIn([...metaPath, key], fromJS(value))\n },\n\n [CLEAR_RESPONSE]: (state, { payload: { path, method } } ) =>{\n return state.deleteIn( [ \"responses\", path, method ])\n },\n\n [CLEAR_REQUEST]: (state, { payload: { path, method } } ) =>{\n return state.deleteIn( [ \"requests\", path, method ])\n },\n\n [SET_SCHEME]: (state, { payload: { scheme, path, method } } ) =>{\n if ( path && method ) {\n return state.setIn( [ \"scheme\", path, method ], scheme)\n }\n\n if (!path && !method) {\n return state.setIn( [ \"scheme\", \"_defaultScheme\" ], scheme)\n }\n\n }\n\n}\n","import { createSelector } from \"reselect\"\nimport { sorters } from \"core/utils\"\nimport { fromJS, Set, Map, OrderedMap, List } from \"immutable\"\nimport { paramToIdentifier } from \"../../utils\"\n\nconst DEFAULT_TAG = \"default\"\n\nconst OPERATION_METHODS = [\n \"get\", \"put\", \"post\", \"delete\", \"options\", \"head\", \"patch\", \"trace\"\n]\n\nconst state = state => {\n return state || Map()\n}\n\nexport const lastError = createSelector(\n state,\n spec => spec.get(\"lastError\")\n)\n\nexport const url = createSelector(\n state,\n spec => spec.get(\"url\")\n)\n\nexport const specStr = createSelector(\n state,\n spec => spec.get(\"spec\") || \"\"\n)\n\nexport const specSource = createSelector(\n state,\n spec => spec.get(\"specSource\") || \"not-editor\"\n)\n\nexport const specJson = createSelector(\n state,\n spec => spec.get(\"json\", Map())\n)\n\nexport const specJS = createSelector(\n specJson,\n (spec) => spec.toJS()\n)\n\nexport const specResolved = createSelector(\n state,\n spec => spec.get(\"resolved\", Map())\n)\n\nexport const specResolvedSubtree = (state, path) => {\n return state.getIn([\"resolvedSubtrees\", ...path], undefined)\n}\n\nconst mergerFn = (oldVal, newVal) => {\n if(Map.isMap(oldVal) && Map.isMap(newVal)) {\n if(newVal.get(\"$$ref\")) {\n // resolver artifacts indicated that this key was directly resolved\n // so we should drop the old value entirely\n return newVal\n }\n\n return OrderedMap().mergeWith(\n mergerFn,\n oldVal,\n newVal\n )\n }\n\n return newVal\n}\n\nexport const specJsonWithResolvedSubtrees = createSelector(\n state,\n spec => OrderedMap().mergeWith(\n mergerFn,\n spec.get(\"json\"),\n spec.get(\"resolvedSubtrees\")\n )\n)\n\n// Default Spec ( as an object )\nexport const spec = state => {\n let res = specJson(state)\n return res\n}\n\nexport const isOAS3 = createSelector(\n // isOAS3 is stubbed out here to work around an issue with injecting more selectors\n // in the OAS3 plugin, and to ensure that the function is always available.\n // It's not perfect, but our hybrid (core+plugin code) implementation for OAS3\n // needs this. //KS\n spec,\n\t() => false\n)\n\nexport const info = createSelector(\n spec,\n\tspec => returnSelfOrNewMap(spec && spec.get(\"info\"))\n)\n\nexport const externalDocs = createSelector(\n spec,\n\tspec => returnSelfOrNewMap(spec && spec.get(\"externalDocs\"))\n)\n\nexport const version = createSelector(\n\tinfo,\n\tinfo => info && info.get(\"version\")\n)\n\nexport const semver = createSelector(\n\tversion,\n\tversion => /v?([0-9]*)\\.([0-9]*)\\.([0-9]*)/i.exec(version).slice(1)\n)\n\nexport const paths = createSelector(\n\tspecJsonWithResolvedSubtrees,\n\tspec => spec.get(\"paths\")\n)\n\nexport const validOperationMethods = createSelector(() => [\"get\", \"put\", \"post\", \"delete\", \"options\", \"head\", \"patch\"])\n\nexport const operations = createSelector(\n paths,\n paths => {\n if(!paths || paths.size < 1)\n return List()\n\n let list = List()\n\n if(!paths || !paths.forEach) {\n return List()\n }\n\n paths.forEach((path, pathName) => {\n if(!path || !path.forEach) {\n return {}\n }\n path.forEach((operation, method) => {\n if(OPERATION_METHODS.indexOf(method) < 0) {\n return\n }\n list = list.push(fromJS({\n path: pathName,\n method,\n operation,\n id: `${method}-${pathName}`\n }))\n })\n })\n\n return list\n }\n)\n\nexport const consumes = createSelector(\n spec,\n spec => Set(spec.get(\"consumes\"))\n)\n\nexport const produces = createSelector(\n spec,\n spec => Set(spec.get(\"produces\"))\n)\n\nexport const security = createSelector(\n spec,\n spec => spec.get(\"security\", List())\n)\n\nexport const securityDefinitions = createSelector(\n spec,\n spec => spec.get(\"securityDefinitions\")\n)\n\n\nexport const findDefinition = ( state, name ) => {\n const resolvedRes = state.getIn([\"resolvedSubtrees\", \"definitions\", name], null)\n const unresolvedRes = state.getIn([\"json\", \"definitions\", name], null)\n return resolvedRes || unresolvedRes || null\n}\n\nexport const definitions = createSelector(\n spec,\n spec => {\n const res = spec.get(\"definitions\")\n return Map.isMap(res) ? res : Map()\n }\n)\n\nexport const basePath = createSelector(\n spec,\n spec => spec.get(\"basePath\")\n)\n\nexport const host = createSelector(\n spec,\n spec => spec.get(\"host\")\n)\n\nexport const schemes = createSelector(\n spec,\n spec => spec.get(\"schemes\", Map())\n)\n\nexport const operationsWithRootInherited = createSelector(\n operations,\n consumes,\n produces,\n (operations, consumes, produces) => {\n return operations.map( ops => ops.update(\"operation\", op => {\n if(op) {\n if(!Map.isMap(op)) { return }\n return op.withMutations( op => {\n if ( !op.get(\"consumes\") ) {\n op.update(\"consumes\", a => Set(a).merge(consumes))\n }\n if ( !op.get(\"produces\") ) {\n op.update(\"produces\", a => Set(a).merge(produces))\n }\n return op\n })\n } else {\n // return something with Immutable methods\n return Map()\n }\n\n }))\n }\n)\n\nexport const tags = createSelector(\n spec,\n json => {\n const tags = json.get(\"tags\", List())\n return List.isList(tags) ? tags.filter(tag => Map.isMap(tag)) : List()\n }\n)\n\nexport const tagDetails = (state, tag) => {\n let currentTags = tags(state) || List()\n return currentTags.filter(Map.isMap).find(t => t.get(\"name\") === tag, Map())\n}\n\nexport const operationsWithTags = createSelector(\n operationsWithRootInherited,\n tags,\n (operations, tags) => {\n return operations.reduce( (taggedMap, op) => {\n let tags = Set(op.getIn([\"operation\",\"tags\"]))\n if(tags.count() < 1)\n return taggedMap.update(DEFAULT_TAG, List(), ar => ar.push(op))\n return tags.reduce( (res, tag) => res.update(tag, List(), (ar) => ar.push(op)), taggedMap )\n }, tags.reduce( (taggedMap, tag) => {\n return taggedMap.set(tag.get(\"name\"), List())\n } , OrderedMap()))\n }\n)\n\nexport const taggedOperations = (state) => ({ getConfigs }) => {\n let { tagsSorter, operationsSorter } = getConfigs()\n return operationsWithTags(state)\n .sortBy(\n (val, key) => key, // get the name of the tag to be passed to the sorter\n (tagA, tagB) => {\n let sortFn = (typeof tagsSorter === \"function\" ? tagsSorter : sorters.tagsSorter[ tagsSorter ])\n return (!sortFn ? null : sortFn(tagA, tagB))\n }\n )\n .map((ops, tag) => {\n let sortFn = (typeof operationsSorter === \"function\" ? operationsSorter : sorters.operationsSorter[ operationsSorter ])\n let operations = (!sortFn ? ops : ops.sort(sortFn))\n\n return Map({ tagDetails: tagDetails(state, tag), operations: operations })\n })\n}\n\nexport const responses = createSelector(\n state,\n state => state.get( \"responses\", Map() )\n)\n\nexport const requests = createSelector(\n state,\n state => state.get( \"requests\", Map() )\n)\n\nexport const mutatedRequests = createSelector(\n state,\n state => state.get( \"mutatedRequests\", Map() )\n)\n\nexport const responseFor = (state, path, method) => {\n return responses(state).getIn([path, method], null)\n}\n\nexport const requestFor = (state, path, method) => {\n return requests(state).getIn([path, method], null)\n}\n\nexport const mutatedRequestFor = (state, path, method) => {\n return mutatedRequests(state).getIn([path, method], null)\n}\n\nexport const allowTryItOutFor = () => {\n // This is just a hook for now.\n return true\n}\n\nexport const parameterWithMetaByIdentity = (state, pathMethod, param) => {\n const opParams = specJsonWithResolvedSubtrees(state).getIn([\"paths\", ...pathMethod, \"parameters\"], OrderedMap())\n const metaParams = state.getIn([\"meta\", \"paths\", ...pathMethod, \"parameters\"], OrderedMap())\n\n const mergedParams = opParams.map((currentParam) => {\n const inNameKeyedMeta = metaParams.get(`${param.get(\"in\")}.${param.get(\"name\")}`)\n const hashKeyedMeta = metaParams.get(`${param.get(\"in\")}.${param.get(\"name\")}.hash-${param.hashCode()}`)\n return OrderedMap().merge(\n currentParam,\n inNameKeyedMeta,\n hashKeyedMeta\n )\n })\n return mergedParams.find(curr => curr.get(\"in\") === param.get(\"in\") && curr.get(\"name\") === param.get(\"name\"), OrderedMap())\n}\n\nexport const parameterInclusionSettingFor = (state, pathMethod, paramName, paramIn) => {\n const paramKey = `${paramIn}.${paramName}`\n return state.getIn([\"meta\", \"paths\", ...pathMethod, \"parameter_inclusions\", paramKey], false)\n}\n\n\nexport const parameterWithMeta = (state, pathMethod, paramName, paramIn) => {\n const opParams = specJsonWithResolvedSubtrees(state).getIn([\"paths\", ...pathMethod, \"parameters\"], OrderedMap())\n const currentParam = opParams.find(param => param.get(\"in\") === paramIn && param.get(\"name\") === paramName, OrderedMap())\n return parameterWithMetaByIdentity(state, pathMethod, currentParam)\n}\n\nexport const operationWithMeta = (state, path, method) => {\n const op = specJsonWithResolvedSubtrees(state).getIn([\"paths\", path, method], OrderedMap())\n const meta = state.getIn([\"meta\", \"paths\", path, method], OrderedMap())\n\n const mergedParams = op.get(\"parameters\", List()).map((param) => {\n return parameterWithMetaByIdentity(state, [path, method], param)\n })\n\n return OrderedMap()\n .merge(op, meta)\n .set(\"parameters\", mergedParams)\n}\n\n// Get the parameter value by parameter name\nexport function getParameter(state, pathMethod, name, inType) {\n pathMethod = pathMethod || []\n let params = state.getIn([\"meta\", \"paths\", ...pathMethod, \"parameters\"], fromJS([]))\n return params.find( (p) => {\n return Map.isMap(p) && p.get(\"name\") === name && p.get(\"in\") === inType\n }) || Map() // Always return a map\n}\n\nexport const hasHost = createSelector(\n spec,\n spec => {\n const host = spec.get(\"host\")\n return typeof host === \"string\" && host.length > 0 && host[0] !== \"/\"\n }\n)\n\n// Get the parameter values, that the user filled out\nexport function parameterValues(state, pathMethod, isXml) {\n pathMethod = pathMethod || []\n let paramValues = operationWithMeta(state, ...pathMethod).get(\"parameters\", List())\n return paramValues.reduce( (hash, p) => {\n let value = isXml && p.get(\"in\") === \"body\" ? p.get(\"value_xml\") : p.get(\"value\")\n return hash.set(paramToIdentifier(p, { allowHashes: false }), value)\n }, fromJS({}))\n}\n\n// True if any parameter includes `in: ?`\nexport function parametersIncludeIn(parameters, inValue=\"\") {\n if(List.isList(parameters)) {\n return parameters.some( p => Map.isMap(p) && p.get(\"in\") === inValue )\n }\n}\n\n// True if any parameter includes `type: ?`\nexport function parametersIncludeType(parameters, typeValue=\"\") {\n if(List.isList(parameters)) {\n return parameters.some( p => Map.isMap(p) && p.get(\"type\") === typeValue )\n }\n}\n\n// Get the consumes/produces value that the user selected\nexport function contentTypeValues(state, pathMethod) {\n pathMethod = pathMethod || []\n let op = specJsonWithResolvedSubtrees(state).getIn([\"paths\", ...pathMethod], fromJS({}))\n let meta = state.getIn([\"meta\", \"paths\", ...pathMethod], fromJS({}))\n let producesValue = currentProducesFor(state, pathMethod)\n\n const parameters = op.get(\"parameters\") || new List()\n\n const requestContentType = (\n meta.get(\"consumes_value\") ? meta.get(\"consumes_value\")\n : parametersIncludeType(parameters, \"file\") ? \"multipart/form-data\"\n : parametersIncludeType(parameters, \"formData\") ? \"application/x-www-form-urlencoded\"\n : undefined\n )\n\n return fromJS({\n requestContentType,\n responseContentType: producesValue\n })\n}\n\n// Get the currently selected produces value for an operation\nexport function currentProducesFor(state, pathMethod) {\n pathMethod = pathMethod || []\n\n const operation = specJsonWithResolvedSubtrees(state).getIn([ \"paths\", ...pathMethod], null)\n\n if(operation === null) {\n // return nothing if the operation does not exist\n return\n }\n\n const currentProducesValue = state.getIn([\"meta\", \"paths\", ...pathMethod, \"produces_value\"], null)\n const firstProducesArrayItem = operation.getIn([\"produces\", 0], null)\n\n return currentProducesValue || firstProducesArrayItem || \"application/json\"\n\n}\n\n// Get the produces options for an operation\nexport function producesOptionsFor(state, pathMethod) {\n pathMethod = pathMethod || []\n\n const spec = specJsonWithResolvedSubtrees(state)\n const operation = spec.getIn([ \"paths\", ...pathMethod], null)\n\n if(operation === null) {\n // return nothing if the operation does not exist\n return\n }\n\n const [path] = pathMethod\n\n const operationProduces = operation.get(\"produces\", null)\n const pathItemProduces = spec.getIn([\"paths\", path, \"produces\"], null)\n const globalProduces = spec.getIn([\"produces\"], null)\n\n return operationProduces || pathItemProduces || globalProduces\n}\n\n// Get the consumes options for an operation\nexport function consumesOptionsFor(state, pathMethod) {\n pathMethod = pathMethod || []\n\n const spec = specJsonWithResolvedSubtrees(state)\n const operation = spec.getIn([\"paths\", ...pathMethod], null)\n\n if (operation === null) {\n // return nothing if the operation does not exist\n return\n }\n\n const [path] = pathMethod\n\n const operationConsumes = operation.get(\"consumes\", null)\n const pathItemConsumes = spec.getIn([\"paths\", path, \"consumes\"], null)\n const globalConsumes = spec.getIn([\"consumes\"], null)\n\n return operationConsumes || pathItemConsumes || globalConsumes\n}\n\nexport const operationScheme = ( state, path, method ) => {\n let url = state.get(\"url\")\n let matchResult = url.match(/^([a-z][a-z0-9+\\-.]*):/)\n let urlScheme = Array.isArray(matchResult) ? matchResult[1] : null\n\n return state.getIn([\"scheme\", path, method]) || state.getIn([\"scheme\", \"_defaultScheme\"]) || urlScheme || \"\"\n}\n\nexport const canExecuteScheme = ( state, path, method ) => {\n return [\"http\", \"https\"].indexOf(operationScheme(state, path, method)) > -1\n}\n\nexport const validationErrors = (state, pathMethod) => {\n pathMethod = pathMethod || []\n let paramValues = state.getIn([\"meta\", \"paths\", ...pathMethod, \"parameters\"], fromJS([]))\n const result = []\n\n paramValues.forEach( (p) => {\n let errors = p.get(\"errors\")\n if ( errors && errors.count() ) {\n errors.forEach( e => result.push(e))\n }\n })\n\n return result\n}\n\nexport const validateBeforeExecute = (state, pathMethod) => {\n return validationErrors(state, pathMethod).length === 0\n}\n\nexport const getOAS3RequiredRequestBodyContentType = (state, pathMethod) => {\n let requiredObj = {\n requestBody: false,\n requestContentType: {}\n }\n let requestBody = state.getIn([\"resolvedSubtrees\", \"paths\", ...pathMethod, \"requestBody\"], fromJS([]))\n if (requestBody.size < 1) {\n return requiredObj\n }\n if (requestBody.getIn([\"required\"])) {\n requiredObj.requestBody = requestBody.getIn([\"required\"])\n }\n requestBody.getIn([\"content\"]).entrySeq().forEach((contentType) => { // e.g application/json\n const key = contentType[0]\n if (contentType[1].getIn([\"schema\", \"required\"])) {\n const val = contentType[1].getIn([\"schema\", \"required\"]).toJS()\n requiredObj.requestContentType[key] = val\n }\n })\n return requiredObj\n}\n\nexport const isMediaTypeSchemaPropertiesEqual = ( state, pathMethod, currentMediaType, targetMediaType) => {\n if((currentMediaType || targetMediaType) && currentMediaType === targetMediaType ) {\n return true\n }\n let requestBodyContent = state.getIn([\"resolvedSubtrees\", \"paths\", ...pathMethod, \"requestBody\", \"content\"], fromJS([]))\n if (requestBodyContent.size < 2 || !currentMediaType || !targetMediaType) {\n // nothing to compare\n return false\n }\n let currentMediaTypeSchemaProperties = requestBodyContent.getIn([currentMediaType, \"schema\", \"properties\"], fromJS([]))\n let targetMediaTypeSchemaProperties = requestBodyContent.getIn([targetMediaType, \"schema\", \"properties\"], fromJS([]))\n return !!currentMediaTypeSchemaProperties.equals(targetMediaTypeSchemaProperties)\n}\n\nfunction returnSelfOrNewMap(obj) {\n // returns obj if obj is an Immutable map, else returns a new Map\n return Map.isMap(obj) ? obj : new Map()\n}\n","import get from \"lodash/get\"\n\nexport const updateSpec = (ori, {specActions}) => (...args) => {\n ori(...args)\n specActions.parseToJson(...args)\n}\n\nexport const updateJsonSpec = (ori, {specActions}) => (...args) => {\n ori(...args)\n\n specActions.invalidateResolvedSubtreeCache()\n\n // Trigger resolution of any path-level $refs.\n const [json] = args\n const pathItems = get(json, [\"paths\"]) || {}\n const pathItemKeys = Object.keys(pathItems)\n\n pathItemKeys.forEach(k => {\n const val = get(pathItems, [k])\n\n if(val.$ref) {\n specActions.requestResolvedSubtree([\"paths\", k])\n }\n })\n\n // Trigger resolution of any securitySchemes-level $refs.\n specActions.requestResolvedSubtree([\"components\", \"securitySchemes\"])\n}\n\n// Log the request ( just for debugging, shouldn't affect prod )\nexport const executeRequest = (ori, { specActions }) => (req) => {\n specActions.logRequest(req)\n return ori(req)\n}\n\nexport const validateParams = (ori, { specSelectors }) => (req) => {\n return ori(req, specSelectors.isOAS3())\n}\n","export const loaded = (ori, system) => (...args) => {\n ori(...args)\n const value = system.getConfigs().withCredentials\n \n if(value !== undefined) {\n system.fn.fetch.withCredentials = typeof value === \"string\" ? (value === \"true\") : !!value\n }\n}\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_swagger_client_es_resolver_strategies_generic_08dd5200__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_swagger_client_es_resolver_strategies_openapi_2_ff6e79cf__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_swagger_client_es_resolver_strategies_openapi_3_0_2fa0ff7c__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_swagger_client_es_resolver_strategies_openapi_3_1_apidom_5e628d39__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"makeResolve\"]: () => __WEBPACK_EXTERNAL_MODULE_swagger_client_es_resolver_f879c638__.makeResolve });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"buildRequest\"]: () => __WEBPACK_EXTERNAL_MODULE_swagger_client_es_execute_d486d3d6__.buildRequest, [\"execute\"]: () => __WEBPACK_EXTERNAL_MODULE_swagger_client_es_execute_d486d3d6__.execute });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_swagger_client_es_http_69655560__[\"default\"], [\"makeHttp\"]: () => __WEBPACK_EXTERNAL_MODULE_swagger_client_es_http_69655560__.makeHttp, [\"serializeRes\"]: () => __WEBPACK_EXTERNAL_MODULE_swagger_client_es_http_69655560__.serializeRes });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"makeResolveSubtree\"]: () => __WEBPACK_EXTERNAL_MODULE_swagger_client_es_subtree_resolver_741cb9d9__.makeResolveSubtree });","import genericResolveStrategy from \"swagger-client/es/resolver/strategies/generic\"\nimport openApi2ResolveStrategy from \"swagger-client/es/resolver/strategies/openapi-2\"\nimport openApi30ResolveStrategy from \"swagger-client/es/resolver/strategies/openapi-3-0\"\nimport openApi31ApiDOMResolveStrategy from \"swagger-client/es/resolver/strategies/openapi-3-1-apidom\"\nimport { makeResolve } from \"swagger-client/es/resolver\"\nimport { execute, buildRequest } from \"swagger-client/es/execute\"\nimport Http, { makeHttp, serializeRes } from \"swagger-client/es/http\"\nimport { makeResolveSubtree } from \"swagger-client/es/subtree-resolver\"\nimport { opId } from \"swagger-client/es/helpers\"\nimport { loaded } from \"./configs-wrap-actions\"\n\nexport default function({ configs, getConfigs }) {\n return {\n fn: {\n fetch: makeHttp(Http, configs.preFetch, configs.postFetch),\n buildRequest,\n execute,\n resolve: makeResolve({\n strategies: [\n openApi31ApiDOMResolveStrategy,\n openApi30ResolveStrategy,\n openApi2ResolveStrategy,\n genericResolveStrategy,\n ],\n }),\n resolveSubtree: async (obj, path, options = {}) => {\n const freshConfigs = getConfigs()\n const defaultOptions = {\n modelPropertyMacro: freshConfigs.modelPropertyMacro,\n parameterMacro: freshConfigs.parameterMacro,\n requestInterceptor: freshConfigs.requestInterceptor,\n responseInterceptor: freshConfigs.responseInterceptor,\n strategies: [\n openApi31ApiDOMResolveStrategy,\n openApi30ResolveStrategy,\n openApi2ResolveStrategy,\n genericResolveStrategy,\n ],\n }\n\n return makeResolveSubtree(defaultOptions)(obj, path, options)\n },\n serializeRes,\n opId\n },\n statePlugins: {\n configs: {\n wrapActions: {\n loaded,\n }\n }\n },\n }\n}\n","import { shallowEqualKeys } from \"core/utils\"\n\nexport default function() {\n return {\n fn: { shallowEqualKeys }\n }\n}\n","export const getDisplayName = (WrappedComponent) => WrappedComponent.displayName || WrappedComponent.name || \"Component\"\n","import { memoize } from \"core/utils\"\n\nimport { getComponent, render, withMappedContainer } from \"./root-injects\"\nimport { getDisplayName } from \"./fn\"\nimport memoizeN from \"../../../helpers/memoizeN\"\n\nconst memoizeForGetComponent = (fn) => {\n const resolver = (...args) => JSON.stringify(args)\n return memoize(fn, resolver)\n}\n\nconst memoizeForWithMappedContainer = (fn) => {\n const resolver = (...args) => args\n return memoizeN(fn, resolver)\n}\n\nconst viewPlugin = ({getComponents, getStore, getSystem}) => {\n // getComponent should be passed into makeMappedContainer, _already_ memoized... otherwise we have a big performance hit ( think, really big )\n const memGetComponent = memoizeForGetComponent(getComponent(getSystem, getStore, getComponents))\n const memMakeMappedContainer = memoizeForWithMappedContainer(withMappedContainer(getSystem, getStore, memGetComponent))\n\n return {\n rootInjects: {\n getComponent: memGetComponent,\n makeMappedContainer: memMakeMappedContainer,\n render: render(getSystem, getStore, getComponent, getComponents),\n },\n fn: {\n getDisplayName,\n },\n }\n}\n\nexport default viewPlugin\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_dom_7dac9eee__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"Provider\"]: () => __WEBPACK_EXTERNAL_MODULE_react_redux_87be03b0__.Provider, [\"connect\"]: () => __WEBPACK_EXTERNAL_MODULE_react_redux_87be03b0__.connect });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_omit_d930e0f3__[\"default\"] });","import React, { Component } from \"react\"\nimport ReactDOM from \"react-dom\"\nimport { compose } from \"redux\"\nimport { connect, Provider } from \"react-redux\"\nimport omit from \"lodash/omit\"\nimport identity from \"lodash/identity\"\n\nconst withSystem = (getSystem) => (WrappedComponent) => {\n const { fn } = getSystem()\n\n class WithSystem extends Component {\n render() {\n return \n }\n }\n WithSystem.displayName = `WithSystem(${fn.getDisplayName(WrappedComponent)})`\n return WithSystem\n}\n\nconst withRoot = (getSystem, reduxStore) => (WrappedComponent) => {\n const { fn } = getSystem()\n\n class WithRoot extends Component {\n render() {\n return (\n \n \n \n )\n }\n }\n WithRoot.displayName = `WithRoot(${fn.getDisplayName(WrappedComponent)})`\n return WithRoot\n}\n\nconst withConnect = (getSystem, WrappedComponent, reduxStore) => {\n const mapStateToProps = (state, ownProps) => {\n const props = {...ownProps, ...getSystem()}\n const customMapStateToProps = WrappedComponent.prototype?.mapStateToProps || (state => ({state}))\n return customMapStateToProps(state, props)\n }\n\n return compose(\n reduxStore ? withRoot(getSystem, reduxStore) : identity,\n connect(mapStateToProps),\n withSystem(getSystem),\n )(WrappedComponent)\n}\n\nconst handleProps = (getSystem, mapping, props, oldProps) => {\n for (const prop in mapping) {\n const fn = mapping[prop]\n\n if (typeof fn === \"function\") {\n fn(props[prop], oldProps[prop], getSystem())\n }\n }\n}\n\nexport const withMappedContainer = (getSystem, getStore, memGetComponent) => (componentName, mapping) => {\n const { fn } = getSystem()\n const WrappedComponent = memGetComponent(componentName, \"root\")\n\n class WithMappedContainer extends Component {\n constructor(props, context) {\n super(props, context)\n handleProps(getSystem, mapping, props, {})\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n handleProps(getSystem, mapping, nextProps, this.props)\n }\n\n render() {\n const cleanProps = omit(this.props, mapping ? Object.keys(mapping) : [])\n return \n }\n }\n WithMappedContainer.displayName = `WithMappedContainer(${fn.getDisplayName(WrappedComponent)})`\n return WithMappedContainer\n}\n\nexport const render = (getSystem, getStore, getComponent, getComponents) => (domNode) => {\n const App = getComponent(getSystem, getStore, getComponents)(\"App\", \"root\")\n ReactDOM.render(, domNode)\n}\n\nexport const getComponent = (getSystem, getStore, getComponents) => (componentName, container, config = {}) => {\n\n if (typeof componentName !== \"string\")\n throw new TypeError(\"Need a string, to fetch a component. Was given a \" + typeof componentName)\n\n // getComponent has a config object as a third, optional parameter\n // using the config object requires the presence of the second parameter, container\n // e.g. getComponent(\"JsonSchema_string_whatever\", false, { failSilently: true })\n const component = getComponents(componentName)\n\n if (!component) {\n if (!config.failSilently) {\n getSystem().log.warn(\"Could not find component:\", componentName)\n }\n return null\n }\n\n if(!container) {\n return component\n }\n\n if(container === \"root\") {\n return withConnect(getSystem, component, getStore())\n }\n\n // container == truthy\n return withConnect(getSystem, component)\n}\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_syntax_highlighter_dist_esm_light_746e1958__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_syntax_highlighter_dist_esm_languages_hljs_javascript_e22911f7__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_syntax_highlighter_dist_esm_languages_hljs_json_b876afc5__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_syntax_highlighter_dist_esm_languages_hljs_xml_a81c807b__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_syntax_highlighter_dist_esm_languages_hljs_bash_1621c621__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_syntax_highlighter_dist_esm_languages_hljs_yaml_02838f34__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_syntax_highlighter_dist_esm_languages_hljs_http_4e924b23__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_syntax_highlighter_dist_esm_languages_hljs_powershell_d51eb4f6__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_syntax_highlighter_dist_esm_styles_hljs_agate_99a46aa2__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_syntax_highlighter_dist_esm_styles_hljs_arta_570691fc__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_syntax_highlighter_dist_esm_styles_hljs_monokai_2529bafb__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_syntax_highlighter_dist_esm_styles_hljs_nord_5bfa1099__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_syntax_highlighter_dist_esm_styles_hljs_obsidian_a278dd52__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_syntax_highlighter_dist_esm_styles_hljs_tomorrow_night_63765df9__[\"default\"] });","import SyntaxHighlighter from \"react-syntax-highlighter/dist/esm/light\"\nimport js from \"react-syntax-highlighter/dist/esm/languages/hljs/javascript\"\nimport json from \"react-syntax-highlighter/dist/esm/languages/hljs/json\"\nimport xml from \"react-syntax-highlighter/dist/esm/languages/hljs/xml\"\nimport bash from \"react-syntax-highlighter/dist/esm/languages/hljs/bash\"\nimport yaml from \"react-syntax-highlighter/dist/esm/languages/hljs/yaml\"\nimport http from \"react-syntax-highlighter/dist/esm/languages/hljs/http\"\nimport powershell from \"react-syntax-highlighter/dist/esm/languages/hljs/powershell\"\nimport javascript from \"react-syntax-highlighter/dist/esm/languages/hljs/javascript\"\n\nimport agate from \"react-syntax-highlighter/dist/esm/styles/hljs/agate\"\nimport arta from \"react-syntax-highlighter/dist/esm/styles/hljs/arta\"\nimport monokai from \"react-syntax-highlighter/dist/esm/styles/hljs/monokai\"\nimport nord from \"react-syntax-highlighter/dist/esm/styles/hljs/nord\"\nimport obsidian from \"react-syntax-highlighter/dist/esm/styles/hljs/obsidian\"\nimport tomorrowNight from \"react-syntax-highlighter/dist/esm/styles/hljs/tomorrow-night\"\n\nSyntaxHighlighter.registerLanguage(\"json\", json)\nSyntaxHighlighter.registerLanguage(\"js\", js)\nSyntaxHighlighter.registerLanguage(\"xml\", xml)\nSyntaxHighlighter.registerLanguage(\"yaml\", yaml)\nSyntaxHighlighter.registerLanguage(\"http\", http)\nSyntaxHighlighter.registerLanguage(\"bash\", bash)\nSyntaxHighlighter.registerLanguage(\"powershell\", powershell)\nSyntaxHighlighter.registerLanguage(\"javascript\", javascript)\n\nconst styles = {agate, arta, monokai, nord, obsidian, \"tomorrow-night\": tomorrowNight}\nexport const availableStyles = Object.keys(styles)\n\nexport const getStyle = name => {\n if (!availableStyles.includes(name)) {\n console.warn(`Request style '${name}' is not available, returning default instead`)\n return agate\n }\n return styles[name]\n}\n\nexport {SyntaxHighlighter, styles}\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_starts_with_a4b73998__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"sanitizeUrl\"]: () => __WEBPACK_EXTERNAL_MODULE__braintree_sanitize_url_2340607f__.sanitizeUrl });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_camelCase_81fadc19__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_upperFirst_9993ecb4__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_find_e8ecc2cb__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_eq_b41b823a__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_css_escape_2d301448__[\"default\"] });","/*\n ATTENTION! This file (but not the functions within) is deprecated.\n\n You should probably add a new file to `./helpers/` instead of adding a new\n function here.\n\n One-function-per-file is a better pattern than what we have here.\n\n If you're refactoring something in here, feel free to break it out to a file\n in `./helpers` if you have the time.\n*/\nimport Im, { fromJS, Set } from \"immutable\"\nimport { sanitizeUrl as braintreeSanitizeUrl } from \"@braintree/sanitize-url\"\nimport camelCase from \"lodash/camelCase\"\nimport upperFirst from \"lodash/upperFirst\"\nimport _memoize from \"lodash/memoize\"\nimport find from \"lodash/find\"\nimport some from \"lodash/some\"\nimport eq from \"lodash/eq\"\nimport isFunction from \"lodash/isFunction\"\nimport win from \"./window\"\nimport cssEscape from \"css.escape\"\nimport getParameterSchema from \"../helpers/get-parameter-schema\"\nimport randomBytes from \"randombytes\"\nimport shaJs from \"sha.js\"\n\nconst DEFAULT_RESPONSE_KEY = \"default\"\n\nexport const isImmutable = (maybe) => Im.Iterable.isIterable(maybe)\n\nexport function objectify (thing) {\n if(!isObject(thing))\n return {}\n if(isImmutable(thing))\n return thing.toJS()\n return thing\n}\n\nexport function arrayify (thing) {\n if(!thing)\n return []\n\n if(thing.toArray)\n return thing.toArray()\n\n return normalizeArray(thing)\n}\n\nexport function fromJSOrdered(js) {\n if (isImmutable(js)) {\n return js // Can't do much here\n }\n if (js instanceof win.File) {\n return js\n }\n if (!isObject(js)) {\n return js\n }\n if (Array.isArray(js)) {\n return Im.Seq(js).map(fromJSOrdered).toList()\n }\n if (isFunction(js.entries)) {\n // handle multipart/form-data\n const objWithHashedKeys = createObjWithHashedKeys(js)\n return Im.OrderedMap(objWithHashedKeys).map(fromJSOrdered)\n }\n return Im.OrderedMap(js).map(fromJSOrdered)\n}\n\n/**\n * Convert a FormData object into plain object\n * Append a hashIdx and counter to the key name, if multiple exists\n * if single, key name = \n * if multiple, key name = \n * @example single entry for vegetable\n * fdObj.entries.vegtables: \"carrot\"\n * // returns newObj.vegetables : \"carrot\"\n * @example multiple entries for fruits[]\n * fdObj.entries.fruits[]: \"apple\"\n * // returns newObj.fruits[]_**[]1 : \"apple\"\n * fdObj.entries.fruits[]: \"banana\"\n * // returns newObj.fruits[]_**[]2 : \"banana\"\n * fdObj.entries.fruits[]: \"grape\"\n * // returns newObj.fruits[]_**[]3 : \"grape\"\n * @param {FormData} fdObj - a FormData object\n * @return {Object} - a plain object\n */\nexport function createObjWithHashedKeys (fdObj) {\n if (!isFunction(fdObj.entries)) {\n return fdObj // not a FormData object with iterable\n }\n const newObj = {}\n const hashIdx = \"_**[]\" // our internal identifier\n const trackKeys = {}\n for (let pair of fdObj.entries()) {\n if (!newObj[pair[0]] && !(trackKeys[pair[0]] && trackKeys[pair[0]].containsMultiple)) {\n newObj[pair[0]] = pair[1] // first key name: no hash required\n } else {\n if (!trackKeys[pair[0]]) {\n // initiate tracking key for multiple\n trackKeys[pair[0]] = {\n containsMultiple: true,\n length: 1\n }\n // \"reassign\" first pair to matching hashed format for multiple\n let hashedKeyFirst = `${pair[0]}${hashIdx}${trackKeys[pair[0]].length}`\n newObj[hashedKeyFirst] = newObj[pair[0]]\n // remove non-hashed key of multiple\n delete newObj[pair[0]] // first\n }\n trackKeys[pair[0]].length += 1\n let hashedKeyCurrent = `${pair[0]}${hashIdx}${trackKeys[pair[0]].length}`\n newObj[hashedKeyCurrent] = pair[1]\n }\n }\n return newObj\n}\n\nexport function bindToState(obj, state) {\n\tvar newObj = {}\n\tObject.keys(obj)\n .filter(key => typeof obj[key] === \"function\")\n .forEach(key => newObj[key] = obj[key].bind(null, state))\n\treturn newObj\n}\n\nexport function normalizeArray(arr) {\n if(Array.isArray(arr))\n return arr\n return [arr]\n}\n\nexport function isFn(fn) {\n return typeof fn === \"function\"\n}\n\nexport function isObject(obj) {\n return !!obj && typeof obj === \"object\"\n}\n\nexport function isFunc(thing) {\n return typeof(thing) === \"function\"\n}\n\nexport function isArray(thing) {\n return Array.isArray(thing)\n}\n\n// I've changed memoize libs more than once, so I'm using this a way to make that simpler\nexport const memoize = _memoize\n\nexport function objMap(obj, fn) {\n return Object.keys(obj).reduce((newObj, key) => {\n newObj[key] = fn(obj[key], key)\n return newObj\n }, {})\n}\n\nexport function objReduce(obj, fn) {\n return Object.keys(obj).reduce((newObj, key) => {\n let res = fn(obj[key], key)\n if(res && typeof res === \"object\")\n Object.assign(newObj, res)\n return newObj\n }, {})\n}\n\n// Redux middleware that exposes the system to async actions (like redux-thunk, but with out system instead of (dispatch, getState)\nexport function systemThunkMiddleware(getSystem) {\n return ({ dispatch, getState }) => { // eslint-disable-line no-unused-vars\n return next => action => {\n if (typeof action === \"function\") {\n return action(getSystem())\n }\n\n return next(action)\n }\n }\n}\n\nexport function defaultStatusCode ( responses ) {\n let codes = responses.keySeq()\n return codes.contains(DEFAULT_RESPONSE_KEY) ? DEFAULT_RESPONSE_KEY : codes.filter( key => (key+\"\")[0] === \"2\").sort().first()\n}\n\n\n/**\n * Returns an Immutable List, safely\n * @param {Immutable.Iterable} iterable the iterable to get the key from\n * @param {String|[String]} key either an array of keys, or a single key\n * @returns {Immutable.List} either iterable.get(keys) or an empty Immutable.List\n */\nexport function getList(iterable, keys) {\n if(!Im.Iterable.isIterable(iterable)) {\n return Im.List()\n }\n let val = iterable.getIn(Array.isArray(keys) ? keys : [keys])\n return Im.List.isList(val) ? val : Im.List()\n}\n\n/**\n * Take an immutable map, and convert to a list.\n * Where the keys are merged with the value objects\n * @param {Immutable.Map} map, the map to convert\n * @param {String} key the key to use, when merging the `key`\n * @returns {Immutable.List}\n */\nexport function mapToList(map, keyNames=\"key\", collectedKeys=Im.Map()) {\n if(!Im.Map.isMap(map) || !map.size) {\n return Im.List()\n }\n\n if(!Array.isArray(keyNames)) {\n keyNames = [ keyNames ]\n }\n\n if(keyNames.length < 1) {\n return map.merge(collectedKeys)\n }\n\n // I need to avoid `flatMap` from merging in the Maps, as well as the lists\n let list = Im.List()\n let keyName = keyNames[0]\n for(let entry of map.entries()) {\n let [key, val] = entry\n let nextList = mapToList(val, keyNames.slice(1), collectedKeys.set(keyName, key))\n if(Im.List.isList(nextList)) {\n list = list.concat(nextList)\n } else {\n list = list.push(nextList)\n }\n }\n\n return list\n}\n\nexport function extractFileNameFromContentDispositionHeader(value){\n let patterns = [\n /filename\\*=[^']+'\\w*'\"([^\"]+)\";?/i,\n /filename\\*=[^']+'\\w*'([^;]+);?/i,\n /filename=\"([^;]*);?\"/i,\n /filename=([^;]*);?/i\n ]\n\n let responseFilename\n patterns.some(regex => {\n responseFilename = regex.exec(value)\n return responseFilename !== null\n })\n\n if (responseFilename !== null && responseFilename.length > 1) {\n try {\n return decodeURIComponent(responseFilename[1])\n } catch(e) {\n console.error(e)\n }\n }\n\n return null\n}\n\n// PascalCase, aka UpperCamelCase\nexport function pascalCase(str) {\n return upperFirst(camelCase(str))\n}\n\n// Remove the ext of a filename, and pascalCase it\nexport function pascalCaseFilename(filename) {\n return pascalCase(filename.replace(/\\.[^./]*$/, \"\"))\n}\n\n// Check if ...\n// - new props\n// - If immutable, use .is()\n// - if in explicit objectList, then compare using _.eq\n// - else use ===\nexport const propChecker = (props, nextProps, objectList=[], ignoreList=[]) => {\n\n if(Object.keys(props).length !== Object.keys(nextProps).length) {\n return true\n }\n\n return (\n some(props, (a, name) => {\n if(ignoreList.includes(name)) {\n return false\n }\n let b = nextProps[name]\n\n if(Im.Iterable.isIterable(a)) {\n return !Im.is(a,b)\n }\n\n // Not going to compare objects\n if(typeof a === \"object\" && typeof b === \"object\") {\n return false\n }\n\n return a !== b\n })\n || objectList.some( objectPropName => !eq(props[objectPropName], nextProps[objectPropName])))\n}\n\nexport const validateMaximum = ( val, max ) => {\n if (val > max) {\n return `Value must be less than ${max}`\n }\n}\n\nexport const validateMinimum = ( val, min ) => {\n if (val < min) {\n return `Value must be greater than ${min}`\n }\n}\n\nexport const validateNumber = ( val ) => {\n if (!/^-?\\d+(\\.?\\d+)?$/.test(val)) {\n return \"Value must be a number\"\n }\n}\n\nexport const validateInteger = ( val ) => {\n if (!/^-?\\d+$/.test(val)) {\n return \"Value must be an integer\"\n }\n}\n\nexport const validateFile = ( val ) => {\n if ( val && !(val instanceof win.File) ) {\n return \"Value must be a file\"\n }\n}\n\nexport const validateBoolean = ( val ) => {\n if ( !(val === \"true\" || val === \"false\" || val === true || val === false) ) {\n return \"Value must be a boolean\"\n }\n}\n\nexport const validateString = ( val ) => {\n if ( val && typeof val !== \"string\" ) {\n return \"Value must be a string\"\n }\n}\n\nexport const validateDateTime = (val) => {\n if (isNaN(Date.parse(val))) {\n return \"Value must be a DateTime\"\n }\n}\n\nexport const validateGuid = (val) => {\n val = val.toString().toLowerCase()\n if (!/^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[)}]?$/.test(val)) {\n return \"Value must be a Guid\"\n }\n}\n\nexport const validateMaxLength = (val, max) => {\n if (val.length > max) {\n return `Value must be no longer than ${max} character${max !== 1 ? \"s\" : \"\"}`\n }\n}\n\nexport const validateUniqueItems = (val, uniqueItems) => {\n if (!val) {\n return\n }\n if (uniqueItems === \"true\" || uniqueItems === true) {\n const list = fromJS(val)\n const set = list.toSet()\n const hasDuplicates = val.length > set.size\n if(hasDuplicates) {\n let errorsPerIndex = Set()\n list.forEach((item, i) => {\n if(list.filter(v => isFunc(v.equals) ? v.equals(item) : v === item).size > 1) {\n errorsPerIndex = errorsPerIndex.add(i)\n }\n })\n if(errorsPerIndex.size !== 0) {\n return errorsPerIndex.map(i => ({index: i, error: \"No duplicates allowed.\"})).toArray()\n }\n }\n }\n}\n\nexport const validateMinItems = (val, min) => {\n if (!val && min >= 1 || val && val.length < min) {\n return `Array must contain at least ${min} item${min === 1 ? \"\" : \"s\"}`\n }\n}\n\nexport const validateMaxItems = (val, max) => {\n if (val && val.length > max) {\n return `Array must not contain more then ${max} item${max === 1 ? \"\" : \"s\"}`\n }\n}\n\nexport const validateMinLength = (val, min) => {\n if (val.length < min) {\n return `Value must be at least ${min} character${min !== 1 ? \"s\" : \"\"}`\n }\n}\n\nexport const validatePattern = (val, rxPattern) => {\n var patt = new RegExp(rxPattern)\n if (!patt.test(val)) {\n return \"Value must follow pattern \" + rxPattern\n }\n}\n\nfunction validateValueBySchema(value, schema, requiredByParam, bypassRequiredCheck, parameterContentMediaType) {\n if(!schema) return []\n let errors = []\n let nullable = schema.get(\"nullable\")\n let requiredBySchema = schema.get(\"required\")\n let maximum = schema.get(\"maximum\")\n let minimum = schema.get(\"minimum\")\n let type = schema.get(\"type\")\n let format = schema.get(\"format\")\n let maxLength = schema.get(\"maxLength\")\n let minLength = schema.get(\"minLength\")\n let uniqueItems = schema.get(\"uniqueItems\")\n let maxItems = schema.get(\"maxItems\")\n let minItems = schema.get(\"minItems\")\n let pattern = schema.get(\"pattern\")\n\n const schemaRequiresValue = requiredByParam || requiredBySchema === true\n const hasValue = value !== undefined && value !== null\n const isValidEmpty = !schemaRequiresValue && !hasValue\n\n const needsExplicitConstraintValidation = hasValue && type === \"array\"\n\n const requiresFurtherValidation =\n schemaRequiresValue\n || needsExplicitConstraintValidation\n || !isValidEmpty\n\n const isValidNullable = nullable && value === null\n\n // will not be included in the request or [schema / value] does not [allow / require] further analysis.\n const noFurtherValidationNeeded =\n isValidNullable\n || !type\n || !requiresFurtherValidation\n\n if(noFurtherValidationNeeded) {\n return []\n }\n\n // Further this point the parameter is considered worth to validate\n let stringCheck = type === \"string\" && value\n let arrayCheck = type === \"array\" && Array.isArray(value) && value.length\n let arrayListCheck = type === \"array\" && Im.List.isList(value) && value.count()\n let arrayStringCheck = type === \"array\" && typeof value === \"string\" && value\n let fileCheck = type === \"file\" && value instanceof win.File\n let booleanCheck = type === \"boolean\" && (value || value === false)\n let numberCheck = type === \"number\" && (value || value === 0)\n let integerCheck = type === \"integer\" && (value || value === 0)\n let objectCheck = type === \"object\" && typeof value === \"object\" && value !== null\n let objectStringCheck = type === \"object\" && typeof value === \"string\" && value\n\n const allChecks = [\n stringCheck, arrayCheck, arrayListCheck, arrayStringCheck, fileCheck,\n booleanCheck, numberCheck, integerCheck, objectCheck, objectStringCheck,\n ]\n\n const passedAnyCheck = allChecks.some(v => !!v)\n\n if (schemaRequiresValue && !passedAnyCheck && !bypassRequiredCheck) {\n errors.push(\"Required field is not provided\")\n return errors\n }\n if (\n type === \"object\" &&\n (parameterContentMediaType === null ||\n parameterContentMediaType === \"application/json\")\n ) {\n let objectVal = value\n if(typeof value === \"string\") {\n try {\n objectVal = JSON.parse(value)\n } catch (e) {\n errors.push(\"Parameter string value must be valid JSON\")\n return errors\n }\n }\n if(schema && schema.has(\"required\") && isFunc(requiredBySchema.isList) && requiredBySchema.isList()) {\n requiredBySchema.forEach(key => {\n if(objectVal[key] === undefined) {\n errors.push({ propKey: key, error: \"Required property not found\" })\n }\n })\n }\n if(schema && schema.has(\"properties\")) {\n schema.get(\"properties\").forEach((val, key) => {\n const errs = validateValueBySchema(objectVal[key], val, false, bypassRequiredCheck, parameterContentMediaType)\n errors.push(...errs\n .map((error) => ({ propKey: key, error })))\n })\n }\n }\n\n if (pattern) {\n let err = validatePattern(value, pattern)\n if (err) errors.push(err)\n }\n\n if (minItems) {\n if (type === \"array\") {\n let err = validateMinItems(value, minItems)\n if (err) errors.push(err)\n }\n }\n\n if (maxItems) {\n if (type === \"array\") {\n let err = validateMaxItems(value, maxItems)\n if (err) errors.push({ needRemove: true, error: err })\n }\n }\n\n if (uniqueItems) {\n if (type === \"array\") {\n let errorPerItem = validateUniqueItems(value, uniqueItems)\n if (errorPerItem) errors.push(...errorPerItem)\n }\n }\n\n if (maxLength || maxLength === 0) {\n let err = validateMaxLength(value, maxLength)\n if (err) errors.push(err)\n }\n\n if (minLength) {\n let err = validateMinLength(value, minLength)\n if (err) errors.push(err)\n }\n\n if (maximum || maximum === 0) {\n let err = validateMaximum(value, maximum)\n if (err) errors.push(err)\n }\n\n if (minimum || minimum === 0) {\n let err = validateMinimum(value, minimum)\n if (err) errors.push(err)\n }\n\n if (type === \"string\") {\n let err\n if (format === \"date-time\") {\n err = validateDateTime(value)\n } else if (format === \"uuid\") {\n err = validateGuid(value)\n } else {\n err = validateString(value)\n }\n if (!err) return errors\n errors.push(err)\n } else if (type === \"boolean\") {\n let err = validateBoolean(value)\n if (!err) return errors\n errors.push(err)\n } else if (type === \"number\") {\n let err = validateNumber(value)\n if (!err) return errors\n errors.push(err)\n } else if (type === \"integer\") {\n let err = validateInteger(value)\n if (!err) return errors\n errors.push(err)\n } else if (type === \"array\") {\n if (!(arrayCheck || arrayListCheck)) {\n return errors\n }\n if(value) {\n value.forEach((item, i) => {\n const errs = validateValueBySchema(item, schema.get(\"items\"), false, bypassRequiredCheck, parameterContentMediaType)\n errors.push(...errs\n .map((err) => ({ index: i, error: err })))\n })\n }\n } else if (type === \"file\") {\n let err = validateFile(value)\n if (!err) return errors\n errors.push(err)\n }\n\n return errors\n}\n\n// validation of parameters before execute\nexport const validateParam = (param, value, { isOAS3 = false, bypassRequiredCheck = false } = {}) => {\n\n let paramRequired = param.get(\"required\")\n\n let {\n schema: paramDetails,\n parameterContentMediaType\n } = getParameterSchema(param, { isOAS3 })\n\n return validateValueBySchema(value, paramDetails, paramRequired, bypassRequiredCheck, parameterContentMediaType)\n}\n\nexport const parseSearch = () => {\n let map = {}\n let search = win.location.search\n\n if(!search)\n return {}\n\n if ( search != \"\" ) {\n let params = search.substr(1).split(\"&\")\n\n for (let i in params) {\n if (!Object.prototype.hasOwnProperty.call(params, i)) {\n continue\n }\n i = params[i].split(\"=\")\n map[decodeURIComponent(i[0])] = (i[1] && decodeURIComponent(i[1])) || \"\"\n }\n }\n\n return map\n}\n\nexport const serializeSearch = (searchMap) => {\n return Object.keys(searchMap).map(k => {\n return encodeURIComponent(k) + \"=\" + encodeURIComponent(searchMap[k])\n }).join(\"&\")\n}\n\nexport const btoa = (str) => {\n let buffer\n\n if (str instanceof Buffer) {\n buffer = str\n } else {\n buffer = Buffer.from(str.toString(), \"utf-8\")\n }\n\n return buffer.toString(\"base64\")\n}\n\nexport const sorters = {\n operationsSorter: {\n alpha: (a, b) => a.get(\"path\").localeCompare(b.get(\"path\")),\n method: (a, b) => a.get(\"method\").localeCompare(b.get(\"method\"))\n },\n tagsSorter: {\n alpha: (a, b) => a.localeCompare(b)\n }\n}\n\nexport const buildFormData = (data) => {\n let formArr = []\n\n for (let name in data) {\n let val = data[name]\n if (val !== undefined && val !== \"\") {\n formArr.push([name, \"=\", encodeURIComponent(val).replace(/%20/g,\"+\")].join(\"\"))\n }\n }\n return formArr.join(\"&\")\n}\n\n// Is this really required as a helper? Perhaps. TODO: expose the system of presets.apis in docs, so we know what is supported\nexport const shallowEqualKeys = (a,b, keys) => {\n return !!find(keys, (key) => {\n return eq(a[key], b[key])\n })\n}\n\nexport function sanitizeUrl(url) {\n if(typeof url !== \"string\" || url === \"\") {\n return \"\"\n }\n\n return braintreeSanitizeUrl(url)\n}\n\nexport function requiresValidationURL(uri) {\n if (!uri || uri.indexOf(\"localhost\") >= 0 || uri.indexOf(\"127.0.0.1\") >= 0 || uri === \"none\") {\n return false\n }\n return true\n}\n\n\nexport function getAcceptControllingResponse(responses) {\n if(!Im.OrderedMap.isOrderedMap(responses)) {\n // wrong type!\n return null\n }\n\n if(!responses.size) {\n // responses is empty\n return null\n }\n\n const suitable2xxResponse = responses.find((res, k) => {\n return k.startsWith(\"2\") && Object.keys(res.get(\"content\") || {}).length > 0\n })\n\n // try to find a suitable `default` responses\n const defaultResponse = responses.get(\"default\") || Im.OrderedMap()\n const defaultResponseMediaTypes = (defaultResponse.get(\"content\") || Im.OrderedMap()).keySeq().toJS()\n const suitableDefaultResponse = defaultResponseMediaTypes.length ? defaultResponse : null\n\n return suitable2xxResponse || suitableDefaultResponse\n}\n\n// suitable for use in URL fragments\nexport const createDeepLinkPath = (str) => typeof str == \"string\" || str instanceof String ? str.trim().replace(/\\s/g, \"%20\") : \"\"\n// suitable for use in CSS classes and ids\nexport const escapeDeepLinkPath = (str) => cssEscape( createDeepLinkPath(str).replace(/%20/g, \"_\") )\n\nexport const getExtensions = (defObj) => defObj.filter((v, k) => /^x-/.test(k))\nexport const getCommonExtensions = (defObj) => defObj.filter((v, k) => /^pattern|maxLength|minLength|maximum|minimum/.test(k))\n\n// Deeply strips a specific key from an object.\n//\n// `predicate` can be used to discriminate the stripping further,\n// by preserving the key's place in the object based on its value.\nexport function deeplyStripKey(input, keyToStrip, predicate = () => true) {\n if(typeof input !== \"object\" || Array.isArray(input) || input === null || !keyToStrip) {\n return input\n }\n\n const obj = Object.assign({}, input)\n\n Object.keys(obj).forEach(k => {\n if(k === keyToStrip && predicate(obj[k], k)) {\n delete obj[k]\n return\n }\n obj[k] = deeplyStripKey(obj[k], keyToStrip, predicate)\n })\n\n return obj\n}\n\nexport function stringify(thing) {\n if (typeof thing === \"string\") {\n return thing\n }\n\n if (thing && thing.toJS) {\n thing = thing.toJS()\n }\n\n if (typeof thing === \"object\" && thing !== null) {\n try {\n return JSON.stringify(thing, null, 2)\n }\n catch (e) {\n return String(thing)\n }\n }\n\n if(thing === null || thing === undefined) {\n return \"\"\n }\n\n return thing.toString()\n}\n\nexport function numberToString(thing) {\n if(typeof thing === \"number\") {\n return thing.toString()\n }\n\n return thing\n}\n\nexport function paramToIdentifier(param, { returnAll = false, allowHashes = true } = {}) {\n if(!Im.Map.isMap(param)) {\n throw new Error(\"paramToIdentifier: received a non-Im.Map parameter as input\")\n }\n const paramName = param.get(\"name\")\n const paramIn = param.get(\"in\")\n\n let generatedIdentifiers = []\n\n // Generate identifiers in order of most to least specificity\n\n if (param && param.hashCode && paramIn && paramName && allowHashes) {\n generatedIdentifiers.push(`${paramIn}.${paramName}.hash-${param.hashCode()}`)\n }\n\n if(paramIn && paramName) {\n generatedIdentifiers.push(`${paramIn}.${paramName}`)\n }\n\n generatedIdentifiers.push(paramName)\n\n // Return the most preferred identifier, or all if requested\n\n return returnAll ? generatedIdentifiers : (generatedIdentifiers[0] || \"\")\n}\n\nexport function paramToValue(param, paramValues) {\n const allIdentifiers = paramToIdentifier(param, { returnAll: true })\n\n // Map identifiers to values in the provided value hash, filter undefined values,\n // and return the first value found\n const values = allIdentifiers\n .map(id => {\n return paramValues[id]\n })\n .filter(value => value !== undefined)\n\n return values[0]\n}\n\n// adapted from https://auth0.com/docs/flows/guides/auth-code-pkce/includes/create-code-verifier\nexport function generateCodeVerifier() {\n return b64toB64UrlEncoded(\n randomBytes(32).toString(\"base64\")\n )\n}\n\nexport function createCodeChallenge(codeVerifier) {\n return b64toB64UrlEncoded(\n shaJs(\"sha256\")\n .update(codeVerifier)\n .digest(\"base64\")\n )\n}\n\nfunction b64toB64UrlEncoded(str) {\n return str\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=/g, \"\")\n}\n\nexport const isEmptyValue = (value) => {\n if (!value) {\n return true\n }\n\n if (isImmutable(value) && value.isEmpty()) {\n return true\n }\n\n return false\n}\n","export function canJsonParse(str) {\n try {\n let testValueForJson = JSON.parse(str)\n return testValueForJson ? true : false\n } catch (e) {\n // exception: string is not valid json\n return null\n }\n}\n\nexport function getKnownSyntaxHighlighterLanguage(val) {\n // to start, only check for json. can expand as needed in future\n const isValidJson = canJsonParse(val)\n return isValidJson ? \"json\" : null\n}\n","export function isAbsoluteUrl(url) {\n return url.match(/^(?:[a-z]+:)?\\/\\//i) // Matches http://, HTTP://, https://, ftp://, //example.com,\n}\n\nexport function addProtocol(url) {\n if (!url.match(/^\\/\\//i)) return url // Checks if protocol is missing e.g. //example.com\n\n return `${window.location.protocol}${url}`\n}\n\nexport function buildBaseUrl(selectedServer, specUrl) {\n if (!selectedServer) return specUrl\n if (isAbsoluteUrl(selectedServer)) return addProtocol(selectedServer)\n\n return new URL(selectedServer, specUrl).href\n}\n\nexport function buildUrl(url, specUrl, { selectedServer=\"\" } = {}) {\n if (!url) return undefined\n if (isAbsoluteUrl(url)) return url\n\n const baseUrl = buildBaseUrl(selectedServer, specUrl)\n if (!isAbsoluteUrl(baseUrl)) {\n return new URL(url, window.location.href).href\n }\n return new URL(url, baseUrl).href\n}\n\n/**\n * Safe version of buildUrl function. `selectedServer` can contain server variables\n * which can fail the URL resolution.\n */\nexport function safeBuildUrl(url, specUrl, { selectedServer=\"\" } = {}) {\n try {\n return buildUrl(url, specUrl, { selectedServer })\n } catch {\n return undefined\n }\n}\n","function makeWindow() {\n var win = {\n location: {},\n history: {},\n open: () => {},\n close: () => {},\n File: function() {}\n }\n\n if(typeof window === \"undefined\") {\n return win\n }\n\n try {\n win = window\n var props = [\"File\", \"Blob\", \"FormData\"]\n for (var prop of props) {\n if (prop in window) {\n win[prop] = window[prop]\n }\n }\n } catch( e ) {\n console.error(e)\n }\n\n return win\n}\n\nexport default makeWindow()\n","/**\n * @prettier\n */\n\nimport Im from \"immutable\"\n\nconst swagger2SchemaKeys = Im.Set.of(\n \"type\",\n \"format\",\n \"items\",\n \"default\",\n \"maximum\",\n \"exclusiveMaximum\",\n \"minimum\",\n \"exclusiveMinimum\",\n \"maxLength\",\n \"minLength\",\n \"pattern\",\n \"maxItems\",\n \"minItems\",\n \"uniqueItems\",\n \"enum\",\n \"multipleOf\"\n)\n\n/**\n * @typedef {Object} ParameterSchemaDescriptor\n * @property {Immutable.Map} schema - the parameter schema\n * @property {string|null} parameterContentMediaType - the effective media type, for `content`-based OpenAPI 3.0 Parameters, or `null` otherwise\n */\n\n/**\n * Get the effective schema value for a parameter, or an empty Immutable.Map if\n * no suitable schema can be found.\n *\n * Supports OpenAPI 3.0 `Parameter.content` priority -- since a Parameter Object\n * cannot have both `schema` and `content`, this function ignores `schema` when\n * `content` is present.\n *\n * @param {Immutable.Map} parameter The parameter to identify a schema for\n * @param {object} config\n * @param {boolean} config.isOAS3 Whether the parameter is from an OpenAPI 2.0\n * or OpenAPI 3.0 definition\n * @return {ParameterSchemaDescriptor} Information about the parameter schema\n */\nexport default function getParameterSchema(parameter, { isOAS3 } = {}) {\n // Return empty Map if `parameter` isn't a Map\n if (!Im.Map.isMap(parameter)) {\n return {\n schema: Im.Map(),\n parameterContentMediaType: null,\n }\n }\n\n if (!isOAS3) {\n // Swagger 2.0\n if (parameter.get(\"in\") === \"body\") {\n return {\n schema: parameter.get(\"schema\", Im.Map()),\n parameterContentMediaType: null,\n }\n } else {\n return {\n schema: parameter.filter((v, k) => swagger2SchemaKeys.includes(k)),\n parameterContentMediaType: null,\n }\n }\n }\n\n // If we've reached here, the parameter is OpenAPI 3.0\n\n if (parameter.get(\"content\")) {\n const parameterContentMediaTypes = parameter\n .get(\"content\", Im.Map({}))\n .keySeq()\n\n const parameterContentMediaType = parameterContentMediaTypes.first()\n\n return {\n schema: parameter.getIn(\n [\"content\", parameterContentMediaType, \"schema\"],\n Im.Map()\n ),\n parameterContentMediaType,\n }\n }\n\n return {\n schema: parameter.get(\"schema\") ? parameter.get(\"schema\", Im.Map()): Im.Map(),\n parameterContentMediaType: null,\n }\n}\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_find_index_99e05360__[\"default\"] });","import memoize from \"lodash/memoize\"\n\n/**\n * This function is extension on top of lodash.memoize.\n * It uses all the arguments of the `fn` as the cache key instead of just the first one.\n * If resolver is provided, it determines the cache key for\n * storing the result based on the arguments provided to the memoized function.\n */\n\nconst shallowArrayEquals = (a) => (b) => {\n return Array.isArray(a) && Array.isArray(b)\n && a.length === b.length\n && a.every((val, index) => val === b[index])\n}\n\nconst list = (...args) => args\n\nclass Cache extends Map {\n delete(key) {\n const keys = Array.from(this.keys())\n const foundKey = keys.find(shallowArrayEquals(key))\n return super.delete(foundKey)\n }\n\n get(key) {\n const keys = Array.from(this.keys())\n const foundKey = keys.find(shallowArrayEquals(key))\n return super.get(foundKey)\n }\n\n has(key) {\n const keys = Array.from(this.keys())\n return keys.findIndex(shallowArrayEquals(key)) !== -1\n }\n}\n\nconst memoizeN = (fn, resolver = list) => {\n const { Cache: OriginalCache } = memoize\n memoize.Cache = Cache\n\n const memoized = memoize(fn, resolver)\n\n memoize.Cache = OriginalCache\n\n return memoized\n}\n\nexport default memoizeN\n","/*!\n * The buffer module from node.js, for the browser.\n *\n * @author Feross Aboukhadijeh \n * @license MIT\n */\n/* eslint-disable no-proto */\n\n'use strict'\n\nconst base64 = require('base64-js')\nconst ieee754 = require('ieee754')\nconst customInspectSymbol =\n (typeof Symbol === 'function' && typeof Symbol['for'] === 'function') // eslint-disable-line dot-notation\n ? Symbol['for']('nodejs.util.inspect.custom') // eslint-disable-line dot-notation\n : null\n\nexports.Buffer = Buffer\nexports.SlowBuffer = SlowBuffer\nexports.INSPECT_MAX_BYTES = 50\n\nconst K_MAX_LENGTH = 0x7fffffff\nexports.kMaxLength = K_MAX_LENGTH\n\n/**\n * If `Buffer.TYPED_ARRAY_SUPPORT`:\n * === true Use Uint8Array implementation (fastest)\n * === false Print warning and recommend using `buffer` v4.x which has an Object\n * implementation (most compatible, even IE6)\n *\n * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,\n * Opera 11.6+, iOS 4.2+.\n *\n * We report that the browser does not support typed arrays if the are not subclassable\n * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array`\n * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support\n * for __proto__ and has a buggy typed array implementation.\n */\nBuffer.TYPED_ARRAY_SUPPORT = typedArraySupport()\n\nif (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' &&\n typeof console.error === 'function') {\n console.error(\n 'This browser lacks typed array (Uint8Array) support which is required by ' +\n '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.'\n )\n}\n\nfunction typedArraySupport () {\n // Can typed array instances can be augmented?\n try {\n const arr = new Uint8Array(1)\n const proto = { foo: function () { return 42 } }\n Object.setPrototypeOf(proto, Uint8Array.prototype)\n Object.setPrototypeOf(arr, proto)\n return arr.foo() === 42\n } catch (e) {\n return false\n }\n}\n\nObject.defineProperty(Buffer.prototype, 'parent', {\n enumerable: true,\n get: function () {\n if (!Buffer.isBuffer(this)) return undefined\n return this.buffer\n }\n})\n\nObject.defineProperty(Buffer.prototype, 'offset', {\n enumerable: true,\n get: function () {\n if (!Buffer.isBuffer(this)) return undefined\n return this.byteOffset\n }\n})\n\nfunction createBuffer (length) {\n if (length > K_MAX_LENGTH) {\n throw new RangeError('The value \"' + length + '\" is invalid for option \"size\"')\n }\n // Return an augmented `Uint8Array` instance\n const buf = new Uint8Array(length)\n Object.setPrototypeOf(buf, Buffer.prototype)\n return buf\n}\n\n/**\n * The Buffer constructor returns instances of `Uint8Array` that have their\n * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of\n * `Uint8Array`, so the returned instances will have all the node `Buffer` methods\n * and the `Uint8Array` methods. Square bracket notation works as expected -- it\n * returns a single octet.\n *\n * The `Uint8Array` prototype remains unmodified.\n */\n\nfunction Buffer (arg, encodingOrOffset, length) {\n // Common case.\n if (typeof arg === 'number') {\n if (typeof encodingOrOffset === 'string') {\n throw new TypeError(\n 'The \"string\" argument must be of type string. Received type number'\n )\n }\n return allocUnsafe(arg)\n }\n return from(arg, encodingOrOffset, length)\n}\n\nBuffer.poolSize = 8192 // not used by this implementation\n\nfunction from (value, encodingOrOffset, length) {\n if (typeof value === 'string') {\n return fromString(value, encodingOrOffset)\n }\n\n if (ArrayBuffer.isView(value)) {\n return fromArrayView(value)\n }\n\n if (value == null) {\n throw new TypeError(\n 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' +\n 'or Array-like Object. Received type ' + (typeof value)\n )\n }\n\n if (isInstance(value, ArrayBuffer) ||\n (value && isInstance(value.buffer, ArrayBuffer))) {\n return fromArrayBuffer(value, encodingOrOffset, length)\n }\n\n if (typeof SharedArrayBuffer !== 'undefined' &&\n (isInstance(value, SharedArrayBuffer) ||\n (value && isInstance(value.buffer, SharedArrayBuffer)))) {\n return fromArrayBuffer(value, encodingOrOffset, length)\n }\n\n if (typeof value === 'number') {\n throw new TypeError(\n 'The \"value\" argument must not be of type number. Received type number'\n )\n }\n\n const valueOf = value.valueOf && value.valueOf()\n if (valueOf != null && valueOf !== value) {\n return Buffer.from(valueOf, encodingOrOffset, length)\n }\n\n const b = fromObject(value)\n if (b) return b\n\n if (typeof Symbol !== 'undefined' && Symbol.toPrimitive != null &&\n typeof value[Symbol.toPrimitive] === 'function') {\n return Buffer.from(value[Symbol.toPrimitive]('string'), encodingOrOffset, length)\n }\n\n throw new TypeError(\n 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' +\n 'or Array-like Object. Received type ' + (typeof value)\n )\n}\n\n/**\n * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError\n * if value is a number.\n * Buffer.from(str[, encoding])\n * Buffer.from(array)\n * Buffer.from(buffer)\n * Buffer.from(arrayBuffer[, byteOffset[, length]])\n **/\nBuffer.from = function (value, encodingOrOffset, length) {\n return from(value, encodingOrOffset, length)\n}\n\n// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug:\n// https://github.com/feross/buffer/pull/148\nObject.setPrototypeOf(Buffer.prototype, Uint8Array.prototype)\nObject.setPrototypeOf(Buffer, Uint8Array)\n\nfunction assertSize (size) {\n if (typeof size !== 'number') {\n throw new TypeError('\"size\" argument must be of type number')\n } else if (size < 0) {\n throw new RangeError('The value \"' + size + '\" is invalid for option \"size\"')\n }\n}\n\nfunction alloc (size, fill, encoding) {\n assertSize(size)\n if (size <= 0) {\n return createBuffer(size)\n }\n if (fill !== undefined) {\n // Only pay attention to encoding if it's a string. This\n // prevents accidentally sending in a number that would\n // be interpreted as a start offset.\n return typeof encoding === 'string'\n ? createBuffer(size).fill(fill, encoding)\n : createBuffer(size).fill(fill)\n }\n return createBuffer(size)\n}\n\n/**\n * Creates a new filled Buffer instance.\n * alloc(size[, fill[, encoding]])\n **/\nBuffer.alloc = function (size, fill, encoding) {\n return alloc(size, fill, encoding)\n}\n\nfunction allocUnsafe (size) {\n assertSize(size)\n return createBuffer(size < 0 ? 0 : checked(size) | 0)\n}\n\n/**\n * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.\n * */\nBuffer.allocUnsafe = function (size) {\n return allocUnsafe(size)\n}\n/**\n * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.\n */\nBuffer.allocUnsafeSlow = function (size) {\n return allocUnsafe(size)\n}\n\nfunction fromString (string, encoding) {\n if (typeof encoding !== 'string' || encoding === '') {\n encoding = 'utf8'\n }\n\n if (!Buffer.isEncoding(encoding)) {\n throw new TypeError('Unknown encoding: ' + encoding)\n }\n\n const length = byteLength(string, encoding) | 0\n let buf = createBuffer(length)\n\n const actual = buf.write(string, encoding)\n\n if (actual !== length) {\n // Writing a hex string, for example, that contains invalid characters will\n // cause everything after the first invalid character to be ignored. (e.g.\n // 'abxxcd' will be treated as 'ab')\n buf = buf.slice(0, actual)\n }\n\n return buf\n}\n\nfunction fromArrayLike (array) {\n const length = array.length < 0 ? 0 : checked(array.length) | 0\n const buf = createBuffer(length)\n for (let i = 0; i < length; i += 1) {\n buf[i] = array[i] & 255\n }\n return buf\n}\n\nfunction fromArrayView (arrayView) {\n if (isInstance(arrayView, Uint8Array)) {\n const copy = new Uint8Array(arrayView)\n return fromArrayBuffer(copy.buffer, copy.byteOffset, copy.byteLength)\n }\n return fromArrayLike(arrayView)\n}\n\nfunction fromArrayBuffer (array, byteOffset, length) {\n if (byteOffset < 0 || array.byteLength < byteOffset) {\n throw new RangeError('\"offset\" is outside of buffer bounds')\n }\n\n if (array.byteLength < byteOffset + (length || 0)) {\n throw new RangeError('\"length\" is outside of buffer bounds')\n }\n\n let buf\n if (byteOffset === undefined && length === undefined) {\n buf = new Uint8Array(array)\n } else if (length === undefined) {\n buf = new Uint8Array(array, byteOffset)\n } else {\n buf = new Uint8Array(array, byteOffset, length)\n }\n\n // Return an augmented `Uint8Array` instance\n Object.setPrototypeOf(buf, Buffer.prototype)\n\n return buf\n}\n\nfunction fromObject (obj) {\n if (Buffer.isBuffer(obj)) {\n const len = checked(obj.length) | 0\n const buf = createBuffer(len)\n\n if (buf.length === 0) {\n return buf\n }\n\n obj.copy(buf, 0, 0, len)\n return buf\n }\n\n if (obj.length !== undefined) {\n if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) {\n return createBuffer(0)\n }\n return fromArrayLike(obj)\n }\n\n if (obj.type === 'Buffer' && Array.isArray(obj.data)) {\n return fromArrayLike(obj.data)\n }\n}\n\nfunction checked (length) {\n // Note: cannot use `length < K_MAX_LENGTH` here because that fails when\n // length is NaN (which is otherwise coerced to zero.)\n if (length >= K_MAX_LENGTH) {\n throw new RangeError('Attempt to allocate Buffer larger than maximum ' +\n 'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes')\n }\n return length | 0\n}\n\nfunction SlowBuffer (length) {\n if (+length != length) { // eslint-disable-line eqeqeq\n length = 0\n }\n return Buffer.alloc(+length)\n}\n\nBuffer.isBuffer = function isBuffer (b) {\n return b != null && b._isBuffer === true &&\n b !== Buffer.prototype // so Buffer.isBuffer(Buffer.prototype) will be false\n}\n\nBuffer.compare = function compare (a, b) {\n if (isInstance(a, Uint8Array)) a = Buffer.from(a, a.offset, a.byteLength)\n if (isInstance(b, Uint8Array)) b = Buffer.from(b, b.offset, b.byteLength)\n if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {\n throw new TypeError(\n 'The \"buf1\", \"buf2\" arguments must be one of type Buffer or Uint8Array'\n )\n }\n\n if (a === b) return 0\n\n let x = a.length\n let y = b.length\n\n for (let i = 0, len = Math.min(x, y); i < len; ++i) {\n if (a[i] !== b[i]) {\n x = a[i]\n y = b[i]\n break\n }\n }\n\n if (x < y) return -1\n if (y < x) return 1\n return 0\n}\n\nBuffer.isEncoding = function isEncoding (encoding) {\n switch (String(encoding).toLowerCase()) {\n case 'hex':\n case 'utf8':\n case 'utf-8':\n case 'ascii':\n case 'latin1':\n case 'binary':\n case 'base64':\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return true\n default:\n return false\n }\n}\n\nBuffer.concat = function concat (list, length) {\n if (!Array.isArray(list)) {\n throw new TypeError('\"list\" argument must be an Array of Buffers')\n }\n\n if (list.length === 0) {\n return Buffer.alloc(0)\n }\n\n let i\n if (length === undefined) {\n length = 0\n for (i = 0; i < list.length; ++i) {\n length += list[i].length\n }\n }\n\n const buffer = Buffer.allocUnsafe(length)\n let pos = 0\n for (i = 0; i < list.length; ++i) {\n let buf = list[i]\n if (isInstance(buf, Uint8Array)) {\n if (pos + buf.length > buffer.length) {\n if (!Buffer.isBuffer(buf)) buf = Buffer.from(buf)\n buf.copy(buffer, pos)\n } else {\n Uint8Array.prototype.set.call(\n buffer,\n buf,\n pos\n )\n }\n } else if (!Buffer.isBuffer(buf)) {\n throw new TypeError('\"list\" argument must be an Array of Buffers')\n } else {\n buf.copy(buffer, pos)\n }\n pos += buf.length\n }\n return buffer\n}\n\nfunction byteLength (string, encoding) {\n if (Buffer.isBuffer(string)) {\n return string.length\n }\n if (ArrayBuffer.isView(string) || isInstance(string, ArrayBuffer)) {\n return string.byteLength\n }\n if (typeof string !== 'string') {\n throw new TypeError(\n 'The \"string\" argument must be one of type string, Buffer, or ArrayBuffer. ' +\n 'Received type ' + typeof string\n )\n }\n\n const len = string.length\n const mustMatch = (arguments.length > 2 && arguments[2] === true)\n if (!mustMatch && len === 0) return 0\n\n // Use a for loop to avoid recursion\n let loweredCase = false\n for (;;) {\n switch (encoding) {\n case 'ascii':\n case 'latin1':\n case 'binary':\n return len\n case 'utf8':\n case 'utf-8':\n return utf8ToBytes(string).length\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return len * 2\n case 'hex':\n return len >>> 1\n case 'base64':\n return base64ToBytes(string).length\n default:\n if (loweredCase) {\n return mustMatch ? -1 : utf8ToBytes(string).length // assume utf8\n }\n encoding = ('' + encoding).toLowerCase()\n loweredCase = true\n }\n }\n}\nBuffer.byteLength = byteLength\n\nfunction slowToString (encoding, start, end) {\n let loweredCase = false\n\n // No need to verify that \"this.length <= MAX_UINT32\" since it's a read-only\n // property of a typed array.\n\n // This behaves neither like String nor Uint8Array in that we set start/end\n // to their upper/lower bounds if the value passed is out of range.\n // undefined is handled specially as per ECMA-262 6th Edition,\n // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.\n if (start === undefined || start < 0) {\n start = 0\n }\n // Return early if start > this.length. Done here to prevent potential uint32\n // coercion fail below.\n if (start > this.length) {\n return ''\n }\n\n if (end === undefined || end > this.length) {\n end = this.length\n }\n\n if (end <= 0) {\n return ''\n }\n\n // Force coercion to uint32. This will also coerce falsey/NaN values to 0.\n end >>>= 0\n start >>>= 0\n\n if (end <= start) {\n return ''\n }\n\n if (!encoding) encoding = 'utf8'\n\n while (true) {\n switch (encoding) {\n case 'hex':\n return hexSlice(this, start, end)\n\n case 'utf8':\n case 'utf-8':\n return utf8Slice(this, start, end)\n\n case 'ascii':\n return asciiSlice(this, start, end)\n\n case 'latin1':\n case 'binary':\n return latin1Slice(this, start, end)\n\n case 'base64':\n return base64Slice(this, start, end)\n\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return utf16leSlice(this, start, end)\n\n default:\n if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)\n encoding = (encoding + '').toLowerCase()\n loweredCase = true\n }\n }\n}\n\n// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package)\n// to detect a Buffer instance. It's not possible to use `instanceof Buffer`\n// reliably in a browserify context because there could be multiple different\n// copies of the 'buffer' package in use. This method works even for Buffer\n// instances that were created from another copy of the `buffer` package.\n// See: https://github.com/feross/buffer/issues/154\nBuffer.prototype._isBuffer = true\n\nfunction swap (b, n, m) {\n const i = b[n]\n b[n] = b[m]\n b[m] = i\n}\n\nBuffer.prototype.swap16 = function swap16 () {\n const len = this.length\n if (len % 2 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 16-bits')\n }\n for (let i = 0; i < len; i += 2) {\n swap(this, i, i + 1)\n }\n return this\n}\n\nBuffer.prototype.swap32 = function swap32 () {\n const len = this.length\n if (len % 4 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 32-bits')\n }\n for (let i = 0; i < len; i += 4) {\n swap(this, i, i + 3)\n swap(this, i + 1, i + 2)\n }\n return this\n}\n\nBuffer.prototype.swap64 = function swap64 () {\n const len = this.length\n if (len % 8 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 64-bits')\n }\n for (let i = 0; i < len; i += 8) {\n swap(this, i, i + 7)\n swap(this, i + 1, i + 6)\n swap(this, i + 2, i + 5)\n swap(this, i + 3, i + 4)\n }\n return this\n}\n\nBuffer.prototype.toString = function toString () {\n const length = this.length\n if (length === 0) return ''\n if (arguments.length === 0) return utf8Slice(this, 0, length)\n return slowToString.apply(this, arguments)\n}\n\nBuffer.prototype.toLocaleString = Buffer.prototype.toString\n\nBuffer.prototype.equals = function equals (b) {\n if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')\n if (this === b) return true\n return Buffer.compare(this, b) === 0\n}\n\nBuffer.prototype.inspect = function inspect () {\n let str = ''\n const max = exports.INSPECT_MAX_BYTES\n str = this.toString('hex', 0, max).replace(/(.{2})/g, '$1 ').trim()\n if (this.length > max) str += ' ... '\n return ''\n}\nif (customInspectSymbol) {\n Buffer.prototype[customInspectSymbol] = Buffer.prototype.inspect\n}\n\nBuffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {\n if (isInstance(target, Uint8Array)) {\n target = Buffer.from(target, target.offset, target.byteLength)\n }\n if (!Buffer.isBuffer(target)) {\n throw new TypeError(\n 'The \"target\" argument must be one of type Buffer or Uint8Array. ' +\n 'Received type ' + (typeof target)\n )\n }\n\n if (start === undefined) {\n start = 0\n }\n if (end === undefined) {\n end = target ? target.length : 0\n }\n if (thisStart === undefined) {\n thisStart = 0\n }\n if (thisEnd === undefined) {\n thisEnd = this.length\n }\n\n if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) {\n throw new RangeError('out of range index')\n }\n\n if (thisStart >= thisEnd && start >= end) {\n return 0\n }\n if (thisStart >= thisEnd) {\n return -1\n }\n if (start >= end) {\n return 1\n }\n\n start >>>= 0\n end >>>= 0\n thisStart >>>= 0\n thisEnd >>>= 0\n\n if (this === target) return 0\n\n let x = thisEnd - thisStart\n let y = end - start\n const len = Math.min(x, y)\n\n const thisCopy = this.slice(thisStart, thisEnd)\n const targetCopy = target.slice(start, end)\n\n for (let i = 0; i < len; ++i) {\n if (thisCopy[i] !== targetCopy[i]) {\n x = thisCopy[i]\n y = targetCopy[i]\n break\n }\n }\n\n if (x < y) return -1\n if (y < x) return 1\n return 0\n}\n\n// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`,\n// OR the last index of `val` in `buffer` at offset <= `byteOffset`.\n//\n// Arguments:\n// - buffer - a Buffer to search\n// - val - a string, Buffer, or number\n// - byteOffset - an index into `buffer`; will be clamped to an int32\n// - encoding - an optional encoding, relevant is val is a string\n// - dir - true for indexOf, false for lastIndexOf\nfunction bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {\n // Empty buffer means no match\n if (buffer.length === 0) return -1\n\n // Normalize byteOffset\n if (typeof byteOffset === 'string') {\n encoding = byteOffset\n byteOffset = 0\n } else if (byteOffset > 0x7fffffff) {\n byteOffset = 0x7fffffff\n } else if (byteOffset < -0x80000000) {\n byteOffset = -0x80000000\n }\n byteOffset = +byteOffset // Coerce to Number.\n if (numberIsNaN(byteOffset)) {\n // byteOffset: it it's undefined, null, NaN, \"foo\", etc, search whole buffer\n byteOffset = dir ? 0 : (buffer.length - 1)\n }\n\n // Normalize byteOffset: negative offsets start from the end of the buffer\n if (byteOffset < 0) byteOffset = buffer.length + byteOffset\n if (byteOffset >= buffer.length) {\n if (dir) return -1\n else byteOffset = buffer.length - 1\n } else if (byteOffset < 0) {\n if (dir) byteOffset = 0\n else return -1\n }\n\n // Normalize val\n if (typeof val === 'string') {\n val = Buffer.from(val, encoding)\n }\n\n // Finally, search either indexOf (if dir is true) or lastIndexOf\n if (Buffer.isBuffer(val)) {\n // Special case: looking for empty string/buffer always fails\n if (val.length === 0) {\n return -1\n }\n return arrayIndexOf(buffer, val, byteOffset, encoding, dir)\n } else if (typeof val === 'number') {\n val = val & 0xFF // Search for a byte value [0-255]\n if (typeof Uint8Array.prototype.indexOf === 'function') {\n if (dir) {\n return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset)\n } else {\n return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset)\n }\n }\n return arrayIndexOf(buffer, [val], byteOffset, encoding, dir)\n }\n\n throw new TypeError('val must be string, number or Buffer')\n}\n\nfunction arrayIndexOf (arr, val, byteOffset, encoding, dir) {\n let indexSize = 1\n let arrLength = arr.length\n let valLength = val.length\n\n if (encoding !== undefined) {\n encoding = String(encoding).toLowerCase()\n if (encoding === 'ucs2' || encoding === 'ucs-2' ||\n encoding === 'utf16le' || encoding === 'utf-16le') {\n if (arr.length < 2 || val.length < 2) {\n return -1\n }\n indexSize = 2\n arrLength /= 2\n valLength /= 2\n byteOffset /= 2\n }\n }\n\n function read (buf, i) {\n if (indexSize === 1) {\n return buf[i]\n } else {\n return buf.readUInt16BE(i * indexSize)\n }\n }\n\n let i\n if (dir) {\n let foundIndex = -1\n for (i = byteOffset; i < arrLength; i++) {\n if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) {\n if (foundIndex === -1) foundIndex = i\n if (i - foundIndex + 1 === valLength) return foundIndex * indexSize\n } else {\n if (foundIndex !== -1) i -= i - foundIndex\n foundIndex = -1\n }\n }\n } else {\n if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength\n for (i = byteOffset; i >= 0; i--) {\n let found = true\n for (let j = 0; j < valLength; j++) {\n if (read(arr, i + j) !== read(val, j)) {\n found = false\n break\n }\n }\n if (found) return i\n }\n }\n\n return -1\n}\n\nBuffer.prototype.includes = function includes (val, byteOffset, encoding) {\n return this.indexOf(val, byteOffset, encoding) !== -1\n}\n\nBuffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) {\n return bidirectionalIndexOf(this, val, byteOffset, encoding, true)\n}\n\nBuffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) {\n return bidirectionalIndexOf(this, val, byteOffset, encoding, false)\n}\n\nfunction hexWrite (buf, string, offset, length) {\n offset = Number(offset) || 0\n const remaining = buf.length - offset\n if (!length) {\n length = remaining\n } else {\n length = Number(length)\n if (length > remaining) {\n length = remaining\n }\n }\n\n const strLen = string.length\n\n if (length > strLen / 2) {\n length = strLen / 2\n }\n let i\n for (i = 0; i < length; ++i) {\n const parsed = parseInt(string.substr(i * 2, 2), 16)\n if (numberIsNaN(parsed)) return i\n buf[offset + i] = parsed\n }\n return i\n}\n\nfunction utf8Write (buf, string, offset, length) {\n return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)\n}\n\nfunction asciiWrite (buf, string, offset, length) {\n return blitBuffer(asciiToBytes(string), buf, offset, length)\n}\n\nfunction base64Write (buf, string, offset, length) {\n return blitBuffer(base64ToBytes(string), buf, offset, length)\n}\n\nfunction ucs2Write (buf, string, offset, length) {\n return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)\n}\n\nBuffer.prototype.write = function write (string, offset, length, encoding) {\n // Buffer#write(string)\n if (offset === undefined) {\n encoding = 'utf8'\n length = this.length\n offset = 0\n // Buffer#write(string, encoding)\n } else if (length === undefined && typeof offset === 'string') {\n encoding = offset\n length = this.length\n offset = 0\n // Buffer#write(string, offset[, length][, encoding])\n } else if (isFinite(offset)) {\n offset = offset >>> 0\n if (isFinite(length)) {\n length = length >>> 0\n if (encoding === undefined) encoding = 'utf8'\n } else {\n encoding = length\n length = undefined\n }\n } else {\n throw new Error(\n 'Buffer.write(string, encoding, offset[, length]) is no longer supported'\n )\n }\n\n const remaining = this.length - offset\n if (length === undefined || length > remaining) length = remaining\n\n if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {\n throw new RangeError('Attempt to write outside buffer bounds')\n }\n\n if (!encoding) encoding = 'utf8'\n\n let loweredCase = false\n for (;;) {\n switch (encoding) {\n case 'hex':\n return hexWrite(this, string, offset, length)\n\n case 'utf8':\n case 'utf-8':\n return utf8Write(this, string, offset, length)\n\n case 'ascii':\n case 'latin1':\n case 'binary':\n return asciiWrite(this, string, offset, length)\n\n case 'base64':\n // Warning: maxLength not taken into account in base64Write\n return base64Write(this, string, offset, length)\n\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return ucs2Write(this, string, offset, length)\n\n default:\n if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)\n encoding = ('' + encoding).toLowerCase()\n loweredCase = true\n }\n }\n}\n\nBuffer.prototype.toJSON = function toJSON () {\n return {\n type: 'Buffer',\n data: Array.prototype.slice.call(this._arr || this, 0)\n }\n}\n\nfunction base64Slice (buf, start, end) {\n if (start === 0 && end === buf.length) {\n return base64.fromByteArray(buf)\n } else {\n return base64.fromByteArray(buf.slice(start, end))\n }\n}\n\nfunction utf8Slice (buf, start, end) {\n end = Math.min(buf.length, end)\n const res = []\n\n let i = start\n while (i < end) {\n const firstByte = buf[i]\n let codePoint = null\n let bytesPerSequence = (firstByte > 0xEF)\n ? 4\n : (firstByte > 0xDF)\n ? 3\n : (firstByte > 0xBF)\n ? 2\n : 1\n\n if (i + bytesPerSequence <= end) {\n let secondByte, thirdByte, fourthByte, tempCodePoint\n\n switch (bytesPerSequence) {\n case 1:\n if (firstByte < 0x80) {\n codePoint = firstByte\n }\n break\n case 2:\n secondByte = buf[i + 1]\n if ((secondByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)\n if (tempCodePoint > 0x7F) {\n codePoint = tempCodePoint\n }\n }\n break\n case 3:\n secondByte = buf[i + 1]\n thirdByte = buf[i + 2]\n if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)\n if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {\n codePoint = tempCodePoint\n }\n }\n break\n case 4:\n secondByte = buf[i + 1]\n thirdByte = buf[i + 2]\n fourthByte = buf[i + 3]\n if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)\n if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {\n codePoint = tempCodePoint\n }\n }\n }\n }\n\n if (codePoint === null) {\n // we did not generate a valid codePoint so insert a\n // replacement char (U+FFFD) and advance only 1 byte\n codePoint = 0xFFFD\n bytesPerSequence = 1\n } else if (codePoint > 0xFFFF) {\n // encode to utf16 (surrogate pair dance)\n codePoint -= 0x10000\n res.push(codePoint >>> 10 & 0x3FF | 0xD800)\n codePoint = 0xDC00 | codePoint & 0x3FF\n }\n\n res.push(codePoint)\n i += bytesPerSequence\n }\n\n return decodeCodePointsArray(res)\n}\n\n// Based on http://stackoverflow.com/a/22747272/680742, the browser with\n// the lowest limit is Chrome, with 0x10000 args.\n// We go 1 magnitude less, for safety\nconst MAX_ARGUMENTS_LENGTH = 0x1000\n\nfunction decodeCodePointsArray (codePoints) {\n const len = codePoints.length\n if (len <= MAX_ARGUMENTS_LENGTH) {\n return String.fromCharCode.apply(String, codePoints) // avoid extra slice()\n }\n\n // Decode in chunks to avoid \"call stack size exceeded\".\n let res = ''\n let i = 0\n while (i < len) {\n res += String.fromCharCode.apply(\n String,\n codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)\n )\n }\n return res\n}\n\nfunction asciiSlice (buf, start, end) {\n let ret = ''\n end = Math.min(buf.length, end)\n\n for (let i = start; i < end; ++i) {\n ret += String.fromCharCode(buf[i] & 0x7F)\n }\n return ret\n}\n\nfunction latin1Slice (buf, start, end) {\n let ret = ''\n end = Math.min(buf.length, end)\n\n for (let i = start; i < end; ++i) {\n ret += String.fromCharCode(buf[i])\n }\n return ret\n}\n\nfunction hexSlice (buf, start, end) {\n const len = buf.length\n\n if (!start || start < 0) start = 0\n if (!end || end < 0 || end > len) end = len\n\n let out = ''\n for (let i = start; i < end; ++i) {\n out += hexSliceLookupTable[buf[i]]\n }\n return out\n}\n\nfunction utf16leSlice (buf, start, end) {\n const bytes = buf.slice(start, end)\n let res = ''\n // If bytes.length is odd, the last 8 bits must be ignored (same as node.js)\n for (let i = 0; i < bytes.length - 1; i += 2) {\n res += String.fromCharCode(bytes[i] + (bytes[i + 1] * 256))\n }\n return res\n}\n\nBuffer.prototype.slice = function slice (start, end) {\n const len = this.length\n start = ~~start\n end = end === undefined ? len : ~~end\n\n if (start < 0) {\n start += len\n if (start < 0) start = 0\n } else if (start > len) {\n start = len\n }\n\n if (end < 0) {\n end += len\n if (end < 0) end = 0\n } else if (end > len) {\n end = len\n }\n\n if (end < start) end = start\n\n const newBuf = this.subarray(start, end)\n // Return an augmented `Uint8Array` instance\n Object.setPrototypeOf(newBuf, Buffer.prototype)\n\n return newBuf\n}\n\n/*\n * Need to make sure that buffer isn't trying to write out of bounds.\n */\nfunction checkOffset (offset, ext, length) {\n if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')\n if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')\n}\n\nBuffer.prototype.readUintLE =\nBuffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {\n offset = offset >>> 0\n byteLength = byteLength >>> 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n let val = this[offset]\n let mul = 1\n let i = 0\n while (++i < byteLength && (mul *= 0x100)) {\n val += this[offset + i] * mul\n }\n\n return val\n}\n\nBuffer.prototype.readUintBE =\nBuffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {\n offset = offset >>> 0\n byteLength = byteLength >>> 0\n if (!noAssert) {\n checkOffset(offset, byteLength, this.length)\n }\n\n let val = this[offset + --byteLength]\n let mul = 1\n while (byteLength > 0 && (mul *= 0x100)) {\n val += this[offset + --byteLength] * mul\n }\n\n return val\n}\n\nBuffer.prototype.readUint8 =\nBuffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 1, this.length)\n return this[offset]\n}\n\nBuffer.prototype.readUint16LE =\nBuffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 2, this.length)\n return this[offset] | (this[offset + 1] << 8)\n}\n\nBuffer.prototype.readUint16BE =\nBuffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 2, this.length)\n return (this[offset] << 8) | this[offset + 1]\n}\n\nBuffer.prototype.readUint32LE =\nBuffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return ((this[offset]) |\n (this[offset + 1] << 8) |\n (this[offset + 2] << 16)) +\n (this[offset + 3] * 0x1000000)\n}\n\nBuffer.prototype.readUint32BE =\nBuffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset] * 0x1000000) +\n ((this[offset + 1] << 16) |\n (this[offset + 2] << 8) |\n this[offset + 3])\n}\n\nBuffer.prototype.readBigUInt64LE = defineBigIntMethod(function readBigUInt64LE (offset) {\n offset = offset >>> 0\n validateNumber(offset, 'offset')\n const first = this[offset]\n const last = this[offset + 7]\n if (first === undefined || last === undefined) {\n boundsError(offset, this.length - 8)\n }\n\n const lo = first +\n this[++offset] * 2 ** 8 +\n this[++offset] * 2 ** 16 +\n this[++offset] * 2 ** 24\n\n const hi = this[++offset] +\n this[++offset] * 2 ** 8 +\n this[++offset] * 2 ** 16 +\n last * 2 ** 24\n\n return BigInt(lo) + (BigInt(hi) << BigInt(32))\n})\n\nBuffer.prototype.readBigUInt64BE = defineBigIntMethod(function readBigUInt64BE (offset) {\n offset = offset >>> 0\n validateNumber(offset, 'offset')\n const first = this[offset]\n const last = this[offset + 7]\n if (first === undefined || last === undefined) {\n boundsError(offset, this.length - 8)\n }\n\n const hi = first * 2 ** 24 +\n this[++offset] * 2 ** 16 +\n this[++offset] * 2 ** 8 +\n this[++offset]\n\n const lo = this[++offset] * 2 ** 24 +\n this[++offset] * 2 ** 16 +\n this[++offset] * 2 ** 8 +\n last\n\n return (BigInt(hi) << BigInt(32)) + BigInt(lo)\n})\n\nBuffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {\n offset = offset >>> 0\n byteLength = byteLength >>> 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n let val = this[offset]\n let mul = 1\n let i = 0\n while (++i < byteLength && (mul *= 0x100)) {\n val += this[offset + i] * mul\n }\n mul *= 0x80\n\n if (val >= mul) val -= Math.pow(2, 8 * byteLength)\n\n return val\n}\n\nBuffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {\n offset = offset >>> 0\n byteLength = byteLength >>> 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n let i = byteLength\n let mul = 1\n let val = this[offset + --i]\n while (i > 0 && (mul *= 0x100)) {\n val += this[offset + --i] * mul\n }\n mul *= 0x80\n\n if (val >= mul) val -= Math.pow(2, 8 * byteLength)\n\n return val\n}\n\nBuffer.prototype.readInt8 = function readInt8 (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 1, this.length)\n if (!(this[offset] & 0x80)) return (this[offset])\n return ((0xff - this[offset] + 1) * -1)\n}\n\nBuffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 2, this.length)\n const val = this[offset] | (this[offset + 1] << 8)\n return (val & 0x8000) ? val | 0xFFFF0000 : val\n}\n\nBuffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 2, this.length)\n const val = this[offset + 1] | (this[offset] << 8)\n return (val & 0x8000) ? val | 0xFFFF0000 : val\n}\n\nBuffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset]) |\n (this[offset + 1] << 8) |\n (this[offset + 2] << 16) |\n (this[offset + 3] << 24)\n}\n\nBuffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset] << 24) |\n (this[offset + 1] << 16) |\n (this[offset + 2] << 8) |\n (this[offset + 3])\n}\n\nBuffer.prototype.readBigInt64LE = defineBigIntMethod(function readBigInt64LE (offset) {\n offset = offset >>> 0\n validateNumber(offset, 'offset')\n const first = this[offset]\n const last = this[offset + 7]\n if (first === undefined || last === undefined) {\n boundsError(offset, this.length - 8)\n }\n\n const val = this[offset + 4] +\n this[offset + 5] * 2 ** 8 +\n this[offset + 6] * 2 ** 16 +\n (last << 24) // Overflow\n\n return (BigInt(val) << BigInt(32)) +\n BigInt(first +\n this[++offset] * 2 ** 8 +\n this[++offset] * 2 ** 16 +\n this[++offset] * 2 ** 24)\n})\n\nBuffer.prototype.readBigInt64BE = defineBigIntMethod(function readBigInt64BE (offset) {\n offset = offset >>> 0\n validateNumber(offset, 'offset')\n const first = this[offset]\n const last = this[offset + 7]\n if (first === undefined || last === undefined) {\n boundsError(offset, this.length - 8)\n }\n\n const val = (first << 24) + // Overflow\n this[++offset] * 2 ** 16 +\n this[++offset] * 2 ** 8 +\n this[++offset]\n\n return (BigInt(val) << BigInt(32)) +\n BigInt(this[++offset] * 2 ** 24 +\n this[++offset] * 2 ** 16 +\n this[++offset] * 2 ** 8 +\n last)\n})\n\nBuffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 4, this.length)\n return ieee754.read(this, offset, true, 23, 4)\n}\n\nBuffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 4, this.length)\n return ieee754.read(this, offset, false, 23, 4)\n}\n\nBuffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 8, this.length)\n return ieee754.read(this, offset, true, 52, 8)\n}\n\nBuffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {\n offset = offset >>> 0\n if (!noAssert) checkOffset(offset, 8, this.length)\n return ieee754.read(this, offset, false, 52, 8)\n}\n\nfunction checkInt (buf, value, offset, ext, max, min) {\n if (!Buffer.isBuffer(buf)) throw new TypeError('\"buffer\" argument must be a Buffer instance')\n if (value > max || value < min) throw new RangeError('\"value\" argument is out of bounds')\n if (offset + ext > buf.length) throw new RangeError('Index out of range')\n}\n\nBuffer.prototype.writeUintLE =\nBuffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset >>> 0\n byteLength = byteLength >>> 0\n if (!noAssert) {\n const maxBytes = Math.pow(2, 8 * byteLength) - 1\n checkInt(this, value, offset, byteLength, maxBytes, 0)\n }\n\n let mul = 1\n let i = 0\n this[offset] = value & 0xFF\n while (++i < byteLength && (mul *= 0x100)) {\n this[offset + i] = (value / mul) & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeUintBE =\nBuffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset >>> 0\n byteLength = byteLength >>> 0\n if (!noAssert) {\n const maxBytes = Math.pow(2, 8 * byteLength) - 1\n checkInt(this, value, offset, byteLength, maxBytes, 0)\n }\n\n let i = byteLength - 1\n let mul = 1\n this[offset + i] = value & 0xFF\n while (--i >= 0 && (mul *= 0x100)) {\n this[offset + i] = (value / mul) & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeUint8 =\nBuffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)\n this[offset] = (value & 0xff)\n return offset + 1\n}\n\nBuffer.prototype.writeUint16LE =\nBuffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n return offset + 2\n}\n\nBuffer.prototype.writeUint16BE =\nBuffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)\n this[offset] = (value >>> 8)\n this[offset + 1] = (value & 0xff)\n return offset + 2\n}\n\nBuffer.prototype.writeUint32LE =\nBuffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)\n this[offset + 3] = (value >>> 24)\n this[offset + 2] = (value >>> 16)\n this[offset + 1] = (value >>> 8)\n this[offset] = (value & 0xff)\n return offset + 4\n}\n\nBuffer.prototype.writeUint32BE =\nBuffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)\n this[offset] = (value >>> 24)\n this[offset + 1] = (value >>> 16)\n this[offset + 2] = (value >>> 8)\n this[offset + 3] = (value & 0xff)\n return offset + 4\n}\n\nfunction wrtBigUInt64LE (buf, value, offset, min, max) {\n checkIntBI(value, min, max, buf, offset, 7)\n\n let lo = Number(value & BigInt(0xffffffff))\n buf[offset++] = lo\n lo = lo >> 8\n buf[offset++] = lo\n lo = lo >> 8\n buf[offset++] = lo\n lo = lo >> 8\n buf[offset++] = lo\n let hi = Number(value >> BigInt(32) & BigInt(0xffffffff))\n buf[offset++] = hi\n hi = hi >> 8\n buf[offset++] = hi\n hi = hi >> 8\n buf[offset++] = hi\n hi = hi >> 8\n buf[offset++] = hi\n return offset\n}\n\nfunction wrtBigUInt64BE (buf, value, offset, min, max) {\n checkIntBI(value, min, max, buf, offset, 7)\n\n let lo = Number(value & BigInt(0xffffffff))\n buf[offset + 7] = lo\n lo = lo >> 8\n buf[offset + 6] = lo\n lo = lo >> 8\n buf[offset + 5] = lo\n lo = lo >> 8\n buf[offset + 4] = lo\n let hi = Number(value >> BigInt(32) & BigInt(0xffffffff))\n buf[offset + 3] = hi\n hi = hi >> 8\n buf[offset + 2] = hi\n hi = hi >> 8\n buf[offset + 1] = hi\n hi = hi >> 8\n buf[offset] = hi\n return offset + 8\n}\n\nBuffer.prototype.writeBigUInt64LE = defineBigIntMethod(function writeBigUInt64LE (value, offset = 0) {\n return wrtBigUInt64LE(this, value, offset, BigInt(0), BigInt('0xffffffffffffffff'))\n})\n\nBuffer.prototype.writeBigUInt64BE = defineBigIntMethod(function writeBigUInt64BE (value, offset = 0) {\n return wrtBigUInt64BE(this, value, offset, BigInt(0), BigInt('0xffffffffffffffff'))\n})\n\nBuffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) {\n const limit = Math.pow(2, (8 * byteLength) - 1)\n\n checkInt(this, value, offset, byteLength, limit - 1, -limit)\n }\n\n let i = 0\n let mul = 1\n let sub = 0\n this[offset] = value & 0xFF\n while (++i < byteLength && (mul *= 0x100)) {\n if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) {\n sub = 1\n }\n this[offset + i] = ((value / mul) >> 0) - sub & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) {\n const limit = Math.pow(2, (8 * byteLength) - 1)\n\n checkInt(this, value, offset, byteLength, limit - 1, -limit)\n }\n\n let i = byteLength - 1\n let mul = 1\n let sub = 0\n this[offset + i] = value & 0xFF\n while (--i >= 0 && (mul *= 0x100)) {\n if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) {\n sub = 1\n }\n this[offset + i] = ((value / mul) >> 0) - sub & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)\n if (value < 0) value = 0xff + value + 1\n this[offset] = (value & 0xff)\n return offset + 1\n}\n\nBuffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n return offset + 2\n}\n\nBuffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)\n this[offset] = (value >>> 8)\n this[offset + 1] = (value & 0xff)\n return offset + 2\n}\n\nBuffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n this[offset + 2] = (value >>> 16)\n this[offset + 3] = (value >>> 24)\n return offset + 4\n}\n\nBuffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)\n if (value < 0) value = 0xffffffff + value + 1\n this[offset] = (value >>> 24)\n this[offset + 1] = (value >>> 16)\n this[offset + 2] = (value >>> 8)\n this[offset + 3] = (value & 0xff)\n return offset + 4\n}\n\nBuffer.prototype.writeBigInt64LE = defineBigIntMethod(function writeBigInt64LE (value, offset = 0) {\n return wrtBigUInt64LE(this, value, offset, -BigInt('0x8000000000000000'), BigInt('0x7fffffffffffffff'))\n})\n\nBuffer.prototype.writeBigInt64BE = defineBigIntMethod(function writeBigInt64BE (value, offset = 0) {\n return wrtBigUInt64BE(this, value, offset, -BigInt('0x8000000000000000'), BigInt('0x7fffffffffffffff'))\n})\n\nfunction checkIEEE754 (buf, value, offset, ext, max, min) {\n if (offset + ext > buf.length) throw new RangeError('Index out of range')\n if (offset < 0) throw new RangeError('Index out of range')\n}\n\nfunction writeFloat (buf, value, offset, littleEndian, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) {\n checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)\n }\n ieee754.write(buf, value, offset, littleEndian, 23, 4)\n return offset + 4\n}\n\nBuffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {\n return writeFloat(this, value, offset, true, noAssert)\n}\n\nBuffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {\n return writeFloat(this, value, offset, false, noAssert)\n}\n\nfunction writeDouble (buf, value, offset, littleEndian, noAssert) {\n value = +value\n offset = offset >>> 0\n if (!noAssert) {\n checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)\n }\n ieee754.write(buf, value, offset, littleEndian, 52, 8)\n return offset + 8\n}\n\nBuffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {\n return writeDouble(this, value, offset, true, noAssert)\n}\n\nBuffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {\n return writeDouble(this, value, offset, false, noAssert)\n}\n\n// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)\nBuffer.prototype.copy = function copy (target, targetStart, start, end) {\n if (!Buffer.isBuffer(target)) throw new TypeError('argument should be a Buffer')\n if (!start) start = 0\n if (!end && end !== 0) end = this.length\n if (targetStart >= target.length) targetStart = target.length\n if (!targetStart) targetStart = 0\n if (end > 0 && end < start) end = start\n\n // Copy 0 bytes; we're done\n if (end === start) return 0\n if (target.length === 0 || this.length === 0) return 0\n\n // Fatal error conditions\n if (targetStart < 0) {\n throw new RangeError('targetStart out of bounds')\n }\n if (start < 0 || start >= this.length) throw new RangeError('Index out of range')\n if (end < 0) throw new RangeError('sourceEnd out of bounds')\n\n // Are we oob?\n if (end > this.length) end = this.length\n if (target.length - targetStart < end - start) {\n end = target.length - targetStart + start\n }\n\n const len = end - start\n\n if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') {\n // Use built-in when available, missing from IE11\n this.copyWithin(targetStart, start, end)\n } else {\n Uint8Array.prototype.set.call(\n target,\n this.subarray(start, end),\n targetStart\n )\n }\n\n return len\n}\n\n// Usage:\n// buffer.fill(number[, offset[, end]])\n// buffer.fill(buffer[, offset[, end]])\n// buffer.fill(string[, offset[, end]][, encoding])\nBuffer.prototype.fill = function fill (val, start, end, encoding) {\n // Handle string cases:\n if (typeof val === 'string') {\n if (typeof start === 'string') {\n encoding = start\n start = 0\n end = this.length\n } else if (typeof end === 'string') {\n encoding = end\n end = this.length\n }\n if (encoding !== undefined && typeof encoding !== 'string') {\n throw new TypeError('encoding must be a string')\n }\n if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) {\n throw new TypeError('Unknown encoding: ' + encoding)\n }\n if (val.length === 1) {\n const code = val.charCodeAt(0)\n if ((encoding === 'utf8' && code < 128) ||\n encoding === 'latin1') {\n // Fast path: If `val` fits into a single byte, use that numeric value.\n val = code\n }\n }\n } else if (typeof val === 'number') {\n val = val & 255\n } else if (typeof val === 'boolean') {\n val = Number(val)\n }\n\n // Invalid ranges are not set to a default, so can range check early.\n if (start < 0 || this.length < start || this.length < end) {\n throw new RangeError('Out of range index')\n }\n\n if (end <= start) {\n return this\n }\n\n start = start >>> 0\n end = end === undefined ? this.length : end >>> 0\n\n if (!val) val = 0\n\n let i\n if (typeof val === 'number') {\n for (i = start; i < end; ++i) {\n this[i] = val\n }\n } else {\n const bytes = Buffer.isBuffer(val)\n ? val\n : Buffer.from(val, encoding)\n const len = bytes.length\n if (len === 0) {\n throw new TypeError('The value \"' + val +\n '\" is invalid for argument \"value\"')\n }\n for (i = 0; i < end - start; ++i) {\n this[i + start] = bytes[i % len]\n }\n }\n\n return this\n}\n\n// CUSTOM ERRORS\n// =============\n\n// Simplified versions from Node, changed for Buffer-only usage\nconst errors = {}\nfunction E (sym, getMessage, Base) {\n errors[sym] = class NodeError extends Base {\n constructor () {\n super()\n\n Object.defineProperty(this, 'message', {\n value: getMessage.apply(this, arguments),\n writable: true,\n configurable: true\n })\n\n // Add the error code to the name to include it in the stack trace.\n this.name = `${this.name} [${sym}]`\n // Access the stack to generate the error message including the error code\n // from the name.\n this.stack // eslint-disable-line no-unused-expressions\n // Reset the name to the actual name.\n delete this.name\n }\n\n get code () {\n return sym\n }\n\n set code (value) {\n Object.defineProperty(this, 'code', {\n configurable: true,\n enumerable: true,\n value,\n writable: true\n })\n }\n\n toString () {\n return `${this.name} [${sym}]: ${this.message}`\n }\n }\n}\n\nE('ERR_BUFFER_OUT_OF_BOUNDS',\n function (name) {\n if (name) {\n return `${name} is outside of buffer bounds`\n }\n\n return 'Attempt to access memory outside buffer bounds'\n }, RangeError)\nE('ERR_INVALID_ARG_TYPE',\n function (name, actual) {\n return `The \"${name}\" argument must be of type number. Received type ${typeof actual}`\n }, TypeError)\nE('ERR_OUT_OF_RANGE',\n function (str, range, input) {\n let msg = `The value of \"${str}\" is out of range.`\n let received = input\n if (Number.isInteger(input) && Math.abs(input) > 2 ** 32) {\n received = addNumericalSeparator(String(input))\n } else if (typeof input === 'bigint') {\n received = String(input)\n if (input > BigInt(2) ** BigInt(32) || input < -(BigInt(2) ** BigInt(32))) {\n received = addNumericalSeparator(received)\n }\n received += 'n'\n }\n msg += ` It must be ${range}. Received ${received}`\n return msg\n }, RangeError)\n\nfunction addNumericalSeparator (val) {\n let res = ''\n let i = val.length\n const start = val[0] === '-' ? 1 : 0\n for (; i >= start + 4; i -= 3) {\n res = `_${val.slice(i - 3, i)}${res}`\n }\n return `${val.slice(0, i)}${res}`\n}\n\n// CHECK FUNCTIONS\n// ===============\n\nfunction checkBounds (buf, offset, byteLength) {\n validateNumber(offset, 'offset')\n if (buf[offset] === undefined || buf[offset + byteLength] === undefined) {\n boundsError(offset, buf.length - (byteLength + 1))\n }\n}\n\nfunction checkIntBI (value, min, max, buf, offset, byteLength) {\n if (value > max || value < min) {\n const n = typeof min === 'bigint' ? 'n' : ''\n let range\n if (byteLength > 3) {\n if (min === 0 || min === BigInt(0)) {\n range = `>= 0${n} and < 2${n} ** ${(byteLength + 1) * 8}${n}`\n } else {\n range = `>= -(2${n} ** ${(byteLength + 1) * 8 - 1}${n}) and < 2 ** ` +\n `${(byteLength + 1) * 8 - 1}${n}`\n }\n } else {\n range = `>= ${min}${n} and <= ${max}${n}`\n }\n throw new errors.ERR_OUT_OF_RANGE('value', range, value)\n }\n checkBounds(buf, offset, byteLength)\n}\n\nfunction validateNumber (value, name) {\n if (typeof value !== 'number') {\n throw new errors.ERR_INVALID_ARG_TYPE(name, 'number', value)\n }\n}\n\nfunction boundsError (value, length, type) {\n if (Math.floor(value) !== value) {\n validateNumber(value, type)\n throw new errors.ERR_OUT_OF_RANGE(type || 'offset', 'an integer', value)\n }\n\n if (length < 0) {\n throw new errors.ERR_BUFFER_OUT_OF_BOUNDS()\n }\n\n throw new errors.ERR_OUT_OF_RANGE(type || 'offset',\n `>= ${type ? 1 : 0} and <= ${length}`,\n value)\n}\n\n// HELPER FUNCTIONS\n// ================\n\nconst INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g\n\nfunction base64clean (str) {\n // Node takes equal signs as end of the Base64 encoding\n str = str.split('=')[0]\n // Node strips out invalid characters like \\n and \\t from the string, base64-js does not\n str = str.trim().replace(INVALID_BASE64_RE, '')\n // Node converts strings with length < 2 to ''\n if (str.length < 2) return ''\n // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not\n while (str.length % 4 !== 0) {\n str = str + '='\n }\n return str\n}\n\nfunction utf8ToBytes (string, units) {\n units = units || Infinity\n let codePoint\n const length = string.length\n let leadSurrogate = null\n const bytes = []\n\n for (let i = 0; i < length; ++i) {\n codePoint = string.charCodeAt(i)\n\n // is surrogate component\n if (codePoint > 0xD7FF && codePoint < 0xE000) {\n // last char was a lead\n if (!leadSurrogate) {\n // no lead yet\n if (codePoint > 0xDBFF) {\n // unexpected trail\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n continue\n } else if (i + 1 === length) {\n // unpaired lead\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n continue\n }\n\n // valid lead\n leadSurrogate = codePoint\n\n continue\n }\n\n // 2 leads in a row\n if (codePoint < 0xDC00) {\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n leadSurrogate = codePoint\n continue\n }\n\n // valid surrogate pair\n codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000\n } else if (leadSurrogate) {\n // valid bmp char, but last char was a lead\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n }\n\n leadSurrogate = null\n\n // encode utf8\n if (codePoint < 0x80) {\n if ((units -= 1) < 0) break\n bytes.push(codePoint)\n } else if (codePoint < 0x800) {\n if ((units -= 2) < 0) break\n bytes.push(\n codePoint >> 0x6 | 0xC0,\n codePoint & 0x3F | 0x80\n )\n } else if (codePoint < 0x10000) {\n if ((units -= 3) < 0) break\n bytes.push(\n codePoint >> 0xC | 0xE0,\n codePoint >> 0x6 & 0x3F | 0x80,\n codePoint & 0x3F | 0x80\n )\n } else if (codePoint < 0x110000) {\n if ((units -= 4) < 0) break\n bytes.push(\n codePoint >> 0x12 | 0xF0,\n codePoint >> 0xC & 0x3F | 0x80,\n codePoint >> 0x6 & 0x3F | 0x80,\n codePoint & 0x3F | 0x80\n )\n } else {\n throw new Error('Invalid code point')\n }\n }\n\n return bytes\n}\n\nfunction asciiToBytes (str) {\n const byteArray = []\n for (let i = 0; i < str.length; ++i) {\n // Node's code seems to be doing this and not & 0x7F..\n byteArray.push(str.charCodeAt(i) & 0xFF)\n }\n return byteArray\n}\n\nfunction utf16leToBytes (str, units) {\n let c, hi, lo\n const byteArray = []\n for (let i = 0; i < str.length; ++i) {\n if ((units -= 2) < 0) break\n\n c = str.charCodeAt(i)\n hi = c >> 8\n lo = c % 256\n byteArray.push(lo)\n byteArray.push(hi)\n }\n\n return byteArray\n}\n\nfunction base64ToBytes (str) {\n return base64.toByteArray(base64clean(str))\n}\n\nfunction blitBuffer (src, dst, offset, length) {\n let i\n for (i = 0; i < length; ++i) {\n if ((i + offset >= dst.length) || (i >= src.length)) break\n dst[i + offset] = src[i]\n }\n return i\n}\n\n// ArrayBuffer or Uint8Array objects from other contexts (i.e. iframes) do not pass\n// the `instanceof` check but they should be treated as of that type.\n// See: https://github.com/feross/buffer/issues/166\nfunction isInstance (obj, type) {\n return obj instanceof type ||\n (obj != null && obj.constructor != null && obj.constructor.name != null &&\n obj.constructor.name === type.name)\n}\nfunction numberIsNaN (obj) {\n // For IE11 support\n return obj !== obj // eslint-disable-line no-self-compare\n}\n\n// Create lookup table for `toString('hex')`\n// See: https://github.com/feross/buffer/issues/219\nconst hexSliceLookupTable = (function () {\n const alphabet = '0123456789abcdef'\n const table = new Array(256)\n for (let i = 0; i < 16; ++i) {\n const i16 = i * 16\n for (let j = 0; j < 16; ++j) {\n table[i16 + j] = alphabet[i] + alphabet[j]\n }\n }\n return table\n})()\n\n// Return not function with Error if BigInt not supported\nfunction defineBigIntMethod (fn) {\n return typeof BigInt === 'undefined' ? BufferBigIntNotDefined : fn\n}\n\nfunction BufferBigIntNotDefined () {\n throw new Error('BigInt not supported')\n}\n","require('../../modules/es.object.define-property');\nvar path = require('../../internals/path');\n\nvar Object = path.Object;\n\nvar defineProperty = module.exports = function defineProperty(it, key, desc) {\n return Object.defineProperty(it, key, desc);\n};\n\nif (Object.defineProperty.sham) defineProperty.sham = true;\n","var isCallable = require('../internals/is-callable');\nvar tryToString = require('../internals/try-to-string');\n\nvar $TypeError = TypeError;\n\n// `Assert: IsCallable(argument) is true`\nmodule.exports = function (argument) {\n if (isCallable(argument)) return argument;\n throw $TypeError(tryToString(argument) + ' is not a function');\n};\n","var isObject = require('../internals/is-object');\n\nvar $String = String;\nvar $TypeError = TypeError;\n\n// `Assert: Type(argument) is Object`\nmodule.exports = function (argument) {\n if (isObject(argument)) return argument;\n throw $TypeError($String(argument) + ' is not an object');\n};\n","var uncurryThis = require('../internals/function-uncurry-this');\n\nvar toString = uncurryThis({}.toString);\nvar stringSlice = uncurryThis(''.slice);\n\nmodule.exports = function (it) {\n return stringSlice(toString(it), 8, -1);\n};\n","var DESCRIPTORS = require('../internals/descriptors');\nvar definePropertyModule = require('../internals/object-define-property');\nvar createPropertyDescriptor = require('../internals/create-property-descriptor');\n\nmodule.exports = DESCRIPTORS ? function (object, key, value) {\n return definePropertyModule.f(object, key, createPropertyDescriptor(1, value));\n} : function (object, key, value) {\n object[key] = value;\n return object;\n};\n","module.exports = function (bitmap, value) {\n return {\n enumerable: !(bitmap & 1),\n configurable: !(bitmap & 2),\n writable: !(bitmap & 4),\n value: value\n };\n};\n","var global = require('../internals/global');\n\n// eslint-disable-next-line es/no-object-defineproperty -- safe\nvar defineProperty = Object.defineProperty;\n\nmodule.exports = function (key, value) {\n try {\n defineProperty(global, key, { value: value, configurable: true, writable: true });\n } catch (error) {\n global[key] = value;\n } return value;\n};\n","var fails = require('../internals/fails');\n\n// Detect IE8's incomplete defineProperty implementation\nmodule.exports = !fails(function () {\n // eslint-disable-next-line es/no-object-defineproperty -- required for testing\n return Object.defineProperty({}, 1, { get: function () { return 7; } })[1] != 7;\n});\n","var documentAll = typeof document == 'object' && document.all;\n\n// https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot\n// eslint-disable-next-line unicorn/no-typeof-undefined -- required for testing\nvar IS_HTMLDDA = typeof documentAll == 'undefined' && documentAll !== undefined;\n\nmodule.exports = {\n all: documentAll,\n IS_HTMLDDA: IS_HTMLDDA\n};\n","var global = require('../internals/global');\nvar isObject = require('../internals/is-object');\n\nvar document = global.document;\n// typeof document.createElement is 'object' in old IE\nvar EXISTS = isObject(document) && isObject(document.createElement);\n\nmodule.exports = function (it) {\n return EXISTS ? document.createElement(it) : {};\n};\n","module.exports = typeof navigator != 'undefined' && String(navigator.userAgent) || '';\n","var global = require('../internals/global');\nvar userAgent = require('../internals/engine-user-agent');\n\nvar process = global.process;\nvar Deno = global.Deno;\nvar versions = process && process.versions || Deno && Deno.version;\nvar v8 = versions && versions.v8;\nvar match, version;\n\nif (v8) {\n match = v8.split('.');\n // in old Chrome, versions of V8 isn't V8 = Chrome / 10\n // but their correct versions are not interesting for us\n version = match[0] > 0 && match[0] < 4 ? 1 : +(match[0] + match[1]);\n}\n\n// BrowserFS NodeJS `process` polyfill incorrectly set `.v8` to `0.0`\n// so check `userAgent` even if `.v8` exists, but 0\nif (!version && userAgent) {\n match = userAgent.match(/Edge\\/(\\d+)/);\n if (!match || match[1] >= 74) {\n match = userAgent.match(/Chrome\\/(\\d+)/);\n if (match) version = +match[1];\n }\n}\n\nmodule.exports = version;\n","'use strict';\nvar global = require('../internals/global');\nvar apply = require('../internals/function-apply');\nvar uncurryThis = require('../internals/function-uncurry-this-clause');\nvar isCallable = require('../internals/is-callable');\nvar getOwnPropertyDescriptor = require('../internals/object-get-own-property-descriptor').f;\nvar isForced = require('../internals/is-forced');\nvar path = require('../internals/path');\nvar bind = require('../internals/function-bind-context');\nvar createNonEnumerableProperty = require('../internals/create-non-enumerable-property');\nvar hasOwn = require('../internals/has-own-property');\n\nvar wrapConstructor = function (NativeConstructor) {\n var Wrapper = function (a, b, c) {\n if (this instanceof Wrapper) {\n switch (arguments.length) {\n case 0: return new NativeConstructor();\n case 1: return new NativeConstructor(a);\n case 2: return new NativeConstructor(a, b);\n } return new NativeConstructor(a, b, c);\n } return apply(NativeConstructor, this, arguments);\n };\n Wrapper.prototype = NativeConstructor.prototype;\n return Wrapper;\n};\n\n/*\n options.target - name of the target object\n options.global - target is the global object\n options.stat - export as static methods of target\n options.proto - export as prototype methods of target\n options.real - real prototype method for the `pure` version\n options.forced - export even if the native feature is available\n options.bind - bind methods to the target, required for the `pure` version\n options.wrap - wrap constructors to preventing global pollution, required for the `pure` version\n options.unsafe - use the simple assignment of property instead of delete + defineProperty\n options.sham - add a flag to not completely full polyfills\n options.enumerable - export as enumerable property\n options.dontCallGetSet - prevent calling a getter on target\n options.name - the .name of the function if it does not match the key\n*/\nmodule.exports = function (options, source) {\n var TARGET = options.target;\n var GLOBAL = options.global;\n var STATIC = options.stat;\n var PROTO = options.proto;\n\n var nativeSource = GLOBAL ? global : STATIC ? global[TARGET] : (global[TARGET] || {}).prototype;\n\n var target = GLOBAL ? path : path[TARGET] || createNonEnumerableProperty(path, TARGET, {})[TARGET];\n var targetPrototype = target.prototype;\n\n var FORCED, USE_NATIVE, VIRTUAL_PROTOTYPE;\n var key, sourceProperty, targetProperty, nativeProperty, resultProperty, descriptor;\n\n for (key in source) {\n FORCED = isForced(GLOBAL ? key : TARGET + (STATIC ? '.' : '#') + key, options.forced);\n // contains in native\n USE_NATIVE = !FORCED && nativeSource && hasOwn(nativeSource, key);\n\n targetProperty = target[key];\n\n if (USE_NATIVE) if (options.dontCallGetSet) {\n descriptor = getOwnPropertyDescriptor(nativeSource, key);\n nativeProperty = descriptor && descriptor.value;\n } else nativeProperty = nativeSource[key];\n\n // export native or implementation\n sourceProperty = (USE_NATIVE && nativeProperty) ? nativeProperty : source[key];\n\n if (USE_NATIVE && typeof targetProperty == typeof sourceProperty) continue;\n\n // bind methods to global for calling from export context\n if (options.bind && USE_NATIVE) resultProperty = bind(sourceProperty, global);\n // wrap global constructors for prevent changes in this version\n else if (options.wrap && USE_NATIVE) resultProperty = wrapConstructor(sourceProperty);\n // make static versions for prototype methods\n else if (PROTO && isCallable(sourceProperty)) resultProperty = uncurryThis(sourceProperty);\n // default case\n else resultProperty = sourceProperty;\n\n // add a flag to not completely full polyfills\n if (options.sham || (sourceProperty && sourceProperty.sham) || (targetProperty && targetProperty.sham)) {\n createNonEnumerableProperty(resultProperty, 'sham', true);\n }\n\n createNonEnumerableProperty(target, key, resultProperty);\n\n if (PROTO) {\n VIRTUAL_PROTOTYPE = TARGET + 'Prototype';\n if (!hasOwn(path, VIRTUAL_PROTOTYPE)) {\n createNonEnumerableProperty(path, VIRTUAL_PROTOTYPE, {});\n }\n // export virtual prototype methods\n createNonEnumerableProperty(path[VIRTUAL_PROTOTYPE], key, sourceProperty);\n // export real prototype methods\n if (options.real && targetPrototype && (FORCED || !targetPrototype[key])) {\n createNonEnumerableProperty(targetPrototype, key, sourceProperty);\n }\n }\n }\n};\n","module.exports = function (exec) {\n try {\n return !!exec();\n } catch (error) {\n return true;\n }\n};\n","var NATIVE_BIND = require('../internals/function-bind-native');\n\nvar FunctionPrototype = Function.prototype;\nvar apply = FunctionPrototype.apply;\nvar call = FunctionPrototype.call;\n\n// eslint-disable-next-line es/no-reflect -- safe\nmodule.exports = typeof Reflect == 'object' && Reflect.apply || (NATIVE_BIND ? call.bind(apply) : function () {\n return call.apply(apply, arguments);\n});\n","var uncurryThis = require('../internals/function-uncurry-this-clause');\nvar aCallable = require('../internals/a-callable');\nvar NATIVE_BIND = require('../internals/function-bind-native');\n\nvar bind = uncurryThis(uncurryThis.bind);\n\n// optional / simple context binding\nmodule.exports = function (fn, that) {\n aCallable(fn);\n return that === undefined ? fn : NATIVE_BIND ? bind(fn, that) : function (/* ...args */) {\n return fn.apply(that, arguments);\n };\n};\n","var fails = require('../internals/fails');\n\nmodule.exports = !fails(function () {\n // eslint-disable-next-line es/no-function-prototype-bind -- safe\n var test = (function () { /* empty */ }).bind();\n // eslint-disable-next-line no-prototype-builtins -- safe\n return typeof test != 'function' || test.hasOwnProperty('prototype');\n});\n","var NATIVE_BIND = require('../internals/function-bind-native');\n\nvar call = Function.prototype.call;\n\nmodule.exports = NATIVE_BIND ? call.bind(call) : function () {\n return call.apply(call, arguments);\n};\n","var classofRaw = require('../internals/classof-raw');\nvar uncurryThis = require('../internals/function-uncurry-this');\n\nmodule.exports = function (fn) {\n // Nashorn bug:\n // https://github.com/zloirock/core-js/issues/1128\n // https://github.com/zloirock/core-js/issues/1130\n if (classofRaw(fn) === 'Function') return uncurryThis(fn);\n};\n","var NATIVE_BIND = require('../internals/function-bind-native');\n\nvar FunctionPrototype = Function.prototype;\nvar call = FunctionPrototype.call;\nvar uncurryThisWithBind = NATIVE_BIND && FunctionPrototype.bind.bind(call, call);\n\nmodule.exports = NATIVE_BIND ? uncurryThisWithBind : function (fn) {\n return function () {\n return call.apply(fn, arguments);\n };\n};\n","var path = require('../internals/path');\nvar global = require('../internals/global');\nvar isCallable = require('../internals/is-callable');\n\nvar aFunction = function (variable) {\n return isCallable(variable) ? variable : undefined;\n};\n\nmodule.exports = function (namespace, method) {\n return arguments.length < 2 ? aFunction(path[namespace]) || aFunction(global[namespace])\n : path[namespace] && path[namespace][method] || global[namespace] && global[namespace][method];\n};\n","var aCallable = require('../internals/a-callable');\nvar isNullOrUndefined = require('../internals/is-null-or-undefined');\n\n// `GetMethod` abstract operation\n// https://tc39.es/ecma262/#sec-getmethod\nmodule.exports = function (V, P) {\n var func = V[P];\n return isNullOrUndefined(func) ? undefined : aCallable(func);\n};\n","var check = function (it) {\n return it && it.Math == Math && it;\n};\n\n// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028\nmodule.exports =\n // eslint-disable-next-line es/no-global-this -- safe\n check(typeof globalThis == 'object' && globalThis) ||\n check(typeof window == 'object' && window) ||\n // eslint-disable-next-line no-restricted-globals -- safe\n check(typeof self == 'object' && self) ||\n check(typeof global == 'object' && global) ||\n // eslint-disable-next-line no-new-func -- fallback\n (function () { return this; })() || this || Function('return this')();\n","var uncurryThis = require('../internals/function-uncurry-this');\nvar toObject = require('../internals/to-object');\n\nvar hasOwnProperty = uncurryThis({}.hasOwnProperty);\n\n// `HasOwnProperty` abstract operation\n// https://tc39.es/ecma262/#sec-hasownproperty\n// eslint-disable-next-line es/no-object-hasown -- safe\nmodule.exports = Object.hasOwn || function hasOwn(it, key) {\n return hasOwnProperty(toObject(it), key);\n};\n","var DESCRIPTORS = require('../internals/descriptors');\nvar fails = require('../internals/fails');\nvar createElement = require('../internals/document-create-element');\n\n// Thanks to IE8 for its funny defineProperty\nmodule.exports = !DESCRIPTORS && !fails(function () {\n // eslint-disable-next-line es/no-object-defineproperty -- required for testing\n return Object.defineProperty(createElement('div'), 'a', {\n get: function () { return 7; }\n }).a != 7;\n});\n","var uncurryThis = require('../internals/function-uncurry-this');\nvar fails = require('../internals/fails');\nvar classof = require('../internals/classof-raw');\n\nvar $Object = Object;\nvar split = uncurryThis(''.split);\n\n// fallback for non-array-like ES3 and non-enumerable old V8 strings\nmodule.exports = fails(function () {\n // throws an error in rhino, see https://github.com/mozilla/rhino/issues/346\n // eslint-disable-next-line no-prototype-builtins -- safe\n return !$Object('z').propertyIsEnumerable(0);\n}) ? function (it) {\n return classof(it) == 'String' ? split(it, '') : $Object(it);\n} : $Object;\n","var $documentAll = require('../internals/document-all');\n\nvar documentAll = $documentAll.all;\n\n// `IsCallable` abstract operation\n// https://tc39.es/ecma262/#sec-iscallable\nmodule.exports = $documentAll.IS_HTMLDDA ? function (argument) {\n return typeof argument == 'function' || argument === documentAll;\n} : function (argument) {\n return typeof argument == 'function';\n};\n","var fails = require('../internals/fails');\nvar isCallable = require('../internals/is-callable');\n\nvar replacement = /#|\\.prototype\\./;\n\nvar isForced = function (feature, detection) {\n var value = data[normalize(feature)];\n return value == POLYFILL ? true\n : value == NATIVE ? false\n : isCallable(detection) ? fails(detection)\n : !!detection;\n};\n\nvar normalize = isForced.normalize = function (string) {\n return String(string).replace(replacement, '.').toLowerCase();\n};\n\nvar data = isForced.data = {};\nvar NATIVE = isForced.NATIVE = 'N';\nvar POLYFILL = isForced.POLYFILL = 'P';\n\nmodule.exports = isForced;\n","// we can't use just `it == null` since of `document.all` special case\n// https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot-aec\nmodule.exports = function (it) {\n return it === null || it === undefined;\n};\n","var isCallable = require('../internals/is-callable');\nvar $documentAll = require('../internals/document-all');\n\nvar documentAll = $documentAll.all;\n\nmodule.exports = $documentAll.IS_HTMLDDA ? function (it) {\n return typeof it == 'object' ? it !== null : isCallable(it) || it === documentAll;\n} : function (it) {\n return typeof it == 'object' ? it !== null : isCallable(it);\n};\n","module.exports = true;\n","var getBuiltIn = require('../internals/get-built-in');\nvar isCallable = require('../internals/is-callable');\nvar isPrototypeOf = require('../internals/object-is-prototype-of');\nvar USE_SYMBOL_AS_UID = require('../internals/use-symbol-as-uid');\n\nvar $Object = Object;\n\nmodule.exports = USE_SYMBOL_AS_UID ? function (it) {\n return typeof it == 'symbol';\n} : function (it) {\n var $Symbol = getBuiltIn('Symbol');\n return isCallable($Symbol) && isPrototypeOf($Symbol.prototype, $Object(it));\n};\n","var DESCRIPTORS = require('../internals/descriptors');\nvar IE8_DOM_DEFINE = require('../internals/ie8-dom-define');\nvar V8_PROTOTYPE_DEFINE_BUG = require('../internals/v8-prototype-define-bug');\nvar anObject = require('../internals/an-object');\nvar toPropertyKey = require('../internals/to-property-key');\n\nvar $TypeError = TypeError;\n// eslint-disable-next-line es/no-object-defineproperty -- safe\nvar $defineProperty = Object.defineProperty;\n// eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe\nvar $getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;\nvar ENUMERABLE = 'enumerable';\nvar CONFIGURABLE = 'configurable';\nvar WRITABLE = 'writable';\n\n// `Object.defineProperty` method\n// https://tc39.es/ecma262/#sec-object.defineproperty\nexports.f = DESCRIPTORS ? V8_PROTOTYPE_DEFINE_BUG ? function defineProperty(O, P, Attributes) {\n anObject(O);\n P = toPropertyKey(P);\n anObject(Attributes);\n if (typeof O === 'function' && P === 'prototype' && 'value' in Attributes && WRITABLE in Attributes && !Attributes[WRITABLE]) {\n var current = $getOwnPropertyDescriptor(O, P);\n if (current && current[WRITABLE]) {\n O[P] = Attributes.value;\n Attributes = {\n configurable: CONFIGURABLE in Attributes ? Attributes[CONFIGURABLE] : current[CONFIGURABLE],\n enumerable: ENUMERABLE in Attributes ? Attributes[ENUMERABLE] : current[ENUMERABLE],\n writable: false\n };\n }\n } return $defineProperty(O, P, Attributes);\n} : $defineProperty : function defineProperty(O, P, Attributes) {\n anObject(O);\n P = toPropertyKey(P);\n anObject(Attributes);\n if (IE8_DOM_DEFINE) try {\n return $defineProperty(O, P, Attributes);\n } catch (error) { /* empty */ }\n if ('get' in Attributes || 'set' in Attributes) throw $TypeError('Accessors not supported');\n if ('value' in Attributes) O[P] = Attributes.value;\n return O;\n};\n","var DESCRIPTORS = require('../internals/descriptors');\nvar call = require('../internals/function-call');\nvar propertyIsEnumerableModule = require('../internals/object-property-is-enumerable');\nvar createPropertyDescriptor = require('../internals/create-property-descriptor');\nvar toIndexedObject = require('../internals/to-indexed-object');\nvar toPropertyKey = require('../internals/to-property-key');\nvar hasOwn = require('../internals/has-own-property');\nvar IE8_DOM_DEFINE = require('../internals/ie8-dom-define');\n\n// eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe\nvar $getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;\n\n// `Object.getOwnPropertyDescriptor` method\n// https://tc39.es/ecma262/#sec-object.getownpropertydescriptor\nexports.f = DESCRIPTORS ? $getOwnPropertyDescriptor : function getOwnPropertyDescriptor(O, P) {\n O = toIndexedObject(O);\n P = toPropertyKey(P);\n if (IE8_DOM_DEFINE) try {\n return $getOwnPropertyDescriptor(O, P);\n } catch (error) { /* empty */ }\n if (hasOwn(O, P)) return createPropertyDescriptor(!call(propertyIsEnumerableModule.f, O, P), O[P]);\n};\n","var uncurryThis = require('../internals/function-uncurry-this');\n\nmodule.exports = uncurryThis({}.isPrototypeOf);\n","'use strict';\nvar $propertyIsEnumerable = {}.propertyIsEnumerable;\n// eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe\nvar getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;\n\n// Nashorn ~ JDK8 bug\nvar NASHORN_BUG = getOwnPropertyDescriptor && !$propertyIsEnumerable.call({ 1: 2 }, 1);\n\n// `Object.prototype.propertyIsEnumerable` method implementation\n// https://tc39.es/ecma262/#sec-object.prototype.propertyisenumerable\nexports.f = NASHORN_BUG ? function propertyIsEnumerable(V) {\n var descriptor = getOwnPropertyDescriptor(this, V);\n return !!descriptor && descriptor.enumerable;\n} : $propertyIsEnumerable;\n","var call = require('../internals/function-call');\nvar isCallable = require('../internals/is-callable');\nvar isObject = require('../internals/is-object');\n\nvar $TypeError = TypeError;\n\n// `OrdinaryToPrimitive` abstract operation\n// https://tc39.es/ecma262/#sec-ordinarytoprimitive\nmodule.exports = function (input, pref) {\n var fn, val;\n if (pref === 'string' && isCallable(fn = input.toString) && !isObject(val = call(fn, input))) return val;\n if (isCallable(fn = input.valueOf) && !isObject(val = call(fn, input))) return val;\n if (pref !== 'string' && isCallable(fn = input.toString) && !isObject(val = call(fn, input))) return val;\n throw $TypeError(\"Can't convert object to primitive value\");\n};\n","module.exports = {};\n","var isNullOrUndefined = require('../internals/is-null-or-undefined');\n\nvar $TypeError = TypeError;\n\n// `RequireObjectCoercible` abstract operation\n// https://tc39.es/ecma262/#sec-requireobjectcoercible\nmodule.exports = function (it) {\n if (isNullOrUndefined(it)) throw $TypeError(\"Can't call method on \" + it);\n return it;\n};\n","var global = require('../internals/global');\nvar defineGlobalProperty = require('../internals/define-global-property');\n\nvar SHARED = '__core-js_shared__';\nvar store = global[SHARED] || defineGlobalProperty(SHARED, {});\n\nmodule.exports = store;\n","var IS_PURE = require('../internals/is-pure');\nvar store = require('../internals/shared-store');\n\n(module.exports = function (key, value) {\n return store[key] || (store[key] = value !== undefined ? value : {});\n})('versions', []).push({\n version: '3.31.0',\n mode: IS_PURE ? 'pure' : 'global',\n copyright: 'ยฉ 2014-2023 Denis Pushkarev (zloirock.ru)',\n license: 'https://github.com/zloirock/core-js/blob/v3.31.0/LICENSE',\n source: 'https://github.com/zloirock/core-js'\n});\n","/* eslint-disable es/no-symbol -- required for testing */\nvar V8_VERSION = require('../internals/engine-v8-version');\nvar fails = require('../internals/fails');\nvar global = require('../internals/global');\n\nvar $String = global.String;\n\n// eslint-disable-next-line es/no-object-getownpropertysymbols -- required for testing\nmodule.exports = !!Object.getOwnPropertySymbols && !fails(function () {\n var symbol = Symbol();\n // Chrome 38 Symbol has incorrect toString conversion\n // `get-own-property-symbols` polyfill symbols converted to object are not Symbol instances\n // nb: Do not call `String` directly to avoid this being optimized out to `symbol+''` which will,\n // of course, fail.\n return !$String(symbol) || !(Object(symbol) instanceof Symbol) ||\n // Chrome 38-40 symbols are not inherited from DOM collections prototypes to instances\n !Symbol.sham && V8_VERSION && V8_VERSION < 41;\n});\n","// toObject with fallback for non-array-like ES3 strings\nvar IndexedObject = require('../internals/indexed-object');\nvar requireObjectCoercible = require('../internals/require-object-coercible');\n\nmodule.exports = function (it) {\n return IndexedObject(requireObjectCoercible(it));\n};\n","var requireObjectCoercible = require('../internals/require-object-coercible');\n\nvar $Object = Object;\n\n// `ToObject` abstract operation\n// https://tc39.es/ecma262/#sec-toobject\nmodule.exports = function (argument) {\n return $Object(requireObjectCoercible(argument));\n};\n","var call = require('../internals/function-call');\nvar isObject = require('../internals/is-object');\nvar isSymbol = require('../internals/is-symbol');\nvar getMethod = require('../internals/get-method');\nvar ordinaryToPrimitive = require('../internals/ordinary-to-primitive');\nvar wellKnownSymbol = require('../internals/well-known-symbol');\n\nvar $TypeError = TypeError;\nvar TO_PRIMITIVE = wellKnownSymbol('toPrimitive');\n\n// `ToPrimitive` abstract operation\n// https://tc39.es/ecma262/#sec-toprimitive\nmodule.exports = function (input, pref) {\n if (!isObject(input) || isSymbol(input)) return input;\n var exoticToPrim = getMethod(input, TO_PRIMITIVE);\n var result;\n if (exoticToPrim) {\n if (pref === undefined) pref = 'default';\n result = call(exoticToPrim, input, pref);\n if (!isObject(result) || isSymbol(result)) return result;\n throw $TypeError(\"Can't convert object to primitive value\");\n }\n if (pref === undefined) pref = 'number';\n return ordinaryToPrimitive(input, pref);\n};\n","var toPrimitive = require('../internals/to-primitive');\nvar isSymbol = require('../internals/is-symbol');\n\n// `ToPropertyKey` abstract operation\n// https://tc39.es/ecma262/#sec-topropertykey\nmodule.exports = function (argument) {\n var key = toPrimitive(argument, 'string');\n return isSymbol(key) ? key : key + '';\n};\n","var $String = String;\n\nmodule.exports = function (argument) {\n try {\n return $String(argument);\n } catch (error) {\n return 'Object';\n }\n};\n","var uncurryThis = require('../internals/function-uncurry-this');\n\nvar id = 0;\nvar postfix = Math.random();\nvar toString = uncurryThis(1.0.toString);\n\nmodule.exports = function (key) {\n return 'Symbol(' + (key === undefined ? '' : key) + ')_' + toString(++id + postfix, 36);\n};\n","/* eslint-disable es/no-symbol -- required for testing */\nvar NATIVE_SYMBOL = require('../internals/symbol-constructor-detection');\n\nmodule.exports = NATIVE_SYMBOL\n && !Symbol.sham\n && typeof Symbol.iterator == 'symbol';\n","var DESCRIPTORS = require('../internals/descriptors');\nvar fails = require('../internals/fails');\n\n// V8 ~ Chrome 36-\n// https://bugs.chromium.org/p/v8/issues/detail?id=3334\nmodule.exports = DESCRIPTORS && fails(function () {\n // eslint-disable-next-line es/no-object-defineproperty -- required for testing\n return Object.defineProperty(function () { /* empty */ }, 'prototype', {\n value: 42,\n writable: false\n }).prototype != 42;\n});\n","var global = require('../internals/global');\nvar shared = require('../internals/shared');\nvar hasOwn = require('../internals/has-own-property');\nvar uid = require('../internals/uid');\nvar NATIVE_SYMBOL = require('../internals/symbol-constructor-detection');\nvar USE_SYMBOL_AS_UID = require('../internals/use-symbol-as-uid');\n\nvar Symbol = global.Symbol;\nvar WellKnownSymbolsStore = shared('wks');\nvar createWellKnownSymbol = USE_SYMBOL_AS_UID ? Symbol['for'] || Symbol : Symbol && Symbol.withoutSetter || uid;\n\nmodule.exports = function (name) {\n if (!hasOwn(WellKnownSymbolsStore, name)) {\n WellKnownSymbolsStore[name] = NATIVE_SYMBOL && hasOwn(Symbol, name)\n ? Symbol[name]\n : createWellKnownSymbol('Symbol.' + name);\n } return WellKnownSymbolsStore[name];\n};\n","var $ = require('../internals/export');\nvar DESCRIPTORS = require('../internals/descriptors');\nvar defineProperty = require('../internals/object-define-property').f;\n\n// `Object.defineProperty` method\n// https://tc39.es/ecma262/#sec-object.defineproperty\n// eslint-disable-next-line es/no-object-defineproperty -- safe\n$({ target: 'Object', stat: true, forced: Object.defineProperty !== defineProperty, sham: !DESCRIPTORS }, {\n defineProperty: defineProperty\n});\n","var parent = require('../../es/object/define-property');\n\nmodule.exports = parent;\n","/*!\n * @description Recursive object extending\n * @author Viacheslav Lotsmanov \n * @license MIT\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2013-2018 Viacheslav Lotsmanov\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of\n * this software and associated documentation files (the \"Software\"), to deal in\n * the Software without restriction, including without limitation the rights to\n * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n * the Software, and to permit persons to whom the Software is furnished to do so,\n * subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\n * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n'use strict';\n\nfunction isSpecificValue(val) {\n\treturn (\n\t\tval instanceof Buffer\n\t\t|| val instanceof Date\n\t\t|| val instanceof RegExp\n\t) ? true : false;\n}\n\nfunction cloneSpecificValue(val) {\n\tif (val instanceof Buffer) {\n\t\tvar x = Buffer.alloc\n\t\t\t? Buffer.alloc(val.length)\n\t\t\t: new Buffer(val.length);\n\t\tval.copy(x);\n\t\treturn x;\n\t} else if (val instanceof Date) {\n\t\treturn new Date(val.getTime());\n\t} else if (val instanceof RegExp) {\n\t\treturn new RegExp(val);\n\t} else {\n\t\tthrow new Error('Unexpected situation');\n\t}\n}\n\n/**\n * Recursive cloning array.\n */\nfunction deepCloneArray(arr) {\n\tvar clone = [];\n\tarr.forEach(function (item, index) {\n\t\tif (typeof item === 'object' && item !== null) {\n\t\t\tif (Array.isArray(item)) {\n\t\t\t\tclone[index] = deepCloneArray(item);\n\t\t\t} else if (isSpecificValue(item)) {\n\t\t\t\tclone[index] = cloneSpecificValue(item);\n\t\t\t} else {\n\t\t\t\tclone[index] = deepExtend({}, item);\n\t\t\t}\n\t\t} else {\n\t\t\tclone[index] = item;\n\t\t}\n\t});\n\treturn clone;\n}\n\nfunction safeGetProperty(object, property) {\n\treturn property === '__proto__' ? undefined : object[property];\n}\n\n/**\n * Extening object that entered in first argument.\n *\n * Returns extended object or false if have no target object or incorrect type.\n *\n * If you wish to clone source object (without modify it), just use empty new\n * object as first argument, like this:\n * deepExtend({}, yourObj_1, [yourObj_N]);\n */\nvar deepExtend = module.exports = function (/*obj_1, [obj_2], [obj_N]*/) {\n\tif (arguments.length < 1 || typeof arguments[0] !== 'object') {\n\t\treturn false;\n\t}\n\n\tif (arguments.length < 2) {\n\t\treturn arguments[0];\n\t}\n\n\tvar target = arguments[0];\n\n\t// convert arguments to array and cut off target object\n\tvar args = Array.prototype.slice.call(arguments, 1);\n\n\tvar val, src, clone;\n\n\targs.forEach(function (obj) {\n\t\t// skip argument if isn't an object, is null, or is an array\n\t\tif (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {\n\t\t\treturn;\n\t\t}\n\n\t\tObject.keys(obj).forEach(function (key) {\n\t\t\tsrc = safeGetProperty(target, key); // source value\n\t\t\tval = safeGetProperty(obj, key); // new value\n\n\t\t\t// recursion prevention\n\t\t\tif (val === target) {\n\t\t\t\treturn;\n\n\t\t\t/**\n\t\t\t * if new value isn't object then just overwrite by new value\n\t\t\t * instead of extending.\n\t\t\t */\n\t\t\t} else if (typeof val !== 'object' || val === null) {\n\t\t\t\ttarget[key] = val;\n\t\t\t\treturn;\n\n\t\t\t// just clone arrays (and recursive clone objects inside)\n\t\t\t} else if (Array.isArray(val)) {\n\t\t\t\ttarget[key] = deepCloneArray(val);\n\t\t\t\treturn;\n\n\t\t\t// custom cloning and overwrite for specific objects\n\t\t\t} else if (isSpecificValue(val)) {\n\t\t\t\ttarget[key] = cloneSpecificValue(val);\n\t\t\t\treturn;\n\n\t\t\t// overwrite by new value if source isn't object or array\n\t\t\t} else if (typeof src !== 'object' || src === null || Array.isArray(src)) {\n\t\t\t\ttarget[key] = deepExtend({}, val);\n\t\t\t\treturn;\n\n\t\t\t// source value and new value is objects both, extending...\n\t\t\t} else {\n\t\t\t\ttarget[key] = deepExtend(src, val);\n\t\t\t\treturn;\n\t\t\t}\n\t\t});\n\t});\n\n\treturn target;\n};\n","// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n'use strict';\n\nvar R = typeof Reflect === 'object' ? Reflect : null\nvar ReflectApply = R && typeof R.apply === 'function'\n ? R.apply\n : function ReflectApply(target, receiver, args) {\n return Function.prototype.apply.call(target, receiver, args);\n }\n\nvar ReflectOwnKeys\nif (R && typeof R.ownKeys === 'function') {\n ReflectOwnKeys = R.ownKeys\n} else if (Object.getOwnPropertySymbols) {\n ReflectOwnKeys = function ReflectOwnKeys(target) {\n return Object.getOwnPropertyNames(target)\n .concat(Object.getOwnPropertySymbols(target));\n };\n} else {\n ReflectOwnKeys = function ReflectOwnKeys(target) {\n return Object.getOwnPropertyNames(target);\n };\n}\n\nfunction ProcessEmitWarning(warning) {\n if (console && console.warn) console.warn(warning);\n}\n\nvar NumberIsNaN = Number.isNaN || function NumberIsNaN(value) {\n return value !== value;\n}\n\nfunction EventEmitter() {\n EventEmitter.init.call(this);\n}\nmodule.exports = EventEmitter;\nmodule.exports.once = once;\n\n// Backwards-compat with node 0.10.x\nEventEmitter.EventEmitter = EventEmitter;\n\nEventEmitter.prototype._events = undefined;\nEventEmitter.prototype._eventsCount = 0;\nEventEmitter.prototype._maxListeners = undefined;\n\n// By default EventEmitters will print a warning if more than 10 listeners are\n// added to it. This is a useful default which helps finding memory leaks.\nvar defaultMaxListeners = 10;\n\nfunction checkListener(listener) {\n if (typeof listener !== 'function') {\n throw new TypeError('The \"listener\" argument must be of type Function. Received type ' + typeof listener);\n }\n}\n\nObject.defineProperty(EventEmitter, 'defaultMaxListeners', {\n enumerable: true,\n get: function() {\n return defaultMaxListeners;\n },\n set: function(arg) {\n if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {\n throw new RangeError('The value of \"defaultMaxListeners\" is out of range. It must be a non-negative number. Received ' + arg + '.');\n }\n defaultMaxListeners = arg;\n }\n});\n\nEventEmitter.init = function() {\n\n if (this._events === undefined ||\n this._events === Object.getPrototypeOf(this)._events) {\n this._events = Object.create(null);\n this._eventsCount = 0;\n }\n\n this._maxListeners = this._maxListeners || undefined;\n};\n\n// Obviously not all Emitters should be limited to 10. This function allows\n// that to be increased. Set to zero for unlimited.\nEventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {\n if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {\n throw new RangeError('The value of \"n\" is out of range. It must be a non-negative number. Received ' + n + '.');\n }\n this._maxListeners = n;\n return this;\n};\n\nfunction _getMaxListeners(that) {\n if (that._maxListeners === undefined)\n return EventEmitter.defaultMaxListeners;\n return that._maxListeners;\n}\n\nEventEmitter.prototype.getMaxListeners = function getMaxListeners() {\n return _getMaxListeners(this);\n};\n\nEventEmitter.prototype.emit = function emit(type) {\n var args = [];\n for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);\n var doError = (type === 'error');\n\n var events = this._events;\n if (events !== undefined)\n doError = (doError && events.error === undefined);\n else if (!doError)\n return false;\n\n // If there is no 'error' event listener then throw.\n if (doError) {\n var er;\n if (args.length > 0)\n er = args[0];\n if (er instanceof Error) {\n // Note: The comments on the `throw` lines are intentional, they show\n // up in Node's output if this results in an unhandled exception.\n throw er; // Unhandled 'error' event\n }\n // At least give some kind of context to the user\n var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : ''));\n err.context = er;\n throw err; // Unhandled 'error' event\n }\n\n var handler = events[type];\n\n if (handler === undefined)\n return false;\n\n if (typeof handler === 'function') {\n ReflectApply(handler, this, args);\n } else {\n var len = handler.length;\n var listeners = arrayClone(handler, len);\n for (var i = 0; i < len; ++i)\n ReflectApply(listeners[i], this, args);\n }\n\n return true;\n};\n\nfunction _addListener(target, type, listener, prepend) {\n var m;\n var events;\n var existing;\n\n checkListener(listener);\n\n events = target._events;\n if (events === undefined) {\n events = target._events = Object.create(null);\n target._eventsCount = 0;\n } else {\n // To avoid recursion in the case that type === \"newListener\"! Before\n // adding it to the listeners, first emit \"newListener\".\n if (events.newListener !== undefined) {\n target.emit('newListener', type,\n listener.listener ? listener.listener : listener);\n\n // Re-assign `events` because a newListener handler could have caused the\n // this._events to be assigned to a new object\n events = target._events;\n }\n existing = events[type];\n }\n\n if (existing === undefined) {\n // Optimize the case of one listener. Don't need the extra array object.\n existing = events[type] = listener;\n ++target._eventsCount;\n } else {\n if (typeof existing === 'function') {\n // Adding the second element, need to change to array.\n existing = events[type] =\n prepend ? [listener, existing] : [existing, listener];\n // If we've already got an array, just append.\n } else if (prepend) {\n existing.unshift(listener);\n } else {\n existing.push(listener);\n }\n\n // Check for listener leak\n m = _getMaxListeners(target);\n if (m > 0 && existing.length > m && !existing.warned) {\n existing.warned = true;\n // No error code for this since it is a Warning\n // eslint-disable-next-line no-restricted-syntax\n var w = new Error('Possible EventEmitter memory leak detected. ' +\n existing.length + ' ' + String(type) + ' listeners ' +\n 'added. Use emitter.setMaxListeners() to ' +\n 'increase limit');\n w.name = 'MaxListenersExceededWarning';\n w.emitter = target;\n w.type = type;\n w.count = existing.length;\n ProcessEmitWarning(w);\n }\n }\n\n return target;\n}\n\nEventEmitter.prototype.addListener = function addListener(type, listener) {\n return _addListener(this, type, listener, false);\n};\n\nEventEmitter.prototype.on = EventEmitter.prototype.addListener;\n\nEventEmitter.prototype.prependListener =\n function prependListener(type, listener) {\n return _addListener(this, type, listener, true);\n };\n\nfunction onceWrapper() {\n if (!this.fired) {\n this.target.removeListener(this.type, this.wrapFn);\n this.fired = true;\n if (arguments.length === 0)\n return this.listener.call(this.target);\n return this.listener.apply(this.target, arguments);\n }\n}\n\nfunction _onceWrap(target, type, listener) {\n var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };\n var wrapped = onceWrapper.bind(state);\n wrapped.listener = listener;\n state.wrapFn = wrapped;\n return wrapped;\n}\n\nEventEmitter.prototype.once = function once(type, listener) {\n checkListener(listener);\n this.on(type, _onceWrap(this, type, listener));\n return this;\n};\n\nEventEmitter.prototype.prependOnceListener =\n function prependOnceListener(type, listener) {\n checkListener(listener);\n this.prependListener(type, _onceWrap(this, type, listener));\n return this;\n };\n\n// Emits a 'removeListener' event if and only if the listener was removed.\nEventEmitter.prototype.removeListener =\n function removeListener(type, listener) {\n var list, events, position, i, originalListener;\n\n checkListener(listener);\n\n events = this._events;\n if (events === undefined)\n return this;\n\n list = events[type];\n if (list === undefined)\n return this;\n\n if (list === listener || list.listener === listener) {\n if (--this._eventsCount === 0)\n this._events = Object.create(null);\n else {\n delete events[type];\n if (events.removeListener)\n this.emit('removeListener', type, list.listener || listener);\n }\n } else if (typeof list !== 'function') {\n position = -1;\n\n for (i = list.length - 1; i >= 0; i--) {\n if (list[i] === listener || list[i].listener === listener) {\n originalListener = list[i].listener;\n position = i;\n break;\n }\n }\n\n if (position < 0)\n return this;\n\n if (position === 0)\n list.shift();\n else {\n spliceOne(list, position);\n }\n\n if (list.length === 1)\n events[type] = list[0];\n\n if (events.removeListener !== undefined)\n this.emit('removeListener', type, originalListener || listener);\n }\n\n return this;\n };\n\nEventEmitter.prototype.off = EventEmitter.prototype.removeListener;\n\nEventEmitter.prototype.removeAllListeners =\n function removeAllListeners(type) {\n var listeners, events, i;\n\n events = this._events;\n if (events === undefined)\n return this;\n\n // not listening for removeListener, no need to emit\n if (events.removeListener === undefined) {\n if (arguments.length === 0) {\n this._events = Object.create(null);\n this._eventsCount = 0;\n } else if (events[type] !== undefined) {\n if (--this._eventsCount === 0)\n this._events = Object.create(null);\n else\n delete events[type];\n }\n return this;\n }\n\n // emit removeListener for all listeners on all events\n if (arguments.length === 0) {\n var keys = Object.keys(events);\n var key;\n for (i = 0; i < keys.length; ++i) {\n key = keys[i];\n if (key === 'removeListener') continue;\n this.removeAllListeners(key);\n }\n this.removeAllListeners('removeListener');\n this._events = Object.create(null);\n this._eventsCount = 0;\n return this;\n }\n\n listeners = events[type];\n\n if (typeof listeners === 'function') {\n this.removeListener(type, listeners);\n } else if (listeners !== undefined) {\n // LIFO order\n for (i = listeners.length - 1; i >= 0; i--) {\n this.removeListener(type, listeners[i]);\n }\n }\n\n return this;\n };\n\nfunction _listeners(target, type, unwrap) {\n var events = target._events;\n\n if (events === undefined)\n return [];\n\n var evlistener = events[type];\n if (evlistener === undefined)\n return [];\n\n if (typeof evlistener === 'function')\n return unwrap ? [evlistener.listener || evlistener] : [evlistener];\n\n return unwrap ?\n unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);\n}\n\nEventEmitter.prototype.listeners = function listeners(type) {\n return _listeners(this, type, true);\n};\n\nEventEmitter.prototype.rawListeners = function rawListeners(type) {\n return _listeners(this, type, false);\n};\n\nEventEmitter.listenerCount = function(emitter, type) {\n if (typeof emitter.listenerCount === 'function') {\n return emitter.listenerCount(type);\n } else {\n return listenerCount.call(emitter, type);\n }\n};\n\nEventEmitter.prototype.listenerCount = listenerCount;\nfunction listenerCount(type) {\n var events = this._events;\n\n if (events !== undefined) {\n var evlistener = events[type];\n\n if (typeof evlistener === 'function') {\n return 1;\n } else if (evlistener !== undefined) {\n return evlistener.length;\n }\n }\n\n return 0;\n}\n\nEventEmitter.prototype.eventNames = function eventNames() {\n return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];\n};\n\nfunction arrayClone(arr, n) {\n var copy = new Array(n);\n for (var i = 0; i < n; ++i)\n copy[i] = arr[i];\n return copy;\n}\n\nfunction spliceOne(list, index) {\n for (; index + 1 < list.length; index++)\n list[index] = list[index + 1];\n list.pop();\n}\n\nfunction unwrapListeners(arr) {\n var ret = new Array(arr.length);\n for (var i = 0; i < ret.length; ++i) {\n ret[i] = arr[i].listener || arr[i];\n }\n return ret;\n}\n\nfunction once(emitter, name) {\n return new Promise(function (resolve, reject) {\n function errorListener(err) {\n emitter.removeListener(name, resolver);\n reject(err);\n }\n\n function resolver() {\n if (typeof emitter.removeListener === 'function') {\n emitter.removeListener('error', errorListener);\n }\n resolve([].slice.call(arguments));\n };\n\n eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });\n if (name !== 'error') {\n addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true });\n }\n });\n}\n\nfunction addErrorHandlerIfEventEmitter(emitter, handler, flags) {\n if (typeof emitter.on === 'function') {\n eventTargetAgnosticAddListener(emitter, 'error', handler, flags);\n }\n}\n\nfunction eventTargetAgnosticAddListener(emitter, name, listener, flags) {\n if (typeof emitter.on === 'function') {\n if (flags.once) {\n emitter.once(name, listener);\n } else {\n emitter.on(name, listener);\n }\n } else if (typeof emitter.addEventListener === 'function') {\n // EventTarget does not have `error` event semantics like Node\n // EventEmitters, we do not listen for `error` events here.\n emitter.addEventListener(name, function wrapListener(arg) {\n // IE does not have builtin `{ once: true }` support so we\n // have to do it manually.\n if (flags.once) {\n emitter.removeEventListener(name, wrapListener);\n }\n listener(arg);\n });\n } else {\n throw new TypeError('The \"emitter\" argument must be of type EventEmitter. Received type ' + typeof emitter);\n }\n}\n","if (typeof Object.create === 'function') {\n // implementation from standard node.js 'util' module\n module.exports = function inherits(ctor, superCtor) {\n if (superCtor) {\n ctor.super_ = superCtor\n ctor.prototype = Object.create(superCtor.prototype, {\n constructor: {\n value: ctor,\n enumerable: false,\n writable: true,\n configurable: true\n }\n })\n }\n };\n} else {\n // old school shim for old browsers\n module.exports = function inherits(ctor, superCtor) {\n if (superCtor) {\n ctor.super_ = superCtor\n var TempCtor = function () {}\n TempCtor.prototype = superCtor.prototype\n ctor.prototype = new TempCtor()\n ctor.prototype.constructor = ctor\n }\n }\n}\n","// shim for using process in browser\nvar process = module.exports = {};\n\n// cached from whatever global is present so that test runners that stub it\n// don't break things. But we need to wrap it in a try catch in case it is\n// wrapped in strict mode code which doesn't define any globals. It's inside a\n// function because try/catches deoptimize in certain engines.\n\nvar cachedSetTimeout;\nvar cachedClearTimeout;\n\nfunction defaultSetTimout() {\n throw new Error('setTimeout has not been defined');\n}\nfunction defaultClearTimeout () {\n throw new Error('clearTimeout has not been defined');\n}\n(function () {\n try {\n if (typeof setTimeout === 'function') {\n cachedSetTimeout = setTimeout;\n } else {\n cachedSetTimeout = defaultSetTimout;\n }\n } catch (e) {\n cachedSetTimeout = defaultSetTimout;\n }\n try {\n if (typeof clearTimeout === 'function') {\n cachedClearTimeout = clearTimeout;\n } else {\n cachedClearTimeout = defaultClearTimeout;\n }\n } catch (e) {\n cachedClearTimeout = defaultClearTimeout;\n }\n} ())\nfunction runTimeout(fun) {\n if (cachedSetTimeout === setTimeout) {\n //normal enviroments in sane situations\n return setTimeout(fun, 0);\n }\n // if setTimeout wasn't available but was latter defined\n if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {\n cachedSetTimeout = setTimeout;\n return setTimeout(fun, 0);\n }\n try {\n // when when somebody has screwed with setTimeout but no I.E. maddness\n return cachedSetTimeout(fun, 0);\n } catch(e){\n try {\n // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally\n return cachedSetTimeout.call(null, fun, 0);\n } catch(e){\n // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error\n return cachedSetTimeout.call(this, fun, 0);\n }\n }\n\n\n}\nfunction runClearTimeout(marker) {\n if (cachedClearTimeout === clearTimeout) {\n //normal enviroments in sane situations\n return clearTimeout(marker);\n }\n // if clearTimeout wasn't available but was latter defined\n if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {\n cachedClearTimeout = clearTimeout;\n return clearTimeout(marker);\n }\n try {\n // when when somebody has screwed with setTimeout but no I.E. maddness\n return cachedClearTimeout(marker);\n } catch (e){\n try {\n // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally\n return cachedClearTimeout.call(null, marker);\n } catch (e){\n // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.\n // Some versions of I.E. have different rules for clearTimeout vs setTimeout\n return cachedClearTimeout.call(this, marker);\n }\n }\n\n\n\n}\nvar queue = [];\nvar draining = false;\nvar currentQueue;\nvar queueIndex = -1;\n\nfunction cleanUpNextTick() {\n if (!draining || !currentQueue) {\n return;\n }\n draining = false;\n if (currentQueue.length) {\n queue = currentQueue.concat(queue);\n } else {\n queueIndex = -1;\n }\n if (queue.length) {\n drainQueue();\n }\n}\n\nfunction drainQueue() {\n if (draining) {\n return;\n }\n var timeout = runTimeout(cleanUpNextTick);\n draining = true;\n\n var len = queue.length;\n while(len) {\n currentQueue = queue;\n queue = [];\n while (++queueIndex < len) {\n if (currentQueue) {\n currentQueue[queueIndex].run();\n }\n }\n queueIndex = -1;\n len = queue.length;\n }\n currentQueue = null;\n draining = false;\n runClearTimeout(timeout);\n}\n\nprocess.nextTick = function (fun) {\n var args = new Array(arguments.length - 1);\n if (arguments.length > 1) {\n for (var i = 1; i < arguments.length; i++) {\n args[i - 1] = arguments[i];\n }\n }\n queue.push(new Item(fun, args));\n if (queue.length === 1 && !draining) {\n runTimeout(drainQueue);\n }\n};\n\n// v8 likes predictible objects\nfunction Item(fun, array) {\n this.fun = fun;\n this.array = array;\n}\nItem.prototype.run = function () {\n this.fun.apply(null, this.array);\n};\nprocess.title = 'browser';\nprocess.browser = true;\nprocess.env = {};\nprocess.argv = [];\nprocess.version = ''; // empty string to avoid regexp issues\nprocess.versions = {};\n\nfunction noop() {}\n\nprocess.on = noop;\nprocess.addListener = noop;\nprocess.once = noop;\nprocess.off = noop;\nprocess.removeListener = noop;\nprocess.removeAllListeners = noop;\nprocess.emit = noop;\nprocess.prependListener = noop;\nprocess.prependOnceListener = noop;\n\nprocess.listeners = function (name) { return [] }\n\nprocess.binding = function (name) {\n throw new Error('process.binding is not supported');\n};\n\nprocess.cwd = function () { return '/' };\nprocess.chdir = function (dir) {\n throw new Error('process.chdir is not supported');\n};\nprocess.umask = function() { return 0; };\n","'use strict'\n\n// limit of Crypto.getRandomValues()\n// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues\nvar MAX_BYTES = 65536\n\n// Node supports requesting up to this number of bytes\n// https://github.com/nodejs/node/blob/master/lib/internal/crypto/random.js#L48\nvar MAX_UINT32 = 4294967295\n\nfunction oldBrowser () {\n throw new Error('Secure random number generation is not supported by this browser.\\nUse Chrome, Firefox or Internet Explorer 11')\n}\n\nvar Buffer = require('safe-buffer').Buffer\nvar crypto = global.crypto || global.msCrypto\n\nif (crypto && crypto.getRandomValues) {\n module.exports = randomBytes\n} else {\n module.exports = oldBrowser\n}\n\nfunction randomBytes (size, cb) {\n // phantomjs needs to throw\n if (size > MAX_UINT32) throw new RangeError('requested too many random bytes')\n\n var bytes = Buffer.allocUnsafe(size)\n\n if (size > 0) { // getRandomValues fails on IE if size == 0\n if (size > MAX_BYTES) { // this is the max bytes crypto.getRandomValues\n // can do at once see https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues\n for (var generated = 0; generated < size; generated += MAX_BYTES) {\n // buffer.slice automatically checks if the end is past the end of\n // the buffer so we don't have to here\n crypto.getRandomValues(bytes.slice(generated, generated + MAX_BYTES))\n }\n } else {\n crypto.getRandomValues(bytes)\n }\n }\n\n if (typeof cb === 'function') {\n return process.nextTick(function () {\n cb(null, bytes)\n })\n }\n\n return bytes\n}\n","'use strict';\n\nfunction _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }\n\nvar codes = {};\n\nfunction createErrorType(code, message, Base) {\n if (!Base) {\n Base = Error;\n }\n\n function getMessage(arg1, arg2, arg3) {\n if (typeof message === 'string') {\n return message;\n } else {\n return message(arg1, arg2, arg3);\n }\n }\n\n var NodeError =\n /*#__PURE__*/\n function (_Base) {\n _inheritsLoose(NodeError, _Base);\n\n function NodeError(arg1, arg2, arg3) {\n return _Base.call(this, getMessage(arg1, arg2, arg3)) || this;\n }\n\n return NodeError;\n }(Base);\n\n NodeError.prototype.name = Base.name;\n NodeError.prototype.code = code;\n codes[code] = NodeError;\n} // https://github.com/nodejs/node/blob/v10.8.0/lib/internal/errors.js\n\n\nfunction oneOf(expected, thing) {\n if (Array.isArray(expected)) {\n var len = expected.length;\n expected = expected.map(function (i) {\n return String(i);\n });\n\n if (len > 2) {\n return \"one of \".concat(thing, \" \").concat(expected.slice(0, len - 1).join(', '), \", or \") + expected[len - 1];\n } else if (len === 2) {\n return \"one of \".concat(thing, \" \").concat(expected[0], \" or \").concat(expected[1]);\n } else {\n return \"of \".concat(thing, \" \").concat(expected[0]);\n }\n } else {\n return \"of \".concat(thing, \" \").concat(String(expected));\n }\n} // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith\n\n\nfunction startsWith(str, search, pos) {\n return str.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;\n} // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith\n\n\nfunction endsWith(str, search, this_len) {\n if (this_len === undefined || this_len > str.length) {\n this_len = str.length;\n }\n\n return str.substring(this_len - search.length, this_len) === search;\n} // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes\n\n\nfunction includes(str, search, start) {\n if (typeof start !== 'number') {\n start = 0;\n }\n\n if (start + search.length > str.length) {\n return false;\n } else {\n return str.indexOf(search, start) !== -1;\n }\n}\n\ncreateErrorType('ERR_INVALID_OPT_VALUE', function (name, value) {\n return 'The value \"' + value + '\" is invalid for option \"' + name + '\"';\n}, TypeError);\ncreateErrorType('ERR_INVALID_ARG_TYPE', function (name, expected, actual) {\n // determiner: 'must be' or 'must not be'\n var determiner;\n\n if (typeof expected === 'string' && startsWith(expected, 'not ')) {\n determiner = 'must not be';\n expected = expected.replace(/^not /, '');\n } else {\n determiner = 'must be';\n }\n\n var msg;\n\n if (endsWith(name, ' argument')) {\n // For cases like 'first argument'\n msg = \"The \".concat(name, \" \").concat(determiner, \" \").concat(oneOf(expected, 'type'));\n } else {\n var type = includes(name, '.') ? 'property' : 'argument';\n msg = \"The \\\"\".concat(name, \"\\\" \").concat(type, \" \").concat(determiner, \" \").concat(oneOf(expected, 'type'));\n }\n\n msg += \". Received type \".concat(typeof actual);\n return msg;\n}, TypeError);\ncreateErrorType('ERR_STREAM_PUSH_AFTER_EOF', 'stream.push() after EOF');\ncreateErrorType('ERR_METHOD_NOT_IMPLEMENTED', function (name) {\n return 'The ' + name + ' method is not implemented';\n});\ncreateErrorType('ERR_STREAM_PREMATURE_CLOSE', 'Premature close');\ncreateErrorType('ERR_STREAM_DESTROYED', function (name) {\n return 'Cannot call ' + name + ' after a stream was destroyed';\n});\ncreateErrorType('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times');\ncreateErrorType('ERR_STREAM_CANNOT_PIPE', 'Cannot pipe, not readable');\ncreateErrorType('ERR_STREAM_WRITE_AFTER_END', 'write after end');\ncreateErrorType('ERR_STREAM_NULL_VALUES', 'May not write null values to stream', TypeError);\ncreateErrorType('ERR_UNKNOWN_ENCODING', function (arg) {\n return 'Unknown encoding: ' + arg;\n}, TypeError);\ncreateErrorType('ERR_STREAM_UNSHIFT_AFTER_END_EVENT', 'stream.unshift() after end event');\nmodule.exports.codes = codes;\n","// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n// a duplex stream is just a stream that is both readable and writable.\n// Since JS doesn't have multiple prototypal inheritance, this class\n// prototypally inherits from Readable, and then parasitically from\n// Writable.\n\n'use strict';\n\n/**/\nvar objectKeys = Object.keys || function (obj) {\n var keys = [];\n for (var key in obj) keys.push(key);\n return keys;\n};\n/**/\n\nmodule.exports = Duplex;\nvar Readable = require('./_stream_readable');\nvar Writable = require('./_stream_writable');\nrequire('inherits')(Duplex, Readable);\n{\n // Allow the keys array to be GC'ed.\n var keys = objectKeys(Writable.prototype);\n for (var v = 0; v < keys.length; v++) {\n var method = keys[v];\n if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method];\n }\n}\nfunction Duplex(options) {\n if (!(this instanceof Duplex)) return new Duplex(options);\n Readable.call(this, options);\n Writable.call(this, options);\n this.allowHalfOpen = true;\n if (options) {\n if (options.readable === false) this.readable = false;\n if (options.writable === false) this.writable = false;\n if (options.allowHalfOpen === false) {\n this.allowHalfOpen = false;\n this.once('end', onend);\n }\n }\n}\nObject.defineProperty(Duplex.prototype, 'writableHighWaterMark', {\n // making it explicit this property is not enumerable\n // because otherwise some prototype manipulation in\n // userland will fail\n enumerable: false,\n get: function get() {\n return this._writableState.highWaterMark;\n }\n});\nObject.defineProperty(Duplex.prototype, 'writableBuffer', {\n // making it explicit this property is not enumerable\n // because otherwise some prototype manipulation in\n // userland will fail\n enumerable: false,\n get: function get() {\n return this._writableState && this._writableState.getBuffer();\n }\n});\nObject.defineProperty(Duplex.prototype, 'writableLength', {\n // making it explicit this property is not enumerable\n // because otherwise some prototype manipulation in\n // userland will fail\n enumerable: false,\n get: function get() {\n return this._writableState.length;\n }\n});\n\n// the no-half-open enforcer\nfunction onend() {\n // If the writable side ended, then we're ok.\n if (this._writableState.ended) return;\n\n // no more data can be written.\n // But allow more writes to happen in this tick.\n process.nextTick(onEndNT, this);\n}\nfunction onEndNT(self) {\n self.end();\n}\nObject.defineProperty(Duplex.prototype, 'destroyed', {\n // making it explicit this property is not enumerable\n // because otherwise some prototype manipulation in\n // userland will fail\n enumerable: false,\n get: function get() {\n if (this._readableState === undefined || this._writableState === undefined) {\n return false;\n }\n return this._readableState.destroyed && this._writableState.destroyed;\n },\n set: function set(value) {\n // we ignore the value if the stream\n // has not been initialized yet\n if (this._readableState === undefined || this._writableState === undefined) {\n return;\n }\n\n // backward compatibility, the user is explicitly\n // managing destroyed\n this._readableState.destroyed = value;\n this._writableState.destroyed = value;\n }\n});","// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n// a passthrough stream.\n// basically just the most minimal sort of Transform stream.\n// Every written chunk gets output as-is.\n\n'use strict';\n\nmodule.exports = PassThrough;\nvar Transform = require('./_stream_transform');\nrequire('inherits')(PassThrough, Transform);\nfunction PassThrough(options) {\n if (!(this instanceof PassThrough)) return new PassThrough(options);\n Transform.call(this, options);\n}\nPassThrough.prototype._transform = function (chunk, encoding, cb) {\n cb(null, chunk);\n};","// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n'use strict';\n\nmodule.exports = Readable;\n\n/**/\nvar Duplex;\n/**/\n\nReadable.ReadableState = ReadableState;\n\n/**/\nvar EE = require('events').EventEmitter;\nvar EElistenerCount = function EElistenerCount(emitter, type) {\n return emitter.listeners(type).length;\n};\n/**/\n\n/**/\nvar Stream = require('./internal/streams/stream');\n/**/\n\nvar Buffer = require('buffer').Buffer;\nvar OurUint8Array = (typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : typeof self !== 'undefined' ? self : {}).Uint8Array || function () {};\nfunction _uint8ArrayToBuffer(chunk) {\n return Buffer.from(chunk);\n}\nfunction _isUint8Array(obj) {\n return Buffer.isBuffer(obj) || obj instanceof OurUint8Array;\n}\n\n/**/\nvar debugUtil = require('util');\nvar debug;\nif (debugUtil && debugUtil.debuglog) {\n debug = debugUtil.debuglog('stream');\n} else {\n debug = function debug() {};\n}\n/**/\n\nvar BufferList = require('./internal/streams/buffer_list');\nvar destroyImpl = require('./internal/streams/destroy');\nvar _require = require('./internal/streams/state'),\n getHighWaterMark = _require.getHighWaterMark;\nvar _require$codes = require('../errors').codes,\n ERR_INVALID_ARG_TYPE = _require$codes.ERR_INVALID_ARG_TYPE,\n ERR_STREAM_PUSH_AFTER_EOF = _require$codes.ERR_STREAM_PUSH_AFTER_EOF,\n ERR_METHOD_NOT_IMPLEMENTED = _require$codes.ERR_METHOD_NOT_IMPLEMENTED,\n ERR_STREAM_UNSHIFT_AFTER_END_EVENT = _require$codes.ERR_STREAM_UNSHIFT_AFTER_END_EVENT;\n\n// Lazy loaded to improve the startup performance.\nvar StringDecoder;\nvar createReadableStreamAsyncIterator;\nvar from;\nrequire('inherits')(Readable, Stream);\nvar errorOrDestroy = destroyImpl.errorOrDestroy;\nvar kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume'];\nfunction prependListener(emitter, event, fn) {\n // Sadly this is not cacheable as some libraries bundle their own\n // event emitter implementation with them.\n if (typeof emitter.prependListener === 'function') return emitter.prependListener(event, fn);\n\n // This is a hack to make sure that our error handler is attached before any\n // userland ones. NEVER DO THIS. This is here only because this code needs\n // to continue to work with older versions of Node.js that do not include\n // the prependListener() method. The goal is to eventually remove this hack.\n if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (Array.isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]];\n}\nfunction ReadableState(options, stream, isDuplex) {\n Duplex = Duplex || require('./_stream_duplex');\n options = options || {};\n\n // Duplex streams are both readable and writable, but share\n // the same options object.\n // However, some cases require setting options to different\n // values for the readable and the writable sides of the duplex stream.\n // These options can be provided separately as readableXXX and writableXXX.\n if (typeof isDuplex !== 'boolean') isDuplex = stream instanceof Duplex;\n\n // object stream flag. Used to make read(n) ignore n and to\n // make all the buffer merging and length checks go away\n this.objectMode = !!options.objectMode;\n if (isDuplex) this.objectMode = this.objectMode || !!options.readableObjectMode;\n\n // the point at which it stops calling _read() to fill the buffer\n // Note: 0 is a valid value, means \"don't call _read preemptively ever\"\n this.highWaterMark = getHighWaterMark(this, options, 'readableHighWaterMark', isDuplex);\n\n // A linked list is used to store data chunks instead of an array because the\n // linked list can remove elements from the beginning faster than\n // array.shift()\n this.buffer = new BufferList();\n this.length = 0;\n this.pipes = null;\n this.pipesCount = 0;\n this.flowing = null;\n this.ended = false;\n this.endEmitted = false;\n this.reading = false;\n\n // a flag to be able to tell if the event 'readable'/'data' is emitted\n // immediately, or on a later tick. We set this to true at first, because\n // any actions that shouldn't happen until \"later\" should generally also\n // not happen before the first read call.\n this.sync = true;\n\n // whenever we return null, then we set a flag to say\n // that we're awaiting a 'readable' event emission.\n this.needReadable = false;\n this.emittedReadable = false;\n this.readableListening = false;\n this.resumeScheduled = false;\n this.paused = true;\n\n // Should close be emitted on destroy. Defaults to true.\n this.emitClose = options.emitClose !== false;\n\n // Should .destroy() be called after 'end' (and potentially 'finish')\n this.autoDestroy = !!options.autoDestroy;\n\n // has it been destroyed\n this.destroyed = false;\n\n // Crypto is kind of old and crusty. Historically, its default string\n // encoding is 'binary' so we have to make this configurable.\n // Everything else in the universe uses 'utf8', though.\n this.defaultEncoding = options.defaultEncoding || 'utf8';\n\n // the number of writers that are awaiting a drain event in .pipe()s\n this.awaitDrain = 0;\n\n // if true, a maybeReadMore has been scheduled\n this.readingMore = false;\n this.decoder = null;\n this.encoding = null;\n if (options.encoding) {\n if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder;\n this.decoder = new StringDecoder(options.encoding);\n this.encoding = options.encoding;\n }\n}\nfunction Readable(options) {\n Duplex = Duplex || require('./_stream_duplex');\n if (!(this instanceof Readable)) return new Readable(options);\n\n // Checking for a Stream.Duplex instance is faster here instead of inside\n // the ReadableState constructor, at least with V8 6.5\n var isDuplex = this instanceof Duplex;\n this._readableState = new ReadableState(options, this, isDuplex);\n\n // legacy\n this.readable = true;\n if (options) {\n if (typeof options.read === 'function') this._read = options.read;\n if (typeof options.destroy === 'function') this._destroy = options.destroy;\n }\n Stream.call(this);\n}\nObject.defineProperty(Readable.prototype, 'destroyed', {\n // making it explicit this property is not enumerable\n // because otherwise some prototype manipulation in\n // userland will fail\n enumerable: false,\n get: function get() {\n if (this._readableState === undefined) {\n return false;\n }\n return this._readableState.destroyed;\n },\n set: function set(value) {\n // we ignore the value if the stream\n // has not been initialized yet\n if (!this._readableState) {\n return;\n }\n\n // backward compatibility, the user is explicitly\n // managing destroyed\n this._readableState.destroyed = value;\n }\n});\nReadable.prototype.destroy = destroyImpl.destroy;\nReadable.prototype._undestroy = destroyImpl.undestroy;\nReadable.prototype._destroy = function (err, cb) {\n cb(err);\n};\n\n// Manually shove something into the read() buffer.\n// This returns true if the highWaterMark has not been hit yet,\n// similar to how Writable.write() returns true if you should\n// write() some more.\nReadable.prototype.push = function (chunk, encoding) {\n var state = this._readableState;\n var skipChunkCheck;\n if (!state.objectMode) {\n if (typeof chunk === 'string') {\n encoding = encoding || state.defaultEncoding;\n if (encoding !== state.encoding) {\n chunk = Buffer.from(chunk, encoding);\n encoding = '';\n }\n skipChunkCheck = true;\n }\n } else {\n skipChunkCheck = true;\n }\n return readableAddChunk(this, chunk, encoding, false, skipChunkCheck);\n};\n\n// Unshift should *always* be something directly out of read()\nReadable.prototype.unshift = function (chunk) {\n return readableAddChunk(this, chunk, null, true, false);\n};\nfunction readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) {\n debug('readableAddChunk', chunk);\n var state = stream._readableState;\n if (chunk === null) {\n state.reading = false;\n onEofChunk(stream, state);\n } else {\n var er;\n if (!skipChunkCheck) er = chunkInvalid(state, chunk);\n if (er) {\n errorOrDestroy(stream, er);\n } else if (state.objectMode || chunk && chunk.length > 0) {\n if (typeof chunk !== 'string' && !state.objectMode && Object.getPrototypeOf(chunk) !== Buffer.prototype) {\n chunk = _uint8ArrayToBuffer(chunk);\n }\n if (addToFront) {\n if (state.endEmitted) errorOrDestroy(stream, new ERR_STREAM_UNSHIFT_AFTER_END_EVENT());else addChunk(stream, state, chunk, true);\n } else if (state.ended) {\n errorOrDestroy(stream, new ERR_STREAM_PUSH_AFTER_EOF());\n } else if (state.destroyed) {\n return false;\n } else {\n state.reading = false;\n if (state.decoder && !encoding) {\n chunk = state.decoder.write(chunk);\n if (state.objectMode || chunk.length !== 0) addChunk(stream, state, chunk, false);else maybeReadMore(stream, state);\n } else {\n addChunk(stream, state, chunk, false);\n }\n }\n } else if (!addToFront) {\n state.reading = false;\n maybeReadMore(stream, state);\n }\n }\n\n // We can push more data if we are below the highWaterMark.\n // Also, if we have no data yet, we can stand some more bytes.\n // This is to work around cases where hwm=0, such as the repl.\n return !state.ended && (state.length < state.highWaterMark || state.length === 0);\n}\nfunction addChunk(stream, state, chunk, addToFront) {\n if (state.flowing && state.length === 0 && !state.sync) {\n state.awaitDrain = 0;\n stream.emit('data', chunk);\n } else {\n // update the buffer info.\n state.length += state.objectMode ? 1 : chunk.length;\n if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk);\n if (state.needReadable) emitReadable(stream);\n }\n maybeReadMore(stream, state);\n}\nfunction chunkInvalid(state, chunk) {\n var er;\n if (!_isUint8Array(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) {\n er = new ERR_INVALID_ARG_TYPE('chunk', ['string', 'Buffer', 'Uint8Array'], chunk);\n }\n return er;\n}\nReadable.prototype.isPaused = function () {\n return this._readableState.flowing === false;\n};\n\n// backwards compatibility.\nReadable.prototype.setEncoding = function (enc) {\n if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder;\n var decoder = new StringDecoder(enc);\n this._readableState.decoder = decoder;\n // If setEncoding(null), decoder.encoding equals utf8\n this._readableState.encoding = this._readableState.decoder.encoding;\n\n // Iterate over current buffer to convert already stored Buffers:\n var p = this._readableState.buffer.head;\n var content = '';\n while (p !== null) {\n content += decoder.write(p.data);\n p = p.next;\n }\n this._readableState.buffer.clear();\n if (content !== '') this._readableState.buffer.push(content);\n this._readableState.length = content.length;\n return this;\n};\n\n// Don't raise the hwm > 1GB\nvar MAX_HWM = 0x40000000;\nfunction computeNewHighWaterMark(n) {\n if (n >= MAX_HWM) {\n // TODO(ronag): Throw ERR_VALUE_OUT_OF_RANGE.\n n = MAX_HWM;\n } else {\n // Get the next highest power of 2 to prevent increasing hwm excessively in\n // tiny amounts\n n--;\n n |= n >>> 1;\n n |= n >>> 2;\n n |= n >>> 4;\n n |= n >>> 8;\n n |= n >>> 16;\n n++;\n }\n return n;\n}\n\n// This function is designed to be inlinable, so please take care when making\n// changes to the function body.\nfunction howMuchToRead(n, state) {\n if (n <= 0 || state.length === 0 && state.ended) return 0;\n if (state.objectMode) return 1;\n if (n !== n) {\n // Only flow one buffer at a time\n if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length;\n }\n // If we're asking for more than the current hwm, then raise the hwm.\n if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n);\n if (n <= state.length) return n;\n // Don't have enough\n if (!state.ended) {\n state.needReadable = true;\n return 0;\n }\n return state.length;\n}\n\n// you can override either this method, or the async _read(n) below.\nReadable.prototype.read = function (n) {\n debug('read', n);\n n = parseInt(n, 10);\n var state = this._readableState;\n var nOrig = n;\n if (n !== 0) state.emittedReadable = false;\n\n // if we're doing read(0) to trigger a readable event, but we\n // already have a bunch of data in the buffer, then just trigger\n // the 'readable' event and move on.\n if (n === 0 && state.needReadable && ((state.highWaterMark !== 0 ? state.length >= state.highWaterMark : state.length > 0) || state.ended)) {\n debug('read: emitReadable', state.length, state.ended);\n if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this);\n return null;\n }\n n = howMuchToRead(n, state);\n\n // if we've ended, and we're now clear, then finish it up.\n if (n === 0 && state.ended) {\n if (state.length === 0) endReadable(this);\n return null;\n }\n\n // All the actual chunk generation logic needs to be\n // *below* the call to _read. The reason is that in certain\n // synthetic stream cases, such as passthrough streams, _read\n // may be a completely synchronous operation which may change\n // the state of the read buffer, providing enough data when\n // before there was *not* enough.\n //\n // So, the steps are:\n // 1. Figure out what the state of things will be after we do\n // a read from the buffer.\n //\n // 2. If that resulting state will trigger a _read, then call _read.\n // Note that this may be asynchronous, or synchronous. Yes, it is\n // deeply ugly to write APIs this way, but that still doesn't mean\n // that the Readable class should behave improperly, as streams are\n // designed to be sync/async agnostic.\n // Take note if the _read call is sync or async (ie, if the read call\n // has returned yet), so that we know whether or not it's safe to emit\n // 'readable' etc.\n //\n // 3. Actually pull the requested chunks out of the buffer and return.\n\n // if we need a readable event, then we need to do some reading.\n var doRead = state.needReadable;\n debug('need readable', doRead);\n\n // if we currently have less than the highWaterMark, then also read some\n if (state.length === 0 || state.length - n < state.highWaterMark) {\n doRead = true;\n debug('length less than watermark', doRead);\n }\n\n // however, if we've ended, then there's no point, and if we're already\n // reading, then it's unnecessary.\n if (state.ended || state.reading) {\n doRead = false;\n debug('reading or ended', doRead);\n } else if (doRead) {\n debug('do read');\n state.reading = true;\n state.sync = true;\n // if the length is currently zero, then we *need* a readable event.\n if (state.length === 0) state.needReadable = true;\n // call internal read method\n this._read(state.highWaterMark);\n state.sync = false;\n // If _read pushed data synchronously, then `reading` will be false,\n // and we need to re-evaluate how much data we can return to the user.\n if (!state.reading) n = howMuchToRead(nOrig, state);\n }\n var ret;\n if (n > 0) ret = fromList(n, state);else ret = null;\n if (ret === null) {\n state.needReadable = state.length <= state.highWaterMark;\n n = 0;\n } else {\n state.length -= n;\n state.awaitDrain = 0;\n }\n if (state.length === 0) {\n // If we have nothing in the buffer, then we want to know\n // as soon as we *do* get something into the buffer.\n if (!state.ended) state.needReadable = true;\n\n // If we tried to read() past the EOF, then emit end on the next tick.\n if (nOrig !== n && state.ended) endReadable(this);\n }\n if (ret !== null) this.emit('data', ret);\n return ret;\n};\nfunction onEofChunk(stream, state) {\n debug('onEofChunk');\n if (state.ended) return;\n if (state.decoder) {\n var chunk = state.decoder.end();\n if (chunk && chunk.length) {\n state.buffer.push(chunk);\n state.length += state.objectMode ? 1 : chunk.length;\n }\n }\n state.ended = true;\n if (state.sync) {\n // if we are sync, wait until next tick to emit the data.\n // Otherwise we risk emitting data in the flow()\n // the readable code triggers during a read() call\n emitReadable(stream);\n } else {\n // emit 'readable' now to make sure it gets picked up.\n state.needReadable = false;\n if (!state.emittedReadable) {\n state.emittedReadable = true;\n emitReadable_(stream);\n }\n }\n}\n\n// Don't emit readable right away in sync mode, because this can trigger\n// another read() call => stack overflow. This way, it might trigger\n// a nextTick recursion warning, but that's not so bad.\nfunction emitReadable(stream) {\n var state = stream._readableState;\n debug('emitReadable', state.needReadable, state.emittedReadable);\n state.needReadable = false;\n if (!state.emittedReadable) {\n debug('emitReadable', state.flowing);\n state.emittedReadable = true;\n process.nextTick(emitReadable_, stream);\n }\n}\nfunction emitReadable_(stream) {\n var state = stream._readableState;\n debug('emitReadable_', state.destroyed, state.length, state.ended);\n if (!state.destroyed && (state.length || state.ended)) {\n stream.emit('readable');\n state.emittedReadable = false;\n }\n\n // The stream needs another readable event if\n // 1. It is not flowing, as the flow mechanism will take\n // care of it.\n // 2. It is not ended.\n // 3. It is below the highWaterMark, so we can schedule\n // another readable later.\n state.needReadable = !state.flowing && !state.ended && state.length <= state.highWaterMark;\n flow(stream);\n}\n\n// at this point, the user has presumably seen the 'readable' event,\n// and called read() to consume some data. that may have triggered\n// in turn another _read(n) call, in which case reading = true if\n// it's in progress.\n// However, if we're not ended, or reading, and the length < hwm,\n// then go ahead and try to read some more preemptively.\nfunction maybeReadMore(stream, state) {\n if (!state.readingMore) {\n state.readingMore = true;\n process.nextTick(maybeReadMore_, stream, state);\n }\n}\nfunction maybeReadMore_(stream, state) {\n // Attempt to read more data if we should.\n //\n // The conditions for reading more data are (one of):\n // - Not enough data buffered (state.length < state.highWaterMark). The loop\n // is responsible for filling the buffer with enough data if such data\n // is available. If highWaterMark is 0 and we are not in the flowing mode\n // we should _not_ attempt to buffer any extra data. We'll get more data\n // when the stream consumer calls read() instead.\n // - No data in the buffer, and the stream is in flowing mode. In this mode\n // the loop below is responsible for ensuring read() is called. Failing to\n // call read here would abort the flow and there's no other mechanism for\n // continuing the flow if the stream consumer has just subscribed to the\n // 'data' event.\n //\n // In addition to the above conditions to keep reading data, the following\n // conditions prevent the data from being read:\n // - The stream has ended (state.ended).\n // - There is already a pending 'read' operation (state.reading). This is a\n // case where the the stream has called the implementation defined _read()\n // method, but they are processing the call asynchronously and have _not_\n // called push() with new data. In this case we skip performing more\n // read()s. The execution ends in this method again after the _read() ends\n // up calling push() with more data.\n while (!state.reading && !state.ended && (state.length < state.highWaterMark || state.flowing && state.length === 0)) {\n var len = state.length;\n debug('maybeReadMore read 0');\n stream.read(0);\n if (len === state.length)\n // didn't get any data, stop spinning.\n break;\n }\n state.readingMore = false;\n}\n\n// abstract method. to be overridden in specific implementation classes.\n// call cb(er, data) where data is <= n in length.\n// for virtual (non-string, non-buffer) streams, \"length\" is somewhat\n// arbitrary, and perhaps not very meaningful.\nReadable.prototype._read = function (n) {\n errorOrDestroy(this, new ERR_METHOD_NOT_IMPLEMENTED('_read()'));\n};\nReadable.prototype.pipe = function (dest, pipeOpts) {\n var src = this;\n var state = this._readableState;\n switch (state.pipesCount) {\n case 0:\n state.pipes = dest;\n break;\n case 1:\n state.pipes = [state.pipes, dest];\n break;\n default:\n state.pipes.push(dest);\n break;\n }\n state.pipesCount += 1;\n debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts);\n var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr;\n var endFn = doEnd ? onend : unpipe;\n if (state.endEmitted) process.nextTick(endFn);else src.once('end', endFn);\n dest.on('unpipe', onunpipe);\n function onunpipe(readable, unpipeInfo) {\n debug('onunpipe');\n if (readable === src) {\n if (unpipeInfo && unpipeInfo.hasUnpiped === false) {\n unpipeInfo.hasUnpiped = true;\n cleanup();\n }\n }\n }\n function onend() {\n debug('onend');\n dest.end();\n }\n\n // when the dest drains, it reduces the awaitDrain counter\n // on the source. This would be more elegant with a .once()\n // handler in flow(), but adding and removing repeatedly is\n // too slow.\n var ondrain = pipeOnDrain(src);\n dest.on('drain', ondrain);\n var cleanedUp = false;\n function cleanup() {\n debug('cleanup');\n // cleanup event handlers once the pipe is broken\n dest.removeListener('close', onclose);\n dest.removeListener('finish', onfinish);\n dest.removeListener('drain', ondrain);\n dest.removeListener('error', onerror);\n dest.removeListener('unpipe', onunpipe);\n src.removeListener('end', onend);\n src.removeListener('end', unpipe);\n src.removeListener('data', ondata);\n cleanedUp = true;\n\n // if the reader is waiting for a drain event from this\n // specific writer, then it would cause it to never start\n // flowing again.\n // So, if this is awaiting a drain, then we just call it now.\n // If we don't know, then assume that we are waiting for one.\n if (state.awaitDrain && (!dest._writableState || dest._writableState.needDrain)) ondrain();\n }\n src.on('data', ondata);\n function ondata(chunk) {\n debug('ondata');\n var ret = dest.write(chunk);\n debug('dest.write', ret);\n if (ret === false) {\n // If the user unpiped during `dest.write()`, it is possible\n // to get stuck in a permanently paused state if that write\n // also returned false.\n // => Check whether `dest` is still a piping destination.\n if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) {\n debug('false write response, pause', state.awaitDrain);\n state.awaitDrain++;\n }\n src.pause();\n }\n }\n\n // if the dest has an error, then stop piping into it.\n // however, don't suppress the throwing behavior for this.\n function onerror(er) {\n debug('onerror', er);\n unpipe();\n dest.removeListener('error', onerror);\n if (EElistenerCount(dest, 'error') === 0) errorOrDestroy(dest, er);\n }\n\n // Make sure our error handler is attached before userland ones.\n prependListener(dest, 'error', onerror);\n\n // Both close and finish should trigger unpipe, but only once.\n function onclose() {\n dest.removeListener('finish', onfinish);\n unpipe();\n }\n dest.once('close', onclose);\n function onfinish() {\n debug('onfinish');\n dest.removeListener('close', onclose);\n unpipe();\n }\n dest.once('finish', onfinish);\n function unpipe() {\n debug('unpipe');\n src.unpipe(dest);\n }\n\n // tell the dest that it's being piped to\n dest.emit('pipe', src);\n\n // start the flow if it hasn't been started already.\n if (!state.flowing) {\n debug('pipe resume');\n src.resume();\n }\n return dest;\n};\nfunction pipeOnDrain(src) {\n return function pipeOnDrainFunctionResult() {\n var state = src._readableState;\n debug('pipeOnDrain', state.awaitDrain);\n if (state.awaitDrain) state.awaitDrain--;\n if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) {\n state.flowing = true;\n flow(src);\n }\n };\n}\nReadable.prototype.unpipe = function (dest) {\n var state = this._readableState;\n var unpipeInfo = {\n hasUnpiped: false\n };\n\n // if we're not piping anywhere, then do nothing.\n if (state.pipesCount === 0) return this;\n\n // just one destination. most common case.\n if (state.pipesCount === 1) {\n // passed in one, but it's not the right one.\n if (dest && dest !== state.pipes) return this;\n if (!dest) dest = state.pipes;\n\n // got a match.\n state.pipes = null;\n state.pipesCount = 0;\n state.flowing = false;\n if (dest) dest.emit('unpipe', this, unpipeInfo);\n return this;\n }\n\n // slow case. multiple pipe destinations.\n\n if (!dest) {\n // remove all.\n var dests = state.pipes;\n var len = state.pipesCount;\n state.pipes = null;\n state.pipesCount = 0;\n state.flowing = false;\n for (var i = 0; i < len; i++) dests[i].emit('unpipe', this, {\n hasUnpiped: false\n });\n return this;\n }\n\n // try to find the right one.\n var index = indexOf(state.pipes, dest);\n if (index === -1) return this;\n state.pipes.splice(index, 1);\n state.pipesCount -= 1;\n if (state.pipesCount === 1) state.pipes = state.pipes[0];\n dest.emit('unpipe', this, unpipeInfo);\n return this;\n};\n\n// set up data events if they are asked for\n// Ensure readable listeners eventually get something\nReadable.prototype.on = function (ev, fn) {\n var res = Stream.prototype.on.call(this, ev, fn);\n var state = this._readableState;\n if (ev === 'data') {\n // update readableListening so that resume() may be a no-op\n // a few lines down. This is needed to support once('readable').\n state.readableListening = this.listenerCount('readable') > 0;\n\n // Try start flowing on next tick if stream isn't explicitly paused\n if (state.flowing !== false) this.resume();\n } else if (ev === 'readable') {\n if (!state.endEmitted && !state.readableListening) {\n state.readableListening = state.needReadable = true;\n state.flowing = false;\n state.emittedReadable = false;\n debug('on readable', state.length, state.reading);\n if (state.length) {\n emitReadable(this);\n } else if (!state.reading) {\n process.nextTick(nReadingNextTick, this);\n }\n }\n }\n return res;\n};\nReadable.prototype.addListener = Readable.prototype.on;\nReadable.prototype.removeListener = function (ev, fn) {\n var res = Stream.prototype.removeListener.call(this, ev, fn);\n if (ev === 'readable') {\n // We need to check if there is someone still listening to\n // readable and reset the state. However this needs to happen\n // after readable has been emitted but before I/O (nextTick) to\n // support once('readable', fn) cycles. This means that calling\n // resume within the same tick will have no\n // effect.\n process.nextTick(updateReadableListening, this);\n }\n return res;\n};\nReadable.prototype.removeAllListeners = function (ev) {\n var res = Stream.prototype.removeAllListeners.apply(this, arguments);\n if (ev === 'readable' || ev === undefined) {\n // We need to check if there is someone still listening to\n // readable and reset the state. However this needs to happen\n // after readable has been emitted but before I/O (nextTick) to\n // support once('readable', fn) cycles. This means that calling\n // resume within the same tick will have no\n // effect.\n process.nextTick(updateReadableListening, this);\n }\n return res;\n};\nfunction updateReadableListening(self) {\n var state = self._readableState;\n state.readableListening = self.listenerCount('readable') > 0;\n if (state.resumeScheduled && !state.paused) {\n // flowing needs to be set to true now, otherwise\n // the upcoming resume will not flow.\n state.flowing = true;\n\n // crude way to check if we should resume\n } else if (self.listenerCount('data') > 0) {\n self.resume();\n }\n}\nfunction nReadingNextTick(self) {\n debug('readable nexttick read 0');\n self.read(0);\n}\n\n// pause() and resume() are remnants of the legacy readable stream API\n// If the user uses them, then switch into old mode.\nReadable.prototype.resume = function () {\n var state = this._readableState;\n if (!state.flowing) {\n debug('resume');\n // we flow only if there is no one listening\n // for readable, but we still have to call\n // resume()\n state.flowing = !state.readableListening;\n resume(this, state);\n }\n state.paused = false;\n return this;\n};\nfunction resume(stream, state) {\n if (!state.resumeScheduled) {\n state.resumeScheduled = true;\n process.nextTick(resume_, stream, state);\n }\n}\nfunction resume_(stream, state) {\n debug('resume', state.reading);\n if (!state.reading) {\n stream.read(0);\n }\n state.resumeScheduled = false;\n stream.emit('resume');\n flow(stream);\n if (state.flowing && !state.reading) stream.read(0);\n}\nReadable.prototype.pause = function () {\n debug('call pause flowing=%j', this._readableState.flowing);\n if (this._readableState.flowing !== false) {\n debug('pause');\n this._readableState.flowing = false;\n this.emit('pause');\n }\n this._readableState.paused = true;\n return this;\n};\nfunction flow(stream) {\n var state = stream._readableState;\n debug('flow', state.flowing);\n while (state.flowing && stream.read() !== null);\n}\n\n// wrap an old-style stream as the async data source.\n// This is *not* part of the readable stream interface.\n// It is an ugly unfortunate mess of history.\nReadable.prototype.wrap = function (stream) {\n var _this = this;\n var state = this._readableState;\n var paused = false;\n stream.on('end', function () {\n debug('wrapped end');\n if (state.decoder && !state.ended) {\n var chunk = state.decoder.end();\n if (chunk && chunk.length) _this.push(chunk);\n }\n _this.push(null);\n });\n stream.on('data', function (chunk) {\n debug('wrapped data');\n if (state.decoder) chunk = state.decoder.write(chunk);\n\n // don't skip over falsy values in objectMode\n if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return;\n var ret = _this.push(chunk);\n if (!ret) {\n paused = true;\n stream.pause();\n }\n });\n\n // proxy all the other methods.\n // important when wrapping filters and duplexes.\n for (var i in stream) {\n if (this[i] === undefined && typeof stream[i] === 'function') {\n this[i] = function methodWrap(method) {\n return function methodWrapReturnFunction() {\n return stream[method].apply(stream, arguments);\n };\n }(i);\n }\n }\n\n // proxy certain important events.\n for (var n = 0; n < kProxyEvents.length; n++) {\n stream.on(kProxyEvents[n], this.emit.bind(this, kProxyEvents[n]));\n }\n\n // when we try to consume some more bytes, simply unpause the\n // underlying stream.\n this._read = function (n) {\n debug('wrapped _read', n);\n if (paused) {\n paused = false;\n stream.resume();\n }\n };\n return this;\n};\nif (typeof Symbol === 'function') {\n Readable.prototype[Symbol.asyncIterator] = function () {\n if (createReadableStreamAsyncIterator === undefined) {\n createReadableStreamAsyncIterator = require('./internal/streams/async_iterator');\n }\n return createReadableStreamAsyncIterator(this);\n };\n}\nObject.defineProperty(Readable.prototype, 'readableHighWaterMark', {\n // making it explicit this property is not enumerable\n // because otherwise some prototype manipulation in\n // userland will fail\n enumerable: false,\n get: function get() {\n return this._readableState.highWaterMark;\n }\n});\nObject.defineProperty(Readable.prototype, 'readableBuffer', {\n // making it explicit this property is not enumerable\n // because otherwise some prototype manipulation in\n // userland will fail\n enumerable: false,\n get: function get() {\n return this._readableState && this._readableState.buffer;\n }\n});\nObject.defineProperty(Readable.prototype, 'readableFlowing', {\n // making it explicit this property is not enumerable\n // because otherwise some prototype manipulation in\n // userland will fail\n enumerable: false,\n get: function get() {\n return this._readableState.flowing;\n },\n set: function set(state) {\n if (this._readableState) {\n this._readableState.flowing = state;\n }\n }\n});\n\n// exposed for testing purposes only.\nReadable._fromList = fromList;\nObject.defineProperty(Readable.prototype, 'readableLength', {\n // making it explicit this property is not enumerable\n // because otherwise some prototype manipulation in\n // userland will fail\n enumerable: false,\n get: function get() {\n return this._readableState.length;\n }\n});\n\n// Pluck off n bytes from an array of buffers.\n// Length is the combined lengths of all the buffers in the list.\n// This function is designed to be inlinable, so please take care when making\n// changes to the function body.\nfunction fromList(n, state) {\n // nothing buffered\n if (state.length === 0) return null;\n var ret;\n if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) {\n // read it all, truncate the list\n if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.first();else ret = state.buffer.concat(state.length);\n state.buffer.clear();\n } else {\n // read part of list\n ret = state.buffer.consume(n, state.decoder);\n }\n return ret;\n}\nfunction endReadable(stream) {\n var state = stream._readableState;\n debug('endReadable', state.endEmitted);\n if (!state.endEmitted) {\n state.ended = true;\n process.nextTick(endReadableNT, state, stream);\n }\n}\nfunction endReadableNT(state, stream) {\n debug('endReadableNT', state.endEmitted, state.length);\n\n // Check that we didn't get one last unshift.\n if (!state.endEmitted && state.length === 0) {\n state.endEmitted = true;\n stream.readable = false;\n stream.emit('end');\n if (state.autoDestroy) {\n // In case of duplex streams we need a way to detect\n // if the writable side is ready for autoDestroy as well\n var wState = stream._writableState;\n if (!wState || wState.autoDestroy && wState.finished) {\n stream.destroy();\n }\n }\n }\n}\nif (typeof Symbol === 'function') {\n Readable.from = function (iterable, opts) {\n if (from === undefined) {\n from = require('./internal/streams/from');\n }\n return from(Readable, iterable, opts);\n };\n}\nfunction indexOf(xs, x) {\n for (var i = 0, l = xs.length; i < l; i++) {\n if (xs[i] === x) return i;\n }\n return -1;\n}","// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n// a transform stream is a readable/writable stream where you do\n// something with the data. Sometimes it's called a \"filter\",\n// but that's not a great name for it, since that implies a thing where\n// some bits pass through, and others are simply ignored. (That would\n// be a valid example of a transform, of course.)\n//\n// While the output is causally related to the input, it's not a\n// necessarily symmetric or synchronous transformation. For example,\n// a zlib stream might take multiple plain-text writes(), and then\n// emit a single compressed chunk some time in the future.\n//\n// Here's how this works:\n//\n// The Transform stream has all the aspects of the readable and writable\n// stream classes. When you write(chunk), that calls _write(chunk,cb)\n// internally, and returns false if there's a lot of pending writes\n// buffered up. When you call read(), that calls _read(n) until\n// there's enough pending readable data buffered up.\n//\n// In a transform stream, the written data is placed in a buffer. When\n// _read(n) is called, it transforms the queued up data, calling the\n// buffered _write cb's as it consumes chunks. If consuming a single\n// written chunk would result in multiple output chunks, then the first\n// outputted bit calls the readcb, and subsequent chunks just go into\n// the read buffer, and will cause it to emit 'readable' if necessary.\n//\n// This way, back-pressure is actually determined by the reading side,\n// since _read has to be called to start processing a new chunk. However,\n// a pathological inflate type of transform can cause excessive buffering\n// here. For example, imagine a stream where every byte of input is\n// interpreted as an integer from 0-255, and then results in that many\n// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in\n// 1kb of data being output. In this case, you could write a very small\n// amount of input, and end up with a very large amount of output. In\n// such a pathological inflating mechanism, there'd be no way to tell\n// the system to stop doing the transform. A single 4MB write could\n// cause the system to run out of memory.\n//\n// However, even in such a pathological case, only a single written chunk\n// would be consumed, and then the rest would wait (un-transformed) until\n// the results of the previous transformed chunk were consumed.\n\n'use strict';\n\nmodule.exports = Transform;\nvar _require$codes = require('../errors').codes,\n ERR_METHOD_NOT_IMPLEMENTED = _require$codes.ERR_METHOD_NOT_IMPLEMENTED,\n ERR_MULTIPLE_CALLBACK = _require$codes.ERR_MULTIPLE_CALLBACK,\n ERR_TRANSFORM_ALREADY_TRANSFORMING = _require$codes.ERR_TRANSFORM_ALREADY_TRANSFORMING,\n ERR_TRANSFORM_WITH_LENGTH_0 = _require$codes.ERR_TRANSFORM_WITH_LENGTH_0;\nvar Duplex = require('./_stream_duplex');\nrequire('inherits')(Transform, Duplex);\nfunction afterTransform(er, data) {\n var ts = this._transformState;\n ts.transforming = false;\n var cb = ts.writecb;\n if (cb === null) {\n return this.emit('error', new ERR_MULTIPLE_CALLBACK());\n }\n ts.writechunk = null;\n ts.writecb = null;\n if (data != null)\n // single equals check for both `null` and `undefined`\n this.push(data);\n cb(er);\n var rs = this._readableState;\n rs.reading = false;\n if (rs.needReadable || rs.length < rs.highWaterMark) {\n this._read(rs.highWaterMark);\n }\n}\nfunction Transform(options) {\n if (!(this instanceof Transform)) return new Transform(options);\n Duplex.call(this, options);\n this._transformState = {\n afterTransform: afterTransform.bind(this),\n needTransform: false,\n transforming: false,\n writecb: null,\n writechunk: null,\n writeencoding: null\n };\n\n // start out asking for a readable event once data is transformed.\n this._readableState.needReadable = true;\n\n // we have implemented the _read method, and done the other things\n // that Readable wants before the first _read call, so unset the\n // sync guard flag.\n this._readableState.sync = false;\n if (options) {\n if (typeof options.transform === 'function') this._transform = options.transform;\n if (typeof options.flush === 'function') this._flush = options.flush;\n }\n\n // When the writable side finishes, then flush out anything remaining.\n this.on('prefinish', prefinish);\n}\nfunction prefinish() {\n var _this = this;\n if (typeof this._flush === 'function' && !this._readableState.destroyed) {\n this._flush(function (er, data) {\n done(_this, er, data);\n });\n } else {\n done(this, null, null);\n }\n}\nTransform.prototype.push = function (chunk, encoding) {\n this._transformState.needTransform = false;\n return Duplex.prototype.push.call(this, chunk, encoding);\n};\n\n// This is the part where you do stuff!\n// override this function in implementation classes.\n// 'chunk' is an input chunk.\n//\n// Call `push(newChunk)` to pass along transformed output\n// to the readable side. You may call 'push' zero or more times.\n//\n// Call `cb(err)` when you are done with this chunk. If you pass\n// an error, then that'll put the hurt on the whole operation. If you\n// never call cb(), then you'll never get another chunk.\nTransform.prototype._transform = function (chunk, encoding, cb) {\n cb(new ERR_METHOD_NOT_IMPLEMENTED('_transform()'));\n};\nTransform.prototype._write = function (chunk, encoding, cb) {\n var ts = this._transformState;\n ts.writecb = cb;\n ts.writechunk = chunk;\n ts.writeencoding = encoding;\n if (!ts.transforming) {\n var rs = this._readableState;\n if (ts.needTransform || rs.needReadable || rs.length < rs.highWaterMark) this._read(rs.highWaterMark);\n }\n};\n\n// Doesn't matter what the args are here.\n// _transform does all the work.\n// That we got here means that the readable side wants more data.\nTransform.prototype._read = function (n) {\n var ts = this._transformState;\n if (ts.writechunk !== null && !ts.transforming) {\n ts.transforming = true;\n this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform);\n } else {\n // mark that we need a transform, so that any data that comes in\n // will get processed, now that we've asked for it.\n ts.needTransform = true;\n }\n};\nTransform.prototype._destroy = function (err, cb) {\n Duplex.prototype._destroy.call(this, err, function (err2) {\n cb(err2);\n });\n};\nfunction done(stream, er, data) {\n if (er) return stream.emit('error', er);\n if (data != null)\n // single equals check for both `null` and `undefined`\n stream.push(data);\n\n // TODO(BridgeAR): Write a test for these two error cases\n // if there's nothing in the write buffer, then that means\n // that nothing more will ever be provided\n if (stream._writableState.length) throw new ERR_TRANSFORM_WITH_LENGTH_0();\n if (stream._transformState.transforming) throw new ERR_TRANSFORM_ALREADY_TRANSFORMING();\n return stream.push(null);\n}","// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n// A bit simpler than readable streams.\n// Implement an async ._write(chunk, encoding, cb), and it'll handle all\n// the drain event emission and buffering.\n\n'use strict';\n\nmodule.exports = Writable;\n\n/* */\nfunction WriteReq(chunk, encoding, cb) {\n this.chunk = chunk;\n this.encoding = encoding;\n this.callback = cb;\n this.next = null;\n}\n\n// It seems a linked list but it is not\n// there will be only 2 of these for each stream\nfunction CorkedRequest(state) {\n var _this = this;\n this.next = null;\n this.entry = null;\n this.finish = function () {\n onCorkedFinish(_this, state);\n };\n}\n/* */\n\n/**/\nvar Duplex;\n/**/\n\nWritable.WritableState = WritableState;\n\n/**/\nvar internalUtil = {\n deprecate: require('util-deprecate')\n};\n/**/\n\n/**/\nvar Stream = require('./internal/streams/stream');\n/**/\n\nvar Buffer = require('buffer').Buffer;\nvar OurUint8Array = (typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : typeof self !== 'undefined' ? self : {}).Uint8Array || function () {};\nfunction _uint8ArrayToBuffer(chunk) {\n return Buffer.from(chunk);\n}\nfunction _isUint8Array(obj) {\n return Buffer.isBuffer(obj) || obj instanceof OurUint8Array;\n}\nvar destroyImpl = require('./internal/streams/destroy');\nvar _require = require('./internal/streams/state'),\n getHighWaterMark = _require.getHighWaterMark;\nvar _require$codes = require('../errors').codes,\n ERR_INVALID_ARG_TYPE = _require$codes.ERR_INVALID_ARG_TYPE,\n ERR_METHOD_NOT_IMPLEMENTED = _require$codes.ERR_METHOD_NOT_IMPLEMENTED,\n ERR_MULTIPLE_CALLBACK = _require$codes.ERR_MULTIPLE_CALLBACK,\n ERR_STREAM_CANNOT_PIPE = _require$codes.ERR_STREAM_CANNOT_PIPE,\n ERR_STREAM_DESTROYED = _require$codes.ERR_STREAM_DESTROYED,\n ERR_STREAM_NULL_VALUES = _require$codes.ERR_STREAM_NULL_VALUES,\n ERR_STREAM_WRITE_AFTER_END = _require$codes.ERR_STREAM_WRITE_AFTER_END,\n ERR_UNKNOWN_ENCODING = _require$codes.ERR_UNKNOWN_ENCODING;\nvar errorOrDestroy = destroyImpl.errorOrDestroy;\nrequire('inherits')(Writable, Stream);\nfunction nop() {}\nfunction WritableState(options, stream, isDuplex) {\n Duplex = Duplex || require('./_stream_duplex');\n options = options || {};\n\n // Duplex streams are both readable and writable, but share\n // the same options object.\n // However, some cases require setting options to different\n // values for the readable and the writable sides of the duplex stream,\n // e.g. options.readableObjectMode vs. options.writableObjectMode, etc.\n if (typeof isDuplex !== 'boolean') isDuplex = stream instanceof Duplex;\n\n // object stream flag to indicate whether or not this stream\n // contains buffers or objects.\n this.objectMode = !!options.objectMode;\n if (isDuplex) this.objectMode = this.objectMode || !!options.writableObjectMode;\n\n // the point at which write() starts returning false\n // Note: 0 is a valid value, means that we always return false if\n // the entire buffer is not flushed immediately on write()\n this.highWaterMark = getHighWaterMark(this, options, 'writableHighWaterMark', isDuplex);\n\n // if _final has been called\n this.finalCalled = false;\n\n // drain event flag.\n this.needDrain = false;\n // at the start of calling end()\n this.ending = false;\n // when end() has been called, and returned\n this.ended = false;\n // when 'finish' is emitted\n this.finished = false;\n\n // has it been destroyed\n this.destroyed = false;\n\n // should we decode strings into buffers before passing to _write?\n // this is here so that some node-core streams can optimize string\n // handling at a lower level.\n var noDecode = options.decodeStrings === false;\n this.decodeStrings = !noDecode;\n\n // Crypto is kind of old and crusty. Historically, its default string\n // encoding is 'binary' so we have to make this configurable.\n // Everything else in the universe uses 'utf8', though.\n this.defaultEncoding = options.defaultEncoding || 'utf8';\n\n // not an actual buffer we keep track of, but a measurement\n // of how much we're waiting to get pushed to some underlying\n // socket or file.\n this.length = 0;\n\n // a flag to see when we're in the middle of a write.\n this.writing = false;\n\n // when true all writes will be buffered until .uncork() call\n this.corked = 0;\n\n // a flag to be able to tell if the onwrite cb is called immediately,\n // or on a later tick. We set this to true at first, because any\n // actions that shouldn't happen until \"later\" should generally also\n // not happen before the first write call.\n this.sync = true;\n\n // a flag to know if we're processing previously buffered items, which\n // may call the _write() callback in the same tick, so that we don't\n // end up in an overlapped onwrite situation.\n this.bufferProcessing = false;\n\n // the callback that's passed to _write(chunk,cb)\n this.onwrite = function (er) {\n onwrite(stream, er);\n };\n\n // the callback that the user supplies to write(chunk,encoding,cb)\n this.writecb = null;\n\n // the amount that is being written when _write is called.\n this.writelen = 0;\n this.bufferedRequest = null;\n this.lastBufferedRequest = null;\n\n // number of pending user-supplied write callbacks\n // this must be 0 before 'finish' can be emitted\n this.pendingcb = 0;\n\n // emit prefinish if the only thing we're waiting for is _write cbs\n // This is relevant for synchronous Transform streams\n this.prefinished = false;\n\n // True if the error was already emitted and should not be thrown again\n this.errorEmitted = false;\n\n // Should close be emitted on destroy. Defaults to true.\n this.emitClose = options.emitClose !== false;\n\n // Should .destroy() be called after 'finish' (and potentially 'end')\n this.autoDestroy = !!options.autoDestroy;\n\n // count buffered requests\n this.bufferedRequestCount = 0;\n\n // allocate the first CorkedRequest, there is always\n // one allocated and free to use, and we maintain at most two\n this.corkedRequestsFree = new CorkedRequest(this);\n}\nWritableState.prototype.getBuffer = function getBuffer() {\n var current = this.bufferedRequest;\n var out = [];\n while (current) {\n out.push(current);\n current = current.next;\n }\n return out;\n};\n(function () {\n try {\n Object.defineProperty(WritableState.prototype, 'buffer', {\n get: internalUtil.deprecate(function writableStateBufferGetter() {\n return this.getBuffer();\n }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.', 'DEP0003')\n });\n } catch (_) {}\n})();\n\n// Test _writableState for inheritance to account for Duplex streams,\n// whose prototype chain only points to Readable.\nvar realHasInstance;\nif (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') {\n realHasInstance = Function.prototype[Symbol.hasInstance];\n Object.defineProperty(Writable, Symbol.hasInstance, {\n value: function value(object) {\n if (realHasInstance.call(this, object)) return true;\n if (this !== Writable) return false;\n return object && object._writableState instanceof WritableState;\n }\n });\n} else {\n realHasInstance = function realHasInstance(object) {\n return object instanceof this;\n };\n}\nfunction Writable(options) {\n Duplex = Duplex || require('./_stream_duplex');\n\n // Writable ctor is applied to Duplexes, too.\n // `realHasInstance` is necessary because using plain `instanceof`\n // would return false, as no `_writableState` property is attached.\n\n // Trying to use the custom `instanceof` for Writable here will also break the\n // Node.js LazyTransform implementation, which has a non-trivial getter for\n // `_writableState` that would lead to infinite recursion.\n\n // Checking for a Stream.Duplex instance is faster here instead of inside\n // the WritableState constructor, at least with V8 6.5\n var isDuplex = this instanceof Duplex;\n if (!isDuplex && !realHasInstance.call(Writable, this)) return new Writable(options);\n this._writableState = new WritableState(options, this, isDuplex);\n\n // legacy.\n this.writable = true;\n if (options) {\n if (typeof options.write === 'function') this._write = options.write;\n if (typeof options.writev === 'function') this._writev = options.writev;\n if (typeof options.destroy === 'function') this._destroy = options.destroy;\n if (typeof options.final === 'function') this._final = options.final;\n }\n Stream.call(this);\n}\n\n// Otherwise people can pipe Writable streams, which is just wrong.\nWritable.prototype.pipe = function () {\n errorOrDestroy(this, new ERR_STREAM_CANNOT_PIPE());\n};\nfunction writeAfterEnd(stream, cb) {\n var er = new ERR_STREAM_WRITE_AFTER_END();\n // TODO: defer error events consistently everywhere, not just the cb\n errorOrDestroy(stream, er);\n process.nextTick(cb, er);\n}\n\n// Checks that a user-supplied chunk is valid, especially for the particular\n// mode the stream is in. Currently this means that `null` is never accepted\n// and undefined/non-string values are only allowed in object mode.\nfunction validChunk(stream, state, chunk, cb) {\n var er;\n if (chunk === null) {\n er = new ERR_STREAM_NULL_VALUES();\n } else if (typeof chunk !== 'string' && !state.objectMode) {\n er = new ERR_INVALID_ARG_TYPE('chunk', ['string', 'Buffer'], chunk);\n }\n if (er) {\n errorOrDestroy(stream, er);\n process.nextTick(cb, er);\n return false;\n }\n return true;\n}\nWritable.prototype.write = function (chunk, encoding, cb) {\n var state = this._writableState;\n var ret = false;\n var isBuf = !state.objectMode && _isUint8Array(chunk);\n if (isBuf && !Buffer.isBuffer(chunk)) {\n chunk = _uint8ArrayToBuffer(chunk);\n }\n if (typeof encoding === 'function') {\n cb = encoding;\n encoding = null;\n }\n if (isBuf) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding;\n if (typeof cb !== 'function') cb = nop;\n if (state.ending) writeAfterEnd(this, cb);else if (isBuf || validChunk(this, state, chunk, cb)) {\n state.pendingcb++;\n ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb);\n }\n return ret;\n};\nWritable.prototype.cork = function () {\n this._writableState.corked++;\n};\nWritable.prototype.uncork = function () {\n var state = this._writableState;\n if (state.corked) {\n state.corked--;\n if (!state.writing && !state.corked && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state);\n }\n};\nWritable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) {\n // node::ParseEncoding() requires lower case.\n if (typeof encoding === 'string') encoding = encoding.toLowerCase();\n if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new ERR_UNKNOWN_ENCODING(encoding);\n this._writableState.defaultEncoding = encoding;\n return this;\n};\nObject.defineProperty(Writable.prototype, 'writableBuffer', {\n // making it explicit this property is not enumerable\n // because otherwise some prototype manipulation in\n // userland will fail\n enumerable: false,\n get: function get() {\n return this._writableState && this._writableState.getBuffer();\n }\n});\nfunction decodeChunk(state, chunk, encoding) {\n if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') {\n chunk = Buffer.from(chunk, encoding);\n }\n return chunk;\n}\nObject.defineProperty(Writable.prototype, 'writableHighWaterMark', {\n // making it explicit this property is not enumerable\n // because otherwise some prototype manipulation in\n // userland will fail\n enumerable: false,\n get: function get() {\n return this._writableState.highWaterMark;\n }\n});\n\n// if we're already writing something, then just put this\n// in the queue, and wait our turn. Otherwise, call _write\n// If we return false, then we need a drain event, so set that flag.\nfunction writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) {\n if (!isBuf) {\n var newChunk = decodeChunk(state, chunk, encoding);\n if (chunk !== newChunk) {\n isBuf = true;\n encoding = 'buffer';\n chunk = newChunk;\n }\n }\n var len = state.objectMode ? 1 : chunk.length;\n state.length += len;\n var ret = state.length < state.highWaterMark;\n // we must ensure that previous needDrain will not be reset to false.\n if (!ret) state.needDrain = true;\n if (state.writing || state.corked) {\n var last = state.lastBufferedRequest;\n state.lastBufferedRequest = {\n chunk: chunk,\n encoding: encoding,\n isBuf: isBuf,\n callback: cb,\n next: null\n };\n if (last) {\n last.next = state.lastBufferedRequest;\n } else {\n state.bufferedRequest = state.lastBufferedRequest;\n }\n state.bufferedRequestCount += 1;\n } else {\n doWrite(stream, state, false, len, chunk, encoding, cb);\n }\n return ret;\n}\nfunction doWrite(stream, state, writev, len, chunk, encoding, cb) {\n state.writelen = len;\n state.writecb = cb;\n state.writing = true;\n state.sync = true;\n if (state.destroyed) state.onwrite(new ERR_STREAM_DESTROYED('write'));else if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite);\n state.sync = false;\n}\nfunction onwriteError(stream, state, sync, er, cb) {\n --state.pendingcb;\n if (sync) {\n // defer the callback if we are being called synchronously\n // to avoid piling up things on the stack\n process.nextTick(cb, er);\n // this can emit finish, and it will always happen\n // after error\n process.nextTick(finishMaybe, stream, state);\n stream._writableState.errorEmitted = true;\n errorOrDestroy(stream, er);\n } else {\n // the caller expect this to happen before if\n // it is async\n cb(er);\n stream._writableState.errorEmitted = true;\n errorOrDestroy(stream, er);\n // this can emit finish, but finish must\n // always follow error\n finishMaybe(stream, state);\n }\n}\nfunction onwriteStateUpdate(state) {\n state.writing = false;\n state.writecb = null;\n state.length -= state.writelen;\n state.writelen = 0;\n}\nfunction onwrite(stream, er) {\n var state = stream._writableState;\n var sync = state.sync;\n var cb = state.writecb;\n if (typeof cb !== 'function') throw new ERR_MULTIPLE_CALLBACK();\n onwriteStateUpdate(state);\n if (er) onwriteError(stream, state, sync, er, cb);else {\n // Check if we're actually ready to finish, but don't emit yet\n var finished = needFinish(state) || stream.destroyed;\n if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) {\n clearBuffer(stream, state);\n }\n if (sync) {\n process.nextTick(afterWrite, stream, state, finished, cb);\n } else {\n afterWrite(stream, state, finished, cb);\n }\n }\n}\nfunction afterWrite(stream, state, finished, cb) {\n if (!finished) onwriteDrain(stream, state);\n state.pendingcb--;\n cb();\n finishMaybe(stream, state);\n}\n\n// Must force callback to be called on nextTick, so that we don't\n// emit 'drain' before the write() consumer gets the 'false' return\n// value, and has a chance to attach a 'drain' listener.\nfunction onwriteDrain(stream, state) {\n if (state.length === 0 && state.needDrain) {\n state.needDrain = false;\n stream.emit('drain');\n }\n}\n\n// if there's something in the buffer waiting, then process it\nfunction clearBuffer(stream, state) {\n state.bufferProcessing = true;\n var entry = state.bufferedRequest;\n if (stream._writev && entry && entry.next) {\n // Fast case, write everything using _writev()\n var l = state.bufferedRequestCount;\n var buffer = new Array(l);\n var holder = state.corkedRequestsFree;\n holder.entry = entry;\n var count = 0;\n var allBuffers = true;\n while (entry) {\n buffer[count] = entry;\n if (!entry.isBuf) allBuffers = false;\n entry = entry.next;\n count += 1;\n }\n buffer.allBuffers = allBuffers;\n doWrite(stream, state, true, state.length, buffer, '', holder.finish);\n\n // doWrite is almost always async, defer these to save a bit of time\n // as the hot path ends with doWrite\n state.pendingcb++;\n state.lastBufferedRequest = null;\n if (holder.next) {\n state.corkedRequestsFree = holder.next;\n holder.next = null;\n } else {\n state.corkedRequestsFree = new CorkedRequest(state);\n }\n state.bufferedRequestCount = 0;\n } else {\n // Slow case, write chunks one-by-one\n while (entry) {\n var chunk = entry.chunk;\n var encoding = entry.encoding;\n var cb = entry.callback;\n var len = state.objectMode ? 1 : chunk.length;\n doWrite(stream, state, false, len, chunk, encoding, cb);\n entry = entry.next;\n state.bufferedRequestCount--;\n // if we didn't call the onwrite immediately, then\n // it means that we need to wait until it does.\n // also, that means that the chunk and cb are currently\n // being processed, so move the buffer counter past them.\n if (state.writing) {\n break;\n }\n }\n if (entry === null) state.lastBufferedRequest = null;\n }\n state.bufferedRequest = entry;\n state.bufferProcessing = false;\n}\nWritable.prototype._write = function (chunk, encoding, cb) {\n cb(new ERR_METHOD_NOT_IMPLEMENTED('_write()'));\n};\nWritable.prototype._writev = null;\nWritable.prototype.end = function (chunk, encoding, cb) {\n var state = this._writableState;\n if (typeof chunk === 'function') {\n cb = chunk;\n chunk = null;\n encoding = null;\n } else if (typeof encoding === 'function') {\n cb = encoding;\n encoding = null;\n }\n if (chunk !== null && chunk !== undefined) this.write(chunk, encoding);\n\n // .end() fully uncorks\n if (state.corked) {\n state.corked = 1;\n this.uncork();\n }\n\n // ignore unnecessary end() calls.\n if (!state.ending) endWritable(this, state, cb);\n return this;\n};\nObject.defineProperty(Writable.prototype, 'writableLength', {\n // making it explicit this property is not enumerable\n // because otherwise some prototype manipulation in\n // userland will fail\n enumerable: false,\n get: function get() {\n return this._writableState.length;\n }\n});\nfunction needFinish(state) {\n return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing;\n}\nfunction callFinal(stream, state) {\n stream._final(function (err) {\n state.pendingcb--;\n if (err) {\n errorOrDestroy(stream, err);\n }\n state.prefinished = true;\n stream.emit('prefinish');\n finishMaybe(stream, state);\n });\n}\nfunction prefinish(stream, state) {\n if (!state.prefinished && !state.finalCalled) {\n if (typeof stream._final === 'function' && !state.destroyed) {\n state.pendingcb++;\n state.finalCalled = true;\n process.nextTick(callFinal, stream, state);\n } else {\n state.prefinished = true;\n stream.emit('prefinish');\n }\n }\n}\nfunction finishMaybe(stream, state) {\n var need = needFinish(state);\n if (need) {\n prefinish(stream, state);\n if (state.pendingcb === 0) {\n state.finished = true;\n stream.emit('finish');\n if (state.autoDestroy) {\n // In case of duplex streams we need a way to detect\n // if the readable side is ready for autoDestroy as well\n var rState = stream._readableState;\n if (!rState || rState.autoDestroy && rState.endEmitted) {\n stream.destroy();\n }\n }\n }\n }\n return need;\n}\nfunction endWritable(stream, state, cb) {\n state.ending = true;\n finishMaybe(stream, state);\n if (cb) {\n if (state.finished) process.nextTick(cb);else stream.once('finish', cb);\n }\n state.ended = true;\n stream.writable = false;\n}\nfunction onCorkedFinish(corkReq, state, err) {\n var entry = corkReq.entry;\n corkReq.entry = null;\n while (entry) {\n var cb = entry.callback;\n state.pendingcb--;\n cb(err);\n entry = entry.next;\n }\n\n // reuse the free corkReq.\n state.corkedRequestsFree.next = corkReq;\n}\nObject.defineProperty(Writable.prototype, 'destroyed', {\n // making it explicit this property is not enumerable\n // because otherwise some prototype manipulation in\n // userland will fail\n enumerable: false,\n get: function get() {\n if (this._writableState === undefined) {\n return false;\n }\n return this._writableState.destroyed;\n },\n set: function set(value) {\n // we ignore the value if the stream\n // has not been initialized yet\n if (!this._writableState) {\n return;\n }\n\n // backward compatibility, the user is explicitly\n // managing destroyed\n this._writableState.destroyed = value;\n }\n});\nWritable.prototype.destroy = destroyImpl.destroy;\nWritable.prototype._undestroy = destroyImpl.undestroy;\nWritable.prototype._destroy = function (err, cb) {\n cb(err);\n};","'use strict';\n\nvar _Object$setPrototypeO;\nfunction _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\nfunction _toPropertyKey(arg) { var key = _toPrimitive(arg, \"string\"); return typeof key === \"symbol\" ? key : String(key); }\nfunction _toPrimitive(input, hint) { if (typeof input !== \"object\" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || \"default\"); if (typeof res !== \"object\") return res; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (hint === \"string\" ? String : Number)(input); }\nvar finished = require('./end-of-stream');\nvar kLastResolve = Symbol('lastResolve');\nvar kLastReject = Symbol('lastReject');\nvar kError = Symbol('error');\nvar kEnded = Symbol('ended');\nvar kLastPromise = Symbol('lastPromise');\nvar kHandlePromise = Symbol('handlePromise');\nvar kStream = Symbol('stream');\nfunction createIterResult(value, done) {\n return {\n value: value,\n done: done\n };\n}\nfunction readAndResolve(iter) {\n var resolve = iter[kLastResolve];\n if (resolve !== null) {\n var data = iter[kStream].read();\n // we defer if data is null\n // we can be expecting either 'end' or\n // 'error'\n if (data !== null) {\n iter[kLastPromise] = null;\n iter[kLastResolve] = null;\n iter[kLastReject] = null;\n resolve(createIterResult(data, false));\n }\n }\n}\nfunction onReadable(iter) {\n // we wait for the next tick, because it might\n // emit an error with process.nextTick\n process.nextTick(readAndResolve, iter);\n}\nfunction wrapForNext(lastPromise, iter) {\n return function (resolve, reject) {\n lastPromise.then(function () {\n if (iter[kEnded]) {\n resolve(createIterResult(undefined, true));\n return;\n }\n iter[kHandlePromise](resolve, reject);\n }, reject);\n };\n}\nvar AsyncIteratorPrototype = Object.getPrototypeOf(function () {});\nvar ReadableStreamAsyncIteratorPrototype = Object.setPrototypeOf((_Object$setPrototypeO = {\n get stream() {\n return this[kStream];\n },\n next: function next() {\n var _this = this;\n // if we have detected an error in the meanwhile\n // reject straight away\n var error = this[kError];\n if (error !== null) {\n return Promise.reject(error);\n }\n if (this[kEnded]) {\n return Promise.resolve(createIterResult(undefined, true));\n }\n if (this[kStream].destroyed) {\n // We need to defer via nextTick because if .destroy(err) is\n // called, the error will be emitted via nextTick, and\n // we cannot guarantee that there is no error lingering around\n // waiting to be emitted.\n return new Promise(function (resolve, reject) {\n process.nextTick(function () {\n if (_this[kError]) {\n reject(_this[kError]);\n } else {\n resolve(createIterResult(undefined, true));\n }\n });\n });\n }\n\n // if we have multiple next() calls\n // we will wait for the previous Promise to finish\n // this logic is optimized to support for await loops,\n // where next() is only called once at a time\n var lastPromise = this[kLastPromise];\n var promise;\n if (lastPromise) {\n promise = new Promise(wrapForNext(lastPromise, this));\n } else {\n // fast path needed to support multiple this.push()\n // without triggering the next() queue\n var data = this[kStream].read();\n if (data !== null) {\n return Promise.resolve(createIterResult(data, false));\n }\n promise = new Promise(this[kHandlePromise]);\n }\n this[kLastPromise] = promise;\n return promise;\n }\n}, _defineProperty(_Object$setPrototypeO, Symbol.asyncIterator, function () {\n return this;\n}), _defineProperty(_Object$setPrototypeO, \"return\", function _return() {\n var _this2 = this;\n // destroy(err, cb) is a private API\n // we can guarantee we have that here, because we control the\n // Readable class this is attached to\n return new Promise(function (resolve, reject) {\n _this2[kStream].destroy(null, function (err) {\n if (err) {\n reject(err);\n return;\n }\n resolve(createIterResult(undefined, true));\n });\n });\n}), _Object$setPrototypeO), AsyncIteratorPrototype);\nvar createReadableStreamAsyncIterator = function createReadableStreamAsyncIterator(stream) {\n var _Object$create;\n var iterator = Object.create(ReadableStreamAsyncIteratorPrototype, (_Object$create = {}, _defineProperty(_Object$create, kStream, {\n value: stream,\n writable: true\n }), _defineProperty(_Object$create, kLastResolve, {\n value: null,\n writable: true\n }), _defineProperty(_Object$create, kLastReject, {\n value: null,\n writable: true\n }), _defineProperty(_Object$create, kError, {\n value: null,\n writable: true\n }), _defineProperty(_Object$create, kEnded, {\n value: stream._readableState.endEmitted,\n writable: true\n }), _defineProperty(_Object$create, kHandlePromise, {\n value: function value(resolve, reject) {\n var data = iterator[kStream].read();\n if (data) {\n iterator[kLastPromise] = null;\n iterator[kLastResolve] = null;\n iterator[kLastReject] = null;\n resolve(createIterResult(data, false));\n } else {\n iterator[kLastResolve] = resolve;\n iterator[kLastReject] = reject;\n }\n },\n writable: true\n }), _Object$create));\n iterator[kLastPromise] = null;\n finished(stream, function (err) {\n if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') {\n var reject = iterator[kLastReject];\n // reject if we are waiting for data in the Promise\n // returned by next() and store the error\n if (reject !== null) {\n iterator[kLastPromise] = null;\n iterator[kLastResolve] = null;\n iterator[kLastReject] = null;\n reject(err);\n }\n iterator[kError] = err;\n return;\n }\n var resolve = iterator[kLastResolve];\n if (resolve !== null) {\n iterator[kLastPromise] = null;\n iterator[kLastResolve] = null;\n iterator[kLastReject] = null;\n resolve(createIterResult(undefined, true));\n }\n iterator[kEnded] = true;\n });\n stream.on('readable', onReadable.bind(null, iterator));\n return iterator;\n};\nmodule.exports = createReadableStreamAsyncIterator;","'use strict';\n\nfunction ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }\nfunction _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, \"prototype\", { writable: false }); return Constructor; }\nfunction _toPropertyKey(arg) { var key = _toPrimitive(arg, \"string\"); return typeof key === \"symbol\" ? key : String(key); }\nfunction _toPrimitive(input, hint) { if (typeof input !== \"object\" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || \"default\"); if (typeof res !== \"object\") return res; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (hint === \"string\" ? String : Number)(input); }\nvar _require = require('buffer'),\n Buffer = _require.Buffer;\nvar _require2 = require('util'),\n inspect = _require2.inspect;\nvar custom = inspect && inspect.custom || 'inspect';\nfunction copyBuffer(src, target, offset) {\n Buffer.prototype.copy.call(src, target, offset);\n}\nmodule.exports = /*#__PURE__*/function () {\n function BufferList() {\n _classCallCheck(this, BufferList);\n this.head = null;\n this.tail = null;\n this.length = 0;\n }\n _createClass(BufferList, [{\n key: \"push\",\n value: function push(v) {\n var entry = {\n data: v,\n next: null\n };\n if (this.length > 0) this.tail.next = entry;else this.head = entry;\n this.tail = entry;\n ++this.length;\n }\n }, {\n key: \"unshift\",\n value: function unshift(v) {\n var entry = {\n data: v,\n next: this.head\n };\n if (this.length === 0) this.tail = entry;\n this.head = entry;\n ++this.length;\n }\n }, {\n key: \"shift\",\n value: function shift() {\n if (this.length === 0) return;\n var ret = this.head.data;\n if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next;\n --this.length;\n return ret;\n }\n }, {\n key: \"clear\",\n value: function clear() {\n this.head = this.tail = null;\n this.length = 0;\n }\n }, {\n key: \"join\",\n value: function join(s) {\n if (this.length === 0) return '';\n var p = this.head;\n var ret = '' + p.data;\n while (p = p.next) ret += s + p.data;\n return ret;\n }\n }, {\n key: \"concat\",\n value: function concat(n) {\n if (this.length === 0) return Buffer.alloc(0);\n var ret = Buffer.allocUnsafe(n >>> 0);\n var p = this.head;\n var i = 0;\n while (p) {\n copyBuffer(p.data, ret, i);\n i += p.data.length;\n p = p.next;\n }\n return ret;\n }\n\n // Consumes a specified amount of bytes or characters from the buffered data.\n }, {\n key: \"consume\",\n value: function consume(n, hasStrings) {\n var ret;\n if (n < this.head.data.length) {\n // `slice` is the same for buffers and strings.\n ret = this.head.data.slice(0, n);\n this.head.data = this.head.data.slice(n);\n } else if (n === this.head.data.length) {\n // First chunk is a perfect match.\n ret = this.shift();\n } else {\n // Result spans more than one buffer.\n ret = hasStrings ? this._getString(n) : this._getBuffer(n);\n }\n return ret;\n }\n }, {\n key: \"first\",\n value: function first() {\n return this.head.data;\n }\n\n // Consumes a specified amount of characters from the buffered data.\n }, {\n key: \"_getString\",\n value: function _getString(n) {\n var p = this.head;\n var c = 1;\n var ret = p.data;\n n -= ret.length;\n while (p = p.next) {\n var str = p.data;\n var nb = n > str.length ? str.length : n;\n if (nb === str.length) ret += str;else ret += str.slice(0, n);\n n -= nb;\n if (n === 0) {\n if (nb === str.length) {\n ++c;\n if (p.next) this.head = p.next;else this.head = this.tail = null;\n } else {\n this.head = p;\n p.data = str.slice(nb);\n }\n break;\n }\n ++c;\n }\n this.length -= c;\n return ret;\n }\n\n // Consumes a specified amount of bytes from the buffered data.\n }, {\n key: \"_getBuffer\",\n value: function _getBuffer(n) {\n var ret = Buffer.allocUnsafe(n);\n var p = this.head;\n var c = 1;\n p.data.copy(ret);\n n -= p.data.length;\n while (p = p.next) {\n var buf = p.data;\n var nb = n > buf.length ? buf.length : n;\n buf.copy(ret, ret.length - n, 0, nb);\n n -= nb;\n if (n === 0) {\n if (nb === buf.length) {\n ++c;\n if (p.next) this.head = p.next;else this.head = this.tail = null;\n } else {\n this.head = p;\n p.data = buf.slice(nb);\n }\n break;\n }\n ++c;\n }\n this.length -= c;\n return ret;\n }\n\n // Make sure the linked list only shows the minimal necessary information.\n }, {\n key: custom,\n value: function value(_, options) {\n return inspect(this, _objectSpread(_objectSpread({}, options), {}, {\n // Only inspect one level.\n depth: 0,\n // It should not recurse.\n customInspect: false\n }));\n }\n }]);\n return BufferList;\n}();","'use strict';\n\n// undocumented cb() API, needed for core, not for public API\nfunction destroy(err, cb) {\n var _this = this;\n var readableDestroyed = this._readableState && this._readableState.destroyed;\n var writableDestroyed = this._writableState && this._writableState.destroyed;\n if (readableDestroyed || writableDestroyed) {\n if (cb) {\n cb(err);\n } else if (err) {\n if (!this._writableState) {\n process.nextTick(emitErrorNT, this, err);\n } else if (!this._writableState.errorEmitted) {\n this._writableState.errorEmitted = true;\n process.nextTick(emitErrorNT, this, err);\n }\n }\n return this;\n }\n\n // we set destroyed to true before firing error callbacks in order\n // to make it re-entrance safe in case destroy() is called within callbacks\n\n if (this._readableState) {\n this._readableState.destroyed = true;\n }\n\n // if this is a duplex stream mark the writable part as destroyed as well\n if (this._writableState) {\n this._writableState.destroyed = true;\n }\n this._destroy(err || null, function (err) {\n if (!cb && err) {\n if (!_this._writableState) {\n process.nextTick(emitErrorAndCloseNT, _this, err);\n } else if (!_this._writableState.errorEmitted) {\n _this._writableState.errorEmitted = true;\n process.nextTick(emitErrorAndCloseNT, _this, err);\n } else {\n process.nextTick(emitCloseNT, _this);\n }\n } else if (cb) {\n process.nextTick(emitCloseNT, _this);\n cb(err);\n } else {\n process.nextTick(emitCloseNT, _this);\n }\n });\n return this;\n}\nfunction emitErrorAndCloseNT(self, err) {\n emitErrorNT(self, err);\n emitCloseNT(self);\n}\nfunction emitCloseNT(self) {\n if (self._writableState && !self._writableState.emitClose) return;\n if (self._readableState && !self._readableState.emitClose) return;\n self.emit('close');\n}\nfunction undestroy() {\n if (this._readableState) {\n this._readableState.destroyed = false;\n this._readableState.reading = false;\n this._readableState.ended = false;\n this._readableState.endEmitted = false;\n }\n if (this._writableState) {\n this._writableState.destroyed = false;\n this._writableState.ended = false;\n this._writableState.ending = false;\n this._writableState.finalCalled = false;\n this._writableState.prefinished = false;\n this._writableState.finished = false;\n this._writableState.errorEmitted = false;\n }\n}\nfunction emitErrorNT(self, err) {\n self.emit('error', err);\n}\nfunction errorOrDestroy(stream, err) {\n // We have tests that rely on errors being emitted\n // in the same tick, so changing this is semver major.\n // For now when you opt-in to autoDestroy we allow\n // the error to be emitted nextTick. In a future\n // semver major update we should change the default to this.\n\n var rState = stream._readableState;\n var wState = stream._writableState;\n if (rState && rState.autoDestroy || wState && wState.autoDestroy) stream.destroy(err);else stream.emit('error', err);\n}\nmodule.exports = {\n destroy: destroy,\n undestroy: undestroy,\n errorOrDestroy: errorOrDestroy\n};","// Ported from https://github.com/mafintosh/end-of-stream with\n// permission from the author, Mathias Buus (@mafintosh).\n\n'use strict';\n\nvar ERR_STREAM_PREMATURE_CLOSE = require('../../../errors').codes.ERR_STREAM_PREMATURE_CLOSE;\nfunction once(callback) {\n var called = false;\n return function () {\n if (called) return;\n called = true;\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n callback.apply(this, args);\n };\n}\nfunction noop() {}\nfunction isRequest(stream) {\n return stream.setHeader && typeof stream.abort === 'function';\n}\nfunction eos(stream, opts, callback) {\n if (typeof opts === 'function') return eos(stream, null, opts);\n if (!opts) opts = {};\n callback = once(callback || noop);\n var readable = opts.readable || opts.readable !== false && stream.readable;\n var writable = opts.writable || opts.writable !== false && stream.writable;\n var onlegacyfinish = function onlegacyfinish() {\n if (!stream.writable) onfinish();\n };\n var writableEnded = stream._writableState && stream._writableState.finished;\n var onfinish = function onfinish() {\n writable = false;\n writableEnded = true;\n if (!readable) callback.call(stream);\n };\n var readableEnded = stream._readableState && stream._readableState.endEmitted;\n var onend = function onend() {\n readable = false;\n readableEnded = true;\n if (!writable) callback.call(stream);\n };\n var onerror = function onerror(err) {\n callback.call(stream, err);\n };\n var onclose = function onclose() {\n var err;\n if (readable && !readableEnded) {\n if (!stream._readableState || !stream._readableState.ended) err = new ERR_STREAM_PREMATURE_CLOSE();\n return callback.call(stream, err);\n }\n if (writable && !writableEnded) {\n if (!stream._writableState || !stream._writableState.ended) err = new ERR_STREAM_PREMATURE_CLOSE();\n return callback.call(stream, err);\n }\n };\n var onrequest = function onrequest() {\n stream.req.on('finish', onfinish);\n };\n if (isRequest(stream)) {\n stream.on('complete', onfinish);\n stream.on('abort', onclose);\n if (stream.req) onrequest();else stream.on('request', onrequest);\n } else if (writable && !stream._writableState) {\n // legacy streams\n stream.on('end', onlegacyfinish);\n stream.on('close', onlegacyfinish);\n }\n stream.on('end', onend);\n stream.on('finish', onfinish);\n if (opts.error !== false) stream.on('error', onerror);\n stream.on('close', onclose);\n return function () {\n stream.removeListener('complete', onfinish);\n stream.removeListener('abort', onclose);\n stream.removeListener('request', onrequest);\n if (stream.req) stream.req.removeListener('finish', onfinish);\n stream.removeListener('end', onlegacyfinish);\n stream.removeListener('close', onlegacyfinish);\n stream.removeListener('finish', onfinish);\n stream.removeListener('end', onend);\n stream.removeListener('error', onerror);\n stream.removeListener('close', onclose);\n };\n}\nmodule.exports = eos;","module.exports = function () {\n throw new Error('Readable.from is not available in the browser')\n};\n","// Ported from https://github.com/mafintosh/pump with\n// permission from the author, Mathias Buus (@mafintosh).\n\n'use strict';\n\nvar eos;\nfunction once(callback) {\n var called = false;\n return function () {\n if (called) return;\n called = true;\n callback.apply(void 0, arguments);\n };\n}\nvar _require$codes = require('../../../errors').codes,\n ERR_MISSING_ARGS = _require$codes.ERR_MISSING_ARGS,\n ERR_STREAM_DESTROYED = _require$codes.ERR_STREAM_DESTROYED;\nfunction noop(err) {\n // Rethrow the error if it exists to avoid swallowing it\n if (err) throw err;\n}\nfunction isRequest(stream) {\n return stream.setHeader && typeof stream.abort === 'function';\n}\nfunction destroyer(stream, reading, writing, callback) {\n callback = once(callback);\n var closed = false;\n stream.on('close', function () {\n closed = true;\n });\n if (eos === undefined) eos = require('./end-of-stream');\n eos(stream, {\n readable: reading,\n writable: writing\n }, function (err) {\n if (err) return callback(err);\n closed = true;\n callback();\n });\n var destroyed = false;\n return function (err) {\n if (closed) return;\n if (destroyed) return;\n destroyed = true;\n\n // request.destroy just do .end - .abort is what we want\n if (isRequest(stream)) return stream.abort();\n if (typeof stream.destroy === 'function') return stream.destroy();\n callback(err || new ERR_STREAM_DESTROYED('pipe'));\n };\n}\nfunction call(fn) {\n fn();\n}\nfunction pipe(from, to) {\n return from.pipe(to);\n}\nfunction popCallback(streams) {\n if (!streams.length) return noop;\n if (typeof streams[streams.length - 1] !== 'function') return noop;\n return streams.pop();\n}\nfunction pipeline() {\n for (var _len = arguments.length, streams = new Array(_len), _key = 0; _key < _len; _key++) {\n streams[_key] = arguments[_key];\n }\n var callback = popCallback(streams);\n if (Array.isArray(streams[0])) streams = streams[0];\n if (streams.length < 2) {\n throw new ERR_MISSING_ARGS('streams');\n }\n var error;\n var destroys = streams.map(function (stream, i) {\n var reading = i < streams.length - 1;\n var writing = i > 0;\n return destroyer(stream, reading, writing, function (err) {\n if (!error) error = err;\n if (err) destroys.forEach(call);\n if (reading) return;\n destroys.forEach(call);\n callback(error);\n });\n });\n return streams.reduce(pipe);\n}\nmodule.exports = pipeline;","'use strict';\n\nvar ERR_INVALID_OPT_VALUE = require('../../../errors').codes.ERR_INVALID_OPT_VALUE;\nfunction highWaterMarkFrom(options, isDuplex, duplexKey) {\n return options.highWaterMark != null ? options.highWaterMark : isDuplex ? options[duplexKey] : null;\n}\nfunction getHighWaterMark(state, options, duplexKey, isDuplex) {\n var hwm = highWaterMarkFrom(options, isDuplex, duplexKey);\n if (hwm != null) {\n if (!(isFinite(hwm) && Math.floor(hwm) === hwm) || hwm < 0) {\n var name = isDuplex ? duplexKey : 'highWaterMark';\n throw new ERR_INVALID_OPT_VALUE(name, hwm);\n }\n return Math.floor(hwm);\n }\n\n // Default value\n return state.objectMode ? 16 : 16 * 1024;\n}\nmodule.exports = {\n getHighWaterMark: getHighWaterMark\n};","module.exports = require('events').EventEmitter;\n","/*! safe-buffer. MIT License. Feross Aboukhadijeh */\n/* eslint-disable node/no-deprecated-api */\nvar buffer = require('buffer')\nvar Buffer = buffer.Buffer\n\n// alternative to using Object.keys for old browsers\nfunction copyProps (src, dst) {\n for (var key in src) {\n dst[key] = src[key]\n }\n}\nif (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) {\n module.exports = buffer\n} else {\n // Copy properties from require('buffer')\n copyProps(buffer, exports)\n exports.Buffer = SafeBuffer\n}\n\nfunction SafeBuffer (arg, encodingOrOffset, length) {\n return Buffer(arg, encodingOrOffset, length)\n}\n\nSafeBuffer.prototype = Object.create(Buffer.prototype)\n\n// Copy static methods from Buffer\ncopyProps(Buffer, SafeBuffer)\n\nSafeBuffer.from = function (arg, encodingOrOffset, length) {\n if (typeof arg === 'number') {\n throw new TypeError('Argument must not be a number')\n }\n return Buffer(arg, encodingOrOffset, length)\n}\n\nSafeBuffer.alloc = function (size, fill, encoding) {\n if (typeof size !== 'number') {\n throw new TypeError('Argument must be a number')\n }\n var buf = Buffer(size)\n if (fill !== undefined) {\n if (typeof encoding === 'string') {\n buf.fill(fill, encoding)\n } else {\n buf.fill(fill)\n }\n } else {\n buf.fill(0)\n }\n return buf\n}\n\nSafeBuffer.allocUnsafe = function (size) {\n if (typeof size !== 'number') {\n throw new TypeError('Argument must be a number')\n }\n return Buffer(size)\n}\n\nSafeBuffer.allocUnsafeSlow = function (size) {\n if (typeof size !== 'number') {\n throw new TypeError('Argument must be a number')\n }\n return buffer.SlowBuffer(size)\n}\n","var Buffer = require('safe-buffer').Buffer\n\n// prototype class for hash functions\nfunction Hash (blockSize, finalSize) {\n this._block = Buffer.alloc(blockSize)\n this._finalSize = finalSize\n this._blockSize = blockSize\n this._len = 0\n}\n\nHash.prototype.update = function (data, enc) {\n if (typeof data === 'string') {\n enc = enc || 'utf8'\n data = Buffer.from(data, enc)\n }\n\n var block = this._block\n var blockSize = this._blockSize\n var length = data.length\n var accum = this._len\n\n for (var offset = 0; offset < length;) {\n var assigned = accum % blockSize\n var remainder = Math.min(length - offset, blockSize - assigned)\n\n for (var i = 0; i < remainder; i++) {\n block[assigned + i] = data[offset + i]\n }\n\n accum += remainder\n offset += remainder\n\n if ((accum % blockSize) === 0) {\n this._update(block)\n }\n }\n\n this._len += length\n return this\n}\n\nHash.prototype.digest = function (enc) {\n var rem = this._len % this._blockSize\n\n this._block[rem] = 0x80\n\n // zero (rem + 1) trailing bits, where (rem + 1) is the smallest\n // non-negative solution to the equation (length + 1 + (rem + 1)) === finalSize mod blockSize\n this._block.fill(0, rem + 1)\n\n if (rem >= this._finalSize) {\n this._update(this._block)\n this._block.fill(0)\n }\n\n var bits = this._len * 8\n\n // uint32\n if (bits <= 0xffffffff) {\n this._block.writeUInt32BE(bits, this._blockSize - 4)\n\n // uint64\n } else {\n var lowBits = (bits & 0xffffffff) >>> 0\n var highBits = (bits - lowBits) / 0x100000000\n\n this._block.writeUInt32BE(highBits, this._blockSize - 8)\n this._block.writeUInt32BE(lowBits, this._blockSize - 4)\n }\n\n this._update(this._block)\n var hash = this._hash()\n\n return enc ? hash.toString(enc) : hash\n}\n\nHash.prototype._update = function () {\n throw new Error('_update must be implemented by subclass')\n}\n\nmodule.exports = Hash\n","var exports = module.exports = function SHA (algorithm) {\n algorithm = algorithm.toLowerCase()\n\n var Algorithm = exports[algorithm]\n if (!Algorithm) throw new Error(algorithm + ' is not supported (we accept pull requests)')\n\n return new Algorithm()\n}\n\nexports.sha = require('./sha')\nexports.sha1 = require('./sha1')\nexports.sha224 = require('./sha224')\nexports.sha256 = require('./sha256')\nexports.sha384 = require('./sha384')\nexports.sha512 = require('./sha512')\n","/*\n * A JavaScript implementation of the Secure Hash Algorithm, SHA-0, as defined\n * in FIPS PUB 180-1\n * This source code is derived from sha1.js of the same repository.\n * The difference between SHA-0 and SHA-1 is just a bitwise rotate left\n * operation was added.\n */\n\nvar inherits = require('inherits')\nvar Hash = require('./hash')\nvar Buffer = require('safe-buffer').Buffer\n\nvar K = [\n 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc | 0, 0xca62c1d6 | 0\n]\n\nvar W = new Array(80)\n\nfunction Sha () {\n this.init()\n this._w = W\n\n Hash.call(this, 64, 56)\n}\n\ninherits(Sha, Hash)\n\nSha.prototype.init = function () {\n this._a = 0x67452301\n this._b = 0xefcdab89\n this._c = 0x98badcfe\n this._d = 0x10325476\n this._e = 0xc3d2e1f0\n\n return this\n}\n\nfunction rotl5 (num) {\n return (num << 5) | (num >>> 27)\n}\n\nfunction rotl30 (num) {\n return (num << 30) | (num >>> 2)\n}\n\nfunction ft (s, b, c, d) {\n if (s === 0) return (b & c) | ((~b) & d)\n if (s === 2) return (b & c) | (b & d) | (c & d)\n return b ^ c ^ d\n}\n\nSha.prototype._update = function (M) {\n var W = this._w\n\n var a = this._a | 0\n var b = this._b | 0\n var c = this._c | 0\n var d = this._d | 0\n var e = this._e | 0\n\n for (var i = 0; i < 16; ++i) W[i] = M.readInt32BE(i * 4)\n for (; i < 80; ++i) W[i] = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]\n\n for (var j = 0; j < 80; ++j) {\n var s = ~~(j / 20)\n var t = (rotl5(a) + ft(s, b, c, d) + e + W[j] + K[s]) | 0\n\n e = d\n d = c\n c = rotl30(b)\n b = a\n a = t\n }\n\n this._a = (a + this._a) | 0\n this._b = (b + this._b) | 0\n this._c = (c + this._c) | 0\n this._d = (d + this._d) | 0\n this._e = (e + this._e) | 0\n}\n\nSha.prototype._hash = function () {\n var H = Buffer.allocUnsafe(20)\n\n H.writeInt32BE(this._a | 0, 0)\n H.writeInt32BE(this._b | 0, 4)\n H.writeInt32BE(this._c | 0, 8)\n H.writeInt32BE(this._d | 0, 12)\n H.writeInt32BE(this._e | 0, 16)\n\n return H\n}\n\nmodule.exports = Sha\n","/*\n * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined\n * in FIPS PUB 180-1\n * Version 2.1a Copyright Paul Johnston 2000 - 2002.\n * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet\n * Distributed under the BSD License\n * See http://pajhome.org.uk/crypt/md5 for details.\n */\n\nvar inherits = require('inherits')\nvar Hash = require('./hash')\nvar Buffer = require('safe-buffer').Buffer\n\nvar K = [\n 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc | 0, 0xca62c1d6 | 0\n]\n\nvar W = new Array(80)\n\nfunction Sha1 () {\n this.init()\n this._w = W\n\n Hash.call(this, 64, 56)\n}\n\ninherits(Sha1, Hash)\n\nSha1.prototype.init = function () {\n this._a = 0x67452301\n this._b = 0xefcdab89\n this._c = 0x98badcfe\n this._d = 0x10325476\n this._e = 0xc3d2e1f0\n\n return this\n}\n\nfunction rotl1 (num) {\n return (num << 1) | (num >>> 31)\n}\n\nfunction rotl5 (num) {\n return (num << 5) | (num >>> 27)\n}\n\nfunction rotl30 (num) {\n return (num << 30) | (num >>> 2)\n}\n\nfunction ft (s, b, c, d) {\n if (s === 0) return (b & c) | ((~b) & d)\n if (s === 2) return (b & c) | (b & d) | (c & d)\n return b ^ c ^ d\n}\n\nSha1.prototype._update = function (M) {\n var W = this._w\n\n var a = this._a | 0\n var b = this._b | 0\n var c = this._c | 0\n var d = this._d | 0\n var e = this._e | 0\n\n for (var i = 0; i < 16; ++i) W[i] = M.readInt32BE(i * 4)\n for (; i < 80; ++i) W[i] = rotl1(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16])\n\n for (var j = 0; j < 80; ++j) {\n var s = ~~(j / 20)\n var t = (rotl5(a) + ft(s, b, c, d) + e + W[j] + K[s]) | 0\n\n e = d\n d = c\n c = rotl30(b)\n b = a\n a = t\n }\n\n this._a = (a + this._a) | 0\n this._b = (b + this._b) | 0\n this._c = (c + this._c) | 0\n this._d = (d + this._d) | 0\n this._e = (e + this._e) | 0\n}\n\nSha1.prototype._hash = function () {\n var H = Buffer.allocUnsafe(20)\n\n H.writeInt32BE(this._a | 0, 0)\n H.writeInt32BE(this._b | 0, 4)\n H.writeInt32BE(this._c | 0, 8)\n H.writeInt32BE(this._d | 0, 12)\n H.writeInt32BE(this._e | 0, 16)\n\n return H\n}\n\nmodule.exports = Sha1\n","/**\n * A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined\n * in FIPS 180-2\n * Version 2.2-beta Copyright Angel Marin, Paul Johnston 2000 - 2009.\n * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet\n *\n */\n\nvar inherits = require('inherits')\nvar Sha256 = require('./sha256')\nvar Hash = require('./hash')\nvar Buffer = require('safe-buffer').Buffer\n\nvar W = new Array(64)\n\nfunction Sha224 () {\n this.init()\n\n this._w = W // new Array(64)\n\n Hash.call(this, 64, 56)\n}\n\ninherits(Sha224, Sha256)\n\nSha224.prototype.init = function () {\n this._a = 0xc1059ed8\n this._b = 0x367cd507\n this._c = 0x3070dd17\n this._d = 0xf70e5939\n this._e = 0xffc00b31\n this._f = 0x68581511\n this._g = 0x64f98fa7\n this._h = 0xbefa4fa4\n\n return this\n}\n\nSha224.prototype._hash = function () {\n var H = Buffer.allocUnsafe(28)\n\n H.writeInt32BE(this._a, 0)\n H.writeInt32BE(this._b, 4)\n H.writeInt32BE(this._c, 8)\n H.writeInt32BE(this._d, 12)\n H.writeInt32BE(this._e, 16)\n H.writeInt32BE(this._f, 20)\n H.writeInt32BE(this._g, 24)\n\n return H\n}\n\nmodule.exports = Sha224\n","/**\n * A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined\n * in FIPS 180-2\n * Version 2.2-beta Copyright Angel Marin, Paul Johnston 2000 - 2009.\n * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet\n *\n */\n\nvar inherits = require('inherits')\nvar Hash = require('./hash')\nvar Buffer = require('safe-buffer').Buffer\n\nvar K = [\n 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5,\n 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,\n 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3,\n 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,\n 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC,\n 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,\n 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7,\n 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,\n 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13,\n 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,\n 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3,\n 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,\n 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5,\n 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,\n 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208,\n 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2\n]\n\nvar W = new Array(64)\n\nfunction Sha256 () {\n this.init()\n\n this._w = W // new Array(64)\n\n Hash.call(this, 64, 56)\n}\n\ninherits(Sha256, Hash)\n\nSha256.prototype.init = function () {\n this._a = 0x6a09e667\n this._b = 0xbb67ae85\n this._c = 0x3c6ef372\n this._d = 0xa54ff53a\n this._e = 0x510e527f\n this._f = 0x9b05688c\n this._g = 0x1f83d9ab\n this._h = 0x5be0cd19\n\n return this\n}\n\nfunction ch (x, y, z) {\n return z ^ (x & (y ^ z))\n}\n\nfunction maj (x, y, z) {\n return (x & y) | (z & (x | y))\n}\n\nfunction sigma0 (x) {\n return (x >>> 2 | x << 30) ^ (x >>> 13 | x << 19) ^ (x >>> 22 | x << 10)\n}\n\nfunction sigma1 (x) {\n return (x >>> 6 | x << 26) ^ (x >>> 11 | x << 21) ^ (x >>> 25 | x << 7)\n}\n\nfunction gamma0 (x) {\n return (x >>> 7 | x << 25) ^ (x >>> 18 | x << 14) ^ (x >>> 3)\n}\n\nfunction gamma1 (x) {\n return (x >>> 17 | x << 15) ^ (x >>> 19 | x << 13) ^ (x >>> 10)\n}\n\nSha256.prototype._update = function (M) {\n var W = this._w\n\n var a = this._a | 0\n var b = this._b | 0\n var c = this._c | 0\n var d = this._d | 0\n var e = this._e | 0\n var f = this._f | 0\n var g = this._g | 0\n var h = this._h | 0\n\n for (var i = 0; i < 16; ++i) W[i] = M.readInt32BE(i * 4)\n for (; i < 64; ++i) W[i] = (gamma1(W[i - 2]) + W[i - 7] + gamma0(W[i - 15]) + W[i - 16]) | 0\n\n for (var j = 0; j < 64; ++j) {\n var T1 = (h + sigma1(e) + ch(e, f, g) + K[j] + W[j]) | 0\n var T2 = (sigma0(a) + maj(a, b, c)) | 0\n\n h = g\n g = f\n f = e\n e = (d + T1) | 0\n d = c\n c = b\n b = a\n a = (T1 + T2) | 0\n }\n\n this._a = (a + this._a) | 0\n this._b = (b + this._b) | 0\n this._c = (c + this._c) | 0\n this._d = (d + this._d) | 0\n this._e = (e + this._e) | 0\n this._f = (f + this._f) | 0\n this._g = (g + this._g) | 0\n this._h = (h + this._h) | 0\n}\n\nSha256.prototype._hash = function () {\n var H = Buffer.allocUnsafe(32)\n\n H.writeInt32BE(this._a, 0)\n H.writeInt32BE(this._b, 4)\n H.writeInt32BE(this._c, 8)\n H.writeInt32BE(this._d, 12)\n H.writeInt32BE(this._e, 16)\n H.writeInt32BE(this._f, 20)\n H.writeInt32BE(this._g, 24)\n H.writeInt32BE(this._h, 28)\n\n return H\n}\n\nmodule.exports = Sha256\n","var inherits = require('inherits')\nvar SHA512 = require('./sha512')\nvar Hash = require('./hash')\nvar Buffer = require('safe-buffer').Buffer\n\nvar W = new Array(160)\n\nfunction Sha384 () {\n this.init()\n this._w = W\n\n Hash.call(this, 128, 112)\n}\n\ninherits(Sha384, SHA512)\n\nSha384.prototype.init = function () {\n this._ah = 0xcbbb9d5d\n this._bh = 0x629a292a\n this._ch = 0x9159015a\n this._dh = 0x152fecd8\n this._eh = 0x67332667\n this._fh = 0x8eb44a87\n this._gh = 0xdb0c2e0d\n this._hh = 0x47b5481d\n\n this._al = 0xc1059ed8\n this._bl = 0x367cd507\n this._cl = 0x3070dd17\n this._dl = 0xf70e5939\n this._el = 0xffc00b31\n this._fl = 0x68581511\n this._gl = 0x64f98fa7\n this._hl = 0xbefa4fa4\n\n return this\n}\n\nSha384.prototype._hash = function () {\n var H = Buffer.allocUnsafe(48)\n\n function writeInt64BE (h, l, offset) {\n H.writeInt32BE(h, offset)\n H.writeInt32BE(l, offset + 4)\n }\n\n writeInt64BE(this._ah, this._al, 0)\n writeInt64BE(this._bh, this._bl, 8)\n writeInt64BE(this._ch, this._cl, 16)\n writeInt64BE(this._dh, this._dl, 24)\n writeInt64BE(this._eh, this._el, 32)\n writeInt64BE(this._fh, this._fl, 40)\n\n return H\n}\n\nmodule.exports = Sha384\n","var inherits = require('inherits')\nvar Hash = require('./hash')\nvar Buffer = require('safe-buffer').Buffer\n\nvar K = [\n 0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd,\n 0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc,\n 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019,\n 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118,\n 0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe,\n 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2,\n 0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1,\n 0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694,\n 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3,\n 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65,\n 0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483,\n 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5,\n 0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210,\n 0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4,\n 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725,\n 0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70,\n 0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926,\n 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df,\n 0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8,\n 0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b,\n 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001,\n 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30,\n 0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910,\n 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8,\n 0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53,\n 0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8,\n 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb,\n 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3,\n 0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60,\n 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec,\n 0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9,\n 0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b,\n 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207,\n 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178,\n 0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6,\n 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b,\n 0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493,\n 0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c,\n 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a,\n 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817\n]\n\nvar W = new Array(160)\n\nfunction Sha512 () {\n this.init()\n this._w = W\n\n Hash.call(this, 128, 112)\n}\n\ninherits(Sha512, Hash)\n\nSha512.prototype.init = function () {\n this._ah = 0x6a09e667\n this._bh = 0xbb67ae85\n this._ch = 0x3c6ef372\n this._dh = 0xa54ff53a\n this._eh = 0x510e527f\n this._fh = 0x9b05688c\n this._gh = 0x1f83d9ab\n this._hh = 0x5be0cd19\n\n this._al = 0xf3bcc908\n this._bl = 0x84caa73b\n this._cl = 0xfe94f82b\n this._dl = 0x5f1d36f1\n this._el = 0xade682d1\n this._fl = 0x2b3e6c1f\n this._gl = 0xfb41bd6b\n this._hl = 0x137e2179\n\n return this\n}\n\nfunction Ch (x, y, z) {\n return z ^ (x & (y ^ z))\n}\n\nfunction maj (x, y, z) {\n return (x & y) | (z & (x | y))\n}\n\nfunction sigma0 (x, xl) {\n return (x >>> 28 | xl << 4) ^ (xl >>> 2 | x << 30) ^ (xl >>> 7 | x << 25)\n}\n\nfunction sigma1 (x, xl) {\n return (x >>> 14 | xl << 18) ^ (x >>> 18 | xl << 14) ^ (xl >>> 9 | x << 23)\n}\n\nfunction Gamma0 (x, xl) {\n return (x >>> 1 | xl << 31) ^ (x >>> 8 | xl << 24) ^ (x >>> 7)\n}\n\nfunction Gamma0l (x, xl) {\n return (x >>> 1 | xl << 31) ^ (x >>> 8 | xl << 24) ^ (x >>> 7 | xl << 25)\n}\n\nfunction Gamma1 (x, xl) {\n return (x >>> 19 | xl << 13) ^ (xl >>> 29 | x << 3) ^ (x >>> 6)\n}\n\nfunction Gamma1l (x, xl) {\n return (x >>> 19 | xl << 13) ^ (xl >>> 29 | x << 3) ^ (x >>> 6 | xl << 26)\n}\n\nfunction getCarry (a, b) {\n return (a >>> 0) < (b >>> 0) ? 1 : 0\n}\n\nSha512.prototype._update = function (M) {\n var W = this._w\n\n var ah = this._ah | 0\n var bh = this._bh | 0\n var ch = this._ch | 0\n var dh = this._dh | 0\n var eh = this._eh | 0\n var fh = this._fh | 0\n var gh = this._gh | 0\n var hh = this._hh | 0\n\n var al = this._al | 0\n var bl = this._bl | 0\n var cl = this._cl | 0\n var dl = this._dl | 0\n var el = this._el | 0\n var fl = this._fl | 0\n var gl = this._gl | 0\n var hl = this._hl | 0\n\n for (var i = 0; i < 32; i += 2) {\n W[i] = M.readInt32BE(i * 4)\n W[i + 1] = M.readInt32BE(i * 4 + 4)\n }\n for (; i < 160; i += 2) {\n var xh = W[i - 15 * 2]\n var xl = W[i - 15 * 2 + 1]\n var gamma0 = Gamma0(xh, xl)\n var gamma0l = Gamma0l(xl, xh)\n\n xh = W[i - 2 * 2]\n xl = W[i - 2 * 2 + 1]\n var gamma1 = Gamma1(xh, xl)\n var gamma1l = Gamma1l(xl, xh)\n\n // W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16]\n var Wi7h = W[i - 7 * 2]\n var Wi7l = W[i - 7 * 2 + 1]\n\n var Wi16h = W[i - 16 * 2]\n var Wi16l = W[i - 16 * 2 + 1]\n\n var Wil = (gamma0l + Wi7l) | 0\n var Wih = (gamma0 + Wi7h + getCarry(Wil, gamma0l)) | 0\n Wil = (Wil + gamma1l) | 0\n Wih = (Wih + gamma1 + getCarry(Wil, gamma1l)) | 0\n Wil = (Wil + Wi16l) | 0\n Wih = (Wih + Wi16h + getCarry(Wil, Wi16l)) | 0\n\n W[i] = Wih\n W[i + 1] = Wil\n }\n\n for (var j = 0; j < 160; j += 2) {\n Wih = W[j]\n Wil = W[j + 1]\n\n var majh = maj(ah, bh, ch)\n var majl = maj(al, bl, cl)\n\n var sigma0h = sigma0(ah, al)\n var sigma0l = sigma0(al, ah)\n var sigma1h = sigma1(eh, el)\n var sigma1l = sigma1(el, eh)\n\n // t1 = h + sigma1 + ch + K[j] + W[j]\n var Kih = K[j]\n var Kil = K[j + 1]\n\n var chh = Ch(eh, fh, gh)\n var chl = Ch(el, fl, gl)\n\n var t1l = (hl + sigma1l) | 0\n var t1h = (hh + sigma1h + getCarry(t1l, hl)) | 0\n t1l = (t1l + chl) | 0\n t1h = (t1h + chh + getCarry(t1l, chl)) | 0\n t1l = (t1l + Kil) | 0\n t1h = (t1h + Kih + getCarry(t1l, Kil)) | 0\n t1l = (t1l + Wil) | 0\n t1h = (t1h + Wih + getCarry(t1l, Wil)) | 0\n\n // t2 = sigma0 + maj\n var t2l = (sigma0l + majl) | 0\n var t2h = (sigma0h + majh + getCarry(t2l, sigma0l)) | 0\n\n hh = gh\n hl = gl\n gh = fh\n gl = fl\n fh = eh\n fl = el\n el = (dl + t1l) | 0\n eh = (dh + t1h + getCarry(el, dl)) | 0\n dh = ch\n dl = cl\n ch = bh\n cl = bl\n bh = ah\n bl = al\n al = (t1l + t2l) | 0\n ah = (t1h + t2h + getCarry(al, t1l)) | 0\n }\n\n this._al = (this._al + al) | 0\n this._bl = (this._bl + bl) | 0\n this._cl = (this._cl + cl) | 0\n this._dl = (this._dl + dl) | 0\n this._el = (this._el + el) | 0\n this._fl = (this._fl + fl) | 0\n this._gl = (this._gl + gl) | 0\n this._hl = (this._hl + hl) | 0\n\n this._ah = (this._ah + ah + getCarry(this._al, al)) | 0\n this._bh = (this._bh + bh + getCarry(this._bl, bl)) | 0\n this._ch = (this._ch + ch + getCarry(this._cl, cl)) | 0\n this._dh = (this._dh + dh + getCarry(this._dl, dl)) | 0\n this._eh = (this._eh + eh + getCarry(this._el, el)) | 0\n this._fh = (this._fh + fh + getCarry(this._fl, fl)) | 0\n this._gh = (this._gh + gh + getCarry(this._gl, gl)) | 0\n this._hh = (this._hh + hh + getCarry(this._hl, hl)) | 0\n}\n\nSha512.prototype._hash = function () {\n var H = Buffer.allocUnsafe(64)\n\n function writeInt64BE (h, l, offset) {\n H.writeInt32BE(h, offset)\n H.writeInt32BE(l, offset + 4)\n }\n\n writeInt64BE(this._ah, this._al, 0)\n writeInt64BE(this._bh, this._bl, 8)\n writeInt64BE(this._ch, this._cl, 16)\n writeInt64BE(this._dh, this._dl, 24)\n writeInt64BE(this._eh, this._el, 32)\n writeInt64BE(this._fh, this._fl, 40)\n writeInt64BE(this._gh, this._gl, 48)\n writeInt64BE(this._hh, this._hl, 56)\n\n return H\n}\n\nmodule.exports = Sha512\n","// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nmodule.exports = Stream;\n\nvar EE = require('events').EventEmitter;\nvar inherits = require('inherits');\n\ninherits(Stream, EE);\nStream.Readable = require('readable-stream/lib/_stream_readable.js');\nStream.Writable = require('readable-stream/lib/_stream_writable.js');\nStream.Duplex = require('readable-stream/lib/_stream_duplex.js');\nStream.Transform = require('readable-stream/lib/_stream_transform.js');\nStream.PassThrough = require('readable-stream/lib/_stream_passthrough.js');\nStream.finished = require('readable-stream/lib/internal/streams/end-of-stream.js')\nStream.pipeline = require('readable-stream/lib/internal/streams/pipeline.js')\n\n// Backwards-compat with node 0.4.x\nStream.Stream = Stream;\n\n\n\n// old-style streams. Note that the pipe method (the only relevant\n// part of this class) is overridden in the Readable class.\n\nfunction Stream() {\n EE.call(this);\n}\n\nStream.prototype.pipe = function(dest, options) {\n var source = this;\n\n function ondata(chunk) {\n if (dest.writable) {\n if (false === dest.write(chunk) && source.pause) {\n source.pause();\n }\n }\n }\n\n source.on('data', ondata);\n\n function ondrain() {\n if (source.readable && source.resume) {\n source.resume();\n }\n }\n\n dest.on('drain', ondrain);\n\n // If the 'end' option is not supplied, dest.end() will be called when\n // source gets the 'end' or 'close' events. Only dest.end() once.\n if (!dest._isStdio && (!options || options.end !== false)) {\n source.on('end', onend);\n source.on('close', onclose);\n }\n\n var didOnEnd = false;\n function onend() {\n if (didOnEnd) return;\n didOnEnd = true;\n\n dest.end();\n }\n\n\n function onclose() {\n if (didOnEnd) return;\n didOnEnd = true;\n\n if (typeof dest.destroy === 'function') dest.destroy();\n }\n\n // don't leave dangling pipes when there are errors.\n function onerror(er) {\n cleanup();\n if (EE.listenerCount(this, 'error') === 0) {\n throw er; // Unhandled stream error in pipe.\n }\n }\n\n source.on('error', onerror);\n dest.on('error', onerror);\n\n // remove all the event listeners that were added.\n function cleanup() {\n source.removeListener('data', ondata);\n dest.removeListener('drain', ondrain);\n\n source.removeListener('end', onend);\n source.removeListener('close', onclose);\n\n source.removeListener('error', onerror);\n dest.removeListener('error', onerror);\n\n source.removeListener('end', cleanup);\n source.removeListener('close', cleanup);\n\n dest.removeListener('close', cleanup);\n }\n\n source.on('end', cleanup);\n source.on('close', cleanup);\n\n dest.on('close', cleanup);\n\n dest.emit('pipe', source);\n\n // Allow for unix-like usage: A.pipe(B).pipe(C)\n return dest;\n};\n","// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n'use strict';\n\n/**/\n\nvar Buffer = require('safe-buffer').Buffer;\n/**/\n\nvar isEncoding = Buffer.isEncoding || function (encoding) {\n encoding = '' + encoding;\n switch (encoding && encoding.toLowerCase()) {\n case 'hex':case 'utf8':case 'utf-8':case 'ascii':case 'binary':case 'base64':case 'ucs2':case 'ucs-2':case 'utf16le':case 'utf-16le':case 'raw':\n return true;\n default:\n return false;\n }\n};\n\nfunction _normalizeEncoding(enc) {\n if (!enc) return 'utf8';\n var retried;\n while (true) {\n switch (enc) {\n case 'utf8':\n case 'utf-8':\n return 'utf8';\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return 'utf16le';\n case 'latin1':\n case 'binary':\n return 'latin1';\n case 'base64':\n case 'ascii':\n case 'hex':\n return enc;\n default:\n if (retried) return; // undefined\n enc = ('' + enc).toLowerCase();\n retried = true;\n }\n }\n};\n\n// Do not cache `Buffer.isEncoding` when checking encoding names as some\n// modules monkey-patch it to support additional encodings\nfunction normalizeEncoding(enc) {\n var nenc = _normalizeEncoding(enc);\n if (typeof nenc !== 'string' && (Buffer.isEncoding === isEncoding || !isEncoding(enc))) throw new Error('Unknown encoding: ' + enc);\n return nenc || enc;\n}\n\n// StringDecoder provides an interface for efficiently splitting a series of\n// buffers into a series of JS strings without breaking apart multi-byte\n// characters.\nexports.StringDecoder = StringDecoder;\nfunction StringDecoder(encoding) {\n this.encoding = normalizeEncoding(encoding);\n var nb;\n switch (this.encoding) {\n case 'utf16le':\n this.text = utf16Text;\n this.end = utf16End;\n nb = 4;\n break;\n case 'utf8':\n this.fillLast = utf8FillLast;\n nb = 4;\n break;\n case 'base64':\n this.text = base64Text;\n this.end = base64End;\n nb = 3;\n break;\n default:\n this.write = simpleWrite;\n this.end = simpleEnd;\n return;\n }\n this.lastNeed = 0;\n this.lastTotal = 0;\n this.lastChar = Buffer.allocUnsafe(nb);\n}\n\nStringDecoder.prototype.write = function (buf) {\n if (buf.length === 0) return '';\n var r;\n var i;\n if (this.lastNeed) {\n r = this.fillLast(buf);\n if (r === undefined) return '';\n i = this.lastNeed;\n this.lastNeed = 0;\n } else {\n i = 0;\n }\n if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i);\n return r || '';\n};\n\nStringDecoder.prototype.end = utf8End;\n\n// Returns only complete characters in a Buffer\nStringDecoder.prototype.text = utf8Text;\n\n// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer\nStringDecoder.prototype.fillLast = function (buf) {\n if (this.lastNeed <= buf.length) {\n buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed);\n return this.lastChar.toString(this.encoding, 0, this.lastTotal);\n }\n buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length);\n this.lastNeed -= buf.length;\n};\n\n// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a\n// continuation byte. If an invalid byte is detected, -2 is returned.\nfunction utf8CheckByte(byte) {\n if (byte <= 0x7F) return 0;else if (byte >> 5 === 0x06) return 2;else if (byte >> 4 === 0x0E) return 3;else if (byte >> 3 === 0x1E) return 4;\n return byte >> 6 === 0x02 ? -1 : -2;\n}\n\n// Checks at most 3 bytes at the end of a Buffer in order to detect an\n// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4)\n// needed to complete the UTF-8 character (if applicable) are returned.\nfunction utf8CheckIncomplete(self, buf, i) {\n var j = buf.length - 1;\n if (j < i) return 0;\n var nb = utf8CheckByte(buf[j]);\n if (nb >= 0) {\n if (nb > 0) self.lastNeed = nb - 1;\n return nb;\n }\n if (--j < i || nb === -2) return 0;\n nb = utf8CheckByte(buf[j]);\n if (nb >= 0) {\n if (nb > 0) self.lastNeed = nb - 2;\n return nb;\n }\n if (--j < i || nb === -2) return 0;\n nb = utf8CheckByte(buf[j]);\n if (nb >= 0) {\n if (nb > 0) {\n if (nb === 2) nb = 0;else self.lastNeed = nb - 3;\n }\n return nb;\n }\n return 0;\n}\n\n// Validates as many continuation bytes for a multi-byte UTF-8 character as\n// needed or are available. If we see a non-continuation byte where we expect\n// one, we \"replace\" the validated continuation bytes we've seen so far with\n// a single UTF-8 replacement character ('\\ufffd'), to match v8's UTF-8 decoding\n// behavior. The continuation byte check is included three times in the case\n// where all of the continuation bytes for a character exist in the same buffer.\n// It is also done this way as a slight performance increase instead of using a\n// loop.\nfunction utf8CheckExtraBytes(self, buf, p) {\n if ((buf[0] & 0xC0) !== 0x80) {\n self.lastNeed = 0;\n return '\\ufffd';\n }\n if (self.lastNeed > 1 && buf.length > 1) {\n if ((buf[1] & 0xC0) !== 0x80) {\n self.lastNeed = 1;\n return '\\ufffd';\n }\n if (self.lastNeed > 2 && buf.length > 2) {\n if ((buf[2] & 0xC0) !== 0x80) {\n self.lastNeed = 2;\n return '\\ufffd';\n }\n }\n }\n}\n\n// Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer.\nfunction utf8FillLast(buf) {\n var p = this.lastTotal - this.lastNeed;\n var r = utf8CheckExtraBytes(this, buf, p);\n if (r !== undefined) return r;\n if (this.lastNeed <= buf.length) {\n buf.copy(this.lastChar, p, 0, this.lastNeed);\n return this.lastChar.toString(this.encoding, 0, this.lastTotal);\n }\n buf.copy(this.lastChar, p, 0, buf.length);\n this.lastNeed -= buf.length;\n}\n\n// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a\n// partial character, the character's bytes are buffered until the required\n// number of bytes are available.\nfunction utf8Text(buf, i) {\n var total = utf8CheckIncomplete(this, buf, i);\n if (!this.lastNeed) return buf.toString('utf8', i);\n this.lastTotal = total;\n var end = buf.length - (total - this.lastNeed);\n buf.copy(this.lastChar, 0, end);\n return buf.toString('utf8', i, end);\n}\n\n// For UTF-8, a replacement character is added when ending on a partial\n// character.\nfunction utf8End(buf) {\n var r = buf && buf.length ? this.write(buf) : '';\n if (this.lastNeed) return r + '\\ufffd';\n return r;\n}\n\n// UTF-16LE typically needs two bytes per character, but even if we have an even\n// number of bytes available, we need to check if we end on a leading/high\n// surrogate. In that case, we need to wait for the next two bytes in order to\n// decode the last character properly.\nfunction utf16Text(buf, i) {\n if ((buf.length - i) % 2 === 0) {\n var r = buf.toString('utf16le', i);\n if (r) {\n var c = r.charCodeAt(r.length - 1);\n if (c >= 0xD800 && c <= 0xDBFF) {\n this.lastNeed = 2;\n this.lastTotal = 4;\n this.lastChar[0] = buf[buf.length - 2];\n this.lastChar[1] = buf[buf.length - 1];\n return r.slice(0, -1);\n }\n }\n return r;\n }\n this.lastNeed = 1;\n this.lastTotal = 2;\n this.lastChar[0] = buf[buf.length - 1];\n return buf.toString('utf16le', i, buf.length - 1);\n}\n\n// For UTF-16LE we do not explicitly append special replacement characters if we\n// end on a partial character, we simply let v8 handle that.\nfunction utf16End(buf) {\n var r = buf && buf.length ? this.write(buf) : '';\n if (this.lastNeed) {\n var end = this.lastTotal - this.lastNeed;\n return r + this.lastChar.toString('utf16le', 0, end);\n }\n return r;\n}\n\nfunction base64Text(buf, i) {\n var n = (buf.length - i) % 3;\n if (n === 0) return buf.toString('base64', i);\n this.lastNeed = 3 - n;\n this.lastTotal = 3;\n if (n === 1) {\n this.lastChar[0] = buf[buf.length - 1];\n } else {\n this.lastChar[0] = buf[buf.length - 2];\n this.lastChar[1] = buf[buf.length - 1];\n }\n return buf.toString('base64', i, buf.length - n);\n}\n\nfunction base64End(buf) {\n var r = buf && buf.length ? this.write(buf) : '';\n if (this.lastNeed) return r + this.lastChar.toString('base64', 0, 3 - this.lastNeed);\n return r;\n}\n\n// Pass bytes on through for single-byte encodings (e.g. ascii, latin1, hex)\nfunction simpleWrite(buf) {\n return buf.toString(this.encoding);\n}\n\nfunction simpleEnd(buf) {\n return buf && buf.length ? this.write(buf) : '';\n}","\n/**\n * Module exports.\n */\n\nmodule.exports = deprecate;\n\n/**\n * Mark that a method should not be used.\n * Returns a modified function which warns once by default.\n *\n * If `localStorage.noDeprecation = true` is set, then it is a no-op.\n *\n * If `localStorage.throwDeprecation = true` is set, then deprecated functions\n * will throw an Error when invoked.\n *\n * If `localStorage.traceDeprecation = true` is set, then deprecated functions\n * will invoke `console.trace()` instead of `console.error()`.\n *\n * @param {Function} fn - the function to deprecate\n * @param {String} msg - the string to print to the console when `fn` is invoked\n * @returns {Function} a new \"deprecated\" version of `fn`\n * @api public\n */\n\nfunction deprecate (fn, msg) {\n if (config('noDeprecation')) {\n return fn;\n }\n\n var warned = false;\n function deprecated() {\n if (!warned) {\n if (config('throwDeprecation')) {\n throw new Error(msg);\n } else if (config('traceDeprecation')) {\n console.trace(msg);\n } else {\n console.warn(msg);\n }\n warned = true;\n }\n return fn.apply(this, arguments);\n }\n\n return deprecated;\n}\n\n/**\n * Checks `localStorage` for boolean values for the given `name`.\n *\n * @param {String} name\n * @returns {Boolean}\n * @api private\n */\n\nfunction config (name) {\n // accessing global.localStorage can trigger a DOMException in sandboxed iframes\n try {\n if (!global.localStorage) return false;\n } catch (_) {\n return false;\n }\n var val = global.localStorage[name];\n if (null == val) return false;\n return String(val).toLowerCase() === 'true';\n}\n","\nvar XML_CHARACTER_MAP = {\n '&': '&',\n '\"': '"',\n \"'\": ''',\n '<': '<',\n '>': '>'\n};\n\nfunction escapeForXML(string) {\n return string && string.replace\n ? string.replace(/([&\"<>'])/g, function(str, item) {\n return XML_CHARACTER_MAP[item];\n })\n : string;\n}\n\nmodule.exports = escapeForXML;\n","var escapeForXML = require('./escapeForXML');\nvar Stream = require('stream').Stream;\n\nvar DEFAULT_INDENT = ' ';\n\nfunction xml(input, options) {\n\n if (typeof options !== 'object') {\n options = {\n indent: options\n };\n }\n\n var stream = options.stream ? new Stream() : null,\n output = \"\",\n interrupted = false,\n indent = !options.indent ? ''\n : options.indent === true ? DEFAULT_INDENT\n : options.indent,\n instant = true;\n\n\n function delay (func) {\n if (!instant) {\n func();\n } else {\n process.nextTick(func);\n }\n }\n\n function append (interrupt, out) {\n if (out !== undefined) {\n output += out;\n }\n if (interrupt && !interrupted) {\n stream = stream || new Stream();\n interrupted = true;\n }\n if (interrupt && interrupted) {\n var data = output;\n delay(function () { stream.emit('data', data) });\n output = \"\";\n }\n }\n\n function add (value, last) {\n format(append, resolve(value, indent, indent ? 1 : 0), last);\n }\n\n function end() {\n if (stream) {\n var data = output;\n delay(function () {\n stream.emit('data', data);\n stream.emit('end');\n stream.readable = false;\n stream.emit('close');\n });\n }\n }\n\n function addXmlDeclaration(declaration) {\n var encoding = declaration.encoding || 'UTF-8',\n attr = { version: '1.0', encoding: encoding };\n\n if (declaration.standalone) {\n attr.standalone = declaration.standalone\n }\n\n add({'?xml': { _attr: attr } });\n output = output.replace('/>', '?>');\n }\n\n // disable delay delayed\n delay(function () { instant = false });\n\n if (options.declaration) {\n addXmlDeclaration(options.declaration);\n }\n\n if (input && input.forEach) {\n input.forEach(function (value, i) {\n var last;\n if (i + 1 === input.length)\n last = end;\n add(value, last);\n });\n } else {\n add(input, end);\n }\n\n if (stream) {\n stream.readable = true;\n return stream;\n }\n return output;\n}\n\nfunction element (/*input, โ€ฆ*/) {\n var input = Array.prototype.slice.call(arguments),\n self = {\n _elem: resolve(input)\n };\n\n self.push = function (input) {\n if (!this.append) {\n throw new Error(\"not assigned to a parent!\");\n }\n var that = this;\n var indent = this._elem.indent;\n format(this.append, resolve(\n input, indent, this._elem.icount + (indent ? 1 : 0)),\n function () { that.append(true) });\n };\n\n self.close = function (input) {\n if (input !== undefined) {\n this.push(input);\n }\n if (this.end) {\n this.end();\n }\n };\n\n return self;\n}\n\nfunction create_indent(character, count) {\n return (new Array(count || 0).join(character || ''))\n}\n\nfunction resolve(data, indent, indent_count) {\n indent_count = indent_count || 0;\n var indent_spaces = create_indent(indent, indent_count);\n var name;\n var values = data;\n var interrupt = false;\n\n if (typeof data === 'object') {\n var keys = Object.keys(data);\n name = keys[0];\n values = data[name];\n\n if (values && values._elem) {\n values._elem.name = name;\n values._elem.icount = indent_count;\n values._elem.indent = indent;\n values._elem.indents = indent_spaces;\n values._elem.interrupt = values;\n return values._elem;\n }\n }\n\n var attributes = [],\n content = [];\n\n var isStringContent;\n\n function get_attributes(obj){\n var keys = Object.keys(obj);\n keys.forEach(function(key){\n attributes.push(attribute(key, obj[key]));\n });\n }\n\n switch(typeof values) {\n case 'object':\n if (values === null) break;\n\n if (values._attr) {\n get_attributes(values._attr);\n }\n\n if (values._cdata) {\n content.push(\n ('/g, ']]]]>') + ']]>'\n );\n }\n\n if (values.forEach) {\n isStringContent = false;\n content.push('');\n values.forEach(function(value) {\n if (typeof value == 'object') {\n var _name = Object.keys(value)[0];\n\n if (_name == '_attr') {\n get_attributes(value._attr);\n } else {\n content.push(resolve(\n value, indent, indent_count + 1));\n }\n } else {\n //string\n content.pop();\n isStringContent=true;\n content.push(escapeForXML(value));\n }\n\n });\n if (!isStringContent) {\n content.push('');\n }\n }\n break;\n\n default:\n //string\n content.push(escapeForXML(values));\n\n }\n\n return {\n name: name,\n interrupt: interrupt,\n attributes: attributes,\n content: content,\n icount: indent_count,\n indents: indent_spaces,\n indent: indent\n };\n}\n\nfunction format(append, elem, end) {\n\n if (typeof elem != 'object') {\n return append(false, elem);\n }\n\n var len = elem.interrupt ? 1 : elem.content.length;\n\n function proceed () {\n while (elem.content.length) {\n var value = elem.content.shift();\n\n if (value === undefined) continue;\n if (interrupt(value)) return;\n\n format(append, value);\n }\n\n append(false, (len > 1 ? elem.indents : '')\n + (elem.name ? '' : '')\n + (elem.indent && !end ? '\\n' : ''));\n\n if (end) {\n end();\n }\n }\n\n function interrupt(value) {\n if (value.interrupt) {\n value.interrupt.append = append;\n value.interrupt.end = proceed;\n value.interrupt = false;\n append(true);\n return true;\n }\n return false;\n }\n\n append(false, elem.indents\n + (elem.name ? '<' + elem.name : '')\n + (elem.attributes.length ? ' ' + elem.attributes.join(' ') : '')\n + (len ? (elem.name ? '>' : '') : (elem.name ? '/>' : ''))\n + (elem.indent && len > 1 ? '\\n' : ''));\n\n if (!len) {\n return append(false, elem.indent ? '\\n' : '');\n }\n\n if (!interrupt(elem)) {\n proceed();\n }\n}\n\nfunction attribute(key, value) {\n return key + '=' + '\"' + escapeForXML(value) + '\"';\n}\n\nmodule.exports = xml;\nmodule.exports.element = module.exports.Element = element;\n","var map = {\n\t\"./all.js\": 5308,\n\t\"./auth/actions.js\": 5812,\n\t\"./auth/configs-extensions/wrap-actions.js\": 3779,\n\t\"./auth/index.js\": 3705,\n\t\"./auth/reducers.js\": 3962,\n\t\"./auth/selectors.js\": 35,\n\t\"./auth/spec-extensions/wrap-actions.js\": 489,\n\t\"./auth/wrap-actions.js\": 2849,\n\t\"./configs/actions.js\": 714,\n\t\"./configs/helpers.js\": 2256,\n\t\"./configs/index.js\": 6709,\n\t\"./configs/reducers.js\": 7743,\n\t\"./configs/selectors.js\": 9018,\n\t\"./configs/spec-actions.js\": 2698,\n\t\"./deep-linking/helpers.js\": 1970,\n\t\"./deep-linking/index.js\": 4980,\n\t\"./deep-linking/layout.js\": 5858,\n\t\"./deep-linking/operation-tag-wrapper.jsx\": 4584,\n\t\"./deep-linking/operation-wrapper.jsx\": 877,\n\t\"./download-url.js\": 8011,\n\t\"./err/actions.js\": 4966,\n\t\"./err/error-transformers/hook.js\": 6808,\n\t\"./err/error-transformers/transformers/not-of-type.js\": 2392,\n\t\"./err/error-transformers/transformers/parameter-oneof.js\": 1835,\n\t\"./err/index.js\": 7793,\n\t\"./err/reducers.js\": 3527,\n\t\"./err/selectors.js\": 7667,\n\t\"./filter/index.js\": 9978,\n\t\"./filter/opsFilter.js\": 4309,\n\t\"./json-schema-2020-12/components/Accordion/Accordion.jsx\": 7349,\n\t\"./json-schema-2020-12/components/ExpandDeepButton/ExpandDeepButton.jsx\": 6867,\n\t\"./json-schema-2020-12/components/JSONSchema/JSONSchema.jsx\": 2675,\n\t\"./json-schema-2020-12/components/icons/ChevronRight.jsx\": 2260,\n\t\"./json-schema-2020-12/components/keywords/$anchor.jsx\": 4922,\n\t\"./json-schema-2020-12/components/keywords/$comment.jsx\": 4685,\n\t\"./json-schema-2020-12/components/keywords/$defs.jsx\": 6418,\n\t\"./json-schema-2020-12/components/keywords/$dynamicAnchor.jsx\": 1338,\n\t\"./json-schema-2020-12/components/keywords/$dynamicRef.jsx\": 7655,\n\t\"./json-schema-2020-12/components/keywords/$id.jsx\": 3460,\n\t\"./json-schema-2020-12/components/keywords/$ref.jsx\": 2348,\n\t\"./json-schema-2020-12/components/keywords/$schema.jsx\": 9359,\n\t\"./json-schema-2020-12/components/keywords/$vocabulary/$vocabulary.jsx\": 7568,\n\t\"./json-schema-2020-12/components/keywords/AdditionalProperties.jsx\": 5253,\n\t\"./json-schema-2020-12/components/keywords/AllOf.jsx\": 6457,\n\t\"./json-schema-2020-12/components/keywords/AnyOf.jsx\": 8776,\n\t\"./json-schema-2020-12/components/keywords/Const.jsx\": 7308,\n\t\"./json-schema-2020-12/components/keywords/Constraint/Constraint.jsx\": 9956,\n\t\"./json-schema-2020-12/components/keywords/Contains.jsx\": 8993,\n\t\"./json-schema-2020-12/components/keywords/ContentSchema.jsx\": 3484,\n\t\"./json-schema-2020-12/components/keywords/Default.jsx\": 5148,\n\t\"./json-schema-2020-12/components/keywords/DependentRequired/DependentRequired.jsx\": 4539,\n\t\"./json-schema-2020-12/components/keywords/DependentSchemas.jsx\": 6076,\n\t\"./json-schema-2020-12/components/keywords/Deprecated.jsx\": 6661,\n\t\"./json-schema-2020-12/components/keywords/Description/Description.jsx\": 9446,\n\t\"./json-schema-2020-12/components/keywords/Else.jsx\": 7207,\n\t\"./json-schema-2020-12/components/keywords/Enum/Enum.jsx\": 1805,\n\t\"./json-schema-2020-12/components/keywords/If.jsx\": 487,\n\t\"./json-schema-2020-12/components/keywords/Items.jsx\": 9206,\n\t\"./json-schema-2020-12/components/keywords/Not.jsx\": 5174,\n\t\"./json-schema-2020-12/components/keywords/OneOf.jsx\": 3834,\n\t\"./json-schema-2020-12/components/keywords/PatternProperties/PatternProperties.jsx\": 6746,\n\t\"./json-schema-2020-12/components/keywords/PrefixItems.jsx\": 3971,\n\t\"./json-schema-2020-12/components/keywords/Properties/Properties.jsx\": 5472,\n\t\"./json-schema-2020-12/components/keywords/PropertyNames.jsx\": 2338,\n\t\"./json-schema-2020-12/components/keywords/ReadOnly.jsx\": 6456,\n\t\"./json-schema-2020-12/components/keywords/Then.jsx\": 7401,\n\t\"./json-schema-2020-12/components/keywords/Title/Title.jsx\": 8137,\n\t\"./json-schema-2020-12/components/keywords/Type.jsx\": 2285,\n\t\"./json-schema-2020-12/components/keywords/UnevaluatedItems.jsx\": 5828,\n\t\"./json-schema-2020-12/components/keywords/UnevaluatedProperties.jsx\": 6907,\n\t\"./json-schema-2020-12/components/keywords/WriteOnly.jsx\": 5789,\n\t\"./json-schema-2020-12/context.js\": 9006,\n\t\"./json-schema-2020-12/fn.js\": 4121,\n\t\"./json-schema-2020-12/hoc.jsx\": 5077,\n\t\"./json-schema-2020-12/hooks.js\": 2603,\n\t\"./json-schema-2020-12/index.js\": 7139,\n\t\"./json-schema-2020-12/prop-types.js\": 6648,\n\t\"./json-schema-2020-12/samples-extensions/fn/api/encoderAPI.js\": 9507,\n\t\"./json-schema-2020-12/samples-extensions/fn/api/formatAPI.js\": 2906,\n\t\"./json-schema-2020-12/samples-extensions/fn/api/mediaTypeAPI.js\": 537,\n\t\"./json-schema-2020-12/samples-extensions/fn/class/EncoderRegistry.js\": 674,\n\t\"./json-schema-2020-12/samples-extensions/fn/class/MediaTypeRegistry.js\": 3782,\n\t\"./json-schema-2020-12/samples-extensions/fn/class/Registry.js\": 4215,\n\t\"./json-schema-2020-12/samples-extensions/fn/core/constants.js\": 8338,\n\t\"./json-schema-2020-12/samples-extensions/fn/core/example.js\": 3783,\n\t\"./json-schema-2020-12/samples-extensions/fn/core/merge.js\": 7078,\n\t\"./json-schema-2020-12/samples-extensions/fn/core/predicates.js\": 3084,\n\t\"./json-schema-2020-12/samples-extensions/fn/core/random.js\": 5202,\n\t\"./json-schema-2020-12/samples-extensions/fn/core/type.js\": 6276,\n\t\"./json-schema-2020-12/samples-extensions/fn/core/utils.js\": 9346,\n\t\"./json-schema-2020-12/samples-extensions/fn/encoders/7bit.js\": 1433,\n\t\"./json-schema-2020-12/samples-extensions/fn/encoders/8bit.js\": 8509,\n\t\"./json-schema-2020-12/samples-extensions/fn/encoders/base16.js\": 5709,\n\t\"./json-schema-2020-12/samples-extensions/fn/encoders/base32.js\": 4180,\n\t\"./json-schema-2020-12/samples-extensions/fn/encoders/base64.js\": 1967,\n\t\"./json-schema-2020-12/samples-extensions/fn/encoders/binary.js\": 4366,\n\t\"./json-schema-2020-12/samples-extensions/fn/encoders/quoted-printable.js\": 5037,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/date-time.js\": 4045,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/date.js\": 1456,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/double.js\": 560,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/duration.js\": 4299,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/email.js\": 3981,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/float.js\": 1890,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/hostname.js\": 9375,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/idn-email.js\": 4518,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/idn-hostname.js\": 273,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/int32.js\": 7864,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/int64.js\": 1726,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/ipv4.js\": 8793,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/ipv6.js\": 8269,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/iri-reference.js\": 5693,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/iri.js\": 3080,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/json-pointer.js\": 7856,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/media-types/application.js\": 5652,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/media-types/audio.js\": 4342,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/media-types/image.js\": 6724,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/media-types/text.js\": 5378,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/media-types/video.js\": 2974,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/password.js\": 3393,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/regex.js\": 4335,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/relative-json-pointer.js\": 375,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/time.js\": 5243,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/uri-reference.js\": 4692,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/uri-template.js\": 3829,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/uri.js\": 2978,\n\t\"./json-schema-2020-12/samples-extensions/fn/generators/uuid.js\": 8859,\n\t\"./json-schema-2020-12/samples-extensions/fn/index.js\": 8591,\n\t\"./json-schema-2020-12/samples-extensions/fn/main.js\": 4277,\n\t\"./json-schema-2020-12/samples-extensions/fn/types/array.js\": 8262,\n\t\"./json-schema-2020-12/samples-extensions/fn/types/boolean.js\": 4108,\n\t\"./json-schema-2020-12/samples-extensions/fn/types/index.js\": 3273,\n\t\"./json-schema-2020-12/samples-extensions/fn/types/integer.js\": 8864,\n\t\"./json-schema-2020-12/samples-extensions/fn/types/null.js\": 853,\n\t\"./json-schema-2020-12/samples-extensions/fn/types/number.js\": 7742,\n\t\"./json-schema-2020-12/samples-extensions/fn/types/object.js\": 6852,\n\t\"./json-schema-2020-12/samples-extensions/fn/types/string.js\": 4522,\n\t\"./layout/actions.js\": 5474,\n\t\"./layout/index.js\": 6821,\n\t\"./layout/reducers.js\": 5672,\n\t\"./layout/selectors.js\": 4400,\n\t\"./layout/spec-extensions/wrap-selector.js\": 8989,\n\t\"./logs/index.js\": 9150,\n\t\"./oas3/actions.js\": 7002,\n\t\"./oas3/auth-extensions/wrap-selectors.js\": 3723,\n\t\"./oas3/components/callbacks.jsx\": 3427,\n\t\"./oas3/components/http-auth.jsx\": 6775,\n\t\"./oas3/components/index.js\": 6467,\n\t\"./oas3/components/operation-link.jsx\": 5757,\n\t\"./oas3/components/operation-servers.jsx\": 6796,\n\t\"./oas3/components/request-body-editor.jsx\": 5327,\n\t\"./oas3/components/request-body.jsx\": 2458,\n\t\"./oas3/components/servers-container.jsx\": 9928,\n\t\"./oas3/components/servers.jsx\": 6617,\n\t\"./oas3/helpers.jsx\": 7779,\n\t\"./oas3/index.js\": 7451,\n\t\"./oas3/reducers.js\": 2109,\n\t\"./oas3/selectors.js\": 5065,\n\t\"./oas3/spec-extensions/selectors.js\": 1741,\n\t\"./oas3/spec-extensions/wrap-selectors.js\": 2044,\n\t\"./oas3/wrap-components/auth-item.jsx\": 356,\n\t\"./oas3/wrap-components/index.js\": 7761,\n\t\"./oas3/wrap-components/json-schema-string.jsx\": 287,\n\t\"./oas3/wrap-components/markdown.jsx\": 2460,\n\t\"./oas3/wrap-components/model.jsx\": 3499,\n\t\"./oas3/wrap-components/online-validator-badge.js\": 58,\n\t\"./oas3/wrap-components/version-stamp.jsx\": 9487,\n\t\"./oas31/after-load.js\": 7754,\n\t\"./oas31/components/contact.jsx\": 9503,\n\t\"./oas31/components/info.jsx\": 6133,\n\t\"./oas31/components/json-schema-dialect.jsx\": 2562,\n\t\"./oas31/components/license.jsx\": 1876,\n\t\"./oas31/components/model/model.jsx\": 2718,\n\t\"./oas31/components/models/models.jsx\": 263,\n\t\"./oas31/components/version-pragma-filter.jsx\": 3429,\n\t\"./oas31/components/webhooks.jsx\": 9508,\n\t\"./oas31/fn.js\": 4380,\n\t\"./oas31/index.js\": 9806,\n\t\"./oas31/json-schema-2020-12-extensions/components/keywords/Description.jsx\": 5989,\n\t\"./oas31/json-schema-2020-12-extensions/components/keywords/Discriminator/Discriminator.jsx\": 9525,\n\t\"./oas31/json-schema-2020-12-extensions/components/keywords/Discriminator/DiscriminatorMapping.jsx\": 7749,\n\t\"./oas31/json-schema-2020-12-extensions/components/keywords/Example.jsx\": 9450,\n\t\"./oas31/json-schema-2020-12-extensions/components/keywords/ExternalDocs.jsx\": 5324,\n\t\"./oas31/json-schema-2020-12-extensions/components/keywords/Properties.jsx\": 9023,\n\t\"./oas31/json-schema-2020-12-extensions/components/keywords/Xml.jsx\": 3995,\n\t\"./oas31/json-schema-2020-12-extensions/fn.js\": 5800,\n\t\"./oas31/json-schema-2020-12-extensions/wrap-components/keywords/Default.jsx\": 4951,\n\t\"./oas31/json-schema-2020-12-extensions/wrap-components/keywords/Description.jsx\": 809,\n\t\"./oas31/json-schema-2020-12-extensions/wrap-components/keywords/Properties.jsx\": 7536,\n\t\"./oas31/selectors.js\": 4280,\n\t\"./oas31/spec-extensions/selectors.js\": 9305,\n\t\"./oas31/spec-extensions/wrap-selectors.js\": 2884,\n\t\"./oas31/wrap-components/contact.jsx\": 7423,\n\t\"./oas31/wrap-components/info.jsx\": 284,\n\t\"./oas31/wrap-components/license.jsx\": 6608,\n\t\"./oas31/wrap-components/model.jsx\": 7042,\n\t\"./oas31/wrap-components/models.jsx\": 2914,\n\t\"./oas31/wrap-components/version-pragma-filter.jsx\": 1434,\n\t\"./oas31/wrap-components/version-stamp.jsx\": 1122,\n\t\"./on-complete/index.js\": 8560,\n\t\"./request-snippets/fn.js\": 4624,\n\t\"./request-snippets/index.js\": 6575,\n\t\"./request-snippets/request-snippets.jsx\": 4206,\n\t\"./request-snippets/selectors.js\": 4669,\n\t\"./safe-render/components/error-boundary.jsx\": 6195,\n\t\"./safe-render/components/fallback.jsx\": 9403,\n\t\"./safe-render/fn.jsx\": 6189,\n\t\"./safe-render/index.js\": 8102,\n\t\"./samples/fn/get-json-sample-schema.js\": 2846,\n\t\"./samples/fn/get-sample-schema.js\": 6132,\n\t\"./samples/fn/get-xml-sample-schema.js\": 1169,\n\t\"./samples/fn/get-yaml-sample-schema.js\": 9431,\n\t\"./samples/fn/index.js\": 9812,\n\t\"./samples/index.js\": 8883,\n\t\"./spec/actions.js\": 7612,\n\t\"./spec/index.js\": 7038,\n\t\"./spec/reducers.js\": 32,\n\t\"./spec/selectors.js\": 3881,\n\t\"./spec/wrap-actions.js\": 7508,\n\t\"./swagger-js/configs-wrap-actions.js\": 4852,\n\t\"./swagger-js/index.js\": 9430,\n\t\"./util/index.js\": 8525,\n\t\"./view/fn.js\": 8347,\n\t\"./view/index.js\": 3420,\n\t\"./view/root-injects.jsx\": 3934,\n\t\"core/plugins/all.js\": 5308,\n\t\"core/plugins/auth/actions.js\": 5812,\n\t\"core/plugins/auth/configs-extensions/wrap-actions.js\": 3779,\n\t\"core/plugins/auth/index.js\": 3705,\n\t\"core/plugins/auth/reducers.js\": 3962,\n\t\"core/plugins/auth/selectors.js\": 35,\n\t\"core/plugins/auth/spec-extensions/wrap-actions.js\": 489,\n\t\"core/plugins/auth/wrap-actions.js\": 2849,\n\t\"core/plugins/configs/actions.js\": 714,\n\t\"core/plugins/configs/helpers.js\": 2256,\n\t\"core/plugins/configs/index.js\": 6709,\n\t\"core/plugins/configs/reducers.js\": 7743,\n\t\"core/plugins/configs/selectors.js\": 9018,\n\t\"core/plugins/configs/spec-actions.js\": 2698,\n\t\"core/plugins/deep-linking/helpers.js\": 1970,\n\t\"core/plugins/deep-linking/index.js\": 4980,\n\t\"core/plugins/deep-linking/layout.js\": 5858,\n\t\"core/plugins/deep-linking/operation-tag-wrapper.jsx\": 4584,\n\t\"core/plugins/deep-linking/operation-wrapper.jsx\": 877,\n\t\"core/plugins/download-url.js\": 8011,\n\t\"core/plugins/err/actions.js\": 4966,\n\t\"core/plugins/err/error-transformers/hook.js\": 6808,\n\t\"core/plugins/err/error-transformers/transformers/not-of-type.js\": 2392,\n\t\"core/plugins/err/error-transformers/transformers/parameter-oneof.js\": 1835,\n\t\"core/plugins/err/index.js\": 7793,\n\t\"core/plugins/err/reducers.js\": 3527,\n\t\"core/plugins/err/selectors.js\": 7667,\n\t\"core/plugins/filter/index.js\": 9978,\n\t\"core/plugins/filter/opsFilter.js\": 4309,\n\t\"core/plugins/json-schema-2020-12/components/Accordion/Accordion.jsx\": 7349,\n\t\"core/plugins/json-schema-2020-12/components/ExpandDeepButton/ExpandDeepButton.jsx\": 6867,\n\t\"core/plugins/json-schema-2020-12/components/JSONSchema/JSONSchema.jsx\": 2675,\n\t\"core/plugins/json-schema-2020-12/components/icons/ChevronRight.jsx\": 2260,\n\t\"core/plugins/json-schema-2020-12/components/keywords/$anchor.jsx\": 4922,\n\t\"core/plugins/json-schema-2020-12/components/keywords/$comment.jsx\": 4685,\n\t\"core/plugins/json-schema-2020-12/components/keywords/$defs.jsx\": 6418,\n\t\"core/plugins/json-schema-2020-12/components/keywords/$dynamicAnchor.jsx\": 1338,\n\t\"core/plugins/json-schema-2020-12/components/keywords/$dynamicRef.jsx\": 7655,\n\t\"core/plugins/json-schema-2020-12/components/keywords/$id.jsx\": 3460,\n\t\"core/plugins/json-schema-2020-12/components/keywords/$ref.jsx\": 2348,\n\t\"core/plugins/json-schema-2020-12/components/keywords/$schema.jsx\": 9359,\n\t\"core/plugins/json-schema-2020-12/components/keywords/$vocabulary/$vocabulary.jsx\": 7568,\n\t\"core/plugins/json-schema-2020-12/components/keywords/AdditionalProperties.jsx\": 5253,\n\t\"core/plugins/json-schema-2020-12/components/keywords/AllOf.jsx\": 6457,\n\t\"core/plugins/json-schema-2020-12/components/keywords/AnyOf.jsx\": 8776,\n\t\"core/plugins/json-schema-2020-12/components/keywords/Const.jsx\": 7308,\n\t\"core/plugins/json-schema-2020-12/components/keywords/Constraint/Constraint.jsx\": 9956,\n\t\"core/plugins/json-schema-2020-12/components/keywords/Contains.jsx\": 8993,\n\t\"core/plugins/json-schema-2020-12/components/keywords/ContentSchema.jsx\": 3484,\n\t\"core/plugins/json-schema-2020-12/components/keywords/Default.jsx\": 5148,\n\t\"core/plugins/json-schema-2020-12/components/keywords/DependentRequired/DependentRequired.jsx\": 4539,\n\t\"core/plugins/json-schema-2020-12/components/keywords/DependentSchemas.jsx\": 6076,\n\t\"core/plugins/json-schema-2020-12/components/keywords/Deprecated.jsx\": 6661,\n\t\"core/plugins/json-schema-2020-12/components/keywords/Description/Description.jsx\": 9446,\n\t\"core/plugins/json-schema-2020-12/components/keywords/Else.jsx\": 7207,\n\t\"core/plugins/json-schema-2020-12/components/keywords/Enum/Enum.jsx\": 1805,\n\t\"core/plugins/json-schema-2020-12/components/keywords/If.jsx\": 487,\n\t\"core/plugins/json-schema-2020-12/components/keywords/Items.jsx\": 9206,\n\t\"core/plugins/json-schema-2020-12/components/keywords/Not.jsx\": 5174,\n\t\"core/plugins/json-schema-2020-12/components/keywords/OneOf.jsx\": 3834,\n\t\"core/plugins/json-schema-2020-12/components/keywords/PatternProperties/PatternProperties.jsx\": 6746,\n\t\"core/plugins/json-schema-2020-12/components/keywords/PrefixItems.jsx\": 3971,\n\t\"core/plugins/json-schema-2020-12/components/keywords/Properties/Properties.jsx\": 5472,\n\t\"core/plugins/json-schema-2020-12/components/keywords/PropertyNames.jsx\": 2338,\n\t\"core/plugins/json-schema-2020-12/components/keywords/ReadOnly.jsx\": 6456,\n\t\"core/plugins/json-schema-2020-12/components/keywords/Then.jsx\": 7401,\n\t\"core/plugins/json-schema-2020-12/components/keywords/Title/Title.jsx\": 8137,\n\t\"core/plugins/json-schema-2020-12/components/keywords/Type.jsx\": 2285,\n\t\"core/plugins/json-schema-2020-12/components/keywords/UnevaluatedItems.jsx\": 5828,\n\t\"core/plugins/json-schema-2020-12/components/keywords/UnevaluatedProperties.jsx\": 6907,\n\t\"core/plugins/json-schema-2020-12/components/keywords/WriteOnly.jsx\": 5789,\n\t\"core/plugins/json-schema-2020-12/context.js\": 9006,\n\t\"core/plugins/json-schema-2020-12/fn.js\": 4121,\n\t\"core/plugins/json-schema-2020-12/hoc.jsx\": 5077,\n\t\"core/plugins/json-schema-2020-12/hooks.js\": 2603,\n\t\"core/plugins/json-schema-2020-12/index.js\": 7139,\n\t\"core/plugins/json-schema-2020-12/prop-types.js\": 6648,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/api/encoderAPI.js\": 9507,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/api/formatAPI.js\": 2906,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/api/mediaTypeAPI.js\": 537,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/class/EncoderRegistry.js\": 674,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/class/MediaTypeRegistry.js\": 3782,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/class/Registry.js\": 4215,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/core/constants.js\": 8338,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/core/example.js\": 3783,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/core/merge.js\": 7078,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/core/predicates.js\": 3084,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/core/random.js\": 5202,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/core/type.js\": 6276,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/core/utils.js\": 9346,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/7bit.js\": 1433,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/8bit.js\": 8509,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/base16.js\": 5709,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/base32.js\": 4180,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/base64.js\": 1967,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/binary.js\": 4366,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/encoders/quoted-printable.js\": 5037,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/date-time.js\": 4045,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/date.js\": 1456,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/double.js\": 560,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/duration.js\": 4299,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/email.js\": 3981,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/float.js\": 1890,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/hostname.js\": 9375,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/idn-email.js\": 4518,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/idn-hostname.js\": 273,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/int32.js\": 7864,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/int64.js\": 1726,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/ipv4.js\": 8793,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/ipv6.js\": 8269,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/iri-reference.js\": 5693,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/iri.js\": 3080,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/json-pointer.js\": 7856,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/application.js\": 5652,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/audio.js\": 4342,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/image.js\": 6724,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/text.js\": 5378,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/media-types/video.js\": 2974,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/password.js\": 3393,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/regex.js\": 4335,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/relative-json-pointer.js\": 375,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/time.js\": 5243,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/uri-reference.js\": 4692,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/uri-template.js\": 3829,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/uri.js\": 2978,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/generators/uuid.js\": 8859,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/index.js\": 8591,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/main.js\": 4277,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/types/array.js\": 8262,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/types/boolean.js\": 4108,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/types/index.js\": 3273,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/types/integer.js\": 8864,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/types/null.js\": 853,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/types/number.js\": 7742,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/types/object.js\": 6852,\n\t\"core/plugins/json-schema-2020-12/samples-extensions/fn/types/string.js\": 4522,\n\t\"core/plugins/layout/actions.js\": 5474,\n\t\"core/plugins/layout/index.js\": 6821,\n\t\"core/plugins/layout/reducers.js\": 5672,\n\t\"core/plugins/layout/selectors.js\": 4400,\n\t\"core/plugins/layout/spec-extensions/wrap-selector.js\": 8989,\n\t\"core/plugins/logs/index.js\": 9150,\n\t\"core/plugins/oas3/actions.js\": 7002,\n\t\"core/plugins/oas3/auth-extensions/wrap-selectors.js\": 3723,\n\t\"core/plugins/oas3/components/callbacks.jsx\": 3427,\n\t\"core/plugins/oas3/components/http-auth.jsx\": 6775,\n\t\"core/plugins/oas3/components/index.js\": 6467,\n\t\"core/plugins/oas3/components/operation-link.jsx\": 5757,\n\t\"core/plugins/oas3/components/operation-servers.jsx\": 6796,\n\t\"core/plugins/oas3/components/request-body-editor.jsx\": 5327,\n\t\"core/plugins/oas3/components/request-body.jsx\": 2458,\n\t\"core/plugins/oas3/components/servers-container.jsx\": 9928,\n\t\"core/plugins/oas3/components/servers.jsx\": 6617,\n\t\"core/plugins/oas3/helpers.jsx\": 7779,\n\t\"core/plugins/oas3/index.js\": 7451,\n\t\"core/plugins/oas3/reducers.js\": 2109,\n\t\"core/plugins/oas3/selectors.js\": 5065,\n\t\"core/plugins/oas3/spec-extensions/selectors.js\": 1741,\n\t\"core/plugins/oas3/spec-extensions/wrap-selectors.js\": 2044,\n\t\"core/plugins/oas3/wrap-components/auth-item.jsx\": 356,\n\t\"core/plugins/oas3/wrap-components/index.js\": 7761,\n\t\"core/plugins/oas3/wrap-components/json-schema-string.jsx\": 287,\n\t\"core/plugins/oas3/wrap-components/markdown.jsx\": 2460,\n\t\"core/plugins/oas3/wrap-components/model.jsx\": 3499,\n\t\"core/plugins/oas3/wrap-components/online-validator-badge.js\": 58,\n\t\"core/plugins/oas3/wrap-components/version-stamp.jsx\": 9487,\n\t\"core/plugins/oas31/after-load.js\": 7754,\n\t\"core/plugins/oas31/components/contact.jsx\": 9503,\n\t\"core/plugins/oas31/components/info.jsx\": 6133,\n\t\"core/plugins/oas31/components/json-schema-dialect.jsx\": 2562,\n\t\"core/plugins/oas31/components/license.jsx\": 1876,\n\t\"core/plugins/oas31/components/model/model.jsx\": 2718,\n\t\"core/plugins/oas31/components/models/models.jsx\": 263,\n\t\"core/plugins/oas31/components/version-pragma-filter.jsx\": 3429,\n\t\"core/plugins/oas31/components/webhooks.jsx\": 9508,\n\t\"core/plugins/oas31/fn.js\": 4380,\n\t\"core/plugins/oas31/index.js\": 9806,\n\t\"core/plugins/oas31/json-schema-2020-12-extensions/components/keywords/Description.jsx\": 5989,\n\t\"core/plugins/oas31/json-schema-2020-12-extensions/components/keywords/Discriminator/Discriminator.jsx\": 9525,\n\t\"core/plugins/oas31/json-schema-2020-12-extensions/components/keywords/Discriminator/DiscriminatorMapping.jsx\": 7749,\n\t\"core/plugins/oas31/json-schema-2020-12-extensions/components/keywords/Example.jsx\": 9450,\n\t\"core/plugins/oas31/json-schema-2020-12-extensions/components/keywords/ExternalDocs.jsx\": 5324,\n\t\"core/plugins/oas31/json-schema-2020-12-extensions/components/keywords/Properties.jsx\": 9023,\n\t\"core/plugins/oas31/json-schema-2020-12-extensions/components/keywords/Xml.jsx\": 3995,\n\t\"core/plugins/oas31/json-schema-2020-12-extensions/fn.js\": 5800,\n\t\"core/plugins/oas31/json-schema-2020-12-extensions/wrap-components/keywords/Default.jsx\": 4951,\n\t\"core/plugins/oas31/json-schema-2020-12-extensions/wrap-components/keywords/Description.jsx\": 809,\n\t\"core/plugins/oas31/json-schema-2020-12-extensions/wrap-components/keywords/Properties.jsx\": 7536,\n\t\"core/plugins/oas31/selectors.js\": 4280,\n\t\"core/plugins/oas31/spec-extensions/selectors.js\": 9305,\n\t\"core/plugins/oas31/spec-extensions/wrap-selectors.js\": 2884,\n\t\"core/plugins/oas31/wrap-components/contact.jsx\": 7423,\n\t\"core/plugins/oas31/wrap-components/info.jsx\": 284,\n\t\"core/plugins/oas31/wrap-components/license.jsx\": 6608,\n\t\"core/plugins/oas31/wrap-components/model.jsx\": 7042,\n\t\"core/plugins/oas31/wrap-components/models.jsx\": 2914,\n\t\"core/plugins/oas31/wrap-components/version-pragma-filter.jsx\": 1434,\n\t\"core/plugins/oas31/wrap-components/version-stamp.jsx\": 1122,\n\t\"core/plugins/on-complete/index.js\": 8560,\n\t\"core/plugins/request-snippets/fn.js\": 4624,\n\t\"core/plugins/request-snippets/index.js\": 6575,\n\t\"core/plugins/request-snippets/request-snippets.jsx\": 4206,\n\t\"core/plugins/request-snippets/selectors.js\": 4669,\n\t\"core/plugins/safe-render/components/error-boundary.jsx\": 6195,\n\t\"core/plugins/safe-render/components/fallback.jsx\": 9403,\n\t\"core/plugins/safe-render/fn.jsx\": 6189,\n\t\"core/plugins/safe-render/index.js\": 8102,\n\t\"core/plugins/samples/fn/get-json-sample-schema.js\": 2846,\n\t\"core/plugins/samples/fn/get-sample-schema.js\": 6132,\n\t\"core/plugins/samples/fn/get-xml-sample-schema.js\": 1169,\n\t\"core/plugins/samples/fn/get-yaml-sample-schema.js\": 9431,\n\t\"core/plugins/samples/fn/index.js\": 9812,\n\t\"core/plugins/samples/index.js\": 8883,\n\t\"core/plugins/spec/actions.js\": 7612,\n\t\"core/plugins/spec/index.js\": 7038,\n\t\"core/plugins/spec/reducers.js\": 32,\n\t\"core/plugins/spec/selectors.js\": 3881,\n\t\"core/plugins/spec/wrap-actions.js\": 7508,\n\t\"core/plugins/swagger-js/configs-wrap-actions.js\": 4852,\n\t\"core/plugins/swagger-js/index.js\": 9430,\n\t\"core/plugins/util/index.js\": 8525,\n\t\"core/plugins/view/fn.js\": 8347,\n\t\"core/plugins/view/index.js\": 3420,\n\t\"core/plugins/view/root-injects.jsx\": 3934\n};\n\n\nfunction webpackContext(req) {\n\tvar id = webpackContextResolve(req);\n\treturn __webpack_require__(id);\n}\nfunction webpackContextResolve(req) {\n\tif(!__webpack_require__.o(map, req)) {\n\t\tvar e = new Error(\"Cannot find module '\" + req + \"'\");\n\t\te.code = 'MODULE_NOT_FOUND';\n\t\tthrow e;\n\t}\n\treturn map[req];\n}\nwebpackContext.keys = function webpackContextKeys() {\n\treturn Object.keys(map);\n};\nwebpackContext.resolve = webpackContextResolve;\nmodule.exports = webpackContext;\nwebpackContext.id = 5102;","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_array_from_6be643d1__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_array_is_array_6a843f38__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_bind_23a689fe__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_concat_ad403b1a__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_entries_97fed13d__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_every_ac7bb0bc__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_filter_13f270a8__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_find_0ad1164d__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_for_each_f55cb86b__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_includes_c33ad5d5__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_index_of_5fb826c6__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_keys_3b8fec80__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_map_868765ae__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_reduce_e87b61a7__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_slice_9832b507__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_some_50ff1b2d__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_sort_abe23e03__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_trim_ca5b709e__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_json_stringify_1bf7a515__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_map_16a511c8__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_number_is_integer_a32e4569__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_object_assign_e13b6141__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_object_entries_20954bdf__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_object_from_entries_c9366fc2__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_object_keys_e09d3035__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_object_values_550c3b22__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_set_3488258a__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_set_timeout_d31e8027__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_url_4cfab046__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_weak_map_2eee9f61__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_weak_set_9e85a4f8__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_helpers_classPrivateFieldGet_c0aa81e1__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_helpers_defineProperty_807a2698__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_helpers_extends_d20d3ceb__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = __WEBPACK_EXTERNAL_MODULE_base64_js_f145eb6e__;","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_classnames__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = __WEBPACK_EXTERNAL_MODULE_ieee754__;","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"List\"]: () => __WEBPACK_EXTERNAL_MODULE_immutable__.List, [\"Map\"]: () => __WEBPACK_EXTERNAL_MODULE_immutable__.Map, [\"OrderedMap\"]: () => __WEBPACK_EXTERNAL_MODULE_immutable__.OrderedMap, [\"Seq\"]: () => __WEBPACK_EXTERNAL_MODULE_immutable__.Seq, [\"Set\"]: () => __WEBPACK_EXTERNAL_MODULE_immutable__.Set, [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_immutable__[\"default\"], [\"fromJS\"]: () => __WEBPACK_EXTERNAL_MODULE_immutable__.fromJS });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"JSON_SCHEMA\"]: () => __WEBPACK_EXTERNAL_MODULE_js_yaml_78384032__.JSON_SCHEMA, [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_js_yaml_78384032__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_get_9427f899__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_identity_75ffe4a7__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_isEmpty_e109fd6b__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_isFunction_f90b20d6__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_isPlainObject_116f2243__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_memoize_2b5bc477__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_some_5cd47809__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_prop_types_adfe8e31__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_randexp__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"Component\"]: () => __WEBPACK_EXTERNAL_MODULE_react__.Component, [\"PureComponent\"]: () => __WEBPACK_EXTERNAL_MODULE_react__.PureComponent, [\"createContext\"]: () => __WEBPACK_EXTERNAL_MODULE_react__.createContext, [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react__[\"default\"], [\"forwardRef\"]: () => __WEBPACK_EXTERNAL_MODULE_react__.forwardRef, [\"useCallback\"]: () => __WEBPACK_EXTERNAL_MODULE_react__.useCallback, [\"useContext\"]: () => __WEBPACK_EXTERNAL_MODULE_react__.useContext, [\"useEffect\"]: () => __WEBPACK_EXTERNAL_MODULE_react__.useEffect, [\"useRef\"]: () => __WEBPACK_EXTERNAL_MODULE_react__.useRef, [\"useState\"]: () => __WEBPACK_EXTERNAL_MODULE_react__.useState });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"CopyToClipboard\"]: () => __WEBPACK_EXTERNAL_MODULE_react_copy_to_clipboard_5b11dd57__.CopyToClipboard });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_immutable_proptypes_89c7d083__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"applyMiddleware\"]: () => __WEBPACK_EXTERNAL_MODULE_redux__.applyMiddleware, [\"bindActionCreators\"]: () => __WEBPACK_EXTERNAL_MODULE_redux__.bindActionCreators, [\"compose\"]: () => __WEBPACK_EXTERNAL_MODULE_redux__.compose, [\"createStore\"]: () => __WEBPACK_EXTERNAL_MODULE_redux__.createStore });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"Remarkable\"]: () => __WEBPACK_EXTERNAL_MODULE_remarkable__.Remarkable });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"createSelector\"]: () => __WEBPACK_EXTERNAL_MODULE_reselect__.createSelector });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"serializeError\"]: () => __WEBPACK_EXTERNAL_MODULE_serialize_error_5f2df3e5__.serializeError });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"opId\"]: () => __WEBPACK_EXTERNAL_MODULE_swagger_client_es_helpers_4d7bea47__.opId });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nmodule.exports = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_url_parse_6456105f__[\"default\"] });","module.exports = require(\"core-js-pure/stable/object/define-property\");","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_last_index_of_bbdfc000__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"combineReducers\"]: () => __WEBPACK_EXTERNAL_MODULE_redux_immutable_446c9f82__.combineReducers });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_merge_cf99375a__[\"default\"] });","import React from \"react\"\nimport { createStore, applyMiddleware, bindActionCreators, compose } from \"redux\"\nimport Im, { fromJS, Map } from \"immutable\"\nimport deepExtend from \"deep-extend\"\nimport { combineReducers } from \"redux-immutable\"\nimport { serializeError } from \"serialize-error\"\nimport merge from \"lodash/merge\"\nimport { NEW_THROWN_ERR } from \"corePlugins/err/actions\"\nimport win from \"core/window\"\n\nimport { systemThunkMiddleware, isFn, objMap, objReduce, isObject, isArray, isFunc } from \"core/utils\"\n\nconst idFn = a => a\n\n// Apply middleware that gets sandwitched between `dispatch` and the reducer function(s)\nfunction createStoreWithMiddleware(rootReducer, initialState, getSystem) {\n\n let middlwares = [\n // createLogger( {\n // stateTransformer: state => state && state.toJS()\n // } ),\n systemThunkMiddleware( getSystem )\n ]\n\n const composeEnhancers = win.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose\n\n return createStore(rootReducer, initialState, composeEnhancers(\n applyMiddleware( ...middlwares )\n ))\n}\n\nexport default class Store {\n\n constructor(opts={}) {\n deepExtend(this, {\n state: {},\n plugins: [],\n pluginsOptions: {},\n system: {\n configs: {},\n fn: {},\n components: {},\n rootInjects: {},\n statePlugins: {}\n },\n boundSystem: {},\n toolbox: {}\n }, opts)\n\n this.getSystem = this._getSystem.bind(this)\n\n // Bare system (nothing in it, besides the state)\n this.store = configureStore(idFn, fromJS(this.state), this.getSystem )\n\n // will be the system + Im, we can add more tools when we need to\n this.buildSystem(false)\n\n // Bootstrap plugins\n this.register(this.plugins)\n }\n\n getStore() {\n return this.store\n }\n\n register(plugins, rebuild=true) {\n var pluginSystem = combinePlugins(plugins, this.getSystem(), this.pluginsOptions)\n systemExtend(this.system, pluginSystem)\n if(rebuild) {\n this.buildSystem()\n }\n\n const needAnotherRebuild = callAfterLoad.call(this.system, plugins, this.getSystem())\n\n if(needAnotherRebuild) {\n this.buildSystem()\n }\n }\n\n buildSystem(buildReducer=true) {\n let dispatch = this.getStore().dispatch\n let getState = this.getStore().getState\n\n this.boundSystem = Object.assign({},\n this.getRootInjects(),\n this.getWrappedAndBoundActions(dispatch),\n this.getWrappedAndBoundSelectors(getState, this.getSystem),\n this.getStateThunks(getState),\n this.getFn(),\n this.getConfigs()\n )\n\n if(buildReducer)\n this.rebuildReducer()\n }\n\n _getSystem() {\n return this.boundSystem\n }\n\n getRootInjects() {\n return Object.assign({\n getSystem: this.getSystem,\n getStore: this.getStore.bind(this),\n getComponents: this.getComponents.bind(this),\n getState: this.getStore().getState,\n getConfigs: this._getConfigs.bind(this),\n Im,\n React\n }, this.system.rootInjects || {})\n }\n\n _getConfigs(){\n return this.system.configs\n }\n\n getConfigs() {\n return {\n configs: this.system.configs\n }\n }\n\n setConfigs(configs) {\n this.system.configs = configs\n }\n\n rebuildReducer() {\n this.store.replaceReducer(buildReducer(this.system.statePlugins))\n }\n\n /**\n * Generic getter from system.statePlugins\n *\n */\n getType(name) {\n let upName = name[0].toUpperCase() + name.slice(1)\n return objReduce(this.system.statePlugins, (val, namespace) => {\n let thing = val[name]\n if(thing)\n return {[namespace+upName]: thing}\n })\n }\n\n getSelectors() {\n return this.getType(\"selectors\")\n }\n\n getActions() {\n let actionHolders = this.getType(\"actions\")\n\n return objMap(actionHolders, (actions) => {\n return objReduce(actions, (action, actionName) => {\n if(isFn(action))\n return {[actionName]: action}\n })\n })\n }\n\n getWrappedAndBoundActions(dispatch) {\n let actionGroups = this.getBoundActions(dispatch)\n return objMap(actionGroups, (actions, actionGroupName) => {\n let wrappers = this.system.statePlugins[actionGroupName.slice(0,-7)].wrapActions\n if(wrappers) {\n return objMap(actions, (action, actionName) => {\n let wrap = wrappers[actionName]\n if(!wrap) {\n return action\n }\n\n if(!Array.isArray(wrap)) {\n wrap = [wrap]\n }\n return wrap.reduce((acc, fn) => {\n let newAction = (...args) => {\n return fn(acc, this.getSystem())(...args)\n }\n if(!isFn(newAction)) {\n throw new TypeError(\"wrapActions needs to return a function that returns a new function (ie the wrapped action)\")\n }\n return wrapWithTryCatch(newAction)\n }, action || Function.prototype)\n })\n }\n return actions\n })\n }\n\n getWrappedAndBoundSelectors(getState, getSystem) {\n let selectorGroups = this.getBoundSelectors(getState, getSystem)\n return objMap(selectorGroups, (selectors, selectorGroupName) => {\n let stateName = [selectorGroupName.slice(0, -9)] // selectors = 9 chars\n let wrappers = this.system.statePlugins[stateName].wrapSelectors\n if(wrappers) {\n return objMap(selectors, (selector, selectorName) => {\n let wrap = wrappers[selectorName]\n if(!wrap) {\n return selector\n }\n\n if(!Array.isArray(wrap)) {\n wrap = [wrap]\n }\n return wrap.reduce((acc, fn) => {\n let wrappedSelector = (...args) => {\n return fn(acc, this.getSystem())(getState().getIn(stateName), ...args)\n }\n if(!isFn(wrappedSelector)) {\n throw new TypeError(\"wrapSelector needs to return a function that returns a new function (ie the wrapped action)\")\n }\n return wrappedSelector\n }, selector || Function.prototype)\n })\n }\n return selectors\n })\n }\n\n getStates(state) {\n return Object.keys(this.system.statePlugins).reduce((obj, key) => {\n obj[key] = state.get(key)\n return obj\n }, {})\n }\n\n getStateThunks(getState) {\n return Object.keys(this.system.statePlugins).reduce((obj, key) => {\n obj[key] = ()=> getState().get(key)\n return obj\n }, {})\n }\n\n getFn() {\n return {\n fn: this.system.fn\n }\n }\n\n getComponents(component) {\n const res = this.system.components[component]\n\n if(Array.isArray(res)) {\n return res.reduce((ori, wrapper) => {\n return wrapper(ori, this.getSystem())\n })\n }\n if(typeof component !== \"undefined\") {\n return this.system.components[component]\n }\n\n return this.system.components\n }\n\n getBoundSelectors(getState, getSystem) {\n return objMap(this.getSelectors(), (obj, key) => {\n let stateName = [key.slice(0, -9)] // selectors = 9 chars\n const getNestedState = ()=> getState().getIn(stateName)\n\n return objMap(obj, (fn) => {\n return (...args) => {\n let res = wrapWithTryCatch(fn).apply(null, [getNestedState(), ...args])\n\n // If a selector returns a function, give it the system - for advanced usage\n if(typeof(res) === \"function\")\n res = wrapWithTryCatch(res)(getSystem())\n\n return res\n }\n })\n })\n }\n\n getBoundActions(dispatch) {\n\n dispatch = dispatch || this.getStore().dispatch\n\n const actions = this.getActions()\n\n const process = creator =>{\n if( typeof( creator ) !== \"function\" ) {\n return objMap(creator, prop => process(prop))\n }\n\n return ( ...args )=>{\n var action = null\n try{\n action = creator( ...args )\n }\n catch( e ){\n action = {type: NEW_THROWN_ERR, error: true, payload: serializeError(e) }\n }\n finally{\n return action // eslint-disable-line no-unsafe-finally\n }\n }\n\n }\n return objMap(actions, actionCreator => bindActionCreators( process( actionCreator ), dispatch ) )\n }\n\n getMapStateToProps() {\n return () => {\n return Object.assign({}, this.getSystem())\n }\n }\n\n getMapDispatchToProps(extras) {\n return (dispatch) => {\n return deepExtend({}, this.getWrappedAndBoundActions(dispatch), this.getFn(), extras)\n }\n }\n\n}\n\nfunction combinePlugins(plugins, toolbox, pluginOptions) {\n if(isObject(plugins) && !isArray(plugins)) {\n return merge({}, plugins)\n }\n\n if(isFunc(plugins)) {\n return combinePlugins(plugins(toolbox), toolbox, pluginOptions)\n }\n\n if(isArray(plugins)) {\n const dest = pluginOptions.pluginLoadType === \"chain\" ? toolbox.getComponents() : {}\n\n return plugins\n .map(plugin => combinePlugins(plugin, toolbox, pluginOptions))\n .reduce(systemExtend, dest)\n }\n\n return {}\n}\n\nfunction callAfterLoad(plugins, system, { hasLoaded } = {}) {\n let calledSomething = hasLoaded\n if(isObject(plugins) && !isArray(plugins)) {\n if(typeof plugins.afterLoad === \"function\") {\n calledSomething = true\n wrapWithTryCatch(plugins.afterLoad).call(this, system)\n }\n }\n\n if(isFunc(plugins))\n return callAfterLoad.call(this, plugins(system), system, { hasLoaded: calledSomething })\n\n if(isArray(plugins)) {\n return plugins.map(plugin => callAfterLoad.call(this, plugin, system, { hasLoaded: calledSomething }))\n }\n\n return calledSomething\n}\n\n// Wraps deepExtend, to account for certain fields, being wrappers.\n// Ie: we need to convert some fields into arrays, and append to them.\n// Rather than overwrite\nfunction systemExtend(dest={}, src={}) {\n\n if(!isObject(dest)) {\n return {}\n }\n if(!isObject(src)) {\n return dest\n }\n\n // Wrap components\n // Parses existing components in the system, and prepares them for wrapping via getComponents\n if(src.wrapComponents) {\n objMap(src.wrapComponents, (wrapperFn, key) => {\n const ori = dest.components && dest.components[key]\n if(ori && Array.isArray(ori)) {\n dest.components[key] = ori.concat([wrapperFn])\n delete src.wrapComponents[key]\n } else if(ori) {\n dest.components[key] = [ori, wrapperFn]\n delete src.wrapComponents[key]\n }\n })\n\n if(!Object.keys(src.wrapComponents).length) {\n // only delete wrapComponents if we've matched all of our wrappers to components\n // this handles cases where the component to wrap may be out of our scope,\n // but a higher recursive `combinePlugins` call will be able to handle it.\n delete src.wrapComponents\n }\n }\n\n\n // Account for wrapActions, make it an array and append to it\n // Modifies `src`\n // 80% of this code is just safe traversal. We need to address that ( ie: use a lib )\n const { statePlugins } = dest\n if(isObject(statePlugins)) {\n for(let namespace in statePlugins) {\n const namespaceObj = statePlugins[namespace]\n if(!isObject(namespaceObj)) {\n continue\n }\n\n const { wrapActions, wrapSelectors } = namespaceObj\n\n // process action wrapping\n if (isObject(wrapActions)) {\n for(let actionName in wrapActions) {\n let action = wrapActions[actionName]\n\n // This should only happen if dest is the first plugin, since invocations after that will ensure its an array\n if(!Array.isArray(action)) {\n action = [action]\n wrapActions[actionName] = action // Put the value inside an array\n }\n\n if(src && src.statePlugins && src.statePlugins[namespace] && src.statePlugins[namespace].wrapActions && src.statePlugins[namespace].wrapActions[actionName]) {\n src.statePlugins[namespace].wrapActions[actionName] = wrapActions[actionName].concat(src.statePlugins[namespace].wrapActions[actionName])\n }\n\n }\n }\n\n // process selector wrapping\n if (isObject(wrapSelectors)) {\n for(let selectorName in wrapSelectors) {\n let selector = wrapSelectors[selectorName]\n\n // This should only happen if dest is the first plugin, since invocations after that will ensure its an array\n if(!Array.isArray(selector)) {\n selector = [selector]\n wrapSelectors[selectorName] = selector // Put the value inside an array\n }\n\n if(src && src.statePlugins && src.statePlugins[namespace] && src.statePlugins[namespace].wrapSelectors && src.statePlugins[namespace].wrapSelectors[selectorName]) {\n src.statePlugins[namespace].wrapSelectors[selectorName] = wrapSelectors[selectorName].concat(src.statePlugins[namespace].wrapSelectors[selectorName])\n }\n\n }\n }\n }\n }\n\n return deepExtend(dest, src)\n}\n\nfunction buildReducer(states) {\n let reducerObj = objMap(states, (val) => {\n return val.reducers\n })\n return allReducers(reducerObj)\n}\n\nfunction allReducers(reducerSystem) {\n let reducers = Object.keys(reducerSystem).reduce((obj, key) => {\n obj[key] = makeReducer(reducerSystem[key])\n return obj\n },{})\n\n if(!Object.keys(reducers).length) {\n return idFn\n }\n\n return combineReducers(reducers)\n}\n\nfunction makeReducer(reducerObj) {\n return (state = new Map(), action) => {\n if(!reducerObj)\n return state\n\n let redFn = (reducerObj[action.type])\n if(redFn) {\n const res = wrapWithTryCatch(redFn)(state, action)\n // If the try/catch wrapper kicks in, we'll get null back...\n // in that case, we want to avoid making any changes to state\n return res === null ? state : res\n }\n return state\n }\n}\n\nfunction wrapWithTryCatch(fn, {\n logErrors = true\n} = {}) {\n if(typeof fn !== \"function\") {\n return fn\n }\n\n return function(...args) {\n try {\n return fn.call(this, ...args)\n } catch(e) {\n if(logErrors) {\n console.error(e)\n }\n return null\n }\n }\n}\n\nfunction configureStore(rootReducer, initialState, getSystem) {\n const store = createStoreWithMiddleware(rootReducer, initialState, getSystem)\n\n // if (module.hot) {\n // // Enable Webpack hot module replacement for reducers\n // module.hot.accept(\"reducers/index\", () => {\n // const nextRootReducer = require(\"reducers/index\")\n // store.replaceReducer(nextRootReducer)\n // })\n // }\n\n return store\n}\n","import React, { PureComponent } from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { opId } from \"swagger-client/es/helpers\"\nimport { Iterable, fromJS, Map } from \"immutable\"\n\nexport default class OperationContainer extends PureComponent {\n constructor(props, context) {\n super(props, context)\n\n const { tryItOutEnabled } = props.getConfigs()\n\n this.state = {\n tryItOutEnabled: tryItOutEnabled === true || tryItOutEnabled === \"true\",\n executeInProgress: false\n }\n }\n\n static propTypes = {\n op: PropTypes.instanceOf(Iterable).isRequired,\n tag: PropTypes.string.isRequired,\n path: PropTypes.string.isRequired,\n method: PropTypes.string.isRequired,\n operationId: PropTypes.string.isRequired,\n showSummary: PropTypes.bool.isRequired,\n isShown: PropTypes.bool.isRequired,\n jumpToKey: PropTypes.string.isRequired,\n allowTryItOut: PropTypes.bool,\n displayOperationId: PropTypes.bool,\n isAuthorized: PropTypes.bool,\n displayRequestDuration: PropTypes.bool,\n response: PropTypes.instanceOf(Iterable),\n request: PropTypes.instanceOf(Iterable),\n security: PropTypes.instanceOf(Iterable),\n isDeepLinkingEnabled: PropTypes.bool.isRequired,\n specPath: ImPropTypes.list.isRequired,\n getComponent: PropTypes.func.isRequired,\n authActions: PropTypes.object,\n oas3Actions: PropTypes.object,\n oas3Selectors: PropTypes.object,\n authSelectors: PropTypes.object,\n specActions: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n layoutActions: PropTypes.object.isRequired,\n layoutSelectors: PropTypes.object.isRequired,\n fn: PropTypes.object.isRequired,\n getConfigs: PropTypes.func.isRequired\n }\n\n static defaultProps = {\n showSummary: true,\n response: null,\n allowTryItOut: true,\n displayOperationId: false,\n displayRequestDuration: false\n }\n\n mapStateToProps(nextState, props) {\n const { op, layoutSelectors, getConfigs } = props\n const { docExpansion, deepLinking, displayOperationId, displayRequestDuration, supportedSubmitMethods } = getConfigs()\n const showSummary = layoutSelectors.showSummary()\n const operationId = op.getIn([\"operation\", \"__originalOperationId\"]) || op.getIn([\"operation\", \"operationId\"]) || opId(op.get(\"operation\"), props.path, props.method) || op.get(\"id\")\n const isShownKey = [\"operations\", props.tag, operationId]\n const isDeepLinkingEnabled = deepLinking && deepLinking !== \"false\"\n const allowTryItOut = supportedSubmitMethods.indexOf(props.method) >= 0 && (typeof props.allowTryItOut === \"undefined\" ?\n props.specSelectors.allowTryItOutFor(props.path, props.method) : props.allowTryItOut)\n const security = op.getIn([\"operation\", \"security\"]) || props.specSelectors.security()\n\n return {\n operationId,\n isDeepLinkingEnabled,\n showSummary,\n displayOperationId,\n displayRequestDuration,\n allowTryItOut,\n security,\n isAuthorized: props.authSelectors.isAuthorized(security),\n isShown: layoutSelectors.isShown(isShownKey, docExpansion === \"full\" ),\n jumpToKey: `paths.${props.path}.${props.method}`,\n response: props.specSelectors.responseFor(props.path, props.method),\n request: props.specSelectors.requestFor(props.path, props.method)\n }\n }\n\n componentDidMount() {\n const { isShown } = this.props\n const resolvedSubtree = this.getResolvedSubtree()\n\n if(isShown && resolvedSubtree === undefined) {\n this.requestResolvedSubtree()\n }\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n const { response, isShown } = nextProps\n const resolvedSubtree = this.getResolvedSubtree()\n\n if(response !== this.props.response) {\n this.setState({ executeInProgress: false })\n }\n\n if(isShown && resolvedSubtree === undefined) {\n this.requestResolvedSubtree()\n }\n }\n\n toggleShown =() => {\n let { layoutActions, tag, operationId, isShown } = this.props\n const resolvedSubtree = this.getResolvedSubtree()\n if(!isShown && resolvedSubtree === undefined) {\n // transitioning from collapsed to expanded\n this.requestResolvedSubtree()\n }\n layoutActions.show([\"operations\", tag, operationId], !isShown)\n }\n\n onCancelClick=() => {\n this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})\n }\n\n onTryoutClick =() => {\n this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})\n }\n\n onResetClick = (pathMethod) => {\n const defaultRequestBodyValue = this.props.oas3Selectors.selectDefaultRequestBodyValue(...pathMethod)\n this.props.oas3Actions.setRequestBodyValue({ value: defaultRequestBodyValue, pathMethod })\n }\n\n onExecute = () => {\n this.setState({ executeInProgress: true })\n }\n\n getResolvedSubtree = () => {\n const {\n specSelectors,\n path,\n method,\n specPath\n } = this.props\n\n if(specPath) {\n return specSelectors.specResolvedSubtree(specPath.toJS())\n }\n\n return specSelectors.specResolvedSubtree([\"paths\", path, method])\n }\n\n requestResolvedSubtree = () => {\n const {\n specActions,\n path,\n method,\n specPath\n } = this.props\n\n\n if(specPath) {\n return specActions.requestResolvedSubtree(specPath.toJS())\n }\n\n return specActions.requestResolvedSubtree([\"paths\", path, method])\n }\n\n render() {\n let {\n op: unresolvedOp,\n tag,\n path,\n method,\n security,\n isAuthorized,\n operationId,\n showSummary,\n isShown,\n jumpToKey,\n allowTryItOut,\n response,\n request,\n displayOperationId,\n displayRequestDuration,\n isDeepLinkingEnabled,\n specPath,\n specSelectors,\n specActions,\n getComponent,\n getConfigs,\n layoutSelectors,\n layoutActions,\n authActions,\n authSelectors,\n oas3Actions,\n oas3Selectors,\n fn\n } = this.props\n\n const Operation = getComponent( \"operation\" )\n\n const resolvedSubtree = this.getResolvedSubtree() || Map()\n\n const operationProps = fromJS({\n op: resolvedSubtree,\n tag,\n path,\n summary: unresolvedOp.getIn([\"operation\", \"summary\"]) || \"\",\n deprecated: resolvedSubtree.get(\"deprecated\") || unresolvedOp.getIn([\"operation\", \"deprecated\"]) || false,\n method,\n security,\n isAuthorized,\n operationId,\n originalOperationId: resolvedSubtree.getIn([\"operation\", \"__originalOperationId\"]),\n showSummary,\n isShown,\n jumpToKey,\n allowTryItOut,\n request,\n displayOperationId,\n displayRequestDuration,\n isDeepLinkingEnabled,\n executeInProgress: this.state.executeInProgress,\n tryItOutEnabled: this.state.tryItOutEnabled\n })\n\n return (\n \n )\n }\n\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class App extends React.Component {\n\n getLayout() {\n let { getComponent, layoutSelectors } = this.props\n const layoutName = layoutSelectors.current()\n const Component = getComponent(layoutName, true)\n return Component ? Component : ()=>

    No layout defined for "{layoutName}"

    \n }\n\n render() {\n const Layout = this.getLayout()\n\n return (\n \n )\n }\n}\n\nApp.propTypes = {\n getComponent: PropTypes.func.isRequired,\n layoutSelectors: PropTypes.object.isRequired,\n}\n\nApp.defaultProps = {\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class AuthorizationPopup extends React.Component {\n close =() => {\n let { authActions } = this.props\n\n authActions.showDefinitions(false)\n }\n\n render() {\n let { authSelectors, authActions, getComponent, errSelectors, specSelectors, fn: { AST = {} } } = this.props\n let definitions = authSelectors.shownDefinitions()\n const Auths = getComponent(\"auths\")\n\n return (\n
    \n
    \n
    \n
    \n
    \n
    \n

    Available authorizations

    \n \n
    \n
    \n\n {\n definitions.valueSeq().map(( definition, key ) => {\n return \n })\n }\n
    \n
    \n
    \n
    \n
    \n )\n }\n\n static propTypes = {\n fn: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n authSelectors: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n errSelectors: PropTypes.object.isRequired,\n authActions: PropTypes.object.isRequired,\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class AuthorizeBtn extends React.Component {\n static propTypes = {\n onClick: PropTypes.func,\n isAuthorized: PropTypes.bool,\n showPopup: PropTypes.bool,\n getComponent: PropTypes.func.isRequired\n }\n\n render() {\n let { isAuthorized, showPopup, onClick, getComponent } = this.props\n\n //must be moved out of button component\n const AuthorizationPopup = getComponent(\"authorizationPopup\", true)\n\n return (\n
    \n \n { showPopup && }\n
    \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class AuthorizeBtnContainer extends React.Component {\n\n static propTypes = {\n specActions: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n authActions: PropTypes.object.isRequired,\n authSelectors: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired\n }\n\n render () {\n const { authActions, authSelectors, specSelectors, getComponent} = this.props\n \n const securityDefinitions = specSelectors.securityDefinitions()\n const authorizableDefinitions = authSelectors.definitionsToAuthorize()\n\n const AuthorizeBtn = getComponent(\"authorizeBtn\")\n\n return securityDefinitions ? (\n authActions.showDefinitions(authorizableDefinitions)}\n isAuthorized={!!authSelectors.authorized().size}\n showPopup={!!authSelectors.shownDefinitions()}\n getComponent={getComponent}\n />\n ) : null\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class AuthorizeOperationBtn extends React.Component {\n static propTypes = {\n isAuthorized: PropTypes.bool.isRequired,\n onClick: PropTypes.func\n }\n\n onClick =(e) => {\n e.stopPropagation()\n let { onClick } = this.props\n\n if(onClick) {\n onClick()\n }\n }\n\n render() {\n let { isAuthorized } = this.props\n\n return (\n \n\n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class Auths extends React.Component {\n static propTypes = {\n definitions: ImPropTypes.iterable.isRequired,\n getComponent: PropTypes.func.isRequired,\n authSelectors: PropTypes.object.isRequired,\n authActions: PropTypes.object.isRequired,\n errSelectors: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired\n }\n\n constructor(props, context) {\n super(props, context)\n\n this.state = {}\n }\n\n onAuthChange =(auth) => {\n let { name } = auth\n\n this.setState({ [name]: auth })\n }\n\n submitAuth =(e) => {\n e.preventDefault()\n\n let { authActions } = this.props\n authActions.authorizeWithPersistOption(this.state)\n }\n\n logoutClick =(e) => {\n e.preventDefault()\n\n let { authActions, definitions } = this.props\n let auths = definitions.map( (val, key) => {\n return key\n }).toArray()\n\n this.setState(auths.reduce((prev, auth) => {\n prev[auth] = \"\"\n return prev\n }, {}))\n\n authActions.logoutWithPersistOption(auths)\n }\n\n close =(e) => {\n e.preventDefault()\n let { authActions } = this.props\n\n authActions.showDefinitions(false)\n }\n\n render() {\n let { definitions, getComponent, authSelectors, errSelectors } = this.props\n const AuthItem = getComponent(\"AuthItem\")\n const Oauth2 = getComponent(\"oauth2\", true)\n const Button = getComponent(\"Button\")\n\n let authorized = authSelectors.authorized()\n\n let authorizedAuth = definitions.filter( (definition, key) => {\n return !!authorized.get(key)\n })\n\n let nonOauthDefinitions = definitions.filter( schema => schema.get(\"type\") !== \"oauth2\")\n let oauthDefinitions = definitions.filter( schema => schema.get(\"type\") === \"oauth2\")\n\n return (\n
    \n {\n !!nonOauthDefinitions.size &&
    \n {\n nonOauthDefinitions.map( (schema, name) => {\n return \n }).toArray()\n }\n
    \n {\n nonOauthDefinitions.size === authorizedAuth.size ? \n : \n }\n \n
    \n \n }\n\n {\n oauthDefinitions && oauthDefinitions.size ?
    \n
    \n

    Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.

    \n

    API requires the following scopes. Select which ones you want to grant to Swagger UI.

    \n
    \n {\n definitions.filter( schema => schema.get(\"type\") === \"oauth2\")\n .map( (schema, name) =>{\n return (
    \n \n
    )\n }\n ).toArray()\n }\n
    : null\n }\n\n
    \n )\n }\n\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class Auths extends React.Component {\n static propTypes = {\n authorized: ImPropTypes.orderedMap.isRequired,\n schema: ImPropTypes.orderedMap.isRequired,\n name: PropTypes.string.isRequired,\n getComponent: PropTypes.func.isRequired,\n onAuthChange: PropTypes.func.isRequired,\n errSelectors: PropTypes.object.isRequired,\n }\n\n render() {\n let {\n schema,\n name,\n getComponent,\n onAuthChange,\n authorized,\n errSelectors\n } = this.props\n const ApiKeyAuth = getComponent(\"apiKeyAuth\")\n const BasicAuth = getComponent(\"basicAuth\")\n\n let authEl\n\n const type = schema.get(\"type\")\n\n switch(type) {\n case \"apiKey\": authEl = \n break\n case \"basic\": authEl = \n break\n default: authEl =
    Unknown security definition type { type }
    \n }\n\n return (
    \n { authEl }\n
    )\n }\n\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class AuthError extends React.Component {\n\n static propTypes = {\n error: PropTypes.object.isRequired\n }\n\n render() {\n let { error } = this.props\n\n let level = error.get(\"level\")\n let message = error.get(\"message\")\n let source = error.get(\"source\")\n\n return (\n
    \n { source } { level }\n { message }\n
    \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class ApiKeyAuth extends React.Component {\n static propTypes = {\n authorized: PropTypes.object,\n getComponent: PropTypes.func.isRequired,\n errSelectors: PropTypes.object.isRequired,\n schema: PropTypes.object.isRequired,\n name: PropTypes.string.isRequired,\n onChange: PropTypes.func\n }\n\n constructor(props, context) {\n super(props, context)\n let { name, schema } = this.props\n let value = this.getValue()\n\n this.state = {\n name: name,\n schema: schema,\n value: value\n }\n }\n\n getValue () {\n let { name, authorized } = this.props\n\n return authorized && authorized.getIn([name, \"value\"])\n }\n\n onChange =(e) => {\n let { onChange } = this.props\n let value = e.target.value\n let newState = Object.assign({}, this.state, { value: value })\n\n this.setState(newState)\n onChange(newState)\n }\n\n render() {\n let { schema, getComponent, errSelectors, name } = this.props\n const Input = getComponent(\"Input\")\n const Row = getComponent(\"Row\")\n const Col = getComponent(\"Col\")\n const AuthError = getComponent(\"authError\")\n const Markdown = getComponent(\"Markdown\", true)\n const JumpToPath = getComponent(\"JumpToPath\", true)\n let value = this.getValue()\n let errors = errSelectors.allErrors().filter( err => err.get(\"authId\") === name)\n\n return (\n
    \n

    \n { name || schema.get(\"name\") } (apiKey)\n \n

    \n { value &&
    Authorized
    }\n \n \n \n \n

    Name: { schema.get(\"name\") }

    \n
    \n \n

    In: { schema.get(\"in\") }

    \n
    \n \n \n {\n value ? ****** \n : \n }\n \n {\n errors.valueSeq().map( (error, key) => {\n return \n } )\n }\n
    \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class BasicAuth extends React.Component {\n static propTypes = {\n authorized: ImPropTypes.map,\n schema: ImPropTypes.map,\n getComponent: PropTypes.func.isRequired,\n onChange: PropTypes.func.isRequired,\n name: PropTypes.string.isRequired,\n errSelectors: PropTypes.object.isRequired,\n }\n\n constructor(props, context) {\n super(props, context)\n let { schema, name } = this.props\n\n let value = this.getValue()\n let username = value.username\n\n this.state = {\n name: name,\n schema: schema,\n value: !username ? {} : {\n username: username\n }\n }\n }\n\n getValue () {\n let { authorized, name } = this.props\n\n return authorized && authorized.getIn([name, \"value\"]) || {}\n }\n\n onChange =(e) => {\n let { onChange } = this.props\n let { value, name } = e.target\n\n let newValue = this.state.value\n newValue[name] = value\n\n this.setState({ value: newValue })\n\n onChange(this.state)\n }\n\n render() {\n let { schema, getComponent, name, errSelectors } = this.props\n const Input = getComponent(\"Input\")\n const Row = getComponent(\"Row\")\n const Col = getComponent(\"Col\")\n const AuthError = getComponent(\"authError\")\n const JumpToPath = getComponent(\"JumpToPath\", true)\n const Markdown = getComponent(\"Markdown\", true)\n let username = this.getValue().username\n let errors = errSelectors.allErrors().filter( err => err.get(\"authId\") === name)\n\n return (\n
    \n

    Basic authorization

    \n { username &&
    Authorized
    }\n \n \n \n \n \n {\n username ? { username } \n : \n }\n \n \n \n {\n username ? ****** \n : \n }\n \n {\n errors.valueSeq().map( (error, key) => {\n return \n } )\n }\n
    \n )\n }\n\n}\n","/**\n * @prettier\n */\n\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { stringify } from \"core/utils\"\n\nexport default function Example(props) {\n const { example, showValue, getComponent, getConfigs } = props\n\n const Markdown = getComponent(\"Markdown\", true)\n const HighlightCode = getComponent(\"highlightCode\")\n\n if(!example) return null\n\n return (\n
    \n {example.get(\"description\") ? (\n
    \n
    Example Description
    \n

    \n \n

    \n
    \n ) : null}\n {showValue && example.has(\"value\") ? (\n
    \n
    Example Value
    \n \n
    \n ) : null}\n
    \n )\n}\n\nExample.propTypes = {\n example: ImPropTypes.map.isRequired,\n showValue: PropTypes.bool,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.getConfigs,\n}\n","/**\n * @prettier\n */\n\nimport React from \"react\"\nimport Im from \"immutable\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class ExamplesSelect extends React.PureComponent {\n static propTypes = {\n examples: ImPropTypes.map.isRequired,\n onSelect: PropTypes.func,\n currentExampleKey: PropTypes.string,\n isModifiedValueAvailable: PropTypes.bool,\n isValueModified: PropTypes.bool,\n showLabels: PropTypes.bool,\n }\n\n static defaultProps = {\n examples: Im.Map({}),\n onSelect: (...args) =>\n console.log( // eslint-disable-line no-console\n // FIXME: remove before merging to master...\n `DEBUG: ExamplesSelect was not given an onSelect callback`,\n ...args\n ),\n currentExampleKey: null,\n showLabels: true,\n }\n\n _onSelect = (key, { isSyntheticChange = false } = {}) => {\n if (typeof this.props.onSelect === \"function\") {\n this.props.onSelect(key, {\n isSyntheticChange,\n })\n }\n }\n\n _onDomSelect = e => {\n if (typeof this.props.onSelect === \"function\") {\n const element = e.target.selectedOptions[0]\n const key = element.getAttribute(\"value\")\n\n this._onSelect(key, {\n isSyntheticChange: false,\n })\n }\n }\n\n getCurrentExample = () => {\n const { examples, currentExampleKey } = this.props\n\n const currentExamplePerProps = examples.get(currentExampleKey)\n\n const firstExamplesKey = examples.keySeq().first()\n const firstExample = examples.get(firstExamplesKey)\n\n return currentExamplePerProps || firstExample || Map({})\n }\n\n componentDidMount() {\n // this is the not-so-great part of ExamplesSelect... here we're\n // artificially kicking off an onSelect event in order to set a default\n // value in state. the consumer has the option to avoid this by checking\n // `isSyntheticEvent`, but we should really be doing this in a selector.\n // TODO: clean this up\n // FIXME: should this only trigger if `currentExamplesKey` is nullish?\n const { onSelect, examples } = this.props\n\n if (typeof onSelect === \"function\") {\n const firstExample = examples.first()\n const firstExampleKey = examples.keyOf(firstExample)\n\n this._onSelect(firstExampleKey, {\n isSyntheticChange: true,\n })\n }\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n const { currentExampleKey, examples } = nextProps\n if (examples !== this.props.examples && !examples.has(currentExampleKey)) {\n // examples have changed from under us, and the currentExampleKey is no longer\n // valid.\n const firstExample = examples.first()\n const firstExampleKey = examples.keyOf(firstExample)\n\n this._onSelect(firstExampleKey, {\n isSyntheticChange: true,\n })\n }\n }\n\n render() {\n const {\n examples,\n currentExampleKey,\n isValueModified,\n isModifiedValueAvailable,\n showLabels,\n } = this.props\n\n return (\n
    \n {\n showLabels ? (\n Examples: \n ) : null\n }\n \n {isModifiedValueAvailable ? (\n \n ) : null}\n {examples\n .map((example, exampleName) => {\n return (\n \n {example.get(\"summary\") || exampleName}\n \n )\n })\n .valueSeq()}\n \n
    \n )\n }\n}\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport { Map, List } from \"immutable\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nimport { stringify } from \"core/utils\"\n\n// This stateful component lets us avoid writing competing values (user\n// modifications vs example values) into global state, and the mess that comes\n// with that: tracking which of the two values are currently used for\n// Try-It-Out, which example a modified value came from, etc...\n//\n// The solution here is to retain the last user-modified value in\n// ExamplesSelectValueRetainer's component state, so that our global state can stay\n// clean, always simply being the source of truth for what value should be both\n// displayed to the user and used as a value during request execution.\n//\n// This approach/tradeoff was chosen in order to encapsulate the particular\n// logic of Examples within the Examples component tree, and to avoid\n// regressions within our current implementation elsewhere (non-Examples\n// definitions, OpenAPI 2.0, etc). A future refactor to global state might make\n// this component unnecessary.\n//\n// TL;DR: this is not our usual approach, but the choice was made consciously.\n\n// Note that `currentNamespace` isn't currently used anywhere!\n\nconst stringifyUnlessList = input =>\n List.isList(input) ? input : stringify(input)\n\nexport default class ExamplesSelectValueRetainer extends React.PureComponent {\n static propTypes = {\n examples: ImPropTypes.map,\n onSelect: PropTypes.func,\n updateValue: PropTypes.func, // mechanism to update upstream value\n userHasEditedBody: PropTypes.bool,\n getComponent: PropTypes.func.isRequired,\n currentUserInputValue: PropTypes.any,\n currentKey: PropTypes.string,\n currentNamespace: PropTypes.string,\n setRetainRequestBodyValueFlag: PropTypes.func.isRequired,\n // (also proxies props for Examples)\n }\n\n static defaultProps = {\n userHasEditedBody: false,\n examples: Map({}),\n currentNamespace: \"__DEFAULT__NAMESPACE__\",\n setRetainRequestBodyValueFlag: () => {\n // NOOP\n },\n onSelect: (...args) =>\n console.log( // eslint-disable-line no-console\n \"ExamplesSelectValueRetainer: no `onSelect` function was provided\",\n ...args\n ),\n updateValue: (...args) =>\n console.log( // eslint-disable-line no-console\n \"ExamplesSelectValueRetainer: no `updateValue` function was provided\",\n ...args\n ),\n }\n\n constructor(props) {\n super(props)\n\n const valueFromExample = this._getCurrentExampleValue()\n\n this.state = {\n // user edited: last value that came from the world around us, and didn't\n // match the current example's value\n // internal: last value that came from user selecting an Example\n [props.currentNamespace]: Map({\n lastUserEditedValue: this.props.currentUserInputValue,\n lastDownstreamValue: valueFromExample,\n isModifiedValueSelected:\n // valueFromExample !== undefined &&\n this.props.userHasEditedBody ||\n this.props.currentUserInputValue !== valueFromExample,\n }),\n }\n }\n\n componentWillUnmount() {\n this.props.setRetainRequestBodyValueFlag(false)\n }\n\n _getStateForCurrentNamespace = () => {\n const { currentNamespace } = this.props\n\n return (this.state[currentNamespace] || Map()).toObject()\n }\n\n _setStateForCurrentNamespace = obj => {\n const { currentNamespace } = this.props\n\n return this._setStateForNamespace(currentNamespace, obj)\n }\n\n _setStateForNamespace = (namespace, obj) => {\n const oldStateForNamespace = this.state[namespace] || Map()\n const newStateForNamespace = oldStateForNamespace.mergeDeep(obj)\n return this.setState({\n [namespace]: newStateForNamespace,\n })\n }\n\n _isCurrentUserInputSameAsExampleValue = () => {\n const { currentUserInputValue } = this.props\n\n const valueFromExample = this._getCurrentExampleValue()\n\n return valueFromExample === currentUserInputValue\n }\n\n _getValueForExample = (exampleKey, props) => {\n // props are accepted so that this can be used in UNSAFE_componentWillReceiveProps,\n // which has access to `nextProps`\n const { examples } = props || this.props\n return stringifyUnlessList(\n (examples || Map({})).getIn([exampleKey, \"value\"])\n )\n }\n\n _getCurrentExampleValue = props => {\n // props are accepted so that this can be used in UNSAFE_componentWillReceiveProps,\n // which has access to `nextProps`\n const { currentKey } = props || this.props\n return this._getValueForExample(currentKey, props || this.props)\n }\n\n _onExamplesSelect = (key, { isSyntheticChange } = {}, ...otherArgs) => {\n const {\n onSelect,\n updateValue,\n currentUserInputValue,\n userHasEditedBody,\n } = this.props\n const { lastUserEditedValue } = this._getStateForCurrentNamespace()\n\n const valueFromExample = this._getValueForExample(key)\n\n if (key === \"__MODIFIED__VALUE__\") {\n updateValue(stringifyUnlessList(lastUserEditedValue))\n return this._setStateForCurrentNamespace({\n isModifiedValueSelected: true,\n })\n }\n\n if (typeof onSelect === \"function\") {\n onSelect(key, { isSyntheticChange }, ...otherArgs)\n }\n\n this._setStateForCurrentNamespace({\n lastDownstreamValue: valueFromExample,\n isModifiedValueSelected:\n (isSyntheticChange && userHasEditedBody) ||\n (!!currentUserInputValue && currentUserInputValue !== valueFromExample),\n })\n\n // we never want to send up value updates from synthetic changes\n if (isSyntheticChange) return\n\n if (typeof updateValue === \"function\") {\n updateValue(stringifyUnlessList(valueFromExample))\n }\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n // update `lastUserEditedValue` as new currentUserInput values come in\n\n const {\n currentUserInputValue: newValue,\n examples,\n onSelect,\n userHasEditedBody,\n } = nextProps\n\n const {\n lastUserEditedValue,\n lastDownstreamValue,\n } = this._getStateForCurrentNamespace()\n\n const valueFromCurrentExample = this._getValueForExample(\n nextProps.currentKey,\n nextProps\n )\n\n const examplesMatchingNewValue = examples.filter(\n (example) =>\n example.get(\"value\") === newValue ||\n // sometimes data is stored as a string (e.g. in Request Bodies), so\n // let's check against a stringified version of our example too\n stringify(example.get(\"value\")) === newValue\n )\n\n if (examplesMatchingNewValue.size) {\n let key\n if(examplesMatchingNewValue.has(nextProps.currentKey))\n {\n key = nextProps.currentKey\n } else {\n key = examplesMatchingNewValue.keySeq().first()\n }\n onSelect(key, {\n isSyntheticChange: true,\n })\n } else if (\n newValue !== this.props.currentUserInputValue && // value has changed\n newValue !== lastUserEditedValue && // value isn't already tracked\n newValue !== lastDownstreamValue // value isn't what we've seen on the other side\n ) {\n this.props.setRetainRequestBodyValueFlag(true)\n this._setStateForNamespace(nextProps.currentNamespace, {\n lastUserEditedValue: nextProps.currentUserInputValue,\n isModifiedValueSelected:\n userHasEditedBody || newValue !== valueFromCurrentExample,\n })\n }\n }\n\n render() {\n const {\n currentUserInputValue,\n examples,\n currentKey,\n getComponent,\n userHasEditedBody,\n } = this.props\n const {\n lastDownstreamValue,\n lastUserEditedValue,\n isModifiedValueSelected,\n } = this._getStateForCurrentNamespace()\n\n const ExamplesSelect = getComponent(\"ExamplesSelect\")\n\n return (\n \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport oauth2Authorize from \"core/oauth2-authorize\"\n\nexport default class Oauth2 extends React.Component {\n static propTypes = {\n name: PropTypes.string,\n authorized: PropTypes.object,\n getComponent: PropTypes.func.isRequired,\n schema: PropTypes.object.isRequired,\n authSelectors: PropTypes.object.isRequired,\n authActions: PropTypes.object.isRequired,\n errSelectors: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n errActions: PropTypes.object.isRequired,\n getConfigs: PropTypes.any\n }\n\n constructor(props, context) {\n super(props, context)\n let { name, schema, authorized, authSelectors } = this.props\n let auth = authorized && authorized.get(name)\n let authConfigs = authSelectors.getConfigs() || {}\n let username = auth && auth.get(\"username\") || \"\"\n let clientId = auth && auth.get(\"clientId\") || authConfigs.clientId || \"\"\n let clientSecret = auth && auth.get(\"clientSecret\") || authConfigs.clientSecret || \"\"\n let passwordType = auth && auth.get(\"passwordType\") || \"basic\"\n let scopes = auth && auth.get(\"scopes\") || authConfigs.scopes || []\n if (typeof scopes === \"string\") {\n scopes = scopes.split(authConfigs.scopeSeparator || \" \")\n }\n\n this.state = {\n appName: authConfigs.appName,\n name: name,\n schema: schema,\n scopes: scopes,\n clientId: clientId,\n clientSecret: clientSecret,\n username: username,\n password: \"\",\n passwordType: passwordType\n }\n }\n\n close = (e) => {\n e.preventDefault()\n let { authActions } = this.props\n\n authActions.showDefinitions(false)\n }\n\n authorize =() => {\n let { authActions, errActions, getConfigs, authSelectors, oas3Selectors } = this.props\n let configs = getConfigs()\n let authConfigs = authSelectors.getConfigs()\n\n errActions.clear({authId: name,type: \"auth\", source: \"auth\"})\n oauth2Authorize({\n auth: this.state,\n currentServer: oas3Selectors.serverEffectiveValue(oas3Selectors.selectedServer()),\n authActions,\n errActions,\n configs,\n authConfigs\n })\n }\n\n onScopeChange =(e) => {\n let { target } = e\n let { checked } = target\n let scope = target.dataset.value\n\n if ( checked && this.state.scopes.indexOf(scope) === -1 ) {\n let newScopes = this.state.scopes.concat([scope])\n this.setState({ scopes: newScopes })\n } else if ( !checked && this.state.scopes.indexOf(scope) > -1) {\n this.setState({ scopes: this.state.scopes.filter((val) => val !== scope) })\n }\n }\n\n onInputChange =(e) => {\n let { target : { dataset : { name }, value } } = e\n let state = {\n [name]: value\n }\n\n this.setState(state)\n }\n\n selectScopes =(e) => {\n if (e.target.dataset.all) {\n this.setState({\n scopes: Array.from((this.props.schema.get(\"allowedScopes\") || this.props.schema.get(\"scopes\")).keys())\n })\n } else {\n this.setState({ scopes: [] })\n }\n }\n\n logout =(e) => {\n e.preventDefault()\n let { authActions, errActions, name } = this.props\n\n errActions.clear({authId: name, type: \"auth\", source: \"auth\"})\n authActions.logoutWithPersistOption([ name ])\n }\n\n render() {\n let {\n schema, getComponent, authSelectors, errSelectors, name, specSelectors\n } = this.props\n const Input = getComponent(\"Input\")\n const Row = getComponent(\"Row\")\n const Col = getComponent(\"Col\")\n const Button = getComponent(\"Button\")\n const AuthError = getComponent(\"authError\")\n const JumpToPath = getComponent(\"JumpToPath\", true)\n const Markdown = getComponent(\"Markdown\", true)\n const InitializedInput = getComponent(\"InitializedInput\")\n\n const { isOAS3 } = specSelectors\n\n let oidcUrl = isOAS3() ? schema.get(\"openIdConnectUrl\") : null\n\n // Auth type consts\n const AUTH_FLOW_IMPLICIT = \"implicit\"\n const AUTH_FLOW_PASSWORD = \"password\"\n const AUTH_FLOW_ACCESS_CODE = isOAS3() ? (oidcUrl ? \"authorization_code\" : \"authorizationCode\") : \"accessCode\"\n const AUTH_FLOW_APPLICATION = isOAS3() ? (oidcUrl ? \"client_credentials\" : \"clientCredentials\") : \"application\"\n\n let authConfigs = authSelectors.getConfigs() || {}\n let isPkceCodeGrant = !!authConfigs.usePkceWithAuthorizationCodeGrant\n\n let flow = schema.get(\"flow\")\n let flowToDisplay = flow === AUTH_FLOW_ACCESS_CODE && isPkceCodeGrant ? flow + \" with PKCE\" : flow\n let scopes = schema.get(\"allowedScopes\") || schema.get(\"scopes\")\n let authorizedAuth = authSelectors.authorized().get(name)\n let isAuthorized = !!authorizedAuth\n let errors = errSelectors.allErrors().filter( err => err.get(\"authId\") === name)\n let isValid = !errors.filter( err => err.get(\"source\") === \"validation\").size\n let description = schema.get(\"description\")\n\n return (\n
    \n

    {name} (OAuth2, { flowToDisplay })

    \n { !this.state.appName ? null :
    Application: { this.state.appName }
    }\n { description && }\n\n { isAuthorized &&
    Authorized
    }\n\n { oidcUrl &&

    OpenID Connect URL: { oidcUrl }

    }\n { ( flow === AUTH_FLOW_IMPLICIT || flow === AUTH_FLOW_ACCESS_CODE ) &&

    Authorization URL: { schema.get(\"authorizationUrl\") }

    }\n { ( flow === AUTH_FLOW_PASSWORD || flow === AUTH_FLOW_ACCESS_CODE || flow === AUTH_FLOW_APPLICATION ) &&

    Token URL: { schema.get(\"tokenUrl\") }

    }\n

    Flow: { flowToDisplay }

    \n\n {\n flow !== AUTH_FLOW_PASSWORD ? null\n : \n \n \n {\n isAuthorized ? { this.state.username } \n : \n \n \n }\n \n {\n\n }\n \n \n {\n isAuthorized ? ****** \n : \n \n \n }\n \n \n \n {\n isAuthorized ? { this.state.passwordType } \n : \n \n \n }\n \n \n }\n {\n ( flow === AUTH_FLOW_APPLICATION || flow === AUTH_FLOW_IMPLICIT || flow === AUTH_FLOW_ACCESS_CODE || flow === AUTH_FLOW_PASSWORD ) &&\n ( !isAuthorized || isAuthorized && this.state.clientId) && \n \n {\n isAuthorized ? ****** \n : \n \n \n }\n \n }\n\n {\n ( (flow === AUTH_FLOW_APPLICATION || flow === AUTH_FLOW_ACCESS_CODE || flow === AUTH_FLOW_PASSWORD) && \n \n {\n isAuthorized ? ****** \n : \n \n \n }\n\n \n )}\n\n {\n !isAuthorized && scopes && scopes.size ?
    \n

    \n Scopes:\n select all\n select none\n

    \n { scopes.map((description, name) => {\n return (\n \n
    \n \n \n
    \n
    \n )\n }).toArray()\n }\n
    : null\n }\n\n {\n errors.valueSeq().map( (error, key) => {\n return \n } )\n }\n
    \n { isValid &&\n ( isAuthorized ? \n : \n )\n }\n \n
    \n\n
    \n )\n }\n}\n","import parseUrl from \"url-parse\"\nimport Im from \"immutable\"\nimport { btoa, sanitizeUrl, generateCodeVerifier, createCodeChallenge } from \"core/utils\"\n\nexport default function authorize ( { auth, authActions, errActions, configs, authConfigs={}, currentServer } ) {\n let { schema, scopes, name, clientId } = auth\n let flow = schema.get(\"flow\")\n let query = []\n\n switch (flow) {\n case \"password\":\n authActions.authorizePassword(auth)\n return\n\n case \"application\":\n authActions.authorizeApplication(auth)\n return\n\n case \"accessCode\":\n query.push(\"response_type=code\")\n break\n\n case \"implicit\":\n query.push(\"response_type=token\")\n break\n\n case \"clientCredentials\":\n case \"client_credentials\":\n // OAS3\n authActions.authorizeApplication(auth)\n return\n\n case \"authorizationCode\":\n case \"authorization_code\":\n // OAS3\n query.push(\"response_type=code\")\n break\n }\n\n if (typeof clientId === \"string\") {\n query.push(\"client_id=\" + encodeURIComponent(clientId))\n }\n\n let redirectUrl = configs.oauth2RedirectUrl\n\n // todo move to parser\n if (typeof redirectUrl === \"undefined\") {\n errActions.newAuthErr( {\n authId: name,\n source: \"validation\",\n level: \"error\",\n message: \"oauth2RedirectUrl configuration is not passed. Oauth2 authorization cannot be performed.\"\n })\n return\n }\n query.push(\"redirect_uri=\" + encodeURIComponent(redirectUrl))\n\n let scopesArray = []\n if (Array.isArray(scopes)) {\n scopesArray = scopes\n } else if (Im.List.isList(scopes)) {\n scopesArray = scopes.toArray()\n }\n\n if (scopesArray.length > 0) {\n let scopeSeparator = authConfigs.scopeSeparator || \" \"\n\n query.push(\"scope=\" + encodeURIComponent(scopesArray.join(scopeSeparator)))\n }\n\n let state = btoa(new Date())\n\n query.push(\"state=\" + encodeURIComponent(state))\n\n if (typeof authConfigs.realm !== \"undefined\") {\n query.push(\"realm=\" + encodeURIComponent(authConfigs.realm))\n }\n\n if ((flow === \"authorizationCode\" || flow === \"authorization_code\" || flow === \"accessCode\") && authConfigs.usePkceWithAuthorizationCodeGrant) {\n const codeVerifier = generateCodeVerifier()\n const codeChallenge = createCodeChallenge(codeVerifier)\n\n query.push(\"code_challenge=\" + codeChallenge)\n query.push(\"code_challenge_method=S256\")\n\n // storing the Code Verifier so it can be sent to the token endpoint\n // when exchanging the Authorization Code for an Access Token\n auth.codeVerifier = codeVerifier\n }\n\n let { additionalQueryStringParams } = authConfigs\n\n for (let key in additionalQueryStringParams) {\n if (typeof additionalQueryStringParams[key] !== \"undefined\") {\n query.push([key, additionalQueryStringParams[key]].map(encodeURIComponent).join(\"=\"))\n }\n }\n\n const authorizationUrl = schema.get(\"authorizationUrl\")\n let sanitizedAuthorizationUrl\n if (currentServer) {\n // OpenAPI 3\n sanitizedAuthorizationUrl = parseUrl(\n sanitizeUrl(authorizationUrl),\n currentServer,\n true\n ).toString()\n } else {\n sanitizedAuthorizationUrl = sanitizeUrl(authorizationUrl)\n }\n let url = [sanitizedAuthorizationUrl, query.join(\"&\")].join(authorizationUrl.indexOf(\"?\") === -1 ? \"?\" : \"&\")\n\n // pass action authorizeOauth2 and authentication data through window\n // to authorize with oauth2\n\n let callback\n if (flow === \"implicit\") {\n callback = authActions.preAuthorizeImplicit\n } else if (authConfigs.useBasicAuthenticationWithAccessCodeGrant) {\n callback = authActions.authorizeAccessCodeWithBasicAuthentication\n } else {\n callback = authActions.authorizeAccessCodeWithFormParams\n }\n\n authActions.authPopup(url, {\n auth: auth,\n state: state,\n redirectUrl: redirectUrl,\n callback: callback,\n errCb: errActions.newAuthErr\n })\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class Clear extends Component {\n\n onClick =() => {\n let { specActions, path, method } = this.props\n specActions.clearResponse( path, method )\n specActions.clearRequest( path, method )\n }\n\n render(){\n return (\n \n )\n }\n\n static propTypes = {\n specActions: PropTypes.object.isRequired,\n path: PropTypes.string.isRequired,\n method: PropTypes.string.isRequired,\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nconst Headers = ( { headers } )=>{\n return (\n
    \n
    Response headers
    \n
    {headers}
    \n
    )\n}\nHeaders.propTypes = {\n headers: PropTypes.array.isRequired\n}\n\nconst Duration = ( { duration } ) => {\n return (\n
    \n
    Request duration
    \n
    {duration} ms
    \n
    \n )\n}\nDuration.propTypes = {\n duration: PropTypes.number.isRequired\n}\n\n\nexport default class LiveResponse extends React.Component {\n static propTypes = {\n response: ImPropTypes.map,\n path: PropTypes.string.isRequired,\n method: PropTypes.string.isRequired,\n displayRequestDuration: PropTypes.bool.isRequired,\n specSelectors: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired\n }\n\n shouldComponentUpdate(nextProps) {\n // BUG: props.response is always coming back as a new Immutable instance\n // same issue as responses.jsx (tryItOutResponse)\n return this.props.response !== nextProps.response\n || this.props.path !== nextProps.path\n || this.props.method !== nextProps.method\n || this.props.displayRequestDuration !== nextProps.displayRequestDuration\n }\n\n render() {\n const { response, getComponent, getConfigs, displayRequestDuration, specSelectors, path, method } = this.props\n const { showMutatedRequest, requestSnippetsEnabled } = getConfigs()\n\n const curlRequest = showMutatedRequest ? specSelectors.mutatedRequestFor(path, method) : specSelectors.requestFor(path, method)\n const status = response.get(\"status\")\n const url = curlRequest.get(\"url\")\n const headers = response.get(\"headers\").toJS()\n const notDocumented = response.get(\"notDocumented\")\n const isError = response.get(\"error\")\n const body = response.get(\"text\")\n const duration = response.get(\"duration\")\n const headersKeys = Object.keys(headers)\n const contentType = headers[\"content-type\"] || headers[\"Content-Type\"]\n\n const ResponseBody = getComponent(\"responseBody\")\n const returnObject = headersKeys.map(key => {\n var joinedHeaders = Array.isArray(headers[key]) ? headers[key].join() : headers[key]\n return {key}: {joinedHeaders} \n })\n const hasHeaders = returnObject.length !== 0\n const Markdown = getComponent(\"Markdown\", true)\n const RequestSnippets = getComponent(\"RequestSnippets\", true)\n const Curl = getComponent(\"curl\")\n\n return (\n
    \n { curlRequest && (requestSnippetsEnabled === true || requestSnippetsEnabled === \"true\"\n ? \n : ) }\n { url &&
    \n
    \n

    Request URL

    \n
    {url}
    \n
    \n
    \n }\n

    Server response

    \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    CodeDetails
    \n { status }\n {\n notDocumented ?
    \n Undocumented \n
    \n : null\n }\n
    \n {\n isError ? \n : null\n }\n {\n body ? \n : null\n }\n {\n hasHeaders ? : null\n }\n {\n displayRequestDuration && duration ? : null\n }\n
    \n
    \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport Im from \"immutable\"\n\nexport default class Operations extends React.Component {\n\n static propTypes = {\n specSelectors: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n oas3Selectors: PropTypes.func.isRequired,\n layoutSelectors: PropTypes.object.isRequired,\n layoutActions: PropTypes.object.isRequired,\n authActions: PropTypes.object.isRequired,\n authSelectors: PropTypes.object.isRequired,\n getConfigs: PropTypes.func.isRequired,\n fn: PropTypes.func.isRequired\n }\n\n render() {\n let {\n specSelectors,\n } = this.props\n\n const taggedOps = specSelectors.taggedOperations()\n\n if(taggedOps.size === 0) {\n return

    No operations defined in spec!

    \n }\n\n return (\n
    \n { taggedOps.map(this.renderOperationTag).toArray() }\n { taggedOps.size < 1 ?

    No operations defined in spec!

    : null }\n
    \n )\n }\n\n renderOperationTag = (tagObj, tag) => {\n const {\n specSelectors,\n getComponent,\n oas3Selectors,\n layoutSelectors,\n layoutActions,\n getConfigs,\n } = this.props\n const validOperationMethods = specSelectors.validOperationMethods()\n const OperationContainer = getComponent(\"OperationContainer\", true)\n const OperationTag = getComponent(\"OperationTag\")\n const operations = tagObj.get(\"operations\")\n return (\n \n
    \n {\n operations.map(op => {\n const path = op.get(\"path\")\n const method = op.get(\"method\")\n const specPath = Im.List([\"paths\", path, method])\n\n if (validOperationMethods.indexOf(method) === -1) {\n return null\n }\n\n return (\n \n )\n }).toArray()\n }\n
    \n \n )\n }\n\n}\n\nOperations.propTypes = {\n layoutActions: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n layoutSelectors: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n fn: PropTypes.object.isRequired\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport Im from \"immutable\"\nimport { createDeepLinkPath, escapeDeepLinkPath, sanitizeUrl } from \"core/utils\"\nimport { safeBuildUrl } from \"core/utils/url\"\nimport { isFunc } from \"core/utils\"\n\nexport default class OperationTag extends React.Component {\n\n static defaultProps = {\n tagObj: Im.fromJS({}),\n tag: \"\",\n }\n\n static propTypes = {\n tagObj: ImPropTypes.map.isRequired,\n tag: PropTypes.string.isRequired,\n\n oas3Selectors: PropTypes.func.isRequired,\n layoutSelectors: PropTypes.object.isRequired,\n layoutActions: PropTypes.object.isRequired,\n\n getConfigs: PropTypes.func.isRequired,\n getComponent: PropTypes.func.isRequired,\n\n specUrl: PropTypes.string.isRequired,\n\n children: PropTypes.element,\n }\n\n render() {\n const {\n tagObj,\n tag,\n children,\n oas3Selectors,\n layoutSelectors,\n layoutActions,\n getConfigs,\n getComponent,\n specUrl,\n } = this.props\n\n let {\n docExpansion,\n deepLinking,\n } = getConfigs()\n\n const isDeepLinkingEnabled = deepLinking && deepLinking !== \"false\"\n\n const Collapse = getComponent(\"Collapse\")\n const Markdown = getComponent(\"Markdown\", true)\n const DeepLink = getComponent(\"DeepLink\")\n const Link = getComponent(\"Link\")\n\n let tagDescription = tagObj.getIn([\"tagDetails\", \"description\"], null)\n let tagExternalDocsDescription = tagObj.getIn([\"tagDetails\", \"externalDocs\", \"description\"])\n let rawTagExternalDocsUrl = tagObj.getIn([\"tagDetails\", \"externalDocs\", \"url\"])\n let tagExternalDocsUrl\n if (isFunc(oas3Selectors) && isFunc(oas3Selectors.selectedServer)) {\n tagExternalDocsUrl = safeBuildUrl(rawTagExternalDocsUrl, specUrl, { selectedServer: oas3Selectors.selectedServer() })\n } else {\n tagExternalDocsUrl = rawTagExternalDocsUrl\n }\n\n let isShownKey = [\"operations-tag\", tag]\n let showTag = layoutSelectors.isShown(isShownKey, docExpansion === \"full\" || docExpansion === \"list\")\n\n return (\n
    \n\n layoutActions.show(isShownKey, !showTag)}\n className={!tagDescription ? \"opblock-tag no-desc\" : \"opblock-tag\"}\n id={isShownKey.map(v => escapeDeepLinkPath(v)).join(\"-\")}\n data-tag={tag}\n data-is-open={showTag}\n >\n \n {!tagDescription ? :\n \n \n \n }\n\n {!tagExternalDocsUrl ? null :\n
    \n \n e.stopPropagation()}\n target=\"_blank\"\n >{tagExternalDocsDescription || tagExternalDocsUrl}\n \n
    \n }\n\n\n layoutActions.show(isShownKey, !showTag)}>\n\n \n \n \n \n \n\n \n {children}\n \n
    \n )\n }\n}\n","import React, { PureComponent } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { getList } from \"core/utils\"\nimport { getExtensions, sanitizeUrl, escapeDeepLinkPath } from \"core/utils\"\nimport { safeBuildUrl } from \"core/utils/url\"\nimport { Iterable, List } from \"immutable\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\n\nexport default class Operation extends PureComponent {\n static propTypes = {\n specPath: ImPropTypes.list.isRequired,\n operation: PropTypes.instanceOf(Iterable).isRequired,\n summary: PropTypes.string,\n response: PropTypes.instanceOf(Iterable),\n request: PropTypes.instanceOf(Iterable),\n\n toggleShown: PropTypes.func.isRequired,\n onTryoutClick: PropTypes.func.isRequired,\n onResetClick: PropTypes.func.isRequired,\n onCancelClick: PropTypes.func.isRequired,\n onExecute: PropTypes.func.isRequired,\n\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n authActions: PropTypes.object,\n authSelectors: PropTypes.object,\n specActions: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n layoutActions: PropTypes.object.isRequired,\n layoutSelectors: PropTypes.object.isRequired,\n fn: PropTypes.object.isRequired\n }\n\n static defaultProps = {\n operation: null,\n response: null,\n request: null,\n specPath: List(),\n summary: \"\"\n }\n\n render() {\n let {\n specPath,\n response,\n request,\n toggleShown,\n onTryoutClick,\n onResetClick,\n onCancelClick,\n onExecute,\n fn,\n getComponent,\n getConfigs,\n specActions,\n specSelectors,\n authActions,\n authSelectors,\n oas3Actions,\n oas3Selectors\n } = this.props\n let operationProps = this.props.operation\n\n let {\n deprecated,\n isShown,\n path,\n method,\n op,\n tag,\n operationId,\n allowTryItOut,\n displayRequestDuration,\n tryItOutEnabled,\n executeInProgress\n } = operationProps.toJS()\n\n let {\n description,\n externalDocs,\n schemes\n } = op\n\n const externalDocsUrl = externalDocs ? safeBuildUrl(externalDocs.url, specSelectors.url(), { selectedServer: oas3Selectors.selectedServer() }) : \"\"\n let operation = operationProps.getIn([\"op\"])\n let responses = operation.get(\"responses\")\n let parameters = getList(operation, [\"parameters\"])\n let operationScheme = specSelectors.operationScheme(path, method)\n let isShownKey = [\"operations\", tag, operationId]\n let extensions = getExtensions(operation)\n\n const Responses = getComponent(\"responses\")\n const Parameters = getComponent( \"parameters\" )\n const Execute = getComponent( \"execute\" )\n const Clear = getComponent( \"clear\" )\n const Collapse = getComponent( \"Collapse\" )\n const Markdown = getComponent(\"Markdown\", true)\n const Schemes = getComponent( \"schemes\" )\n const OperationServers = getComponent( \"OperationServers\" )\n const OperationExt = getComponent( \"OperationExt\" )\n const OperationSummary = getComponent( \"OperationSummary\" )\n const Link = getComponent( \"Link\" )\n\n const { showExtensions } = getConfigs()\n\n // Merge in Live Response\n if(responses && response && response.size > 0) {\n let notDocumented = !responses.get(String(response.get(\"status\"))) && !responses.get(\"default\")\n response = response.set(\"notDocumented\", notDocumented)\n }\n\n let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method )\n\n const validationErrors = specSelectors.validationErrors([path, method])\n\n return (\n
    \n \n \n
    \n { (operation && operation.size) || operation === null ? null :\n \n }\n { deprecated &&

    Warning: Deprecated

    }\n { description &&\n
    \n
    \n \n
    \n
    \n }\n {\n externalDocsUrl ?\n
    \n

    Find more details

    \n
    \n {externalDocs.description &&\n \n \n \n }\n {externalDocsUrl}\n
    \n
    : null\n }\n\n { !operation || !operation.size ? null :\n \n }\n\n { !tryItOutEnabled ? null :\n \n }\n\n {!tryItOutEnabled || !allowTryItOut ? null : schemes && schemes.size ?
    \n \n
    : null\n }\n\n { !tryItOutEnabled || !allowTryItOut || validationErrors.length <= 0 ? null :
    \n Please correct the following validation errors and try again.\n
      \n { validationErrors.map((error, index) =>
    • { error }
    • ) }\n
    \n
    \n }\n\n
    \n { !tryItOutEnabled || !allowTryItOut ? null :\n\n \n }\n\n { (!tryItOutEnabled || !response || !allowTryItOut) ? null :\n \n }\n
    \n\n {executeInProgress ?
    : null}\n\n { !responses ? null :\n \n }\n\n { !showExtensions || !extensions.size ? null :\n \n }\n
    \n
    \n
    \n )\n }\n\n}\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_toString_da931f05__[\"default\"] });","import React, { PureComponent } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { Iterable, List } from \"immutable\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport toString from \"lodash/toString\"\n\n\nexport default class OperationSummary extends PureComponent {\n\n static propTypes = {\n specPath: ImPropTypes.list.isRequired,\n operationProps: PropTypes.instanceOf(Iterable).isRequired,\n isShown: PropTypes.bool.isRequired,\n toggleShown: PropTypes.func.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n authActions: PropTypes.object,\n authSelectors: PropTypes.object,\n }\n\n static defaultProps = {\n operationProps: null,\n specPath: List(),\n summary: \"\"\n }\n\n render() {\n\n let {\n isShown,\n toggleShown,\n getComponent,\n authActions,\n authSelectors,\n operationProps,\n specPath,\n } = this.props\n\n let {\n summary,\n isAuthorized,\n method,\n op,\n showSummary,\n path,\n operationId,\n originalOperationId,\n displayOperationId,\n } = operationProps.toJS()\n\n let {\n summary: resolvedSummary,\n } = op\n\n let security = operationProps.get(\"security\")\n\n const AuthorizeOperationBtn = getComponent(\"authorizeOperationBtn\")\n const OperationSummaryMethod = getComponent(\"OperationSummaryMethod\")\n const OperationSummaryPath = getComponent(\"OperationSummaryPath\")\n const JumpToPath = getComponent(\"JumpToPath\", true)\n const CopyToClipboardBtn = getComponent(\"CopyToClipboardBtn\", true)\n\n const hasSecurity = security && !!security.count()\n const securityIsOptional = hasSecurity && security.size === 1 && security.first().isEmpty()\n const allowAnonymous = !hasSecurity || securityIsOptional\n return (\n
    \n \n \n \n\n {!showSummary ? null :\n
    \n {toString(resolvedSummary || summary)}\n
    \n }\n\n {displayOperationId && (originalOperationId || operationId) ? {originalOperationId || operationId} : null}\n\n \n \n \n \n\n {\n allowAnonymous ? null :\n {\n const applicableDefinitions = authSelectors.definitionsForRequirements(security)\n authActions.showDefinitions(applicableDefinitions)\n }}\n />\n }\n \n {/* TODO: use wrapComponents here, swagger-ui doesn't care about jumpToPath */}\n
    \n )\n\n }\n}\n","import React, { PureComponent } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { Iterable } from \"immutable\"\n\nexport default class OperationSummaryMethod extends PureComponent {\n\n static propTypes = {\n operationProps: PropTypes.instanceOf(Iterable).isRequired,\n method: PropTypes.string.isRequired,\n }\n\n static defaultProps = {\n operationProps: null,\n }\n render() {\n\n let {\n method,\n } = this.props\n\n return (\n {method.toUpperCase()}\n )\n }\n}\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_splice_d746fc5e__[\"default\"] });","import React, { PureComponent } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { Iterable } from \"immutable\"\nimport { createDeepLinkPath } from \"core/utils\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class OperationSummaryPath extends PureComponent{\n\n static propTypes = {\n specPath: ImPropTypes.list.isRequired,\n operationProps: PropTypes.instanceOf(Iterable).isRequired,\n getComponent: PropTypes.func.isRequired,\n }\n\n render(){\n let {\n getComponent,\n operationProps,\n } = this.props\n\n\n let {\n deprecated,\n isShown,\n path,\n tag,\n operationId,\n isDeepLinkingEnabled,\n } = operationProps.toJS()\n\n /**\n * Add word-break elements between each segment, before the slash\n * to allow browsers an opportunity to break long paths into sensible segments.\n */\n const pathParts = path.split(/(?=\\/)/g)\n for (let i = 1; i < pathParts.length; i += 2) {\n pathParts.splice(i, 0, )\n }\n\n const DeepLink = getComponent( \"DeepLink\" )\n\n return(\n \n \n \n\n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport const OperationExt = ({ extensions, getComponent }) => {\n let OperationExtRow = getComponent(\"OperationExtRow\")\n return (\n
    \n
    \n

    Extensions

    \n
    \n
    \n\n \n \n \n \n \n \n \n \n {\n extensions.entrySeq().map(([k, v]) => )\n }\n \n
    FieldValue
    \n
    \n
    \n )\n}\nOperationExt.propTypes = {\n extensions: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired\n}\n\nexport default OperationExt\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport const OperationExtRow = ({ xKey, xVal }) => {\n const xNormalizedValue = !xVal ? null : xVal.toJS ? xVal.toJS() : xVal\n\n return (\n { xKey }\n { JSON.stringify(xNormalizedValue) }\n )\n}\nOperationExtRow.propTypes = {\n xKey: PropTypes.string,\n xVal: PropTypes.any\n}\n\nexport default OperationExtRow\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_js_file_download_bd23dbb6__[\"default\"] });","import React, { useRef, useEffect } from \"react\"\nimport PropTypes from \"prop-types\"\nimport cx from \"classnames\"\nimport {SyntaxHighlighter, getStyle} from \"core/syntax-highlighting\"\nimport get from \"lodash/get\"\nimport isFunction from \"lodash/isFunction\"\nimport saveAs from \"js-file-download\"\nimport { CopyToClipboard } from \"react-copy-to-clipboard\"\n\nconst HighlightCode = ({value, fileName, className, downloadable, getConfigs, canCopy, language}) => {\n const config = isFunction(getConfigs) ? getConfigs() : null\n const canSyntaxHighlight = get(config, \"syntaxHighlight\") !== false && get(config, \"syntaxHighlight.activated\", true)\n const rootRef = useRef(null)\n\n useEffect(() => {\n const childNodes = Array\n .from(rootRef.current.childNodes)\n .filter(node => !!node.nodeType && node.classList.contains(\"microlight\"))\n\n // eslint-disable-next-line no-use-before-define\n childNodes.forEach(node => node.addEventListener(\"mousewheel\", handlePreventYScrollingBeyondElement, { passive: false }))\n\n return () => {\n // eslint-disable-next-line no-use-before-define\n childNodes.forEach(node => node.removeEventListener(\"mousewheel\", handlePreventYScrollingBeyondElement))\n }\n }, [value, className, language])\n\n const handleDownload = () => {\n saveAs(value, fileName)\n }\n\n const handlePreventYScrollingBeyondElement = (e) => {\n const { target, deltaY } = e\n const { scrollHeight: contentHeight, offsetHeight: visibleHeight, scrollTop } = target\n const scrollOffset = visibleHeight + scrollTop\n const isElementScrollable = contentHeight > visibleHeight\n const isScrollingPastTop = scrollTop === 0 && deltaY < 0\n const isScrollingPastBottom = scrollOffset >= contentHeight && deltaY > 0\n\n if (isElementScrollable && (isScrollingPastTop || isScrollingPastBottom)) {\n e.preventDefault()\n }\n }\n\n return (\n
    \n {!downloadable ? null :\n
    \n Download\n
    \n }\n\n {canCopy && (\n
    \n
    \n )}\n\n {canSyntaxHighlight\n ? \n {value}\n \n :
    {value}
    \n }\n\n
    \n )\n}\n\nHighlightCode.propTypes = {\n value: PropTypes.string.isRequired,\n getConfigs: PropTypes.func.isRequired,\n className: PropTypes.string,\n downloadable: PropTypes.bool,\n fileName: PropTypes.string,\n language: PropTypes.string,\n canCopy: PropTypes.bool\n}\n\nHighlightCode.defaultProps = {\n fileName: \"response.txt\"\n}\n\nexport default HighlightCode\n","import React from \"react\"\nimport { fromJS, Iterable } from \"immutable\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { defaultStatusCode, getAcceptControllingResponse } from \"core/utils\"\nimport createHtmlReadyId from \"../../helpers/create-html-ready-id\"\n\nexport default class Responses extends React.Component {\n static propTypes = {\n tryItOutResponse: PropTypes.instanceOf(Iterable),\n responses: PropTypes.instanceOf(Iterable).isRequired,\n produces: PropTypes.instanceOf(Iterable),\n producesValue: PropTypes.any,\n displayRequestDuration: PropTypes.bool.isRequired,\n path: PropTypes.string.isRequired,\n method: PropTypes.string.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n specPath: ImPropTypes.list.isRequired,\n fn: PropTypes.object.isRequired\n }\n\n static defaultProps = {\n tryItOutResponse: null,\n produces: fromJS([\"application/json\"]),\n displayRequestDuration: false\n }\n\n // These performance-enhancing checks were disabled as part of Multiple Examples\n // because they were causing data-consistency issues\n //\n // shouldComponentUpdate(nextProps) {\n // // BUG: props.tryItOutResponse is always coming back as a new Immutable instance\n // let render = this.props.tryItOutResponse !== nextProps.tryItOutResponse\n // || this.props.responses !== nextProps.responses\n // || this.props.produces !== nextProps.produces\n // || this.props.producesValue !== nextProps.producesValue\n // || this.props.displayRequestDuration !== nextProps.displayRequestDuration\n // || this.props.path !== nextProps.path\n // || this.props.method !== nextProps.method\n // return render\n // }\n\n\tonChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue([this.props.path, this.props.method], val)\n\n onResponseContentTypeChange = ({ controlsAcceptHeader, value }) => {\n const { oas3Actions, path, method } = this.props\n if(controlsAcceptHeader) {\n oas3Actions.setResponseContentType({\n value,\n path,\n method\n })\n }\n }\n\n render() {\n let {\n responses,\n tryItOutResponse,\n getComponent,\n getConfigs,\n specSelectors,\n fn,\n producesValue,\n displayRequestDuration,\n specPath,\n path,\n method,\n oas3Selectors,\n oas3Actions,\n } = this.props\n let defaultCode = defaultStatusCode( responses )\n\n const ContentType = getComponent( \"contentType\" )\n const LiveResponse = getComponent( \"liveResponse\" )\n const Response = getComponent( \"response\" )\n\n let produces = this.props.produces && this.props.produces.size ? this.props.produces : Responses.defaultProps.produces\n\n const isSpecOAS3 = specSelectors.isOAS3()\n\n const acceptControllingResponse = isSpecOAS3 ?\n getAcceptControllingResponse(responses) : null\n\n const regionId = createHtmlReadyId(`${method}${path}_responses`)\n const controlId = `${regionId}_select`\n\n return (\n
    \n
    \n

    Responses

    \n { specSelectors.isOAS3() ? null : }\n
    \n
    \n {\n !tryItOutResponse ? null\n :
    \n \n

    Responses

    \n
    \n\n }\n\n \n \n \n \n \n { specSelectors.isOAS3() ? : null }\n \n \n \n {\n responses.entrySeq().map( ([code, response]) => {\n\n let className = tryItOutResponse && tryItOutResponse.get(\"status\") == code ? \"response_current\" : \"\"\n return (\n \n )\n }).toArray()\n }\n \n
    CodeDescriptionLinks
    \n
    \n
    \n )\n }\n}\n","/**\n * Replace invalid characters from a string to create an html-ready ID\n *\n * @param {string} id A string that may contain invalid characters for the HTML ID attribute\n * @param {string} [replacement=_] The string to replace invalid characters with; \"_\" by default\n * @return {string} Information about the parameter schema\n */\nexport default function createHtmlReadyId(id, replacement = \"_\") {\n return id.replace(/[^\\w-]/g, replacement)\n}\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE__babel_runtime_corejs3_core_js_stable_instance_values_a68750d2__[\"default\"] });","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport cx from \"classnames\"\nimport { fromJS, Seq, Iterable, List, Map } from \"immutable\"\nimport { getExtensions, fromJSOrdered, stringify } from \"core/utils\"\nimport { getKnownSyntaxHighlighterLanguage } from \"core/utils/jsonParse\"\n\n\nconst getExampleComponent = ( sampleResponse, HighlightCode, getConfigs ) => {\n if (\n sampleResponse !== undefined &&\n sampleResponse !== null\n ) {\n let language = null\n let testValueForJson = getKnownSyntaxHighlighterLanguage(sampleResponse)\n if (testValueForJson) {\n language = \"json\"\n }\n return
    \n \n
    \n }\n return null\n}\n\nexport default class Response extends React.Component {\n constructor(props, context) {\n super(props, context)\n\n this.state = {\n responseContentType: \"\",\n }\n }\n\n static propTypes = {\n path: PropTypes.string.isRequired,\n method: PropTypes.string.isRequired,\n code: PropTypes.string.isRequired,\n response: PropTypes.instanceOf(Iterable),\n className: PropTypes.string,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n specPath: ImPropTypes.list.isRequired,\n fn: PropTypes.object.isRequired,\n contentType: PropTypes.string,\n activeExamplesKey: PropTypes.string,\n controlsAcceptHeader: PropTypes.bool,\n onContentTypeChange: PropTypes.func\n }\n\n static defaultProps = {\n response: fromJS({}),\n onContentTypeChange: () => {}\n }\n\n _onContentTypeChange = (value) => {\n const { onContentTypeChange, controlsAcceptHeader } = this.props\n this.setState({ responseContentType: value })\n onContentTypeChange({\n value: value,\n controlsAcceptHeader\n })\n }\n\n getTargetExamplesKey = () => {\n const { response, contentType, activeExamplesKey } = this.props\n\n const activeContentType = this.state.responseContentType || contentType\n const activeMediaType = response.getIn([\"content\", activeContentType], Map({}))\n const examplesForMediaType = activeMediaType.get(\"examples\", null)\n\n const firstExamplesKey = examplesForMediaType.keySeq().first()\n return activeExamplesKey || firstExamplesKey\n }\n\n render() {\n let {\n path,\n method,\n code,\n response,\n className,\n specPath,\n fn,\n getComponent,\n getConfigs,\n specSelectors,\n contentType,\n controlsAcceptHeader,\n oas3Actions,\n } = this.props\n\n let { inferSchema, getSampleSchema } = fn\n let isOAS3 = specSelectors.isOAS3()\n const { showExtensions } = getConfigs()\n\n let extensions = showExtensions ? getExtensions(response) : null\n let headers = response.get(\"headers\")\n let links = response.get(\"links\")\n const ResponseExtension = getComponent(\"ResponseExtension\")\n const Headers = getComponent(\"headers\")\n const HighlightCode = getComponent(\"highlightCode\")\n const ModelExample = getComponent(\"modelExample\")\n const Markdown = getComponent(\"Markdown\", true)\n const OperationLink = getComponent(\"operationLink\")\n const ContentType = getComponent(\"contentType\")\n const ExamplesSelect = getComponent(\"ExamplesSelect\")\n const Example = getComponent(\"Example\")\n\n\n var schema, specPathWithPossibleSchema\n\n const activeContentType = this.state.responseContentType || contentType\n const activeMediaType = response.getIn([\"content\", activeContentType], Map({}))\n const examplesForMediaType = activeMediaType.get(\"examples\", null)\n\n // Goal: find a schema value for `schema`\n if(isOAS3) {\n const oas3SchemaForContentType = activeMediaType.get(\"schema\")\n\n schema = oas3SchemaForContentType ? inferSchema(oas3SchemaForContentType.toJS()) : null\n specPathWithPossibleSchema = oas3SchemaForContentType ? List([\"content\", this.state.responseContentType, \"schema\"]) : specPath\n } else {\n schema = response.get(\"schema\")\n specPathWithPossibleSchema = response.has(\"schema\") ? specPath.push(\"schema\") : specPath\n }\n\n let mediaTypeExample\n let shouldOverrideSchemaExample = false\n let sampleSchema\n let sampleGenConfig = {\n includeReadOnly: true\n }\n\n // Goal: find an example value for `sampleResponse`\n if(isOAS3) {\n sampleSchema = activeMediaType.get(\"schema\")?.toJS()\n if(examplesForMediaType) {\n const targetExamplesKey = this.getTargetExamplesKey()\n const targetExample = examplesForMediaType\n .get(targetExamplesKey, Map({}))\n const getMediaTypeExample = (targetExample) =>\n targetExample.get(\"value\")\n mediaTypeExample = getMediaTypeExample(targetExample)\n if(mediaTypeExample === undefined) {\n mediaTypeExample = getMediaTypeExample(examplesForMediaType.values().next().value)\n }\n shouldOverrideSchemaExample = true\n } else if(activeMediaType.get(\"example\") !== undefined) {\n // use the example key's value\n mediaTypeExample = activeMediaType.get(\"example\")\n shouldOverrideSchemaExample = true\n }\n } else {\n sampleSchema = schema\n sampleGenConfig = {...sampleGenConfig, includeWriteOnly: true}\n const oldOASMediaTypeExample = response.getIn([\"examples\", activeContentType])\n if(oldOASMediaTypeExample) {\n mediaTypeExample = oldOASMediaTypeExample\n shouldOverrideSchemaExample = true\n }\n }\n\n const sampleResponse = getSampleSchema(\n sampleSchema,\n activeContentType,\n sampleGenConfig,\n shouldOverrideSchemaExample ? mediaTypeExample : undefined\n )\n\n let example = getExampleComponent( sampleResponse, HighlightCode, getConfigs )\n\n return (\n \n \n { code }\n \n \n\n
    \n \n
    \n\n { !showExtensions || !extensions.size ? null : extensions.entrySeq().map(([key, v]) => )}\n\n {isOAS3 && response.get(\"content\") ? (\n
    \n \n \n Media type\n \n \n {controlsAcceptHeader ? (\n \n Controls Accept header.\n \n ) : null}\n \n {examplesForMediaType ? (\n
    \n \n Examples\n \n \n oas3Actions.setActiveExamplesMember({\n name: key,\n pathMethod: [path, method],\n contextType: \"responses\",\n contextName: code\n })\n }\n showLabels={false}\n />\n
    \n ) : null}\n
    \n ) : null}\n\n { example || schema ? (\n \n ) : null }\n\n { isOAS3 && examplesForMediaType ? (\n \n ) : null}\n\n { headers ? (\n \n ) : null}\n\n \n {isOAS3 ? \n { links ?\n links.toSeq().entrySeq().map(([key, link]) => {\n return \n })\n : No links}\n : null}\n \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport const ResponseExtension = ({ xKey, xVal }) => {\n return
    { xKey }: { String(xVal) }
    \n}\nResponseExtension.propTypes = {\n xKey: PropTypes.string,\n xVal: PropTypes.any\n}\n\nexport default ResponseExtension\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_xml_but_prettier_2ed4d5cb__[\"default\"] });","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_lodash_toLower_c29ee2b0__[\"default\"] });","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport formatXml from \"xml-but-prettier\"\nimport toLower from \"lodash/toLower\"\nimport { extractFileNameFromContentDispositionHeader } from \"core/utils\"\nimport { getKnownSyntaxHighlighterLanguage } from \"core/utils/jsonParse\"\nimport win from \"core/window\"\n\nexport default class ResponseBody extends React.PureComponent {\n state = {\n parsedContent: null\n }\n\n static propTypes = {\n content: PropTypes.any.isRequired,\n contentType: PropTypes.string,\n getConfigs: PropTypes.func.isRequired,\n getComponent: PropTypes.func.isRequired,\n headers: PropTypes.object,\n url: PropTypes.string\n }\n\n updateParsedContent = (prevContent) => {\n const { content } = this.props\n\n if(prevContent === content) {\n return\n }\n\n if(content && content instanceof Blob) {\n var reader = new FileReader()\n reader.onload = () => {\n this.setState({\n parsedContent: reader.result\n })\n }\n reader.readAsText(content)\n } else {\n this.setState({\n parsedContent: content.toString()\n })\n }\n }\n\n componentDidMount() {\n this.updateParsedContent(null)\n }\n\n componentDidUpdate(prevProps) {\n this.updateParsedContent(prevProps.content)\n }\n\n render() {\n let { content, contentType, url, headers={}, getConfigs, getComponent } = this.props\n const { parsedContent } = this.state\n const HighlightCode = getComponent(\"highlightCode\")\n const downloadName = \"response_\" + new Date().getTime()\n let body, bodyEl\n url = url || \"\"\n\n if (\n (/^application\\/octet-stream/i.test(contentType) ||\n (headers[\"Content-Disposition\"] && /attachment/i.test(headers[\"Content-Disposition\"])) ||\n (headers[\"content-disposition\"] && /attachment/i.test(headers[\"content-disposition\"])) ||\n (headers[\"Content-Description\"] && /File Transfer/i.test(headers[\"Content-Description\"])) ||\n (headers[\"content-description\"] && /File Transfer/i.test(headers[\"content-description\"]))) &&\n content.size > 0\n ) {\n // Download\n\n if (\"Blob\" in window) {\n let type = contentType || \"text/html\"\n let blob = (content instanceof Blob) ? content : new Blob([content], {type: type})\n let href = window.URL.createObjectURL(blob)\n let fileName = url.substr(url.lastIndexOf(\"/\") + 1)\n let download = [type, fileName, href].join(\":\")\n\n // Use filename from response header,\n // First check if filename is quoted (e.g. contains space), if no, fallback to not quoted check\n let disposition = headers[\"content-disposition\"] || headers[\"Content-Disposition\"]\n if (typeof disposition !== \"undefined\") {\n let responseFilename = extractFileNameFromContentDispositionHeader(disposition)\n if (responseFilename !== null) {\n download = responseFilename\n }\n }\n\n if(win.navigator && win.navigator.msSaveOrOpenBlob) {\n bodyEl = \n } else {\n bodyEl = \n }\n } else {\n bodyEl =
    Download headers detected but your browser does not support downloading binary via XHR (Blob).
    \n }\n\n // Anything else (CORS)\n } else if (/json/i.test(contentType)) {\n // JSON\n let language = null\n let testValueForJson = getKnownSyntaxHighlighterLanguage(content)\n if (testValueForJson) {\n language = \"json\"\n }\n try {\n body = JSON.stringify(JSON.parse(content), null, \" \")\n } catch (error) {\n body = \"can't parse JSON. Raw result:\\n\\n\" + content\n }\n\n bodyEl = \n\n // XML\n } else if (/xml/i.test(contentType)) {\n body = formatXml(content, {\n textNodesOnSameLine: true,\n indentor: \" \"\n })\n bodyEl = \n\n // HTML or Plain Text\n } else if (toLower(contentType) === \"text/html\" || /text\\/plain/.test(contentType)) {\n bodyEl = \n\n // CSV\n } else if (toLower(contentType) === \"text/csv\" || /text\\/csv/.test(contentType)) {\n bodyEl = \n\n // Image\n } else if (/^image\\//i.test(contentType)) {\n if(contentType.includes(\"svg\")) {\n bodyEl =
    { content }
    \n } else {\n bodyEl = \n }\n\n // Audio\n } else if (/^audio\\//i.test(contentType)) {\n bodyEl =
    \n } else if (typeof content === \"string\") {\n bodyEl = \n } else if ( content.size > 0 ) {\n // We don't know the contentType, but there was some content returned\n if(parsedContent) {\n // We were able to squeeze something out of content\n // in `updateParsedContent`, so let's display it\n bodyEl =
    \n

    \n Unrecognized response type; displaying content as text.\n

    \n \n
    \n\n } else {\n // Give up\n bodyEl =

    \n Unrecognized response type; unable to display.\n

    \n }\n } else {\n // We don't know the contentType and there was no content returned\n bodyEl = null\n }\n\n return ( !bodyEl ? null :
    \n
    Response body
    \n { bodyEl }\n
    \n )\n }\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { Map, List } from \"immutable\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class Parameters extends Component {\n\n constructor(props) {\n super(props)\n this.state = {\n callbackVisible: false,\n parametersVisible: true,\n }\n }\n\n static propTypes = {\n parameters: ImPropTypes.list.isRequired,\n operation: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n fn: PropTypes.object.isRequired,\n tryItOutEnabled: PropTypes.bool,\n allowTryItOut: PropTypes.bool,\n onTryoutClick: PropTypes.func,\n onResetClick: PropTypes.func,\n onCancelClick: PropTypes.func,\n onChangeKey: PropTypes.array,\n pathMethod: PropTypes.array.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specPath: ImPropTypes.list.isRequired,\n }\n\n\n static defaultProps = {\n onTryoutClick: Function.prototype,\n onCancelClick: Function.prototype,\n tryItOutEnabled: false,\n allowTryItOut: true,\n onChangeKey: [],\n specPath: [],\n }\n\n onChange = (param, value, isXml) => {\n let {\n specActions: { changeParamByIdentity },\n onChangeKey,\n } = this.props\n\n changeParamByIdentity(onChangeKey, param, value, isXml)\n }\n\n onChangeConsumesWrapper = (val) => {\n let {\n specActions: { changeConsumesValue },\n onChangeKey,\n } = this.props\n\n changeConsumesValue(onChangeKey, val)\n }\n\n toggleTab = (tab) => {\n if (tab === \"parameters\") {\n return this.setState({\n parametersVisible: true,\n callbackVisible: false,\n })\n } else if (tab === \"callbacks\") {\n return this.setState({\n callbackVisible: true,\n parametersVisible: false,\n })\n }\n }\n \n onChangeMediaType = ({ value, pathMethod }) => {\n let { specActions, oas3Selectors, oas3Actions } = this.props\n const userHasEditedBody = oas3Selectors.hasUserEditedBody(...pathMethod)\n const shouldRetainRequestBodyValue = oas3Selectors.shouldRetainRequestBodyValue(...pathMethod)\n oas3Actions.setRequestContentType({ value, pathMethod })\n oas3Actions.initRequestBodyValidateError({ pathMethod })\n if (!userHasEditedBody) {\n if(!shouldRetainRequestBodyValue) {\n oas3Actions.setRequestBodyValue({ value: undefined, pathMethod })\n }\n specActions.clearResponse(...pathMethod)\n specActions.clearRequest(...pathMethod)\n specActions.clearValidateParams(pathMethod)\n }\n }\n\n render() {\n\n let {\n onTryoutClick,\n onResetClick,\n parameters,\n allowTryItOut,\n tryItOutEnabled,\n specPath,\n fn,\n getComponent,\n getConfigs,\n specSelectors,\n specActions,\n pathMethod,\n oas3Actions,\n oas3Selectors,\n operation,\n } = this.props\n\n const ParameterRow = getComponent(\"parameterRow\")\n const TryItOutButton = getComponent(\"TryItOutButton\")\n const ContentType = getComponent(\"contentType\")\n const Callbacks = getComponent(\"Callbacks\", true)\n const RequestBody = getComponent(\"RequestBody\", true)\n\n const isExecute = tryItOutEnabled && allowTryItOut\n const isOAS3 = specSelectors.isOAS3()\n\n\n const requestBody = operation.get(\"requestBody\")\n\n const groupedParametersArr = Object.values(parameters\n .reduce((acc, x) => {\n const key = x.get(\"in\")\n acc[key] ??= []\n acc[key].push(x)\n return acc\n }, {}))\n .reduce((acc, x) => acc.concat(x), [])\n\n const retainRequestBodyValueFlagForOperation = (f) => oas3Actions.setRetainRequestBodyValueFlag({ value: f, pathMethod })\n return (\n
    \n
    \n {isOAS3 ? (\n
    \n
    this.toggleTab(\"parameters\")}\n className={`tab-item ${this.state.parametersVisible && \"active\"}`}>\n

    Parameters

    \n
    \n {operation.get(\"callbacks\") ?\n (\n
    this.toggleTab(\"callbacks\")}\n className={`tab-item ${this.state.callbackVisible && \"active\"}`}>\n

    Callbacks

    \n
    \n ) : null\n }\n
    \n ) : (\n
    \n

    Parameters

    \n
    \n )}\n {allowTryItOut ? (\n onResetClick(pathMethod)}/>\n ) : null}\n
    \n {this.state.parametersVisible ?
    \n {!groupedParametersArr.length ?

    No parameters

    :\n
    \n \n \n \n \n \n \n \n \n {\n groupedParametersArr.map((parameter, i) => (\n \n ))\n }\n \n
    NameDescription
    \n
    \n }\n
    : null}\n\n {this.state.callbackVisible ?
    \n \n
    : null}\n {\n isOAS3 && requestBody && this.state.parametersVisible &&\n
    \n
    \n

    Request\n body

    \n \n
    \n
    \n {\n this.props.oas3Actions.setActiveExamplesMember({\n name: key,\n pathMethod: this.props.pathMethod,\n contextType: \"requestBody\",\n contextName: \"requestBody\", // RBs are currently not stored per-mediaType\n })\n }\n }\n onChange={(value, path) => {\n if (path) {\n const lastValue = oas3Selectors.requestBodyValue(...pathMethod)\n const usableValue = Map.isMap(lastValue) ? lastValue : Map()\n return oas3Actions.setRequestBodyValue({\n pathMethod,\n value: usableValue.setIn(path, value),\n })\n }\n oas3Actions.setRequestBodyValue({ value, pathMethod })\n }}\n onChangeIncludeEmpty={(name, value) => {\n oas3Actions.setRequestBodyInclusion({\n pathMethod,\n value,\n name,\n })\n }}\n contentType={oas3Selectors.requestContentType(...pathMethod)} />\n
    \n
    \n }\n
    \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport const ParameterExt = ({ xKey, xVal }) => {\n return
    { xKey }: { String(xVal) }
    \n}\nParameterExt.propTypes = {\n xKey: PropTypes.string,\n xVal: PropTypes.any\n}\n\nexport default ParameterExt\n","import React, { Component } from \"react\"\nimport cx from \"classnames\"\nimport PropTypes from \"prop-types\"\n\n\nconst noop = () => { }\n\nconst ParameterIncludeEmptyPropTypes = {\n isIncluded: PropTypes.bool.isRequired,\n isDisabled: PropTypes.bool.isRequired,\n isIncludedOptions: PropTypes.object,\n onChange: PropTypes.func.isRequired,\n}\n\nconst ParameterIncludeEmptyDefaultProps = {\n onChange: noop,\n isIncludedOptions: {},\n}\nexport default class ParameterIncludeEmpty extends Component {\n static propTypes = ParameterIncludeEmptyPropTypes\n static defaultProps = ParameterIncludeEmptyDefaultProps\n\n componentDidMount() {\n const { isIncludedOptions, onChange } = this.props\n const { shouldDispatchInit, defaultValue } = isIncludedOptions\n if (shouldDispatchInit) {\n onChange(defaultValue)\n }\n }\n\n onCheckboxChange = e => {\n const { onChange } = this.props\n onChange(e.target.checked)\n }\n\n render() {\n let { isIncluded, isDisabled } = this.props\n\n return (\n
    \n \n
    \n )\n }\n}\n","import React, { Component } from \"react\"\nimport { Map, List } from \"immutable\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport win from \"core/window\"\nimport { getExtensions, getCommonExtensions, numberToString, stringify, isEmptyValue } from \"core/utils\"\nimport getParameterSchema from \"../../helpers/get-parameter-schema.js\"\n\nexport default class ParameterRow extends Component {\n static propTypes = {\n onChange: PropTypes.func.isRequired,\n param: PropTypes.object.isRequired,\n rawParam: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n fn: PropTypes.object.isRequired,\n isExecute: PropTypes.bool,\n onChangeConsumes: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n pathMethod: PropTypes.array.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specPath: ImPropTypes.list.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n }\n\n constructor(props, context) {\n super(props, context)\n\n this.setDefaultValue()\n }\n\n UNSAFE_componentWillReceiveProps(props) {\n let { specSelectors, pathMethod, rawParam } = props\n let isOAS3 = specSelectors.isOAS3()\n\n let parameterWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || new Map()\n // fallback, if the meta lookup fails\n parameterWithMeta = parameterWithMeta.isEmpty() ? rawParam : parameterWithMeta\n\n let enumValue\n\n if(isOAS3) {\n let { schema } = getParameterSchema(parameterWithMeta, { isOAS3 })\n enumValue = schema ? schema.get(\"enum\") : undefined\n } else {\n enumValue = parameterWithMeta ? parameterWithMeta.get(\"enum\") : undefined\n }\n let paramValue = parameterWithMeta ? parameterWithMeta.get(\"value\") : undefined\n\n let value\n\n if ( paramValue !== undefined ) {\n value = paramValue\n } else if ( rawParam.get(\"required\") && enumValue && enumValue.size ) {\n value = enumValue.first()\n }\n\n if ( value !== undefined && value !== paramValue ) {\n this.onChangeWrapper(numberToString(value))\n }\n // todo: could check if schema here; if not, do not call. impact?\n this.setDefaultValue()\n }\n\n onChangeWrapper = (value, isXml = false) => {\n let { onChange, rawParam } = this.props\n let valueForUpstream\n\n // Coerce empty strings and empty Immutable objects to null\n if(value === \"\" || (value && value.size === 0)) {\n valueForUpstream = null\n } else {\n valueForUpstream = value\n }\n\n return onChange(rawParam, valueForUpstream, isXml)\n }\n\n _onExampleSelect = (key, /* { isSyntheticChange } = {} */) => {\n this.props.oas3Actions.setActiveExamplesMember({\n name: key,\n pathMethod: this.props.pathMethod,\n contextType: \"parameters\",\n contextName: this.getParamKey()\n })\n }\n\n onChangeIncludeEmpty = (newValue) => {\n let { specActions, param, pathMethod } = this.props\n const paramName = param.get(\"name\")\n const paramIn = param.get(\"in\")\n return specActions.updateEmptyParamInclusion(pathMethod, paramName, paramIn, newValue)\n }\n\n setDefaultValue = () => {\n let { specSelectors, pathMethod, rawParam, oas3Selectors, fn } = this.props\n\n const paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()\n const { schema } = getParameterSchema(paramWithMeta, { isOAS3: specSelectors.isOAS3() })\n const parameterMediaType = paramWithMeta\n .get(\"content\", Map())\n .keySeq()\n .first()\n\n // getSampleSchema could return null\n const generatedSampleValue = schema ? fn.getSampleSchema(schema.toJS(), parameterMediaType, {\n\n includeWriteOnly: true\n }) : null\n\n if (!paramWithMeta || paramWithMeta.get(\"value\") !== undefined) {\n return\n }\n\n if( paramWithMeta.get(\"in\") !== \"body\" ) {\n let initialValue\n\n //// Find an initial value\n\n if (specSelectors.isSwagger2()) {\n initialValue =\n paramWithMeta.get(\"x-example\") !== undefined\n ? paramWithMeta.get(\"x-example\")\n : paramWithMeta.getIn([\"schema\", \"example\"]) !== undefined\n ? paramWithMeta.getIn([\"schema\", \"example\"])\n : (schema && schema.getIn([\"default\"]))\n } else if (specSelectors.isOAS3()) {\n const currentExampleKey = oas3Selectors.activeExamplesMember(...pathMethod, \"parameters\", this.getParamKey())\n initialValue =\n paramWithMeta.getIn([\"examples\", currentExampleKey, \"value\"]) !== undefined\n ? paramWithMeta.getIn([\"examples\", currentExampleKey, \"value\"])\n : paramWithMeta.getIn([\"content\", parameterMediaType, \"example\"]) !== undefined\n ? paramWithMeta.getIn([\"content\", parameterMediaType, \"example\"])\n : paramWithMeta.get(\"example\") !== undefined\n ? paramWithMeta.get(\"example\")\n : (schema && schema.get(\"example\")) !== undefined\n ? (schema && schema.get(\"example\"))\n : (schema && schema.get(\"default\")) !== undefined\n ? (schema && schema.get(\"default\"))\n : paramWithMeta.get(\"default\") // ensures support for `parameterMacro`\n }\n\n //// Process the initial value\n\n if(initialValue !== undefined && !List.isList(initialValue)) {\n // Stringify if it isn't a List\n initialValue = stringify(initialValue)\n }\n\n //// Dispatch the initial value\n\n if(initialValue !== undefined) {\n this.onChangeWrapper(initialValue)\n } else if(\n schema && schema.get(\"type\") === \"object\"\n && generatedSampleValue\n && !paramWithMeta.get(\"examples\")\n ) {\n // Object parameters get special treatment.. if the user doesn't set any\n // default or example values, we'll provide initial values generated from\n // the schema.\n // However, if `examples` exist for the parameter, we won't do anything,\n // so that the appropriate `examples` logic can take over.\n this.onChangeWrapper(\n List.isList(generatedSampleValue) ? (\n generatedSampleValue\n ) : (\n stringify(generatedSampleValue)\n )\n )\n }\n }\n }\n\n getParamKey() {\n const { param } = this.props\n\n if(!param) return null\n\n return `${param.get(\"name\")}-${param.get(\"in\")}`\n }\n\n render() {\n let {param, rawParam, getComponent, getConfigs, isExecute, fn, onChangeConsumes, specSelectors, pathMethod, specPath, oas3Selectors} = this.props\n\n let isOAS3 = specSelectors.isOAS3()\n\n const { showExtensions, showCommonExtensions } = getConfigs()\n\n if(!param) {\n param = rawParam\n }\n\n if(!rawParam) return null\n\n // const onChangeWrapper = (value) => onChange(param, value)\n const JsonSchemaForm = getComponent(\"JsonSchemaForm\")\n const ParamBody = getComponent(\"ParamBody\")\n let inType = param.get(\"in\")\n let bodyParam = inType !== \"body\" ? null\n : \n\n const ModelExample = getComponent(\"modelExample\")\n const Markdown = getComponent(\"Markdown\", true)\n const ParameterExt = getComponent(\"ParameterExt\")\n const ParameterIncludeEmpty = getComponent(\"ParameterIncludeEmpty\")\n const ExamplesSelectValueRetainer = getComponent(\"ExamplesSelectValueRetainer\")\n const Example = getComponent(\"Example\")\n\n let { schema } = getParameterSchema(param, { isOAS3 })\n let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()\n\n let format = schema ? schema.get(\"format\") : null\n let type = schema ? schema.get(\"type\") : null\n let itemType = schema ? schema.getIn([\"items\", \"type\"]) : null\n let isFormData = inType === \"formData\"\n let isFormDataSupported = \"FormData\" in win\n let required = param.get(\"required\")\n\n let value = paramWithMeta ? paramWithMeta.get(\"value\") : \"\"\n let commonExt = showCommonExtensions ? getCommonExtensions(schema) : null\n let extensions = showExtensions ? getExtensions(param) : null\n\n let paramItems // undefined\n let paramEnum // undefined\n let paramDefaultValue // undefined\n let paramExample // undefined\n let isDisplayParamEnum = false\n\n if ( param !== undefined && schema ) {\n paramItems = schema.get(\"items\")\n }\n\n if (paramItems !== undefined) {\n paramEnum = paramItems.get(\"enum\")\n paramDefaultValue = paramItems.get(\"default\")\n } else if (schema) {\n paramEnum = schema.get(\"enum\")\n }\n\n if ( paramEnum && paramEnum.size && paramEnum.size > 0) {\n isDisplayParamEnum = true\n }\n\n // Default and Example Value for readonly doc\n if ( param !== undefined ) {\n if (schema) {\n paramDefaultValue = schema.get(\"default\")\n }\n if (paramDefaultValue === undefined) {\n paramDefaultValue = param.get(\"default\")\n }\n paramExample = param.get(\"example\")\n if (paramExample === undefined) {\n paramExample = param.get(\"x-example\")\n }\n }\n\n return (\n \n \n
    \n { param.get(\"name\") }\n { !required ? null :  * }\n
    \n
    \n { type }\n { itemType && `[${itemType}]` }\n { format && (${format})}\n
    \n
    \n { isOAS3 && param.get(\"deprecated\") ? \"deprecated\": null }\n
    \n
    ({ param.get(\"in\") })
    \n { !showCommonExtensions || !commonExt.size ? null : commonExt.entrySeq().map(([key, v]) => )}\n { !showExtensions || !extensions.size ? null : extensions.entrySeq().map(([key, v]) => )}\n \n\n \n { param.get(\"description\") ? : null }\n\n { (bodyParam || !isExecute) && isDisplayParamEnum ?\n Available values : \" + paramEnum.map(function(item) {\n return item\n }).toArray().join(\", \")}/>\n : null\n }\n\n { (bodyParam || !isExecute) && paramDefaultValue !== undefined ?\n Default value : \" + paramDefaultValue}/>\n : null\n }\n\n { (bodyParam || !isExecute) && paramExample !== undefined ?\n Example : \" + paramExample}/>\n : null\n }\n\n {(isFormData && !isFormDataSupported) &&
    Error: your browser does not support FormData
    }\n\n {\n isOAS3 && param.get(\"examples\") ? (\n
    \n \n
    \n ) : null\n }\n\n { bodyParam ? null\n : \n }\n\n\n {\n bodyParam && schema ? \n : null\n }\n\n {\n !bodyParam && isExecute && param.get(\"allowEmptyValue\") ?\n \n : null\n }\n\n {\n isOAS3 && param.get(\"examples\") ? (\n \n ) : null\n }\n\n \n\n \n )\n\n }\n\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class Execute extends Component {\n\n static propTypes = {\n specSelectors: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n operation: PropTypes.object.isRequired,\n path: PropTypes.string.isRequired,\n method: PropTypes.string.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n onExecute: PropTypes.func,\n disabled: PropTypes.bool\n }\n\n handleValidateParameters = () => {\n let { specSelectors, specActions, path, method } = this.props\n specActions.validateParams([path, method])\n return specSelectors.validateBeforeExecute([path, method])\n }\n\n handleValidateRequestBody = () => {\n let { path, method, specSelectors, oas3Selectors, oas3Actions } = this.props\n let validationErrors = {\n missingBodyValue: false,\n missingRequiredKeys: []\n }\n // context: reset errors, then (re)validate\n oas3Actions.clearRequestBodyValidateError({ path, method })\n let oas3RequiredRequestBodyContentType = specSelectors.getOAS3RequiredRequestBodyContentType([path, method])\n let oas3RequestBodyValue = oas3Selectors.requestBodyValue(path, method)\n let oas3ValidateBeforeExecuteSuccess = oas3Selectors.validateBeforeExecute([path, method])\n let oas3RequestContentType = oas3Selectors.requestContentType(path, method)\n\n if (!oas3ValidateBeforeExecuteSuccess) {\n validationErrors.missingBodyValue = true\n oas3Actions.setRequestBodyValidateError({ path, method, validationErrors })\n return false\n }\n if (!oas3RequiredRequestBodyContentType) {\n return true\n }\n let missingRequiredKeys = oas3Selectors.validateShallowRequired({\n oas3RequiredRequestBodyContentType,\n oas3RequestContentType,\n oas3RequestBodyValue\n })\n if (!missingRequiredKeys || missingRequiredKeys.length < 1) {\n return true\n }\n missingRequiredKeys.forEach((missingKey) => {\n validationErrors.missingRequiredKeys.push(missingKey)\n })\n oas3Actions.setRequestBodyValidateError({ path, method, validationErrors })\n return false\n }\n\n handleValidationResultPass = () => {\n let { specActions, operation, path, method } = this.props\n if (this.props.onExecute) {\n // loading spinner\n this.props.onExecute()\n }\n specActions.execute({ operation, path, method })\n }\n\n handleValidationResultFail = () => {\n let { specActions, path, method } = this.props\n // deferred by 40ms, to give element class change time to settle.\n specActions.clearValidateParams([path, method])\n setTimeout(() => {\n specActions.validateParams([path, method])\n }, 40)\n }\n\n handleValidationResult = (isPass) => {\n if (isPass) {\n this.handleValidationResultPass()\n } else {\n this.handleValidationResultFail()\n }\n }\n\n onClick = () => {\n let paramsResult = this.handleValidateParameters()\n let requestBodyResult = this.handleValidateRequestBody()\n let isPass = paramsResult && requestBodyResult\n this.handleValidationResult(isPass)\n }\n\n onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue([this.props.path, this.props.method], val)\n\n render(){\n const { disabled } = this.props\n return (\n \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport Im from \"immutable\"\n\nconst propClass = \"header-example\"\n\nexport default class Headers extends React.Component {\n static propTypes = {\n headers: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired\n }\n\n render() {\n let { headers, getComponent } = this.props\n\n const Property = getComponent(\"Property\")\n const Markdown = getComponent(\"Markdown\", true)\n\n if ( !headers || !headers.size )\n return null\n\n return (\n
    \n

    Headers:

    \n \n \n \n \n \n \n \n \n \n {\n headers.entrySeq().map( ([ key, header ]) => {\n if(!Im.Map.isMap(header)) {\n return null\n }\n\n const description = header.get(\"description\")\n const type = header.getIn([\"schema\"]) ? header.getIn([\"schema\", \"type\"]) : header.getIn([\"type\"])\n const schemaExample = header.getIn([\"schema\", \"example\"])\n\n return (\n \n \n \n )\n }).toArray()\n }\n \n
    NameDescriptionType
    { key }{\n !description ? null : \n }{ type } { schemaExample ? : null }
    \n
    \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport { List } from \"immutable\"\n\nexport default class Errors extends React.Component {\n\n static propTypes = {\n editorActions: PropTypes.object,\n errSelectors: PropTypes.object.isRequired,\n layoutSelectors: PropTypes.object.isRequired,\n layoutActions: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n }\n\n render() {\n let { editorActions, errSelectors, layoutSelectors, layoutActions, getComponent } = this.props\n\n const Collapse = getComponent(\"Collapse\")\n\n if(editorActions && editorActions.jumpToLine) {\n var jumpToLine = editorActions.jumpToLine\n }\n\n let errors = errSelectors.allErrors()\n\n // all thrown errors, plus error-level everything else\n let allErrorsToDisplay = errors.filter(err => err.get(\"type\") === \"thrown\" ? true :err.get(\"level\") === \"error\")\n\n if(!allErrorsToDisplay || allErrorsToDisplay.count() < 1) {\n return null\n }\n\n let isVisible = layoutSelectors.isShown([\"errorPane\"], true)\n let toggleVisibility = () => layoutActions.show([\"errorPane\"], !isVisible)\n\n let sortedJSErrors = allErrorsToDisplay.sortBy(err => err.get(\"line\"))\n\n return (\n
    \n        
    \n

    Errors

    \n \n
    \n \n
    \n { sortedJSErrors.map((err, i) => {\n let type = err.get(\"type\")\n if(type === \"thrown\" || type === \"auth\") {\n return \n }\n if(type === \"spec\") {\n return \n }\n }) }\n
    \n
    \n
    \n )\n }\n}\n\nconst ThrownErrorItem = ( { error, jumpToLine } ) => {\n if(!error) {\n return null\n }\n let errorLine = error.get(\"line\")\n\n return (\n
    \n { !error ? null :\n
    \n

    { (error.get(\"source\") && error.get(\"level\")) ?\n toTitleCase(error.get(\"source\")) + \" \" + error.get(\"level\") : \"\" }\n { error.get(\"path\") ? at {error.get(\"path\")}: null }

    \n \n { error.get(\"message\") }\n \n
    \n { errorLine && jumpToLine ? Jump to line { errorLine } : null }\n
    \n
    \n }\n
    \n )\n }\n\nconst SpecErrorItem = ( { error, jumpToLine } ) => {\n let locationMessage = null\n\n if(error.get(\"path\")) {\n if(List.isList(error.get(\"path\"))) {\n locationMessage = at { error.get(\"path\").join(\".\") }\n } else {\n locationMessage = at { error.get(\"path\") }\n }\n } else if(error.get(\"line\") && !jumpToLine) {\n locationMessage = on line { error.get(\"line\") }\n }\n\n return (\n
    \n { !error ? null :\n
    \n

    { toTitleCase(error.get(\"source\")) + \" \" + error.get(\"level\") } { locationMessage }

    \n { error.get(\"message\") }\n
    \n { jumpToLine ? (\n Jump to line { error.get(\"line\") }\n ) : null }\n
    \n
    \n }\n
    \n )\n }\n\nfunction toTitleCase(str) {\n return (str || \"\")\n .split(\" \")\n .map(substr => substr[0].toUpperCase() + substr.slice(1))\n .join(\" \")\n}\n\nThrownErrorItem.propTypes = {\n error: PropTypes.object.isRequired,\n jumpToLine: PropTypes.func\n}\n\nThrownErrorItem.defaultProps = {\n jumpToLine: null\n}\n\nSpecErrorItem.propTypes = {\n error: PropTypes.object.isRequired,\n jumpToLine: PropTypes.func\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { fromJS } from \"immutable\"\n\nconst noop = ()=>{}\n\nexport default class ContentType extends React.Component {\n\n static propTypes = {\n ariaControls: PropTypes.string,\n contentTypes: PropTypes.oneOfType([ImPropTypes.list, ImPropTypes.set, ImPropTypes.seq]),\n controlId: PropTypes.string,\n value: PropTypes.string,\n onChange: PropTypes.func,\n className: PropTypes.string,\n ariaLabel: PropTypes.string\n }\n\n static defaultProps = {\n onChange: noop,\n value: null,\n contentTypes: fromJS([\"application/json\"]),\n }\n\n componentDidMount() {\n // Needed to populate the form, initially\n if(this.props.contentTypes) {\n this.props.onChange(this.props.contentTypes.first())\n }\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n if(!nextProps.contentTypes || !nextProps.contentTypes.size) {\n return\n }\n\n if(!nextProps.contentTypes.includes(nextProps.value)) {\n nextProps.onChange(nextProps.contentTypes.first())\n }\n }\n\n onChangeWrapper = e => this.props.onChange(e.target.value)\n\n render() {\n let { ariaControls, ariaLabel, className, contentTypes, controlId, value } = this.props\n\n if ( !contentTypes || !contentTypes.size )\n return null\n\n return (\n
    \n \n
    \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nfunction xclass(...args) {\n return args.filter(a => !!a).join(\" \").trim()\n}\n\nexport class Container extends React.Component {\n render() {\n let { fullscreen, full, ...rest } = this.props\n // Normal element\n\n if(fullscreen)\n return
    \n\n let containerClass = \"swagger-container\" + (full ? \"-full\" : \"\")\n return (\n
    \n )\n }\n}\n\nContainer.propTypes = {\n fullscreen: PropTypes.bool,\n full: PropTypes.bool,\n className: PropTypes.string\n}\n\nconst DEVICES = {\n \"mobile\": \"\",\n \"tablet\": \"-tablet\",\n \"desktop\": \"-desktop\",\n \"large\": \"-hd\"\n}\n\nexport class Col extends React.Component {\n\n render() {\n const {\n hide,\n keepContents,\n /* we don't want these in the `rest` object that passes to the final component,\n since React now complains. So we extract them */\n /* eslint-disable no-unused-vars */\n mobile,\n tablet,\n desktop,\n large,\n /* eslint-enable no-unused-vars */\n ...rest\n } = this.props\n\n if(hide && !keepContents)\n return \n\n let classesAr = []\n\n for (let device in DEVICES) {\n if (!Object.prototype.hasOwnProperty.call(DEVICES, device)) {\n continue\n }\n let deviceClass = DEVICES[device]\n if(device in this.props) {\n let val = this.props[device]\n\n if(val < 1) {\n classesAr.push(\"none\" + deviceClass)\n continue\n }\n\n classesAr.push(\"block\" + deviceClass)\n classesAr.push(\"col-\" + val + deviceClass)\n }\n }\n\n if (hide) {\n classesAr.push(\"hidden\")\n }\n\n let classes = xclass(rest.className, ...classesAr)\n\n return (\n
    \n )\n }\n\n}\n\nCol.propTypes = {\n hide: PropTypes.bool,\n keepContents: PropTypes.bool,\n mobile: PropTypes.number,\n tablet: PropTypes.number,\n desktop: PropTypes.number,\n large: PropTypes.number,\n className: PropTypes.string\n}\n\nexport class Row extends React.Component {\n\n render() {\n return
    \n }\n\n}\n\nRow.propTypes = {\n className: PropTypes.string\n}\n\nexport class Button extends React.Component {\n\n static propTypes = {\n className: PropTypes.string\n }\n\n static defaultProps = {\n className: \"\"\n }\n\n render() {\n return
    \n
    \n {curlBlock}\n
    \n \n )\n }\n\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class Schemes extends React.Component {\n\n static propTypes = {\n specActions: PropTypes.object.isRequired,\n schemes: PropTypes.object.isRequired,\n currentScheme: PropTypes.string.isRequired,\n path: PropTypes.string,\n method: PropTypes.string,\n }\n\n UNSAFE_componentWillMount() {\n let { schemes } = this.props\n\n //fire 'change' event to set default 'value' of select\n this.setScheme(schemes.first())\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n if ( !this.props.currentScheme || !nextProps.schemes.includes(this.props.currentScheme) ) {\n // if we don't have a selected currentScheme or if our selected scheme is no longer an option,\n // then fire 'change' event and select the first scheme in the list of options\n this.setScheme(nextProps.schemes.first())\n }\n }\n\n onChange =( e ) => {\n this.setScheme( e.target.value )\n }\n\n setScheme = ( value ) => {\n let { path, method, specActions } = this.props\n\n specActions.setScheme( value, path, method )\n }\n\n render() {\n let { schemes, currentScheme } = this.props\n\n return (\n \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class SchemesContainer extends React.Component {\n\n static propTypes = {\n specActions: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired\n }\n\n render () {\n const {specActions, specSelectors, getComponent} = this.props\n\n const currentScheme = specSelectors.operationScheme()\n const schemes = specSelectors.schemes()\n\n const Schemes = getComponent(\"schemes\")\n\n const schemesArePresent = schemes && schemes.size\n\n return schemesArePresent ? (\n \n ) : null\n }\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport Im from \"immutable\"\n\nexport default class ModelCollapse extends Component {\n static propTypes = {\n collapsedContent: PropTypes.any,\n expanded: PropTypes.bool,\n children: PropTypes.any,\n title: PropTypes.element,\n modelName: PropTypes.string,\n classes: PropTypes.string,\n onToggle: PropTypes.func,\n hideSelfOnExpand: PropTypes.bool,\n layoutActions: PropTypes.object,\n layoutSelectors: PropTypes.object.isRequired,\n specPath: ImPropTypes.list.isRequired,\n }\n\n static defaultProps = {\n collapsedContent: \"{...}\",\n expanded: false,\n title: null,\n onToggle: () => {},\n hideSelfOnExpand: false,\n specPath: Im.List([]),\n }\n\n constructor(props, context) {\n super(props, context)\n\n let { expanded, collapsedContent } = this.props\n\n this.state = {\n expanded : expanded,\n collapsedContent: collapsedContent || ModelCollapse.defaultProps.collapsedContent\n }\n }\n\n componentDidMount() {\n const { hideSelfOnExpand, expanded, modelName } = this.props\n if(hideSelfOnExpand && expanded) {\n // We just mounted pre-expanded, and we won't be going back..\n // So let's give our parent an `onToggle` call..\n // Since otherwise it will never be called.\n this.props.onToggle(modelName, expanded)\n }\n }\n\n UNSAFE_componentWillReceiveProps(nextProps){\n if(this.props.expanded !== nextProps.expanded){\n this.setState({expanded: nextProps.expanded})\n }\n }\n\n toggleCollapsed=()=>{\n if(this.props.onToggle){\n this.props.onToggle(this.props.modelName,!this.state.expanded)\n }\n\n this.setState({\n expanded: !this.state.expanded\n })\n }\n\n onLoad = (ref) => {\n if (ref && this.props.layoutSelectors) {\n const scrollToKey = this.props.layoutSelectors.getScrollToKey()\n\n if( Im.is(scrollToKey, this.props.specPath) ) this.toggleCollapsed()\n this.props.layoutActions.readyToScroll(this.props.specPath, ref.parentElement)\n }\n }\n\n render () {\n const { title, classes } = this.props\n\n if(this.state.expanded ) {\n if(this.props.hideSelfOnExpand) {\n return \n {this.props.children}\n \n }\n }\n\n return (\n \n \n\n { this.state.expanded && this.props.children }\n \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport cx from \"classnames\"\nimport randomBytes from \"randombytes\"\n\nexport default class ModelExample extends React.Component {\n static propTypes = {\n getComponent: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n schema: PropTypes.object.isRequired,\n example: PropTypes.any.isRequired,\n isExecute: PropTypes.bool,\n getConfigs: PropTypes.func.isRequired,\n specPath: ImPropTypes.list.isRequired,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n }\n\n constructor(props, context) {\n super(props, context)\n let { getConfigs, isExecute } = this.props\n let { defaultModelRendering } = getConfigs()\n\n let activeTab = defaultModelRendering\n\n if (defaultModelRendering !== \"example\" && defaultModelRendering !== \"model\") {\n activeTab = \"example\"\n }\n\n if(isExecute) {\n activeTab = \"example\"\n }\n\n this.state = {\n activeTab,\n }\n }\n\n activeTab = ( e ) => {\n let { target : { dataset : { name } } } = e\n\n this.setState({\n activeTab: name\n })\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n if (\n nextProps.isExecute &&\n !this.props.isExecute &&\n this.props.example\n ) {\n this.setState({ activeTab: \"example\" })\n }\n }\n\n render() {\n let { getComponent, specSelectors, schema, example, isExecute, getConfigs, specPath, includeReadOnly, includeWriteOnly } = this.props\n let { defaultModelExpandDepth } = getConfigs()\n const ModelWrapper = getComponent(\"ModelWrapper\")\n const HighlightCode = getComponent(\"highlightCode\")\n const exampleTabId = randomBytes(5).toString(\"base64\")\n const examplePanelId = randomBytes(5).toString(\"base64\")\n const modelTabId = randomBytes(5).toString(\"base64\")\n const modelPanelId = randomBytes(5).toString(\"base64\")\n\n let isOAS3 = specSelectors.isOAS3()\n\n return (\n
    \n
      \n
    • \n \n {isExecute ? \"Edit Value\" : \"Example Value\"}\n \n
    • \n { schema && (\n
    • \n \n {isOAS3 ? \"Schema\" : \"Model\" }\n \n
    • \n )}\n
    \n {this.state.activeTab === \"example\" && (\n \n {example ? example : (\n \n )}\n
    \n )}\n\n {this.state.activeTab === \"model\" && (\n \n \n \n )}\n \n )\n }\n\n}\n","import React, { Component, } from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class ModelWrapper extends Component {\n\n static propTypes = {\n schema: PropTypes.object.isRequired,\n name: PropTypes.string,\n displayName: PropTypes.string,\n fullPath: PropTypes.array.isRequired,\n specPath: ImPropTypes.list.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n expandDepth: PropTypes.number,\n layoutActions: PropTypes.object,\n layoutSelectors: PropTypes.object.isRequired,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n }\n\n onToggle = (name,isShown) => {\n // If this prop is present, we'll have deepLinking for it\n if(this.props.layoutActions) {\n this.props.layoutActions.show(this.props.fullPath, isShown)\n }\n }\n\n render(){\n let { getComponent, getConfigs } = this.props\n const Model = getComponent(\"Model\")\n\n let expanded\n if(this.props.layoutSelectors) {\n // If this is prop is present, we'll have deepLinking for it\n expanded = this.props.layoutSelectors.isShown(this.props.fullPath)\n }\n\n return
    \n \n
    \n }\n}\n","import React, { Component } from \"react\"\nimport Im, { Map } from \"immutable\"\nimport PropTypes from \"prop-types\"\n\nexport default class Models extends Component {\n static propTypes = {\n getComponent: PropTypes.func,\n specSelectors: PropTypes.object,\n specActions: PropTypes.object.isRequired,\n layoutSelectors: PropTypes.object,\n layoutActions: PropTypes.object,\n getConfigs: PropTypes.func.isRequired\n }\n\n getSchemaBasePath = () => {\n const isOAS3 = this.props.specSelectors.isOAS3()\n return isOAS3 ? [\"components\", \"schemas\"] : [\"definitions\"]\n }\n\n getCollapsedContent = () => {\n return \" \"\n }\n\n handleToggle = (name, isExpanded) => {\n const { layoutActions } = this.props\n layoutActions.show([...this.getSchemaBasePath(), name], isExpanded)\n if(isExpanded) {\n this.props.specActions.requestResolvedSubtree([...this.getSchemaBasePath(), name])\n }\n }\n\n onLoadModels = (ref) => {\n if (ref) {\n this.props.layoutActions.readyToScroll(this.getSchemaBasePath(), ref)\n }\n }\n\n onLoadModel = (ref) => {\n if (ref) {\n const name = ref.getAttribute(\"data-name\")\n this.props.layoutActions.readyToScroll([...this.getSchemaBasePath(), name], ref)\n }\n }\n\n render(){\n let { specSelectors, getComponent, layoutSelectors, layoutActions, getConfigs } = this.props\n let definitions = specSelectors.definitions()\n let { docExpansion, defaultModelsExpandDepth } = getConfigs()\n if (!definitions.size || defaultModelsExpandDepth < 0) return null\n\n const specPathBase = this.getSchemaBasePath()\n let showModels = layoutSelectors.isShown(specPathBase, defaultModelsExpandDepth > 0 && docExpansion !== \"none\")\n const isOAS3 = specSelectors.isOAS3()\n\n const ModelWrapper = getComponent(\"ModelWrapper\")\n const Collapse = getComponent(\"Collapse\")\n const ModelCollapse = getComponent(\"ModelCollapse\")\n const JumpToPath = getComponent(\"JumpToPath\", true)\n\n return
    \n

    \n layoutActions.show(specPathBase, !showModels)}\n >\n {isOAS3 ? \"Schemas\" : \"Models\"}\n \n \n \n \n

    \n \n {\n definitions.entrySeq().map(([name])=>{\n\n const fullPath = [...specPathBase, name]\n const specPath = Im.List(fullPath)\n\n const schemaValue = specSelectors.specResolvedSubtree(fullPath)\n const rawSchemaValue = specSelectors.specJson().getIn(fullPath)\n\n const schema = Map.isMap(schemaValue) ? schemaValue : Im.Map()\n const rawSchema = Map.isMap(rawSchemaValue) ? rawSchemaValue : Im.Map()\n\n const displayName = schema.get(\"title\") || rawSchema.get(\"title\") || name\n const isShown = layoutSelectors.isShown(fullPath, false)\n\n if( isShown && (schema.size === 0 && rawSchema.size > 0) ) {\n // Firing an action in a container render is not great,\n // but it works for now.\n this.props.specActions.requestResolvedSubtree(fullPath)\n }\n\n const content = \n\n const title = \n \n {displayName}\n \n \n\n return
    \n \n 0 && isShown }\n >{content}\n
    \n }).toArray()\n }\n
    \n
    \n }\n}\n","import React from \"react\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nconst EnumModel = ({ value, getComponent }) => {\n let ModelCollapse = getComponent(\"ModelCollapse\")\n let collapsedContent = Array [ { value.count() } ]\n return \n Enum:
    \n \n [ { value.join(\", \") } ]\n \n
    \n}\nEnumModel.propTypes = {\n value: ImPropTypes.iterable,\n getComponent: ImPropTypes.func\n}\n\nexport default EnumModel","import React, { Component, } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { List } from \"immutable\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { sanitizeUrl } from \"core/utils\"\n\nconst braceOpen = \"{\"\nconst braceClose = \"}\"\nconst propClass = \"property\"\n\nexport default class ObjectModel extends Component {\n static propTypes = {\n schema: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n expanded: PropTypes.bool,\n onToggle: PropTypes.func,\n specSelectors: PropTypes.object.isRequired,\n name: PropTypes.string,\n displayName: PropTypes.string,\n isRef: PropTypes.bool,\n expandDepth: PropTypes.number,\n depth: PropTypes.number,\n specPath: ImPropTypes.list.isRequired,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n }\n\n render(){\n let { schema, name, displayName, isRef, getComponent, getConfigs, depth, onToggle, expanded, specPath, ...otherProps } = this.props\n let { specSelectors,expandDepth, includeReadOnly, includeWriteOnly} = otherProps\n const { isOAS3 } = specSelectors\n\n if(!schema) {\n return null\n }\n\n const { showExtensions } = getConfigs()\n\n let description = schema.get(\"description\")\n let properties = schema.get(\"properties\")\n let additionalProperties = schema.get(\"additionalProperties\")\n let title = schema.get(\"title\") || displayName || name\n let requiredProperties = schema.get(\"required\")\n let infoProperties = schema\n .filter( ( v, key) => [\"maxProperties\", \"minProperties\", \"nullable\", \"example\"].indexOf(key) !== -1 )\n let deprecated = schema.get(\"deprecated\")\n let externalDocsUrl = schema.getIn([\"externalDocs\", \"url\"])\n let externalDocsDescription = schema.getIn([\"externalDocs\", \"description\"])\n\n const JumpToPath = getComponent(\"JumpToPath\", true)\n const Markdown = getComponent(\"Markdown\", true)\n const Model = getComponent(\"Model\")\n const ModelCollapse = getComponent(\"ModelCollapse\")\n const Property = getComponent(\"Property\")\n const Link = getComponent(\"Link\")\n\n const JumpToPathSection = () => {\n return \n }\n const collapsedContent = (\n { braceOpen }...{ braceClose }\n {\n isRef ? : \"\"\n }\n )\n\n const anyOf = specSelectors.isOAS3() ? schema.get(\"anyOf\") : null\n const oneOf = specSelectors.isOAS3() ? schema.get(\"oneOf\") : null\n const not = specSelectors.isOAS3() ? schema.get(\"not\") : null\n\n const titleEl = title && \n { isRef && schema.get(\"$$ref\") && { schema.get(\"$$ref\") } }\n { title }\n \n\n return \n \n\n { braceOpen }\n {\n !isRef ? null : \n }\n \n {\n \n {\n !description ? null : \n \n \n \n }\n {\n externalDocsUrl &&\n \n \n \n \n }\n {\n !deprecated ? null :\n \n \n \n \n }\n {\n !(properties && properties.size) ? null : properties.entrySeq().filter(\n ([, value]) => {\n return (!value.get(\"readOnly\") || includeReadOnly) &&\n (!value.get(\"writeOnly\") || includeWriteOnly)\n }\n ).map(\n ([key, value]) => {\n let isDeprecated = isOAS3() && value.get(\"deprecated\")\n let isRequired = List.isList(requiredProperties) && requiredProperties.contains(key)\n\n let classNames = [\"property-row\"]\n\n if (isDeprecated) {\n classNames.push(\"deprecated\")\n }\n\n if (isRequired) {\n classNames.push(\"required\")\n }\n\n return (\n \n \n )\n }).toArray()\n }\n {\n // empty row before extensions...\n !showExtensions ? null : \n }\n {\n !showExtensions ? null :\n schema.entrySeq().map(\n ([key, value]) => {\n if(key.slice(0,2) !== \"x-\") {\n return\n }\n\n const normalizedValue = !value ? null : value.toJS ? value.toJS() : value\n\n return (\n \n \n )\n }).toArray()\n }\n {\n !additionalProperties || !additionalProperties.size ? null\n : \n \n \n \n }\n {\n !anyOf ? null\n : \n \n \n \n }\n {\n !oneOf ? null\n : \n \n \n \n }\n {\n !not ? null\n : \n \n \n \n }\n
    description:\n \n
    \n externalDocs:\n \n {externalDocsDescription || externalDocsUrl}\n
    \n deprecated:\n \n true\n
    \n { key }{ isRequired && * }\n \n \n
     
    \n { key }\n \n { JSON.stringify(normalizedValue) }\n
    { \"< * >:\" }\n \n
    { \"anyOf ->\" }\n {anyOf.map((schema, k) => {\n return
    \n })}\n
    { \"oneOf ->\" }\n {oneOf.map((schema, k) => {\n return
    \n })}\n
    { \"not ->\" }\n
    \n \n
    \n
    \n }\n
    \n { braceClose }\n \n {\n infoProperties.size ? infoProperties.entrySeq().map( ( [ key, v ] ) => ) : null\n }\n
    \n }\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { sanitizeUrl } from \"core/utils\"\n\nconst propClass = \"property\"\n\nexport default class ArrayModel extends Component {\n static propTypes = {\n schema: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n name: PropTypes.string,\n displayName: PropTypes.string,\n required: PropTypes.bool,\n expandDepth: PropTypes.number,\n specPath: ImPropTypes.list.isRequired,\n depth: PropTypes.number,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n }\n\n render(){\n let { getComponent, getConfigs, schema, depth, expandDepth, name, displayName, specPath } = this.props\n let description = schema.get(\"description\")\n let items = schema.get(\"items\")\n let title = schema.get(\"title\") || displayName || name\n let properties = schema.filter( ( v, key) => [\"type\", \"items\", \"description\", \"$$ref\", \"externalDocs\"].indexOf(key) === -1 )\n let externalDocsUrl = schema.getIn([\"externalDocs\", \"url\"])\n let externalDocsDescription = schema.getIn([\"externalDocs\", \"description\"])\n\n\n const Markdown = getComponent(\"Markdown\", true)\n const ModelCollapse = getComponent(\"ModelCollapse\")\n const Model = getComponent(\"Model\")\n const Property = getComponent(\"Property\")\n const Link = getComponent(\"Link\")\n\n const titleEl = title &&\n \n { title }\n \n\n /*\n Note: we set `name={null}` in below because we don't want\n the name of the current Model passed (and displayed) as the name of the array element Model\n */\n\n return \n \n [\n {\n properties.size ? properties.entrySeq().map( ( [ key, v ] ) => ) : null\n }\n {\n !description ? (properties.size ?
    : null) :\n \n }\n { externalDocsUrl &&\n
    \n {externalDocsDescription || externalDocsUrl}\n
    \n }\n \n \n \n ]\n
    \n
    \n }\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { getExtensions, sanitizeUrl } from \"core/utils\"\n\nconst propClass = \"property primitive\"\n\nexport default class Primitive extends Component {\n static propTypes = {\n schema: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n name: PropTypes.string,\n displayName: PropTypes.string,\n depth: PropTypes.number,\n expandDepth: PropTypes.number\n }\n\n render() {\n let { schema, getComponent, getConfigs, name, displayName, depth, expandDepth } = this.props\n\n const { showExtensions } = getConfigs()\n\n if (!schema || !schema.get) {\n // don't render if schema isn't correctly formed\n return
    \n }\n\n let type = schema.get(\"type\")\n let format = schema.get(\"format\")\n let xml = schema.get(\"xml\")\n let enumArray = schema.get(\"enum\")\n let title = schema.get(\"title\") || displayName || name\n let description = schema.get(\"description\")\n let extensions = getExtensions(schema)\n let properties = schema\n .filter((_, key) => [\"enum\", \"type\", \"format\", \"description\", \"$$ref\", \"externalDocs\"].indexOf(key) === -1)\n .filterNot((_, key) => extensions.has(key))\n let externalDocsUrl = schema.getIn([\"externalDocs\", \"url\"])\n let externalDocsDescription = schema.getIn([\"externalDocs\", \"description\"])\n\n const Markdown = getComponent(\"Markdown\", true)\n const EnumModel = getComponent(\"EnumModel\")\n const Property = getComponent(\"Property\")\n const ModelCollapse = getComponent(\"ModelCollapse\")\n const Link = getComponent(\"Link\")\n\n const titleEl = title &&\n \n {title}\n \n\n return \n \n \n {name && depth > 1 && {title}}\n {type}\n {format && (${format})}\n {\n properties.size ? properties.entrySeq().map(([key, v]) => ) : null\n }\n {\n showExtensions && extensions.size ? extensions.entrySeq().map(([key, v]) => ) : null\n }\n {\n !description ? null :\n \n }\n {\n externalDocsUrl &&\n
    \n {externalDocsDescription || externalDocsUrl}\n
    \n }\n {\n xml && xml.size ? (
    xml:\n {\n xml.entrySeq().map(([key, v]) =>
       {key}: {String(v)}
    ).toArray()\n }\n
    ) : null\n }\n {\n enumArray && \n }\n
    \n
    \n
    \n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport const Property = ({ propKey, propVal, propClass }) => {\n return (\n \n
    { propKey }: { String(propVal) }
    \n )\n}\nProperty.propTypes = {\n propKey: PropTypes.string,\n propVal: PropTypes.any,\n propClass: PropTypes.string\n}\n\nexport default Property\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class TryItOutButton extends React.Component {\n\n static propTypes = {\n onTryoutClick: PropTypes.func,\n onResetClick: PropTypes.func,\n onCancelClick: PropTypes.func,\n enabled: PropTypes.bool, // Try it out is enabled, ie: the user has access to the form\n hasUserEditedBody: PropTypes.bool, // Try it out is enabled, ie: the user has access to the form\n isOAS3: PropTypes.bool, // Try it out is enabled, ie: the user has access to the form\n }\n\n static defaultProps = {\n onTryoutClick: Function.prototype,\n onCancelClick: Function.prototype,\n onResetClick: Function.prototype,\n enabled: false,\n hasUserEditedBody: false,\n isOAS3: false,\n }\n\n render() {\n const { onTryoutClick, onCancelClick, onResetClick, enabled, hasUserEditedBody, isOAS3 } = this.props\n\n const showReset = isOAS3 && hasUserEditedBody\n return (\n
    \n {\n enabled ? \n : \n\n }\n {\n showReset && \n }\n
    \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class VersionPragmaFilter extends React.PureComponent {\n static propTypes = {\n isSwagger2: PropTypes.bool.isRequired,\n isOAS3: PropTypes.bool.isRequired,\n bypass: PropTypes.bool,\n alsoShow: PropTypes.element,\n children: PropTypes.any,\n }\n\n static defaultProps = {\n alsoShow: null,\n children: null,\n bypass: false,\n }\n\n render() {\n const { bypass, isSwagger2, isOAS3, alsoShow } = this.props\n\n if(bypass) {\n return
    { this.props.children }
    \n }\n\n if(isSwagger2 && isOAS3) {\n return
    \n {alsoShow}\n
    \n
    \n

    Unable to render this definition

    \n

    swagger and openapi fields cannot be present in the same Swagger or OpenAPI definition. Please remove one of the fields.

    \n

    Supported version fields are swagger: {\"\\\"2.0\\\"\"} and those that match openapi: 3.0.n (for example, openapi: 3.0.0).

    \n
    \n
    \n
    \n }\n\n if(!isSwagger2 && !isOAS3) {\n return
    \n {alsoShow}\n
    \n
    \n

    Unable to render this definition

    \n

    The provided definition does not specify a valid version field.

    \n

    Please indicate a valid Swagger or OpenAPI version field. Supported version fields are swagger: {\"\\\"2.0\\\"\"} and those that match openapi: 3.0.n (for example, openapi: 3.0.0).

    \n
    \n
    \n
    \n }\n\n return
    { this.props.children }
    \n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nconst VersionStamp = ({ version }) => {\n return
     { version } 
    \n}\n\nVersionStamp.propTypes = {\n version: PropTypes.string.isRequired\n}\n\nexport default VersionStamp\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport const DeepLink = ({ enabled, path, text }) => {\n return (\n e.preventDefault() : null}\n href={enabled ? `#/${path}` : null}>\n {text}\n \n )\n}\nDeepLink.propTypes = {\n enabled: PropTypes.bool,\n isShown: PropTypes.bool,\n path: PropTypes.string,\n text: PropTypes.node\n}\n\nexport default DeepLink\n","import React from \"react\"\nconst SvgAssets = () =>\n
    \n \n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n \n\n \n \n
    \n\nexport default SvgAssets\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class BaseLayout extends React.Component {\n static propTypes = {\n errSelectors: PropTypes.object.isRequired,\n errActions: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n }\n\n render() {\n const { errSelectors, specSelectors, getComponent } = this.props\n\n const SvgAssets = getComponent(\"SvgAssets\")\n const InfoContainer = getComponent(\"InfoContainer\", true)\n const VersionPragmaFilter = getComponent(\"VersionPragmaFilter\")\n const Operations = getComponent(\"operations\", true)\n const Models = getComponent(\"Models\", true)\n const Webhooks = getComponent(\"Webhooks\", true)\n const Row = getComponent(\"Row\")\n const Col = getComponent(\"Col\")\n const Errors = getComponent(\"errors\", true)\n\n const ServersContainer = getComponent(\"ServersContainer\", true)\n const SchemesContainer = getComponent(\"SchemesContainer\", true)\n const AuthorizeBtnContainer = getComponent(\"AuthorizeBtnContainer\", true)\n const FilterContainer = getComponent(\"FilterContainer\", true)\n const isSwagger2 = specSelectors.isSwagger2()\n const isOAS3 = specSelectors.isOAS3()\n const isOAS31 = specSelectors.isOAS31()\n\n const isSpecEmpty = !specSelectors.specStr()\n\n const loadingStatus = specSelectors.loadingStatus()\n\n let loadingMessage = null\n\n if (loadingStatus === \"loading\") {\n loadingMessage = (\n
    \n
    \n
    \n
    \n
    \n )\n }\n\n if (loadingStatus === \"failed\") {\n loadingMessage = (\n
    \n
    \n

    Failed to load API definition.

    \n \n
    \n
    \n )\n }\n\n if (loadingStatus === \"failedConfig\") {\n const lastErr = errSelectors.lastError()\n const lastErrMsg = lastErr ? lastErr.get(\"message\") : \"\"\n loadingMessage = (\n
    \n
    \n

    Failed to load remote configuration.

    \n

    {lastErrMsg}

    \n
    \n
    \n )\n }\n\n if (!loadingMessage && isSpecEmpty) {\n loadingMessage =

    No API definition provided.

    \n }\n\n if (loadingMessage) {\n return (\n
    \n
    {loadingMessage}
    \n
    \n )\n }\n\n const servers = specSelectors.servers()\n const schemes = specSelectors.schemes()\n\n const hasServers = servers && servers.size\n const hasSchemes = schemes && schemes.size\n const hasSecurityDefinitions = !!specSelectors.securityDefinitions()\n\n return (\n
    \n \n }\n >\n \n \n \n \n \n \n\n {hasServers || hasSchemes || hasSecurityDefinitions ? (\n
    \n \n {hasServers ? : null}\n {hasSchemes ? : null}\n {hasSecurityDefinitions ? : null}\n \n
    \n ) : null}\n\n \n\n \n \n \n \n \n\n {isOAS31 && (\n \n \n \n \n \n )}\n\n \n \n \n \n \n \n
    \n )\n }\n}\n","var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }\nvar y = x => () => x\nconst __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: () => __WEBPACK_EXTERNAL_MODULE_react_debounce_input_7ed3e068__[\"default\"] });","import React, { PureComponent, Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { List, fromJS } from \"immutable\"\nimport cx from \"classnames\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport DebounceInput from \"react-debounce-input\"\nimport { stringify } from \"core/utils\"\n\nconst noop = ()=> {}\nconst JsonSchemaPropShape = {\n getComponent: PropTypes.func.isRequired,\n value: PropTypes.any,\n onChange: PropTypes.func,\n keyName: PropTypes.any,\n fn: PropTypes.object.isRequired,\n schema: PropTypes.object,\n errors: ImPropTypes.list,\n required: PropTypes.bool,\n dispatchInitialValue: PropTypes.bool,\n description: PropTypes.any,\n disabled: PropTypes.bool,\n}\n\nconst JsonSchemaDefaultProps = {\n value: \"\",\n onChange: noop,\n schema: {},\n keyName: \"\",\n required: false,\n errors: List()\n}\n\nexport class JsonSchemaForm extends Component {\n\n static propTypes = JsonSchemaPropShape\n static defaultProps = JsonSchemaDefaultProps\n\n componentDidMount() {\n const { dispatchInitialValue, value, onChange } = this.props\n if(dispatchInitialValue) {\n onChange(value)\n } else if(dispatchInitialValue === false) {\n onChange(\"\")\n }\n }\n\n render() {\n let { schema, errors, value, onChange, getComponent, fn, disabled } = this.props\n const format = schema && schema.get ? schema.get(\"format\") : null\n const type = schema && schema.get ? schema.get(\"type\") : null\n\n let getComponentSilently = (name) => getComponent(name, false, { failSilently: true })\n let Comp = type ? format ?\n getComponentSilently(`JsonSchema_${type}_${format}`) :\n getComponentSilently(`JsonSchema_${type}`) :\n getComponent(\"JsonSchema_string\")\n if (!Comp) {\n Comp = getComponent(\"JsonSchema_string\")\n }\n return \n }\n}\n\nexport class JsonSchema_string extends Component {\n static propTypes = JsonSchemaPropShape\n static defaultProps = JsonSchemaDefaultProps\n onChange = (e) => {\n const value = this.props.schema && this.props.schema.get(\"type\") === \"file\" ? e.target.files[0] : e.target.value\n this.props.onChange(value, this.props.keyName)\n }\n onEnumChange = (val) => this.props.onChange(val)\n render() {\n let { getComponent, value, schema, errors, required, description, disabled } = this.props\n const enumValue = schema && schema.get ? schema.get(\"enum\") : null\n const format = schema && schema.get ? schema.get(\"format\") : null\n const type = schema && schema.get ? schema.get(\"type\") : null\n const schemaIn = schema && schema.get ? schema.get(\"in\") : null\n if (!value) {\n value = \"\" // value should not be null; this fixes a Debounce error\n }\n errors = errors.toJS ? errors.toJS() : []\n\n if ( enumValue ) {\n const Select = getComponent(\"Select\")\n return (\n )\n }\n else {\n return (\n \n )\n }\n }\n}\n\nexport class JsonSchema_array extends PureComponent {\n\n static propTypes = JsonSchemaPropShape\n static defaultProps = JsonSchemaDefaultProps\n\n constructor(props, context) {\n super(props, context)\n this.state = { value: valueOrEmptyList(props.value), schema: props.schema}\n }\n\n UNSAFE_componentWillReceiveProps(props) {\n const value = valueOrEmptyList(props.value)\n if(value !== this.state.value)\n this.setState({ value })\n\n if(props.schema !== this.state.schema)\n this.setState({ schema: props.schema })\n }\n\n onChange = () => {\n this.props.onChange(this.state.value)\n }\n\n onItemChange = (itemVal, i) => {\n this.setState(({ value }) => ({\n value: value.set(i, itemVal)\n }), this.onChange)\n }\n\n removeItem = (i) => {\n this.setState(({ value }) => ({\n value: value.delete(i)\n }), this.onChange)\n }\n\n addItem = () => {\n const { fn } = this.props\n let newValue = valueOrEmptyList(this.state.value)\n this.setState(() => ({\n value: newValue.push(fn.getSampleSchema(this.state.schema.get(\"items\"), false, {\n includeWriteOnly: true\n }))\n }), this.onChange)\n }\n\n onEnumChange = (value) => {\n this.setState(() => ({\n value: value\n }), this.onChange)\n }\n\n render() {\n let { getComponent, required, schema, errors, fn, disabled } = this.props\n\n errors = errors.toJS ? errors.toJS() : Array.isArray(errors) ? errors : []\n const arrayErrors = errors.filter(e => typeof e === \"string\")\n const needsRemoveError = errors.filter(e => e.needRemove !== undefined)\n .map(e => e.error)\n const value = this.state.value // expect Im List\n const shouldRenderValue =\n value && value.count && value.count() > 0 ? true : false\n const schemaItemsEnum = schema.getIn([\"items\", \"enum\"])\n const schemaItemsType = schema.getIn([\"items\", \"type\"])\n const schemaItemsFormat = schema.getIn([\"items\", \"format\"])\n const schemaItemsSchema = schema.get(\"items\")\n let ArrayItemsComponent\n let isArrayItemText = false\n let isArrayItemFile = (schemaItemsType === \"file\" || (schemaItemsType === \"string\" && schemaItemsFormat === \"binary\")) ? true : false\n if (schemaItemsType && schemaItemsFormat) {\n ArrayItemsComponent = getComponent(`JsonSchema_${schemaItemsType}_${schemaItemsFormat}`)\n } else if (schemaItemsType === \"boolean\" || schemaItemsType === \"array\" || schemaItemsType === \"object\") {\n ArrayItemsComponent = getComponent(`JsonSchema_${schemaItemsType}`)\n }\n // if ArrayItemsComponent not assigned or does not exist,\n // use default schemaItemsType === \"string\" & JsonSchemaArrayItemText component\n if (!ArrayItemsComponent && !isArrayItemFile) {\n isArrayItemText = true\n }\n\n if ( schemaItemsEnum ) {\n const Select = getComponent(\"Select\")\n return ()\n }\n}\n\nexport class JsonSchema_boolean extends Component {\n static propTypes = JsonSchemaPropShape\n static defaultProps = JsonSchemaDefaultProps\n\n onEnumChange = (val) => this.props.onChange(val)\n render() {\n let { getComponent, value, errors, schema, required, disabled } = this.props\n errors = errors.toJS ? errors.toJS() : []\n let enumValue = schema && schema.get ? schema.get(\"enum\") : null\n let allowEmptyValue = !enumValue || !required\n let booleanValue = !enumValue && [\"true\", \"false\"]\n const Select = getComponent(\"Select\")\n\n return ( -
    - -
    - -
    -
    -
    - -
    - -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    - - - - -

    -
    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -

    ACL

    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - - - -

    -
    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    - - - -

    -
    - - - -
    - -
    - -
    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    -
    - -
    - -

    -
    -
    -
    - -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    - -
    -
    -
    -

    Domain: (._domainkey)

    -
    -
    -
    -
    -
    - -
    -
    -
    - - -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -

    -

    -
    -
    -
    -
    -
    - - - -
    -
    -
    -
    - - - - -
    -
    -
    -
    -

    -

    -
    -
    -
    -
    -
    - - - -
    -
    -
    -
    - - - - -
    -
    -
    -
    - - - -

    -
    - -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - - -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -

    Domain: (._domainkey)

    -
    -
    -
    -
    -
    - - - -

    -
    - - - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -

    Ratelimit

    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -
    -

    ACL

    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -

    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    - - - -

    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    - - - -

    -
    -
    - -
    - -
    - - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    - - - -

    :

    -
    -
    - -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    - - - -

    :

    -
    -
    - -
    - -
    - - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    - - - -

    -
    - - - - - - - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - - 1-3600 -
    -
    -
    - -
    - -
    -
    -
    - -
    - - 0-32000 -
    -
    -
    - -
    - - 0-125000000 -
    -
    -
    - -
    - - 1-32000 -
    -
    -
    - -
    - - 1-32000 -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    - - - -

    Filter

    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    - - - - - - - - - - - - - - - + $alias, + 'goto' => (preg_match('/^(null|ham|spam)@localhost$/i', $result['goto'])) ? null : $result['goto'], + ]; + } + elseif (isset($_GET['domainadmin'])) { + $domain_admin = $_GET["domainadmin"]; + $result = domain_admin('details', $domain_admin); + $template = 'edit/domainadmin.twig'; + $template_data = [ + 'domain_admin' => $domain_admin, + 'da_acls' => acl('get', 'domainadmin', $domain_admin), + ]; + } + elseif (isset($_GET['admin'])) { + $admin = $_GET["admin"]; + $result = admin('details', $admin); + $template = 'edit/admin.twig'; + $template_data = ['admin' => $admin]; + } + elseif (isset($_GET['domain'])) { + if (is_valid_domain_name($_GET["domain"]) && + !empty($_GET["domain"])) { + // edit domain + $domain = $_GET["domain"]; + $result = mailbox('get', 'domain_details', $domain); + $quota_notification_bcc = quota_notification_bcc('get', $domain); + $rl = ratelimit('get', 'domain', $domain); + $rlyhosts = relayhost('get'); + $domain_footer = mailbox('get', 'domain_wide_footer', $domain); + $mta_sts = mailbox('get', 'mta_sts', $domain); + if (count($mta_sts) == 0) { + $mta_sts = false; + } elseif (isset($mta_sts['mx'])) { + $mta_sts['mx'] = implode(',', $mta_sts['mx']); + } + $template = 'edit/domain.twig'; + $template_data = [ + 'acl' => $_SESSION['acl'], + 'domain' => $domain, + 'quota_notification_bcc' => $quota_notification_bcc, + 'rl' => $rl, + 'rlyhosts' => $rlyhosts, + 'dkim' => dkim('details', $domain), + 'domain_details' => $result, + 'domain_footer' => $domain_footer, + 'mta_sts' => $mta_sts, + 'mailboxes' => mailbox('get', 'mailboxes', $_GET["domain"]), + 'aliases' => mailbox('get', 'aliases', $_GET["domain"], 'address'), + 'alias_domains' => mailbox('get', 'alias_domains', $_GET["domain"]) + ]; + } + } + elseif (isset($_GET['template'])){ + $domain_template = mailbox('get', 'domain_templates', $_GET['template']); + if ($domain_template){ + $template_data = [ + 'template' => $domain_template, + 'rl' => ['frame' => $domain_template['attributes']['rl_frame']], + ]; + $template = 'edit/domain-templates.twig'; + $result = true; + } + else { + $mailbox_template = mailbox('get', 'mailbox_templates', $_GET['template']); + if ($mailbox_template){ + $template_data = [ + 'template' => $mailbox_template, + 'rl' => ['frame' => $mailbox_template['attributes']['rl_frame']], + ]; + $template = 'edit/mailbox-templates.twig'; + $result = true; + } + } + } + elseif (isset($_GET['oauth2client']) && + is_numeric($_GET["oauth2client"]) && + !empty($_GET["oauth2client"])) { + $oauth2client = $_GET["oauth2client"]; + $result = oauth2('details', 'client', $oauth2client); + $template = 'edit/oauth2client.twig'; + $template_data = ['oauth2client' => $oauth2client]; + } + elseif (isset($_GET['aliasdomain']) && + is_valid_domain_name(html_entity_decode(rawurldecode($_GET["aliasdomain"]))) && + !empty($_GET["aliasdomain"])) { + $alias_domain = html_entity_decode(rawurldecode($_GET["aliasdomain"])); + $result = mailbox('get', 'alias_domain_details', $alias_domain); + $rl = ratelimit('get', 'domain', $alias_domain); + $template = 'edit/aliasdomain.twig'; + $template_data = [ + 'alias_domain' => $alias_domain, + 'rl' => $rl, + 'domains' => mailbox('get', 'domains'), + 'dkim' => dkim('details', $alias_domain), + ]; + } + elseif (isset($_GET['mailbox'])){ + if(filter_var(html_entity_decode(rawurldecode($_GET["mailbox"])), FILTER_VALIDATE_EMAIL) && !empty($_GET["mailbox"])) { + // edit mailbox + $mailbox = html_entity_decode(rawurldecode($_GET["mailbox"])); + $result = mailbox('get', 'mailbox_details', $mailbox); + $rl = ratelimit('get', 'mailbox', $mailbox); + $pushover_data = pushover('get', $mailbox); + $quarantine_notification = mailbox('get', 'quarantine_notification', $mailbox); + $quarantine_category = mailbox('get', 'quarantine_category', $mailbox); + $get_tls_policy = mailbox('get', 'tls_policy', $mailbox); + $rlyhosts = relayhost('get'); + $template = 'edit/mailbox.twig'; + $template_data = [ + 'acl' => $_SESSION['acl'], + 'mailbox' => $mailbox, + 'rl' => $rl, + 'pushover_data' => $pushover_data, + 'get_tagging_options' => mailbox('get', 'delimiter_action', $mailbox), + 'quarantine_notification' => $quarantine_notification, + 'quarantine_category' => $quarantine_category, + 'get_tls_policy' => $get_tls_policy, + 'rlyhosts' => $rlyhosts, + 'sender_acl_handles' => mailbox('get', 'sender_acl_handles', $mailbox), + 'user_acls' => acl('get', 'user', $mailbox), + 'mailbox_details' => $result, + 'iam_settings' => $iam_settings, + ]; + } + } + elseif (isset($_GET['relayhost']) && is_numeric($_GET["relayhost"]) && !empty($_GET["relayhost"])) { + $relayhost = intval($_GET["relayhost"]); + $result = relayhost('details', $relayhost); + $template = 'edit/relayhost.twig'; + $template_data = ['relayhost' => $relayhost]; + } + elseif (isset($_GET['transport']) && is_numeric($_GET["transport"]) && !empty($_GET["transport"])) { + $transport = intval($_GET["transport"]); + $result = transport('details', $transport); + $template = 'edit/transport.twig'; + $template_data = ['transport' => $transport]; + } + elseif (isset($_GET['resource']) && filter_var(html_entity_decode(rawurldecode($_GET["resource"])), FILTER_VALIDATE_EMAIL) && !empty($_GET["resource"])) { + $resource = html_entity_decode(rawurldecode($_GET["resource"])); + $result = mailbox('get', 'resource_details', $resource); + $template = 'edit/resource.twig'; + } + elseif (isset($_GET['bcc']) && !empty($_GET["bcc"])) { + $bcc = intval($_GET["bcc"]); + $result = bcc('details', $bcc); + $template = 'edit/bcc.twig'; + $template_data = ['bcc' => $bcc]; + } + elseif (isset($_GET['recipient_map']) && + !empty($_GET["recipient_map"]) && + $_SESSION['mailcow_cc_role'] == "admin") { + $map = intval($_GET["recipient_map"]); + $result = recipient_map('details', $map); + if (substr($result['recipient_map_old'], 0, 1) == '@') { + $result['recipient_map_old'] = substr($result['recipient_map_old'], 1); + } + if (substr($result['recipient_map_new'], 0, 1) == '@') { + $result['recipient_map_new'] = substr($result['recipient_map_new'], 1); + } + $template = 'edit/recipient_map.twig'; + $template_data = ['map' => $map]; + } + elseif (isset($_GET['tls_policy_map']) && + !empty($_GET["tls_policy_map"]) && + $_SESSION['mailcow_cc_role'] == "admin") { + $map = intval($_GET["tls_policy_map"]); + $result = tls_policy_maps('details', $map); + $template = 'edit/tls_policy_map.twig'; + $template_data = [ + 'map' => $map, + 'policy_options' => [ + 'none', + 'may', + 'encrypt', + 'dane', + 'dane-only', + 'fingerprint', + 'verify', + 'secure', + ], + ]; + } + } + if ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin" || $_SESSION['mailcow_cc_role'] == "user") { + if (isset($_GET['syncjob']) && + is_numeric($_GET['syncjob'])) { + $id = $_GET["syncjob"]; + $result = mailbox('get', 'syncjob_details', $id); + $template = 'edit/syncjob.twig'; + } + elseif (isset($_GET['filter']) && + is_numeric($_GET['filter'])) { + $id = $_GET["filter"]; + $result = mailbox('get', 'filter_details', $id); + $template = 'edit/filter.twig'; + } + elseif (isset($_GET['app-passwd']) && + is_numeric($_GET['app-passwd'])) { + $id = $_GET["app-passwd"]; + $result = app_passwd('details', $id); + $template = 'edit/app-passwd.twig'; + } + } +} +else { + $template_data['access_denied'] = true; +} + +$js_minifier->add('/web/js/site/edit.js'); +$js_minifier->add('/web/js/site/pwgen.js'); + +$template_data['result'] = $result; +$template_data['return_to'] = $_SESSION['return_to']; +$template_data['lang_user'] = json_encode($lang['user']); +$template_data['lang_admin'] = json_encode($lang['admin']); +$template_data['lang_datatables'] = json_encode($lang['datatables']); + +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; diff --git a/data/web/f2b-banlist.php b/data/web/f2b-banlist.php new file mode 100644 index 0000000000..05c769907a --- /dev/null +++ b/data/web/f2b-banlist.php @@ -0,0 +1,11 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/data/web/fonts/glyphicons-halflings-regular.ttf b/data/web/fonts/glyphicons-halflings-regular.ttf deleted file mode 100644 index 1413fc609a..0000000000 Binary files a/data/web/fonts/glyphicons-halflings-regular.ttf and /dev/null differ diff --git a/data/web/fonts/glyphicons-halflings-regular.woff b/data/web/fonts/glyphicons-halflings-regular.woff deleted file mode 100644 index 9e612858f8..0000000000 Binary files a/data/web/fonts/glyphicons-halflings-regular.woff and /dev/null differ diff --git a/data/web/fonts/glyphicons-halflings-regular.woff2 b/data/web/fonts/glyphicons-halflings-regular.woff2 deleted file mode 100644 index 64539b54c3..0000000000 Binary files a/data/web/fonts/glyphicons-halflings-regular.woff2 and /dev/null differ diff --git a/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-700.woff b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-700.woff new file mode 100644 index 0000000000..386899f3b1 Binary files /dev/null and b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-700.woff differ diff --git a/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-700.woff2 b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-700.woff2 new file mode 100644 index 0000000000..ca640807e5 Binary files /dev/null and b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-700.woff2 differ diff --git a/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff new file mode 100644 index 0000000000..62f4d7778b Binary files /dev/null and b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff differ diff --git a/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff2 b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff2 new file mode 100644 index 0000000000..226210c431 Binary files /dev/null and b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff2 differ diff --git a/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-italic.woff b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-italic.woff new file mode 100644 index 0000000000..9d40dec272 Binary files /dev/null and b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-italic.woff differ diff --git a/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-italic.woff2 b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-italic.woff2 new file mode 100644 index 0000000000..f43754dbe6 Binary files /dev/null and b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-italic.woff2 differ diff --git a/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-regular.woff b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-regular.woff new file mode 100644 index 0000000000..2b512bc31e Binary files /dev/null and b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-regular.woff differ diff --git a/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-regular.woff2 b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-regular.woff2 new file mode 100644 index 0000000000..1f1db1a2cf Binary files /dev/null and b/data/web/fonts/noto-sans-v12-latin_greek_cyrillic-regular.woff2 differ diff --git a/data/web/fonts/source-sans-pro-v21-latin-300.woff b/data/web/fonts/source-sans-pro-v21-latin-300.woff new file mode 100644 index 0000000000..e966494d35 Binary files /dev/null and b/data/web/fonts/source-sans-pro-v21-latin-300.woff differ diff --git a/data/web/fonts/source-sans-pro-v21-latin-300.woff2 b/data/web/fonts/source-sans-pro-v21-latin-300.woff2 new file mode 100644 index 0000000000..fed32a9335 Binary files /dev/null and b/data/web/fonts/source-sans-pro-v21-latin-300.woff2 differ diff --git a/data/web/fonts/source-sans-pro-v21-latin-300italic.woff b/data/web/fonts/source-sans-pro-v21-latin-300italic.woff new file mode 100644 index 0000000000..c0dca072a3 Binary files /dev/null and b/data/web/fonts/source-sans-pro-v21-latin-300italic.woff differ diff --git a/data/web/fonts/source-sans-pro-v21-latin-300italic.woff2 b/data/web/fonts/source-sans-pro-v21-latin-300italic.woff2 new file mode 100644 index 0000000000..a8e1fff22a Binary files /dev/null and b/data/web/fonts/source-sans-pro-v21-latin-300italic.woff2 differ diff --git a/data/web/fonts/source-sans-pro-v21-latin-700.woff b/data/web/fonts/source-sans-pro-v21-latin-700.woff new file mode 100644 index 0000000000..a6786d1f4a Binary files /dev/null and b/data/web/fonts/source-sans-pro-v21-latin-700.woff differ diff --git a/data/web/fonts/source-sans-pro-v21-latin-700.woff2 b/data/web/fonts/source-sans-pro-v21-latin-700.woff2 new file mode 100644 index 0000000000..cd6bfd0f4b Binary files /dev/null and b/data/web/fonts/source-sans-pro-v21-latin-700.woff2 differ diff --git a/data/web/fonts/source-sans-pro-v21-latin-700italic.woff b/data/web/fonts/source-sans-pro-v21-latin-700italic.woff new file mode 100644 index 0000000000..729bdee92b Binary files /dev/null and b/data/web/fonts/source-sans-pro-v21-latin-700italic.woff differ diff --git a/data/web/fonts/source-sans-pro-v21-latin-700italic.woff2 b/data/web/fonts/source-sans-pro-v21-latin-700italic.woff2 new file mode 100644 index 0000000000..b413356f73 Binary files /dev/null and b/data/web/fonts/source-sans-pro-v21-latin-700italic.woff2 differ diff --git a/data/web/fonts/source-sans-pro-v21-latin-italic.woff b/data/web/fonts/source-sans-pro-v21-latin-italic.woff new file mode 100644 index 0000000000..f927419cba Binary files /dev/null and b/data/web/fonts/source-sans-pro-v21-latin-italic.woff differ diff --git a/data/web/fonts/source-sans-pro-v21-latin-italic.woff2 b/data/web/fonts/source-sans-pro-v21-latin-italic.woff2 new file mode 100644 index 0000000000..9448cd526e Binary files /dev/null and b/data/web/fonts/source-sans-pro-v21-latin-italic.woff2 differ diff --git a/data/web/fonts/source-sans-pro-v21-latin-regular.woff b/data/web/fonts/source-sans-pro-v21-latin-regular.woff new file mode 100644 index 0000000000..db90a83e04 Binary files /dev/null and b/data/web/fonts/source-sans-pro-v21-latin-regular.woff differ diff --git a/data/web/fonts/source-sans-pro-v21-latin-regular.woff2 b/data/web/fonts/source-sans-pro-v21-latin-regular.woff2 new file mode 100644 index 0000000000..e49928e829 Binary files /dev/null and b/data/web/fonts/source-sans-pro-v21-latin-regular.woff2 differ diff --git a/data/web/img/rspamd_logo.png b/data/web/img/rspamd_logo_dark.png similarity index 100% rename from data/web/img/rspamd_logo.png rename to data/web/img/rspamd_logo_dark.png diff --git a/data/web/img/rspamd_logo_light.png b/data/web/img/rspamd_logo_light.png new file mode 100644 index 0000000000..0818e4d171 Binary files /dev/null and b/data/web/img/rspamd_logo_light.png differ diff --git a/data/web/inc/ajax/container_ctrl.php b/data/web/inc/ajax/container_ctrl.php index 792e2fe46f..48c21cb13b 100644 --- a/data/web/inc/ajax/container_ctrl.php +++ b/data/web/inc/ajax/container_ctrl.php @@ -1,5 +1,11 @@ diff --git a/data/web/inc/ajax/dns_diagnostics.php b/data/web/inc/ajax/dns_diagnostics.php index dd67dec943..7ed92db77e 100644 --- a/data/web/inc/ajax/dns_diagnostics.php +++ b/data/web/inc/ajax/dns_diagnostics.php @@ -2,418 +2,498 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/spf.inc.php'; -define('state_good', ''); -define('state_missing', ''); +define('state_good', ''); +define('state_missing', ''); define('state_nomatch', "?"); define('state_optional', " 2"); if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin"|| $_SESSION['mailcow_cc_role'] == "domainadmin")) { -$domains = mailbox('get', 'domains'); -foreach(mailbox('get', 'domains') as $dn) { - $domains = array_merge($domains, mailbox('get', 'alias_domains', $dn)); -} + $alias_domains = array(); -if (isset($_GET['domain'])) { - if (is_valid_domain_name($_GET['domain'])) { - if (in_array($_GET['domain'], $domains)) { + if (isset($_GET['domain'])) { + $domain_details = mailbox('get', 'domain_details', $_GET['domain']); + if ($domain_details !== false) { $domain = $_GET['domain']; + $alias_domains = array_merge($alias_domains, mailbox('get', 'alias_domains', $domain)); } else { echo "No such domain in context"; - die(); + exit(); } } - else { - echo "Invalid domain name"; - die(); + + $ch = curl_init('http://ip4.mailcow.email'); + curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + curl_setopt($ch, CURLOPT_VERBOSE, false); + curl_setopt($ch, CURLOPT_HEADER, false); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); + $ip = curl_exec($ch); + curl_close($ch); + + $ch = curl_init('http://ip6.mailcow.email'); + curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); + curl_setopt($ch, CURLOPT_VERBOSE, false); + curl_setopt($ch, CURLOPT_HEADER, false); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); + $ip6 = curl_exec($ch); + curl_close($ch); + + $ptr = implode('.', array_reverse(explode('.', $ip))) . '.in-addr.arpa'; + if (!empty($ip6)) { + $ip6_full = str_replace('::', str_repeat(':', 9-substr_count($ip6, ':')), $ip6); + $ip6_full = str_replace('::', ':0:', $ip6_full); + $ip6_full = str_replace('::', ':0:', $ip6_full); + $ptr6 = ''; + foreach (explode(':', $ip6_full) as $part) { + $ptr6 .= str_pad($part, 4, '0', STR_PAD_LEFT); + } + $ptr6 = implode('.', array_reverse(str_split($ptr6, 1))) . '.ip6.arpa'; } -} -$ch = curl_init('http://ip4.mailcow.email'); -curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); -curl_setopt($ch, CURLOPT_VERBOSE, false); -curl_setopt($ch, CURLOPT_HEADER, false); -curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); -curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); -$ip = curl_exec($ch); -curl_close($ch); - -$ch = curl_init('http://ip6.mailcow.email'); -curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); -curl_setopt($ch, CURLOPT_VERBOSE, false); -curl_setopt($ch, CURLOPT_HEADER, false); -curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); -curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); -$ip6 = curl_exec($ch); -curl_close($ch); - -$ptr = implode('.', array_reverse(explode('.', $ip))) . '.in-addr.arpa'; -if (!empty($ip6)) { - $ip6_full = str_replace('::', str_repeat(':', 9-substr_count($ip6, ':')), $ip6); - $ip6_full = str_replace('::', ':0:', $ip6_full); - $ip6_full = str_replace('::', ':0:', $ip6_full); - $ptr6 = ''; - foreach (explode(':', $ip6_full) as $part) { - $ptr6 .= str_pad($part, 4, '0', STR_PAD_LEFT); + $https_port = strpos($_SERVER['HTTP_HOST'], ':'); + if ($https_port === FALSE) { + $https_port = 443; + } + else { + $https_port = substr($_SERVER['HTTP_HOST'], $https_port+1); } - $ptr6 = implode('.', array_reverse(str_split($ptr6, 1))) . '.ip6.arpa'; -} -$https_port = strpos($_SERVER['HTTP_HOST'], ':'); -if ($https_port === FALSE) { - $https_port = 443; -} -else { - $https_port = substr($_SERVER['HTTP_HOST'], $https_port+1); -} + if (!isset($autodiscover_config['sieve'])) { + $autodiscover_config['sieve'] = array( + 'server' => $mailcow_hostname, + 'port' => array_pop(explode(':', getenv('SIEVE_PORT'))) + ); + } -if (!isset($autodiscover_config['sieve'])) { - $autodiscover_config['sieve'] = array('server' => $mailcow_hostname, 'port' => array_pop(explode(':', getenv('SIEVE_PORT')))); -} + // Init records array + $spf_link = 'SPF Record Syntax
    '; + $dmarc_link = 'DMARC Assistant'; + $mtasts_report_link = 'TLS Report Record Syntax'; -// Init records array -$spf_link = 'SPF Record Syntax
    '; -$dmarc_link = 'DMARC Assistant'; + $records = array(); -$records = array(); -if ($_SESSION['mailcow_cc_role'] == "admin") { - $records[] = array( - $mailcow_hostname, - 'A', - $ip - ); - $records[] = array( - $ptr, - 'PTR', - $mailcow_hostname - ); - if (!empty($ip6)) { + if ($_SESSION['mailcow_cc_role'] == "admin") { $records[] = array( $mailcow_hostname, - 'AAAA', - $ip6 + 'A', + $ip ); $records[] = array( - $ptr6, + $ptr, 'PTR', $mailcow_hostname ); + if (!empty($ip6)) { + $records[] = array( + $mailcow_hostname, + 'AAAA', + expand_ipv6($ip6) + ); + $records[] = array( + $ptr6, + 'PTR', + $mailcow_hostname + ); + } + $records[] = array( + '_25._tcp.' . $autodiscover_config['smtp']['server'], + 'TLSA', + generate_tlsa_digest($autodiscover_config['smtp']['server'], 25, 1) + ); } + $records[] = array( - '_25._tcp.'.$autodiscover_config['smtp']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['smtp']['server'], 25, 1) - ); - $records[] = array( - '_'.$https_port. - '._tcp.'.$mailcow_hostname, - 'TLSA', - generate_tlsa_digest($mailcow_hostname, $https_port) - ); - $records[] = array( - '_'.$autodiscover_config['pop3']['tlsport']. - '._tcp.'.$autodiscover_config['pop3']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['tlsport'], 1) - ); - $records[] = array( - '_'.$autodiscover_config['imap']['tlsport']. - '._tcp.'.$autodiscover_config['imap']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['tlsport'], 1) - ); - $records[] = array( - '_'.$autodiscover_config['smtp']['port']. - '._tcp.'.$autodiscover_config['smtp']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['port']) - ); - $records[] = array( - '_'.$autodiscover_config['smtp']['tlsport']. - '._tcp.'.$autodiscover_config['smtp']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['tlsport'], 1) - ); - $records[] = array( - '_'.$autodiscover_config['imap']['port']. - '._tcp.'.$autodiscover_config['imap']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['port']) - ); - $records[] = array( - '_'.$autodiscover_config['pop3']['port']. - '._tcp.'.$autodiscover_config['pop3']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['port']) - ); - $records[] = array( - '_'.$autodiscover_config['sieve']['port']. - '._tcp.'.$autodiscover_config['sieve']['server'], - 'TLSA', - generate_tlsa_digest($autodiscover_config['sieve']['server'], $autodiscover_config['sieve']['port'], 1) - ); -} -$records[] = array( - $domain, - 'MX', - $mailcow_hostname -); -$records[] = array( - 'autodiscover.'.$domain, - 'CNAME', - $mailcow_hostname -); -$records[] = array( - '_autodiscover._tcp.'.$domain, - 'SRV', - $mailcow_hostname. - ' '.$https_port -); -$records[] = array( - 'autoconfig.'.$domain, - 'CNAME', - $mailcow_hostname -); -$records[] = array( - $domain, - 'TXT', - $spf_link, - state_optional -); -$records[] = array( - '_dmarc.'.$domain, - 'TXT', - $dmarc_link, - state_optional -); - -if (!empty($dkim = dkim('details', $domain))) { - $records[] = array( - $dkim['dkim_selector'] . '._domainkey.' . $domain, - 'TXT', - $dkim['dkim_txt'] + $domain, + 'MX', + $mailcow_hostname ); -} -$current_records = dns_get_record('_pop3._tcp.' . $domain, DNS_SRV); -if (count($current_records) == 0 || $current_records[0]['target'] != '') { - if ($autodiscover_config['pop3']['tlsport'] != '110') { + + if (!in_array($domain, $alias_domains)) { $records[] = array( - '_pop3._tcp.' . $domain, + 'autodiscover.' . $domain, + 'CNAME', + $mailcow_hostname + ); + $records[] = array( + '_autodiscover._tcp.' . $domain, 'SRV', - $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['tlsport'] + $mailcow_hostname . ' ' . $https_port + ); + $records[] = array( + 'autoconfig.' . $domain, + 'CNAME', + $mailcow_hostname ); } -} -else { - $records[] = array( - '_pop3._tcp.' . $domain, - 'SRV', - '. 0' - ); -} -$current_records = dns_get_record('_pop3s._tcp.' . $domain, DNS_SRV); -if (count($current_records) == 0 || $current_records[0]['target'] != '') { - if ($autodiscover_config['pop3']['port'] != '995') { + + // Check if domain is an alias domain and get target domain's MTA-STS + $alias_domain_details = mailbox('get', 'alias_domain_details', $domain); + $mta_sts_domain = $domain; + + if ($alias_domain_details !== false && !empty($alias_domain_details['target_domain'])) { + // This is an alias domain, check target domain for MTA-STS + $mta_sts_domain = $alias_domain_details['target_domain']; + } + + $mta_sts = mailbox('get', 'mta_sts', $mta_sts_domain); + if (count($mta_sts) > 0 && $mta_sts['active'] == 1) { + if (!in_array($domain, $alias_domains)) { + $records[] = array( + 'mta-sts.' . $domain, + 'CNAME', + $mailcow_hostname + ); + } $records[] = array( - '_pop3s._tcp.' . $domain, - 'SRV', - $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['port'] + '_mta-sts.' . $domain, + 'TXT', + "v={$mta_sts['version']};id={$mta_sts['id']};", + ); + $records[] = array( + '_smtp._tls.' . $domain, + 'TXT', + $mtasts_report_link, ); } -} -else { - $records[] = array( - '_pop3s._tcp.' . $domain, - 'SRV', - '. 0' - ); -} -if ($autodiscover_config['imap']['tlsport'] != '143') { - $records[] = array( - '_imap._tcp.' . $domain, - 'SRV', - $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['tlsport'] - ); -} -if ($autodiscover_config['imap']['port'] != '993') { - $records[] = array( - '_imaps._tcp.' . $domain, - 'SRV', - $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['port'] - ); -} -if ($autodiscover_config['smtp']['tlsport'] != '587') { - $records[] = array( - '_submission._tcp.' . $domain, - 'SRV', - $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['tlsport'] - ); -} -if ($autodiscover_config['smtp']['port'] != '465') { + $records[] = array( - '_smtps._tcp.' . $domain, - 'SRV', - $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['port'] + $domain, + 'TXT', + $spf_link, + state_optional ); -} -if ($autodiscover_config['sieve']['port'] != '4190') { + $records[] = array( - '_sieve._tcp.' . $domain, - 'SRV', - $autodiscover_config['sieve']['server'] . ' ' . $autodiscover_config['sieve']['port'] + '_dmarc.' . $domain, + 'TXT', + $dmarc_link, + state_optional ); -} -$record_types = array( - 'A' => DNS_A, - 'AAAA' => DNS_AAAA, - 'CNAME' => DNS_CNAME, - 'MX' => DNS_MX, - 'PTR' => DNS_PTR, - 'SRV' => DNS_SRV, - 'TXT' => DNS_TXT, -); -$data_field = array( - 'A' => 'ip', - 'AAAA' => 'ipv6', - 'CNAME' => 'target', - 'MX' => 'target', - 'PTR' => 'target', - 'SRV' => 'data', - 'TLSA' => 'data', - 'TXT' => 'txt', -); -?> -
    - - - - - - - - 0 && count($cname) > 0) { - if ($a[0]['ip'] == $cname[0]['ip']) { - $currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $record[2])); - $aaaa = dns_get_record($record[0], DNS_AAAA); - $cname = dns_get_record($record[2], DNS_AAAA); - if (count($aaaa) == 0 || count($cname) == 0 || $aaaa[0]['ipv6'] != $cname[0]['ipv6']) { - $currents[0]['target'] = $aaaa[0]['ipv6'] . ' 1'; - } - } - else { - $currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $a[0]['ip'] . ' 1')); + $current_records = (array)dns_get_record('_pop3s._tcp.' . $domain, DNS_SRV); + + if (count($current_records) == 0 || $current_records[0]['target'] != '') { + if ($autodiscover_config['pop3']['port'] != '995') { + $records[] = array( + '_pop3s._tcp.' . $domain, + 'SRV', + $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['port'] + ); } } - } + else { + $records[] = array( + '_pop3s._tcp.' . $domain, + 'SRV', + '. 0' + ); + } - foreach ($currents as &$current) { - if ($current['type'] == 'TXT' && - stripos($current['txt'], 'v=dmarc') === 0 && - $record[2] == $dmarc_link) { - $current['txt'] = str_replace(' ', '', $current['txt']); - $state = $current[$data_field[$current['type']]] . state_optional; + if ($autodiscover_config['imap']['tlsport'] != '143') { + $records[] = array( + '_imap._tcp.' . $domain, + 'SRV', + $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['tlsport'] + ); } - elseif ($current['type'] == 'TXT' && - stripos($current['txt'], 'v=spf') === 0 && - $record[2] == $spf_link) { - $state = state_nomatch; - $rslt = get_spf_allowed_hosts($record[0]); - if(in_array($ip, $rslt) && in_array($ip6, $rslt)){ - $state = state_good; - } - $state .= '
    ' . $current[$data_field[$current['type']]].state_optional; + + if ($autodiscover_config['imap']['port'] != '993') { + $records[] = array( + '_imaps._tcp.' . $domain, + 'SRV', + $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['port'] + ); } - elseif ($current['type'] == 'TXT' && - stripos($current['txt'], 'v=dkim') === 0 && - stripos($record[2], 'v=dkim') === 0) { - preg_match('/v=DKIM1;.*k=rsa;.*p=(.*)/i', $current[$data_field[$current['type']]], $dkim_matches_current); - preg_match('/v=DKIM1;.*k=rsa;.*p=(.*)/i', $record[2], $dkim_matches_good); - if ($dkim_matches_current[1] == $dkim_matches_good[1]) { - $state = state_good; - } + + if ($autodiscover_config['smtp']['tlsport'] != '587') { + $records[] = array( + '_submission._tcp.' . $domain, + 'SRV', + $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['tlsport'] + ); } - elseif ($current['type'] != 'TXT' && - isset($data_field[$current['type']]) && $state != state_good) { - $state = state_nomatch; - if ($current[$data_field[$current['type']]] == $record[2]) { - $state = state_good; - } + + if ($autodiscover_config['smtp']['port'] != '465') { + $records[] = array( + '_smtps._tcp.' . $domain, + 'SRV', + $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['port'] + ); } - } - unset($current); - if (isset($record[3]) && - $record[3] == state_optional && - ($state == state_missing || $state == state_nomatch)) { - $state = state_optional; - } - - if ($state == state_nomatch) { - $state = array(); - foreach ($currents as $current) { - $state[] = $current[$data_field[$current['type']]]; + if ($autodiscover_config['sieve']['port'] != '4190') { + $records[] = array( + '_sieve._tcp.' . $domain, + 'SRV', + $autodiscover_config['sieve']['server'] . ' ' . $autodiscover_config['sieve']['port'] + ); } - $state = implode('
    ', $state); } - echo sprintf(' - - - - - ', $record[0], $record[1], $record[2], $state); + + $record_types = array( + 'A' => DNS_A, + 'AAAA' => DNS_AAAA, + 'CNAME' => DNS_CNAME, + 'MX' => DNS_MX, + 'PTR' => DNS_PTR, + 'SRV' => DNS_SRV, + 'TXT' => DNS_TXT, + ); + + $data_field = array( + 'A' => 'ip', + 'AAAA' => 'ipv6', + 'CNAME' => 'target', + 'MX' => 'target', + 'PTR' => 'target', + 'SRV' => 'data', + 'TLSA' => 'data', + 'TXT' => 'txt', + ); + + ?> +
    +
    %s%s%s
    + + + + + + + (CNAME)'; + $currents[0]['ipv6'] = state_missing . ' (CNAME)'; + } + } + if ($record[1] == 'SRV') { + foreach ($currents as &$current) { + if ($current['target'] == '') { + $current['target'] = '.'; + $current['port'] = '0'; + } + $current['data'] = $current['target'] . ' ' . $current['port']; + } + unset($current); + } + elseif ($record[1] == 'TXT') { + foreach ($currents as &$current) { + unset($current); + } + unset($current); + } + elseif ($record[1] == 'AAAA') { + foreach ($currents as &$current) { + $current['ipv6'] = expand_ipv6($current['ipv6']); + } + } + } + + if ($record[1] == 'CNAME' && count($currents) == 0) { + // A and AAAA are also valid instead of CNAME + $a = (array)dns_get_record($record[0], DNS_A); + $cname = (array)dns_get_record($record[2], DNS_A); + if (count($a) > 0 && count($cname) > 0) { + if ($a[0]['ip'] == $cname[0]['ip']) { + $currents = array( + array( + 'host' => $record[0], + 'class' => 'IN', + 'type' => 'CNAME', + 'target' => $record[2] + ) + ); + $aaaa = (array)dns_get_record($record[0], DNS_AAAA); + $cname = (array)dns_get_record($record[2], DNS_AAAA); + if (count($aaaa) == 0 || count($cname) == 0 || expand_ipv6($aaaa[0]['ipv6']) != expand_ipv6($cname[0]['ipv6'])) { + $currents[0]['target'] = expand_ipv6($aaaa[0]['ipv6']) . ' 1'; + } + } + else { + $currents = array( + array( + 'host' => $record[0], + 'class' => 'IN', + 'type' => 'CNAME', + 'target' => $a[0]['ip'] . ' 1' + ) + ); + } + } + } + + foreach ($currents as &$current) { + if ($current['type'] == "TXT" && + stripos(strtolower($current['txt']), 'v=sts') === 0) { + if (strtolower($current[$data_field[$current['type']]]) == strtolower($record[2])) { + $state = state_good; + } + else { + $state = state_nomatch; + } + $state .= '
    ' . htmlspecialchars($current[$data_field[$current['type']]]); + } + if ($current['type'] == 'TXT' && + stripos($current['txt'], 'v=dmarc') === 0 && + $record[2] == $dmarc_link) { + $current['txt'] = str_replace(' ', '', $current['txt']); + $state = htmlspecialchars($current[$data_field[$current['type']]]) . state_optional; + } + elseif ($current['type'] == 'TXT' && + stripos($current['txt'], 'v=spf') === 0 && + $record[2] == $spf_link) { + $state = state_nomatch; + $rslt = get_spf_allowed_hosts($record[0], true); + if (in_array($ip, $rslt) && in_array(expand_ipv6($ip6), $rslt)) { + $state = state_good; + } + $state .= '
    ' . htmlspecialchars($current[$data_field[$current['type']]]) . state_optional; + } + elseif ($current['type'] == 'TXT' && + stripos($current['txt'], 'v=dkim') === 0 && + stripos($record[2], 'v=dkim') === 0) { + preg_match('/v=DKIM1;.*k=rsa;.*p=([^;]*).*/i', $current[$data_field[$current['type']]], $dkim_matches_current); + preg_match('/v=DKIM1;.*k=rsa;.*p=([^;]*).*/i', $record[2], $dkim_matches_good); + if ($dkim_matches_current[1] == $dkim_matches_good[1]) { + $state = state_good; + } + } + elseif ($current['type'] != 'TXT' && + isset($data_field[$current['type']]) && $state != state_good) { + $state = state_nomatch; + if ($current[$data_field[$current['type']]] == $record[2]) { + $state = state_good; + } + } + } + unset($current); + + if (isset($record[3]) && + $record[3] == state_optional && + ($state == state_missing || $state == state_nomatch)) { + $state = state_optional; + } + + if ($state == state_nomatch) { + $state = array(); + foreach ($currents as $current) { + $state[] = htmlspecialchars($current[$data_field[$current['type']]]); + } + $state = implode('
    ', $state); + } + echo sprintf(' + + + + + + ', htmlspecialchars($record[0]), htmlspecialchars($record[1]), $record[2], $state); + $record[3] = explode('
    ', $state); + } + + unset($record); + + $dns_data = sprintf("\$ORIGIN %s.\n", $domain); + foreach ($records as $record) { + if ($domain == substr($record[0], -strlen($domain))) { + $label = substr($record[0], 0, -strlen($domain)-1); + $val = $record[2]; + + if (strlen($label) == 0) { + $label = "@"; + } + + $vals = array(); + if (strpos($val, " 0) { + $vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val); + } + } + } + else { + $vals[] = sprintf("%s\tIN\t%s\t%s\n", $label, $record[1], $val); + } + + foreach ($vals as $val) { + $dns_data .= str_replace($domain, $domain . '.', $val); + } + } + } + ?> +
    %s%s%s
    + .txt' type='text/csv'>Download + +
    +

    + 1
    + 2 +

    + - - -

    -1
    -2 -

    - diff --git a/data/web/inc/ajax/qitem_details.php b/data/web/inc/ajax/qitem_details.php index 801fd3d0ab..1611c82277 100644 --- a/data/web/inc/ajax/qitem_details.php +++ b/data/web/inc/ajax/qitem_details.php @@ -1,10 +1,7 @@ getAddresses($headerName); + foreach ($addresses as $address) { + if (filter_var($address['address'], FILTER_VALIDATE_EMAIL)) { + $list[] = array('address' => $address['address'], 'type' => $headerName); + } + } +} + +if (!empty($_GET['hash']) && ctype_alnum($_GET['hash'])) { + $mailc = quarantine('hash_details', $_GET['hash']); + if ($mailc === false) { + echo json_encode(array('error' => 'Message invalid')); + exit; + } + if (strlen($mailc['msg']) > 10485760) { + echo json_encode(array('error' => 'Message size exceeds 10 MiB.')); + exit; + } + if (!empty($mailc['msg'])) { + // Init message array + $data = array(); + // Init parser + $mail_parser = new PhpMimeMailParser\Parser(); + $html2text = new Html2Text\Html2Text(); + // Load msg to parser + $mail_parser->setText($mailc['msg']); + // Get mail recipients + { + $recipientsList = array(); + addAddresses($recipientsList, $mail_parser, 'to'); + addAddresses($recipientsList, $mail_parser, 'cc'); + addAddresses($recipientsList, $mail_parser, 'bcc'); + $recipientsList[] = array('address' => $mailc['rcpt'], 'type' => 'smtp'); + $data['recipients'] = $recipientsList; + } + // Get from + $data['header_from'] = $mail_parser->getHeader('from'); + $data['env_from'] = $mailc['sender']; + // Get rspamd score + $data['score'] = $mailc['score']; + // Get rspamd action + $data['action'] = $mailc['action']; + // Get rspamd symbols + $data['symbols'] = json_decode($mailc['symbols']); + // Get fuzzy hashes + $data['fuzzy_hashes'] = json_decode($mailc['fuzzy_hashes']); + $data['subject'] = mb_convert_encoding($mail_parser->getHeader('subject'), "UTF-8", "auto"); + (empty($data['subject'])) ? $data['subject'] = '-' : null; + echo json_encode($data); + } +} +elseif (!empty($_GET['id']) && ctype_alnum($_GET['id'])) { + if (!isset($_SESSION['mailcow_cc_role'])) { + echo json_encode(array('error' => 'Access denied')); + exit(); + } $tmpdir = '/tmp/' . $_GET['id'] . '/'; $mailc = quarantine('details', $_GET['id']); + if ($mailc === false) { + echo json_encode(array('error' => 'Access denied')); + exit; + } if (strlen($mailc['msg']) > 10485760) { echo json_encode(array('error' => 'Message size exceeds 10 MiB.')); exit; } if (!empty($mailc['msg'])) { + if (isset($_GET['quick_release'])) { + $hash = hash('sha256', $mailc['id'] . $mailc['qid']); + header('Location: /qhandler/release/' . $hash); + exit; + } + if (isset($_GET['quick_delete'])) { + $hash = hash('sha256', $mailc['id'] . $mailc['qid']); + header('Location: /qhandler/delete/' . $hash); + exit; + } // Init message array $data = array(); // Init parser @@ -36,13 +104,43 @@ function rrmdir($src) { $html2text = new Html2Text\Html2Text(); // Load msg to parser $mail_parser->setText($mailc['msg']); + + // Get mail recipients + { + $recipientsList = array(); + addAddresses($recipientsList, $mail_parser, 'to'); + addAddresses($recipientsList, $mail_parser, 'cc'); + addAddresses($recipientsList, $mail_parser, 'bcc'); + $recipientsList[] = array('address' => $mailc['rcpt'], 'type' => 'smtp'); + $data['recipients'] = $recipientsList; + } + // Get from + $data['header_from'] = $mail_parser->getHeader('from'); + $data['env_from'] = $mailc['sender']; + // Get rspamd score + $data['score'] = $mailc['score']; + // Get rspamd action + $data['action'] = $mailc['action']; + // Get rspamd symbols + $data['symbols'] = json_decode($mailc['symbols']); + // Get fuzzy hashes + $data['fuzzy_hashes'] = json_decode($mailc['fuzzy_hashes']); // Get text/plain content $data['text_plain'] = $mail_parser->getMessageBody('text'); + if (!json_encode($data['text_plain'])) $data['text_plain'] = ''; // Get html content and convert to text $data['text_html'] = $html2text->convert($mail_parser->getMessageBody('html')); + if (empty($data['text_plain']) && empty($data['text_html'])) { + // Failed to parse content, try raw + $text = trim(substr($mailc['msg'], strpos($mailc['msg'], "\r\n\r\n") + 1)); + // Only return html->text + $data['text_plain'] = 'Parser failed, assuming HTML'; + $data['text_html'] = $html2text->convert($text); + } (empty($data['text_plain'])) ? $data['text_plain'] = '-' : null; // Get subject $data['subject'] = $mail_parser->getHeader('subject'); + $data['subject'] = mb_convert_encoding($mail_parser->getHeader('subject'), "UTF-8", "auto"); (empty($data['subject'])) ? $data['subject'] = '-' : null; // Get attachments if (is_dir($tmpdir)) { @@ -66,16 +164,36 @@ function rrmdir($src) { ); } } + if (isset($_GET['eml'])) { + $dl_filename = filter_var($data['subject'], FILTER_SANITIZE_STRING); + $dl_filename = strlen($dl_filename) > 30 ? substr($dl_filename,0,30) : $dl_filename; + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Cache-Control: private', false); + header('Content-Type: message/rfc822'); + header('Content-Disposition: attachment; filename="'. $dl_filename . '.eml";'); + header('Content-Transfer-Encoding: binary'); + header('Content-Length: ' . strlen($mailc['msg'])); + echo $mailc['msg']; + exit; + } if (isset($_GET['att'])) { + if ($_SESSION['acl']['quarantine_attachments'] == 0) { + exit(json_encode('Forbidden')); + } $dl_id = intval($_GET['att']); - $dl_filename = $data['attachments'][$dl_id][0]; + $dl_filename = filter_var($data['attachments'][$dl_id][0], FILTER_SANITIZE_STRING); + $dl_filename_short = strlen($dl_filename) > 20 ? substr($dl_filename, 0, 20) : $dl_filename; + $dl_filename_extension = pathinfo($tmpdir . $dl_filename)['extension']; + $dl_filename_short = preg_replace('/\.' . $dl_filename_extension . '$/', '', $dl_filename_short); if (!is_dir($tmpdir . $dl_filename) && file_exists($tmpdir . $dl_filename)) { header('Pragma: public'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Cache-Control: private', false); header('Content-Type: ' . $data['attachments'][$dl_id][1]); - header('Content-Disposition: attachment; filename="'. $dl_filename . '";'); + header('Content-Disposition: attachment; filename="'. $dl_filename_short . '.' . $dl_filename_extension . '";'); header('Content-Transfer-Encoding: binary'); header('Content-Length: ' . $data['attachments'][$dl_id][2]); readfile($tmpdir . $dl_filename); @@ -84,5 +202,6 @@ function rrmdir($src) { } echo json_encode($data); } + } ?> diff --git a/data/web/inc/ajax/qr_gen.php b/data/web/inc/ajax/qr_gen.php new file mode 100644 index 0000000000..267d65ad55 --- /dev/null +++ b/data/web/inc/ajax/qr_gen.php @@ -0,0 +1,10 @@ +getQRCodeImageAsDataUri($_SESSION['mailcow_cc_username'], $_GET['token']); +} +?> diff --git a/data/web/inc/ajax/relay_check.php b/data/web/inc/ajax/relay_check.php deleted file mode 100644 index 34f8eceb45..0000000000 --- a/data/web/inc/ajax/relay_check.php +++ /dev/null @@ -1,81 +0,0 @@ -Timeout = 10; - $mail->SMTPOptions = array( - 'ssl' => array( - 'verify_peer' => false, - 'verify_peer_name' => false, - 'allow_self_signed' => true - ) - ); - $mail->SMTPDebug = 3; - $mail->Debugoutput = function($str, $level) { - foreach(preg_split("/((\r?\n)|(\r\n?)|\n)/", $str) as $line){ - if (empty($line)) { continue; } - if (preg_match("/SERVER \-\> CLIENT: 2\d\d.+/i", $line)) { - echo '' . htmlspecialchars($line) . '
    '; - } - elseif (preg_match("/SERVER \-\> CLIENT: 3\d\d.+/i", $line)) { - echo '' . htmlspecialchars($line) . '
    '; - } - elseif (preg_match("/SERVER \-\> CLIENT: 4\d\d.+/i", $line)) { - echo '' . htmlspecialchars($line) . '
    '; - } - elseif (preg_match("/SERVER \-\> CLIENT: 5\d\d.+/i", $line)) { - echo '' . htmlspecialchars($line) . '
    '; - } - elseif (preg_match("/CLIENT \-\> SERVER:.+/i", $line)) { - echo '' . htmlspecialchars($line) . '
    '; - } - elseif (preg_match("/^(?!SERVER|CLIENT|Connection:|\)).+$/i", $line)) { - echo '    โ†ช ' . htmlspecialchars($line) . '
    '; - } - else { - echo htmlspecialchars($line) . '
    '; - } - } - }; - $mail->isSMTP(); - $mail->Host = $hostname; - if (!empty($username)) { - $mail->SMTPAuth = true; - $mail->Username = $username; - $mail->Password = $password; - } - $mail->Port = $port; - $mail->setFrom($mail_from, 'Mailer'); - $mail->Subject = 'A subject for a SMTP test'; - $mail->addAddress($RELAY_TO, 'Joe Null'); - $mail->Body = 'This is our test body'; - $mail->send(); - } - else { - echo "Unknown relayhost."; - } -} -else { - echo "Permission denied."; -} diff --git a/data/web/inc/ajax/show_rspamd_global_filters.php b/data/web/inc/ajax/show_rspamd_global_filters.php new file mode 100644 index 0000000000..7c7affe8cc --- /dev/null +++ b/data/web/inc/ajax/show_rspamd_global_filters.php @@ -0,0 +1,3 @@ + 'danger', 'msg' => 'Script cannot be empty')); + if (empty($_REQUEST['script'])) { + echo json_encode(array('type' => 'danger', 'msg' => $lang['danger']['script_empty'])); exit(); } - $sieve->parse($_GET['script']); + $sieve->parse($_REQUEST['script']); } catch (Exception $e) { echo json_encode(array('type' => 'danger', 'msg' => $e->getMessage())); diff --git a/data/web/inc/ajax/syncjob_logs.php b/data/web/inc/ajax/syncjob_logs.php index a0568167de..9c521aec79 100644 --- a/data/web/inc/ajax/syncjob_logs.php +++ b/data/web/inc/ajax/syncjob_logs.php @@ -1,5 +1,4 @@ '; + } + else { + echo 'No MX records for ' . $hostname . ' were found in DNS, skipping and using hostname as next-hop.
    '; + } + } + // Use port 25 if no port was given + $port = (empty($port)) ? 25 : $port; + $username = $transport_details['username']; + $password = $transport_details['password']; + + $mail = new PHPMailer; + $mail->Timeout = 15; + $mail->SMTPOptions = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ); + $mail->SMTPDebug = 3; + // smtp: and smtp_enforced_tls: do not support wrapped tls, todo? + // change postfix map to detect wrapped tls or add a checkbox to toggle wrapped tls + // if ($port == 465) { + // $mail->SMTPSecure = "ssl"; + // } + $mail->Debugoutput = function($str, $level) { + foreach(preg_split("/((\r?\n)|(\r\n?)|\n)/", $str) as $line){ + if (empty($line)) { continue; } + if (preg_match("/SERVER \-\> CLIENT: 2\d\d.+/i", $line)) { + echo '' . htmlspecialchars($line) . '
    '; + } + elseif (preg_match("/SERVER \-\> CLIENT: 3\d\d.+/i", $line)) { + echo '' . htmlspecialchars($line) . '
    '; + } + elseif (preg_match("/SERVER \-\> CLIENT: 4\d\d.+/i", $line)) { + echo '' . htmlspecialchars($line) . '
    '; + } + elseif (preg_match("/SERVER \-\> CLIENT: 5\d\d.+/i", $line)) { + echo '' . htmlspecialchars($line) . '
    '; + } + elseif (preg_match("/CLIENT \-\> SERVER:.+/i", $line)) { + echo '' . htmlspecialchars($line) . '
    '; + } + elseif (preg_match("/^(?!SERVER|CLIENT|Connection:|\)).+$/i", $line)) { + echo '    โ†ช ' . htmlspecialchars($line) . '
    '; + } + else { + echo htmlspecialchars($line) . '
    '; + } + } + }; + $mail->isSMTP(); + $mail->Host = $hostname; + if (!empty($username)) { + $mail->SMTPAuth = true; + $mail->Username = $username; + $mail->Password = $password; + } + $mail->Port = $port; + $mail->setFrom($mail_from, 'Mailer'); + $mail->Subject = 'A subject for a SMTP test'; + $mail->addAddress($mail_rcpt, 'Joe Null'); + $mail->Body = 'This is our test body'; + $mail->send(); + } + else { + echo "Unknown transport."; + } +} +else { + echo "Permission denied."; +} diff --git a/data/web/inc/ajax/update_check.php b/data/web/inc/ajax/update_check.php new file mode 100644 index 0000000000..6bf0160019 --- /dev/null +++ b/data/web/inc/ajax/update_check.php @@ -0,0 +1,67 @@ + 'error', 'message' => 'access denied')); + exit; +} + +$owner = $GLOBALS['MAILCOW_GIT_OWNER']; +$repo = $GLOBALS['MAILCOW_GIT_REPO']; +$current = $GLOBALS['MAILCOW_GIT_VERSION']; + +// Cache key is bound to the running version, so an update invalidates it naturally +$cache_key = 'MAILCOW_UPDATE_CHECK/' . $current; + +// Serve cached result if present (one GitHub call per TTL, not one per page load) +$cached = $redis->get($cache_key); +if ($cached !== false) { + echo $cached; + exit; +} + +// GitHub requires a User-Agent and rate-limits unauthenticated requests to 60/h per IP +function github_get($url) { + $ch = curl_init($url); + curl_setopt_array($ch, array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_USERAGENT => 'mailcow-update-check', + CURLOPT_TIMEOUT => 10, + CURLOPT_HTTPHEADER => array('Accept: application/vnd.github+json'), + )); + $body = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($body === false || $code !== 200) return false; + $json = json_decode($body, true); + return is_array($json) ? $json : false; +} + +$latest = github_get("https://api.github.com/repos/{$owner}/{$repo}/releases/latest"); +$release = github_get("https://api.github.com/repos/{$owner}/{$repo}/releases/tags/{$current}"); + +// Any failure (rate limit, offline, unexpected payload) -> honest error, cached briefly +if ($latest === false || $release === false || + empty($latest['tag_name']) || empty($latest['created_at']) || empty($release['created_at'])) { + $result = json_encode(array('status' => 'error', 'message' => 'could not reach GitHub API')); + $redis->setex($cache_key, 300, $result); + echo $result; + exit; +} + +$date_latest = strtotime($latest['created_at']); +$date_current = strtotime($release['created_at']); + +if ($date_latest <= $date_current) { + $result = json_encode(array('status' => 'no_update')); +} else { + $result = json_encode(array('status' => 'update_available', 'latest_tag' => $latest['tag_name'])); +} + +// Cache the positive result for an hour +$redis->setex($cache_key, 3600, $result); +echo $result; diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index 9e8daf7b81..4ced152ae7 100644 --- a/data/web/inc/footer.inc.php +++ b/data/web/inc/footer.inc.php @@ -1,160 +1,100 @@ -
    - - - - - - - - - - - - - - +foreach ($globalVariables as $globalVariableName => $globalVariableValue) { + $twig->addGlobal($globalVariableName, $globalVariableValue); +} - - -render($template, $template_data); +} + +if (isset($_SESSION['mailcow_cc_api'])) { + session_regenerate_id(true); + session_unset(); + session_destroy(); + session_write_close(); + header("Location: /"); +} $stmt = null; $pdo = null; diff --git a/data/web/inc/functions.acl.inc.php b/data/web/inc/functions.acl.inc.php index b2fbd76638..dde9b1236e 100644 --- a/data/web/inc/functions.acl.inc.php +++ b/data/web/inc/functions.acl.inc.php @@ -1,5 +1,5 @@ 'danger', 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), @@ -34,7 +34,7 @@ function acl($_action, $_scope = null, $_data = null) { } // Read all available acl options by calling acl(get) // Set all available acl options we cannot find in the post data to 0, else 1 - $is_now = acl('get', 'user', $username); + $is_now = acl('get', 'user', $username, $_extra); if (!empty($is_now)) { foreach ($is_now as $acl_now_name => $acl_now_val) { $set_acls[$acl_now_name] = (isset($acl_post[$acl_now_name])) ? 1 : 0; @@ -136,6 +136,16 @@ function acl($_action, $_scope = null, $_data = null) { $stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username"); $stmt->execute(array(':username' => $_data)); $data = $stmt->fetch(PDO::FETCH_ASSOC); + if ($_SESSION['mailcow_cc_role'] == 'domainadmin') { + // Domain admins cannot see, add or remove user ACLs they don't have access to by themselves + // Editing a user will use acl("get", "user") to determine granted ACLs and therefore block unallowed access escalation via form editing + $self_da_acl = acl('get', 'domainadmin', $_SESSION['mailcow_cc_username']); + foreach ($self_da_acl as $self_da_acl_key => $self_da_acl_val) { + if ($self_da_acl_val == 0) { + unset($data[$self_da_acl_key]); + } + } + } if (!empty($data)) { unset($data['username']); return $data; diff --git a/data/web/inc/functions.address_rewriting.inc.php b/data/web/inc/functions.address_rewriting.inc.php index bbf36c427e..0c89d001ee 100644 --- a/data/web/inc/functions.address_rewriting.inc.php +++ b/data/web/inc/functions.address_rewriting.inc.php @@ -1,5 +1,5 @@ 'danger', 'log' => array(__FUNCTION__, $_action, $_data, $_attr), @@ -56,10 +59,16 @@ function bcc($_action, $_data = null, $attr = null) { ); return false; } - $domain = mailbox('get', 'mailbox_details', $local_dest)['domain']; - if (empty($domain)) { - return false; + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $local_dest) && + !hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $local_dest)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'access_denied' + ); + return false; } + $domain = idn_to_ascii(substr(strstr($local_dest, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); $local_dest_sane = $local_dest; } else { @@ -69,7 +78,7 @@ function bcc($_action, $_data = null, $attr = null) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'bcc_must_be_email' + 'msg' => array('bcc_must_be_email', htmlspecialchars($bcc_dest)) ); return false; } @@ -115,7 +124,7 @@ function bcc($_action, $_data = null, $attr = null) { foreach ($ids as $id) { $is_now = bcc('details', $id); if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; $bcc_dest = (!empty($_data['bcc_dest'])) ? $_data['bcc_dest'] : $is_now['bcc_dest']; $local_dest = $is_now['local_dest']; $type = (!empty($_data['type'])) ? $_data['type'] : $is_now['type']; @@ -128,7 +137,6 @@ function bcc($_action, $_data = null, $attr = null) { ); continue; } - $active = intval($_data['active']); if (!filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -180,8 +188,7 @@ function bcc($_action, $_data = null, $attr = null) { $stmt = $pdo->prepare("SELECT `id`, `local_dest`, `bcc_dest`, - `active` AS `active_int`, - CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, + `active`, `type`, `created`, `domain`, @@ -263,9 +270,12 @@ function recipient_map($_action, $_data = null, $attr = null) { $old_dest = substr($old_dest, 1); } $new_dest = strtolower(trim($_data['recipient_map_new'])); + if (substr($new_dest, 0, 1) == '@') { + $new_dest = substr($new_dest, 1); + } $active = intval($_data['active']); if (is_valid_domain_name($old_dest)) { - $old_dest_sane = '@' . idn_to_ascii($old_dest); + $old_dest_sane = '@' . idn_to_ascii($old_dest, 0, INTL_IDNA_VARIANT_UTS46); } elseif (filter_var($old_dest, FILTER_VALIDATE_EMAIL)) { $old_dest_sane = $old_dest; @@ -278,7 +288,13 @@ function recipient_map($_action, $_data = null, $attr = null) { ); return false; } - if (!filter_var($new_dest, FILTER_VALIDATE_EMAIL)) { + if (is_valid_domain_name($new_dest)) { + $new_dest_sane = '@' . idn_to_ascii($new_dest, 0, INTL_IDNA_VARIANT_UTS46); + } + elseif (filter_var($new_dest, FILTER_VALIDATE_EMAIL)) { + $new_dest_sane = $new_dest; + } + else { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data, $_attr), @@ -301,7 +317,7 @@ function recipient_map($_action, $_data = null, $attr = null) { (:old_dest, :new_dest, :active)"); $stmt->execute(array( ':old_dest' => $old_dest_sane, - ':new_dest' => $new_dest, + ':new_dest' => $new_dest_sane, ':active' => $active )); $_SESSION['return'][] = array( @@ -315,9 +331,12 @@ function recipient_map($_action, $_data = null, $attr = null) { foreach ($ids as $id) { $is_now = recipient_map('details', $id); if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; $new_dest = (!empty($_data['recipient_map_new'])) ? $_data['recipient_map_new'] : $is_now['recipient_map_new']; $old_dest = (!empty($_data['recipient_map_old'])) ? $_data['recipient_map_old'] : $is_now['recipient_map_old']; + if (substr($new_dest, 0, 1) == '@') { + $new_dest = substr($new_dest, 1); + } if (substr($old_dest, 0, 1) == '@') { $old_dest = substr($old_dest, 1); } @@ -331,7 +350,7 @@ function recipient_map($_action, $_data = null, $attr = null) { continue; } if (is_valid_domain_name($old_dest)) { - $old_dest_sane = '@' . idn_to_ascii($old_dest); + $old_dest_sane = '@' . idn_to_ascii($old_dest, 0, INTL_IDNA_VARIANT_UTS46); } elseif (filter_var($old_dest, FILTER_VALIDATE_EMAIL)) { $old_dest_sane = $old_dest; @@ -344,7 +363,13 @@ function recipient_map($_action, $_data = null, $attr = null) { ); continue; } - if (!filter_var($new_dest, FILTER_VALIDATE_EMAIL)) { + if (is_valid_domain_name($new_dest)) { + $new_dest_sane = '@' . idn_to_ascii($new_dest, 0, INTL_IDNA_VARIANT_UTS46); + } + elseif (filter_var($new_dest, FILTER_VALIDATE_EMAIL)) { + $new_dest_sane = $new_dest; + } + else { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data, $_attr), @@ -371,7 +396,7 @@ function recipient_map($_action, $_data = null, $attr = null) { WHERE `id`= :id"); $stmt->execute(array( ':old_dest' => $old_dest_sane, - ':new_dest' => $new_dest, + ':new_dest' => $new_dest_sane, ':active' => $active, ':id' => $id )); @@ -389,8 +414,7 @@ function recipient_map($_action, $_data = null, $attr = null) { $stmt = $pdo->prepare("SELECT `id`, `old_dest` AS `recipient_map_old`, `new_dest` AS `recipient_map_new`, - `active` AS `active_int`, - CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, + `active`, `created`, `modified` FROM `recipient_maps` WHERE `id` = :id"); diff --git a/data/web/inc/functions.admin.inc.php b/data/web/inc/functions.admin.inc.php index 79c4b9cc35..7bd0af42f2 100644 --- a/data/web/inc/functions.admin.inc.php +++ b/data/web/inc/functions.admin.inc.php @@ -15,15 +15,15 @@ function admin($_action, $_data = null) { !isset($_data_log['password2']) ?: $_data_log['password2'] = '*'; switch ($_action) { case 'add': - $username = strtolower(trim($_data['username'])); - $password = $_data['password']; + $username = strtolower(trim($_data['username'])); + $password = $_data['password']; $password2 = $_data['password2']; $active = intval($_data['active']); - if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username)) { + if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username) || $username == 'API') { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'username_invalid' + 'msg' => array('username_invalid', $username) ); return false; } @@ -48,40 +48,17 @@ function admin($_action, $_data = null) { return false; } } - if (!empty($password) && !empty($password2)) { - if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'password_complexity' - ); - return false; - } - if ($password != $password2) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'password_mismatch' - ); - return false; - } - $password_hashed = hash_password($password); - $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`) - VALUES (:username, :password_hashed, '1', :active)"); - $stmt->execute(array( - ':username' => $username, - ':password_hashed' => $password_hashed, - ':active' => $active - )); - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'password_empty' - ); + if (password_check($password, $password2) !== true) { return false; } + $password_hashed = hash_password($password); + $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`) + VALUES (:username, :password_hashed, '1', :active)"); + $stmt->execute(array( + ':username' => $username, + ':password_hashed' => $password_hashed, + ':active' => $active + )); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_data_log), @@ -99,7 +76,7 @@ function admin($_action, $_data = null) { foreach ($usernames as $username) { $is_now = admin('details', $username); if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; $username_new = (!empty($_data['username_new'])) ? $_data['username_new'] : $is_now['username']; } else { @@ -115,7 +92,7 @@ function admin($_action, $_data = null) { if ($active == 0) { $left_active = 0; foreach (admin('get') as $admin) { - $left_active = $left_active + admin('details', $admin)['active_int']; + $left_active = $left_active + admin('details', $admin)['active']; } if ($left_active == 1) { $_SESSION['return'][] = array( @@ -144,47 +121,56 @@ function admin($_action, $_data = null) { continue; } } - if (!empty($password) && !empty($password2)) { - if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'password_complexity' - ); - continue; + // Check if this is a self password change via forced update + if ($username == $_SESSION['mailcow_cc_username'] && !empty($_SESSION['pending_pw_update'])) { + // Forced password update: only change password and clear force_pw_update flag + if (!empty($password)) { + if (password_check($password, $_data['password2']) !== true) { + return false; + } + $password_hashed = hash_password($password); + $stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed, + `attributes` = JSON_SET(COALESCE(`attributes`, '{}'), '$.force_pw_update', '0') + WHERE `username` = :username"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username' => $username + )); + unset($_SESSION['pending_pw_update']); } - if ($password != $password2) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'password_mismatch' - ); - continue; - } - $password_hashed = hash_password($password); - $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username"); - $stmt->execute(array( - ':password_hashed' => $password_hashed, - ':username_new' => $username_new, - ':username' => $username, - ':active' => $active - )); - if (isset($_data['disable_tfa'])) { - $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); + } else { + // Normal admin edit: update all attributes + $force_tfa = intval($_data['force_tfa'] ?? 0) ? 1 : 0; + $force_pw_update = intval($_data['force_pw_update'] ?? 0) ? 1 : 0; + if (!empty($password)) { + if (password_check($password, $password2) !== true) { + return false; + } + $password_hashed = hash_password($password); + $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed, + `attributes` = JSON_SET(COALESCE(`attributes`, '{}'), '$.force_tfa', :force_tfa, '$.force_pw_update', :force_pw_update) + WHERE `username` = :username"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username_new' => $username_new, + ':username' => $username, + ':active' => $active, + ':force_tfa' => strval($force_tfa), + ':force_pw_update' => strval($force_pw_update) + )); } else { - $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); - $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); + $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, + `attributes` = JSON_SET(COALESCE(`attributes`, '{}'), '$.force_tfa', :force_tfa, '$.force_pw_update', :force_pw_update) + WHERE `username` = :username"); + $stmt->execute(array( + ':username_new' => $username_new, + ':username' => $username, + ':active' => $active, + ':force_tfa' => strval($force_tfa), + ':force_pw_update' => strval($force_pw_update) + )); } - } - else { - $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username"); - $stmt->execute(array( - ':username_new' => $username_new, - ':username' => $username, - ':active' => $active - )); if (isset($_data['disable_tfa'])) { $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); $stmt->execute(array(':username' => $username)); @@ -205,7 +191,7 @@ function admin($_action, $_data = null) { case 'delete': $usernames = (array)$_data['username']; foreach ($usernames as $username) { - if ($_SESSION['mailcow_cc_role'] == $username) { + if ($_SESSION['mailcow_cc_username'] == $username) { $_SESSION['return'][] = array( 'type' => 'warning', 'log' => array(__FUNCTION__, $_action, $_data_log), @@ -229,6 +215,14 @@ function admin($_action, $_data = null) { $stmt->execute(array( ':username' => $username, )); + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_data_log), @@ -248,12 +242,11 @@ function admin($_action, $_data = null) { case 'details': $admindata = array(); $stmt = $pdo->prepare("SELECT - `tfa`.`active` AS `tfa_active_int`, - CASE `tfa`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `tfa_active`, + `tfa`.`active` AS `tfa_active`, `admin`.`username`, `admin`.`created`, - `admin`.`active` AS `active_int`, - CASE `admin`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` + `admin`.`active` AS `active`, + `admin`.`attributes` AS `attributes` FROM `admin` LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`admin`.`username` WHERE `admin`.`username`= :admin AND `superadmin` = '1'"); @@ -261,15 +254,16 @@ function admin($_action, $_data = null) { ':admin' => $_data )); $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (empty($row)) { + if (empty($row)) { return false; } $admindata['username'] = $row['username']; - $admindata['tfa_active'] = $row['tfa_active']; + $admindata['tfa_active'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active']; + $admindata['tfa_active_int'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active']; $admindata['active'] = $row['active']; - $admindata['tfa_active_int'] = $row['tfa_active_int']; - $admindata['active_int'] = $row['active_int']; + $admindata['active_int'] = $row['active']; $admindata['created'] = $row['created']; + $admindata['attributes'] = json_decode($row['attributes'], true) ?? array('force_tfa' => '0', 'force_pw_update' => '0'); return $admindata; break; } diff --git a/data/web/inc/functions.app_passwd.inc.php b/data/web/inc/functions.app_passwd.inc.php new file mode 100644 index 0000000000..aacbb82dfe --- /dev/null +++ b/data/web/inc/functions.app_passwd.inc.php @@ -0,0 +1,214 @@ + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + else { + $username = $_data['username']; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + switch ($_action) { + case 'add': + $app_name = htmlspecialchars(trim($_data['app_name'])); + $password = $_data['app_passwd']; + $password2 = $_data['app_passwd2']; + $active = intval($_data['active']); + $protocols = (array)$_data['protocols']; + $imap_access = (in_array('imap_access', $protocols)) ? 1 : 0; + $dav_access = (in_array('dav_access', $protocols)) ? 1 : 0; + $smtp_access = (in_array('smtp_access', $protocols)) ? 1 : 0; + $eas_access = (in_array('eas_access', $protocols)) ? 1 : 0; + $pop3_access = (in_array('pop3_access', $protocols)) ? 1 : 0; + $sieve_access = (in_array('sieve_access', $protocols)) ? 1 : 0; + $domain = mailbox('get', 'mailbox_details', $username)['domain']; + if (empty($domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + if (password_check($password, $password2) !== true) { + return false; + } + $password_hashed = hash_password($password); + if (empty($app_name)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'app_name_empty' + ); + return false; + } + $stmt = $pdo->prepare("INSERT INTO `app_passwd` (`name`, `mailbox`, `domain`, `password`, `imap_access`, `smtp_access`, `eas_access`, `dav_access`, `pop3_access`, `sieve_access`, `active`) + VALUES (:app_name, :mailbox, :domain, :password, :imap_access, :smtp_access, :eas_access, :dav_access, :pop3_access, :sieve_access, :active)"); + $stmt->execute(array( + ':app_name' => $app_name, + ':mailbox' => $username, + ':domain' => $domain, + ':password' => $password_hashed, + ':imap_access' => $imap_access, + ':smtp_access' => $smtp_access, + ':eas_access' => $eas_access, + ':dav_access' => $dav_access, + ':pop3_access' => $pop3_access, + ':sieve_access' => $sieve_access, + ':active' => $active + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'app_passwd_added' + ); + break; + case 'edit': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = app_passwd('details', $id); + if (!empty($is_now)) { + $app_name = (!empty($_data['app_name'])) ? $_data['app_name'] : $is_now['name']; + $password = (!empty($_data['app_passwd'])) ? $_data['app_passwd'] : null; + $password2 = (!empty($_data['app_passwd2'])) ? $_data['app_passwd2'] : null; + if (isset($_data['protocols'])) { + $protocols = (array)$_data['protocols']; + $imap_access = (in_array('imap_access', $protocols)) ? 1 : 0; + $dav_access = (in_array('dav_access', $protocols)) ? 1 : 0; + $smtp_access = (in_array('smtp_access', $protocols)) ? 1 : 0; + $eas_access = (in_array('eas_access', $protocols)) ? 1 : 0; + $pop3_access = (in_array('pop3_access', $protocols)) ? 1 : 0; + $sieve_access = (in_array('sieve_access', $protocols)) ? 1 : 0; + } + else { + $imap_access = $is_now['imap_access']; + $smtp_access = $is_now['smtp_access']; + $dav_access = $is_now['dav_access']; + $eas_access = $is_now['eas_access']; + $pop3_access = $is_now['pop3_access']; + $sieve_access = $is_now['sieve_access']; + } + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('app_passwd_id_invalid', $id) + ); + continue; + } + $app_name = htmlspecialchars(trim($app_name)); + if (!empty($password) && !empty($password2)) { + if (password_check($password, $password2) !== true) { + continue; + } + $password_hashed = hash_password($password); + $stmt = $pdo->prepare("UPDATE `app_passwd` SET + `password` = :password_hashed + WHERE `mailbox` = :username AND `id` = :id"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username' => $username, + ':id' => $id + )); + } + + $stmt = $pdo->prepare("UPDATE `app_passwd` SET + `name` = :app_name, + `mailbox` = :username, + `imap_access` = :imap_access, + `smtp_access` = :smtp_access, + `eas_access` = :eas_access, + `dav_access` = :dav_access, + `pop3_access` = :pop3_access, + `sieve_access` = :sieve_access, + `active` = :active + WHERE `id` = :id"); + $stmt->execute(array( + ':app_name' => $app_name, + ':username' => $username, + ':imap_access' => $imap_access, + ':smtp_access' => $smtp_access, + ':eas_access' => $eas_access, + ':dav_access' => $dav_access, + ':pop3_access' => $pop3_access, + ':sieve_access' => $sieve_access, + ':active' => $active, + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars(implode(', ', $ids))) + ); + } + break; + case 'delete': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $stmt = $pdo->prepare("SELECT `mailbox` FROM `app_passwd` WHERE `id` = :id"); + $stmt->execute(array(':id' => $id)); + $mailbox = $stmt->fetch(PDO::FETCH_ASSOC)['mailbox']; + if (empty($mailbox)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'app_passwd_id_invalid' + ); + return false; + } + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $mailbox)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `app_passwd` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('app_passwd_removed', htmlspecialchars($id)) + ); + } + break; + case 'get': + $app_passwds = array(); + $stmt = $pdo->prepare("SELECT `id`, `name` FROM `app_passwd` WHERE `mailbox` = :username"); + $stmt->execute(array(':username' => $username)); + $app_passwds = $stmt->fetchAll(PDO::FETCH_ASSOC); + return $app_passwds; + break; + case 'details': + $app_passwd_data = array(); + $stmt = $pdo->prepare("SELECT * FROM `app_passwd` WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $app_passwd_data = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($app_passwd_data)) { + return false; + } + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $app_passwd_data['mailbox'])) { + $app_passwd_data = array(); + return false; + } + $app_passwd_data['name'] = htmlspecialchars(trim($app_passwd_data['name'])); + return $app_passwd_data; + break; + } +} diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php new file mode 100644 index 0000000000..e5a303f9b7 --- /dev/null +++ b/data/web/inc/functions.auth.inc.php @@ -0,0 +1,753 @@ +publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); + error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); + } + elseif (!isset($_SESSION['mailcow_cc_username'])) { + $_SESSION['ldelay'] = $_SESSION['ldelay']+0.5; + $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); + error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); + } + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => 'login_failed' + ); + + sleep($_SESSION['ldelay']); + return false; +} + +function admin_login($user, $pass){ + global $pdo; + + if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) { + if (!$is_internal){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => 'malformed_username' + ); + } + return false; + } + + $user = strtolower(trim($user)); + $stmt = $pdo->prepare("SELECT `password`, `attributes` FROM `admin` + WHERE `superadmin` = '1' + AND `active` = '1' + AND `username` = :user"); + $stmt->execute(array(':user' => $user)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + // verify password + if (verify_hash($row['password'], $pass)) { + $admin_attrs = json_decode($row['attributes'], true) ?? []; + + // Check force_pw_update + if (intval($admin_attrs['force_pw_update'] ?? 0) == 1) { + $_SESSION['pending_pw_update'] = true; + } + + // check for tfa authenticators + $authenticators = get_tfa($user); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { + // active tfa authenticators found, set pending user login + $_SESSION['pending_mailcow_cc_username'] = $user; + $_SESSION['pending_mailcow_cc_role'] = "admin"; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; + unset($_SESSION['ldelay']); + $_SESSION['return'][] = array( + 'type' => 'info', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => 'awaiting_tfa_confirmation' + ); + return "pending"; + } else { + unset($_SESSION['ldelay']); + // Reactivate TFA if it was set to "deactivate TFA for next login" + $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); + $stmt->execute(array(':user' => $user)); + // Check force_tfa: only force setup if NO TFA exists at all + if (intval($admin_attrs['force_tfa'] ?? 0) == 1 && !tfa_exists($user)) { + $_SESSION['pending_tfa_setup'] = true; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => array('logged_in_as', $user) + ); + return "admin"; + } + } + + return false; +} +function domainadmin_login($user, $pass){ + global $pdo; + + if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) { + if (!$is_internal){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => 'malformed_username' + ); + } + return false; + } + + $stmt = $pdo->prepare("SELECT `password`, `attributes` FROM `admin` + WHERE `superadmin` = '0' + AND `active`='1' + AND `username` = :user"); + $stmt->execute(array(':user' => $user)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + // verify password + if (verify_hash($row['password'], $pass) !== false) { + $admin_attrs = json_decode($row['attributes'], true) ?? []; + + // Check force_pw_update + if (intval($admin_attrs['force_pw_update'] ?? 0) == 1) { + $_SESSION['pending_pw_update'] = true; + } + + // check for tfa authenticators + $authenticators = get_tfa($user); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { + $_SESSION['pending_mailcow_cc_username'] = $user; + $_SESSION['pending_mailcow_cc_role'] = "domainadmin"; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; + unset($_SESSION['ldelay']); + $_SESSION['return'][] = array( + 'type' => 'info', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => 'awaiting_tfa_confirmation' + ); + return "pending"; + } + else { + unset($_SESSION['ldelay']); + // Reactivate TFA if it was set to "deactivate TFA for next login" + $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); + $stmt->execute(array(':user' => $user)); + // Check force_tfa: only force setup if NO TFA exists at all + if (intval($admin_attrs['force_tfa'] ?? 0) == 1 && !tfa_exists($user)) { + $_SESSION['pending_tfa_setup'] = true; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => array('logged_in_as', $user) + ); + return "domainadmin"; + } + } + + return false; +} +function user_login($user, $pass, $extra = null){ + global $pdo; + global $iam_provider; + global $iam_settings; + + $is_internal = $extra['is_internal']; + $extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service']; + + if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) { + if (!$is_internal){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => 'malformed_username' + ); + } + return false; + } + + $stmt = $pdo->prepare("SELECT + mailbox.*, + domain.active AS d_active + FROM `mailbox` + INNER JOIN domain on mailbox.domain = domain.domain + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `username` = :user"); + $stmt->execute(array(':user' => $user)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + // user does not exist, try call idp login and create user if possible via rest flow + if (!$row){ + $result = false; + if ($iam_settings['authsource'] == 'keycloak' && intval($iam_settings['mailpassword_flow']) == 1){ + $result = keycloak_mbox_login_rest($user, $pass, array('is_internal' => $is_internal, 'create' => true)); + } else if ($iam_settings['authsource'] == 'ldap') { + $result = ldap_mbox_login($user, $pass, array('is_internal' => $is_internal, 'create' => true)); + } + if ($result !== false){ + // double check if mailbox is active + $stmt = $pdo->prepare("SELECT * FROM `mailbox` + INNER JOIN domain on mailbox.domain = domain.domain + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `mailbox`.`active`='1' + AND `domain`.`active`='1' + AND `username` = :user"); + $stmt->execute(array(':user' => $user)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!empty($row)) { + // check if user has access to service (imap, smtp, pop3, sieve, dav, eas) if service is set + $row['attributes'] = json_decode($row['attributes'], true); + if ($extra['service'] != 'NONE') { + $key = strtolower($extra['service']) . "_access"; + if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') { + return false; + } + } + return true; + } + } + clear_session(); + return false; + } + + // check if user has access to service (imap, smtp, pop3, sieve) if service is set + $row['attributes'] = json_decode($row['attributes'], true); + if ($extra['service'] != 'NONE') { + $key = strtolower($extra['service']) . "_access"; + if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') { + return false; + } + } + switch ($row['authsource']) { + case 'keycloak': + // user authsource is keycloak, try using via rest flow + if (intval($iam_settings['mailpassword_flow']) == 1){ + $result = keycloak_mbox_login_rest($user, $pass, array('is_internal' => $is_internal)); + if ($result !== false) { + // double check if mailbox and domain is active + $stmt = $pdo->prepare("SELECT * FROM `mailbox` + INNER JOIN domain on mailbox.domain = domain.domain + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `mailbox`.`active`='1' + AND `domain`.`active`='1' + AND `username` = :user"); + $stmt->execute(array(':user' => $user)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row)) { + return false; + } + + $row['attributes'] = json_decode($row['attributes'], true); + + // check for tfa authenticators + $authenticators = get_tfa($user); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) { + // authenticators found, init TFA flow + $_SESSION['pending_mailcow_cc_username'] = $user; + $_SESSION['pending_mailcow_cc_role'] = "user"; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; + unset($_SESSION['ldelay']); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $user, '*', 'Provider: Keycloak'), + 'msg' => array('logged_in_as', $user) + ); + return "pending"; + } else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) { + // no authenticators found, login successfull + if (!$is_internal){ + unset($_SESSION['ldelay']); + // Reactivate TFA if it was set to "deactivate TFA for next login" + $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); + $stmt->execute(array(':user' => $user)); + // Check force_tfa: only force setup if NO TFA exists at all + if (intval($row['attributes']['force_tfa']) == 1 && !tfa_exists($user)) { + $_SESSION['pending_tfa_setup'] = true; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $user, '*', 'Provider: Keycloak'), + 'msg' => array('logged_in_as', $user) + ); + } + return "user"; + } + } + return $result; + } else { + return false; + } + break; + case 'ldap': + // user authsource is ldap + $result = ldap_mbox_login($user, $pass, array('is_internal' => $is_internal)); + if ($result !== false) { + // double check if mailbox and domain is active + $stmt = $pdo->prepare("SELECT * FROM `mailbox` + INNER JOIN domain on mailbox.domain = domain.domain + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `mailbox`.`active`='1' + AND `domain`.`active`='1' + AND `username` = :user"); + $stmt->execute(array(':user' => $user)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row)) { + return false; + } + + $row['attributes'] = json_decode($row['attributes'], true); + + // check for tfa authenticators + $authenticators = get_tfa($user); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) { + // authenticators found, init TFA flow + $_SESSION['pending_mailcow_cc_username'] = $user; + $_SESSION['pending_mailcow_cc_role'] = "user"; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; + unset($_SESSION['ldelay']); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $user, '*', 'Provider: LDAP'), + 'msg' => array('logged_in_as', $user) + ); + return "pending"; + } else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) { + // no authenticators found, login successfull + if (!$is_internal){ + unset($_SESSION['ldelay']); + // Reactivate TFA if it was set to "deactivate TFA for next login" + $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); + $stmt->execute(array(':user' => $user)); + // Check force_tfa: only force setup if NO TFA exists at all + if (intval($row['attributes']['force_tfa']) == 1 && !tfa_exists($user)) { + $_SESSION['pending_tfa_setup'] = true; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $user, '*', 'Provider: LDAP'), + 'msg' => array('logged_in_as', $user) + ); + } + return "user"; + } + } + return $result; + break; + case 'mailcow': + if ($row['active'] != 1 || $row['d_active'] != 1) { + return false; + } + // verify password + if (verify_hash($row['password'], $pass) !== false) { + + if (intval($row['attributes']['force_pw_update']) == 1) { + $_SESSION['pending_pw_update'] = true; + } + + // check for tfa authenticators + $authenticators = get_tfa($user); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) { + // authenticators found, init TFA flow + $_SESSION['pending_mailcow_cc_username'] = $user; + $_SESSION['pending_mailcow_cc_role'] = "user"; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; + unset($_SESSION['ldelay']); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $user, '*', 'Provider: mailcow'), + 'msg' => array('logged_in_as', $user) + ); + return "pending"; + } else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) { + // no authenticators found, login successfull + if (!$is_internal){ + unset($_SESSION['ldelay']); + // Reactivate TFA if it was set to "deactivate TFA for next login" + $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); + $stmt->execute(array(':user' => $user)); + // Check force_tfa: only force setup if NO TFA exists at all + if (intval($row['attributes']['force_tfa']) == 1 && !tfa_exists($user)) { + $_SESSION['pending_tfa_setup'] = true; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $user, '*', 'Provider: mailcow'), + 'msg' => array('logged_in_as', $user) + ); + } + return "user"; + } + } + break; + } + + return false; +} +function apppass_login($user, $pass, $extra = null){ + global $pdo; + + $is_internal = $extra['is_internal']; + + if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) { + if (!$is_internal){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => 'malformed_username' + ); + } + return false; + } + + $extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service']; + if (!$is_internal && $extra['service'] == 'NONE') { + return false; + } + + // fetch app password data + $stmt = $pdo->prepare("SELECT `app_passwd`.*, `app_passwd`.`password` as `password`, `app_passwd`.`id` as `app_passwd_id` FROM `app_passwd` + INNER JOIN `mailbox` ON `mailbox`.`username` = `app_passwd`.`mailbox` + INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain` + WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group' + AND `mailbox`.`active` = '1' + AND `domain`.`active` = '1' + AND `app_passwd`.`active` = '1' + AND `app_passwd`.`mailbox` = :user" + ); + // fetch password data + $stmt->execute(array( + ':user' => $user, + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + foreach ($rows as $row) { + if ($extra['service'] != 'NONE' && $row[strtolower($extra['service']) . '_access'] != '1'){ + continue; + } + + // verify password + if (verify_hash($row['password'], $pass) !== false) { + $_SESSION['app_passwd_id'] = $row['app_passwd_id']; + unset($_SESSION['ldelay']); + return "user"; + } + } + + return false; +} +// Keycloak REST Api Flow - auth user by mailcow_password attribute +// This password will be used for direct UI, IMAP and SMTP Auth +// To use direct user credentials, only Authorization Code Flow is valid +function keycloak_mbox_login_rest($user, $pass, $extra = null){ + global $pdo; + global $iam_provider; + global $iam_settings; + + $is_internal = $extra['is_internal']; + $create = $extra['create']; + + if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) { + if (!$is_internal){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => 'malformed_username' + ); + } + return false; + } + if (!$iam_provider) { + return false; + } + + // get access_token for service account of mailcow client + $admin_token = identity_provider("get-keycloak-admin-token"); + + // get the mailcow_password attribute from keycloak user + $url = "{$iam_settings['server_url']}/admin/realms/{$iam_settings['realm']}/users"; + $queryParams = array('email' => $user, 'exact' => true); + $queryString = http_build_query($queryParams); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_TIMEOUT, 7); + curl_setopt($curl, CURLOPT_URL, $url . '?' . $queryString); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_HTTPHEADER, array( + 'Authorization: Bearer ' . $admin_token, + 'Content-Type: application/json' + )); + $user_res = json_decode(curl_exec($curl), true)[0]; + $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + if ($code != 200) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*', 'Identity Provider returned HTTP ' . $code), + 'msg' => 'generic_server_error' + ); + return false; + } + if (!isset($user_res['attributes']['mailcow_password']) || !is_array($user_res['attributes']['mailcow_password'])){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*', 'User has no mailcow_password attribute'), + 'msg' => 'generic_server_error' + ); + return false; + } + if (empty($user_res['attributes']['mailcow_password'][0])){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*', "User's mailcow_password attribute is empty"), + 'msg' => 'generic_server_error' + ); + return false; + } + + // validate mailcow_password + $mailcow_password = $user_res['attributes']['mailcow_password'][0]; + if (!verify_hash($mailcow_password, $pass)) { + return false; + } + + // get mapped template + $user_template = $user_res['attributes']['mailcow_template'][0]; + $mapper_key = array_search($user_template, $iam_settings['mappers']); + + if (!$create) { + // login success + if ($mapper_key !== false) { + // update user + $_SESSION['access_all_exception'] = '1'; + mailbox('edit', 'mailbox_from_template', array( + 'username' => $user, + 'name' => $user_res['name'], + 'template' => $iam_settings['templates'][$mapper_key] + )); + $_SESSION['access_all_exception'] = '0'; + } + return 'user'; + } + + // check if login provisioning is enabled before creating user + if (!$iam_settings['login_provisioning']){ + if (!$is_internal){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, "Auto-create users on login is deactivated"), + 'msg' => 'login_failed' + ); + } + return false; + } + // check if matching attribute exist + if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) { + if (!empty($iam_settings['default_template'])) { + $mbox_template = $iam_settings['default_template']; + } else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*', 'No matching attribute mapping was found'), + 'msg' => 'generic_server_error' + ); + return false; + } + } else { + $mbox_template = $iam_settings['templates'][$mapper_key]; + } + + // create mailbox + $_SESSION['access_all_exception'] = '1'; + $create_res = mailbox('add', 'mailbox_from_template', array( + 'domain' => explode('@', $user)[1], + 'local_part' => explode('@', $user)[0], + 'name' => $user_res['name'], + 'authsource' => 'keycloak', + 'template' => $mbox_template + )); + $_SESSION['access_all_exception'] = '0'; + if (!$create_res){ + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*', 'Could not create mailbox on login'), + 'msg' => 'generic_server_error' + ); + return false; + } + + return 'user'; +} +function ldap_mbox_login($user, $pass, $extra = null){ + global $pdo; + global $iam_provider; + global $iam_settings; + + $is_internal = $extra['is_internal']; + $create = $extra['create']; + + if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) { + if (!$is_internal){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => 'malformed_username' + ); + } + return false; + } + if (!$iam_provider) { + return false; + } + + try { + $ldap_query = $iam_provider->query(); + if (!empty($iam_settings['filter'])) { + $ldap_query = $ldap_query->rawFilter($iam_settings['filter']); + } + $ldap_query = $ldap_query->where($iam_settings['username_field'], '=', $user) + ->select([$iam_settings['username_field'], $iam_settings['attribute_field'], 'displayname', 'distinguishedname', 'dn']); + + $user_res = $ldap_query->firstOrFail(); + } catch (Exception $e) { + // clear $_SESSION['return'] to not leak data + $_SESSION['return'] = array(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*', $e->getMessage()), + 'msg' => 'generic_server_error' + ); + return false; + } + try { + if (!$iam_provider->auth()->attempt($user_res['dn'], $pass)) { + return false; + } + } catch (Exception $e) { + // clear $_SESSION['return'] to not leak data + $_SESSION['return'] = array(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*', $e->getMessage()), + 'msg' => 'generic_server_error' + ); + return false; + } + + // get mapped template + $user_template = $user_res[$iam_settings['attribute_field']][0]; + $mapper_key = array_search($user_template, $iam_settings['mappers']); + + if (!$create) { + // login success + if ($mapper_key !== false) { + // update user + $_SESSION['access_all_exception'] = '1'; + mailbox('edit', 'mailbox_from_template', array( + 'username' => $user, + 'name' => $user_res['displayname'][0], + 'template' => $iam_settings['templates'][$mapper_key] + )); + $_SESSION['access_all_exception'] = '0'; + } + return 'user'; + } + + // check if login provisioning is enabled before creating user + if (!$iam_settings['login_provisioning']){ + if (!$is_internal){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, "Auto-create users on login is deactivated"), + 'msg' => 'login_failed' + ); + } + return false; + } + // check if matching attribute exist + if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) { + if (!empty($iam_settings['default_template'])) { + $mbox_template = $iam_settings['default_template']; + } else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*', 'No matching attribute mapping was found'), + 'msg' => 'generic_server_error' + ); + return false; + } + } else { + $mbox_template = $iam_settings['templates'][$mapper_key]; + } + + // create mailbox + $_SESSION['access_all_exception'] = '1'; + $create_res = mailbox('add', 'mailbox_from_template', array( + 'domain' => explode('@', $user)[1], + 'local_part' => explode('@', $user)[0], + 'name' => $user_res['displayname'][0], + 'authsource' => 'ldap', + 'template' => $mbox_template + )); + $_SESSION['access_all_exception'] = '0'; + if (!$create_res){ + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*', 'Could not create mailbox on login'), + 'msg' => 'generic_server_error' + ); + return false; + } + + return 'user'; +} diff --git a/data/web/inc/functions.customize.inc.php b/data/web/inc/functions.customize.inc.php index a737bdbc97..b396eec218 100644 --- a/data/web/inc/functions.customize.inc.php +++ b/data/web/inc/functions.customize.inc.php @@ -2,8 +2,19 @@ function customize($_action, $_item, $_data = null) { global $redis; global $lang; + global $LOGO_LIMITS; + switch ($_action) { case 'add': + // disable functionality when demo mode is enabled + if ($GLOBALS["DEMO_MODE"]) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'demo_mode_enabled' + ); + return false; + } if ($_SESSION['mailcow_cc_role'] != "admin") { $_SESSION['return'][] = array( 'type' => 'danger', @@ -14,9 +25,10 @@ function customize($_action, $_item, $_data = null) { } switch ($_item) { case 'main_logo': - if (in_array($_data['main_logo']['type'], array('image/gif', 'image/jpeg', 'image/pjpeg', 'image/x-png', 'image/png', 'image/svg+xml'))) { + case 'main_logo_dark': + if (in_array($_data[$_item]['type'], array('image/gif', 'image/jpeg', 'image/pjpeg', 'image/x-png', 'image/png', 'image/svg+xml'))) { try { - if (file_exists($_data['main_logo']['tmp_name']) !== true) { + if (file_exists($_data[$_item]['tmp_name']) !== true) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_item, $_data), @@ -24,7 +36,24 @@ function customize($_action, $_item, $_data = null) { ); return false; } - $image = new Imagick($_data['main_logo']['tmp_name']); + if ($_data[$_item]['size'] > $LOGO_LIMITS['max_size']) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'img_size_exceeded' + ); + return false; + } + list($width, $height) = getimagesize($_data[$_item]['tmp_name']); + if ($width > $LOGO_LIMITS['max_width'] || $height > $LOGO_LIMITS['max_height']) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'img_dimensions_exceeded' + ); + return false; + } + $image = new Imagick($_data[$_item]['tmp_name']); if ($image->valid() !== true) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -53,7 +82,7 @@ function customize($_action, $_item, $_data = null) { return false; } try { - $redis->Set('MAIN_LOGO', 'data:' . $_data['main_logo']['type'] . ';base64,' . base64_encode(file_get_contents($_data['main_logo']['tmp_name']))); + $redis->Set(strtoupper($_item), 'data:' . $_data[$_item]['type'] . ';base64,' . base64_encode(file_get_contents($_data[$_item]['tmp_name']))); } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -72,6 +101,15 @@ function customize($_action, $_item, $_data = null) { } break; case 'edit': + // disable functionality when demo mode is enabled + if ($GLOBALS["DEMO_MODE"]) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'demo_mode_enabled' + ); + return false; + } if ($_SESSION['mailcow_cc_role'] != "admin") { $_SESSION['return'][] = array( 'type' => 'danger', @@ -84,10 +122,16 @@ function customize($_action, $_item, $_data = null) { case 'app_links': $apps = (array)$_data['app']; $links = (array)$_data['href']; + $user_links = (array)$_data['user_href']; + $hide = (array)$_data['hide']; $out = array(); - if (count($apps) == count($links)) { + if (count($apps) == count($links) && count($apps) == count($user_links) && count($apps) == count($hide)) { for ($i = 0; $i < count($apps); $i++) { - $out[] = array($apps[$i] => $links[$i]); + $out[] = array($apps[$i] => array( + 'link' => $links[$i], + 'user_link' => $user_links[$i], + 'hide' => ($hide[$i] === '0' || $hide[$i] === 0) ? false : true + )); } try { $redis->set('APP_LINKS', json_encode($out)); @@ -112,11 +156,20 @@ function customize($_action, $_item, $_data = null) { $main_name = $_data['main_name']; $apps_name = $_data['apps_name']; $help_text = $_data['help_text']; + $ui_footer = $_data['ui_footer']; + $ui_announcement_text = $_data['ui_announcement_text']; + $ui_announcement_type = (in_array($_data['ui_announcement_type'], array('info', 'warning', 'danger'))) ? $_data['ui_announcement_type'] : false; + $ui_announcement_active = (!empty($_data['ui_announcement_active']) ? 1 : 0); + try { $redis->set('TITLE_NAME', htmlspecialchars($title_name)); $redis->set('MAIN_NAME', htmlspecialchars($main_name)); $redis->set('APPS_NAME', htmlspecialchars($apps_name)); $redis->set('HELP_TEXT', $help_text); + $redis->set('UI_FOOTER', $ui_footer); + $redis->set('UI_ANNOUNCEMENT_TEXT', $ui_announcement_text); + $redis->set('UI_ANNOUNCEMENT_TYPE', $ui_announcement_type); + $redis->set('UI_ANNOUNCEMENT_ACTIVE', $ui_announcement_active); } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -132,9 +185,66 @@ function customize($_action, $_item, $_data = null) { 'msg' => 'ui_texts' ); break; + case 'ip_check': + $ip_check = ($_data['ip_check_opt_in'] == "1") ? 1 : 0; + try { + $redis->set('IP_CHECK', $ip_check); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'ip_check_opt_in_modified' + ); + break; + case 'custom_login': + $hide_user_quicklink = ($_data['hide_user_quicklink'] == "1") ? 1 : 0; + $hide_domainadmin_quicklink = ($_data['hide_domainadmin_quicklink'] == "1") ? 1 : 0; + $hide_admin_quicklink = ($_data['hide_admin_quicklink'] == "1") ? 1 : 0; + $force_sso = ($_data['force_sso'] == "1") ? 1 : 0; + + $custom_login = array( + "hide_user_quicklink" => $hide_user_quicklink, + "hide_domainadmin_quicklink" => $hide_domainadmin_quicklink, + "hide_admin_quicklink" => $hide_admin_quicklink, + "force_sso" => $force_sso, + ); + try { + $redis->set('CUSTOM_LOGIN', json_encode($custom_login)); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'custom_login_modified' + ); + break; } break; case 'delete': + // disable functionality when demo mode is enabled + if ($GLOBALS["DEMO_MODE"]) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'demo_mode_enabled' + ); + return false; + } if ($_SESSION['mailcow_cc_role'] != "admin") { $_SESSION['return'][] = array( 'type' => 'danger', @@ -145,8 +255,9 @@ function customize($_action, $_item, $_data = null) { } switch ($_item) { case 'main_logo': + case 'main_logo_dark': try { - if ($redis->del('MAIN_LOGO')) { + if ($redis->del(strtoupper($_item))) { $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_item, $_data), @@ -180,11 +291,28 @@ function customize($_action, $_item, $_data = null) { ); return false; } - return ($app_links) ? $app_links : false; + + if (empty($app_links)){ + return []; + } + + // convert from old style + foreach($app_links as $i => $entry){ + foreach($entry as $app => $link){ + if (empty($link['link']) && empty($link['user_link'])){ + $app_links[$i][$app] = array(); + $app_links[$i][$app]['link'] = $link; + $app_links[$i][$app]['user_link'] = $link; + } + } + } + + return $app_links; break; case 'main_logo': + case 'main_logo_dark': try { - return $redis->get('MAIN_LOGO'); + return $redis->get(strtoupper($_item)); } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -197,10 +325,20 @@ function customize($_action, $_item, $_data = null) { break; case 'ui_texts': try { - $data['title_name'] = ($title_name = $redis->get('TITLE_NAME')) ? $title_name : 'mailcow UI'; - $data['main_name'] = ($main_name = $redis->get('MAIN_NAME')) ? $main_name : 'mailcow UI'; - $data['apps_name'] = ($apps_name = $redis->get('APPS_NAME')) ? $apps_name : 'mailcow Apps'; + $mailcow_hostname = strtolower(getenv("MAILCOW_HOSTNAME")); + + $data['title_name'] = ($title_name = $redis->get('TITLE_NAME')) ? $title_name : "$mailcow_hostname - mail UI"; + $data['main_name'] = ($main_name = $redis->get('MAIN_NAME')) ? $main_name : "$mailcow_hostname - mail UI"; + $data['apps_name'] = ($apps_name = $redis->get('APPS_NAME')) ? $apps_name : $lang['header']['apps']; $data['help_text'] = ($help_text = $redis->get('HELP_TEXT')) ? $help_text : false; + if (!empty($redis->get('UI_IMPRESS'))) { + $redis->set('UI_FOOTER', $redis->get('UI_IMPRESS')); + $redis->del('UI_IMPRESS'); + } + $data['ui_footer'] = ($ui_footer = $redis->get('UI_FOOTER')) ? $ui_footer : false; + $data['ui_announcement_text'] = ($ui_announcement_text = $redis->get('UI_ANNOUNCEMENT_TEXT')) ? $ui_announcement_text : false; + $data['ui_announcement_type'] = ($ui_announcement_type = $redis->get('UI_ANNOUNCEMENT_TYPE')) ? $ui_announcement_type : false; + $data['ui_announcement_active'] = ($redis->get('UI_ANNOUNCEMENT_ACTIVE') == 1) ? 1 : 0; return $data; } catch (RedisException $e) { @@ -213,13 +351,19 @@ function customize($_action, $_item, $_data = null) { } break; case 'main_logo_specs': + case 'main_logo_dark_specs': try { $image = new Imagick(); - $img_data = explode('base64,', customize('get', 'main_logo')); + if($_item == 'main_logo_specs') { + $img_data = explode('base64,', customize('get', 'main_logo')); + } else { + $img_data = explode('base64,', customize('get', 'main_logo_dark')); + } if ($img_data[1]) { $image->readImageBlob(base64_decode($img_data[1])); + return $image->identifyImage(); } - return $image->identifyImage(); + return false; } catch (ImagickException $e) { $_SESSION['return'][] = array( @@ -230,7 +374,35 @@ function customize($_action, $_item, $_data = null) { return false; } break; + case 'ip_check': + try { + $ip_check = ($ip_check = $redis->get('IP_CHECK')) ? $ip_check : 0; + return $ip_check; + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + break; + case 'custom_login': + try { + $custom_login = $redis->get('CUSTOM_LOGIN'); + return $custom_login ? json_decode($custom_login, true) : array(); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + break; } break; } -} \ No newline at end of file +} diff --git a/data/web/inc/functions.dkim.inc.php b/data/web/inc/functions.dkim.inc.php index 0338289b56..e7e4117305 100644 --- a/data/web/inc/functions.dkim.inc.php +++ b/data/web/inc/functions.dkim.inc.php @@ -1,19 +1,11 @@ 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'access_denied' - ); - return false; - } - $key_length = intval($_data['key_size']); + $key_length = intval($_data['key_size']); $dkim_selector = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : 'dkim'; $domains = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['domains'])); $domains = array_filter($domains); @@ -34,7 +26,7 @@ function dkim($_action, $_data = null) { ); continue; } - if (!ctype_alnum($dkim_selector)) { + if (!ctype_alnum(str_replace(['-', '_', '.'], '', $dkim_selector))) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data), @@ -42,6 +34,14 @@ function dkim($_action, $_data = null) { ); continue; } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('access_denied', $domain) + ); + continue; + } $config = array( "digest_alg" => "sha256", "private_key_bits" => $key_length, @@ -50,10 +50,10 @@ function dkim($_action, $_data = null) { if ($keypair_ressource = openssl_pkey_new($config)) { $key_details = openssl_pkey_get_details($keypair_ressource); $pubKey = implode(array_slice( - array_filter( - explode(PHP_EOL, $key_details['key']) - ), 1, -1) - ); + array_filter( + explode(PHP_EOL, $key_details['key']) + ), 1, -1) + ); // Save public key and selector to redis try { $redis->hSet('DKIM_PUB_KEYS', $domain, $pubKey); @@ -108,7 +108,7 @@ function dkim($_action, $_data = null) { return false; } $from_domain = $_data['from_domain']; - $from_domain_dkim = dkim('details', $from_domain); + $from_domain_dkim = dkim('details', $from_domain, true); if (empty($from_domain_dkim)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -123,7 +123,7 @@ function dkim($_action, $_data = null) { try { $redis->hSet('DKIM_PUB_KEYS', $to_domain, $from_domain_dkim['pubkey']); $redis->hSet('DKIM_SELECTORS', $to_domain, $from_domain_dkim['dkim_selector']); - $redis->hSet('DKIM_PRIV_KEYS', $from_domain_dkim['dkim_selector'] . '.' . $to_domain, trim($from_domain_dkim['privkey'])); + $redis->hSet('DKIM_PRIV_KEYS', $from_domain_dkim['dkim_selector'] . '.' . $to_domain, base64_decode(trim($from_domain_dkim['privkey']))); } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -150,6 +150,7 @@ function dkim($_action, $_data = null) { return false; } $private_key_input = trim($_data['private_key_file']); + $overwrite_existing = intval($_data['overwrite_existing']); $private_key_normalized = preg_replace('~\r\n?~', "\n", $private_key_input); $private_key = openssl_pkey_get_private($private_key_normalized); if ($ssl_error = openssl_error_string()) { @@ -166,9 +167,9 @@ function dkim($_action, $_data = null) { array_shift($pem_public_key_array); array_pop($pem_public_key_array); // Implode as single string - $pem_public_key = implode('', $pem_public_key_array); + $pem_public_key = implode('', (array)$pem_public_key_array); $dkim_selector = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : 'dkim'; - $domain = $_data['domain']; + $domain = $_data['domain']; if (!is_valid_domain_name($domain)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -178,14 +179,16 @@ function dkim($_action, $_data = null) { return false; } if ($redis->hGet('DKIM_PUB_KEYS', $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('dkim_domain_or_sel_invalid', $domain) - ); - return false; + if ($overwrite_existing == 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_domain_or_sel_exists', $domain) + ); + return false; + } } - if (!ctype_alnum($dkim_selector)) { + if (!ctype_alnum(str_replace(['-', '_', '.'], '', $dkim_selector))) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data), @@ -194,6 +197,7 @@ function dkim($_action, $_data = null) { return false; } try { + dkim('delete', array('domains' => $domain)); $redis->hSet('DKIM_PUB_KEYS', $domain, $pem_public_key); $redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector); $redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, $private_key_normalized); @@ -227,7 +231,7 @@ function dkim($_action, $_data = null) { return true; break; case 'details': - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data) && $_SESSION['mailcow_cc_role'] != "admin") { return false; } $dkimdata = array(); @@ -236,18 +240,32 @@ function dkim($_action, $_data = null) { if (strlen($dkimdata['pubkey']) < 391) { $dkimdata['length'] = "1024"; } - elseif (strlen($dkimdata['pubkey']) < 736) { + elseif (strlen($dkimdata['pubkey']) < 564) { $dkimdata['length'] = "2048"; } + elseif (strlen($dkimdata['pubkey']) < 736) { + $dkimdata['length'] = "3072"; + } elseif (strlen($dkimdata['pubkey']) < 1416) { $dkimdata['length'] = "4096"; } else { $dkimdata['length'] = ">= 8192"; } - $dkimdata['dkim_txt'] = 'v=DKIM1;k=rsa;t=s;s=email;p=' . $redis_dkim_key_data; + if ($GLOBALS['SPLIT_DKIM_255'] === true) { + $dkim_txt_tmp = str_split('v=DKIM1;k=rsa;t=s;s=email;p=' . $redis_dkim_key_data, 255); + $dkimdata['dkim_txt'] = sprintf('"%s"', implode('" "', (array)$dkim_txt_tmp ) ); + } + else { + $dkimdata['dkim_txt'] = 'v=DKIM1;k=rsa;t=s;s=email;p=' . $redis_dkim_key_data; + } $dkimdata['dkim_selector'] = $redis->hGet('DKIM_SELECTORS', $_data); - $dkimdata['privkey'] = base64_encode($redis->hGet('DKIM_PRIV_KEYS', $dkimdata['dkim_selector'] . '.' . $_data)); + if ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] || $privkey == true) { + $dkimdata['privkey'] = base64_encode($redis->hGet('DKIM_PRIV_KEYS', $dkimdata['dkim_selector'] . '.' . $_data)); + } + else { + $dkimdata['privkey'] = ''; + } } return $dkimdata; break; @@ -307,4 +325,4 @@ function dkim($_action, $_data = null) { } break; } -} \ No newline at end of file +} diff --git a/data/web/inc/functions.docker.inc.php b/data/web/inc/functions.docker.inc.php index 9c63eec7fa..daed17c638 100644 --- a/data/web/inc/functions.docker.inc.php +++ b/data/web/inc/functions.docker.inc.php @@ -1,6 +1,7 @@ array( - // 'type' => 'danger', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // 'msg' => $err, - // ))); return $err; } else { curl_close($curl); - // logger(array('return' => array( - // 'type' => 'success', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // ))); $containers = json_decode($response, true); if (!empty($containers)) { foreach ($containers as $container) { - if ($container['Config']['Labels']['com.docker.compose.service'] == $service_name - && $container['Config']['Labels']['com.docker.compose.project'] == getenv('COMPOSE_PROJECT_NAME')) { + if (isset($container['Config']['Labels']['com.docker.compose.service']) + && $container['Config']['Labels']['com.docker.compose.service'] == $service_name + && strtolower($container['Config']['Labels']['com.docker.compose.project']) == strtolower(getenv('COMPOSE_PROJECT_NAME'))) { return trim($container['Id']); } } } } return false; + break; case 'containers': curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/json'); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); @@ -49,25 +43,17 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex if ($response === false) { $err = curl_error($curl); curl_close($curl); - // logger(array('return' => array( - // 'type' => 'danger', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // 'msg' => $err, - // ))); return $err; } else { curl_close($curl); - // logger(array('return' => array( - // 'type' => 'success', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // ))); $containers = json_decode($response, true); if (!empty($containers)) { foreach ($containers as $container) { - if ($container['Config']['Labels']['com.docker.compose.project'] == getenv('COMPOSE_PROJECT_NAME')) { + if (strtolower($container['Config']['Labels']['com.docker.compose.project']) == strtolower(getenv('COMPOSE_PROJECT_NAME'))) { $out[$container['Config']['Labels']['com.docker.compose.service']]['State'] = $container['State']; $out[$container['Config']['Labels']['com.docker.compose.service']]['Config'] = $container['Config']; + $out[$container['Config']['Labels']['com.docker.compose.service']]['Id'] = trim($container['Id']); } } } @@ -77,7 +63,7 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex break; case 'info': if (empty($service_name)) { - curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/json'); + curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/json?all=true'); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POST, 0); curl_setopt($curl, CURLOPT_TIMEOUT, $DOCKER_TIMEOUT); @@ -88,11 +74,6 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/containers/' . $container_id . '/json'); } else { - // logger(array('return' => array( - // 'type' => 'danger', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // 'msg' => 'invalid_container_id' - // ))); return false; } } @@ -103,35 +84,30 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex if ($response === false) { $err = curl_error($curl); curl_close($curl); - // logger(array('return' => array( - // 'type' => 'danger', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // 'msg' => $err, - // ))); return $err; } else { curl_close($curl); - // logger(array('return' => array( - // 'type' => 'success', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // ))); $decoded_response = json_decode($response, true); if (!empty($decoded_response)) { if (empty($service_name)) { foreach ($decoded_response as $container) { - if ($container['Config']['Labels']['com.docker.compose.project'] == getenv('COMPOSE_PROJECT_NAME')) { + if (isset($container['Config']['Labels']['com.docker.compose.project']) + && strtolower($container['Config']['Labels']['com.docker.compose.project']) == strtolower(getenv('COMPOSE_PROJECT_NAME'))) { unset($container['Config']['Env']); $out[$container['Config']['Labels']['com.docker.compose.service']]['State'] = $container['State']; $out[$container['Config']['Labels']['com.docker.compose.service']]['Config'] = $container['Config']; + $out[$container['Config']['Labels']['com.docker.compose.service']]['Id'] = trim($container['Id']); } } } else { - if ($decoded_response['Config']['Labels']['com.docker.compose.project'] == getenv('COMPOSE_PROJECT_NAME')) { + if (isset($decoded_response['Config']['Labels']['com.docker.compose.project']) + && strtolower($decoded_response['Config']['Labels']['com.docker.compose.project']) == strtolower(getenv('COMPOSE_PROJECT_NAME'))) { unset($container['Config']['Env']); $out[$decoded_response['Config']['Labels']['com.docker.compose.service']]['State'] = $decoded_response['State']; $out[$decoded_response['Config']['Labels']['com.docker.compose.service']]['Config'] = $decoded_response['Config']; + $out[$decoded_response['Config']['Labels']['com.docker.compose.service']]['Id'] = trim($decoded_response['Id']); } } } @@ -161,19 +137,10 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex if ($response === false) { $err = curl_error($curl); curl_close($curl); - // logger(array('return' => array(array( - // 'type' => 'danger', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // 'msg' => $err, - // )))); return $err; } else { curl_close($curl); - // logger(array('return' => array(array( - // 'type' => 'success', - // 'log' => array(__FUNCTION__, $action, $service_name, $attr1, $attr2, $extra_headers), - // )))); if (empty($response)) { return true; } @@ -184,5 +151,57 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex } } break; + case 'container_stats': + if (empty($service_name)){ + return false; + } + + $container_id = $service_name; + curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/container/' . $container_id . '/stats/update'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_TIMEOUT, $DOCKER_TIMEOUT); + $response = curl_exec($curl); + if ($response === false) { + $err = curl_error($curl); + curl_close($curl); + return $err; + } + else { + curl_close($curl); + $stats = json_decode($response, true); + if (!empty($stats)) return $stats; + } + return false; + break; + case 'host_stats': + curl_setopt($curl, CURLOPT_URL, 'https://dockerapi:443/host/stats'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 0); + curl_setopt($curl, CURLOPT_TIMEOUT, $DOCKER_TIMEOUT); + $response = curl_exec($curl); + if ($response === false) { + $err = curl_error($curl); + curl_close($curl); + return $err; + } + else { + curl_close($curl); + $stats = json_decode($response, true); + if (!empty($stats)) return $stats; + } + return false; + break; + case 'broadcast': + $request = array( + "api_call" => "container_post", + "container_name" => $service_name, + "post_action" => $attr1, + "request" => $attr2 + ); + + $redis->publish("MC_CHANNEL", json_encode($request)); + return true; + break; } } diff --git a/data/web/inc/functions.domain_admin.inc.php b/data/web/inc/functions.domain_admin.inc.php index bf9900e830..17aa4ad6a7 100644 --- a/data/web/inc/functions.domain_admin.inc.php +++ b/data/web/inc/functions.domain_admin.inc.php @@ -1,445 +1,498 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - if (empty($domains)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'domain_invalid' - ); - return false; - } - if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'username_invalid' - ); - return false; - } - - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` - WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - - $stmt = $pdo->prepare("SELECT `username` FROM `admin` - WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - - $stmt = $pdo->prepare("SELECT `username` FROM `domain_admins` - WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - - foreach ($num_results as $num_results_each) { - if ($num_results_each != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_exists', htmlspecialchars($username)) - ); - return false; - } - } - if (!empty($password) && !empty($password2)) { - if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'password_complexity' - ); - return false; - } - if ($password != $password2) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'password_mismatch' - ); - return false; - } - $password_hashed = hash_password($password); - foreach ($domains as $domain) { - if (!is_valid_domain_name($domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'domain_invalid' - ); - return false; - } - $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) - VALUES (:username, :domain, :created, :active)"); - $stmt->execute(array( - ':username' => $username, - ':domain' => $domain, - ':created' => date('Y-m-d H:i:s'), - ':active' => $active - )); - } - $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`) - VALUES (:username, :password_hashed, '0', :active)"); - $stmt->execute(array( - ':username' => $username, - ':password_hashed' => $password_hashed, - ':active' => $active - )); - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'password_empty' - ); - return false; - } - $stmt = $pdo->prepare("INSERT INTO `da_acl` (`username`) VALUES (:username)"); - $stmt->execute(array( - ':username' => $username - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('domain_admin_added', htmlspecialchars($username)) - ); - break; - case 'edit': - if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - // Administrator - if ($_SESSION['mailcow_cc_role'] == "admin") { - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - foreach ($usernames as $username) { - $is_now = domain_admin('details', $username); - $domains = (isset($_data['domains'])) ? (array)$_data['domains'] : null; - if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; - $domains = (!empty($domains)) ? $domains : $is_now['selected_domains']; - $username_new = (!empty($_data['username_new'])) ? $_data['username_new'] : $is_now['username']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - $password = $_data['password']; - $password2 = $_data['password2']; - if (!empty($domains)) { - foreach ($domains as $domain) { - if (!is_valid_domain_name($domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('domain_invalid', htmlspecialchars($domain)) - ); - continue 2; - } - } - } - if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('username_invalid', $username_new) - ); - continue; - } - if ($username_new != $username) { - if (!empty(domain_admin('details', $username_new)['username'])) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('username_invalid', $username_new) - ); - continue; - } - } - $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - $stmt = $pdo->prepare("UPDATE `da_acl` SET `username` = :username_new WHERE `username` = :username"); - $stmt->execute(array( - ':username_new' => $username_new, - ':username' => $username - )); - if (!empty($domains)) { - foreach ($domains as $domain) { - $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) - VALUES (:username_new, :domain, :created, :active)"); - $stmt->execute(array( - ':username_new' => $username_new, - ':domain' => $domain, - ':created' => date('Y-m-d H:i:s'), - ':active' => $active - )); - } - } - if (!empty($password) && !empty($password2)) { - if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'password_complexity' - ); - continue; - } - if ($password != $password2) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'password_mismatch' - ); - continue; - } - $password_hashed = hash_password($password); - $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username"); - $stmt->execute(array( - ':password_hashed' => $password_hashed, - ':username_new' => $username_new, - ':username' => $username, - ':active' => $active - )); - if (isset($_data['disable_tfa'])) { - $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - } - else { - $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); - $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); - } - } - else { - $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username"); - $stmt->execute(array( - ':username_new' => $username_new, - ':username' => $username, - ':active' => $active - )); - if (isset($_data['disable_tfa'])) { - $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - } - else { - $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); - $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); - } - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('domain_admin_modified', htmlspecialchars($username)) - ); - } - return true; - } - // Domain administrator - // Can only edit itself - elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") { - $username = $_SESSION['mailcow_cc_username']; - $password_old = $_data['user_old_pass']; - $password_new = $_data['user_new_pass']; - $password_new2 = $_data['user_new_pass2']; - - $stmt = $pdo->prepare("SELECT `password` FROM `admin` - WHERE `username` = :user"); - $stmt->execute(array(':user' => $username)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!verify_hash($row['password'], $password_old)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - if (!empty($password_new2) && !empty($password_new)) { - if ($password_new2 != $password_new) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'password_mismatch' - ); - return false; - } - if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password_new)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'password_complexity' - ); - return false; - } - $password_hashed = hash_password($password_new); - $stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username"); - $stmt->execute(array( - ':password_hashed' => $password_hashed, - ':username' => $username - )); - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('domain_admin_modified', htmlspecialchars($username)) - ); - } - break; - case 'delete': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $usernames = (array)$_data['username']; - foreach ($usernames as $username) { - if (empty(domain_admin('details', $username))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('username_invalid', $username) - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - $stmt = $pdo->prepare("DELETE FROM `da_acl` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('domain_admin_removed', htmlspecialchars($username)) - ); - } - break; - case 'get': - $domainadmins = array(); - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->query("SELECT DISTINCT - `username` - FROM `domain_admins` - WHERE `username` IN ( - SELECT `username` FROM `admin` - WHERE `superadmin`!='1' - )"); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($rows)) { - $domainadmins[] = $row['username']; - } - return $domainadmins; - break; - case 'details': - $domainadmindata = array(); - if ($_SESSION['mailcow_cc_role'] == "domainadmin" && $_data != $_SESSION['mailcow_cc_username']) { - return false; - } - elseif ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { - return false; - } - if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $_data))) { - return false; - } - $stmt = $pdo->prepare("SELECT - `tfa`.`active` AS `tfa_active_int`, - CASE `tfa`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `tfa_active`, - `domain_admins`.`username`, - `domain_admins`.`created`, - `domain_admins`.`active` AS `active_int`, - CASE `domain_admins`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` - FROM `domain_admins` - LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username` - WHERE `domain_admins`.`username`= :domain_admin"); - $stmt->execute(array( - ':domain_admin' => $_data - )); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (empty($row)) { - return false; - } - $domainadmindata['username'] = $row['username']; - $domainadmindata['tfa_active'] = $row['tfa_active']; - $domainadmindata['active'] = $row['active']; - $domainadmindata['tfa_active_int'] = $row['tfa_active_int']; - $domainadmindata['active_int'] = $row['active_int']; - $domainadmindata['created'] = $row['created']; - // GET SELECTED - $stmt = $pdo->prepare("SELECT `domain` FROM `domain` - WHERE `domain` IN ( - SELECT `domain` FROM `domain_admins` - WHERE `username`= :domain_admin)"); - $stmt->execute(array(':domain_admin' => $_data)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $domainadmindata['selected_domains'][] = $row['domain']; - } - // GET UNSELECTED - $stmt = $pdo->prepare("SELECT `domain` FROM `domain` - WHERE `domain` NOT IN ( - SELECT `domain` FROM `domain_admins` - WHERE `username`= :domain_admin)"); - $stmt->execute(array(':domain_admin' => $_data)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $domainadmindata['unselected_domains'][] = $row['domain']; - } - if (!isset($domainadmindata['unselected_domains'])) { - $domainadmindata['unselected_domains'] = ""; - } - - return $domainadmindata; - break; - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + if (empty($domains)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'domain_invalid' + ); + return false; + } + if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username) || $username == 'API') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('username_invalid', $username) + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + + $stmt = $pdo->prepare("SELECT `username` FROM `admin` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + + $stmt = $pdo->prepare("SELECT `username` FROM `domain_admins` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + + foreach ($num_results as $num_results_each) { + if ($num_results_each != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_exists', htmlspecialchars($username)) + ); + return false; + } + } + if (password_check($password, $password2) !== true) { + continue; + } + $password_hashed = hash_password($password); + $valid_domains = 0; + foreach ($domains as $domain) { + if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('domain_invalid', htmlspecialchars($domain)) + ); + continue; + } + $valid_domains++; + $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) + VALUES (:username, :domain, :created, :active)"); + $stmt->execute(array( + ':username' => $username, + ':domain' => $domain, + ':created' => date('Y-m-d H:i:s'), + ':active' => $active + )); + } + if ($valid_domains != 0) { + $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`) + VALUES (:username, :password_hashed, '0', :active)"); + $stmt->execute(array( + ':username' => $username, + ':password_hashed' => $password_hashed, + ':active' => $active + )); + } + $stmt = $pdo->prepare("INSERT INTO `da_acl` (`username`) VALUES (:username)"); + $stmt->execute(array( + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('domain_admin_added', htmlspecialchars($username)) + ); + break; + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + // Administrator + if ($_SESSION['mailcow_cc_role'] == "admin") { + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + foreach ($usernames as $username) { + $is_now = domain_admin('details', $username); + $domains = (isset($_data['domains'])) ? (array)$_data['domains'] : null; + if (!empty($is_now)) { + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $domains = (!empty($domains)) ? $domains : $is_now['selected_domains']; + $username_new = (!empty($_data['username_new'])) ? $_data['username_new'] : $is_now['username']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + $password = $_data['password']; + $password2 = $_data['password2']; + if (!empty($domains)) { + foreach ($domains as $domain) { + if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('domain_invalid', htmlspecialchars($domain)) + ); + continue 2; + } + } + } + if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('username_invalid', $username_new) + ); + continue; + } + if ($username_new != $username) { + if (!empty(domain_admin('details', $username_new)['username'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('username_invalid', $username_new) + ); + continue; + } + } + $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("UPDATE `da_acl` SET `username` = :username_new WHERE `username` = :username"); + $stmt->execute(array( + ':username_new' => $username_new, + ':username' => $username + )); + if (!empty($domains)) { + foreach ($domains as $domain) { + $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) + VALUES (:username_new, :domain, :created, :active)"); + $stmt->execute(array( + ':username_new' => $username_new, + ':domain' => $domain, + ':created' => date('Y-m-d H:i:s'), + ':active' => $active + )); + } + } + $force_tfa = intval($_data['force_tfa'] ?? 0) ? 1 : 0; + $force_pw_update = intval($_data['force_pw_update'] ?? 0) ? 1 : 0; + if (!empty($password)) { + if (password_check($password, $password2) !== true) { + return false; + } + $password_hashed = hash_password($password); + $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed, + `attributes` = JSON_SET(COALESCE(`attributes`, '{}'), '$.force_tfa', :force_tfa, '$.force_pw_update', :force_pw_update) + WHERE `username` = :username"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username_new' => $username_new, + ':username' => $username, + ':active' => $active, + ':force_tfa' => strval($force_tfa), + ':force_pw_update' => strval($force_pw_update) + )); + if (isset($_data['disable_tfa'])) { + $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + } + else { + $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); + $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); + } + } + else { + $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, + `attributes` = JSON_SET(COALESCE(`attributes`, '{}'), '$.force_tfa', :force_tfa, '$.force_pw_update', :force_pw_update) + WHERE `username` = :username"); + $stmt->execute(array( + ':username_new' => $username_new, + ':username' => $username, + ':active' => $active, + ':force_tfa' => strval($force_tfa), + ':force_pw_update' => strval($force_pw_update) + )); + if (isset($_data['disable_tfa'])) { + $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + } + else { + $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); + $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('domain_admin_modified', htmlspecialchars($username)) + ); + } + return true; + } + // Domain administrator + // Can only edit itself + elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") { + $username = $_SESSION['mailcow_cc_username']; + $password_old = $_data['user_old_pass'] ?? ''; + $password_new = $_data['user_new_pass']; + $password_new2 = $_data['user_new_pass2']; + + // Only verify old password if this is NOT a forced password update + if (empty($_SESSION['pending_pw_update'])) { + $stmt = $pdo->prepare("SELECT `password` FROM `admin` + WHERE `username` = :user"); + $stmt->execute(array(':user' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!verify_hash($row['password'], $password_old)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + } + if (password_check($password_new, $password_new2) !== true) { + return false; + } + $password_hashed = hash_password($password_new); + $stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed, + `attributes` = JSON_SET(COALESCE(`attributes`, '{}'), '$.force_pw_update', '0') + WHERE `username` = :username"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username' => $username + )); + unset($_SESSION['pending_pw_update']); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('domain_admin_modified', htmlspecialchars($username)) + ); + } + break; + case 'delete': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $usernames = (array)$_data['username']; + foreach ($usernames as $username) { + if (empty(domain_admin('details', $username))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('username_invalid', $username) + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `da_acl` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('domain_admin_removed', htmlspecialchars($username)) + ); + } + break; + case 'get': + $domainadmins = array(); + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->query("SELECT DISTINCT + `username` + FROM `domain_admins` + WHERE `username` IN ( + SELECT `username` FROM `admin` + WHERE `superadmin`!='1' + )"); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows)) { + $domainadmins[] = $row['username']; + } + return $domainadmins; + break; + case 'details': + $domainadmindata = array(); + if ($_SESSION['mailcow_cc_role'] == "domainadmin" && $_data != $_SESSION['mailcow_cc_username']) { + return false; + } + elseif ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { + return false; + } + if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $_data))) { + return false; + } + $stmt = $pdo->prepare("SELECT + `tfa`.`active` AS `tfa_active`, + `domain_admins`.`username`, + `domain_admins`.`created`, + `domain_admins`.`active` AS `active`, + `admin`.`attributes` AS `attributes` + FROM `domain_admins` + LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username` + LEFT OUTER JOIN `admin` ON `admin`.`username`=`domain_admins`.`username` + WHERE `domain_admins`.`username`= :domain_admin"); + $stmt->execute(array( + ':domain_admin' => $_data + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row)) { + return false; + } + $domainadmindata['username'] = $row['username']; + $domainadmindata['tfa_active'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active']; + $domainadmindata['tfa_active_int'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active']; + $domainadmindata['active'] = $row['active']; + $domainadmindata['active_int'] = $row['active']; + $domainadmindata['created'] = $row['created']; + $domainadmindata['attributes'] = json_decode($row['attributes'], true) ?? array('force_tfa' => '0', 'force_pw_update' => '0'); + // GET SELECTED + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain` IN ( + SELECT `domain` FROM `domain_admins` + WHERE `username`= :domain_admin)"); + $stmt->execute(array(':domain_admin' => $_data)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $domainadmindata['selected_domains'][] = $row['domain']; + } + // GET UNSELECTED + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain` NOT IN ( + SELECT `domain` FROM `domain_admins` + WHERE `username`= :domain_admin)"); + $stmt->execute(array(':domain_admin' => $_data)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $domainadmindata['unselected_domains'][] = $row['domain']; + } + if (!isset($domainadmindata['unselected_domains'])) { + $domainadmindata['unselected_domains'] = ""; + } + + return $domainadmindata; + break; + } +} +function domain_admin_sso($_action, $_data) { + global $pdo; + + switch ($_action) { + case 'check': + $token = preg_replace('/[^a-zA-Z0-9-]/', '', $_data); + + $stmt = $pdo->prepare("SELECT `t1`.`username` FROM `da_sso` AS `t1` JOIN `admin` AS `t2` ON `t1`.`username` = `t2`.`username` WHERE `t1`.`token` = :token AND `t1`.`created` > DATE_SUB(NOW(), INTERVAL '30' SECOND) AND `t2`.`active` = 1 AND `t2`.`superadmin` = 0;"); + $stmt->execute(array( + ':token' => $token + )); + $return = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($return['username'])) { + return false; + } + // single-use: consume the token; if a concurrent request already used it, deny + $del = $pdo->prepare("DELETE FROM `da_sso` WHERE `token` = :token"); + $del->execute(array( + ':token' => $token + )); + if ($del->rowCount() < 1) { + return false; + } + return $return['username']; + case 'issue': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + + $username = $_data['username']; + + $stmt = $pdo->prepare("SELECT `username` FROM `domain_admins` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + + if ($num_results < 1) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('object_doesnt_exist', htmlspecialchars($username)) + ); + return false; + } + + $token = implode('-', array( + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))) + )); + + $stmt = $pdo->prepare("INSERT INTO `da_sso` (`username`, `token`) + VALUES (:username, :token)"); + $stmt->execute(array( + ':username' => $username, + ':token' => $token + )); + + // perform cleanup + $pdo->query("DELETE FROM `da_sso` WHERE created < DATE_SUB(NOW(), INTERVAL '30' SECOND);"); + + return ['token' => $token]; + break; + } +} diff --git a/data/web/inc/functions.fail2ban.inc.php b/data/web/inc/functions.fail2ban.inc.php index 166606ef2b..5962237fc8 100644 --- a/data/web/inc/functions.fail2ban.inc.php +++ b/data/web/inc/functions.fail2ban.inc.php @@ -1,17 +1,6 @@ = 0 && $cidr[1] <= 32))) { - return true; - } - elseif (filter_var($cidr[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && (!isset($cidr[1]) || ($cidr[1] >= 0 && $cidr[1] <= 128))) { - return true; - } - return false; -} -function fail2ban($_action, $_data = null) { +function fail2ban($_action, $_data = null, $_extra = null) { global $redis; - global $lang; $_data_log = $_data; switch ($_action) { case 'get': @@ -21,14 +10,15 @@ function fail2ban($_action, $_data = null) { } try { $f2b_options = json_decode($redis->Get('F2B_OPTIONS'), true); + $f2b_options['regex'] = json_decode($redis->Get('F2B_REGEX'), true); $wl = $redis->hGetAll('F2B_WHITELIST'); if (is_array($wl)) { foreach ($wl as $key => $value) { $tmp_wl_data[] = $key; } if (isset($tmp_wl_data)) { - sort($tmp_wl_data); - $f2b_options['whitelist'] = implode(PHP_EOL, $tmp_wl_data); + natsort($tmp_wl_data); + $f2b_options['whitelist'] = implode(PHP_EOL, (array)$tmp_wl_data); } else { $f2b_options['whitelist'] = ""; @@ -43,8 +33,8 @@ function fail2ban($_action, $_data = null) { $tmp_bl_data[] = $key; } if (isset($tmp_bl_data)) { - sort($tmp_bl_data); - $f2b_options['blacklist'] = implode(PHP_EOL, $tmp_bl_data); + natsort($tmp_bl_data); + $f2b_options['blacklist'] = implode(PHP_EOL, (array)$tmp_bl_data); } else { $f2b_options['blacklist'] = ""; @@ -56,7 +46,11 @@ function fail2ban($_action, $_data = null) { $pb = $redis->hGetAll('F2B_PERM_BANS'); if (is_array($pb)) { foreach ($pb as $key => $value) { - $f2b_options['perm_bans'][] = $key; + $f2b_options['perm_bans'][] = array( + 'network'=>$key, + 'ip' => strtok($key,'/') + ); + } } else { @@ -71,6 +65,7 @@ function fail2ban($_action, $_data = null) { $f2b_options['active_bans'][] = array( 'queued_for_unban' => $queued_for_unban, 'network' => $network, + 'ip' => strtok($network,'/'), 'banned_until' => sprintf('%02dh %02dm %02ds', ($difference/3600), ($difference/60%60), $difference%60) ); } @@ -98,53 +93,161 @@ function fail2ban($_action, $_data = null) { ); return false; } - if (isset($_data['action']) && !empty($_data['network'])) { - $networks = (array) $_data['network']; - foreach ($networks as $network) { + // Start to read actions, if any + if (isset($_data['action'])) { + // Reset regex filters + if ($_data['action'] == "reset-regex") { try { + $redis->Del('F2B_REGEX'); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + // Rules will also be recreated on log events, but rules may seem empty for a second in the UI + docker('post', 'netfilter-mailcow', 'restart'); + $fail_count = 0; + $regex_result = json_decode($redis->Get('F2B_REGEX'), true); + while (empty($regex_result) && $fail_count < 10) { + $regex_result = json_decode($redis->Get('F2B_REGEX'), true); + $fail_count++; + sleep(1); + } + if ($fail_count >= 10) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('reset_f2b_regex') + ); + return false; + } + } + elseif ($_data['action'] == "edit-regex") { + if (!empty($_data['regex'])) { + $rule_id = 1; + $regex_array = array(); + foreach($_data['regex'] as $regex) { + $regex_array[$rule_id] = $regex; + $rule_id++; + } + if (!empty($regex_array)) { + $redis->Set('F2B_REGEX', json_encode($regex_array, JSON_UNESCAPED_SLASHES)); + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars($network)) + ); + return true; + } + + // Start actions in dependency of network + if (!empty($_data['network'])) { + $networks = (array)$_data['network']; + foreach ($networks as $network) { + // Unban network if ($_data['action'] == "unban") { if (valid_network($network)) { - $redis->hSet('F2B_QUEUE_UNBAN', $network, 1); + try { + $redis->hSet('F2B_QUEUE_UNBAN', $network, 1); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } } } + // Whitelist network elseif ($_data['action'] == "whitelist") { + if (empty($network)) { continue; } if (valid_network($network)) { - $redis->hSet('F2B_WHITELIST', $network, 1); - $redis->hDel('F2B_BLACKLIST', $network, 1); - $redis->hSet('F2B_QUEUE_UNBAN', $network, 1); + try { + $redis->hSet('F2B_WHITELIST', $network, 1); + $redis->hDel('F2B_BLACKLIST', $network, 1); + $redis->hSet('F2B_QUEUE_UNBAN', $network, 1); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('network_host_invalid', $network) + ); + continue; } } + // Blacklist network elseif ($_data['action'] == "blacklist") { - if (valid_network($network)) { - $redis->hSet('F2B_BLACKLIST', $network, 1); + if (empty($network)) { continue; } + if (valid_network($network) && !in_array($network, array( + '0.0.0.0', + '0.0.0.0/0', + getenv('IPV4_NETWORK') . '0/24', + getenv('IPV4_NETWORK') . '0', + getenv('IPV6_NETWORK') + ))) { + try { + $redis->hSet('F2B_BLACKLIST', $network, 1); + $redis->hDel('F2B_WHITELIST', $network, 1); + //$response = docker('post', 'netfilter-mailcow', 'restart'); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('network_host_invalid', $network) + ); + continue; } } - } - catch (RedisException $e) { $_SESSION['return'][] = array( - 'type' => 'danger', + 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) + 'msg' => array('object_modified', htmlspecialchars($network)) ); - continue; } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_modified', htmlspecialchars($network)) - ); + return true; } - return true; } + // Start default edit without specific action $is_now = fail2ban('get'); if (!empty($is_now)) { $ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']); - $max_attempts = intval((isset($_data['max_attempts'])) ? $_data['max_attempts'] : $is_now['active_int']); + $ban_time_increment = (isset($_data['ban_time_increment']) && $_data['ban_time_increment'] == "1") ? 1 : 0; + $max_attempts = intval((isset($_data['max_attempts'])) ? $_data['max_attempts'] : $is_now['max_attempts']); + $max_ban_time = intval((isset($_data['max_ban_time'])) ? $_data['max_ban_time'] : $is_now['max_ban_time']); $retry_window = intval((isset($_data['retry_window'])) ? $_data['retry_window'] : $is_now['retry_window']); $netban_ipv4 = intval((isset($_data['netban_ipv4'])) ? $_data['netban_ipv4'] : $is_now['netban_ipv4']); $netban_ipv6 = intval((isset($_data['netban_ipv6'])) ? $_data['netban_ipv6'] : $is_now['netban_ipv6']); $wl = (isset($_data['whitelist'])) ? $_data['whitelist'] : $is_now['whitelist']; $bl = (isset($_data['blacklist'])) ? $_data['blacklist'] : $is_now['blacklist']; + $manage_external = (isset($_data['manage_external'])) ? intval($_data['manage_external']) : 0; } else { $_SESSION['return'][] = array( @@ -156,33 +259,61 @@ function fail2ban($_action, $_data = null) { } $f2b_options = array(); $f2b_options['ban_time'] = ($ban_time < 60) ? 60 : $ban_time; + $f2b_options['ban_time_increment'] = ($ban_time_increment == 1) ? true : false; + $f2b_options['max_ban_time'] = ($max_ban_time < 60) ? 60 : $max_ban_time; $f2b_options['netban_ipv4'] = ($netban_ipv4 < 8) ? 8 : $netban_ipv4; $f2b_options['netban_ipv6'] = ($netban_ipv6 < 8) ? 8 : $netban_ipv6; $f2b_options['netban_ipv4'] = ($netban_ipv4 > 32) ? 32 : $netban_ipv4; $f2b_options['netban_ipv6'] = ($netban_ipv6 > 128) ? 128 : $netban_ipv6; $f2b_options['max_attempts'] = ($max_attempts < 1) ? 1 : $max_attempts; $f2b_options['retry_window'] = ($retry_window < 1) ? 1 : $retry_window; + $f2b_options['banlist_id'] = $is_now['banlist_id']; + $f2b_options['manage_external'] = ($manage_external > 0) ? 1 : 0; try { $redis->Set('F2B_OPTIONS', json_encode($f2b_options)); $redis->Del('F2B_WHITELIST'); $redis->Del('F2B_BLACKLIST'); if(!empty($wl)) { $wl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $wl)); + $wl_array = array_filter($wl_array); if (is_array($wl_array)) { foreach ($wl_array as $wl_item) { - if (valid_network($wl_item)) { + if (valid_network($wl_item) || valid_hostname($wl_item)) { $redis->hSet('F2B_WHITELIST', $wl_item, 1); } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('network_host_invalid', $wl_item) + ); + continue; + } } } } if(!empty($bl)) { $bl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $bl)); + $bl_array = array_filter($bl_array); if (is_array($bl_array)) { foreach ($bl_array as $bl_item) { - if (valid_network($bl_item)) { + if (valid_network($bl_item) && !in_array($bl_item, array( + '0.0.0.0', + '0.0.0.0/0', + getenv('IPV4_NETWORK') . '0/24', + getenv('IPV4_NETWORK') . '0', + getenv('IPV6_NETWORK') + ))) { $redis->hSet('F2B_BLACKLIST', $bl_item, 1); } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('network_host_invalid', $bl_item) + ); + continue; + } } } } @@ -201,5 +332,71 @@ function fail2ban($_action, $_data = null) { 'msg' => 'f2b_modified' ); break; + case 'banlist': + try { + $f2b_options = json_decode($redis->Get('F2B_OPTIONS'), true); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log, $_extra), + 'msg' => array('redis_error', $e) + ); + http_response_code(500); + return false; + } + if (is_array($_extra)) { + $_extra = $_extra[0]; + } + if ($_extra != $f2b_options['banlist_id']){ + http_response_code(404); + return false; + } + + switch ($_data) { + case 'get': + try { + $bl = $redis->hKeys('F2B_BLACKLIST'); + $active_bans = $redis->hKeys('F2B_ACTIVE_BANS'); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log, $_extra), + 'msg' => array('redis_error', $e) + ); + http_response_code(500); + return false; + } + $banlist = implode("\n", array_merge($bl, $active_bans)); + return $banlist; + break; + case 'refresh': + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + + $f2b_options['banlist_id'] = uuid4(); + try { + $redis->Set('F2B_OPTIONS', json_encode($f2b_options)); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log, $_extra), + 'msg' => array('redis_error', $e) + ); + return false; + } + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log, $_extra), + 'msg' => 'f2b_banlist_refreshed' + ); + return true; + break; + } + break; } } diff --git a/data/web/inc/functions.fwdhost.inc.php b/data/web/inc/functions.fwdhost.inc.php index 5c511f4f26..52c751591d 100644 --- a/data/web/inc/functions.fwdhost.inc.php +++ b/data/web/inc/functions.fwdhost.inc.php @@ -1,8 +1,8 @@ 'success', 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('forwarding_host_added', htmlspecialchars(implode(', ', $hosts))) + 'msg' => array('forwarding_host_added', htmlspecialchars(implode(', ', (array)$hosts))) ); break; case 'edit': @@ -108,6 +108,14 @@ function fwdhost($_action, $_data = null) { } break; case 'delete': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } $hosts = (array)$_data['forwardinghost']; foreach ($hosts as $host) { try { @@ -180,4 +188,4 @@ function fwdhost($_action, $_data = null) { return $fwdhostdetails; break; } -} \ No newline at end of file +} diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 4cc43ee39b..90c830b695 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1,22 +1,451 @@ 1) { + $bits=(int)($ar[1]); + } + else { + $bits = $iplen * 8; + } + for ($c=0; $bits>0; $c++) { + $bytemask = ($bits < 8) ? 0xff ^ ((1 << (8-$bits))-1) : 0xff; + if (((ord($ipb[$c]) ^ ord($ip1b[$c])) & $bytemask) != 0) { + continue 2; + } + $bits-=8; + } + return true; + } + return false; +} function hash_password($password) { - $salt_str = bin2hex(openssl_random_pseudo_bytes(8)); - return "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str); + // default_pass_scheme is determined in vars.inc.php (or corresponding local file) + // in case default pass scheme is not defined, falling back to BLF-CRYPT. + global $default_pass_scheme; + $pw_hash = NULL; + // support pre-hashed passwords + if (preg_match('/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i', $password)) { + $pw_hash = $password; + } + else { + switch (strtoupper($default_pass_scheme)) { + case "SSHA": + $salt_str = bin2hex(openssl_random_pseudo_bytes(8)); + $pw_hash = "{SSHA}".base64_encode(hash('sha1', $password . $salt_str, true) . $salt_str); + break; + case "SSHA256": + $salt_str = bin2hex(openssl_random_pseudo_bytes(8)); + $pw_hash = "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str); + break; + case "SSHA512": + $salt_str = bin2hex(openssl_random_pseudo_bytes(8)); + $pw_hash = "{SSHA512}".base64_encode(hash('sha512', $password . $salt_str, true) . $salt_str); + break; + case "BLF-CRYPT": + default: + $pw_hash = "{BLF-CRYPT}" . password_hash($password, PASSWORD_BCRYPT); + break; + } + } + return $pw_hash; +} +function password_complexity($_action, $_data = null) { + global $redis; + global $lang; + switch ($_action) { + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + $is_now = password_complexity('get'); + if (!empty($is_now)) { + $length = (isset($_data['length']) && intval($_data['length']) >= 3) ? intval($_data['length']) : $is_now['length']; + $chars = (isset($_data['chars'])) ? intval($_data['chars']) : $is_now['chars']; + $lowerupper = (isset($_data['lowerupper'])) ? intval($_data['lowerupper']) : $is_now['lowerupper']; + $special_chars = (isset($_data['special_chars'])) ? intval($_data['special_chars']) : $is_now['special_chars']; + $numbers = (isset($_data['numbers'])) ? intval($_data['numbers']) : $is_now['numbers']; + } + try { + $redis->hMSet('PASSWD_POLICY', [ + 'length' => $length, + 'chars' => $chars, + 'special_chars' => $special_chars, + 'lowerupper' => $lowerupper, + 'numbers' => $numbers + ]); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'password_policy_saved' + ); + break; + case 'get': + try { + $length = $redis->hGet('PASSWD_POLICY', 'length'); + $chars = $redis->hGet('PASSWD_POLICY', 'chars'); + $special_chars = $redis->hGet('PASSWD_POLICY', 'special_chars'); + $lowerupper = $redis->hGet('PASSWD_POLICY', 'lowerupper'); + $numbers = $redis->hGet('PASSWD_POLICY', 'numbers'); + return array( + 'length' => $length, + 'chars' => $chars, + 'special_chars' => $special_chars, + 'lowerupper' => $lowerupper, + 'numbers' => $numbers + ); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + return false; + break; + case 'html': + $policies = password_complexity('get'); + foreach ($policies as $name => $value) { + if ($value != 0) { + $policy_text[] = sprintf($lang['admin']["password_policy_$name"], $value); + } + } + return '

    - ' . implode('
    - ', (array)$policy_text) . '

    '; + break; + } +} + +function password_generate(){ + $password_complexity = password_complexity('get'); + $min_length = max(16, intval($password_complexity['length'])); + + $lowercase = range('a', 'z'); + $uppercase = range('A', 'Z'); + $digits = range(0, 9); + $special_chars = str_split('!@#$%^&*()?='); + + $password = [ + $lowercase[random_int(0, count($lowercase) - 1)], + $uppercase[random_int(0, count($uppercase) - 1)], + $digits[random_int(0, count($digits) - 1)], + $special_chars[random_int(0, count($special_chars) - 1)], + ]; + + $all = array_merge($lowercase, $uppercase, $digits, $special_chars); + + while (count($password) < $min_length) { + $password[] = $all[random_int(0, count($all) - 1)]; + } + + // Cryptographically secure shuffle using Fisher-Yates algorithm + $count = count($password); + for ($i = $count - 1; $i > 0; $i--) { + $j = random_int(0, $i); + $temp = $password[$i]; + $password[$i] = $password[$j]; + $password[$j] = $temp; + } + + return implode('', $password); + +} + +function password_check($password1, $password2) { + $password_complexity = password_complexity('get'); + + if (empty($password1) || empty($password2)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type), + 'msg' => 'password_complexity' + ); + return false; + } + + if ($password1 != $password2) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type), + 'msg' => 'password_mismatch' + ); + return false; + } + + $given_password['length'] = strlen($password1); + $given_password['special_chars'] = preg_match('/[^a-zA-Z\d]/', $password1); + $given_password['chars'] = preg_match('/[a-zA-Z]/',$password1); + $given_password['numbers'] = preg_match('/\d/', $password1); + $lower = strlen(preg_replace("/[^a-z]/", '', $password1)); + $upper = strlen(preg_replace("/[^A-Z]/", '', $password1)); + $given_password['lowerupper'] = ($lower > 0 && $upper > 0) ? true : false; + + if ( + ($given_password['length'] < $password_complexity['length']) || + ($password_complexity['special_chars'] == 1 && (intval($given_password['special_chars']) != $password_complexity['special_chars'])) || + ($password_complexity['chars'] == 1 && (intval($given_password['chars']) != $password_complexity['chars'])) || + ($password_complexity['numbers'] == 1 && (intval($given_password['numbers']) != $password_complexity['numbers'])) || + ($password_complexity['lowerupper'] == 1 && (intval($given_password['lowerupper']) != $password_complexity['lowerupper'])) + ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type), + 'msg' => 'password_complexity' + ); + return false; + } + + return true; } -function last_login($user) { +function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) { global $pdo; - $stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs` - WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login" - AND JSON_EXTRACT(`call`, "$[1]") = :user - AND `type` = "success" ORDER BY `time` DESC LIMIT 1'); - $stmt->execute(array(':user' => $user)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($row)) { - return $row; + global $redis; + $sasl_limit_days = intval($sasl_limit_days); + switch ($action) { + case 'get': + if (filter_var($username, FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $stmt = $pdo->prepare('SELECT `real_rip`, MAX(`datetime`) as `datetime`, `service`, `app_password`, MAX(`app_passwd`.`name`) as `app_password_name` FROM `sasl_log` + LEFT OUTER JOIN `app_passwd` on `sasl_log`.`app_password` = `app_passwd`.`id` + WHERE `username` = :username + AND HOUR(TIMEDIFF(NOW(), `datetime`)) < :sasl_limit_days + GROUP BY `real_rip`, `service`, `app_password` + ORDER BY `datetime` DESC;'); + $stmt->execute(array(':username' => $username, ':sasl_limit_days' => ($sasl_limit_days * 24))); + $sasl = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($sasl as $k => $v) { + if (!filter_var($sasl[$k]['real_rip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + $sasl[$k]['real_rip'] = 'Web/EAS/Internal (' . $sasl[$k]['real_rip'] . ')'; + } + elseif (filter_var($sasl[$k]['real_rip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + try { + $sasl[$k]['location'] = $redis->hGet('IP_SHORTCOUNTRY', $sasl[$k]['real_rip']); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + if (!$sasl[$k]['location']) { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL,"https://dfdata.bella.network/country/" . $sasl[$k]['real_rip']); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_USERAGENT, 'Moocow'); + curl_setopt($curl, CURLOPT_TIMEOUT, 5); + $ip_data = curl_exec($curl); + if (!curl_errno($curl)) { + $ip_data_array = json_decode($ip_data, true); + if ($ip_data_array !== false and !empty($ip_data_array['shortcountry'])) { + $sasl[$k]['location'] = $ip_data_array['shortcountry']; + try { + $redis->hSet('IP_SHORTCOUNTRY', $sasl[$k]['real_rip'], $ip_data_array['shortcountry']); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + curl_close($curl); + return false; + } + } + } + curl_close($curl); + } + } + } + } + else { + $sasl = array(); + } + if ($_SESSION['mailcow_cc_role'] == "admin" || $username == $_SESSION['mailcow_cc_username']) { + $stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs` + WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login" + AND JSON_EXTRACT(`call`, "$[1]") = :username + AND `type` = "success" ORDER BY `time` DESC LIMIT 1 OFFSET :offset'); + $stmt->execute(array( + ':username' => $username, + ':offset' => $ui_offset + )); + $ui = $stmt->fetch(PDO::FETCH_ASSOC); + } + else { + $ui = array(); + } + + return array('ui' => $ui, 'sasl' => $sasl); + break; + case 'reset': + if (filter_var($username, FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $stmt = $pdo->prepare('DELETE FROM `sasl_log` + WHERE `username` = :username'); + $stmt->execute(array(':username' => $username)); + } + if ($_SESSION['mailcow_cc_role'] == "admin" || $username == $_SESSION['mailcow_cc_username']) { + $stmt = $pdo->prepare('DELETE FROM `logs` + WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login" + AND JSON_EXTRACT(`call`, "$[1]") = :username + AND `type` = "success"'); + $stmt->execute(array(':username' => $username)); + } + return true; + break; } - else { + +} +function set_sasl_log($username, $real_rip, $service){ + global $pdo; + + try { + if (!empty($_SESSION['app_passwd_id'])) { + $app_password = $_SESSION['app_passwd_id']; + } else { + $app_password = 0; + } + + $stmt = $pdo->prepare('REPLACE INTO `sasl_log` (`username`, `real_rip`, `service`, `app_password`) VALUES (:username, :real_rip, :service, :app_password)'); + $stmt->execute(array( + ':username' => $username, + ':real_rip' => $real_rip, + ':service' => $service, + ':app_password' => $app_password + )); + } catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('mysql_error', $e) + ); return false; } + + return true; } function flush_memcached() { try { @@ -30,42 +459,43 @@ function flush_memcached() { } function sys_mail($_data) { if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', + $_SESSION['return'][] = array( + 'type' => 'danger', 'log' => array(__FUNCTION__), - 'msg' => 'access_denied' - ); - return false; - } + 'msg' => 'access_denied' + ); + return false; + } $excludes = $_data['mass_exclude']; $includes = $_data['mass_include']; $mailboxes = array(); $mass_from = $_data['mass_from']; $mass_text = $_data['mass_text']; + $mass_html = $_data['mass_html']; $mass_subject = $_data['mass_subject']; if (!filter_var($mass_from, FILTER_VALIDATE_EMAIL)) { - $_SESSION['return'][] = array( - 'type' => 'danger', + $_SESSION['return'][] = array( + 'type' => 'danger', 'log' => array(__FUNCTION__), - 'msg' => 'from_invalid' - ); - return false; + 'msg' => 'from_invalid' + ); + return false; } if (empty($mass_subject)) { - $_SESSION['return'][] = array( - 'type' => 'danger', + $_SESSION['return'][] = array( + 'type' => 'danger', 'log' => array(__FUNCTION__), - 'msg' => 'subject_empty' - ); - return false; + 'msg' => 'subject_empty' + ); + return false; } if (empty($mass_text)) { - $_SESSION['return'][] = array( - 'type' => 'danger', + $_SESSION['return'][] = array( + 'type' => 'danger', 'log' => array(__FUNCTION__), - 'msg' => 'text_empty' - ); - return false; + 'msg' => 'text_empty' + ); + return false; } $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); foreach ($domains as $domain) { @@ -101,7 +531,13 @@ function sys_mail($_data) { $mail->setFrom($mass_from); $mail->Subject = $mass_subject; $mail->CharSet ="UTF-8"; - $mail->Body = $mass_text; + if (!empty($mass_html)) { + $mail->Body = $mass_html; + $mail->AltBody = $mass_text; + } + else { + $mail->Body = $mass_text; + } $mail->XMailer = 'MooMassMail'; foreach ($rcpts as $rcpt) { $mail->AddAddress($rcpt); @@ -153,8 +589,14 @@ function logger($_data = false) { $task = substr(strtoupper(md5(uniqid(rand(), true))), 0, 6); foreach ($_data['return'] as $return) { $type = $return['type']; - $msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE); - $call = json_encode($return['log'], JSON_UNESCAPED_UNICODE); + $msg = null; + if (isset($return['msg'])) { + $msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); + } + $call = null; + if (isset($return['log'])) { + $call = json_encode($return['log'], JSON_UNESCAPED_UNICODE); + } if (!empty($_SESSION["dual-login"]["username"])) { $user = $_SESSION["dual-login"]["username"] . ' => ' . $_SESSION['mailcow_cc_username']; $role = $_SESSION["dual-login"]["role"] . ' => ' . $_SESSION['mailcow_cc_role']; @@ -181,8 +623,9 @@ function logger($_data = false) { ':remote' => get_remote_ip() )); } - catch (Exception $e) { - // Do nothing + catch (PDOException $e) { + # handle the exception here, as the exception handler function results in a white page + error_log($e->getMessage(), 0); } } } @@ -191,16 +634,19 @@ function logger($_data = false) { } } function hasDomainAccess($username, $role, $domain) { - global $pdo; - if (!filter_var($username, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { - return false; - } - if (empty($domain) || !is_valid_domain_name($domain)) { - return false; - } - if ($role != 'admin' && $role != 'domainadmin') { - return false; - } + global $pdo; + if (empty($domain) || !is_valid_domain_name($domain)) { + return false; + } + if (isset($_SESSION['access_all_exception']) && $_SESSION['access_all_exception'] == "1") { + return true; + } + if (!filter_var($username, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { + return false; + } + if ($role != 'admin' && $role != 'domainadmin') { + return false; + } if ($role == 'admin') { $stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain` = :domain"); @@ -227,26 +673,65 @@ function hasDomainAccess($username, $role, $domain) { return true; } } - return false; + return false; } function hasMailboxObjectAccess($username, $role, $object) { - global $pdo; - if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { - return false; - } - if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') { - return false; - } - if ($username == $object) { - return true; - } + global $pdo; + if (isset($_SESSION['access_all_exception']) && $_SESSION['access_all_exception'] == "1") { + return true; + } + if (empty($username) || empty($role) || empty($object)) { + return false; + } + if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { + return false; + } + if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') { + return false; + } + if ($username == $object) { + return true; + } $stmt = $pdo->prepare("SELECT `domain` FROM `mailbox` WHERE `username` = :object"); $stmt->execute(array(':object' => $object)); $row = $stmt->fetch(PDO::FETCH_ASSOC); if (isset($row['domain']) && hasDomainAccess($username, $role, $row['domain'])) { return true; } - return false; + return false; +} +// does also verify mailboxes as a mailbox is a alias == goto +function hasAliasObjectAccess($username, $role, $object) { + global $pdo; + if (isset($_SESSION['access_all_exception']) && $_SESSION['access_all_exception'] == "1") { + return true; + } + if (empty($username) || empty($role) || empty($object)) { + return false; + } + if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { + return false; + } + if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') { + return false; + } + $stmt = $pdo->prepare("SELECT `domain` FROM `alias` WHERE `address` = :object"); + $stmt->execute(array(':object' => $object)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (isset($row['domain']) && hasDomainAccess($username, $role, $row['domain'])) { + return true; + } + return false; +} +function hasACLAccess($type) { + if (isset($_SESSION['access_all_exception']) && $_SESSION['access_all_exception'] == "1") { + return true; + } + if (isset($_SESSION['acl'][$type]) && $_SESSION['acl'][$type] == "1") { + return true; + } + + return false; } function pem_to_der($pem_key) { // Need to remove BEGIN/END PUBLIC KEY @@ -255,6 +740,11 @@ function pem_to_der($pem_key) { unset($lines[0]); return base64_decode(implode('', $lines)); } +function expand_ipv6($ip) { + $hex = unpack("H*hex", inet_pton($ip)); + $ip = substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hex['hex']), 0, -1); + return $ip; +} function generate_tlsa_digest($hostname, $port, $starttls = null) { if (!is_valid_domain_name($hostname)) { return "Not a valid hostname"; @@ -314,15 +804,20 @@ function generate_tlsa_digest($hostname, $port, $starttls = null) { return 'Error: Cannot read peer certificate'; } } -function alertbox_log_parser($_data){ +function alertbox_log_parser($_data) { global $lang; if (isset($_data['return'])) { foreach ($_data['return'] as $return) { // Get type $type = $return['type']; // If a lang[type][msg] string exists, use it as message - if (is_string($lang[$return['type']][$return['msg']])) { - $msg = $lang[$return['type']][$return['msg']]; + if (isset($return['type']) && isset($return['msg']) && !is_array($return['msg'])) { + if (isset($lang[$return['type']][$return['msg']])) { + $msg = $lang[$return['type']][$return['msg']]; + } + else { + $msg = $return['msg']; + } } // If msg is an array, use first element as language string and run printf on it with remaining array elements elseif (is_array($return['msg'])) { @@ -332,212 +827,235 @@ function alertbox_log_parser($_data){ $return['msg'] ); } - // If none applies, use msg as returned message else { - $msg = $return['msg']; + $msg = '-'; } - $log_array[] = array('msg' => json_encode($msg), 'type' => json_encode($type)); + $log_array[] = array('msg' => $msg, 'type' => json_encode($type)); } - if (!empty($log_array)) { + if (!empty($log_array)) { return $log_array; } } return false; } -function verify_hash($hash, $password) { - if (preg_match('/^{SSHA256}/i', $hash)) { - // Remove tag if any - $hash = preg_replace('/^{SSHA256}/i', '', $hash); - // Decode hash - $dhash = base64_decode($hash); - // Get first 32 bytes of binary which equals a SHA256 hash - $ohash = substr($dhash, 0, 32); - // Remove SHA256 hash from decoded hash to get original salt string - $osalt = str_replace($ohash, '', $dhash); - // Check single salted SHA256 hash against extracted hash - if (hash_equals(hash('sha256', $password . $osalt, true), $ohash)) { - return true; - } +function verify_salted_hash($hash, $password, $algo, $salt_length) { + // Decode hash + $dhash = base64_decode($hash); + // Get first n bytes of binary which equals a SSHA hash + $ohash = substr($dhash, 0, $salt_length); + // Remove SSHA hash from decoded hash to get original salt string + $osalt = str_replace($ohash, '', $dhash); + // Check single salted SSHA hash against extracted hash + if (hash_equals(hash($algo, $password . $osalt, true), $ohash)) { + return true; } - elseif (preg_match('/^{SHA512-CRYPT}/i', $hash)) { - // Remove tag if any - $hash = preg_replace('/^{SHA512-CRYPT}/i', '', $hash); - // Decode hash - preg_match('/\\$6\\$(.*)\\$(.*)/i', $hash, $hash_array); - $osalt = $hash_array[1]; - $ohash = $hash_array[2]; - if (hash_equals(crypt($password, '$6$' . $osalt . '$'), $hash)) { - return true; + return false; +} +function verify_hash($hash, $password) { + if (preg_match('/^{(.+)}(.+)/i', $hash, $hash_array)) { + $scheme = strtoupper($hash_array[1]); + $hash = $hash_array[2]; + switch ($scheme) { + case "ARGON2I": + case "ARGON2ID": + case "BLF-CRYPT": + case "CRYPT": + case "DES-CRYPT": + case "MD5-CRYPT": + case "MD5": + case "SHA256-CRYPT": + case "SHA512-CRYPT": + return password_verify($password, $hash); + + case "CLEAR": + case "CLEARTEXT": + case "PLAIN": + return $password == $hash; + + case "LDAP-MD5": + $hash = base64_decode($hash); + return hash_equals(hash('md5', $password, true), $hash); + + case "PBKDF2": + $components = explode('$', $hash); + $salt = $components[2]; + $rounds = $components[3]; + $hash = $components[4]; + return hash_equals(hash_pbkdf2('sha1', $password, $salt, $rounds), $hash); + + case "PBKDF2-SHA512": + // Handle FreeIPA-style hash: {PBKDF2-SHA512}10000$$ + $components = explode('$', $hash); + if (count($components) !== 3) return false; + + // 1st part: iteration count (integer) + $iterations = intval($components[0]); + if ($iterations <= 0) return false; + + // 2nd part: salt (base64-encoded) + $salt = $components[1]; + // 3rd part: hash (base64-encoded) + $stored_hash_b64 = $components[2]; + + // Decode salt and hash from base64 + $salt_bin = base64_decode($salt, true); + $hash_bin = base64_decode($stored_hash_b64, true); + if ($salt_bin === false || $hash_bin === false) return false; + // Get length of hash in bytes + $hash_len = strlen($hash_bin); + if ($hash_len === 0) return false; + + // Calculate PBKDF2-SHA512 hash for provided password + $test_hash = hash_pbkdf2('sha512', $password, $salt_bin, $iterations, $hash_len, true); + return hash_equals($hash_bin, $test_hash); + + case "PLAIN-MD4": + return hash_equals(hash('md4', $password), $hash); + + case "PLAIN-MD5": + return md5($password) == $hash; + + case "PLAIN-TRUNC": + $components = explode('-', $hash); + if (count($components) > 1) { + $trunc_len = $components[0]; + $trunc_password = $components[1]; + + return substr($password, 0, $trunc_len) == $trunc_password; + } else { + return $password == $hash; + } + + case "SHA": + case "SHA1": + case "SHA256": + case "SHA512": + // SHA is an alias for SHA1 + $scheme = $scheme == "SHA" ? "sha1" : strtolower($scheme); + $hash = base64_decode($hash); + return hash_equals(hash($scheme, $password, true), $hash); + + case "SMD5": + return verify_salted_hash($hash, $password, 'md5', 16); + + case "SSHA": + return verify_salted_hash($hash, $password, 'sha1', 20); + + case "SSHA256": + return verify_salted_hash($hash, $password, 'sha256', 32); + + case "SSHA512": + return verify_salted_hash($hash, $password, 'sha512', 64); + + default: + return false; } } - elseif (preg_match('/^{SSHA512}/i', $hash)) { - $hash = preg_replace('/^{SSHA512}/i', '', $hash); - // Decode hash - $dhash = base64_decode($hash); - // Get first 64 bytes of binary which equals a SHA512 hash - $ohash = substr($dhash, 0, 64); - // Remove SHA512 hash from decoded hash to get original salt string - $osalt = str_replace($ohash, '', $dhash); - // Check single salted SHA512 hash against extracted hash - if (hash_equals(hash('sha512', $password . $osalt, true), $ohash)) { - return true; - } + return false; +} +function formatBytes($size, $precision = 2) { + if(!is_numeric($size)) { + return "0"; } - elseif (preg_match('/^{MD5-CRYPT}/i', $hash)) { - $hash = preg_replace('/^{MD5-CRYPT}/i', '', $hash); - if (password_verify($password, $hash)) { - return true; - } + $base = log($size, 1024); + $suffixes = array(' Byte', ' KiB', ' MiB', ' GiB', ' TiB'); + if ($size == "0") { + return "0"; } - return false; + return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)]; } -function check_login($user, $pass) { - global $pdo; - global $redis; - if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => 'malformed_username' - ); - return false; - } - $user = strtolower(trim($user)); - $stmt = $pdo->prepare("SELECT `password` FROM `admin` - WHERE `superadmin` = '1' - AND `username` = :user"); - $stmt->execute(array(':user' => $user)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($rows as $row) { - if (verify_hash($row['password'], $pass)) { - if (get_tfa($user)['name'] != "none") { - $_SESSION['pending_mailcow_cc_username'] = $user; - $_SESSION['pending_mailcow_cc_role'] = "admin"; - $_SESSION['pending_tfa_method'] = get_tfa($user)['name']; - unset($_SESSION['ldelay']); - $_SESSION['return'][] = array( - 'type' => 'info', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => 'awaiting_tfa_confirmation' - ); - return "pending"; - } - else { - unset($_SESSION['ldelay']); - // Reactivate TFA if it was set to "deactivate TFA for next login" - $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); - $stmt->execute(array(':user' => $user)); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => array('logged_in_as', $user) - ); - return "admin"; - } - } - } - $stmt = $pdo->prepare("SELECT `password` FROM `admin` - WHERE `superadmin` = '0' - AND `active`='1' - AND `username` = :user"); - $stmt->execute(array(':user' => $user)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($rows as $row) { - if (verify_hash($row['password'], $pass) !== false) { - if (get_tfa($user)['name'] != "none") { - $_SESSION['pending_mailcow_cc_username'] = $user; - $_SESSION['pending_mailcow_cc_role'] = "domainadmin"; - $_SESSION['pending_tfa_method'] = get_tfa($user)['name']; - unset($_SESSION['ldelay']); - $_SESSION['return'][] = array( - 'type' => 'info', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => 'awaiting_tfa_confirmation' - ); - return "pending"; - } - else { - unset($_SESSION['ldelay']); - // Reactivate TFA if it was set to "deactivate TFA for next login" - $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); - $stmt->execute(array(':user' => $user)); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => array('logged_in_as', $user) - ); - return "domainadmin"; - } - } - } - $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` - WHERE `kind` NOT REGEXP 'location|thing|group' - AND `active`='1' - AND `username` = :user"); - $stmt->execute(array(':user' => $user)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($rows as $row) { - if (verify_hash($row['password'], $pass) !== false) { - unset($_SESSION['ldelay']); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => array('logged_in_as', $user) - ); - return "user"; - } - } - if (!isset($_SESSION['ldelay'])) { - $_SESSION['ldelay'] = "0"; - $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); - error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); - } - elseif (!isset($_SESSION['mailcow_cc_username'])) { - $_SESSION['ldelay'] = $_SESSION['ldelay']+0.5; - $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); - error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); - } - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => 'login_failed' - ); - sleep($_SESSION['ldelay']); - return false; -} -function formatBytes($size, $precision = 2) { - if(!is_numeric($size)) { - return "0"; - } - $base = log($size, 1024); - $suffixes = array(' Byte', ' KiB', ' MiB', ' GiB', ' TiB'); - if ($size == "0") { - return "0"; - } - return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)]; -} -function update_sogo_static_view() { +function update_sogo_static_view($mailbox = null) { + if (getenv('SKIP_SOGO') == "y") { + return true; + } global $pdo; global $lang; - $stmt = $pdo->query("SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_NAME = 'sogo_view'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $stmt = $pdo->query("REPLACE INTO _sogo_static_view SELECT * from sogo_view"); - $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');"); + + $mailbox_exists = false; + if ($mailbox !== null) { + // Check if the mailbox exists + $stmt = $pdo->prepare("SELECT username FROM mailbox WHERE username = :mailbox AND active = '1'"); + $stmt->execute(array(':mailbox' => $mailbox)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row){ + $mailbox_exists = true; + } + } + + // generate random password for sogo to deny direct login + $random_password = base64_encode(openssl_random_pseudo_bytes(24)); + $random_salt = base64_encode(openssl_random_pseudo_bytes(16)); + $random_hash = '{SSHA256}' . base64_encode(hash('sha256', base64_decode($password) . $salt, true) . $salt); + + $subquery = "GROUP BY mailbox.username"; + if ($mailbox_exists) { + $subquery = "AND mailbox.username = :mailbox"; + } + $query = "INSERT INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`) + SELECT + mailbox.username, + mailbox.domain, + mailbox.username, + :random_hash, + mailbox.name, + mailbox.username, + IFNULL(GROUP_CONCAT(ga.aliases ORDER BY ga.aliases SEPARATOR ' '), ''), + IFNULL(gda.ad_alias, ''), + IFNULL(external_acl.send_as_acl, ''), + mailbox.kind, + mailbox.multiple_bookings + FROM + mailbox + LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)') + LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username + LEFT OUTER JOIN grouped_sender_acl_external external_acl ON external_acl.username = mailbox.username + WHERE + mailbox.active = '1' + $subquery + ON DUPLICATE KEY UPDATE + `domain` = VALUES(`domain`), + `c_name` = VALUES(`c_name`), + `c_password` = VALUES(`c_password`), + `c_cn` = VALUES(`c_cn`), + `mail` = VALUES(`mail`), + `aliases` = VALUES(`aliases`), + `ad_aliases` = VALUES(`ad_aliases`), + `ext_acl` = VALUES(`ext_acl`), + `kind` = VALUES(`kind`), + `multiple_bookings` = VALUES(`multiple_bookings`)"; + + + if ($mailbox_exists) { + $stmt = $pdo->prepare($query); + $stmt->execute(array( + ':random_hash' => $random_hash, + ':mailbox' => $mailbox + )); + } else { + $stmt = $pdo->prepare($query); + $stmt->execute(array( + ':random_hash' => $random_hash + )); } + + $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');"); + flush_memcached(); } function edit_user_account($_data) { - global $lang; - global $pdo; + global $lang; + global $pdo; + $_data_log = $_data; !isset($_data_log['user_new_pass']) ?: $_data_log['user_new_pass'] = '*'; !isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*'; !isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*'; + $username = $_SESSION['mailcow_cc_username']; $role = $_SESSION['mailcow_cc_role']; - $password_old = $_data['user_old_pass']; + $password_old = $_data['user_old_pass']; + $pw_recovery_email = $_data['pw_recovery_email']; + if (filter_var($username, FILTER_VALIDATE_EMAIL === false) || $role != 'user') { $_SESSION['return'][] = array( 'type' => 'danger', @@ -546,91 +1064,119 @@ function edit_user_account($_data) { ); return false; } - if (isset($_data['user_new_pass']) && isset($_data['user_new_pass2'])) { - $password_new = $_data['user_new_pass']; - $password_new2 = $_data['user_new_pass2']; - } - $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` - WHERE `kind` NOT REGEXP 'location|thing|group' - AND `username` = :user"); - $stmt->execute(array(':user' => $username)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!verify_hash($row['password'], $password_old)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - if (isset($password_new) && isset($password_new2)) { - if (!empty($password_new2) && !empty($password_new)) { - if ($password_new2 != $password_new) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'password_mismatch' - ); - return false; - } - if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password_new)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'password_complexity' - ); - return false; - } - $password_hashed = hash_password($password_new); - try { - $stmt = $pdo->prepare("UPDATE `mailbox` SET `password` = :password_hashed, `attributes` = JSON_SET(`attributes`, '$.force_pw_update', '0') WHERE `username` = :username"); - $stmt->execute(array( - ':password_hashed' => $password_hashed, - ':username' => $username - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', + + // edit password + $is_forced_pw_update = !empty($_SESSION['pending_pw_update']); + if (((!empty($password_old) || $is_forced_pw_update) && !empty($_data['user_new_pass']) && !empty($_data['user_new_pass2']))) { + // Only verify old password if this is NOT a forced password update + if (!$is_forced_pw_update) { + $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `username` = :user AND authsource = 'mailcow'"); + $stmt->execute(array(':user' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!verify_hash($row['password'], $password_old)) { + $_SESSION['return'][] = array( + 'type' => 'danger', 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } - } - } - update_sogo_static_view(); - $_SESSION['return'][] = array( - 'type' => 'success', + 'msg' => 'access_denied' + ); + return false; + } + } + + $password_new = $_data['user_new_pass']; + $password_new2 = $_data['user_new_pass2']; + if (password_check($password_new, $password_new2) !== true) { + return false; + } + $password_hashed = hash_password($password_new); + $stmt = $pdo->prepare("UPDATE `mailbox` SET `password` = :password_hashed, + `attributes` = JSON_SET(`attributes`, '$.force_pw_update', '0'), + `attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW()) + WHERE `username` = :username AND authsource = 'mailcow'"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username' => $username + )); + $_SESSION['pending_pw_update'] = false; + + update_sogo_static_view(); + } + // edit password recovery email + elseif (!empty($password_old) && isset($pw_recovery_email)) { + if (!isset($_SESSION['acl']['pw_reset']) || $_SESSION['acl']['pw_reset'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `username` = :user AND authsource = 'mailcow'"); + $stmt->execute(array(':user' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!verify_hash($row['password'], $password_old)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + + $pw_recovery_email = (!filter_var($pw_recovery_email, FILTER_VALIDATE_EMAIL)) ? '' : $pw_recovery_email; + $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email) + WHERE `username` = :username AND authsource = 'mailcow'"); + $stmt->execute(array( + ':recovery_email' => $pw_recovery_email, + ':username' => $username + )); + } + + $_SESSION['return'][] = array( + 'type' => 'success', 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('mailbox_modified', htmlspecialchars($username)) - ); + 'msg' => array('mailbox_modified', htmlspecialchars($username)) + ); } function user_get_alias_details($username) { - global $lang; - global $pdo; + global $pdo; + global $lang; + $data['direct_aliases'] = array(); + $data['shared_aliases'] = array(); if ($_SESSION['mailcow_cc_role'] == "user") { - $username = $_SESSION['mailcow_cc_username']; + $username = $_SESSION['mailcow_cc_username']; } if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { return false; } + if (!hasMailboxObjectAccess($username, $_SESSION['mailcow_cc_role'], $username)) { + return false; + } $data['address'] = $username; - $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '✘') AS `shared_aliases` FROM `alias` + $stmt = $pdo->prepare("SELECT `address` AS `shared_aliases`, `public_comment` FROM `alias` WHERE `goto` REGEXP :username_goto AND `address` NOT LIKE '@%' AND `goto` != :username_goto2 AND `address` != :username_address"); $stmt->execute(array( - ':username_goto' => '(^|,)'.$username.'($|,)', + ':username_goto' => '(^|,)'.preg_quote($username, '/').'($|,)', ':username_goto2' => $username, ':username_address' => $username )); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { - $data['shared_aliases'] = $row['shared_aliases']; + $data['shared_aliases'][$row['shared_aliases']]['public_comment'] = htmlspecialchars($row['public_comment']); + //$data['shared_aliases'][] = $row['shared_aliases']; } - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`address` SEPARATOR ', ') AS `direct_aliases` FROM `alias` + + $stmt = $pdo->prepare("SELECT `address` AS `direct_aliases`, `public_comment` FROM `alias` WHERE `goto` = :username_goto AND `address` NOT LIKE '@%' AND `address` != :username_address"); @@ -638,83 +1184,119 @@ function user_get_alias_details($username) { array( ':username_goto' => $username, ':username_address' => $username - )); + )); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { - $data['direct_aliases'][] = $row['direct_aliases']; + $data['direct_aliases'][$row['direct_aliases']]['public_comment'] = htmlspecialchars($row['public_comment']); } - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ', ') AS `ad_alias` FROM `mailbox` + $stmt = $pdo->prepare("SELECT CONCAT(local_part, '@', alias_domain) AS `ad_alias`, `alias_domain` FROM `mailbox` LEFT OUTER JOIN `alias_domain` on `target_domain` = `domain` WHERE `username` = :username ;"); $stmt->execute(array(':username' => $username)); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { - $data['direct_aliases'][] = $row['ad_alias']; + if (empty($row['ad_alias'])) { + continue; + } + $data['direct_aliases'][$row['ad_alias']]['public_comment'] = $lang['add']['alias_domain']; + $data['alias_domains'][] = $row['alias_domain']; } - $data['direct_aliases'] = implode(', ', array_filter($data['direct_aliases'])); - $data['direct_aliases'] = empty($data['direct_aliases']) ? '✘' : $data['direct_aliases']; - $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '✘') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';"); + $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';"); $stmt->execute(array(':username' => $username)); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { $data['aliases_also_send_as'] = $row['send_as']; } - $stmt = $pdo->prepare("SELECT IFNULL(CONCAT(GROUP_CONCAT(DISTINCT `send_as` SEPARATOR ', '), ', ', GROUP_CONCAT(DISTINCT CONCAT('@',`alias_domain`) SEPARATOR ', ')), '✘') AS `send_as` FROM `sender_acl` LEFT JOIN `alias_domain` ON `alias_domain`.`target_domain` = TRIM(LEADING '@' FROM `send_as`) WHERE `logged_in_as` = :username AND `send_as` LIKE '@%';"); + $stmt = $pdo->prepare("SELECT CONCAT_WS(', ', IFNULL(GROUP_CONCAT(DISTINCT `send_as` SEPARATOR ', '), ''), GROUP_CONCAT(DISTINCT CONCAT('@',`alias_domain`) SEPARATOR ', ')) AS `send_as` FROM `sender_acl` LEFT JOIN `alias_domain` ON `alias_domain`.`target_domain` = TRIM(LEADING '@' FROM `send_as`) WHERE `logged_in_as` = :username AND `send_as` LIKE '@%';"); $stmt->execute(array(':username' => $username)); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { $data['aliases_send_as_all'] = $row['send_as']; } - $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '✘') as `address` FROM `alias` WHERE `goto` REGEXP :username AND `address` LIKE '@%';"); - $stmt->execute(array(':username' => '(^|,)'.$username.'($|,)')); + $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '') as `address` FROM `alias` WHERE `goto` REGEXP :username AND `address` LIKE '@%';"); + $stmt->execute(array(':username' => '(^|,)'.preg_quote($username, '/').'($|,)')); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { $data['is_catch_all'] = $row['address']; } return $data; } -function is_valid_domain_name($domain_name) { - if (empty($domain_name)) { - return false; - } - $domain_name = idn_to_ascii($domain_name); - return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name) - && preg_match("/^.{1,253}$/", $domain_name) - && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name)); +function is_valid_domain_name($domain_name, $options = array()) { + if (empty($domain_name)) { + return false; + } + + // Convert domain name to ASCII for validation + $domain_name = idn_to_ascii($domain_name, 0, INTL_IDNA_VARIANT_UTS46); + + if (isset($options['allow_wildcard']) && $options['allow_wildcard'] == true) { + // Remove '*.' if wildcard subdomains are allowed + if (strpos($domain_name, '*.') === 0) { + $domain_name = substr($domain_name, 2); + } + } + + return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name) + && preg_match("/^.{1,253}$/", $domain_name) + && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name)); } function set_tfa($_data) { - global $lang; - global $pdo; - global $yubi; - global $u2f; - global $tfa; + global $pdo; + global $yubi; + global $tfa; + global $iam_settings; + $_data_log = $_data; !isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*'; - $username = $_SESSION['mailcow_cc_username']; - if ($_SESSION['mailcow_cc_role'] != "domainadmin" && - $_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); + + // skip password check if this is a forced TFA enrollment after login + if (!empty($_SESSION['pending_tfa_setup'])) { + $username = $_SESSION['mailcow_cc_username']; + if (empty($username) || !isset($_SESSION['mailcow_cc_role'])) { + $_SESSION['return'][] = array('type' => 'danger', 'log' => array(__FUNCTION__, $_data_log), 'msg' => 'access_denied'); return false; + } + } else { + $username = $_SESSION['mailcow_cc_username']; + $access_denied = null; + + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true; + + // check admin password + if ($access_denied === null) { + $stmt = $pdo->prepare("SELECT `password` FROM `admin` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row) { + if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true; + else $access_denied = false; + } + } + + // check mailbox password + if ($access_denied === null) { + $stmt = $pdo->prepare("SELECT `password`, `authsource` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row) { + if ($row['authsource'] == 'ldap'){ + if (!ldap_mbox_login($username, $_data["confirm_password"], $iam_settings)) $access_denied = true; + else $access_denied = false; + } else { + if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true; + else $access_denied = false; + } + } + } + + if ($access_denied) { + $_SESSION['return'][] = array('type' => 'danger', 'log' => array(__FUNCTION__, $_data_log), 'msg' => 'access_denied'); + return false; + } } - $stmt = $pdo->prepare("SELECT `password` FROM `admin` - WHERE `username` = :user"); - $stmt->execute(array(':user' => $username)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!verify_hash($row['password'], $_data["confirm_password"])) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - - switch ($_data["tfa_method"]) { - case "yubi_otp": + + switch ($_data["tfa_method"]) { + case "yubi_otp": $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; $yubico_id = $_data['yubico_id']; $yubico_key = $_data['yubico_key']; @@ -727,91 +1309,57 @@ function set_tfa($_data) { ); return false; } - if (!ctype_alnum($_data["otp_token"]) || strlen($_data["otp_token"]) != 44) { - $_SESSION['return'][] = array( - 'type' => 'danger', + if (!ctype_alnum($_data["otp_token"]) || strlen($_data["otp_token"]) != 44) { + $_SESSION['return'][] = array( + 'type' => 'danger', 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'tfa_token_invalid' - ); - return false; - } + 'msg' => 'tfa_token_invalid' + ); + return false; + } $yauth = $yubi->verify($_data["otp_token"]); if (PEAR::isError($yauth)) { - $_SESSION['return'][] = array( - 'type' => 'danger', + $_SESSION['return'][] = array( + 'type' => 'danger', 'log' => array(__FUNCTION__, $_data_log), 'msg' => array('yotp_verification_failed', $yauth->getMessage()) - ); - return false; + ); + return false; } - try { + try { // We could also do a modhex translation here $yubico_modhex_id = substr($_data["otp_token"], 0, 12); - $stmt = $pdo->prepare("DELETE FROM `tfa` + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username - AND (`authmech` != 'yubi_otp') - OR (`authmech` = 'yubi_otp' AND `secret` LIKE :modhex)"); - $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id)); - $stmt = $pdo->prepare("INSERT INTO `tfa` (`key_id`, `username`, `authmech`, `active`, `secret`) VALUES - (:key_id, :username, 'yubi_otp', '1', :secret)"); - $stmt->execute(array(':key_id' => $key_id, ':username' => $username, ':secret' => $yubico_id . ':' . $yubico_key . ':' . $yubico_modhex_id)); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('object_modified', htmlspecialchars($username)) - ); - break; - case "u2f": - $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; - try { - $reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($_data['token'])); - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'u2f'"); - $stmt->execute(array(':username' => $username)); - $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) VALUES (?, ?, 'u2f', ?, ?, ?, ?, '1')"); - $stmt->execute(array($username, $key_id, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter)); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('object_modified', $username) - ); - $_SESSION['regReq'] = null; + AND (`authmech` = 'yubi_otp' AND `secret` LIKE :modhex)"); + $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id)); + $stmt = $pdo->prepare("INSERT INTO `tfa` (`key_id`, `username`, `authmech`, `active`, `secret`) VALUES + (:key_id, :username, 'yubi_otp', '1', :secret)"); + $stmt->execute(array(':key_id' => $key_id, ':username' => $username, ':secret' => $yubico_id . ':' . $yubico_key . ':' . $yubico_modhex_id)); } - catch (Exception $e) { + catch (PDOException $e) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('u2f_verification_failed', $e->getMessage()) + 'msg' => array('mysql_error', $e) ); - $_SESSION['regReq'] = null; return false; } - break; - case "totp": + unset($_SESSION['pending_tfa_setup']); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('object_modified', htmlspecialchars($username)) + ); + break; + case "totp": $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; if ($tfa->verifyCode($_POST['totp_secret'], $_POST['totp_confirm_token']) === true) { - try { - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); + //$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); + //$stmt->execute(array(':username' => $username)); $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `secret`, `active`) VALUES (?, ?, 'totp', ?, '1')"); $stmt->execute(array($username, $key_id, $_POST['totp_secret'])); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } + unset($_SESSION['pending_tfa_setup']); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_data_log), @@ -825,445 +1373,2224 @@ function set_tfa($_data) { 'msg' => 'totp_verification_failed' ); } - break; - case "none": - try { - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', + break; + case "webauthn": + $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; + + $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) + VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')"); + $stmt->execute(array( + $username, + $key_id, + base64_encode($_data['registration']->credentialId), + $_data['registration']->credentialPublicKey, + $_data['registration']->certificate, + 0 + )); + + unset($_SESSION['pending_tfa_setup']); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('object_modified', $username) + ); + break; + case "none": + // Block TFA removal if force_tfa policy is active + $is_forced_tfa = false; + if ($_SESSION['mailcow_cc_role'] === 'user') { + $stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?"); + $stmt_check->execute(array($username)); + $is_forced_tfa = ($stmt_check->fetchColumn() == '1'); + } else { + $stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?"); + $stmt_check->execute(array($username)); + $is_forced_tfa = ($stmt_check->fetchColumn() == '1'); + } + if ($is_forced_tfa) { + $_SESSION['return'][] = array( + 'type' => 'danger', 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('object_modified', htmlspecialchars($username)) - ); - break; - } -} -function unset_tfa_key($_data) { - // Can only unset own keys - // Needs at least one key left - global $pdo; - global $lang; - $_data_log = $_data; - $id = intval($_data['unset_tfa_key']); - $username = $_SESSION['mailcow_cc_username']; - if ($_SESSION['mailcow_cc_role'] != "domainadmin" && - $_SESSION['mailcow_cc_role'] != "admin") { + 'msg' => 'tfa_removal_blocked' + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); $_SESSION['return'][] = array( - 'type' => 'danger', + 'type' => 'success', 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' + 'msg' => array('object_modified', htmlspecialchars($username)) ); - return false; + break; } - try { - if (!is_numeric($id)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' +} +function fido2($_data) { + global $pdo; + global $WebAuthn; + $_data_log = $_data; + // Not logging registration data, only actions + // Silent errors for "get" requests + switch ($_data["action"]) { + case "register": + $username = $_SESSION['mailcow_cc_username']; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data["action"]), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("INSERT INTO `fido2` (`username`, `rpId`, `credentialPublicKey`, `certificateChain`, `certificate`, `certificateIssuer`, `certificateSubject`, `signatureCounter`, `AAGUID`, `credentialId`) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute(array( + $username, + $_data['registration']->rpId, + $_data['registration']->credentialPublicKey, + $_data['registration']->certificateChain, + $_data['registration']->certificate, + $_data['registration']->certificateIssuer, + $_data['registration']->certificateSubject, + $_data['registration']->signatureCounter, + $_data['registration']->AAGUID, + $_data['registration']->credentialId) ); - return false; - } - $stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa` - WHERE `username` = :username AND `active` = '1'"); - $stmt->execute(array(':username' => $username)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if ($row['keys'] == "1") { $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'last_key' + 'type' => 'success', + 'log' => array(__FUNCTION__, $_data["action"]), + 'msg' => array('object_modified', $username) ); - return false; - } - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `id` = :id"); - $stmt->execute(array(':username' => $username, ':id' => $id)); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('object_modified', $username) - ); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } -} -function get_tfa($username = null) { - global $pdo; - if (isset($_SESSION['mailcow_cc_username'])) { - $username = $_SESSION['mailcow_cc_username']; - } - elseif (empty($username)) { - return false; - } - $stmt = $pdo->prepare("SELECT * FROM `tfa` - WHERE `username` = :username AND `active` = '1'"); - $stmt->execute(array(':username' => $username)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - - switch ($row["authmech"]) { - case "yubi_otp": - $data['name'] = "yubi_otp"; - $data['pretty'] = "Yubico OTP"; - $stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); + break; + case "get_user_cids": + // Used to exclude existing CredentialIds while registering + $username = $_SESSION['mailcow_cc_username']; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + return false; + } + $stmt = $pdo->prepare("SELECT `credentialId` FROM `fido2` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { - $data['additional'][] = $row; + $cids[] = $row['credentialId']; } - return $data; + return $cids; break; - case "u2f": - $data['name'] = "u2f"; - $data['pretty'] = "Fido U2F"; - $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); + case "get_all_cids": + // Only needed when using fido2 with username + $stmt = $pdo->query("SELECT `credentialId` FROM `fido2`"); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { - $data['additional'][] = $row; + $cids[] = $row['credentialId']; } - return $data; + return $cids; break; - case "hotp": - $data['name'] = "hotp"; - $data['pretty'] = "HMAC-based OTP"; + case "get_by_b64cid": + if (!isset($_data['cid']) || empty($_data['cid'])) { + return false; + } + $stmt = $pdo->prepare("SELECT `certificateSubject`, `username`, `credentialPublicKey`, SHA2(`credentialId`, 256) AS `cid` FROM `fido2` WHERE `credentialId` = :cid"); + $stmt->execute(array(':cid' => base64_decode($_data['cid']))); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row) || empty($row['credentialPublicKey']) || empty($row['username'])) { + return false; + } + $data['pub_key'] = $row['credentialPublicKey']; + $data['username'] = $row['username']; + $data['subject'] = $row['certificateSubject']; + $data['cid'] = $row['cid']; return $data; - break; - case "totp": - $data['name'] = "totp"; - $data['pretty'] = "Time-based OTP"; - $stmt = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username"); + break; + case "get_friendly_names": + $username = $_SESSION['mailcow_cc_username']; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + return false; + } + $stmt = $pdo->prepare("SELECT SHA2(`credentialId`, 256) AS `cid`, `created`, `certificateSubject`, `friendlyName` FROM `fido2` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $fns[] = array( + "subject" => (empty($row['certificateSubject']) ? 'Unknown (' . $row['created'] . ')' : $row['certificateSubject']), + "fn" => $row['friendlyName'], + "cid" => $row['cid'] + ); + } + return $fns; + break; + case "unset_fido2_key": + $username = $_SESSION['mailcow_cc_username']; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data["action"]), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username AND SHA2(`credentialId`, 256) = :cid"); $stmt->execute(array( ':username' => $username, + ':cid' => $_data['post_data']['unset_fido2_key'] )); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $data['additional'][] = $row; + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('object_modified', htmlspecialchars($username)) + ); + break; + case "edit_fn": + $username = $_SESSION['mailcow_cc_username']; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data["action"]), + 'msg' => 'access_denied' + ); + return false; } - return $data; - break; - default: - $data['name'] = 'none'; - $data['pretty'] = "-"; - return $data; + $stmt = $pdo->prepare("UPDATE `fido2` SET `friendlyName` = :friendlyName WHERE SHA2(`credentialId`, 256) = :cid AND `username` = :username"); + $stmt->execute(array( + ':username' => $username, + ':friendlyName' => $_data['fido2_attrs']['fido2_fn'], + ':cid' => $_data['fido2_attrs']['fido2_cid'] + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('object_modified', htmlspecialchars($username)) + ); break; - } -} -function verify_tfa_login($username, $token) { - global $pdo; - global $lang; - global $yubi; - global $u2f; - global $tfa; - $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa` - WHERE `username` = :username AND `active` = '1'"); - $stmt->execute(array(':username' => $username)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - - switch ($row["authmech"]) { - case "yubi_otp": - if (!ctype_alnum($token) || strlen($token) != 44) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => array('yotp_verification_failed', 'token length error') - ); + case "verify": + $role = ""; + $tokenData = json_decode($_data['token']); + $clientDataJSON = base64_decode($tokenData->clientDataJSON); + $authenticatorData = base64_decode($tokenData->authenticatorData); + $signature = base64_decode($tokenData->signature); + $id = base64_decode($tokenData->id); + $challenge = $_SESSION['challenge']; + $process_fido2 = fido2(array("action" => "get_by_b64cid", "cid" => $tokenData->id)); + if ($process_fido2['pub_key'] === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array("fido2_login", $_data['user'], $process_fido2['username']), + 'msg' => "login_failed" + ); return false; } - $yubico_modhex_id = substr($token, 0, 12); - $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` - WHERE `username` = :username - AND `authmech` = 'yubi_otp' - AND `active`='1' - AND `secret` LIKE :modhex"); - $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $yubico_auth = explode(':', $row['secret']); - $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]); - $yauth = $yubi->verify($token); - if (PEAR::isError($yauth)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => array('yotp_verification_failed', $yauth->getMessage()) - ); - return false; + try { + $WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $process_fido2['pub_key'], $challenge, null, $GLOBALS['FIDO2_UV_FLAG_LOGIN'], $GLOBALS['FIDO2_USER_PRESENT_FLAG']); + } + catch (Throwable $ex) { + unset($process_fido2); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array("fido2_login", $_data['user'], $process_fido2['username'], $ex->getMessage()), + 'msg' => "login_failed" + ); + return false; + } + $return = new stdClass(); + $return->success = true; + $stmt = $pdo->prepare("SELECT `superadmin` FROM `admin` WHERE `username` = :username"); + $stmt->execute(array(':username' => $process_fido2['username'])); + $obj_props = $stmt->fetch(PDO::FETCH_ASSOC); + if ($obj_props['superadmin'] === 1 && (!$_data['user'] || $_data['user'] == "admin")) { + $role = "admin"; + } + elseif ($obj_props['superadmin'] === 0 && (!$_data['user'] || $_data['user'] == "domainadmin")) { + $role = "domainadmin"; + } + elseif (!isset($obj_props['superadmin']) && (!$_data['user'] || $_data['user'] == "user")) { + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $process_fido2['username'])); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row['username'] == $process_fido2['username']) { + $role = "user"; + } } else { - $_SESSION['tfa_id'] = $row['id']; - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => 'verified_yotp_login' - ); - return true; + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array("fido2_login", $_data['user'], $process_fido2['username']), + 'msg' => 'login_failed' + ); + return false; } + if (empty($role)) { + session_unset(); + session_destroy(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array("fido2_login", $_data['user'], $process_fido2['username']), + 'msg' => 'login_failed' + ); + return false; + } + unset($_SESSION["challenge"]); $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array("fido2_login", $_data['user'], $process_fido2['username']), + 'msg' => array('logged_in_as', $process_fido2['username']) + ); + return array( + "role" => $role, + "username" => $process_fido2['username'], + "cid" => $process_fido2['cid'] + ); + break; + } +} +function unset_tfa_key($_data) { + // Can only unset own keys + // Needs at least one key left + global $pdo; + global $lang; + $_data_log = $_data; + $access_denied = null; + $id = intval($_data['unset_tfa_key']); + $username = $_SESSION['mailcow_cc_username']; + + // check for empty user and role + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true; + + try { + if (!is_numeric($id)) $access_denied = true; + + // set access_denied error + if ($access_denied){ + $_SESSION['return'][] = array( 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => array('yotp_verification_failed', 'unknown') + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'access_denied' ); - return false; - break; - case "u2f": - try { - $reg = $u2f->doAuthenticate(json_decode($_SESSION['authReq']), get_u2f_registrations($username), json_decode($token)); - $stmt = $pdo->prepare("UPDATE `tfa` SET `counter` = ? WHERE `id` = ?"); - $stmt->execute(array($reg->counter, $reg->id)); - $_SESSION['tfa_id'] = $reg->id; - $_SESSION['authReq'] = null; + return false; + } + + // Block key removal if force_tfa policy is active + $is_forced_tfa = false; + if ($_SESSION['mailcow_cc_role'] === 'user') { + $stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `mailbox` WHERE `username` = ?"); + $stmt_check->execute(array($username)); + $is_forced_tfa = ($stmt_check->fetchColumn() == '1'); + } else { + $stmt_check = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.force_tfa') FROM `admin` WHERE `username` = ?"); + $stmt_check->execute(array($username)); + $is_forced_tfa = ($stmt_check->fetchColumn() == '1'); + } + if ($is_forced_tfa) { $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => 'verified_u2f_login' + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'tfa_removal_blocked' ); - return true; + return false; } - catch (Exception $e) { + + // check if it's last key + $stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa` + WHERE `username` = :username AND `active` = '1'"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row['keys'] == "1") { $_SESSION['return'][] = array( 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => array('u2f_verification_failed', $e->getMessage()) + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'last_key' ); - $_SESSION['regReq'] = null; return false; } + + // delete key + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `id` = :id"); + $stmt->execute(array(':username' => $username, ':id' => $id)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('object_modified', $username) + ); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('mysql_error', $e) + ); + return false; + } +} +function tfa_exists($username) { + global $pdo; + if (empty($username)) { + return false; + } + $stmt = $pdo->prepare("SELECT COUNT(*) as count FROM `tfa` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + return $stmt->fetch(PDO::FETCH_ASSOC)['count'] > 0; +} +function get_tfa($username = null, $id = null) { + global $pdo; + if (empty($username) && isset($_SESSION['mailcow_cc_username'])) { + $username = $_SESSION['mailcow_cc_username']; + } + elseif (empty($username)) { + return false; + } + + if (!isset($id)){ + // fetch all tfa methods - just get information about possible authenticators + $stmt = $pdo->prepare("SELECT `id`, `key_id`, `authmech` FROM `tfa` + WHERE `username` = :username AND `active` = '1'"); + $stmt->execute(array(':username' => $username)); + $results = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // no tfa methods found + if (count($results) == 0) { + $data['name'] = 'none'; + $data['pretty'] = "-"; + $data['additional'] = array(); + return $data; + } + + $data['additional'] = $results; + return $data; + } else { + // fetch specific authenticator details by id + $stmt = $pdo->prepare("SELECT * FROM `tfa` + WHERE `username` = :username AND `id` = :id AND `active` = '1'"); + $stmt->execute(array(':username' => $username, ':id' => $id)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + if (isset($row["authmech"])) { + switch ($row["authmech"]) { + case "yubi_otp": + $data['name'] = "yubi_otp"; + $data['pretty'] = "Yubico OTP"; + $stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username AND `id` = :id"); + $stmt->execute(array( + ':username' => $username, + ':id' => $id + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $data['additional'][] = $row; + } + return $data; + break; + // u2f - deprecated, should be removed + case "u2f": + $data['name'] = "u2f"; + $data['pretty'] = "Fido U2F"; + $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username AND `id` = :id"); + $stmt->execute(array( + ':username' => $username, + ':id' => $id + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $data['additional'][] = $row; + } + return $data; + break; + case "hotp": + $data['name'] = "hotp"; + $data['pretty'] = "HMAC-based OTP"; + return $data; + break; + case "totp": + $data['name'] = "totp"; + $data['pretty'] = "Time-based OTP"; + $stmt = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username AND `id` = :id"); + $stmt->execute(array( + ':username' => $username, + ':id' => $id + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $data['additional'][] = $row; + } + return $data; + break; + case "webauthn": + $data['name'] = "webauthn"; + $data['pretty'] = "WebAuthn"; + $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' AND `username` = :username AND `id` = :id"); + $stmt->execute(array( + ':username' => $username, + ':id' => $id + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $data['additional'][] = $row; + } + return $data; + break; + default: + $data['name'] = 'none'; + $data['pretty'] = "-"; + return $data; + break; + } + } + else { + $data['name'] = 'none'; + $data['pretty'] = "-"; + return $data; + } + } +} +function verify_tfa_login($username, $_data) { + global $pdo; + global $yubi; + global $u2f; + global $tfa; + global $WebAuthn; + + if ($_data['tfa_method'] != 'u2f'){ + + switch ($_data["tfa_method"]) { + case "yubi_otp": + if (!ctype_alnum($_data['token']) || strlen($_data['token']) != 44) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('yotp_verification_failed', 'token length error') + ); + return false; + } + $yubico_modhex_id = substr($_data['token'], 0, 12); + $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` + WHERE `username` = :username + AND `authmech` = 'yubi_otp' + AND `active` = '1' + AND `secret` LIKE :modhex"); + $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $yubico_auth = explode(':', $row['secret']); + $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]); + $yauth = $yubi->verify($_data['token']); + if (PEAR::isError($yauth)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('yotp_verification_failed', $yauth->getMessage()) + ); + return false; + } + else { + $_SESSION['tfa_id'] = $row['id']; + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => 'verified_yotp_login' + ); + return true; + } + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('yotp_verification_failed', 'unknown') + ); + return false; + break; + case "hotp": + return false; + break; + case "totp": + try { + $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` + WHERE `username` = :username + AND `authmech` = 'totp' + AND `id` = :id + AND `active`='1'"); + $stmt->execute(array(':username' => $username, ':id' => $_data['id'])); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($rows as $row) { + if ($tfa->verifyCode($row['secret'], $_data['token']) === true) { + $_SESSION['tfa_id'] = $row['id']; + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => 'verified_totp_login' + ); + return true; + } + } + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => 'totp_verification_failed' + ); + return false; + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('mysql_error', $e) + ); + return false; + } + break; + case "webauthn": + $tokenData = json_decode($_data['token']); + $clientDataJSON = base64_decode($tokenData->clientDataJSON); + $authenticatorData = base64_decode($tokenData->authenticatorData); + $signature = base64_decode($tokenData->signature); + $id = base64_decode($tokenData->id); + $challenge = $_SESSION['challenge']; + + $stmt = $pdo->prepare("SELECT `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `id` = :id AND `active`='1'"); + $stmt->execute(array(':id' => $_data['id'])); + $process_webauthn = $stmt->fetch(PDO::FETCH_ASSOC); + + if (empty($process_webauthn)){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('webauthn_authenticator_failed') + ); + return false; + } + + if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('webauthn_publickey_failed') + ); + return false; + } + + if ($process_webauthn['username'] != $_SESSION['pending_mailcow_cc_username']){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('webauthn_username_failed') + ); + return false; + } + + try { + $WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $process_webauthn['publicKey'], $challenge, null, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN'], $GLOBALS['WEBAUTHN_USER_PRESENT_FLAG']); + } + catch (Throwable $ex) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('webauthn_verification_failed', $ex->getMessage()) + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `superadmin` FROM `admin` WHERE `username` = :username"); + $stmt->execute(array(':username' => $process_webauthn['username'])); + $obj_props = $stmt->fetch(PDO::FETCH_ASSOC); + if ($obj_props['superadmin'] === 1) { + $_SESSION["mailcow_cc_role"] = "admin"; + } + elseif ($obj_props['superadmin'] === 0) { + $_SESSION["mailcow_cc_role"] = "domainadmin"; + } + else { + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $process_webauthn['username'])); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($row['username'])) { + $_SESSION["mailcow_cc_role"] = "user"; + } else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('webauthn_role_failed') + ); + return false; + } + } + + $_SESSION["mailcow_cc_username"] = $process_webauthn['username']; + $_SESSION['tfa_id'] = $process_webauthn['id']; + $_SESSION['authReq'] = null; + unset($_SESSION["challenge"]); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array("webauthn_login"), + 'msg' => array('logged_in_as', $process_webauthn['username']) + ); + return true; + break; + default: + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => 'unknown_tfa_method' + ); + return false; + break; + } + + return false; + } else { + // delete old keys that used u2f + $stmt = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username"); + $stmt->execute(array(':username' => $username)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + if (count($rows) == 0) return false; + + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username"); + $stmt->execute(array(':username' => $username)); + return true; + } +} +function admin_api($access, $action, $data = null) { + global $pdo; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'access_denied' + ); + return false; + } + if ($access !== "ro" && $access !== "rw") { $_SESSION['return'][] = array( 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => array('u2f_verification_failed', 'unknown') + 'log' => array(__FUNCTION__), + 'msg' => 'invalid access type' ); return false; - break; - case "hotp": + } + if ($action == "edit") { + $active = (!empty($data['active'])) ? 1 : 0; + $skip_ip_check = (isset($data['skip_ip_check'])) ? 1 : 0; + $allow_from = array(); + if (isset($data['allow_from'])) { + $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from'])); + } + foreach ($allow_from as $key => $val) { + if (empty($val)) { + unset($allow_from[$key]); + continue; + } + if (valid_network($val) !== true) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $data), + 'msg' => array('ip_invalid', htmlspecialchars($allow_from[$key])) + ); + unset($allow_from[$key]); + continue; + } + } + $allow_from = implode(',', array_unique(array_filter($allow_from))); + if (empty($allow_from) && $skip_ip_check == 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $data), + 'msg' => 'ip_list_empty' + ); return false; - break; - case "totp": - try { - $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` - WHERE `username` = :username - AND `authmech` = 'totp' - AND `active`='1'"); + } + $api_key = implode('-', array( + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))) + )); + $stmt = $pdo->query("SELECT `api_key` FROM `api` WHERE `access` = '" . $access . "'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if (empty($num_results)) { + $stmt = $pdo->prepare("INSERT INTO `api` (`api_key`, `skip_ip_check`, `active`, `allow_from`, `access`) + VALUES (:api_key, :skip_ip_check, :active, :allow_from, :access);"); + $stmt->execute(array( + ':api_key' => $api_key, + ':skip_ip_check' => $skip_ip_check, + ':active' => $active, + ':allow_from' => $allow_from, + ':access' => $access + )); + } + else { + if ($skip_ip_check == 0) { + $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, + `active` = :active, + `allow_from` = :allow_from + WHERE `access` = :access;"); + $stmt->execute(array( + ':active' => $active, + ':skip_ip_check' => $skip_ip_check, + ':allow_from' => $allow_from, + ':access' => $access + )); + } + else { + $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, + `active` = :active + WHERE `access` = :access;"); + $stmt->execute(array( + ':active' => $active, + ':skip_ip_check' => $skip_ip_check, + ':access' => $access + )); + } + } + } + elseif ($action == "regen_key") { + $api_key = implode('-', array( + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))) + )); + $stmt = $pdo->prepare("UPDATE `api` SET `api_key` = :api_key WHERE `access` = :access"); + $stmt->execute(array( + ':api_key' => $api_key, + ':access' => $access + )); + } + elseif ($action == "get") { + $stmt = $pdo->query("SELECT * FROM `api` WHERE `access` = '" . $access . "'"); + $apidata = $stmt->fetch(PDO::FETCH_ASSOC); + if ($apidata !== false) { + $apidata['allow_from'] = str_replace(',', PHP_EOL, $apidata['allow_from']); + } + return $apidata; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $data), + 'msg' => 'admin_api_modified' + ); +} +function license($action, $data = null) { + global $pdo; + global $redis; + global $lang; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'access_denied' + ); + return false; + } + switch ($action) { + case "verify": + // Keep result until revalidate button is pressed or session expired + $stmt = $pdo->query("SELECT `version` FROM `versions` WHERE `application` = 'GUID'"); + $versions = $stmt->fetch(PDO::FETCH_ASSOC); + $post = array('guid' => $versions['version']); + $curl = curl_init('https://verify.mailcow.email'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($curl, CURLOPT_POSTFIELDS, $post); + $response = curl_exec($curl); + curl_close($curl); + $json_return = json_decode($response, true); + if ($response && $json_return) { + if ($json_return['response'] === "ok") { + $_SESSION['gal']['valid'] = "true"; + $_SESSION['gal']['c'] = $json_return['c']; + $_SESSION['gal']['s'] = $json_return['s']; + if ($json_return['m'] == 'NoMoore') { + $_SESSION['gal']['m'] = '๐Ÿ„'; + } + else { + $_SESSION['gal']['m'] = str_repeat('๐Ÿ„', substr_count($json_return['m'], 'o')); + } + } + elseif ($json_return['response'] === "invalid") { + $_SESSION['gal']['valid'] = "false"; + $_SESSION['gal']['c'] = $lang['mailbox']['no']; + $_SESSION['gal']['s'] = $lang['mailbox']['no']; + $_SESSION['gal']['m'] = $lang['mailbox']['no']; + } + } + else { + $_SESSION['gal']['valid'] = "false"; + $_SESSION['gal']['c'] = $lang['danger']['temp_error']; + $_SESSION['gal']['s'] = $lang['danger']['temp_error']; + $_SESSION['gal']['m'] = $lang['danger']['temp_error']; + } + try { + // json_encode needs "true"/"false" instead of true/false, to not encode it to 0 or 1 + $redis->Set('LICENSE_STATUS_CACHE', json_encode($_SESSION['gal'])); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return $_SESSION['gal']['valid']; + break; + case "guid": + $stmt = $pdo->query("SELECT `version` FROM `versions` WHERE `application` = 'GUID'"); + $versions = $stmt->fetch(PDO::FETCH_ASSOC); + return $versions['version']; + break; + } +} +function rspamd_ui($action, $data = null) { + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'access_denied' + ); + return false; + } + switch ($action) { + case "edit": + $rspamd_ui_pass = $data['rspamd_ui_pass']; + $rspamd_ui_pass2 = $data['rspamd_ui_pass2']; + if (empty($rspamd_ui_pass) || empty($rspamd_ui_pass2)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, '*', '*'), + 'msg' => 'password_empty' + ); + return false; + } + if ($rspamd_ui_pass != $rspamd_ui_pass2) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, '*', '*'), + 'msg' => 'password_mismatch' + ); + return false; + } + if (strlen($rspamd_ui_pass) < 6) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, '*', '*'), + 'msg' => 'rspamd_ui_pw_length' + ); + return false; + } + $docker_return = docker('post', 'rspamd-mailcow', 'exec', array('cmd' => 'rspamd', 'task' => 'worker_password', 'raw' => $rspamd_ui_pass), array('Content-Type: application/json')); + if ($docker_return_array = json_decode($docker_return, true)) { + if ($docker_return_array['type'] == 'success') { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, '*', '*'), + 'msg' => 'rspamd_ui_pw_set' + ); + return true; + } + else { + $_SESSION['return'][] = array( + 'type' => $docker_return_array['type'], + 'log' => array(__FUNCTION__, '*', '*'), + 'msg' => $docker_return_array['msg'] + ); + return false; + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, '*', '*'), + 'msg' => 'unknown' + ); + return false; + } + break; + } +} +function cors($action, $data = null) { + global $redis; + + switch ($action) { + case "edit": + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $action, $data), + 'msg' => 'access_denied' + ); + return false; + } + + $allowed_origins = isset($data['allowed_origins']) ? $data['allowed_origins'] : array(getBaseURL()); + $allowed_origins = !is_array($allowed_origins) ? array_filter(array_map('trim', explode("\n", $allowed_origins))) : $allowed_origins; + foreach ($allowed_origins as &$origin) { + if ($origin === '*') { + continue; + } + if (!valid_origin($origin)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $action, $data), + 'msg' => 'cors_invalid_origin' + ); + return false; + } + // browsers always send a lowercase scheme/host in the Origin header, so normalize to match + $origin = strtolower($origin); + } + unset($origin); + + $allowed_methods = isset($data['allowed_methods']) ? $data['allowed_methods'] : array('GET', 'POST', 'PUT', 'DELETE'); + $allowed_methods = !is_array($allowed_methods) ? array_map('trim', preg_split( "/( |,|;|\n)/", $allowed_methods)) : $allowed_methods; + $available_methods = array('GET', 'POST', 'PUT', 'DELETE'); + foreach ($allowed_methods as $method) { + if (!in_array($method, $available_methods)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $action, $data), + 'msg' => 'cors_invalid_method' + ); + return false; + } + } + + try { + $redis->hMSet('CORS_SETTINGS', array( + 'allowed_origins' => implode(', ', $allowed_origins), + 'allowed_methods' => implode(', ', $allowed_methods) + )); + } catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $action, $data), + 'msg' => array('redis_error', $e) + ); + return false; + } + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $action, $data), + 'msg' => 'cors_headers_edited' + ); + return true; + break; + case "get": + try { + $cors_settings = $redis->hMGet('CORS_SETTINGS', array('allowed_origins', 'allowed_methods')); + } catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $action, $data), + 'msg' => array('redis_error', $e) + ); + } + + $cors_settings = !$cors_settings ? array('allowed_origins' => getBaseURL(), 'allowed_methods' => 'GET, POST, PUT, DELETE') : $cors_settings; + $cors_settings['allowed_origins'] = empty($cors_settings['allowed_origins']) ? getBaseURL() : $cors_settings['allowed_origins']; + $cors_settings['allowed_methods'] = empty($cors_settings['allowed_methods']) ? 'GET, POST, PUT, DELETE, OPTION' : $cors_settings['allowed_methods']; + + return $cors_settings; + break; + case "set_headers": + $cors_settings = cors('get'); + // normalize the stored list; it may still hold bare hostnames written before origins were validated as origins + $allowed_origins = array_filter(array_map('normalize_cors_origin', explode(',', $cors_settings['allowed_origins']))); + $origin = isset($_SERVER['HTTP_ORIGIN']) ? normalize_cors_origin($_SERVER['HTTP_ORIGIN']) : false; + // check if requested origin is in allowed origins + $allow_origin = null; + if (in_array('*', $allowed_origins, true)) { + $allow_origin = '*'; + } else if ($origin !== false && in_array($origin, $allowed_origins, true)) { + $allow_origin = $origin; + } + // always allow OPTIONS for preflight request + $cors_settings["allowed_methods"] = empty($cors_settings["allowed_methods"]) ? 'OPTIONS' : $cors_settings["allowed_methods"] . ', ' . 'OPTIONS'; + + // a disallowed origin gets no Access-Control-Allow-Origin at all; echoing a different origin than the requesting one tells a browser nothing + if ($allow_origin !== null) { + header('Access-Control-Allow-Origin: ' . $allow_origin); + } + if ($allow_origin !== '*') { + // the response depends on the request origin, keep caches from mixing them up + header('Vary: Origin', false); + } + header('Access-Control-Allow-Methods: '. $cors_settings['allowed_methods']); + header('Access-Control-Allow-Headers: Accept, Content-Type, X-Api-Key, Origin'); + + // Access-Control settings requested, this is just a preflight request + if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && + isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) && + isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) { + + $allowed_methods = explode(', ', $cors_settings["allowed_methods"]); + if (in_array($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'], $allowed_methods, true)) + // method allowed send 200 OK + http_response_code(200); + else + // method not allowed send 405 METHOD NOT ALLOWED + http_response_code(405); + + exit; + } + break; + } +} +function getBaseURL($protocol = null) { + // Get current server name + $host = strtolower($_SERVER['SERVER_NAME']); + + // craft allowed server name list + $mailcow_hostname = strtolower(getenv("MAILCOW_HOSTNAME")); + $additional_server_names = strtolower(getenv("ADDITIONAL_SERVER_NAMES")) ?: ""; + $additional_server_names = preg_replace('/\s+/', '', $additional_server_names); + $allowed_server_names = $additional_server_names !== "" ? explode(',', $additional_server_names) : array(); + array_push($allowed_server_names, $mailcow_hostname); + + // Fallback to MAILCOW HOSTNAME if current server name is not in allowed list + if (!in_array($host, $allowed_server_names)) { + $host = $mailcow_hostname; + } + + if (!isset($protocol)) { + $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http'; + } + $base_url = $protocol . '://' . $host; + + return $base_url; +} +function uuid4() { + $data = openssl_random_pseudo_bytes(16); + + $data[6] = chr(ord($data[6]) & 0x0f | 0x40); + $data[8] = chr(ord($data[8]) & 0x3f | 0x80); + + return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); +} +function identity_provider($_action = null, $_data = null, $_extra = null) { + global $pdo; + global $iam_provider; + global $iam_settings; + + $data_log = $_data; + if (isset($data_log['client_secret'])) $data_log['client_secret'] = '*'; + if (isset($data_log['access_token'])) $data_log['access_token'] = '*'; + + switch ($_action) { + case 'get': + $settings = array(); + $stmt = $pdo->prepare("SELECT * FROM `identity_provider`;"); + $stmt->execute(); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach($rows as $row){ + switch ($row["key"]) { + case "redirect_url_extra": + case "mappers": + case "templates": + $settings[$row["key"]] = json_decode($row["value"]); + break; + case "use_ssl": + case "use_tls": + case "login_provisioning": + case "ignore_ssl_errors": + $settings[$row["key"]] = boolval($row["value"]); + break; + default: + $settings[$row["key"]] = $row["value"]; + break; + } + } + // set login_provisioning if not exists + if (!array_key_exists('login_provisioning', $settings)) { + $settings['login_provisioning'] = 1; + } + // return default client_scopes for generic-oidc if none is set + if ($settings["authsource"] == "generic-oidc" && empty($settings["client_scopes"])){ + $settings["client_scopes"] = "openid profile email mailcow_template"; + } + if ($_extra['hide_sensitive']){ + $settings['client_secret'] = ''; + $settings['access_token'] = ''; + } + // return default ldap options + if ($settings["authsource"] == "ldap"){ + $settings['use_ssl'] = !isset($settings['use_ssl']) ? false : $settings['use_ssl']; + $settings['use_tls'] = !isset($settings['use_tls']) ? false : $settings['use_tls']; + $settings['ignore_ssl_errors'] = !isset($settings['ignore_ssl_errors']) ? false : $settings['ignore_ssl_errors']; + } + return $settings; + break; + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + if (!isset($_data['authsource'])){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $data_log), + 'msg' => array('required_data_missing', '') + ); + return false; + } + + $available_authsources = array( + "keycloak", + "generic-oidc", + "ldap" + ); + $_data['authsource'] = strtolower($_data['authsource']); + if (!in_array($_data['authsource'], $available_authsources)){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $data_log), + 'msg' => array('invalid_authsource', $setting) + ); + return false; + } + + $stmt = $pdo->prepare("SELECT * FROM `mailbox` + WHERE `authsource` != 'mailcow' + AND `authsource` IS NOT NULL + AND `authsource` != :authsource"); + $stmt->execute(array(':authsource' => $_data['authsource'])); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + if ($rows) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $data_log), + 'msg' => array('authsource_in_use', $setting) + ); + return false; + } + + $_data['ignore_ssl_error'] = isset($_data['ignore_ssl_error']) ? boolval($_data['ignore_ssl_error']) : false; + $_data['login_provisioning'] = isset($_data['login_provisioning']) ? boolval($_data['login_provisioning']) : false; + switch ($_data['authsource']) { + case "keycloak": + $_data['server_url'] = (!empty($_data['server_url'])) ? rtrim($_data['server_url'], '/') : null; + $_data['mailpassword_flow'] = isset($_data['mailpassword_flow']) ? intval($_data['mailpassword_flow']) : 0; + $_data['periodic_sync'] = isset($_data['periodic_sync']) ? intval($_data['periodic_sync']) : 0; + $_data['import_users'] = isset($_data['import_users']) ? intval($_data['import_users']) : 0; + $_data['sync_interval'] = (!empty($_data['sync_interval'])) ? intval($_data['sync_interval']) : 15; + $_data['sync_interval'] = $_data['sync_interval'] < 1 ? 1 : $_data['sync_interval']; + $required_settings = array('authsource', 'server_url', 'realm', 'client_id', 'client_secret', 'redirect_url', 'version', 'mailpassword_flow', 'periodic_sync', 'import_users', 'sync_interval', 'ignore_ssl_error', 'login_provisioning'); + break; + case "generic-oidc": + $_data['authorize_url'] = (!empty($_data['authorize_url'])) ? $_data['authorize_url'] : null; + $_data['token_url'] = (!empty($_data['token_url'])) ? $_data['token_url'] : null; + $_data['userinfo_url'] = (!empty($_data['userinfo_url'])) ? $_data['userinfo_url'] : null; + $_data['client_scopes'] = (!empty($_data['client_scopes'])) ? $_data['client_scopes'] : "openid profile email mailcow_template"; + $required_settings = array('authsource', 'authorize_url', 'token_url', 'client_id', 'client_secret', 'redirect_url', 'userinfo_url', 'client_scopes', 'ignore_ssl_error', 'login_provisioning'); + break; + case "ldap": + $_data['host'] = (!empty($_data['host'])) ? str_replace(" ", "", $_data['host']) : ""; + $_data['port'] = (!empty($_data['port'])) ? intval($_data['port']) : 389; + $_data['username_field'] = (!empty($_data['username_field'])) ? strtolower($_data['username_field']) : "mail"; + $_data['attribute_field'] = (!empty($_data['attribute_field'])) ? strtolower($_data['attribute_field']) : ""; + $_data['filter'] = (!empty($_data['filter'])) ? $_data['filter'] : ""; + $_data['periodic_sync'] = isset($_data['periodic_sync']) ? intval($_data['periodic_sync']) : 0; + $_data['import_users'] = isset($_data['import_users']) ? intval($_data['import_users']) : 0; + $_data['use_ssl'] = isset($_data['use_ssl']) ? boolval($_data['use_ssl']) : false; + $_data['use_tls'] = isset($_data['use_tls']) && !$_data['use_ssl'] ? boolval($_data['use_tls']) : false; + $_data['sync_interval'] = (!empty($_data['sync_interval'])) ? intval($_data['sync_interval']) : 15; + $_data['sync_interval'] = $_data['sync_interval'] < 1 ? 1 : $_data['sync_interval']; + $required_settings = array('authsource', 'host', 'port', 'basedn', 'username_field', 'filter', 'attribute_field', 'binddn', 'bindpass', 'periodic_sync', 'import_users', 'sync_interval', 'use_ssl', 'use_tls', 'ignore_ssl_error', 'login_provisioning'); + break; + } + + $pdo->beginTransaction(); + $stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES (:key, :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);"); + // add connection settings + foreach($required_settings as $setting){ + if (!isset($_data[$setting])){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $data_log), + 'msg' => array('required_data_missing', $setting) + ); + $pdo->rollback(); + return false; + } + + $stmt->bindParam(':key', $setting); + $stmt->bindParam(':value', $_data[$setting]); + $stmt->execute(); + } + $pdo->commit(); + + // add redirect_url_extra + if (isset($_data['redirect_url_extra'])){ + $_data['redirect_url_extra'] = (!is_array($_data['redirect_url_extra'])) ? array($_data['redirect_url_extra']) : $_data['redirect_url_extra']; + + $redirect_url_extra = array_filter($_data['redirect_url_extra']); + $redirect_url_extra = json_encode($redirect_url_extra); + + $stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES ('redirect_url_extra', :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);"); + $stmt->bindParam(':value', $redirect_url_extra); + $stmt->execute(); + } + + // add default template + if (isset($_data['default_template'])) { + $_data['default_template'] = (empty($_data['default_template'])) ? "" : $_data['default_template']; + $stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES ('default_template', :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);"); + $stmt->bindParam(':value', $_data['default_template']); + $stmt->execute(); + } + + // add mappers + if (isset($_data['mappers']) && isset($_data['templates'])){ + $_data['mappers'] = (!is_array($_data['mappers'])) ? array($_data['mappers']) : $_data['mappers']; + $_data['templates'] = (!is_array($_data['templates'])) ? array($_data['templates']) : $_data['templates']; + + $mappers = array_filter($_data['mappers']); + $templates = array_filter($_data['templates']); + if (count($mappers) == count($templates)){ + $mappers = json_encode($mappers); + $templates = json_encode($templates); + + $stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES ('mappers', :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);"); + $stmt->bindParam(':value', $mappers); + $stmt->execute(); + $stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES ('templates', :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);"); + $stmt->bindParam(':value', $templates); + $stmt->execute(); + } + } + + // delete old access_token + $stmt = $pdo->query("INSERT INTO identity_provider (`key`, `value`) VALUES ('access_token', '') ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);"); + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $data_log), + 'msg' => array('object_modified', '') + ); + return true; + break; + case 'test': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + + switch ($_data['authsource']) { + case 'keycloak': + $url = "{$_data['server_url']}/realms/{$_data['realm']}/protocol/openid-connect/token"; + $req = http_build_query(array( + 'grant_type' => 'client_credentials', + 'client_id' => $_data['client_id'], + 'client_secret' => $_data['client_secret'] + )); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_TIMEOUT, 7); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $req); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded')); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + if ($_data['ignore_ssl_error'] == "1"){ + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); + } + $res = curl_exec($curl); + $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close ($curl); + + if ($code != 200) { + return false; + } + break; + case 'generic-oidc': + $url = $_data['token_url']; + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_TIMEOUT, 7); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "OPTIONS"); + if ($_data['ignore_ssl_error'] == "1"){ + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); + } + $res = curl_exec($curl); + $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close ($curl); + + if ($code != 200) { + return false; + } + break; + case 'ldap': + if (!$_data['host'] || !$_data['port'] || !$_data['basedn'] || + !$_data['binddn'] || !$_data['bindpass']){ + return false; + } + $_data['use_ssl'] = isset($_data['use_ssl']) ? boolval($_data['use_ssl']) : false; + $_data['use_tls'] = isset($_data['use_tls']) && !$_data['use_ssl'] ? boolval($_data['use_tls']) : false; + $_data['ignore_ssl_error'] = isset($_data['ignore_ssl_error']) ? boolval($_data['ignore_ssl_error']) : false; + $options = array(); + if ($_data['ignore_ssl_error']) { + $options[LDAP_OPT_X_TLS_REQUIRE_CERT] = LDAP_OPT_X_TLS_NEVER; + } + $provider = new \LdapRecord\Connection([ + 'hosts' => explode(",", $_data['host']), + 'port' => $_data['port'], + 'base_dn' => $_data['basedn'], + 'username' => $_data['binddn'], + 'password' => $_data['bindpass'], + 'use_ssl' => $_data['use_ssl'], + 'use_tls' => $_data['use_tls'], + 'options' => $options + ]); + try { + $provider->connect(); + } catch (Throwable $e) { + return false; + } + break; + } + + return true; + break; + case "delete": + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + + $stmt = $pdo->query("SELECT * FROM `mailbox` + WHERE `authsource` != 'mailcow' + AND `authsource` IS NOT NULL"); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + if ($rows) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $data_log), + 'msg' => array('authsource_in_use', $setting) + ); + return false; + } + + $stmt = $pdo->query("DELETE FROM identity_provider;"); + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $data_log), + 'msg' => array('item_deleted', '') + ); + return true; + break; + case "init": + $settings = identity_provider('get'); + $provider = null; + + switch ($settings['authsource']) { + case "keycloak": + if ($settings['server_url'] && $settings['realm'] && $settings['client_id'] && + $settings['client_secret'] && $settings['redirect_url'] && $settings['version']){ + $guzzyClient = new GuzzleHttp\Client([ + 'defaults' => [ + \GuzzleHttp\RequestOptions::CONNECT_TIMEOUT => 5, + \GuzzleHttp\RequestOptions::ALLOW_REDIRECTS => true], + \GuzzleHttp\RequestOptions::VERIFY => !$settings['ignore_ssl_error'], + ] + ); + $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([ + 'authServerUrl' => $settings['server_url'], + 'realm' => $settings['realm'], + 'clientId' => $settings['client_id'], + 'clientSecret' => $settings['client_secret'], + 'redirectUri' => $settings['redirect_url'], + 'version' => $settings['version'], + // 'encryptionAlgorithm' => 'RS256', // optional + // 'encryptionKeyPath' => '../key.pem' // optional + // 'encryptionKey' => 'contents_of_key_or_certificate' // optional + ]); + $provider->setHttpClient($guzzyClient); + } + break; + case "generic-oidc": + if ($settings['client_id'] && $settings['client_secret'] && $settings['redirect_url'] && + $settings['authorize_url'] && $settings['token_url'] && $settings['userinfo_url']){ + $guzzyClient = new GuzzleHttp\Client([ + 'defaults' => [ + \GuzzleHttp\RequestOptions::CONNECT_TIMEOUT => 5, + \GuzzleHttp\RequestOptions::ALLOW_REDIRECTS => true], + \GuzzleHttp\RequestOptions::VERIFY => !$settings['ignore_ssl_error'], + ] + ); + $provider = new \League\OAuth2\Client\Provider\GenericProvider([ + 'clientId' => $settings['client_id'], + 'clientSecret' => $settings['client_secret'], + 'redirectUri' => $settings['redirect_url'], + 'urlAuthorize' => $settings['authorize_url'], + 'urlAccessToken' => $settings['token_url'], + 'urlResourceOwnerDetails' => $settings['userinfo_url'], + 'scopes' => $settings['client_scopes'] + ]); + $provider->setHttpClient($guzzyClient); + } + break; + case "ldap": + if ($settings['host'] && $settings['port'] && $settings['basedn'] && + $settings['binddn'] && $settings['bindpass']){ + $options = array(); + if ($settings['ignore_ssl_error']) { + $options[LDAP_OPT_X_TLS_REQUIRE_CERT] = LDAP_OPT_X_TLS_NEVER; + } + $provider = new \LdapRecord\Connection([ + 'hosts' => explode(",", $settings['host']), + 'port' => $settings['port'], + 'base_dn' => $settings['basedn'], + 'username' => $settings['binddn'], + 'password' => $settings['bindpass'], + 'use_ssl' => $settings['use_ssl'], + 'use_tls' => $settings['use_tls'], + 'options' => $options + ]); + try { + $provider->connect(); + } catch (Throwable $e) { + $provider = null; + } + } + break; + } + return $provider; + break; + case "verify-sso": + if ($iam_settings['authsource'] != 'keycloak' && $iam_settings['authsource'] != 'generic-oidc'){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, "no OIDC provider configured"), + 'msg' => 'login_failed' + ); + return false; + } + + try { + $token = $iam_provider->getAccessToken('authorization_code', ['code' => $_GET['code']]); + $plain_token = $token->getToken(); + $plain_refreshtoken = $token->getRefreshToken(); + $info = $iam_provider->getResourceOwner($token)->toArray(); + } catch (Throwable $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $e->getMessage()), + 'msg' => 'login_failed' + ); + return false; + } + // check if email address is given + if (empty($info['email'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, 'No email address found for user'), + 'msg' => 'login_failed' + ); + return false; + } + + // get mapped template + $user_template = $info['mailcow_template']; + $mapper_key = array_search($user_template, $iam_settings['mappers']); + + // token valid, get mailbox + $stmt = $pdo->prepare("SELECT + mailbox.*, + domain.active AS d_active + FROM `mailbox` + INNER JOIN domain on mailbox.domain = domain.domain + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `username` = :user"); + $stmt->execute(array(':user' => $info['email'])); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row){ + if (!in_array($row['authsource'], array("keycloak", "generic-oidc"))) { + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $info['email'], "The user's authentication source is not of type OIDC"), + 'msg' => 'login_failed' + ); + return false; + } + if ($mapper_key !== false) { + // update user + $_SESSION['access_all_exception'] = '1'; + mailbox('edit', 'mailbox_from_template', array( + 'username' => $info['email'], + 'name' => $info['name'], + 'template' => $iam_settings['templates'][$mapper_key] + )); + $_SESSION['access_all_exception'] = '0'; + + // get updated row + $stmt = $pdo->prepare("SELECT + mailbox.*, + domain.active AS d_active + FROM `mailbox` + INNER JOIN domain on mailbox.domain = domain.domain + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `username` = :user"); + $stmt->execute(array(':user' => $info['email'])); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + } + if ($row['active'] != 1 || $row['d_active'] != 1) { + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $info['email'], 'Domain or mailbox is inactive'), + 'msg' => 'login_failed' + ); + return false; + } + set_user_loggedin_session($info['email']); + $_SESSION['iam_token'] = $plain_token; + $_SESSION['iam_refresh_token'] = $plain_refreshtoken; + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), + 'msg' => array('logged_in_as', $_SESSION['mailcow_cc_username']) + ); + return true; + } + + // user doesn't exist, check if login provisioning is enabled + if (!$iam_settings['login_provisioning']){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, "Auto-create users on login is deactivated"), + 'msg' => 'login_failed' + ); + return false; + } + + if (empty($iam_settings['mappers']) || empty($user_template) || $mapper_key === false){ + if (!empty($iam_settings['default_template'])) { + $mbox_template = $iam_settings['default_template']; + } else { + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $info['email'], 'No matching attribute mapping was found'), + 'msg' => 'login_failed' + ); + return false; + } + } else { + $mbox_template = $iam_settings['templates'][$mapper_key]; + } + + // create mailbox + $_SESSION['access_all_exception'] = '1'; + $create_res = mailbox('add', 'mailbox_from_template', array( + 'domain' => explode('@', $info['email'])[1], + 'local_part' => explode('@', $info['email'])[0], + 'name' => $info['name'], + 'authsource' => $iam_settings['authsource'], + 'template' => $mbox_template + )); + $_SESSION['access_all_exception'] = '0'; + if (!$create_res){ + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $info['email'], 'Could not create mailbox on login'), + 'msg' => 'login_failed' + ); + return false; + } + + // double check if mailbox and domain is active + $stmt = $pdo->prepare("SELECT * FROM `mailbox` + INNER JOIN domain on mailbox.domain = domain.domain + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `mailbox`.`active`='1' + AND `domain`.`active`='1' + AND `username` = :user"); + $stmt->execute(array(':user' => $info['email'])); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row)) { + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $info['email'], 'Domain or mailbox is inactive'), + 'msg' => 'login_failed' + ); + return false; + } + + set_user_loggedin_session($info['email']); + $_SESSION['iam_token'] = $plain_token; + $_SESSION['iam_refresh_token'] = $plain_refreshtoken; + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), + 'msg' => array('logged_in_as', $_SESSION['mailcow_cc_username']) + ); + return true; + break; + case "refresh-token": + try { + $token = $iam_provider->getAccessToken('refresh_token', ['refresh_token' => $_SESSION['iam_refresh_token']]); + $plain_token = $token->getToken(); + $plain_refreshtoken = $token->getRefreshToken(); + $info = $iam_provider->getResourceOwner($token)->toArray(); + } catch (Throwable $e) { + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => array('refresh_login_failed', $e->getMessage()) + ); + return false; + } + + if (empty($info['email'])){ + clear_session(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role']), + 'msg' => 'refresh_login_failed' + ); + return false; + } + + set_user_loggedin_session($info['email']); + $_SESSION['iam_token'] = $plain_token; + $_SESSION['iam_refresh_token'] = $plain_refreshtoken; + return true; + break; + case "get-redirect": + if ($iam_settings['authsource'] != 'keycloak' && $iam_settings['authsource'] != 'generic-oidc') + return false; + $options = []; + if (isset($iam_settings['redirect_url_extra'])) { + // check if the current domain is used in an extra redirect URL + $targetDomain = strtolower($_SERVER['HTTP_HOST']); + foreach ($iam_settings['redirect_url_extra'] as $testUrl) { + $testUrlParsed = parse_url($testUrl); + if (isset($testUrlParsed['host']) && strtolower($testUrlParsed['host']) == $targetDomain) { + $options['redirect_uri'] = $testUrl; + break; + } + } + } + $authUrl = $iam_provider->getAuthorizationUrl($options); + $_SESSION['oauth2state'] = $iam_provider->getState(); + return $authUrl; + break; + case "get-keycloak-admin-token": + // get access_token for service account of mailcow client + if ($iam_settings['authsource'] !== 'keycloak') return false; + if (isset($iam_settings['access_token'])) { + // check if access_token is valid + $url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token/introspect"; + $req = http_build_query(array( + 'token' => $iam_settings['access_token'], + 'client_id' => $iam_settings['client_id'], + 'client_secret' => $iam_settings['client_secret'] + )); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_TIMEOUT, 7); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $req); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded')); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_TIMEOUT, 5); + $res = json_decode(curl_exec($curl), true); + $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close ($curl); + if ($code == 200 && $res['active'] == true) { + // token is valid + return $iam_settings['access_token']; + } + } + + $url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token"; + $req = http_build_query(array( + 'grant_type' => 'client_credentials', + 'client_id' => $iam_settings['client_id'], + 'client_secret' => $iam_settings['client_secret'] + )); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_TIMEOUT, 7); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $req); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded')); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_TIMEOUT, 5); + $res = json_decode(curl_exec($curl), true); + $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close ($curl); + if ($code != 200) { + return false; + } + + $stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES (:key, :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);"); + $stmt->execute(array( + ':key' => 'access_token', + ':value' => $res['access_token'] + )); + return $res['access_token']; + break; + } +} +function reset_password($action, $data = null) { + global $pdo; + global $redis; + global $mailcow_hostname; + global $PW_RESET_TOKEN_LIMIT; + global $PW_RESET_TOKEN_LIFETIME; + + $_data_log = $data; + if (isset($_data_log['new_password'])) $_data_log['new_password'] = '*'; + if (isset($_data_log['new_password2'])) $_data_log['new_password2'] = '*'; + + switch ($action) { + case 'check': + $token = $data; + + $stmt = $pdo->prepare("SELECT `t1`.`username` FROM `reset_password` AS `t1` JOIN `mailbox` AS `t2` ON `t1`.`username` = `t2`.`username` WHERE `t1`.`token` = :token AND `t1`.`created` > DATE_SUB(NOW(), INTERVAL :lifetime MINUTE) AND `t2`.`active` = 1 AND `t2`.`authsource` = 'mailcow';"); + $stmt->execute(array( + ':token' => preg_replace('/[^a-zA-Z0-9-]/', '', $token), + ':lifetime' => $PW_RESET_TOKEN_LIFETIME + )); + $return = $stmt->fetch(PDO::FETCH_ASSOC); + return empty($return['username']) ? false : $return['username']; + break; + case 'issue': + $username = $data; + + // perform cleanup + $stmt = $pdo->prepare("DELETE FROM `reset_password` WHERE created < DATE_SUB(NOW(), INTERVAL :lifetime MINUTE);"); + $stmt->execute(array(':lifetime' => $PW_RESET_TOKEN_LIFETIME)); + + if (filter_var($username, FILTER_VALIDATE_EMAIL) === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + + $pw_reset_notification = reset_password('get_notification', 'raw'); + if (!$pw_reset_notification) return false; + if (empty($pw_reset_notification['from']) || empty($pw_reset_notification['subject'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => 'password_reset_na' + ); + return false; + } + + $stmt = $pdo->prepare("SELECT * FROM `mailbox` + WHERE `username` = :username AND authsource = 'mailcow'"); $stmt->execute(array(':username' => $username)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if ($tfa->verifyCode($row['secret'], $_POST['token']) === true) { - $_SESSION['tfa_id'] = $row['id']; - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => 'verified_totp_login' + $mailbox_data = $stmt->fetch(PDO::FETCH_ASSOC); + + if (empty($mailbox_data)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => 'password_reset_invalid_user' ); - return true; + return false; } - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => 'totp_verification_failed' - ); - return false; - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => array('mysql_error', $e) - ); - return false; - } - break; - default: - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => 'unknown_tfa_method' - ); - return false; - break; - } - return false; -} -function admin_api($action, $data = null) { - global $pdo; - global $lang; - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => 'access_denied' - ); - return false; - } - switch ($action) { - case "edit": - $regen_key = $data['admin_api_regen_key']; - $active = (isset($data['active'])) ? 1 : 0; - $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from'])); - foreach ($allow_from as $key => $val) { - if (!filter_var($val, FILTER_VALIDATE_IP)) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $data), - 'msg' => array('ip_invalid', htmlspecialchars($allow_from[$key])) - ); - unset($allow_from[$key]); - continue; - } + + $mailbox_attr = json_decode($mailbox_data['attributes'], true); + if (empty($mailbox_attr['recovery_email']) || filter_var($mailbox_attr['recovery_email'], FILTER_VALIDATE_EMAIL) === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => "password_reset_invalid_user" + ); + return false; } - $allow_from = implode(',', array_unique(array_filter($allow_from))); - if (empty($allow_from)) { - $_SESSION['return'][] = array( + + $stmt = $pdo->prepare("SELECT * FROM `reset_password` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $generated_token_count = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($generated_token_count >= $PW_RESET_TOKEN_LIMIT) { + $_SESSION['return'][] = array( 'type' => 'danger', - 'log' => array(__FUNCTION__, $data), - 'msg' => 'ip_list_empty' + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => "reset_token_limit_exceeded" ); return false; } - $api_key = implode('-', array( + + $token = implode('-', array( strtoupper(bin2hex(random_bytes(3))), strtoupper(bin2hex(random_bytes(3))), strtoupper(bin2hex(random_bytes(3))), strtoupper(bin2hex(random_bytes(3))), strtoupper(bin2hex(random_bytes(3))) )); - $stmt = $pdo->query("SELECT `api_key` FROM `api`"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if (empty($num_results)) { - $stmt = $pdo->prepare("INSERT INTO `api` (`api_key`, `active`, `allow_from`) - VALUES (:api_key, :active, :allow_from);"); - $stmt->execute(array( - ':api_key' => $api_key, - ':active' => $active, - ':allow_from' => $allow_from - )); + + $stmt = $pdo->prepare("INSERT INTO `reset_password` (`username`, `token`) + VALUES (:username, :token)"); + $stmt->execute(array( + ':username' => $username, + ':token' => $token + )); + + $reset_link = getBaseURL() . "/reset-password?token=" . $token; + + $request_date = new DateTime(); + $locale_date = locale_get_default(); + $date_formatter = new IntlDateFormatter( + $locale_date, + IntlDateFormatter::FULL, + IntlDateFormatter::FULL + ); + $formatted_request_date = $date_formatter->format($request_date); + + // set template vars + // subject + $pw_reset_notification['subject'] = str_replace('{{hostname}}', $mailcow_hostname, $pw_reset_notification['subject']); + $pw_reset_notification['subject'] = str_replace('{{link}}', $reset_link, $pw_reset_notification['subject']); + $pw_reset_notification['subject'] = str_replace('{{username}}', $username, $pw_reset_notification['subject']); + $pw_reset_notification['subject'] = str_replace('{{username2}}', $mailbox_attr['recovery_email'], $pw_reset_notification['subject']); + $pw_reset_notification['subject'] = str_replace('{{date}}', $formatted_request_date, $pw_reset_notification['subject']); + $pw_reset_notification['subject'] = str_replace('{{token_lifetime}}', $PW_RESET_TOKEN_LIFETIME, $pw_reset_notification['subject']); + // text + $pw_reset_notification['text_tmpl'] = str_replace('{{hostname}}', $mailcow_hostname, $pw_reset_notification['text_tmpl']); + $pw_reset_notification['text_tmpl'] = str_replace('{{link}}', $reset_link, $pw_reset_notification['text_tmpl']); + $pw_reset_notification['text_tmpl'] = str_replace('{{username}}', $username, $pw_reset_notification['text_tmpl']); + $pw_reset_notification['text_tmpl'] = str_replace('{{username2}}', $mailbox_attr['recovery_email'], $pw_reset_notification['text_tmpl']); + $pw_reset_notification['text_tmpl'] = str_replace('{{date}}', $formatted_request_date, $pw_reset_notification['text_tmpl']); + $pw_reset_notification['text_tmpl'] = str_replace('{{token_lifetime}}', $PW_RESET_TOKEN_LIFETIME, $pw_reset_notification['text_tmpl']); + // html + $pw_reset_notification['html_tmpl'] = str_replace('{{hostname}}', $mailcow_hostname, $pw_reset_notification['html_tmpl']); + $pw_reset_notification['html_tmpl'] = str_replace('{{link}}', $reset_link, $pw_reset_notification['html_tmpl']); + $pw_reset_notification['html_tmpl'] = str_replace('{{username}}', $username, $pw_reset_notification['html_tmpl']); + $pw_reset_notification['html_tmpl'] = str_replace('{{username2}}', $mailbox_attr['recovery_email'], $pw_reset_notification['html_tmpl']); + $pw_reset_notification['html_tmpl'] = str_replace('{{date}}', $formatted_request_date, $pw_reset_notification['html_tmpl']); + $pw_reset_notification['html_tmpl'] = str_replace('{{token_lifetime}}', $PW_RESET_TOKEN_LIFETIME, $pw_reset_notification['html_tmpl']); + + + $email_sent = reset_password('send_mail', array( + "from" => $pw_reset_notification['from'], + "to" => $mailbox_attr['recovery_email'], + "subject" => $pw_reset_notification['subject'], + "text" => $pw_reset_notification['text_tmpl'], + "html" => $pw_reset_notification['html_tmpl'] + )); + + if (!$email_sent){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => "recovery_email_failed" + ); + return false; } - else { - $stmt = $pdo->prepare("UPDATE `api` SET `active` = :active, `allow_from` = :allow_from ;"); - $stmt->execute(array( - ':active' => $active, - ':allow_from' => $allow_from - )); + + list($localPart, $domainPart) = explode('@', $mailbox_attr['recovery_email']); + if (strlen($localPart) > 1) { + $maskedLocalPart = $localPart[0] . str_repeat('*', strlen($localPart) - 1); + } else { + $maskedLocalPart = "*"; } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => array("recovery_email_sent", $maskedLocalPart . '@' . $domainPart) + ); + return array( + "username" => $username, + "issue" => "success" + ); break; - case "regen_key": - $api_key = implode('-', array( - strtoupper(bin2hex(random_bytes(3))), - strtoupper(bin2hex(random_bytes(3))), - strtoupper(bin2hex(random_bytes(3))), - strtoupper(bin2hex(random_bytes(3))), - strtoupper(bin2hex(random_bytes(3))) + case 'reset': + $token = $data['token']; + $new_password = $data['new_password']; + $new_password2 = $data['new_password2']; + $username = $data['username']; + $check_tfa = $data['check_tfa']; + + if (!$username || !$token) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => 'invalid_reset_token' + ); + return false; + } + + # check new password + if (!password_check($new_password, $new_password2)) { + return false; + } + + if ($check_tfa){ + // check for tfa authenticators + $authenticators = get_tfa($username); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { + $_SESSION['pending_mailcow_cc_username'] = $username; + $_SESSION['pending_pw_reset_token'] = $token; + $_SESSION['pending_pw_new_password'] = $new_password; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; + $_SESSION['return'][] = array( + 'type' => 'info', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => 'awaiting_tfa_confirmation' + ); + return false; + } + } + + # set new password + $password_hashed = hash_password($new_password); + $stmt = $pdo->prepare("UPDATE `mailbox` SET + `password` = :password_hashed, + `attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW()) + WHERE `username` = :username AND authsource = 'mailcow'"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username' => $username )); - $stmt = $pdo->prepare("UPDATE `api` SET `api_key` = :api_key"); + + // perform cleanup + $stmt = $pdo->prepare("DELETE FROM `reset_password` WHERE `username` = :username;"); $stmt->execute(array( - ':api_key' => $api_key + ':username' => $username )); + + update_sogo_static_view($username); + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => 'password_changed_success' + ); + return true; break; - case "get": - $stmt = $pdo->query("SELECT * FROM `api`"); - $apidata = $stmt->fetch(PDO::FETCH_ASSOC); - return $apidata; + case 'get_notification': + $type = $data; + + try { + $settings['from'] = $redis->Get('PW_RESET_FROM'); + $settings['subject'] = $redis->Get('PW_RESET_SUBJ'); + $settings['html_tmpl'] = $redis->Get('PW_RESET_HTML'); + $settings['text_tmpl'] = $redis->Get('PW_RESET_TEXT'); + if (empty($settings['html_tmpl']) && empty($settings['text_tmpl'])) { + $settings['html_tmpl'] = file_get_contents("/tpls/pw_reset_html.tpl"); + $settings['text_tmpl'] = file_get_contents("/tpls/pw_reset_text.tpl"); + } + + if ($type != "raw") { + $settings['html_tmpl'] = htmlspecialchars($settings['html_tmpl']); + $settings['text_tmpl'] = htmlspecialchars($settings['text_tmpl']); + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + + return $settings; break; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $data), - 'msg' => 'admin_api_modified' - ); -} -function rspamd_ui($action, $data = null) { - global $lang; - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => 'access_denied' - ); - return false; - } - switch ($action) { - case "edit": - $rspamd_ui_pass = $data['rspamd_ui_pass']; - $rspamd_ui_pass2 = $data['rspamd_ui_pass2']; - if (empty($rspamd_ui_pass) || empty($rspamd_ui_pass2)) { + case 'send_mail': + $from = $data['from']; + $to = $data['to']; + $text = $data['text']; + $html = $data['html']; + $subject = $data['subject']; + + if (!filter_var($from, FILTER_VALIDATE_EMAIL)) { $_SESSION['return'][] = array( 'type' => 'danger', - 'log' => array(__FUNCTION__, '*', '*'), - 'msg' => 'password_empty' + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => 'from_invalid' ); return false; } - if ($rspamd_ui_pass != $rspamd_ui_pass2) { + if (!filter_var($to, FILTER_VALIDATE_EMAIL)) { $_SESSION['return'][] = array( 'type' => 'danger', - 'log' => array(__FUNCTION__, '*', '*'), - 'msg' => 'password_mismatch' + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => 'to_invalid' ); return false; } - if (strlen($rspamd_ui_pass) < 6) { + if (empty($subject)) { $_SESSION['return'][] = array( 'type' => 'danger', - 'log' => array(__FUNCTION__, '*', '*'), - 'msg' => 'rspamd_ui_pw_length' + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => 'subject_empty' ); return false; } - $docker_return = docker('post', 'rspamd-mailcow', 'exec', array('cmd' => 'worker_password', 'raw' => $rspamd_ui_pass), array('Content-Type: application/json')); - if ($docker_return_array = json_decode($docker_return, true)) { - if ($docker_return_array['type'] == 'success') { - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, '*', '*'), - 'msg' => 'rspamd_ui_pw_set' - ); - return true; - } - else { - $_SESSION['return'][] = array( - 'type' => $docker_return_array['type'], - 'log' => array(__FUNCTION__, '*', '*'), - 'msg' => $docker_return_array['msg'] - ); - return false; - } + if (empty($text)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => 'text_empty' + ); + return false; + } + + ini_set('max_execution_time', 0); + ini_set('max_input_time', 0); + $mail = new PHPMailer; + $mail->Timeout = 10; + $mail->SMTPOptions = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ); + $mail->isSMTP(); + $mail->Host = 'postfix-mailcow'; + $mail->SMTPAuth = false; + $mail->Port = 25; + $mail->setFrom($from); + $mail->Subject = $subject; + $mail->CharSet ="UTF-8"; + if (!empty($html)) { + $mail->Body = $html; + $mail->AltBody = $text; } else { - $_SESSION['return'][] = array( + $mail->Body = $text; + } + $mail->XMailer = 'MooMail'; + $mail->AddAddress($to); + if (!$mail->send()) { + return false; + } + $mail->ClearAllRecipients(); + + return true; + break; + } + + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + + switch ($action) { + case 'edit_notification': + $subject = $data['subject']; + $from = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $data['from']); + + $from = (!filter_var($from, FILTER_VALIDATE_EMAIL)) ? "" : $from; + $subject = (empty($subject)) ? "" : $subject; + $text = (empty($data['text_tmpl'])) ? "" : $data['text_tmpl']; + $html = (empty($data['html_tmpl'])) ? "" : $data['html_tmpl']; + + try { + $redis->Set('PW_RESET_FROM', $from); + $redis->Set('PW_RESET_SUBJ', $subject); + $redis->Set('PW_RESET_HTML', $html); + $redis->Set('PW_RESET_TEXT', $text); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( 'type' => 'danger', - 'log' => array(__FUNCTION__, '*', '*'), - 'msg' => 'unknown' + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => array('redis_error', $e) ); return false; } + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $action, $_data_log), + 'msg' => 'saved_settings' + ); break; } } -function get_u2f_registrations($username) { - global $pdo; - $sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = ? AND `active` = '1'"); - $sel->execute(array($username)); - return $sel->fetchAll(PDO::FETCH_OBJ); +function clear_session(){ + session_regenerate_id(true); + session_unset(); + session_destroy(); + session_write_close(); +} +function set_user_loggedin_session($user) { + session_regenerate_id(true); + $_SESSION['mailcow_cc_username'] = $user; + $_SESSION['mailcow_cc_role'] = 'user'; + $sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass"); + $_SESSION['sogo-sso-user-allowed'][] = $user; + $_SESSION['sogo-sso-pass'] = $sogo_sso_pass; + unset($_SESSION['pending_mailcow_cc_username']); + unset($_SESSION['pending_mailcow_cc_role']); + unset($_SESSION['pending_tfa_methods']); +} +function protect_route($allowed_roles = ['admin', 'domainadmin', 'user'], $redirects = []) { + // Check if user is authenticated + if (!isset($_SESSION['mailcow_cc_role'])) { + if (isset($redirects['unauthenticated'])) { + header('Location: ' . $redirects['unauthenticated']); + } else { + header('Location: /'); + } + exit(); + } + + // Check for pending actions (2FA setup, password update) + if (!empty($_SESSION['pending_tfa_setup']) || !empty($_SESSION['pending_pw_update'])) { + $pending_redirect = '/'; + if ($_SESSION['mailcow_cc_role'] === 'admin') { + $pending_redirect = '/admin'; + } elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') { + $pending_redirect = '/domainadmin'; + } + header('Location: ' . $pending_redirect); + exit(); + } + + // Check if user's role is in the allowed roles for the route + if (!in_array($_SESSION['mailcow_cc_role'], $allowed_roles)) { + if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') { + header('Location: /admin/dashboard'); + exit(); + } + elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') { + header('Location: /domainadmin/mailbox'); + exit(); + } + elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') { + header('Location: /user'); + exit(); + } + else { + header('Location: /'); + exit(); + } + } } -function get_logs($container, $lines = false) { +function get_logs($application, $lines = false) { if ($lines === false) { - $lines = $GLOBALS['LOG_LINES'] - 1; + $lines = $GLOBALS['LOG_LINES'] - 1; } elseif(is_numeric($lines) && $lines >= 1) { $lines = abs(intval($lines) - 1); @@ -1274,14 +3601,13 @@ function get_logs($container, $lines = false) { $to = intval($to); if ($from < 1 || $to < $from) { return false; } } - global $lang; - global $redis; - global $pdo; - if ($_SESSION['mailcow_cc_role'] != "admin") { - return false; - } + global $redis; + global $pdo; + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } // SQL - if ($container == "mailcow-ui") { + if ($application == "mailcow-ui") { if (isset($from) && isset($to)) { $stmt = $pdo->prepare("SELECT * FROM `logs` ORDER BY `id` DESC LIMIT :from, :to"); $stmt->execute(array( @@ -1301,8 +3627,28 @@ function get_logs($container, $lines = false) { return $data; } } + if ($application == "sasl") { + if (isset($from) && isset($to)) { + $stmt = $pdo->prepare("SELECT * FROM `sasl_log` ORDER BY `datetime` DESC LIMIT :from, :to"); + $stmt->execute(array( + ':from' => $from - 1, + ':to' => $to + )); + $data = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + else { + $stmt = $pdo->prepare("SELECT * FROM `sasl_log` ORDER BY `datetime` DESC LIMIT :lines"); + $stmt->execute(array( + ':lines' => $lines + 1, + )); + $data = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + if (is_array($data)) { + return $data; + } + } // Redis - if ($container == "dovecot-mailcow") { + if ($application == "dovecot-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('DOVECOT_MAILLOG', $from - 1, $to - 1); } @@ -1316,7 +3662,21 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "postfix-mailcow") { + if ($application == "cron-mailcow") { + if (isset($from) && isset($to)) { + $data = $redis->lRange('CRON_LOG', $from - 1, $to - 1); + } + else { + $data = $redis->lRange('CRON_LOG', 0, $lines); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } + if ($application == "postfix-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('POSTFIX_MAILLOG', $from - 1, $to - 1); } @@ -1330,7 +3690,7 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "sogo-mailcow") { + if ($application == "sogo-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('SOGO_LOG', $from - 1, $to - 1); } @@ -1344,7 +3704,7 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "watchdog-mailcow") { + if ($application == "watchdog-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('WATCHDOG_LOG', $from - 1, $to - 1); } @@ -1358,7 +3718,7 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "acme-mailcow") { + if ($application == "acme-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('ACME_LOG', $from - 1, $to - 1); } @@ -1372,7 +3732,21 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "api-mailcow") { + if ($application == "ratelimited") { + if (isset($from) && isset($to)) { + $data = $redis->lRange('RL_LOG', $from - 1, $to - 1); + } + else { + $data = $redis->lRange('RL_LOG', 0, $lines); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } + if ($application == "api-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('API_LOG', $from - 1, $to - 1); } @@ -1386,7 +3760,7 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "netfilter-mailcow") { + if ($application == "netfilter-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('NETFILTER_LOG', $from - 1, $to - 1); } @@ -1400,7 +3774,7 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "autodiscover-mailcow") { + if ($application == "autodiscover-mailcow") { if (isset($from) && isset($to)) { $data = $redis->lRange('AUTODISCOVER_LOG', $from - 1, $to - 1); } @@ -1414,7 +3788,7 @@ function get_logs($container, $lines = false) { return $data_array; } } - if ($container == "rspamd-history") { + if ($application == "rspamd-history") { $curl = curl_init(); curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); if (!is_numeric($lines)) { @@ -1434,6 +3808,62 @@ function get_logs($container, $lines = false) { curl_close($curl); return false; } + if ($application == "rspamd-stats") { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/stat"); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + $stats = curl_exec($curl); + if (!curl_errno($curl)) { + $data_array = json_decode($stats, true); + curl_close($curl); + return $data_array; + } + curl_close($curl); + return false; + } return false; } +function getGUID() { + if (function_exists('com_create_guid')) { + return com_create_guid(); + } + mt_srand((double)microtime()*10000);//optional for php 4.2.0 and up. + $charid = strtoupper(md5(uniqid(rand(), true))); + $hyphen = chr(45);// "-" + return substr($charid, 0, 8).$hyphen + .substr($charid, 8, 4).$hyphen + .substr($charid,12, 4).$hyphen + .substr($charid,16, 4).$hyphen + .substr($charid,20,12); +} + +function cleanupJS($ignore = '', $folder = '/tmp/*.js') { + $now = time(); + foreach (glob($folder) as $filename) { + if(strpos($filename, $ignore) !== false) { + continue; + } + if (is_file($filename)) { + if ($now - filemtime($filename) >= 60 * 60) { + unlink($filename); + } + } + } +} + +function cleanupCSS($ignore = '', $folder = '/tmp/*.css') { + $now = time(); + foreach (glob($folder) as $filename) { + if(strpos($filename, $ignore) !== false) { + continue; + } + if (is_file($filename)) { + if ($now - filemtime($filename) >= 60 * 60) { + unlink($filename); + } + } + } +} + ?> diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 1a4fa3ca27..acbbb94d41 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -4,9 +4,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { global $redis; global $lang; global $MAILBOX_DEFAULT_ATTRIBUTES; + global $iam_settings; + $_data_log = $_data; !isset($_data_log['password']) ?: $_data_log['password'] = '*'; !isset($_data_log['password2']) ?: $_data_log['password2'] = '*'; + + // Track mailboxes affected by alias operations for incremental SOGo updates + $update_sogo_mailboxes = array(); + switch ($_action) { case 'add': switch ($_type) { @@ -35,32 +41,167 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { else { $username = $_SESSION['mailcow_cc_username']; } - if (!is_numeric($_data["validity"]) || $_data["validity"] > 672) { + if (isset($_data["validity"])) { + if (!filter_var($_data["validity"], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 87600)))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'validity_missing' + ); + return false; + } + } + else { + // Default to 1 yr + $_data["validity"] = 8760; + } + if (isset($_data["permanent"]) && filter_var($_data["permanent"], FILTER_VALIDATE_BOOL)) { + $permanent = 1; + } + else { + $permanent = 0; + } + $domain = $_data['domain']; + // spamalias.description is NOT NULL, an absent description must not become a null insert + $description = $_data['description'] ?? ''; + $valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain']; + $valid_alias_domains = user_get_alias_details($username)['alias_domains']; + if (!empty($valid_alias_domains)) { + $valid_domains = array_merge($valid_domains, $valid_alias_domains); + } + if (!in_array($domain, $valid_domains)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'validity_missing' + 'msg' => 'domain_invalid' ); return false; } - $stmt = $pdo->prepare("SELECT `domain` FROM `mailbox` WHERE `username` = :username"); - $stmt->execute(array(':username' => $_SESSION['mailcow_cc_username'])); - $domain = $stmt->fetch(PDO::FETCH_ASSOC)['domain']; - $validity = strtotime("+".$_data["validity"]." hour"); - $letters = 'abcefghijklmnopqrstuvwxyz1234567890'; - $random_name = substr(str_shuffle($letters), 0, 24); - $stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `goto`, `validity`) VALUES - (:address, :goto, :validity)"); + $validity = strtotime("+" . $_data["validity"] . " hour"); + $stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `description`, `goto`, `validity`, `permanent`) VALUES + (:address, :description, :goto, :validity, :permanent)"); $stmt->execute(array( - ':address' => $random_name . '@' . $domain, + ':address' => readable_random_string(rand(rand(3, 9), rand(3, 9))) . '.' . readable_random_string(rand(rand(3, 9), rand(3, 9))) . '@' . $domain, + ':description' => $description, ':goto' => $username, - ':validity' => $validity + ':validity' => $validity, + ':permanent' => $permanent )); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', htmlspecialchars($_SESSION['mailcow_cc_username'])) + 'msg' => array('mailbox_modified', $username) + ); + break; + case 'global_filter': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + $sieve = new Sieve\SieveParser(); + $script_data = $_data['script_data']; + $script_data = str_replace("\r\n", "\n", $script_data); // windows -> unix + $script_data = str_replace("\r", "\n", $script_data); // remaining -> unix + $filter_type = $_data['filter_type']; + try { + $sieve->parse($script_data); + } + catch (Exception $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('sieve_error', $e->getMessage()) + ); + return false; + } + if ($filter_type == 'prefilter') { + try { + if (file_exists('/global_sieve/before')) { + $filter_handle = fopen('/global_sieve/before', 'w'); + if (!$filter_handle) { + throw new Exception($lang['danger']['file_open_error']); + } + fwrite($filter_handle, $script_data); + fclose($filter_handle); + } + $restart_response = json_decode(docker('post', 'dovecot-mailcow', 'restart'), true); + if ($restart_response['type'] == "success") { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'dovecot_restart_success' + ); + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'dovecot_restart_failed' + ); + } + } + catch (Exception $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('global_filter_write_error', htmlspecialchars($e->getMessage())) + ); + return false; + } + } + elseif ($filter_type == 'postfilter') { + try { + if (file_exists('/global_sieve/after')) { + $filter_handle = fopen('/global_sieve/after', 'w'); + if (!$filter_handle) { + throw new Exception($lang['danger']['file_open_error']); + } + fwrite($filter_handle, $script_data); + fclose($filter_handle); + } + $restart_response = json_decode(docker('post', 'dovecot-mailcow', 'restart'), true); + if ($restart_response['type'] == "success") { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'dovecot_restart_success' + ); + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'dovecot_restart_failed' + ); + } + } + catch (Exception $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('global_filter_write_error', htmlspecialchars($e->getMessage())) + ); + return false; + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'invalid_filter_type' + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'global_filter_written' ); + return true; break; case 'filter': $sieve = new Sieve\SieveParser(); @@ -203,6 +344,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $timeout2 = intval($_data['timeout2']); $skipcrossduplicates = intval($_data['skipcrossduplicates']); $automap = intval($_data['automap']); + $dry = intval($_data['dry']); $port1 = $_data['port1']; $host1 = strtolower($_data['host1']); $password1 = $_data['password1']; @@ -214,6 +356,38 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $mins_interval = $_data['mins_interval']; $enc1 = $_data['enc1']; $custom_params = (empty(trim($_data['custom_params']))) ? '' : trim($_data['custom_params']); + + // validate custom params + foreach (explode('-', $custom_params) as $param){ + if(empty($param)) continue; + + // extract option + if (str_contains($param, '=')) $param = explode('=', $param)[0]; + else $param = rtrim($param, ' '); + // remove first char if first char is - + if ($param[0] == '-') $param = ltrim($param, $param[0]); + + if (str_contains($param, ' ')) { + // bad char + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'bad character SPACE' + ); + return false; + } + + // check if param is whitelisted + if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){ + // bad option + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'bad option '. $param + ); + return false; + } + } if (empty($subfolder2)) { $subfolder2 = ""; } @@ -237,15 +411,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 3600)))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - if (!is_valid_domain_name($host1)) { + if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 43800)))) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -253,6 +419,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } + // if (!is_valid_domain_name($host1)) { + // $_SESSION['return'][] = array( + // 'type' => 'danger', + // 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + // 'msg' => 'access_denied' + // ); + // return false; + // } if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") { $_SESSION['return'][] = array( 'type' => 'danger', @@ -281,8 +455,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `delete2`, `timeout1`, `timeout2`, `automap`, `skipcrossduplicates`, `maxbytespersecond`, `subscribeall`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `custom_params`, `active`) - VALUES (:user2, :exclude, :delete1, :delete2, :timeout1, :timeout2, :automap, :skipcrossduplicates, :maxbytespersecond, :subscribeall, :maxage, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :custom_params, :active)"); + $stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `delete2`, `timeout1`, `timeout2`, `automap`, `skipcrossduplicates`, `maxbytespersecond`, `subscribeall`, `dry`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `custom_params`, `active`) + VALUES (:user2, :exclude, :delete1, :delete2, :timeout1, :timeout2, :automap, :skipcrossduplicates, :maxbytespersecond, :subscribeall, :dry, :maxage, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :custom_params, :active)"); $stmt->execute(array( ':user2' => $username, ':custom_params' => $custom_params, @@ -296,6 +470,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':skipcrossduplicates' => $skipcrossduplicates, ':maxbytespersecond' => $maxbytespersecond, ':subscribeall' => $subscribeall, + ':dry' => $dry, ':subfolder2' => $subfolder2, ':host1' => $host1, ':authmech1' => 'PLAIN', @@ -317,18 +492,37 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if ($_SESSION['mailcow_cc_role'] != "admin") { $_SESSION['return'][] = array( 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), 'msg' => 'access_denied' ); return false; } - $domain = idn_to_ascii(strtolower(trim($_data['domain']))); + $DOMAIN_DEFAULT_ATTRIBUTES = null; + if ($_data['template']){ + $DOMAIN_DEFAULT_ATTRIBUTES = mailbox('get', 'domain_templates', $_data['template'])['attributes']; + } + if (empty($DOMAIN_DEFAULT_ATTRIBUTES)) { + $DOMAIN_DEFAULT_ATTRIBUTES = mailbox('get', 'domain_templates')[0]['attributes']; + } + + $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); $description = $_data['description']; - $aliases = $_data['aliases']; - $mailboxes = $_data['mailboxes']; - $maxquota = $_data['maxquota']; - $restart_sogo = $_data['restart_sogo']; - $quota = $_data['quota']; + if (empty($description)) $description = $domain; + $tags = (isset($_data['tags'])) ? (array)$_data['tags'] : $DOMAIN_DEFAULT_ATTRIBUTES['tags']; + $aliases = (isset($_data['aliases'])) ? (int)$_data['aliases'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_num_aliases_for_domain']; + $mailboxes = (isset($_data['mailboxes'])) ? (int)$_data['mailboxes'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_num_mboxes_for_domain']; + $defquota = (isset($_data['defquota'])) ? (int)$_data['defquota'] : $DOMAIN_DEFAULT_ATTRIBUTES['def_quota_for_mbox'] / 1024 ** 2; + $maxquota = (isset($_data['maxquota'])) ? (int)$_data['maxquota'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_quota_for_mbox'] / 1024 ** 2; + $restart_sogo = (int)$_data['restart_sogo']; + $quota = (isset($_data['quota'])) ? (int)$_data['quota'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_quota_for_domain'] / 1024 ** 2; + if ($defquota > $maxquota) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'mailbox_defquota_exceeds_mailbox_maxquota' + ); + return false; + } if ($maxquota > $quota) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -337,6 +531,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } + if ($defquota == "0" || empty($defquota)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'defquota_empty' + ); + return false; + } if ($maxquota == "0" || empty($maxquota)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -345,10 +547,18 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $active = intval($_data['active']); - $relay_all_recipients = intval($_data['relay_all_recipients']); - $backupmx = intval($_data['backupmx']); - ($relay_all_recipients == 1) ? $backupmx = '1' : null; + $active = (isset($_data['active'])) ? intval($_data['active']) : $DOMAIN_DEFAULT_ATTRIBUTES['active']; + $relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $DOMAIN_DEFAULT_ATTRIBUTES['relay_all_recipients']; + $relay_unknown_only = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : $DOMAIN_DEFAULT_ATTRIBUTES['relay_unknown_only']; + $backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $DOMAIN_DEFAULT_ATTRIBUTES['backupmx']; + $gal = (isset($_data['gal'])) ? intval($_data['gal']) : $DOMAIN_DEFAULT_ATTRIBUTES['gal']; + if ($relay_all_recipients == 1) { + $backupmx = '1'; + } + if ($relay_unknown_only == 1) { + $backupmx = 1; + $relay_all_recipients = 1; + } if (!is_valid_domain_name($domain)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -391,19 +601,46 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `maxquota`, `quota`, `backupmx`, `active`, `relay_all_recipients`) - VALUES (:domain, :description, :aliases, :mailboxes, :maxquota, :quota, :backupmx, :active, :relay_all_recipients)"); + + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `send_as` LIKE :domain"); + $stmt->execute(array( + ':domain' => '%@' . $domain + )); + // save domain + $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `defquota`, `maxquota`, `quota`, `backupmx`, `gal`, `active`, `relay_unknown_only`, `relay_all_recipients`) + VALUES (:domain, :description, :aliases, :mailboxes, :defquota, :maxquota, :quota, :backupmx, :gal, :active, :relay_unknown_only, :relay_all_recipients)"); $stmt->execute(array( ':domain' => $domain, ':description' => $description, ':aliases' => $aliases, ':mailboxes' => $mailboxes, + ':defquota' => $defquota, ':maxquota' => $maxquota, ':quota' => $quota, ':backupmx' => $backupmx, + ':gal' => $gal, ':active' => $active, + ':relay_unknown_only' => $relay_unknown_only, ':relay_all_recipients' => $relay_all_recipients )); + // save tags + foreach($tags as $index => $tag){ + if (empty($tag)) continue; + if ($index > $GLOBALS['TAGGING_LIMIT']) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) + ); + break; + } + $stmt = $pdo->prepare("INSERT INTO `tags_domain` (`domain`, `tag_name`) VALUES (:domain, :tag_name)"); + $stmt->execute(array( + ':domain' => $domain, + ':tag_name' => $tag, + )); + } + try { $redis->hSet('DOMAIN_MAP', $domain, 1); } @@ -415,12 +652,28 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - if (!empty(intval($_data['rl_value']))) { + $_data['rl_value'] = (isset($_data['rl_value'])) ? intval($_data['rl_value']) : $DOMAIN_DEFAULT_ATTRIBUTES['rl_value']; + $_data['rl_frame'] = (isset($_data['rl_frame'])) ? $_data['rl_frame'] : $DOMAIN_DEFAULT_ATTRIBUTES['rl_frame']; + if (!empty($_data['rl_value']) && !empty($_data['rl_frame'])){ ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $domain)); } + $_data['key_size'] = (isset($_data['key_size'])) ? intval($_data['key_size']) : $DOMAIN_DEFAULT_ATTRIBUTES['key_size']; + $_data['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : $DOMAIN_DEFAULT_ATTRIBUTES['dkim_selector']; + if (!empty($_data['key_size']) && !empty($_data['dkim_selector'])) { + if (!empty($redis->hGet('DKIM_SELECTORS', $domain))) { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_add_dkim_available' + ); + } + else { + dkim('add', array('key_size' => $_data['key_size'], 'dkim_selector' => $_data['dkim_selector'], 'domains' => $domain)); + } + } if (!empty($restart_sogo)) { - $restart_reponse = json_decode(docker('post', 'sogo-mailcow', 'restart'), true); - if ($restart_reponse['type'] == "success") { + $restart_response = json_decode(docker('post', 'sogo-mailcow', 'restart'), true); + if ($restart_response['type'] == "success") { $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -445,12 +698,25 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return true; break; case 'alias': - $addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['address'])); - $gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto'])); - $active = intval($_data['active']); - $goto_null = intval($_data['goto_null']); - $goto_spam = intval($_data['goto_spam']); - $goto_ham = intval($_data['goto_ham']); + $addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['address'])); + $gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto'])); + $internal = intval($_data['internal']); + $active = intval($_data['active']); + $sender_allowed = intval($_data['sender_allowed']); + $sogo_visible = intval($_data['sogo_visible']); + $goto_null = intval($_data['goto_null']); + $goto_spam = intval($_data['goto_spam']); + $goto_ham = intval($_data['goto_ham']); + $private_comment = $_data['private_comment']; + $public_comment = $_data['public_comment']; + if (strlen($private_comment) > 160 | strlen($public_comment) > 160){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'comment_too_long' + ); + return false; + } if (empty($addresses[0])) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -481,7 +747,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (empty($goto)) { continue; } - $goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1)); + $goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); $goto_local_part = strstr($goto, '@', true); $goto = $goto_local_part.'@'.$goto_domain; $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` @@ -508,9 +774,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { continue; } } + $gotos = array_unique($gotos); $gotos = array_filter($gotos); if (empty($gotos)) { return false; } - $goto = implode(",", $gotos); + $goto = implode(",", (array)$gotos); } foreach ($addresses as $address) { if (empty($address)) { @@ -519,7 +786,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (in_array($address, $gotos)) { continue; } - $domain = idn_to_ascii(substr(strstr($address, '@'), 1)); + $domain = idn_to_ascii(substr(strstr($address, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); $local_part = strstr($address, '@', true); $address = $local_part.'@'.$domain; $domaindata = mailbox('get', 'domain_details', $domain); @@ -549,7 +816,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('is_alias_or_mailbox', htmlspecialchars($address)) ); - return false; + continue; } $stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)"); @@ -561,7 +828,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('domain_not_found', htmlspecialchars($domain)) ); - return false; + continue; } $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` WHERE `address`= :address"); @@ -573,15 +840,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('is_spam_alias', htmlspecialchars($address)) ); - return false; + continue; } if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'alias_invalid' + 'msg' => array('alias_invalid', $address) ); - return false; + continue; } if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { $_SESSION['return'][] = array( @@ -589,38 +856,69 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => 'access_denied' ); - return false; + continue; } - $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `active`) - VALUES (:address, :goto, :domain, :active)"); + $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `internal`, `sender_allowed`, `active`) + VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :internal, :sender_allowed, :active)"); if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) { $stmt->execute(array( + ':address' => '@'.$domain, + ':public_comment' => $public_comment, + ':private_comment' => $private_comment, ':address' => '@'.$domain, ':goto' => $goto, ':domain' => $domain, + ':sogo_visible' => $sogo_visible, + ':internal' => $internal, + ':sender_allowed' => $sender_allowed, ':active' => $active )); } else { $stmt->execute(array( ':address' => $address, + ':public_comment' => $public_comment, + ':private_comment' => $private_comment, ':goto' => $goto, ':domain' => $domain, + ':sogo_visible' => $sogo_visible, + ':internal' => $internal, + ':sender_allowed' => $sender_allowed, ':active' => $active )); } + $id = $pdo->lastInsertId(); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('alias_added', $address) + 'msg' => array('alias_added', $address, $id) ); + + // Track affected mailboxes for SOGo update + if (!empty($goto)) { + $gotos = array_map('trim', explode(',', $goto)); + foreach ($gotos as $g) { + if (filter_var($g, FILTER_VALIDATE_EMAIL) && + !in_array($g, array('null@localhost', 'spam@localhost', 'ham@localhost'))) { + $update_sogo_mailboxes[] = $g; + } + } + } } break; case 'alias_domain': $active = intval($_data['active']); $alias_domains = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['alias_domain'])); $alias_domains = array_filter($alias_domains); - $target_domain = idn_to_ascii(strtolower(trim($_data['target_domain']))); + $target_domain = idn_to_ascii(strtolower(trim($_data['target_domain'])), 0, INTL_IDNA_VARIANT_UTS46); + if (!isset($_SESSION['acl']['alias_domains']) || $_SESSION['acl']['alias_domains'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } if (!is_valid_domain_name($target_domain)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -638,7 +936,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } foreach ($alias_domains as $i => $alias_domain) { - $alias_domain = idn_to_ascii(strtolower(trim($alias_domain))); + $alias_domain = idn_to_ascii(strtolower(trim($alias_domain)), 0, INTL_IDNA_VARIANT_UTS46); if (!is_valid_domain_name($alias_domain)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -667,6 +965,18 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain`= :target_domain AND `backupmx` = '1'"); + $stmt->execute(array(':target_domain' => $target_domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 1) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('targetd_relay_domain', htmlspecialchars($target_domain)) + ); + continue; + } $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain`= :alias_domain UNION SELECT `domain` FROM `domain` WHERE `domain`= :alias_domain_in_domain"); @@ -680,6 +990,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `send_as` LIKE :domain"); + $stmt->execute(array( + ':domain' => '%@' . $domain + )); $stmt = $pdo->prepare("INSERT INTO `alias_domain` (`alias_domain`, `target_domain`, `active`) VALUES (:alias_domain, :target_domain, :active)"); $stmt->execute(array( @@ -698,6 +1012,21 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } + if (!empty(intval($_data['rl_value']))) { + ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $alias_domain)); + } + if (!empty($_data['key_size']) && !empty($_data['dkim_selector'])) { + if (!empty($redis->hGet('DKIM_SELECTORS', $alias_domain))) { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_add_dkim_available' + ); + } + else { + dkim('add', array('key_size' => $_data['key_size'], 'dkim_selector' => $_data['dkim_selector'], 'domains' => $alias_domain)); + } + } $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -707,8 +1036,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { break; case 'mailbox': $local_part = strtolower(trim($_data['local_part'])); - $domain = idn_to_ascii(strtolower(trim($_data['domain']))); + $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); $username = $local_part . '@' . $domain; + $authsource = 'mailcow'; if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -725,22 +1055,103 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $password = $_data['password']; - $password2 = $_data['password2']; - $name = $_data['name']; - $quota_m = filter_var($_data['quota'], FILTER_SANITIZE_NUMBER_FLOAT); + if ($_data['authsource'] == "mailcow" || + in_array($_data['authsource'], array('keycloak', 'generic-oidc', 'ldap')) && $iam_settings['authsource'] == $_data['authsource']){ + $authsource = $_data['authsource']; + } if (empty($name)) { $name = $local_part; } - $active = intval($_data['active']); - $quota_b = ($quota_m * 1048576); - $maildir = $domain . "/" . $local_part . "/"; + $template_attr = null; + if ($_data['template']){ + $template_attr = mailbox('get', 'mailbox_templates', $_data['template'], $_extra)['attributes']; + } + if (empty($template_attr)) { + $template_attr = mailbox('get', 'mailbox_templates', null, $_extra)[0]['attributes']; + } + $MAILBOX_DEFAULT_ATTRIBUTES = array_merge($MAILBOX_DEFAULT_ATTRIBUTES, $template_attr); + + $password = $_data['password']; + $password2 = $_data['password2']; + $name = ltrim(rtrim($_data['name'], '>'), '<'); + $tags = (isset($_data['tags'])) ? $_data['tags'] : $MAILBOX_DEFAULT_ATTRIBUTES['tags']; + $quota_m = (isset($_data['quota'])) ? intval($_data['quota']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['quota']) / 1024 ** 2; + if ($authsource != 'mailcow'){ + $password = ''; + $password2 = ''; + $password_hashed = ''; + } + if (!hasACLAccess("unlimited_quota") && $quota_m === 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'unlimited_quota_acl' + ); + return false; + } + + if (isset($_data['protocol_access'])) { + $_data['protocol_access'] = (array)$_data['protocol_access']; + $_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0; + $_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; + $_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; + $_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; + $_data['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0; + $_data['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0; + } + $active = (isset($_data['active'])) ? intval($_data['active']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['active']); + $force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']); + $force_tfa = (isset($_data['force_tfa'])) ? intval($_data['force_tfa']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_tfa']); + $tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']); + $tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']); + $sogo_access = (isset($_data['sogo_access'])) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']); + $imap_access = (isset($_data['imap_access'])) ? intval($_data['imap_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']); + $pop3_access = (isset($_data['pop3_access'])) ? intval($_data['pop3_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); + $smtp_access = (isset($_data['smtp_access'])) ? intval($_data['smtp_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']); + $sieve_access = (isset($_data['sieve_access'])) ? intval($_data['sieve_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']); + $eas_access = (isset($_data['eas_access'])) ? intval($_data['eas_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['eas_access']); + $dav_access = (isset($_data['dav_access'])) ? intval($_data['dav_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['dav_access']); + $relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : 0; + $quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']); + $quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']); + // Validate quarantine_category + if (!in_array($quarantine_category, array('add_header', 'reject', 'all'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'quarantine_category_invalid' + ); + return false; + } + $quota_b = ($quota_m * 1048576); + $attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : ''; + if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){ + $force_pw_update = 0; + } + if ($authsource == 'generic-oidc'){ + $force_tfa = 0; + } $mailbox_attrs = json_encode( array( - 'force_pw_update' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'])), - 'tls_enforce_in' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in'])), - 'tls_enforce_out' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']))) - ); + 'force_pw_update' => strval($force_pw_update), + 'force_tfa' => strval($force_tfa), + 'tls_enforce_in' => strval($tls_enforce_in), + 'tls_enforce_out' => strval($tls_enforce_out), + 'sogo_access' => strval($sogo_access), + 'imap_access' => strval($imap_access), + 'pop3_access' => strval($pop3_access), + 'smtp_access' => strval($smtp_access), + 'sieve_access' => strval($sieve_access), + 'eas_access' => strval($eas_access), + 'dav_access' => strval($dav_access), + 'relayhost' => strval($relayhost), + 'passwd_update' => time(), + 'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']), + 'quarantine_notification' => strval($quarantine_notification), + 'quarantine_category' => strval($quarantine_category), + 'attribute_hash' => $attribute_hash + ) + ); if (!is_valid_domain_name($domain)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -761,11 +1172,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { WHERE `domain` = :domain"); $stmt->execute(array(':domain' => $domain)); $DomainData = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt = $pdo->prepare("SELECT + $stmt = $pdo->prepare("SELECT COUNT(*) as count, COALESCE(ROUND(SUM(`quota`)/1048576), 0) as `quota` FROM `mailbox` - WHERE `kind` NOT REGEXP 'location|thing|group' + WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain"); $stmt->execute(array(':domain' => $domain)); $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); @@ -813,41 +1224,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - if (!is_numeric($quota_m) || $quota_m == "0") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'quota_not_0_not_numeric' - ); - return false; - } - if (!empty($password) && !empty($password2)) { - if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'password_complexity' - ); - return false; - } - if ($password != $password2) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'password_mismatch' - ); + if ($authsource == 'mailcow'){ + if (password_check($password, $password2) !== true) { return false; } $password_hashed = hash_password($password); } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'password_empty' - ); - return false; - } if ($MailboxData['count'] >= $DomainData['mailboxes']) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -873,42 +1255,271 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `attributes`, `active`) - VALUES (:username, :password_hashed, :name, :maildir, :quota_b, :local_part, :domain, :mailbox_attrs, :active)"); + $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `attributes`, `authsource`, `active`) + VALUES (:username, :password_hashed, :name, :quota_b, :local_part, :domain, :mailbox_attrs, :authsource, :active)"); $stmt->execute(array( ':username' => $username, ':password_hashed' => $password_hashed, ':name' => $name, - ':maildir' => $maildir, ':quota_b' => $quota_b, ':local_part' => $local_part, ':domain' => $domain, ':mailbox_attrs' => $mailbox_attrs, + ':authsource' => $authsource, ':active' => $active )); - $stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`) - VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';"); - $stmt->execute(array(':username' => $username)); - $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `active`) - VALUES (:username1, :username2, :domain, :active)"); - $stmt->execute(array( - ':username1' => $username, - ':username2' => $username, - ':domain' => $domain, - ':active' => $active - )); - $stmt = $pdo->prepare("INSERT INTO `user_acl` (`username`) VALUES (:username)"); + $stmt = $pdo->prepare("UPDATE `mailbox` SET + `attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW()) + WHERE `username` = :username"); $stmt->execute(array( ':username' => $username )); - $_SESSION['return'][] = array( + // save delimiter_action + if (isset($_data['tagged_mail_handler'])) { + mailbox('edit', 'delimiter_action', array( + 'username' => $username, + 'tagged_mail_handler' => $_data['tagged_mail_handler'] + )); + } + + // save tags + foreach($tags as $index => $tag){ + if (empty($tag)) continue; + if ($index > $GLOBALS['TAGGING_LIMIT']) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) + ); + break; + } + try { + $stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)"); + $stmt->execute(array( + ':username' => $username, + ':tag_name' => $tag, + )); + } catch (Exception $e) { + } + } + $stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`) + VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';"); + $stmt->execute(array(':username' => $username)); + $stmt = $pdo->prepare("INSERT INTO `quota2replica` (`username`, `bytes`, `messages`) + VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';"); + $stmt->execute(array(':username' => $username)); + $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `active`) + VALUES (:username1, :username2, :domain, :active)"); + $stmt->execute(array( + ':username1' => $username, + ':username2' => $username, + ':domain' => $domain, + ':active' => $active + )); + + + if (isset($_data['acl'])) { + $_data['acl'] = (array)$_data['acl']; + $_data['spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0; + $_data['tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0; + $_data['spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0; + $_data['spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0; + $_data['delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0; + $_data['syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0; + $_data['eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0; + $_data['sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0; + $_data['pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0; + $_data['quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0; + $_data['quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0; + $_data['quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0; + $_data['quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0; + $_data['app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0; + $_data['pw_reset'] = (in_array('pw_reset', $_data['acl'])) ? 1 : 0; + } else { + $_data['spam_alias'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_alias']); + $_data['tls_policy'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_tls_policy']); + $_data['spam_score'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_score']); + $_data['spam_policy'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_policy']); + $_data['delimiter_action'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_delimiter_action']); + $_data['syncjobs'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_syncjobs']); + $_data['eas_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_eas_reset']); + $_data['sogo_profile_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_sogo_profile_reset']); + $_data['pushover'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_pushover']); + $_data['quarantine'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine']); + $_data['quarantine_attachments'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_attachments']); + $_data['quarantine_notification'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_notification']); + $_data['quarantine_category'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_category']); + $_data['app_passwds'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_app_passwds']); + $_data['pw_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_pw_reset']); + } + + try { + $stmt = $pdo->prepare("INSERT INTO `user_acl` + (`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`, + `pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`, `pw_reset`) + VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset, + :pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds, :pw_reset) "); + $stmt->execute(array( + ':username' => $username, + ':spam_alias' => $_data['spam_alias'], + ':tls_policy' => $_data['tls_policy'], + ':spam_score' => $_data['spam_score'], + ':spam_policy' => $_data['spam_policy'], + ':delimiter_action' => $_data['delimiter_action'], + ':syncjobs' => $_data['syncjobs'], + ':eas_reset' => $_data['eas_reset'], + ':sogo_profile_reset' => $_data['sogo_profile_reset'], + ':pushover' => $_data['pushover'], + ':quarantine' => $_data['quarantine'], + ':quarantine_attachments' => $_data['quarantine_attachments'], + ':quarantine_notification' => $_data['quarantine_notification'], + ':quarantine_category' => $_data['quarantine_category'], + ':app_passwds' => $_data['app_passwds'], + ':pw_reset' => $_data['pw_reset'] + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => $e->getMessage() + ); + return false; + } + + $_data['rl_frame'] = (isset($_data['rl_frame'])) ? $_data['rl_frame'] : $MAILBOX_DEFAULT_ATTRIBUTES['rl_frame']; + $_data['rl_value'] = (isset($_data['rl_value'])) ? $_data['rl_value'] : $MAILBOX_DEFAULT_ATTRIBUTES['rl_value']; + if (isset($_data['rl_frame']) && isset($_data['rl_value'])){ + ratelimit('edit', 'mailbox', array( + 'object' => $username, + 'rl_frame' => $_data['rl_frame'], + 'rl_value' => $_data['rl_value'] + ), $_extra); + } + + // Track affected mailboxes for SOGo update + $update_sogo_mailboxes[] = $username; + $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('mailbox_added', htmlspecialchars($username)) ); break; + case 'mailbox_from_template': + $stmt = $pdo->prepare("SELECT * FROM `templates` + WHERE `template` = :template AND type = 'mailbox'"); + $stmt->execute(array( + ":template" => $_data['template'] + )); + $mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($mbox_template_data)){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'template_missing' + ); + return false; + } + + $attribute_hash = sha1(json_encode($mbox_template_data["attributes"])); + $mbox_template_data = json_decode($mbox_template_data["attributes"], true); + $mbox_template_data['domain'] = $_data['domain']; + $mbox_template_data['name'] = $_data['name']; + $mbox_template_data['local_part'] = $_data['local_part']; + $mbox_template_data['authsource'] = $_data['authsource']; + $mbox_template_data['attribute_hash'] = $attribute_hash; + $mbox_template_data['quota'] = intval($mbox_template_data['quota'] / 1048576); + + $mailbox_attributes = array('acl' => array()); + foreach ($mbox_template_data as $key => $value){ + switch (true) { + case (strpos($key, 'acl_') === 0 && $value != 0): + array_push($mailbox_attributes['acl'], str_replace('acl_' , '', $key)); + break; + default: + $mailbox_attributes[$key] = $value; + break; + } + } + + return mailbox('add', 'mailbox', $mailbox_attributes); + break; + case 'mta_sts': + $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); + $version = strtolower($_data['version']); + $mode = strtolower($_data['mode']); + $mx = explode(",", preg_replace('/\s+/', '', $_data['mx'])); + $max_age = intval($_data['max_age']); + $active = (intval($_data['active']) == 1) ? 1 : 0; + $id = date('YmdHis'); + + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + if (empty($version) || !in_array($version, array('stsv1'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr), + 'msg' => array('version_invalid', htmlspecialchars($domain)) + ); + return false; + } + if (empty($mode) || !in_array($mode, array('enforce', 'testing', 'none'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr), + 'msg' => array('mode_invalid', htmlspecialchars($domain)) + ); + return false; + } + if (empty($max_age) || $max_age < 0 || $max_age > 31536000) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr), + 'msg' => array('max_age_invalid', htmlspecialchars($domain)) + ); + return false; + } + foreach ($mx as $index => $mx_domain) { + $mx_domain = idn_to_ascii(strtolower(trim($mx_domain)), 0, INTL_IDNA_VARIANT_UTS46); + if (!is_valid_domain_name($mx_domain, array('allow_wildcard' => true))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr), + 'msg' => array('mx_invalid', htmlspecialchars($mx_domain)) + ); + return false; + } + } + + try { + $stmt = $pdo->prepare("INSERT INTO `mta_sts` (`id`, `domain`, `version`, `mode`, `mx`, `max_age`, `active`) + VALUES (:id, :domain, :version, :mode, :mx, :max_age, :active)"); + $stmt->execute(array( + ':id' => $id, + ':domain' => $domain, + ':version' => $version, + ':mode' => $mode, + ':mx' => implode(",", $mx), + ':max_age' => $max_age, + ':active' => $active + )); + } catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => $e->getMessage() + ); + return false; + } + break; case 'resource': - $domain = idn_to_ascii(strtolower(trim($_data['domain']))); + $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); $description = $_data['description']; $local_part = preg_replace('/[^\da-z]/i', '', preg_quote($description, '/')); $name = $local_part . '@' . $domain; @@ -1002,8 +1613,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `active`, `multiple_bookings`, `kind`) - VALUES (:name, 'RESOURCE', :description, 'RESOURCE', 0, :local_part, :domain, :active, :multiple_bookings, :kind)"); + $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `active`, `multiple_bookings`, `kind`) + VALUES (:name, 'RESOURCE', :description, 0, :local_part, :domain, :active, :multiple_bookings, :kind)"); $stmt->execute(array( ':name' => $name, ':description' => $description, @@ -1018,6 +1629,209 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('resource_added', htmlspecialchars($name)) ); + + // Track affected mailboxes for SOGo update + $update_sogo_mailboxes[] = $name; + break; + case 'domain_templates': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'access_denied' + ); + return false; + } + if (empty($_data["template"])){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'template_name_invalid' + ); + return false; + } + + // check if template name exists, return false + $stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template"); + $stmt->execute(array( + ":type" => "domain", + ":template" => $_data["template"] + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!empty($row)){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => array('template_exists', $_data["template"]) + ); + return false; + } + + // check attributes + $attr = array(); + $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); + $attr['max_num_aliases_for_domain'] = (!empty($_data['max_num_aliases_for_domain'])) ? intval($_data['max_num_aliases_for_domain']) : 400; + $attr['max_num_mboxes_for_domain'] = (!empty($_data['max_num_mboxes_for_domain'])) ? intval($_data['max_num_mboxes_for_domain']) : 10; + $attr['def_quota_for_mbox'] = (!empty($_data['def_quota_for_mbox'])) ? intval($_data['def_quota_for_mbox']) * 1048576 : 3072 * 1048576; + $attr['max_quota_for_mbox'] = (!empty($_data['max_quota_for_mbox'])) ? intval($_data['max_quota_for_mbox']) * 1048576 : 10240 * 1048576; + $attr['max_quota_for_domain'] = (!empty($_data['max_quota_for_domain'])) ? intval($_data['max_quota_for_domain']) * 1048576 : 10240 * 1048576; + $attr['rl_frame'] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s"; + $attr['rl_value'] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : ""; + $attr['active'] = isset($_data['active']) ? intval($_data['active']) : 1; + $attr['gal'] = (isset($_data['gal'])) ? intval($_data['gal']) : 1; + $attr['backupmx'] = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0; + $attr['relay_all_recipients'] = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0; + $attr['relay_unknown_only'] = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0; + $attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim"; + $attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048; + + // save template + $stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`) + VALUES (:type, :template, :attributes)"); + $stmt->execute(array( + ":type" => "domain", + ":template" => $_data["template"], + ":attributes" => json_encode($attr) + )); + + // success + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('template_added', $_data["template"]) + ); + return true; + break; + case 'mailbox_templates': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'access_denied' + ); + return false; + } + if (empty($_data["template"])){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'template_name_invalid' + ); + return false; + } + + // check if template name exists, return false + $stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template"); + $stmt->execute(array( + ":type" => "mailbox", + ":template" => $_data["template"] + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($row)){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => array('template_exists', $_data["template"]) + ); + return false; + } + + + // check attributes + $attr = array(); + $attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0; + $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); + $attr["tagged_mail_handler"] = (!empty($_data['tagged_mail_handler'])) ? $_data['tagged_mail_handler'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['tagged_mail_handler']); + $attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']); + $attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']); + // Validate quarantine_category + if (!in_array($attr["quarantine_category"], array('add_header', 'reject', 'all'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'quarantine_category_invalid' + ); + return false; + } + $attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s"; + $attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : ""; + $attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']); + $attr["force_tfa"] = isset($_data['force_tfa']) ? intval($_data['force_tfa']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_tfa']); + $attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']); + $attr["active"] = isset($_data['active']) ? intval($_data['active']) : 1; + $attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']); + $attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']); + if (isset($_data['protocol_access'])) { + $_data['protocol_access'] = (array)$_data['protocol_access']; + $attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0; + $attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; + $attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; + $attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; + $attr['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0; + $attr['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0; + } + else { + $attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']); + $attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); + $attr['smtp_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']); + $attr['sieve_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']); + $attr['eas_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['eas_access']); + $attr['dav_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['dav_access']); + } + if (isset($_data['acl'])) { + $_data['acl'] = (array)$_data['acl']; + $attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0; + $attr['acl_tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0; + $attr['acl_spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0; + $attr['acl_spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0; + $attr['acl_delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0; + $attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0; + $attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0; + $attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0; + $attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0; + $attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0; + $attr['acl_pw_reset'] = (in_array('pw_reset', $_data['acl'])) ? 1 : 0; + } else { + $_data['acl'] = (array)$_data['acl']; + $attr['acl_spam_alias'] = 0; + $attr['acl_tls_policy'] = 0; + $attr['acl_spam_score'] = 0; + $attr['acl_spam_policy'] = 0; + $attr['acl_delimiter_action'] = 0; + $attr['acl_syncjobs'] = 0; + $attr['acl_eas_reset'] = 0; + $attr['acl_sogo_profile_reset'] = 0; + $attr['acl_pushover'] = 0; + $attr['acl_quarantine'] = 0; + $attr['acl_quarantine_attachments'] = 0; + $attr['acl_quarantine_notification'] = 0; + $attr['acl_quarantine_category'] = 0; + $attr['acl_app_passwds'] = 0; + } + + + + // save template + $stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`) + VALUES (:type, :template, :attributes)"); + $stmt->execute(array( + ":type" => "mailbox", + ":template" => $_data["template"], + ":attributes" => json_encode($attr) + )); + + // success + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('template_added', $_data["template"]) + ); + return true; break; } break; @@ -1026,11 +1840,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { case 'alias_domain': $alias_domains = (array)$_data['alias_domain']; foreach ($alias_domains as $alias_domain) { - $alias_domain = idn_to_ascii(strtolower(trim($alias_domain))); + $alias_domain = idn_to_ascii(strtolower(trim($alias_domain)), 0, INTL_IDNA_VARIANT_UTS46); $is_now = mailbox('get', 'alias_domain_details', $alias_domain); if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; - $target_domain = (!empty($_data['target_domain'])) ? idn_to_ascii(strtolower(trim($_data['target_domain']))) : $is_now['target_domain']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $target_domain = (!empty($_data['target_domain'])) ? idn_to_ascii(strtolower(trim($_data['target_domain'])), 0, INTL_IDNA_VARIANT_UTS46) : $is_now['target_domain']; } else { $_SESSION['return'][] = array( @@ -1088,7 +1902,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { else { $usernames = $_data['username']; } - if (!isset($_SESSION['acl']['tls_policy']) || $_SESSION['acl']['tls_policy'] != "1" ) { + if (!hasACLAccess("tls_policy")) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1097,7 +1911,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } foreach ($usernames as $username) { - if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1105,7 +1919,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $is_now = mailbox('get', 'tls_policy', $username); + $is_now = mailbox('get', 'tls_policy', $username, $_extra); if (!empty($is_now)) { $tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in']; $tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out']; @@ -1134,7 +1948,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); } break; - case 'spam_score': + case 'quarantine_notification': if (!is_array($_data['username'])) { $usernames = array(); $usernames[] = $_data['username']; @@ -1142,7 +1956,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { else { $usernames = $_data['username']; } - if (!isset($_SESSION['acl']['spam_score']) || $_SESSION['acl']['spam_score'] != "1" ) { + if (!hasACLAccess("quarantine_notification")) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1151,9 +1965,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } foreach ($usernames as $username) { - $lowspamlevel = explode(',', $_data['spam_score'])[0]; - $highspamlevel = explode(',', $_data['spam_score'])[1]; - if (!is_numeric($lowspamlevel) || !is_numeric($highspamlevel)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1161,23 +1973,33 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username - AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); + $is_now = mailbox('get', 'quarantine_notification', $username, $_extra); + if (!empty($is_now)) { + $quarantine_notification = (isset($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (!in_array($quarantine_notification, array('never', 'hourly', 'daily', 'weekly'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("UPDATE `mailbox` + SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', :quarantine_notification) + WHERE `username` = :username"); $stmt->execute(array( + ':quarantine_notification' => $quarantine_notification, ':username' => $username )); - $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option`, `value`) - VALUES (:username, 'highspamlevel', :highspamlevel)"); - $stmt->execute(array( - ':username' => $username, - ':highspamlevel' => $highspamlevel - )); - $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option`, `value`) - VALUES (:username, 'lowspamlevel', :lowspamlevel)"); - $stmt->execute(array( - ':username' => $username, - ':lowspamlevel' => $lowspamlevel - )); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1185,8 +2007,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); } break; - case 'time_limited_alias': - if (!isset($_SESSION['acl']['spam_alias']) || $_SESSION['acl']['spam_alias'] != "1" ) { + case 'quarantine_category': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + if (!hasACLAccess("quarantine_category")) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1194,18 +2023,28 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - if (!is_array($_data['address'])) { - $addresses = array(); - $addresses[] = $_data['address']; - } - else { - $addresses = $_data['address']; - } - foreach ($addresses as $address) { - $stmt = $pdo->prepare("SELECT `goto` FROM `spamalias` WHERE `address` = :address"); - $stmt->execute(array(':address' => $address)); - $goto = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $goto)) { + foreach ($usernames as $username) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $is_now = mailbox('get', 'quarantine_category', $username, $_extra); + if (!empty($is_now)) { + $quarantine_category = (isset($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (!in_array($quarantine_category, array('add_header', 'reject', 'all'))) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1213,21 +2052,21 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = (`validity` + 3600) WHERE - `address` = :address AND - `validity` >= :validity"); + $stmt = $pdo->prepare("UPDATE `mailbox` + SET `attributes` = JSON_SET(`attributes`, '$.quarantine_category', :quarantine_category) + WHERE `username` = :username"); $stmt->execute(array( - ':address' => $address, - ':validity' => time() + ':quarantine_category' => $quarantine_category, + ':username' => $username )); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', htmlspecialchars(implode(', ', $usernames))) + 'msg' => array('mailbox_modified', $username) ); } break; - case 'delimiter_action': + case 'spam_score': if (!is_array($_data['username'])) { $usernames = array(); $usernames[] = $_data['username']; @@ -1235,7 +2074,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { else { $usernames = $_data['username']; } - if (!isset($_SESSION['acl']['delimiter_action']) || $_SESSION['acl']['delimiter_action'] != "1" ) { + if (!isset($_SESSION['acl']['spam_score']) || $_SESSION['acl']['spam_score'] != "1" ) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1244,7 +2083,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } foreach ($usernames as $username) { - if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1252,25 +2091,153 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subject") { - try { - $redis->hSet('RCPT_WANTS_SUBJECT_TAG', $username, 1); - $redis->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('redis_error', $e) - ); - continue; - } - } - else if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subfolder") { - try { - $redis->hSet('RCPT_WANTS_SUBFOLDER_TAG', $username, 1); - $redis->hDel('RCPT_WANTS_SUBJECT_TAG', $username); - } + if ($_data['spam_score'] == "default") { + $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username + AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); + $stmt->execute(array( + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + continue; + } + $lowspamlevel = explode(',', $_data['spam_score'])[0]; + $highspamlevel = explode(',', $_data['spam_score'])[1]; + if (!is_numeric($lowspamlevel) || !is_numeric($highspamlevel)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Invalid spam score, format must be "1,2" where first is low and second is high spam value.' + ); + continue; + } + if ($lowspamlevel == $highspamlevel) { + $highspamlevel = $highspamlevel + 0.1; + } + $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username + AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option`, `value`) + VALUES (:username, 'highspamlevel', :highspamlevel)"); + $stmt->execute(array( + ':username' => $username, + ':highspamlevel' => $highspamlevel + )); + $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option`, `value`) + VALUES (:username, 'lowspamlevel', :lowspamlevel)"); + $stmt->execute(array( + ':username' => $username, + ':lowspamlevel' => $lowspamlevel + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + } + break; + case 'time_limited_alias': + if (!isset($_SESSION['acl']['spam_alias']) || $_SESSION['acl']['spam_alias'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['address'])) { + $addresses = array(); + $addresses[] = $_data['address']; + } + else { + $addresses = $_data['address']; + } + foreach ($addresses as $address) { + $stmt = $pdo->prepare("SELECT `goto` FROM `spamalias` WHERE `address` = :address"); + $stmt->execute(array(':address' => $address)); + $goto = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $goto)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (empty($_data['validity']) && empty($_data['permanent'])) { + continue; + } + if (isset($_data['permanent']) && filter_var($_data['permanent'], FILTER_VALIDATE_BOOL)) { + $permanent = 1; + $validity = 0; + } + else if (isset($_data['validity'])) { + $permanent = 0; + $validity = round((int)time() + ($_data['validity'] * 3600)); + } + $stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity, `permanent` = :permanent WHERE + `address` = :address"); + $stmt->execute(array( + ':address' => $address, + ':validity' => $validity, + ':permanent' => $permanent + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', htmlspecialchars(implode(', ', (array)$usernames))) + ); + } + break; + case 'delimiter_action': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + if (!isset($_SESSION['acl']['delimiter_action']) || $_SESSION['acl']['delimiter_action'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($usernames as $username) { + if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subject") { + try { + $redis->hSet('RCPT_WANTS_SUBJECT_TAG', $username, 1); + $redis->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + else if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subfolder") { + try { + $redis->hSet('RCPT_WANTS_SUBFOLDER_TAG', $username, 1); + $redis->hDel('RCPT_WANTS_SUBJECT_TAG', $username); + } catch (RedisException $e) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -1322,10 +2289,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (!empty($is_now)) { $username = $is_now['user2']; $user1 = (!empty($_data['user1'])) ? $_data['user1'] : $is_now['user1']; - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; $last_run = (isset($_data['last_run'])) ? NULL : $is_now['last_run']; + $success = (isset($_data['success'])) ? NULL : $is_now['success']; $delete2duplicates = (isset($_data['delete2duplicates'])) ? intval($_data['delete2duplicates']) : $is_now['delete2duplicates']; $subscribeall = (isset($_data['subscribeall'])) ? intval($_data['subscribeall']) : $is_now['subscribeall']; + $dry = (isset($_data['dry'])) ? intval($_data['dry']) : $is_now['dry']; $delete1 = (isset($_data['delete1'])) ? intval($_data['delete1']) : $is_now['delete1']; $delete2 = (isset($_data['delete2'])) ? intval($_data['delete2']) : $is_now['delete2']; $automap = (isset($_data['automap'])) ? intval($_data['automap']) : $is_now['automap']; @@ -1351,6 +2320,38 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } + + // validate custom params + foreach (explode('-', $custom_params) as $param){ + if(empty($param)) continue; + + // extract option + if (str_contains($param, '=')) $param = explode('=', $param)[0]; + else $param = rtrim($param, ' '); + // remove first char if first char is - + if ($param[0] == '-') $param = ltrim($param, $param[0]); + + if (str_contains($param, ' ')) { + // bad char + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'bad character SPACE' + ); + return false; + } + + // check if param is whitelisted + if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){ + // bad option + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'bad option '. $param + ); + return false; + } + } if (empty($subfolder2)) { $subfolder2 = ""; } @@ -1374,7 +2375,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 3600)))) { + if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 43800)))) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1416,6 +2417,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `exclude` = :exclude, `host1` = :host1, `last_run` = :last_run, + `success` = :success, `user1` = :user1, `password1` = :password1, `mins_interval` = :mins_interval, @@ -1426,6 +2428,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `timeout1` = :timeout1, `timeout2` = :timeout2, `subscribeall` = :subscribeall, + `dry` = :dry, `active` = :active WHERE `id` = :id"); $stmt->execute(array( @@ -1442,6 +2445,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':user1' => $user1, ':password1' => $password1, ':last_run' => $last_run, + ':success' => $success, ':mins_interval' => $mins_interval, ':port1' => $port1, ':enc1' => $enc1, @@ -1450,6 +2454,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':timeout1' => $timeout1, ':timeout2' => $timeout2, ':subscribeall' => $subscribeall, + ':dry' => $dry, ':active' => $active, )); $_SESSION['return'][] = array( @@ -1480,7 +2485,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $is_now = mailbox('get', 'filter_details', $id); if (!empty($is_now)) { $username = $is_now['username']; - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; $script_desc = (!empty($_data['script_desc'])) ? $_data['script_desc'] : $is_now['script_desc']; $script_data = (!empty($_data['script_data'])) ? $_data['script_data'] : $is_now['script_data']; $filter_type = (!empty($_data['filter_type'])) ? $_data['filter_type'] : $is_now['filter_type']; @@ -1553,10 +2558,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { foreach ($ids as $id) { $is_now = mailbox('get', 'alias_details', $id); if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + $internal = (isset($_data['internal'])) ? intval($_data['internal']) : $is_now['internal']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $sender_allowed = (isset($_data['sender_allowed'])) ? intval($_data['sender_allowed']) : $is_now['sender_allowed']; + $sogo_visible = (isset($_data['sogo_visible'])) ? intval($_data['sogo_visible']) : $is_now['sogo_visible']; $goto_null = (isset($_data['goto_null'])) ? intval($_data['goto_null']) : 0; $goto_spam = (isset($_data['goto_spam'])) ? intval($_data['goto_spam']) : 0; $goto_ham = (isset($_data['goto_ham'])) ? intval($_data['goto_ham']) : 0; + $public_comment = (isset($_data['public_comment'])) ? $_data['public_comment'] : $is_now['public_comment']; + $private_comment = (isset($_data['private_comment'])) ? $_data['private_comment'] : $is_now['private_comment']; $goto = (!empty($_data['goto'])) ? $_data['goto'] : $is_now['goto']; $address = (!empty($_data['address'])) ? $_data['address'] : $is_now['address']; } @@ -1564,12 +2574,58 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'alias_invalid' + 'msg' => array('alias_invalid', $address) + ); + continue; + } + if ($_data['expand_alias'] === true || $_data['expand_alias'] == 1) { + $stmt = $pdo->prepare("SELECT `address` FROM `alias` + WHERE `address` = :address + AND `domain` NOT IN ( + SELECT `alias_domain` FROM `alias_domain` + )"); + $stmt->execute(array( + ':address' => $address, + )); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('is_not_primary_alias', htmlspecialchars($address)) + ); + continue; + } + $stmt = $pdo->prepare("SELECT `goto`, GROUP_CONCAT(CONCAT(SUBSTRING(`alias`.`address`, 1, LOCATE('@', `alias`.`address`) - 1), '@', `alias_domain`.`alias_domain`)) AS `missing_alias` + FROM `alias` JOIN `alias_domain` ON `alias_domain`.`target_domain` = `alias`.`domain` + WHERE CONCAT(SUBSTRING(`alias`.`address`, 1, LOCATE('@', `alias`.`address`) - 1), '@', `alias_domain`.`alias_domain`) NOT IN ( + SELECT `address` FROM `alias` WHERE `address` != `goto` + ) + AND `alias`.`address` NOT IN ( + SELECT `address` FROM `alias` WHERE `address` = `goto` + ) + AND `address` = :address ;"); + $stmt->execute(array( + ':address' => $address + )); + $missing_aliases = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($missing_aliases['missing_alias'])) { + mailbox('add', 'alias', array( + 'address' => $missing_aliases['missing_alias'], + 'goto' => $missing_aliases['goto'], + 'sogo_visible' => 1, + 'active' => 1 + )); + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('alias_modified', htmlspecialchars($address)) ); continue; } + $domain = idn_to_ascii(substr(strstr($address, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); if ($is_now['address'] != $address) { - $domain = idn_to_ascii(substr(strstr($address, '@'), 1)); $local_part = strstr($address, '@', true); $address = $local_part.'@'.$domain; if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { @@ -1584,29 +2640,31 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'alias_invalid' + 'msg' => array('alias_invalid', $address) ); continue; } - $stmt = $pdo->prepare("SELECT `address` FROM `alias` - WHERE `address`= :address OR `address` IN ( - SELECT `username` FROM `mailbox`, `alias_domain` - WHERE ( - `alias_domain`.`alias_domain` = :address_d - AND `mailbox`.`username` = CONCAT(:address_l, '@', alias_domain.target_domain)))"); - $stmt->execute(array( - ':address' => $address, - ':address_l' => $local_part, - ':address_d' => $domain - )); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('is_alias_or_mailbox', htmlspecialchars($address)) - ); - continue; + if (strtolower($is_now['address']) != strtolower($address)) { + $stmt = $pdo->prepare("SELECT `address` FROM `alias` + WHERE `address`= :address OR `address` IN ( + SELECT `username` FROM `mailbox`, `alias_domain` + WHERE ( + `alias_domain`.`alias_domain` = :address_d + AND `mailbox`.`username` = CONCAT(:address_l, '@', alias_domain.target_domain)))"); + $stmt->execute(array( + ':address' => $address, + ':address_l' => $local_part, + ':address_d' => $domain + )); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('is_alias_or_mailbox', htmlspecialchars($address)) + ); + continue; + } } $stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)"); @@ -1666,21 +2724,42 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { unset($gotos[$i]); continue; } + // Delete from sender_acl to prevent duplicates + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE + `logged_in_as` = :goto AND + `send_as` = :address"); + $stmt->execute(array( + ':goto' => $goto, + ':address' => $address + )); } + $gotos = array_unique($gotos); $gotos = array_filter($gotos); - $goto = implode(",", $gotos); + $goto = implode(",", (array)$gotos); } if (!empty($goto)) { $stmt = $pdo->prepare("UPDATE `alias` SET `address` = :address, + `public_comment` = :public_comment, + `private_comment` = :private_comment, + `domain` = :domain, `goto` = :goto, + `sogo_visible`= :sogo_visible, + `internal`= :internal, + `sender_allowed`= :sender_allowed, `active`= :active WHERE `id` = :id"); $stmt->execute(array( ':address' => $address, + ':public_comment' => $public_comment, + ':private_comment' => $private_comment, + ':domain' => $domain, ':goto' => $goto, + ':sogo_visible' => $sogo_visible, + ':internal' => $internal, + ':sender_allowed' => $sender_allowed, ':active' => $active, - ':id' => $id + ':id' => $is_now['id'] )); } $_SESSION['return'][] = array( @@ -1688,6 +2767,28 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('alias_modified', htmlspecialchars($address)) ); + + // Track affected mailboxes for SOGo update (both old and new goto addresses) + // Old goto: to remove alias from their view + if (!empty($is_now['goto'])) { + $old_gotos = array_map('trim', explode(',', $is_now['goto'])); + foreach ($old_gotos as $g) { + if (filter_var($g, FILTER_VALIDATE_EMAIL) && + !in_array($g, array('null@localhost', 'spam@localhost', 'ham@localhost'))) { + $update_sogo_mailboxes[] = $g; + } + } + } + // New goto: to add alias to their view + if (!empty($goto)) { + $new_gotos = array_map('trim', explode(',', $goto)); + foreach ($new_gotos as $g) { + if (filter_var($g, FILTER_VALIDATE_EMAIL) && + !in_array($g, array('null@localhost', 'spam@localhost', 'ham@localhost'))) { + $update_sogo_mailboxes[] = $g; + } + } + } } break; case 'domain': @@ -1699,7 +2800,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $domains = $_data['domain']; } foreach ($domains as $domain) { - $domain = idn_to_ascii($domain); + $domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46); if (!is_valid_domain_name($domain)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -1710,15 +2811,49 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } if ($_SESSION['mailcow_cc_role'] == "domainadmin" && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $description = $_data['description']; - $active = intval($_data['active']); - $stmt = $pdo->prepare("UPDATE `domain` SET - `description` = :description + $is_now = mailbox('get', 'domain_details', $domain); + if (!empty($is_now)) { + $gal = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal']; + $description = (!empty($_data['description']) && isset($_SESSION['acl']['domain_desc']) && $_SESSION['acl']['domain_desc'] == "1") ? $_data['description'] : $is_now['description']; + (int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['domain_relayhost']) && $_SESSION['acl']['domain_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['relayhost']); + $tags = (is_array($_data['tags']) ? $_data['tags'] : array()); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + continue; + } + + $stmt = $pdo->prepare("UPDATE `domain` SET + `description` = :description, + `gal` = :gal WHERE `domain` = :domain"); $stmt->execute(array( ':description' => $description, + ':gal' => $gal, ':domain' => $domain )); + // save tags + foreach($tags as $index => $tag){ + if (empty($tag)) continue; + if ($index > $GLOBALS['TAGGING_LIMIT']) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) + ); + break; + } + $stmt = $pdo->prepare("INSERT INTO `tags_domain` (`domain`, `tag_name`) VALUES (:domain, :tag_name)"); + $stmt->execute(array( + ':domain' => $domain, + ':tag_name' => $tag, + )); + } + $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1728,16 +2863,26 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { elseif ($_SESSION['mailcow_cc_role'] == "admin") { $is_now = mailbox('get', 'domain_details', $domain); if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; - $backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $is_now['backupmx_int']; - $relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $is_now['relay_all_recipients_int']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $is_now['backupmx']; + $gal = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal']; + $relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $is_now['relay_all_recipients']; + $relay_unknown_only = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : $is_now['relay_unknown_only']; $relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : $is_now['relayhost']; $aliases = (!empty($_data['aliases'])) ? $_data['aliases'] : $is_now['max_num_aliases_for_domain']; - $mailboxes = (!empty($_data['mailboxes'])) ? $_data['mailboxes'] : $is_now['max_num_mboxes_for_domain']; + $mailboxes = (isset($_data['mailboxes']) && $_data['mailboxes'] != '') ? intval($_data['mailboxes']) : $is_now['max_num_mboxes_for_domain']; + $defquota = (isset($_data['defquota']) && $_data['defquota'] != '') ? intval($_data['defquota']) : ($is_now['def_quota_for_mbox'] / 1048576); $maxquota = (!empty($_data['maxquota'])) ? $_data['maxquota'] : ($is_now['max_quota_for_mbox'] / 1048576); $quota = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['max_quota_for_domain'] / 1048576); $description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description']; - ($relay_all_recipients == '1') ? $backupmx = '1' : null; + $tags = (is_array($_data['tags']) ? $_data['tags'] : array()); + if ($relay_all_recipients == '1') { + $backupmx = '1'; + } + if ($relay_unknown_only == '1') { + $backupmx = '1'; + $relay_all_recipients = '1'; + } } else { $_SESSION['return'][] = array( @@ -1748,12 +2893,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { continue; } // todo: should be using api here - $stmt = $pdo->prepare("SELECT + $stmt = $pdo->prepare("SELECT COUNT(*) AS count, MAX(COALESCE(ROUND(`quota`/1048576), 0)) AS `biggest_mailbox`, COALESCE(ROUND(SUM(`quota`)/1048576), 0) AS `quota_all` FROM `mailbox` - WHERE `kind` NOT REGEXP 'location|thing|group' + WHERE (`kind` = '' OR `kind` = NULL) AND domain = :domain"); $stmt->execute(array(':domain' => $domain)); $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); @@ -1765,6 +2910,22 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { )"); $stmt->execute(array(':domain' => $domain)); $AliasData = $stmt->fetch(PDO::FETCH_ASSOC); + if ($defquota > $maxquota) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'mailbox_defquota_exceeds_mailbox_maxquota' + ); + continue; + } + if ($defquota == "0" || empty($defquota)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'defquota_empty' + ); + continue; + } if ($maxquota > $quota) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -1813,11 +2974,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $stmt = $pdo->prepare("UPDATE `domain` SET + + $stmt = $pdo->prepare("UPDATE `domain` SET `relay_all_recipients` = :relay_all_recipients, + `relay_unknown_only` = :relay_unknown_only, `backupmx` = :backupmx, + `gal` = :gal, `active` = :active, `quota` = :quota, + `defquota` = :defquota, `maxquota` = :maxquota, `relayhost` = :relayhost, `mailboxes` = :mailboxes, @@ -1826,9 +2991,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { WHERE `domain` = :domain"); $stmt->execute(array( ':relay_all_recipients' => $relay_all_recipients, + ':relay_unknown_only' => $relay_unknown_only, ':backupmx' => $backupmx, + ':gal' => $gal, ':active' => $active, ':quota' => $quota, + ':defquota' => $defquota, ':maxquota' => $maxquota, ':relayhost' => $relayhost, ':mailboxes' => $mailboxes, @@ -1836,6 +3004,24 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':description' => $description, ':domain' => $domain )); + // save tags + foreach($tags as $index => $tag){ + if (empty($tag)) continue; + if ($index > $GLOBALS['TAGGING_LIMIT']) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) + ); + break; + } + $stmt = $pdo->prepare("INSERT INTO `tags_domain` (`domain`, `tag_name`) VALUES (:domain, :tag_name)"); + $stmt->execute(array( + ':domain' => $domain, + ':tag_name' => $tag, + )); + } + $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1844,6 +3030,79 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } } break; + case 'domain_templates': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['ids'])) { + $ids = array(); + $ids[] = $_data['ids']; + } + else { + $ids = $_data['ids']; + } + foreach ($ids as $id) { + $is_now = mailbox("get", "domain_templates", $id); + if (empty($is_now) || + $is_now["type"] != "domain"){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'template_id_invalid' + ); + continue; + } + + // check name + if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){ + // keep template name of Default template + $_data["template"] = $is_now["template"]; + } + else { + $_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"]; + } + // check attributes + $attr = array(); + $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); + $attr['max_num_aliases_for_domain'] = (isset($_data['max_num_aliases_for_domain'])) ? intval($_data['max_num_aliases_for_domain']) : 0; + $attr['max_num_mboxes_for_domain'] = (isset($_data['max_num_mboxes_for_domain'])) ? intval($_data['max_num_mboxes_for_domain']) : 0; + $attr['def_quota_for_mbox'] = (isset($_data['def_quota_for_mbox'])) ? intval($_data['def_quota_for_mbox']) * 1048576 : 0; + $attr['max_quota_for_mbox'] = (isset($_data['max_quota_for_mbox'])) ? intval($_data['max_quota_for_mbox']) * 1048576 : 0; + $attr['max_quota_for_domain'] = (isset($_data['max_quota_for_domain'])) ? intval($_data['max_quota_for_domain']) * 1048576 : 0; + $attr['rl_frame'] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s"; + $attr['rl_value'] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : ""; + $attr['active'] = isset($_data['active']) ? intval($_data['active']) : 1; + $attr['gal'] = (isset($_data['gal'])) ? intval($_data['gal']) : 1; + $attr['backupmx'] = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0; + $attr['relay_all_recipients'] = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0; + $attr['relay_unknown_only'] = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0; + $attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim"; + $attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048; + + // update template + $stmt = $pdo->prepare("UPDATE `templates` + SET `template` = :template, `attributes` = :attributes + WHERE id = :id"); + $stmt->execute(array( + ":id" => $id , + ":template" => $_data["template"] , + ":attributes" => json_encode($attr) + )); + } + + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('template_modified', $_data["template"]) + ); + return true; + break; case 'mailbox': if (!is_array($_data['username'])) { $usernames = array(); @@ -1861,16 +3120,48 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $is_now = mailbox('get', 'mailbox_details', $username); + $is_now = mailbox('get', 'mailbox_details', $username, $_extra); + if (isset($_data['protocol_access'])) { + $_data['protocol_access'] = (array)$_data['protocol_access']; + $_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0; + $_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; + $_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; + $_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; + $_data['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0; + $_data['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0; + } if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; (int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']); - $name = (!empty($_data['name'])) ? $_data['name'] : $is_now['name']; - $domain = $is_now['domain']; - $quota_m = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['quota'] / 1048576); - $quota_b = $quota_m * 1048576; - $password = (!empty($_data['password'])) ? $_data['password'] : null; - $password2 = (!empty($_data['password2'])) ? $_data['password2'] : null; + (int)$force_tfa = (isset($_data['force_tfa'])) ? intval($_data['force_tfa']) : intval($is_now['attributes']['force_tfa']); + (int)$sogo_access = (isset($_data['sogo_access']) && hasACLAccess("sogo_access")) ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']); + (int)$imap_access = (isset($_data['imap_access']) && hasACLAccess("protocol_access")) ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']); + (int)$pop3_access = (isset($_data['pop3_access']) && hasACLAccess("protocol_access")) ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']); + (int)$smtp_access = (isset($_data['smtp_access']) && hasACLAccess("protocol_access")) ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']); + (int)$sieve_access = (isset($_data['sieve_access']) && hasACLAccess("protocol_access")) ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']); + (int)$eas_access = (isset($_data['eas_access']) && hasACLAccess("protocol_access")) ? intval($_data['eas_access']) : intval($is_now['attributes']['eas_access']); + (int)$dav_access = (isset($_data['dav_access']) && hasACLAccess("protocol_access")) ? intval($_data['dav_access']) : intval($is_now['attributes']['dav_access']); + (int)$relayhost = (isset($_data['relayhost']) && hasACLAccess("mailbox_relayhost")) ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']); + (int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576); + $name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name']; + $domain = $is_now['domain']; + $quota_b = $quota_m * 1048576; + $password = (!empty($_data['password'])) ? $_data['password'] : null; + $password2 = (!empty($_data['password2'])) ? $_data['password2'] : null; + $tags = (is_array($_data['tags']) ? $_data['tags'] : array()); + $attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : ''; + $authsource = $is_now['authsource']; + if ($_data['authsource'] == "mailcow" || + in_array($_data['authsource'], array('keycloak', 'generic-oidc', 'ldap')) && $iam_settings['authsource'] == $_data['authsource']){ + $authsource = $_data['authsource']; + } + if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){ + $force_pw_update = 0; + } + if ($authsource == 'generic-oidc'){ + $force_tfa = 0; + } + $pw_recovery_email = (isset($_data['pw_recovery_email']) && $authsource == 'mailcow') ? $_data['pw_recovery_email'] : $is_now['attributes']['recovery_email']; } else { $_SESSION['return'][] = array( @@ -1880,48 +3171,116 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $stmt = $pdo->prepare("SELECT `quota`, `maxquota` - FROM `domain` - WHERE `domain` = :domain"); - $stmt->execute(array(':domain' => $domain)); - $DomainData = $stmt->fetch(PDO::FETCH_ASSOC); - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + // if already 0 == ok + if (!hasACLAccess("unlimited_quota") && ($quota_m == 0 && $is_now['quota'] != 0)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' + 'msg' => 'unlimited_quota_acl' ); - continue; + return false; } - if (!is_numeric($quota_m) || $quota_m == "0") { + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('quota_not_0_not_numeric', htmlspecialchars($quota_m)) + 'msg' => 'access_denied' ); continue; } - if ($quota_m > $DomainData['maxquota']) { + $DomainData = mailbox('get', 'domain_details', $domain, $_extra); + if ($quota_m > ($is_now['max_new_quota'] / 1048576)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_quota_exceeded', $DomainData['maxquota']) + 'msg' => array('mailbox_quota_left_exceeded', ($is_now['max_new_quota'] / 1048576)) ); continue; } - if (((($is_now['quota_used'] / 1048576) - $quota_m) + $quota_m) > $DomainData['quota']) { + if ($quota_m > $DomainData['max_quota_for_mbox']) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_quota_left_exceeded', ($is_now['max_new_quota'] / 1048576)) + 'msg' => array('mailbox_quota_exceeded', $DomainData['max_quota_for_mbox']) ); continue; } + $extra_acls = array(); + if (isset($_data['extended_sender_acl'])) { + if (!hasACLAccess("extend_sender_acl")) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'extended_sender_acl_denied' + ); + } + else { + $extra_acls = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['extended_sender_acl'])); + foreach ($extra_acls as $i => &$extra_acl) { + if (empty($extra_acl)) { + continue; + } + if (substr($extra_acl, 0, 1) === "@") { + $extra_acl = ltrim($extra_acl, '@'); + } + if (!filter_var($extra_acl, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name($extra_acl)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('extra_acl_invalid', htmlspecialchars($extra_acl)) + ); + unset($extra_acls[$i]); + continue; + } + $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); + if (filter_var($extra_acl, FILTER_VALIDATE_EMAIL)) { + $extra_acl_domain = idn_to_ascii(substr(strstr($extra_acl, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); + if (in_array($extra_acl_domain, $domains)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('extra_acl_invalid_domain', $extra_acl_domain) + ); + unset($extra_acls[$i]); + continue; + } + } + else { + if (in_array($extra_acl, $domains)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('extra_acl_invalid_domain', $extra_acl_domain) + ); + unset($extra_acls[$i]); + continue; + } + $extra_acl = '@' . $extra_acl; + } + } + $extra_acls = array_filter($extra_acls); + $extra_acls = array_values($extra_acls); + $extra_acls = array_unique($extra_acls); + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `logged_in_as` = :username"); + $stmt->execute(array( + ':username' => $username + )); + foreach ($extra_acls as $sender_acl_external) { + $stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`, `external`) + VALUES (:sender_acl, :username, 1)"); + $stmt->execute(array( + ':sender_acl' => $sender_acl_external, + ':username' => $username + )); + } + } + } if (isset($_data['sender_acl'])) { // Get sender_acl items set by admin + $current_sender_acls = mailbox('get', 'sender_acl_handles', $username); $sender_acl_admin = array_merge( - mailbox('get', 'sender_acl_handles', $username)['sender_acl_domains']['ro'], - mailbox('get', 'sender_acl_handles', $username)['sender_acl_addresses']['ro'] + $current_sender_acls['sender_acl_domains']['ro'], + $current_sender_acls['sender_acl_addresses']['ro'] ); // Get sender_acl items from POST array // Set sender_acl_domain_admin to empty array if sender_acl contains "default" to trigger a reset @@ -1988,9 +3347,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { unset($sender_acl_domain_admin[$key]); continue; } - // Check if user has mailbox access (if object is email) + // Check if user has alias access (if object is email) if (filter_var($val, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $val)) { + if (!hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $val)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -2005,15 +3364,29 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $sender_acl_merged = array_merge($sender_acl_domain_admin, $sender_acl_admin); // If merged array still contains "*", set it as only value !in_array('*', $sender_acl_merged) ?: $sender_acl_merged = array('*'); - $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username"); + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 0 AND `logged_in_as` = :username"); $stmt->execute(array( ':username' => $username )); + $sender_acl_handles = mailbox('get', 'sender_acl_handles', $username); + $fixed_sender_aliases_allowed = $sender_acl_handles['fixed_sender_aliases_allowed']; + $fixed_sender_aliases_blocked = $sender_acl_handles['fixed_sender_aliases_blocked']; + foreach ($sender_acl_merged as $sender_acl) { $domain = ltrim($sender_acl, '@'); if (is_valid_domain_name($domain)) { $sender_acl = '@' . $domain; } + + // Always add to sender_acl table to create explicit permission + // Skip only if it's in allowed list (would be redundant) + // But DO add if it's in blocked list (creates override) + if (in_array($sender_acl, $fixed_sender_aliases_allowed)) { + // Skip: already allowed by sender_allowed=1, no need for sender_acl entry + continue; + } + + // Add to sender_acl (either override for blocked aliases, or grant for selectable ones) $stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`) VALUES (:sender_acl, :username)"); $stmt->execute(array( @@ -2023,64 +3396,676 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } } else { - $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username"); + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 0 AND `logged_in_as` = :username"); $stmt->execute(array( ':username' => $username )); } } - if (!empty($password) && !empty($password2)) { - if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { + if (!empty($password)) { + if (password_check($password, $password2) !== true) { + continue; + } + $password_hashed = hash_password($password); + $stmt = $pdo->prepare("UPDATE `mailbox` SET + `password` = :password_hashed, + `attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW()) + WHERE `username` = :username AND authsource = 'mailcow'"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username' => $username + )); + } + // We could either set alias = 1 if alias = 2 or tune the Postfix alias table (that's what we did, TODO: do it the other way) + $stmt = $pdo->prepare("UPDATE `alias` SET + `active` = :active + WHERE `address` = :address"); + $stmt->execute(array( + ':address' => $username, + ':active' => $active + )); + try { + $stmt = $pdo->prepare("UPDATE `mailbox` SET + `active` = :active, + `name`= :name, + `quota` = :quota_b, + `authsource` = :authsource, + `attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update), + `attributes` = JSON_SET(`attributes`, '$.force_tfa', :force_tfa), + `attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access), + `attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access), + `attributes` = JSON_SET(`attributes`, '$.sieve_access', :sieve_access), + `attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access), + `attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost), + `attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access), + `attributes` = JSON_SET(`attributes`, '$.eas_access', :eas_access), + `attributes` = JSON_SET(`attributes`, '$.dav_access', :dav_access), + `attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email), + `attributes` = JSON_SET(`attributes`, '$.attribute_hash', :attribute_hash) + WHERE `username` = :username"); + $stmt->execute(array( + ':active' => $active, + ':name' => $name, + ':quota_b' => $quota_b, + ':attribute_hash' => $attribute_hash, + ':force_pw_update' => $force_pw_update, + ':force_tfa' => $force_tfa, + ':sogo_access' => $sogo_access, + ':imap_access' => $imap_access, + ':pop3_access' => $pop3_access, + ':sieve_access' => $sieve_access, + ':smtp_access' => $smtp_access, + ':eas_access' => $eas_access, + ':dav_access' => $dav_access, + ':recovery_email' => $pw_recovery_email, + ':relayhost' => $relayhost, + ':username' => $username, + ':authsource' => $authsource + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => $e->getMessage() + ); + return false; + } + // save delimiter_action + if (isset($_data['tagged_mail_handler'])) { + mailbox('edit', 'delimiter_action', array( + 'username' => $username, + 'tagged_mail_handler' => $_data['tagged_mail_handler'] + )); + } + // save tags + foreach($tags as $index => $tag){ + if (empty($tag)) continue; + if ($index > $GLOBALS['TAGGING_LIMIT']) { $_SESSION['return'][] = array( - 'type' => 'danger', + 'type' => 'warning', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'password_complexity' + 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) ); - continue; + break; } - if ($password != $password2) { + try { + $stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)"); + $stmt->execute(array( + ':username' => $username, + ':tag_name' => $tag, + )); + } catch (Exception $e) { + } + } + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + + // Track affected mailboxes for SOGo update + $update_sogo_mailboxes[] = $username; + } + break; + case 'mailbox_rename': + $domain = $_data['domain']; + $old_local_part = $_data['old_local_part']; + $old_username = $old_local_part . "@" . $domain; + $new_local_part = $_data['new_local_part']; + $new_username = $new_local_part . "@" . $domain; + $create_alias = intval($_data['create_alias']); + + if (!filter_var($old_username, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('username_invalid', $old_username) + ); + return false; + } + if (!filter_var($new_username, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('username_invalid', $new_username) + ); + return false; + } + + $is_now = mailbox('get', 'mailbox_details', $old_username); + if (empty($is_now) || ($is_now['active'] != '1' && $is_now['active'] != '2')) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $is_now['domain'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + + // get imap acls + try { + $exec_fields = array( + 'cmd' => 'doveadm', + 'task' => 'get_acl', + 'id' => $old_username + ); + $imap_acls = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); + // delete imap acls + foreach ($imap_acls as $imap_acl) { + $exec_fields = array( + 'cmd' => 'doveadm', + 'task' => 'delete_acl', + 'user' => $imap_acl['user'], + 'mailbox' => $imap_acl['mailbox'], + 'id' => $imap_acl['id'] + ); + docker('post', 'dovecot-mailcow', 'exec', $exec_fields); + } + } catch (Exception $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => $e->getMessage() + ); + return false; + } + + // rename username in sql + try { + $pdo->beginTransaction(); + $pdo->exec('SET FOREIGN_KEY_CHECKS = 0'); + + // Update username in mailbox table + $pdo->prepare('UPDATE mailbox SET username = :new_username, local_part = :new_local_part WHERE username = :old_username') + ->execute([ + ':new_username' => $new_username, + ':new_local_part' => $new_local_part, + ':old_username' => $old_username + ]); + + $pdo->prepare("UPDATE alias SET address = :new_username, goto = :new_username2 WHERE address = :old_username") + ->execute([ + ':new_username' => $new_username, + ':new_username2' => $new_username, + ':old_username' => $old_username + ]); + + // Update the username in all related tables + $tables = [ + 'tags_mailbox' => ['username'], + 'sieve_filters' => ['username'], + 'app_passwd' => ['mailbox'], + 'user_acl' => ['username'], + 'da_acl' => ['username'], + 'quota2' => ['username'], + 'quota2replica' => ['username'], + 'pushover' => ['username'], + 'alias' => ['goto'], + "imapsync" => ['user2'], + 'bcc_maps' => ['local_dest', 'bcc_dest'], + 'recipient_maps' => ['old_dest', 'new_dest'], + 'sender_acl' => ['logged_in_as', 'send_as'] + ]; + foreach ($tables as $table => $columns) { + foreach ($columns as $column) { + $stmt = $pdo->prepare("UPDATE $table SET $column = :new_username WHERE $column = :old_username") + ->execute([ + ':new_username' => $new_username, + ':old_username' => $old_username + ]); + } + } + + // Update c_uid, c_name and mail in _sogo_static_view table + $pdo->prepare("UPDATE _sogo_static_view SET c_uid = :new_username, c_name = :new_username2, mail = :new_username3 WHERE c_uid = :old_username") + ->execute([ + ':new_username' => $new_username, + ':new_username2' => $new_username, + ':new_username3' => $new_username, + ':old_username' => $old_username + ]); + + // Re-enable foreign key checks + $pdo->exec('SET FOREIGN_KEY_CHECKS = 1'); + $pdo->commit(); + } catch (PDOException $e) { + // Rollback the transaction if something goes wrong + $pdo->rollBack(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => $e->getMessage() + ); + return false; + } + + // move maildir + $exec_fields = array( + 'cmd' => 'maildir', + 'task' => 'move', + 'old_maildir' => $domain . '/' . $old_local_part, + 'new_maildir' => $domain . '/' . $new_local_part + ); + if (getenv("CLUSTERMODE") == "replication") { + // broadcast to each dovecot container + docker('broadcast', 'dovecot-mailcow', 'exec', $exec_fields); + } else { + docker('post', 'dovecot-mailcow', 'exec', $exec_fields); + } + + // rename username in sogo + $exec_fields = array( + 'cmd' => 'sogo', + 'task' => 'rename_user', + 'old_username' => $old_username, + 'new_username' => $new_username + ); + docker('post', 'sogo-mailcow', 'exec', $exec_fields); + + // set imap acls + foreach ($imap_acls as $imap_acl) { + $user_id = ($imap_acl['id'] == $old_username) ? $new_username : $imap_acl['id']; + $user = ($imap_acl['user'] == $old_username) ? $new_username : $imap_acl['user']; + $exec_fields = array( + 'cmd' => 'doveadm', + 'task' => 'set_acl', + 'user' => $user, + 'mailbox' => $imap_acl['mailbox'], + 'id' => $user_id, + 'rights' => $imap_acl['rights'] + ); + docker('post', 'dovecot-mailcow', 'exec', $exec_fields); + } + + // create alias + if ($create_alias == 1) { + mailbox("add", "alias", array( + "address" => $old_username, + "goto" => $new_username, + "active" => 1, + "sogo_visible" => 1, + "private_comment" => sprintf($lang['success']['mailbox_renamed'], $old_username, $new_username) + )); + } + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_renamed', $old_username, $new_username) + ); + break; + case 'mailbox_from_template': + $stmt = $pdo->prepare("SELECT * FROM `templates` + WHERE `template` = :template AND type = 'mailbox'"); + $stmt->execute(array( + ":template" => $_data['template'] + )); + $mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($mbox_template_data)){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'template_missing' + ); + return false; + } + + $attribute_hash = sha1(json_encode($mbox_template_data["attributes"])); + $is_now = mailbox('get', 'mailbox_details', $_data['username']); + $name = ltrim(rtrim($_data['name'], '>'), '<'); + if ($is_now['attributes']['attribute_hash'] == $attribute_hash && $is_now['name'] == $name) + return true; + + $mbox_template_data = json_decode($mbox_template_data["attributes"], true); + $mbox_template_data['attribute_hash'] = $attribute_hash; + $mbox_template_data['name'] = $name; + $quarantine_attributes = array('username' => $_data['username']); + $tls_attributes = array('username' => $_data['username']); + $ratelimit_attributes = array('object' => $_data['username']); + $acl_attributes = array('username' => $_data['username'], 'user_acl' => array()); + $mailbox_attributes = array('username' => $_data['username']); + foreach ($mbox_template_data as $key => $value){ + switch (true) { + case (strpos($key, 'quarantine_') === 0): + $quarantine_attributes[$key] = $value; + break; + case (strpos($key, 'tls_') === 0): + if ($value == null) + $value = 0; + $tls_attributes[$key] = $value; + break; + case (strpos($key, 'rl_') === 0): + $ratelimit_attributes[$key] = $value; + break; + case (strpos($key, 'acl_') === 0 && $value != 0): + array_push($acl_attributes['user_acl'], str_replace('acl_' , '', $key)); + break; + default: + $mailbox_attributes[$key] = $value; + break; + } + } + + $mailbox_attributes['quota'] = intval($mailbox_attributes['quota'] / 1048576); + $result = mailbox('edit', 'mailbox', $mailbox_attributes); + if ($result === false) return $result; + $result = mailbox('edit', 'tls_policy', $tls_attributes); + if ($result === false) return $result; + $result = mailbox('edit', 'quarantine_notification', $quarantine_attributes); + if ($result === false) return $result; + $result = mailbox('edit', 'quarantine_category', $quarantine_attributes); + if ($result === false) return $result; + $result = ratelimit('edit', 'mailbox', $ratelimit_attributes); + if ($result === false) return $result; + $result = acl('edit', 'user', $acl_attributes); + if ($result === false) return $result; + + $_SESSION['return'] = array(); + return true; + break; + case 'mailbox_templates': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['ids'])) { + $ids = array(); + $ids[] = $_data['ids']; + } + else { + $ids = $_data['ids']; + } + foreach ($ids as $id) { + $is_now = mailbox("get", "mailbox_templates", $id); + if (empty($is_now) || + $is_now["type"] != "mailbox"){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'template_id_invalid' + ); + continue; + } + + + // check name + if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){ + // keep template name of Default template + $_data["template"] = $is_now["template"]; + } + else { + $_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"]; + } + // check attributes + $attr = array(); + $attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0; + $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : $is_now['tags']; + $attr["tagged_mail_handler"] = (!empty($_data['tagged_mail_handler'])) ? $_data['tagged_mail_handler'] : $is_now['tagged_mail_handler']; + $attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification']; + $attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category']; + // Validate quarantine_category + if (!in_array($attr["quarantine_category"], array('add_header', 'reject', 'all'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'quarantine_category_invalid' + ); + continue; + } + $attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame']; + $attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : $is_now['rl_value']; + $attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update']; + $attr["force_tfa"] = isset($_data['force_tfa']) ? intval($_data['force_tfa']) : $is_now['force_tfa']; + $attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : $is_now['sogo_access']; + $attr["active"] = isset($_data['active']) ? intval($_data['active']) : $is_now['active']; + $attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in']; + $attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out']; + if (isset($_data['protocol_access'])) { + $_data['protocol_access'] = (array)$_data['protocol_access']; + $attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0; + $attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; + $attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; + $attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; + $attr['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0; + $attr['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0; + } + else { + foreach ($is_now as $key => $value){ + $attr[$key] = $is_now[$key]; + } + } + if (isset($_data['acl'])) { + $_data['acl'] = (array)$_data['acl']; + $attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0; + $attr['acl_tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0; + $attr['acl_spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0; + $attr['acl_spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0; + $attr['acl_delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0; + $attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0; + $attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0; + $attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0; + $attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0; + $attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0; + $attr['acl_pw_reset'] = (in_array('pw_reset', $_data['acl'])) ? 1 : 0; + } else { + foreach ($is_now as $key => $value){ + $attr[$key] = $is_now[$key]; + } + } + + + // update template + $stmt = $pdo->prepare("UPDATE `templates` + SET `template` = :template, `attributes` = :attributes + WHERE id = :id"); + $stmt->execute(array( + ":id" => $id , + ":template" => $_data["template"] , + ":attributes" => json_encode($attr) + )); + } + + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('template_modified', $_data["template"]) + ); + return true; + break; + case 'mailbox_custom_attribute': + $_data['attribute'] = isset($_data['attribute']) ? $_data['attribute'] : array(); + $_data['attribute'] = is_array($_data['attribute']) ? $_data['attribute'] : array($_data['attribute']); + $_data['attribute'] = array_map(function($value) { return str_replace(' ', '', $value); }, $_data['attribute']); + $_data['value'] = isset($_data['value']) ? $_data['value'] : array(); + $_data['value'] = is_array($_data['value']) ? $_data['value'] : array($_data['value']); + $attributes = (object)array_combine($_data['attribute'], $_data['value']); + $mailboxes = is_array($_data['mailboxes']) ? $_data['mailboxes'] : array($_data['mailboxes']); + + foreach ($mailboxes as $mailbox) { + if (!filter_var($mailbox, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('username_invalid', $mailbox) + ); + continue; + } + $is_now = mailbox('get', 'mailbox_details', $mailbox); + if(!empty($is_now)){ + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $is_now['domain'])) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'password_mismatch' + 'msg' => 'access_denied' ); continue; } - $password_hashed = hash_password($password); - $stmt = $pdo->prepare("UPDATE `mailbox` SET - `password` = :password_hashed - WHERE `username` = :username"); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + + + $stmt = $pdo->prepare("UPDATE `mailbox` + SET `custom_attributes` = :custom_attributes + WHERE username = :username"); + $stmt->execute(array( + ":username" => $mailbox, + ":custom_attributes" => json_encode($attributes) + )); + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $mailbox) + ); + } + + return true; + break; + case 'mta_sts': + if (!is_array($_data['domains'])) { + $domains = array(); + $domains[] = $_data['domains']; + } + else { + $domains = $_data['domains']; + } + + foreach ($domains as $domain) { + $domain = idn_to_ascii(strtolower(trim($domain)), 0, INTL_IDNA_VARIANT_UTS46); + + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + + $is_now = mailbox('get', 'mta_sts', $domain); + if (!empty($is_now)) { + $version = (isset($_data['version'])) ? strtolower($_data['version']) : $is_now['version']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $active = ($active == 1) ? 1 : 0; + $mode = (isset($_data['mode'])) ? strtolower($_data['mode']) : $is_now['mode']; + $mx = (isset($_data['mx'])) ? explode(",", preg_replace('/\s+/', '', $_data['mx'])) : $is_now['mx']; + $max_age = (isset($_data['max_age'])) ? intval($_data['max_age']) : $is_now['max_age']; + + // Update ID if neccesary + if ($version != strtolower($is_now['version']) || + $mode != strtolower($is_now['mode']) || + $mx != $is_now['mx'] || + $max_age != $is_now['max_age']) { + $id = date('YmdHis'); + } else { + $id = $is_now['id']; + } + + } else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + + if (empty($version) || !in_array($version, array('stsv1'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr), + 'msg' => array('version_invalid', htmlspecialchars($version)) + ); + continue; + } + if (empty($mode) || !in_array($mode, array('enforce', 'testing', 'none'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr), + 'msg' => array('mode_invalid', htmlspecialchars($domain)) + ); + continue; + } + if (empty($max_age) || $max_age < 0 || $max_age > 31557600) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr), + 'msg' => array('max_age_invalid', htmlspecialchars($domain)) + ); + continue; + } + foreach ($mx as $index => $mx_domain) { + $mx_domain = idn_to_ascii(strtolower(trim($mx_domain)), 0, INTL_IDNA_VARIANT_UTS46); + $invalid_mx = false; + if (!is_valid_domain_name($mx_domain, array('allow_wildcard' => true))) { + $invalid_mx = $mx_domain; + break; + } + } + if ($invalid_mx) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr), + 'msg' => array('mx_invalid', htmlspecialchars($invalid_mx)) + ); + continue; + } + + try { + $stmt = $pdo->prepare("UPDATE `mta_sts` SET `id` = :id, `version` = :version, `mode` = :mode, `mx` = :mx, `max_age` = :max_age, `active` = :active WHERE `domain` = :domain"); $stmt->execute(array( - ':password_hashed' => $password_hashed, - ':username' => $username + ':id' => $id, + ':domain' => $domain, + ':version' => $version, + ':mode' => $mode, + ':mx' => implode(",", $mx), + ':max_age' => $max_age, + ':active' => $active )); + } catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => $e->getMessage() + ); + continue; } - $stmt = $pdo->prepare("UPDATE `alias` SET - `active` = :active - WHERE `address` = :address"); - $stmt->execute(array( - ':address' => $username, - ':active' => $active - )); - $stmt = $pdo->prepare("UPDATE `mailbox` SET - `active` = :active, - `name`= :name, - `quota` = :quota_b, - `attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update) - WHERE `username` = :username"); - $stmt->execute(array( - ':active' => $active, - ':name' => $name, - ':quota_b' => $quota_b, - ':force_pw_update' => $force_pw_update, - ':username' => $username - )); + $_SESSION['return'][] = array( 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', $username) + 'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr), + 'msg' => array('object_modified', $domain) ); } + + return true; break; case 'resource': if (!is_array($_data['name'])) { @@ -2093,7 +4078,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { foreach ($names as $name) { $is_now = mailbox('get', 'resource_details', $name); if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; $multiple_bookings = (isset($_data['multiple_bookings'])) ? intval($_data['multiple_bookings']) : $is_now['multiple_bookings']; $description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description']; $kind = (!empty($_data['kind'])) ? $_data['kind'] : $is_now['kind']; @@ -2159,6 +4144,120 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('resource_modified', htmlspecialchars($name)) ); + + // Track affected mailboxes for SOGo update + $update_sogo_mailboxes[] = $name; + } + break; + case 'domain_wide_footer': + if (!is_array($_data['domains'])) { + $domains = array(); + $domains[] = $_data['domains']; + } + else { + $domains = $_data['domains']; + } + + $footers = array(); + $footers['html'] = isset($_data['html']) ? $_data['html'] : ''; + $footers['plain'] = isset($_data['plain']) ? $_data['plain'] : ''; + $footers['skip_replies'] = isset($_data['skip_replies']) ? (int)$_data['skip_replies'] : 0; + $footers['mbox_exclude'] = array(); + $footers['alias_domain_exclude'] = array(); + if (isset($_data["exclude"])){ + if (!is_array($_data["exclude"])) { + $_data["exclude"] = array($_data["exclude"]); + } + foreach ($_data["exclude"] as $exclude) { + if (filter_var($exclude, FILTER_VALIDATE_EMAIL)) { + $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `address` = :address + UNION + SELECT `username` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array( + ':address' => $exclude, + ':username' => $exclude, + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if(!$row){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('username_invalid', $exclude) + ); + continue; + } + array_push($footers['mbox_exclude'], $exclude); + } + elseif (is_valid_domain_name($exclude)) { + $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain` = :alias_domain"); + $stmt->execute(array( + ':alias_domain' => $exclude, + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if(!$row){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('username_invalid', $exclude) + ); + continue; + } + array_push($footers['alias_domain_exclude'], $exclude); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('username_invalid', $exclude) + ); + } + } + } + foreach ($domains as $domain) { + $domain = idn_to_ascii(strtolower(trim($domain)), 0, INTL_IDNA_VARIANT_UTS46); + if (!is_valid_domain_name($domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + return false; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + + try { + $stmt = $pdo->prepare("DELETE FROM `domain_wide_footer` WHERE `domain`= :domain"); + $stmt->execute(array(':domain' => $domain)); + $stmt = $pdo->prepare("INSERT INTO `domain_wide_footer` (`domain`, `html`, `plain`, `mbox_exclude`, `alias_domain_exclude`, `skip_replies`) VALUES (:domain, :html, :plain, :mbox_exclude, :alias_domain_exclude, :skip_replies)"); + $stmt->execute(array( + ':domain' => $domain, + ':html' => $footers['html'], + ':plain' => $footers['plain'], + ':mbox_exclude' => json_encode($footers['mbox_exclude']), + ':alias_domain_exclude' => json_encode($footers['alias_domain_exclude']), + ':skip_replies' => $footers['skip_replies'], + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => $e->getMessage() + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_footer_modified', htmlspecialchars($domain)) + ); } break; } @@ -2176,12 +4275,22 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $data['sender_acl_addresses']['rw'] = array(); $data['sender_acl_addresses']['selectable'] = array(); $data['fixed_sender_aliases'] = array(); - // Fixed addresses - $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` REGEXP :goto AND `address` NOT LIKE '@%'"); - $stmt->execute(array(':goto' => '(^|,)'.$_data.'($|,)')); + $data['fixed_sender_aliases_allowed'] = array(); + $data['fixed_sender_aliases_blocked'] = array(); + $data['external_sender_aliases'] = array(); + // Fixed addresses - split by sender_allowed status + $stmt = $pdo->prepare("SELECT `address`, `sender_allowed` FROM `alias` WHERE `goto` REGEXP :goto AND `address` NOT LIKE '@%'"); + $stmt->execute(array(':goto' => '(^|,)'.preg_quote($_data, '/').'($|,)')); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($rows)) { + // Keep old array for backward compatibility $data['fixed_sender_aliases'][] = $row['address']; + // Split into allowed/blocked for proper display + if ($row['sender_allowed'] == '1') { + $data['fixed_sender_aliases_allowed'][] = $row['address']; + } else { + $data['fixed_sender_aliases_blocked'][] = $row['address']; + } } $stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias_domain_alias` FROM `mailbox`, `alias_domain` WHERE `alias_domain`.`target_domain` = `mailbox`.`domain` @@ -2193,9 +4302,18 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $data['fixed_sender_aliases'][] = $row['alias_domain_alias']; } } + // External addresses + $stmt = $pdo->prepare("SELECT `send_as` as `send_as_external` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `external` = '1'"); + $stmt->execute(array(':logged_in_as' => $_data)); + $exernal_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($exernal_rows)) { + if (!empty($row['send_as_external'])) { + $data['external_sender_aliases'][] = $row['send_as_external']; + } + } // Return array $data['sender_acl_domains/addresses']['ro'] with read-only objects // Return array $data['sender_acl_domains/addresses']['rw'] with read-write objects (can be deleted) - $stmt = $pdo->prepare("SELECT REPLACE(`send_as`, '@', '') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND (`send_as` LIKE '@%' OR `send_as` = '*')"); + $stmt = $pdo->prepare("SELECT REPLACE(`send_as`, '@', '') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `external` = '0' AND (`send_as` LIKE '@%' OR `send_as` = '*')"); $stmt->execute(array(':logged_in_as' => $_data)); $domain_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($domain_row = array_shift($domain_rows)) { @@ -2214,29 +4332,31 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $data['sender_acl_domains']['rw'][] = $domain_row['send_as']; } } - $stmt = $pdo->prepare("SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND (`send_as` NOT LIKE '@%' AND `send_as` != '*')"); + $stmt = $pdo->prepare("SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `external` = '0' AND (`send_as` NOT LIKE '@%' AND `send_as` != '*')"); $stmt->execute(array(':logged_in_as' => $_data)); $address_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($address_row = array_shift($address_rows)) { - if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $address_row['send_as'])) { + if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && !hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $address_row['send_as'])) { $data['sender_acl_addresses']['ro'][] = $address_row['send_as']; continue; } - if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $address_row['send_as'])) { + if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $address_row['send_as'])) { $data['sender_acl_addresses']['rw'][] = $address_row['send_as']; continue; } } $stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain` NOT IN ( - SELECT REPLACE(`send_as`, '@', '') FROM `sender_acl` + SELECT REPLACE(`send_as`, '@', '') FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as1 + AND `external` = '0' AND `send_as` LIKE '@%') UNION SELECT '*' FROM `domain` WHERE '*' NOT IN ( - SELECT `send_as` FROM `sender_acl` + SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as2 + AND `external` = '0' )"); $stmt->execute(array( ':logged_in_as1' => $_data, @@ -2256,8 +4376,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` != :goto AND `address` NOT IN ( - SELECT `send_as` FROM `sender_acl` + SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as + AND `external` = '0' AND `send_as` NOT LIKE '@%')"); $stmt->execute(array( ':logged_in_as' => $_data, @@ -2265,7 +4386,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { )); $rows_mbox = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($rows_mbox)) { - if (filter_var($row['address'], FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) { + // Aliases are not selectable + if (in_array($row['address'], $data['fixed_sender_aliases'])) { + continue; + } + if (filter_var($row['address'], FILTER_VALIDATE_EMAIL) && hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) { $data['sender_acl_addresses']['selectable'][] = $row['address']; } } @@ -2273,11 +4398,35 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { break; case 'mailboxes': $mailboxes = array(); - if (isset($_data) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; + if (isset($_extra) && is_array($_extra) && isset($_data)) { + // get by domain and tags + $tags = is_array($_extra) ? $_extra : array(); + + $sql = ""; + foreach ($tags as $key => $tag) { + $sql = $sql."SELECT DISTINCT `username` FROM `tags_mailbox` WHERE `username` LIKE ? AND `tag_name` LIKE ?"; // distinct, avoid duplicates + if ($key === array_key_last($tags)) break; + $sql = $sql.' UNION DISTINCT '; // combine querys with union - distinct, avoid duplicates + } + + // prepend domain to array + $params = array(); + foreach ($tags as $key => $val){ + array_push($params, '%'.$_data.'%'); + array_push($params, '%'.$val.'%'); + } + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], explode('@', $row['username'])[1])) + $mailboxes[] = $row['username']; + } } elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain"); + // get by domain + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain"); $stmt->execute(array( ':domain' => $_data, )); @@ -2287,7 +4436,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } } else { - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND (`domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role)"); + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND (`domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role)"); $stmt->execute(array( ':username' => $_SESSION['mailcow_cc_username'], ':role' => $_SESSION['mailcow_cc_role'], @@ -2318,6 +4467,38 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'tls_enforce_out' => $attrs['tls_enforce_out'] ); break; + case 'quarantine_notification': + $attrs = array(); + if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + } + else { + $_data = $_SESSION['mailcow_cc_username']; + } + $stmt = $pdo->prepare("SELECT `attributes` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $_data)); + $attrs = $stmt->fetch(PDO::FETCH_ASSOC); + $attrs = json_decode($attrs['attributes'], true); + return $attrs['quarantine_notification']; + break; + case 'quarantine_category': + $attrs = array(); + if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + } + else { + $_data = $_SESSION['mailcow_cc_username']; + } + $stmt = $pdo->prepare("SELECT `attributes` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $_data)); + $attrs = $stmt->fetch(PDO::FETCH_ASSOC); + $attrs = json_decode($attrs['attributes'], true); + return $attrs['quarantine_category']; + break; case 'filters': $filters = array(); if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { @@ -2336,13 +4517,21 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } return $filters; break; + case 'global_filter_details': + $global_filters = array(); + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + $global_filters['prefilter'] = file_get_contents('/global_sieve/before'); + $global_filters['postfilter'] = file_get_contents('/global_sieve/after'); + return $global_filters; + break; case 'filter_details': $filter_details = array(); if (!is_numeric($_data)) { return false; } - $stmt = $pdo->prepare("SELECT CASE `script_name` WHEN 'active' THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, - CASE `script_name` WHEN 'active' THEN 1 ELSE 0 END AS `active_int`, + $stmt = $pdo->prepare("SELECT CASE `script_name` WHEN 'active' THEN 1 ELSE 0 END AS `active`, id, username, filter_type, @@ -2368,20 +4557,23 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $_data = $_SESSION['mailcow_cc_username']; } $exec_fields = array( - 'cmd' => 'sieve_list', + 'cmd' => 'sieve', + 'task' => 'list', 'username' => $_data ); - $filters = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); - $filters = array_filter(explode(PHP_EOL, $filters)); + $filters = docker('post', 'dovecot-mailcow', 'exec', $exec_fields); + $filters = array_filter(preg_split("/(\r\n|\n|\r)/",$filters)); foreach ($filters as $filter) { if (preg_match('/.+ ACTIVE/i', $filter)) { $exec_fields = array( - 'cmd' => 'sieve_print', + 'cmd' => 'sieve', + 'task' => 'print', 'script_name' => substr($filter, 0, -7), 'username' => $_data ); - $filters = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); - return preg_replace('/^.+\n/', '', $filters); + $script = docker('post', 'dovecot-mailcow', 'exec', $exec_fields); + // Remove first line + return preg_replace('/^.+\n/', '', $script); } } return false; @@ -2397,15 +4589,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { while($field = array_shift($fields)) { $shown_fields[] = $field['Field']; } - $stmt = $pdo->prepare("SELECT " . implode(',', $shown_fields) . ", - `active` AS `active_int`, - CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` + $stmt = $pdo->prepare("SELECT " . implode(',', (array)$shown_fields) . ", + `active` FROM `imapsync` WHERE id = :id"); } elseif (isset($_extra) && in_array('with_password', $_extra)) { $stmt = $pdo->prepare("SELECT *, - `active` AS `active_int`, - CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` + `active` FROM `imapsync` WHERE id = :id"); } else { @@ -2414,9 +4604,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { while($field = array_shift($fields)) { $shown_fields[] = $field['Field']; } - $stmt = $pdo->prepare("SELECT " . implode(',', $shown_fields) . ", - `active` AS `active_int`, - CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` + $stmt = $pdo->prepare("SELECT " . implode(',', (array)$shown_fields) . ", + `active` FROM `imapsync` WHERE id = :id"); } $stmt->execute(array(':id' => $_data)); @@ -2452,7 +4641,47 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return $syncjobdata; break; case 'spam_score': - $default = "5, 15"; + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/actions"); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + $default_actions = curl_exec($curl); + if (!curl_errno($curl)) { + $data_array = json_decode($default_actions, true); + curl_close($curl); + foreach ($data_array as $data) { + if ($data['action'] == 'reject') { + $reject = $data['value']; + continue; + } + elseif ($data['action'] == 'add header') { + $add_header = $data['value']; + continue; + } + } + if (empty($add_header) || empty($reject)) { + // Assume default, set warning + $default = "5, 15"; + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not determine servers default spam score, assuming default' + ); + } + else { + $default = $add_header . ', ' . $reject; + } + } + else { + // Assume default, set warning + $default = "5, 15"; + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not determine servers default spam score, assuming default' + ); + } + curl_close($curl); $policydata = array(); if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { @@ -2491,10 +4720,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } $stmt = $pdo->prepare("SELECT `address`, `goto`, - `validity` + `description`, + `validity`, + `created`, + `modified`, + `permanent` FROM `spamalias` WHERE `goto` = :username - AND `validity` >= :unixnow"); + AND (`validity` >= :unixnow + OR `permanent` != 0)"); $stmt->execute(array(':username' => $_data, ':unixnow' => time())); $tladata = $stmt->fetchAll(PDO::FETCH_ASSOC); return $tladata; @@ -2590,13 +4824,17 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { return false; } - $stmt = $pdo->prepare("SELECT `id` FROM `alias` WHERE `address` != `goto` AND `domain` = :domain"); + $stmt = $pdo->prepare("SELECT `id`, `address` FROM `alias` WHERE `address` != `goto` AND `domain` = :domain"); $stmt->execute(array( ':domain' => $_data, )); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { - $aliases[] = $row['id']; + if ($_extra == "address"){ + $aliases[] = $row['address']; + } else { + $aliases[] = $row['id']; + } } return $aliases; break; @@ -2607,14 +4845,19 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `domain`, `goto`, `address`, - `active` as `active_int`, - CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, + `public_comment`, + `private_comment`, + `internal`, + `active`, + `sogo_visible`, + `sender_allowed`, `created`, `modified` FROM `alias` - WHERE `id` = :id AND `address` != `goto`"); + WHERE (`id` = :id OR `address` = :address) AND `address` != `goto`"); $stmt->execute(array( - ':id' => intval($_data), + ':id' => $_data, + ':address' => $_data, )); $row = $stmt->fetch(PDO::FETCH_ASSOC); $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain"); @@ -2630,11 +4873,18 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } $aliasdata['id'] = $row['id']; $aliasdata['domain'] = $row['domain']; + $aliasdata['public_comment'] = $row['public_comment']; + $aliasdata['private_comment'] = $row['private_comment']; + $aliasdata['domain'] = $row['domain']; $aliasdata['goto'] = $row['goto']; $aliasdata['address'] = $row['address']; (!filter_var($aliasdata['address'], FILTER_VALIDATE_EMAIL)) ? $aliasdata['is_catch_all'] = 1 : $aliasdata['is_catch_all'] = 0; + $aliasdata['internal'] = $row['internal']; $aliasdata['active'] = $row['active']; - $aliasdata['active_int'] = $row['active_int']; + $aliasdata['active_int'] = $row['active']; + $aliasdata['sogo_visible'] = $row['sogo_visible']; + $aliasdata['sogo_visible_int'] = $row['sogo_visible']; + $aliasdata['sender_allowed'] = $row['sender_allowed']; $aliasdata['created'] = $row['created']; $aliasdata['modified'] = $row['modified']; if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdata['domain'])) { @@ -2648,8 +4898,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt = $pdo->prepare("SELECT `alias_domain`, `target_domain`, - `active` AS `active_int`, - CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, + `active`, `created`, `modified` FROM `alias_domain` @@ -2658,11 +4907,17 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':aliasdomain' => $_data, )); $row = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt = $pdo->prepare("SELECT `backupmx` FROM `domain` WHERE `domain` = :target_domain"); + $stmt->execute(array( + ':target_domain' => $row['target_domain'] + )); + $row_parent = $stmt->fetch(PDO::FETCH_ASSOC); $aliasdomaindata['alias_domain'] = $row['alias_domain']; + $aliasdomaindata['parent_is_backupmx'] = $row_parent['backupmx']; $aliasdomaindata['target_domain'] = $row['target_domain']; $aliasdomaindata['active'] = $row['active']; + $aliasdomaindata['active_int'] = $row['active']; $aliasdomaindata['rl'] = $rl; - $aliasdomaindata['active_int'] = $row['active_int']; $aliasdomaindata['created'] = $row['created']; $aliasdomaindata['modified'] = $row['modified']; if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdomaindata['target_domain'])) { @@ -2670,30 +4925,89 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } return $aliasdomaindata; break; + case 'shared_aliases': + $shared_aliases = array(); + $stmt = $pdo->query("SELECT `address` FROM `alias` + WHERE `goto` REGEXP ',' + AND `address` NOT LIKE '@%' + AND `goto` != `address`"); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $domain = explode("@", $row['address'])[1]; + if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $shared_aliases[] = $row['address']; + } + } + + return $shared_aliases; + break; + case 'direct_aliases': + $direct_aliases = array(); + $stmt = $pdo->query("SELECT `address` FROM `alias` + WHERE `goto` NOT LIKE '%,%' + AND `address` NOT LIKE '@%' + AND `goto` != `address`"); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + while($row = array_shift($rows)) { + $domain = explode("@", $row['address'])[1]; + if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $direct_aliases[] = $row['address']; + } + } + + return $direct_aliases; + break; case 'domains': $domains = array(); if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { return false; } - $stmt = $pdo->prepare("SELECT `domain` FROM `domain` - WHERE (`domain` IN ( - SELECT `domain` from `domain_admins` - WHERE (`active`='1' AND `username` = :username)) - ) - OR 'admin'= :role"); - $stmt->execute(array( - ':username' => $_SESSION['mailcow_cc_username'], - ':role' => $_SESSION['mailcow_cc_role'], - )); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $domains[] = $row['domain']; + + if (isset($_extra) && is_array($_extra)){ + // get by tags + $tags = is_array($_extra) ? $_extra : array(); + // add % as prefix and suffix to every element for relative searching + $tags = array_map(function($x){ return '%'.$x.'%'; }, $tags); + $sql = ""; + foreach ($tags as $key => $tag) { + $sql = $sql."SELECT DISTINCT `domain` FROM `tags_domain` WHERE `tag_name` LIKE ?"; // distinct, avoid duplicates + if ($key === array_key_last($tags)) break; + $sql = $sql.' UNION DISTINCT '; // combine querys with union - distinct, avoid duplicates + } + $stmt = $pdo->prepare($sql); + $stmt->execute($tags); + + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + if ($_SESSION['mailcow_cc_role'] == "admin") + $domains[] = $row['domain']; + elseif (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['domain'])) + $domains[] = $row['domain']; + } + } else { + // get all + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE (`domain` IN ( + SELECT `domain` from `domain_admins` + WHERE (`active`='1' AND `username` = :username)) + ) + OR 'admin'= :role"); + $stmt->execute(array( + ':username' => $_SESSION['mailcow_cc_username'], + ':role' => $_SESSION['mailcow_cc_role'], + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $domains[] = $row['domain']; + } } + return $domains; break; case 'domain_details': $domaindata = array(); - $_data = idn_to_ascii(strtolower(trim($_data))); + $_data = idn_to_ascii(strtolower(trim($_data)), 0, INTL_IDNA_VARIANT_UTS46); if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { return false; } @@ -2705,58 +5019,91 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (!empty($row)) { $_data = $row['target_domain']; } - $stmt = $pdo->prepare("SELECT + $stmt = $pdo->prepare("SELECT `domain`, `description`, `aliases`, - `mailboxes`, + `mailboxes`, + `defquota`, `maxquota`, + `created`, + `modified`, `quota`, `relayhost`, - `relay_all_recipients` as `relay_all_recipients_int`, - `backupmx` as `backupmx_int`, - `active` as `active_int`, - CASE `relay_all_recipients` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `relay_all_recipients`, - CASE `backupmx` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `backupmx`, - CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` + `relay_all_recipients`, + `relay_unknown_only`, + `backupmx`, + `gal`, + `active` FROM `domain` WHERE `domain`= :domain"); $stmt->execute(array( ':domain' => $_data )); $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (empty($row)) { + if (empty($row)) { return false; } - $stmt = $pdo->prepare("SELECT COUNT(*) AS `count`, + $stmt = $pdo->prepare("SELECT COUNT(`username`) AS `count`, COALESCE(SUM(`quota`), 0) AS `in_use` FROM `mailbox` - WHERE `kind` NOT REGEXP 'location|thing|group' + WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain"); $stmt->execute(array(':domain' => $row['domain'])); - $MailboxDataDomain = $stmt->fetch(PDO::FETCH_ASSOC); + $MailboxDataDomain = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt = $pdo->prepare("SELECT SUM(bytes) AS `bytes_total`, SUM(messages) AS `msgs_total` FROM `quota2` + WHERE `username` IN ( + SELECT `username` FROM `mailbox` + WHERE `domain` = :domain + );"); + $stmt->execute(array(':domain' => $row['domain'])); + $SumQuotaInUse = $stmt->fetch(PDO::FETCH_ASSOC); $rl = ratelimit('get', 'domain', $_data); - $domaindata['max_new_mailbox_quota'] = ($row['quota'] * 1048576) - $MailboxDataDomain['in_use']; + $domaindata['max_new_mailbox_quota'] = ($row['quota'] * 1048576) - $MailboxDataDomain['in_use']; if ($domaindata['max_new_mailbox_quota'] > ($row['maxquota'] * 1048576)) { $domaindata['max_new_mailbox_quota'] = ($row['maxquota'] * 1048576); } + $domaindata['def_new_mailbox_quota'] = $domaindata['max_new_mailbox_quota']; + if ($domaindata['def_new_mailbox_quota'] > ($row['defquota'] * 1048576)) { + $domaindata['def_new_mailbox_quota'] = ($row['defquota'] * 1048576); + } $domaindata['quota_used_in_domain'] = $MailboxDataDomain['in_use']; + if (!empty($SumQuotaInUse['bytes_total'])) { + $domaindata['bytes_total'] = $SumQuotaInUse['bytes_total']; + } + else { + $domaindata['bytes_total'] = 0; + } + if (!empty($SumQuotaInUse['msgs_total'])) { + $domaindata['msgs_total'] = $SumQuotaInUse['msgs_total']; + } + else { + $domaindata['msgs_total'] = 0; + } $domaindata['mboxes_in_domain'] = $MailboxDataDomain['count']; - $domaindata['mboxes_left'] = $row['mailboxes'] - $MailboxDataDomain['count']; + $domaindata['mboxes_left'] = $row['mailboxes'] - $MailboxDataDomain['count']; $domaindata['domain_name'] = $row['domain']; + $domaindata['domain_h_name'] = idn_to_utf8($row['domain']); $domaindata['description'] = $row['description']; $domaindata['max_num_aliases_for_domain'] = $row['aliases']; $domaindata['max_num_mboxes_for_domain'] = $row['mailboxes']; + $domaindata['def_quota_for_mbox'] = $row['defquota'] * 1048576; $domaindata['max_quota_for_mbox'] = $row['maxquota'] * 1048576; $domaindata['max_quota_for_domain'] = $row['quota'] * 1048576; $domaindata['relayhost'] = $row['relayhost']; $domaindata['backupmx'] = $row['backupmx']; - $domaindata['backupmx_int'] = $row['backupmx_int']; + $domaindata['backupmx_int'] = $row['backupmx']; + $domaindata['gal'] = $row['gal']; + $domaindata['gal_int'] = $row['gal']; $domaindata['rl'] = $rl; $domaindata['active'] = $row['active']; - $domaindata['active_int'] = $row['active_int']; + $domaindata['active_int'] = $row['active']; $domaindata['relay_all_recipients'] = $row['relay_all_recipients']; - $domaindata['relay_all_recipients_int'] = $row['relay_all_recipients_int']; - $stmt = $pdo->prepare("SELECT COUNT(*) AS `alias_count` FROM `alias` + $domaindata['relay_all_recipients_int'] = $row['relay_all_recipients']; + $domaindata['relay_unknown_only'] = $row['relay_unknown_only']; + $domaindata['relay_unknown_only_int'] = $row['relay_unknown_only']; + $domaindata['created'] = $row['created']; + $domaindata['modified'] = $row['modified']; + $stmt = $pdo->prepare("SELECT COUNT(`address`) AS `alias_count` FROM `alias` WHERE (`domain`= :domain OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain2)) AND `address` NOT IN ( SELECT `username` FROM `mailbox` @@ -2767,61 +5114,139 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { )); $AliasDataDomain = $stmt->fetch(PDO::FETCH_ASSOC); (isset($AliasDataDomain['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasDataDomain['alias_count'] : $domaindata['aliases_in_domain'] = "0"; - $domaindata['aliases_left'] = $row['aliases'] - $AliasDataDomain['alias_count']; + $domaindata['aliases_left'] = $row['aliases'] - $AliasDataDomain['alias_count']; + if ($_SESSION['mailcow_cc_role'] == "admin") + { + $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`username` SEPARATOR ', ') AS domain_admins FROM `domain_admins` WHERE `domain` = :domain"); + $stmt->execute(array( + ':domain' => $_data + )); + $domain_admins = $stmt->fetch(PDO::FETCH_ASSOC); + (isset($domain_admins['domain_admins'])) ? $domaindata['domain_admins'] = $domain_admins['domain_admins'] : $domaindata['domain_admins'] = "-"; + } + $stmt = $pdo->prepare("SELECT `tag_name` + FROM `tags_domain` WHERE `domain`= :domain"); + $stmt->execute(array( + ':domain' => $_data + )); + $tags = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($tag = array_shift($tags)) { + $domaindata['tags'][] = $tag['tag_name']; + } + return $domaindata; break; + case 'domain_templates': + if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { + return false; + } + $_data = (isset($_data)) ? intval($_data) : null; + + if (isset($_data)){ + $stmt = $pdo->prepare("SELECT * FROM `templates` + WHERE `id` = :id AND type = :type"); + $stmt->execute(array( + ":id" => $_data, + ":type" => "domain" + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + if (empty($row)){ + return false; + } + + $row["attributes"] = json_decode($row["attributes"], true); + return $row; + } + else { + $stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'domain'"); + $stmt->execute(); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (empty($rows)){ + return false; + } + + foreach($rows as $key => $row){ + $rows[$key]["attributes"] = json_decode($row["attributes"], true); + } + return $rows; + } + break; case 'mailbox_details': if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { return false; } $mailboxdata = array(); - $rl = ratelimit('get', 'mailbox', $_data); - $stmt = $pdo->prepare("SELECT + if (preg_match('/y|yes/i', getenv('MASTER'))) { + $stmt = $pdo->prepare("SELECT `domain`.`backupmx`, `mailbox`.`username`, `mailbox`.`name`, - `mailbox`.`active` AS `active_int`, - CASE `mailbox`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, + `mailbox`.`active`, `mailbox`.`domain`, - `mailbox`.`maildir`, + `mailbox`.`local_part`, `mailbox`.`quota`, + `mailbox`.`created`, + `mailbox`.`modified`, + `mailbox`.`authsource`, `quota2`.`bytes`, `attributes`, + `custom_attributes`, `quota2`.`messages` FROM `mailbox`, `quota2`, `domain` - WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group' AND `mailbox`.`username` = `quota2`.`username` AND `domain`.`domain` = `mailbox`.`domain` AND `mailbox`.`username` = :mailbox"); + WHERE (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL) + AND `mailbox`.`username` = `quota2`.`username` + AND `domain`.`domain` = `mailbox`.`domain` + AND `mailbox`.`username` = :mailbox"); + } + else { + $stmt = $pdo->prepare("SELECT + `domain`.`backupmx`, + `mailbox`.`username`, + `mailbox`.`name`, + `mailbox`.`active`, + `mailbox`.`domain`, + `mailbox`.`local_part`, + `mailbox`.`quota`, + `mailbox`.`created`, + `mailbox`.`modified`, + `mailbox`.`authsource`, + `quota2replica`.`bytes`, + `attributes`, + `custom_attributes`, + `quota2replica`.`messages` + FROM `mailbox`, `quota2replica`, `domain` + WHERE (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL) + AND `mailbox`.`username` = `quota2replica`.`username` + AND `domain`.`domain` = `mailbox`.`domain` + AND `mailbox`.`username` = :mailbox"); + } $stmt->execute(array( ':mailbox' => $_data, )); $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt = $pdo->prepare("SELECT `maxquota`, `quota` FROM `domain` WHERE `domain` = :domain"); - $stmt->execute(array(':domain' => $row['domain'])); - $DomainQuota = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain AND `username` != :username"); - $stmt->execute(array(':domain' => $row['domain'], ':username' => $_data)); - $MailboxUsage = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND `validity` >= :unixnow"); - $stmt->execute(array(':address' => $_data, ':unixnow' => time())); - $SpamaliasUsage = $stmt->fetch(PDO::FETCH_ASSOC); - $mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use']; - if ($mailboxdata['max_new_quota'] > ($DomainQuota['maxquota'] * 1048576)) { - $mailboxdata['max_new_quota'] = ($DomainQuota['maxquota'] * 1048576); - } + $mailboxdata['username'] = $row['username']; - $mailboxdata['rl'] = $rl; - $mailboxdata['is_relayed'] = $row['backupmx']; - $mailboxdata['name'] = $row['name']; $mailboxdata['active'] = $row['active']; - $mailboxdata['active_int'] = $row['active_int']; + $mailboxdata['active_int'] = $row['active']; $mailboxdata['domain'] = $row['domain']; - $mailboxdata['maildir'] = $row['maildir']; + $mailboxdata['name'] = $row['name']; + $mailboxdata['local_part'] = $row['local_part']; $mailboxdata['quota'] = $row['quota']; + $mailboxdata['messages'] = $row['messages']; $mailboxdata['attributes'] = json_decode($row['attributes'], true); + $mailboxdata['custom_attributes'] = json_decode($row['custom_attributes'], true); $mailboxdata['quota_used'] = intval($row['bytes']); - $mailboxdata['percent_in_use'] = round((intval($row['bytes']) / intval($row['quota'])) * 100); - $mailboxdata['messages'] = $row['messages']; - $mailboxdata['spam_aliases'] = $SpamaliasUsage['sa_count']; - if ($mailboxdata['percent_in_use'] >= 90) { + $mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100); + $mailboxdata['created'] = $row['created']; + $mailboxdata['modified'] = $row['modified']; + $mailboxdata['authsource'] = ($row['authsource']) ? $row['authsource'] : 'mailcow'; + + if ($mailboxdata['percent_in_use'] === '- ') { + $mailboxdata['percent_class'] = "info"; + } + elseif ($mailboxdata['percent_in_use'] >= 90) { $mailboxdata['percent_class'] = "danger"; } elseif ($mailboxdata['percent_in_use'] >= 75) { @@ -2830,7 +5255,145 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { else { $mailboxdata['percent_class'] = "success"; } - return $mailboxdata; + + // Determine last logins + $stmt = $pdo->prepare("SELECT MAX(`datetime`) AS `datetime`, `service` FROM `sasl_log` + WHERE `username` = :mailbox + GROUP BY `service` DESC"); + $stmt->execute(array(':mailbox' => $_data)); + $SaslLogsData = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($SaslLogsData as $SaslLogs) { + if ($SaslLogs['service'] == 'imap') { + $last_imap_login = strtotime($SaslLogs['datetime']); + } + else if ($SaslLogs['service'] == 'smtp') { + $last_smtp_login = strtotime($SaslLogs['datetime']); + } + else if ($SaslLogs['service'] == 'pop3') { + $last_pop3_login = strtotime($SaslLogs['datetime']); + } + else if ($SaslLogs['service'] == 'SSO') { + $last_sso_login = strtotime($SaslLogs['datetime']); + } + } + if (!isset($last_imap_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { + $last_imap_login = 0; + } + if (!isset($last_smtp_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { + $last_smtp_login = 0; + } + if (!isset($last_pop3_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { + $last_pop3_login = 0; + } + if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { + $last_sso_login = 0; + } + $mailboxdata['last_imap_login'] = $last_imap_login; + $mailboxdata['last_smtp_login'] = $last_smtp_login; + $mailboxdata['last_pop3_login'] = $last_pop3_login; + $mailboxdata['last_sso_login'] = $last_sso_login; + + if (!isset($_extra) || $_extra != 'reduced') { + $rl = ratelimit('get', 'mailbox', $_data); + $stmt = $pdo->prepare("SELECT `maxquota`, `quota` FROM `domain` WHERE `domain` = :domain"); + $stmt->execute(array(':domain' => $row['domain'])); + $DomainQuota = $stmt->fetch(PDO::FETCH_ASSOC); + + $stmt = $pdo->prepare("SELECT IFNULL(COUNT(`active`), 0) AS `pushover_active` FROM `pushover` WHERE `username` = :username AND `active` = 1"); + $stmt->execute(array(':username' => $_data)); + $PushoverActive = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain AND `username` != :username"); + $stmt->execute(array(':domain' => $row['domain'], ':username' => $_data)); + $MailboxUsage = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND (`validity` >= :unixnow OR `permanent` != 0)"); + $stmt->execute(array(':address' => $_data, ':unixnow' => time())); + $SpamaliasUsage = $stmt->fetch(PDO::FETCH_ASSOC); + $mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use']; + $mailboxdata['spam_aliases'] = $SpamaliasUsage['sa_count']; + $mailboxdata['pushover_active'] = ($PushoverActive['pushover_active'] == 1) ? 1 : 0; + if ($mailboxdata['max_new_quota'] > ($DomainQuota['maxquota'] * 1048576)) { + $mailboxdata['max_new_quota'] = ($DomainQuota['maxquota'] * 1048576); + } + if (!empty($rl)) { + $mailboxdata['rl'] = $rl; + $mailboxdata['rl_scope'] = 'mailbox'; + } + else { + $mailboxdata['rl'] = ratelimit('get', 'domain', $row['domain']); + $mailboxdata['rl_scope'] = 'domain'; + } + $mailboxdata['is_relayed'] = $row['backupmx']; + // Internal send-as ACL for this mailbox, mirrors the sender_acl field accepted by edit/mailbox + $mailboxdata['sender_acl'] = array(); + $stmt = $pdo->prepare("SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `external` = '0'"); + $stmt->execute(array(':username' => $_data)); + $sender_acl_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($sender_acl_rows as $sender_acl_row) { + $mailboxdata['sender_acl'][] = $sender_acl_row['send_as']; + } + } + $stmt = $pdo->prepare("SELECT `tag_name` + FROM `tags_mailbox` WHERE `username`= :username"); + $stmt->execute(array( + ':username' => $_data + )); + $tags = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($tag = array_shift($tags)) { + $mailboxdata['tags'][] = $tag['tag_name']; + } + + return $mailboxdata; + break; + case 'mailbox_templates': + if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin" && $_SESSION['access_all_exception'] != "1") { + return false; + } + $_data = (isset($_data)) ? intval($_data) : null; + + if (isset($_data)){ + $stmt = $pdo->prepare("SELECT * FROM `templates` + WHERE `id` = :id AND type = :type"); + $stmt->execute(array( + ":id" => $_data, + ":type" => "mailbox" + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + if (empty($row)){ + return false; + } + + $row["attributes"] = json_decode($row["attributes"], true); + return $row; + } + else { + $stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'mailbox'"); + $stmt->execute(); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (empty($rows)){ + return false; + } + + foreach($rows as $key => $row){ + $rows[$key]["attributes"] = json_decode($row["attributes"], true); + } + return $rows; + } + break; + case 'mta_sts': + $stmt = $pdo->prepare("SELECT * FROM `mta_sts` WHERE `domain` = :domain"); + $stmt->execute(array( + ':domain' => $_data, + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row)){ + return []; + } + $row['mx'] = explode(',', $row['mx']); + $row['version'] = strtoupper(substr($row['version'], 0, 3)) . substr($row['version'], 3); + + return $row; break; case 'resource_details': $resourcedata = array(); @@ -2843,8 +5406,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `kind`, `multiple_bookings`, `local_part`, - `active` AS `active_int`, - CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, + `active`, `domain` FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `username` = :resource"); $stmt->execute(array( @@ -2856,7 +5418,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $resourcedata['multiple_bookings'] = $row['multiple_bookings']; $resourcedata['description'] = $row['name']; $resourcedata['active'] = $row['active']; - $resourcedata['active_int'] = $row['active_int']; + $resourcedata['active_int'] = $row['active']; $resourcedata['domain'] = $row['domain']; $resourcedata['local_part'] = $row['local_part']; if (!isset($resourcedata['domain']) || @@ -2865,6 +5427,44 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } return $resourcedata; break; + case 'domain_wide_footer': + $domain = idn_to_ascii(strtolower(trim($_data)), 0, INTL_IDNA_VARIANT_UTS46); + if (!is_valid_domain_name($domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + return false; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + + try { + $stmt = $pdo->prepare("SELECT `html`, `plain`, `mbox_exclude`, `alias_domain_exclude`, `skip_replies` FROM `domain_wide_footer` + WHERE `domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain + )); + $footer = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => $e->getMessage() + ); + return false; + } + + return $footer; + break; } break; case 'delete': @@ -3025,6 +5625,69 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); } break; + case 'sogo_profile': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + if (!isset($_SESSION['acl']['sogo_profile_reset']) || $_SESSION['acl']['sogo_profile_reset'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($usernames as $username) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + // bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match) + $c_object_like = '%/' . addcslashes($username, '\\%_') . '/%'; + $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE :c_object_like OR `c_uid` = :username"); + $stmt->execute(array( + ':c_object_like' => $c_object_like, + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('sogo_profile_reset', htmlspecialchars($username)) + ); + } + break; case 'domain': if (!is_array($_data['domain'])) { $domains = array(); @@ -3050,7 +5713,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $domain = idn_to_ascii(strtolower(trim($domain))); + $domain = idn_to_ascii(strtolower(trim($domain)), 0, INTL_IDNA_VARIANT_UTS46); $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `domain` = :domain"); $stmt->execute(array(':domain' => $domain)); @@ -3059,17 +5722,17 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_not_empty' + 'msg' => array('domain_not_empty', $domain) ); continue; } - $exec_fields = array('cmd' => 'maildir_cleanup', 'maildir' => $domain); + $exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $domain); $maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); if ($maildir_gc['type'] != 'success') { $_SESSION['return'][] = array( 'type' => 'warning', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'Could not move maildir to garbage collector: ' . $maildir_gc['msg'] + 'msg' => 'Could not move mail storage to garbage collector: ' . $maildir_gc['msg'] ); } $stmt = $pdo->prepare("DELETE FROM `domain` WHERE `domain` = :domain"); @@ -3100,6 +5763,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt->execute(array( ':domain' => '%@'.$domain, )); + $stmt = $pdo->prepare("DELETE FROM `pushover` WHERE `username` LIKE :domain"); + $stmt->execute(array( + ':domain' => '%@'.$domain, + )); + $stmt = $pdo->prepare("DELETE FROM `quota2replica` WHERE `username` LIKE :domain"); + $stmt->execute(array( + ':domain' => '%@'.$domain, + )); $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `address` LIKE :domain"); $stmt->execute(array( ':domain' => '%@'.$domain, @@ -3112,6 +5783,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt->execute(array( ':domain' => $domain, )); + $stmt = $pdo->prepare("DELETE FROM `mta_sts` WHERE `domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $stmt = $pdo->query("DELETE FROM `admin` WHERE `superadmin` = 0 AND `username` NOT IN (SELECT `username`FROM `domain_admins`);"); + $stmt = $pdo->query("DELETE FROM `da_acl` WHERE `username` NOT IN (SELECT `username`FROM `domain_admins`);"); try { $redis->hDel('DOMAIN_MAP', $domain); $redis->hDel('RL_VALUE', $domain); @@ -3131,6 +5808,42 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); } break; + case 'domain_templates': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['ids'])) { + $ids = array(); + $ids[] = $_data['ids']; + } + else { + $ids = $_data['ids']; + } + + + foreach ($ids as $id) { + // delete template + $stmt = $pdo->prepare("DELETE FROM `templates` + WHERE id = :id AND type = :type AND NOT template = :template"); + $stmt->execute(array( + ":id" => $id, + ":type" => "domain", + ":template" => "Default" + )); + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('template_removed', htmlspecialchars($id)) + ); + return true; + } + break; case 'alias': if (!is_array($_data['id'])) { $ids = array(); @@ -3149,9 +5862,25 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } + + // Track affected mailboxes for SOGo update (capture before deletion) + if (!empty($alias_data['goto'])) { + $gotos = array_map('trim', explode(',', $alias_data['goto'])); + foreach ($gotos as $g) { + if (filter_var($g, FILTER_VALIDATE_EMAIL) && + !in_array($g, array('null@localhost', 'spam@localhost', 'ham@localhost'))) { + $update_sogo_mailboxes[] = $g; + } + } + } + $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `id` = :id"); $stmt->execute(array( - ':id' => $id + ':id' => $alias_data['id'] + )); + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `send_as` = :alias_address"); + $stmt->execute(array( + ':alias_address' => $alias_data['address'] )); $_SESSION['return'][] = array( 'type' => 'success', @@ -3197,6 +5926,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt->execute(array( ':alias_domain' => $alias_domain, )); + $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `address` LIKE :domain"); + $stmt->execute(array( + ':domain' => '%@'.$alias_domain, + )); $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :alias_domain"); $stmt->execute(array( ':alias_domain' => $alias_domain, @@ -3245,29 +5978,63 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $maildir = mailbox('get', 'mailbox_details', $username)['maildir']; - $exec_fields = array('cmd' => 'maildir_cleanup', 'maildir' => $maildir); - $maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); - if ($maildir_gc['type'] != 'success') { + $mailbox_details = mailbox('get', 'mailbox_details', $username); + if (!empty($mailbox_details['domain']) && !empty($mailbox_details['local_part'])) { + $maildir = $mailbox_details['domain'] . '/' . $mailbox_details['local_part']; + $exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $maildir); + + if (getenv("CLUSTERMODE") == "replication") { + // broadcast to each dovecot container + docker('broadcast', 'dovecot-mailcow', 'exec', $exec_fields); + } else { + $maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); + if ($maildir_gc['type'] != 'success') { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not move maildir to garbage collector: ' . $maildir_gc['msg'] + ); + } + } + } + else { $_SESSION['return'][] = array( 'type' => 'warning', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'Could not move maildir to garbage collector: ' . $maildir_gc['msg'] + 'msg' => 'Could not move maildir to garbage collector: variables local_part and/or domain empty' ); } $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `goto` = :username"); $stmt->execute(array( ':username' => $username )); + $stmt = $pdo->prepare("DELETE FROM `pushover` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `rcpt` = :username"); + $stmt->execute(array( + ':username' => $username + )); $stmt = $pdo->prepare("DELETE FROM `quota2` WHERE `username` = :username"); $stmt->execute(array( ':username' => $username )); + $stmt = $pdo->prepare("DELETE FROM `quota2replica` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); $stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `username` = :username"); $stmt->execute(array( ':username' => $username )); - $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username"); + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as OR `send_as` = :send_as"); + $stmt->execute(array( + ':logged_in_as' => $username, + ':send_as' => $username + )); + // fk, better safe than sorry + $stmt = $pdo->prepare("DELETE FROM `user_acl` WHERE `username` = :username"); $stmt->execute(array( ':username' => $username )); @@ -3291,8 +6058,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt->execute(array( ':username' => $username )); - $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $username . "/%' OR `c_uid` = :username"); + // bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match) + $c_object_like = '%/' . addcslashes($username, '\\%_') . '/%'; + $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE :c_object_like OR `c_uid` = :username"); $stmt->execute(array( + ':c_object_like' => $c_object_like, ':username' => $username )); $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); @@ -3315,16 +6085,36 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt->execute(array( ':username' => $username )); + $stmt = $pdo->prepare("DELETE FROM `oauth_access_tokens` WHERE `user_id` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `oauth_refresh_tokens` WHERE `user_id` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `oauth_authorization_codes` WHERE `user_id` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); $stmt = $pdo->prepare("SELECT `address`, `goto` FROM `alias` WHERE `goto` REGEXP :username"); - $stmt->execute(array(':username' => '(^|,)'.$username.'($|,)')); + $stmt->execute(array(':username' => '(^|,)'.preg_quote($username, '/').'($|,)')); $GotoData = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($GotoData as $gotos) { $goto_exploded = explode(',', $gotos['goto']); if (($key = array_search($username, $goto_exploded)) !== false) { unset($goto_exploded[$key]); } - $gotos_rebuild = implode(',', $goto_exploded); + $gotos_rebuild = implode(',', (array)$goto_exploded); $stmt = $pdo->prepare("UPDATE `alias` SET `goto` = :goto WHERE `address` = :address"); @@ -3344,12 +6134,52 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } + $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('mailbox_removed', htmlspecialchars($username)) ); + + // Track affected mailboxes for SOGo update + $update_sogo_mailboxes[] = $username; + } + break; + case 'mailbox_templates': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['ids'])) { + $ids = array(); + $ids[] = $_data['ids']; + } + else { + $ids = $_data['ids']; } + + + foreach ($ids as $id) { + // delete template + $stmt = $pdo->prepare("DELETE FROM `templates` + WHERE id = :id AND type = :type AND NOT template = :template"); + $stmt->execute(array( + ":id" => $id, + ":type" => "mailbox", + ":template" => "Default" + )); + } + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'template_removed' + ); + return true; break; case 'resource': if (!is_array($_data['name'])) { @@ -3388,8 +6218,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt->execute(array( ':username' => $name )); - $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $name . "/%' OR `c_uid` = :username"); + // bind LIKE pattern + escape LIKE wildcards so the value matches literally (no SQLi, no over-match) + $c_object_like = '%/' . addcslashes($name, '\\%_') . '/%'; + $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE :c_object_like OR `c_uid` = :username"); $stmt->execute(array( + ':c_object_like' => $c_object_like, ':username' => $name )); $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); @@ -3413,12 +6246,138 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('resource_removed', htmlspecialchars($name)) ); + + // Track affected mailboxes for SOGo update + $update_sogo_mailboxes[] = $name; + } + break; + case 'tags_domain': + if (!is_array($_data['domain'])) { + $domains = array(); + $domains[] = $_data['domain']; + } + else { + $domains = $_data['domain']; + } + $tags = $_data['tags']; + if (!is_array($tags)) $tags = array(); + + + $wasModified = false; + foreach ($domains as $domain) { + if (!is_valid_domain_name($domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + continue; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + + foreach($tags as $tag){ + // delete tag + $wasModified = true; + $stmt = $pdo->prepare("DELETE FROM `tags_domain` WHERE `domain` = :domain AND `tag_name` = :tag_name"); + $stmt->execute(array( + ':domain' => $domain, + ':tag_name' => $tag, + )); + } + } + + if (!$wasModified) return false; + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_modified', $domain) + ); + break; + case 'tags_mailbox': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; } + else { + $usernames = $_data['username']; + } + $tags = $_data['tags']; + if (!is_array($tags)) $tags = array(); + + $wasModified = false; + foreach ($usernames as $username) { + if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'email invalid' + ); + continue; + } + + $is_now = mailbox('get', 'mailbox_details', $username); + $domain = $is_now['domain']; + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + + // delete tags + foreach($tags as $tag){ + $wasModified = true; + + $stmt = $pdo->prepare("DELETE FROM `tags_mailbox` WHERE `username` = :username AND `tag_name` = :tag_name"); + $stmt->execute(array( + ':username' => $username, + ':tag_name' => $tag, + )); + } + } + + if (!$wasModified) return false; + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); break; } break; } - if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox'))) { - update_sogo_static_view(); + if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'resource', 'mailbox')) && getenv('SKIP_SOGO') != "y") { + try { + if (($_type == 'alias' || $_type == 'resource' || $_type == 'mailbox') && !empty($update_sogo_mailboxes)) { + // INCREMENTAL UPDATE: Update only affected mailboxes/resources + $update_sogo_mailboxes = array_unique($update_sogo_mailboxes); + foreach ($update_sogo_mailboxes as $mailbox) { + update_sogo_static_view($mailbox); + } + } + else { + // FULL REBUILD: For domain and alias_domain operations or if no tracked mailboxes + // Domain operations affect all mailboxes + // Alias_domain operations affect entire target domain + update_sogo_static_view(); + } + }catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => $e->getMessage() + ); + } } + + return true; } diff --git a/data/web/inc/functions.mailq.inc.php b/data/web/inc/functions.mailq.inc.php new file mode 100644 index 0000000000..f42ab7cc11 --- /dev/null +++ b/data/web/inc/functions.mailq.inc.php @@ -0,0 +1,121 @@ + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + function process_mailq_output($returned_output, $_action, $_data) { + if ($returned_output !== NULL) { + if ($_action == 'cat') { + logger(array('return' => array( + array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'queue_cat_success' + ) + ))); + return $returned_output; + } + else { + if (isset($returned_output['type']) && $returned_output['type'] == 'danger') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'Error: ' . $returned_output['msg'] + ); + } + if (isset($returned_output['type']) && $returned_output['type'] == 'success') { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'queue_command_success' + ); + } + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'unknown' + ); + } + } + if ($_action == 'get') { + $mailq_lines = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => 'list')); + $lines = 0; + // Hard limit to 10000 items + foreach (preg_split("/((\r?\n)|(\r\n?))/", $mailq_lines) as $mailq_item) if ($lines++ < 10000) { + if (empty($mailq_item) || $mailq_item == '1') { + continue; + } + $mq_line = json_decode($mailq_item, true); + if ($mq_line !== NULL) { + $rcpts = array(); + foreach ($mq_line['recipients'] as $rcpt) { + if (isset($rcpt['delay_reason'])) { + $rcpts[] = $rcpt['address'] . ' (' . $rcpt['delay_reason'] . ')'; + } + else { + $rcpts[] = $rcpt['address']; + } + } + if (!empty($rcpts)) { + $mq_line['recipients'] = $rcpts; + } + $line[] = $mq_line; + } + } + if (!isset($line) || empty($line)) { + return '[]'; + } + else { + return json_encode($line); + } + } + elseif ($_action == 'delete') { + if (!is_array($_data['qid'])) { + $qids = array(); + $qids[] = $_data['qid']; + } + else { + $qids = $_data['qid']; + } + $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => 'delete', 'items' => $qids)); + process_mailq_output(json_decode($docker_return, true), $_action, $_data); + } + elseif ($_action == 'cat') { + if (!is_array($_data['qid'])) { + $qids = array(); + $qids[] = $_data['qid']; + } + else { + $qids = $_data['qid']; + } + $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => 'cat', 'items' => $qids)); + return process_mailq_output($docker_return, $_action, $_data); + } + elseif ($_action == 'edit') { + if (in_array($_data['action'], array('hold', 'unhold', 'deliver'))) { + if (!is_array($_data['qid'])) { + $qids = array(); + $qids[] = $_data['qid']; + } + else { + $qids = $_data['qid']; + } + if (!empty($qids)) { + $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => $_data['action'], 'items' => $qids)); + process_mailq_output(json_decode($docker_return, true), $_action, $_data); + } + } + if (in_array($_data['action'], array('flush', 'super_delete'))) { + $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => $_data['action'])); + process_mailq_output(json_decode($docker_return, true), $_action, $_data); + } + } +} diff --git a/data/web/inc/functions.oauth2.inc.php b/data/web/inc/functions.oauth2.inc.php new file mode 100644 index 0000000000..7dc5602513 --- /dev/null +++ b/data/web/inc/functions.oauth2.inc.php @@ -0,0 +1,242 @@ + 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'access_denied' + ); + return false; + } + switch ($_action) { + case 'add': + switch ($_type) { + case 'client': + $client_id = bin2hex(random_bytes(6)); + $client_secret = bin2hex(random_bytes(12)); + $redirect_uri = $_data['redirect_uri']; + $scope = 'profile'; + // For future use + // $grant_type = isset($_data['grant_type']) ? $_data['grant_type'] : 'authorization_code'; + // $scope = isset($_data['scope']) ? $_data['scope'] : 'profile'; + // if ($grant_type != "authorization_code" && $grant_type != "password") { + // $_SESSION['return'][] = array( + // 'type' => 'danger', + // 'log' => array(__FUNCTION__, $_action, $_type, $_data), + // 'msg' => 'access_denied' + // ); + // return false; + // } + if ($scope != "profile") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'Invalid scope' + ); + return false; + } + $stmt = $pdo->prepare("SELECT 'client' FROM `oauth_clients` + WHERE `client_id` = :client_id"); + $stmt->execute(array(':client_id' => $client_id)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'Client ID exists' + ); + return false; + } + $stmt = $pdo->prepare("INSERT INTO `oauth_clients` (`client_id`, `client_secret`, `redirect_uri`, `scope`) + VALUES (:client_id, :client_secret, :redirect_uri, :scope)"); + $stmt->execute(array( + ':client_id' => $client_id, + ':client_secret' => $client_secret, + ':redirect_uri' => $redirect_uri, + ':scope' => $scope + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'Added client access' + ); + break; + } + break; + case 'edit': + switch ($_type) { + case 'client': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = oauth2('details', 'client', $id); + if (!empty($is_now)) { + $redirect_uri = (!empty($_data['redirect_uri'])) ? $_data['redirect_uri'] : $is_now['redirect_uri']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'access_denied' + ); + return false; + } + if (isset($_data['revoke_tokens'])) { + $stmt = $pdo->prepare("DELETE FROM `oauth_access_tokens` + WHERE `client_id` IN ( + SELECT `client_id` FROM `oauth_clients` WHERE `id` = :id + )"); + $stmt->execute(array( + ':id' => $id + )); + $stmt = $pdo->prepare("DELETE FROM `oauth_refresh_tokens` + WHERE `client_id` IN ( + SELECT `client_id` FROM `oauth_clients` WHERE `id` = :id + )"); + $stmt->execute(array( + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => array('object_modified', htmlspecialchars($id)) + ); + continue; + } + if (isset($_data['renew_secret'])) { + $client_secret = bin2hex(random_bytes(12)); + $stmt = $pdo->prepare("UPDATE `oauth_clients` SET `client_secret` = :client_secret WHERE `id` = :id"); + $stmt->execute(array( + ':client_secret' => $client_secret, + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => array('object_modified', htmlspecialchars($id)) + ); + continue; + } + if (empty($redirect_uri)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'Redirect/Callback URL cannot be empty' + ); + continue; + } + $stmt = $pdo->prepare("UPDATE `oauth_clients` SET + `redirect_uri` = :redirect_uri + WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id, + ':redirect_uri' => $redirect_uri + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => array('object_modified', htmlspecialchars($id)) + ); + } + break; + } + break; + case 'delete': + switch ($_type) { + case 'client': + (array)$ids = $_data['id']; + foreach ($ids as $id) { + if (!is_numeric($id)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `oauth_clients` + WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id + )); + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => array('items_deleted', htmlspecialchars($id)) + ); + break; + case 'access_token': + (array)$access_tokens = $_data['access_token']; + foreach ($access_tokens as $access_token) { + if (!ctype_alnum($access_token)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `oauth_access_tokens` + WHERE `access_token` = :access_token"); + $stmt->execute(array( + ':access_token' => $access_token + )); + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => sprintf($lang['success']['items_deleted'], implode(', ', (array)$access_tokens)) + ); + break; + case 'refresh_token': + (array)$refresh_tokens = $_data['refresh_token']; + foreach ($refresh_tokens as $refresh_token) { + if (!ctype_alnum($refresh_token)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `oauth_refresh_tokens` WHERE `refresh_token` = :refresh_token"); + $stmt->execute(array( + ':refresh_token' => $refresh_token + )); + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => sprintf($lang['success']['items_deleted'], implode(', ', (array)$refresh_tokens)) + ); + break; + } + break; + case 'get': + switch ($_type) { + case 'clients': + $stmt = $pdo->query("SELECT `id` FROM `oauth_clients`"); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows)) { + $oauth_clients[] = $row['id']; + } + return $oauth_clients; + break; + } + break; + case 'details': + switch ($_type) { + case 'client': + $stmt = $pdo->prepare("SELECT * FROM `oauth_clients` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $oauth_client_details = $stmt->fetch(PDO::FETCH_ASSOC); + return $oauth_client_details; + break; + } + break; + } +} diff --git a/data/web/inc/functions.policy.inc.php b/data/web/inc/functions.policy.inc.php index 12aacdd5c0..498f991f72 100644 --- a/data/web/inc/functions.policy.inc.php +++ b/data/web/inc/functions.policy.inc.php @@ -1,9 +1,9 @@ 'danger', @@ -104,7 +104,7 @@ function policy($_action, $_scope, $_data = null) { elseif ($_data['object_list'] == "wl") { $object_list = "whitelist_from"; } - $object_from = preg_replace('/\.+/', '.', rtrim(preg_replace("/\.\*/", "*", trim(strtolower($_data['object_from']))), '.')); + $object_from = trim(strtolower($_data['object_from'])); if (!ctype_alnum(str_replace(array('@', '_', '.', '-', '*'), '', $object_from))) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -183,7 +183,7 @@ function policy($_action, $_scope, $_data = null) { ); continue; } - $object = idn_to_ascii(strtolower(trim($object))); + $object = idn_to_ascii(strtolower(trim($object)), 0, INTL_IDNA_VARIANT_UTS46); } else { $_SESSION['return'][] = array( @@ -261,7 +261,7 @@ function policy($_action, $_scope, $_data = null) { $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('items_deleted', implode(', ', $prefids)) + 'msg' => array('items_deleted', implode(', ', (array)$prefids)) ); } break; @@ -277,7 +277,7 @@ function policy($_action, $_scope, $_data = null) { if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { return false; } - $_data = idn_to_ascii(strtolower(trim($_data))); + $_data = idn_to_ascii(strtolower(trim($_data)), 0, INTL_IDNA_VARIANT_UTS46); } // WHITELIST @@ -317,4 +317,4 @@ function policy($_action, $_scope, $_data = null) { } break; } -} \ No newline at end of file +} diff --git a/data/web/inc/functions.presets.inc.php b/data/web/inc/functions.presets.inc.php new file mode 100644 index 0000000000..17aaa3fbbe --- /dev/null +++ b/data/web/inc/functions.presets.inc.php @@ -0,0 +1,38 @@ + basename($filePath, '.yml')] + $preset; + /* get translated headlines */ + if (isset($preset['headline']) && strpos($preset['headline'], 'lang.') === 0) { + $langTextName = trim(substr($preset['headline'], 5)); + if (isset($lang[$lang_base][$langTextName])) { + $preset['headline'] = $lang[$lang_base][$langTextName]; + } + } + return $preset; +} diff --git a/data/web/inc/functions.pushover.inc.php b/data/web/inc/functions.pushover.inc.php new file mode 100644 index 0000000000..5393c0d5ef --- /dev/null +++ b/data/web/inc/functions.pushover.inc.php @@ -0,0 +1,223 @@ + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + foreach ($usernames as $username) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + continue; + } + $delete = $_data['delete']; + if ($delete == "true") { + $stmt = $pdo->prepare("DELETE FROM `pushover` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'pushover_settings_edited' + ); + continue; + } + $is_now = pushover('get', $username); + if (!empty($is_now)) { + $key = (!empty($_data['key'])) ? $_data['key'] : $is_now['key']; + $token = (!empty($_data['token'])) ? $_data['token'] : $is_now['token']; + $senders = (isset($_data['senders'])) ? $_data['senders'] : $is_now['senders']; + $senders_regex = (isset($_data['senders_regex'])) ? $_data['senders_regex'] : $is_now['senders_regex']; + $title = (!empty($_data['title'])) ? $_data['title'] : $is_now['title']; + $text = (!empty($_data['text'])) ? $_data['text'] : $is_now['text']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $evaluate_x_prio = (isset($_data['evaluate_x_prio'])) ? intval($_data['evaluate_x_prio']) : $is_now['evaluate_x_prio']; + $only_x_prio = (isset($_data['only_x_prio'])) ? intval($_data['only_x_prio']) : $is_now['only_x_prio']; + $sound = (isset($_data['sound'])) ? $_data['sound'] : $is_now['sound']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + continue; + } + if (!empty($senders_regex) && !is_valid_regex($senders_regex)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'Invalid regex' + ); + continue; + } + $senders = array_map('trim', preg_split( "/( |,|;|\n)/", $senders)); + foreach ($senders as $i => &$sender) { + if (empty($sender)) { + continue; + } + if (!filter_var($sender, FILTER_VALIDATE_EMAIL) === true) { + unset($senders[$i]); + continue; + } + $senders[$i] = preg_replace('/\.(?=.*?@gmail\.com$)/', '$1', $sender); + } + $senders = array_filter($senders); + if (empty($senders)) { $senders = ''; } + $senders = implode(",", (array)$senders); + if (!ctype_alnum($key) || strlen($key) != 30) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_data), + 'msg' => 'pushover_key' + ); + continue; + } + if (!ctype_alnum($token) || strlen($token) != 30) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_data), + 'msg' => 'pushover_token' + ); + continue; + } + $po_attributes = json_encode( + array( + 'evaluate_x_prio' => strval(intval($evaluate_x_prio)), + 'only_x_prio' => strval(intval($only_x_prio)), + 'sound' => strval($sound) + ) + ); + $stmt = $pdo->prepare("REPLACE INTO `pushover` (`username`, `key`, `attributes`, `senders_regex`, `senders`, `token`, `title`, `text`, `active`) + VALUES (:username, :key, :po_attributes, :senders_regex, :senders, :token, :title, :text, :active)"); + $stmt->execute(array( + ':username' => $username, + ':key' => $key, + ':po_attributes' => $po_attributes, + ':senders_regex' => $senders_regex, + ':senders' => $senders, + ':token' => $token, + ':title' => $title, + ':text' => $text, + ':active' => $active + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'pushover_settings_edited' + ); + } + break; + case 'get': + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("SELECT * FROM `pushover` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $_data + )); + $data = $stmt->fetch(PDO::FETCH_ASSOC); + $data['attributes'] = json_decode($data['attributes'], true); + if (empty($data)) { + return false; + } + else { + return $data; + } + break; + case 'test': + if (!isset($_SESSION['acl']['pushover']) || $_SESSION['acl']['pushover'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + foreach ($usernames as $username) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("SELECT * FROM `pushover` + WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $api_data = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($api_data)) { + $title = (!empty($api_data['title'])) ? $api_data['title'] : 'Mail'; + $text = (!empty($api_data['text'])) ? $api_data['text'] : 'You\'ve got mail ๐Ÿ“ง'; + curl_setopt_array($ch = curl_init(), array( + CURLOPT_URL => "https://api.pushover.net/1/users/validate.json", + CURLOPT_POSTFIELDS => array( + "token" => $api_data['token'], + "user" => $api_data['key'] + ), + CURLOPT_SAFE_UPLOAD => true, + CURLOPT_RETURNTRANSFER => true, + )); + $result = curl_exec($ch); + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($httpcode == 200) { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => sprintf('Pushover API OK (%d): %s', $httpcode, $result) + ); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => sprintf('Pushover API ERR (%d): %s', $httpcode, $result) + ); + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'pushover_credentials_missing' + ); + return false; + } + } + break; + } +} diff --git a/data/web/inc/functions.quarantine.inc.php b/data/web/inc/functions.quarantine.inc.php index 14f38b8f4b..c89b3e408e 100644 --- a/data/web/inc/functions.quarantine.inc.php +++ b/data/web/inc/functions.quarantine.inc.php @@ -1,10 +1,231 @@ array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return false; + } + $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt` + WHERE `qhash` = :hash + AND user_acl.quarantine = 1 + AND rcpt IN (SELECT username FROM mailbox)'); + $stmt->execute(array(':hash' => $hash)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row['id']) || !is_numeric($row['id'])) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return false; + } + else { + $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE id = :id"); + $stmt->execute(array( + ':id' => $row['id'] + )); + } + logger(array('return' => array( + array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('item_deleted', $row['id']) + ) + ))); + break; + case 'quick_release': + // Dont return results, just log + $hash = trim($_data); + if (preg_match("/^([a-f0-9]{64})$/", $hash) === false) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return false; + } + $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt` + WHERE `qhash` = :hash + AND `user_acl`.`quarantine` = 1 + AND `username` IN (SELECT `username` FROM `mailbox`)'); + $stmt->execute(array(':hash' => $hash)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row['id']) || !is_numeric($row['id'])) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return false; + } + else { + $stmt = $pdo->prepare('SELECT `msg`, `qid`, `sender`, `rcpt` FROM `quarantine` WHERE `id` = :id'); + $stmt->execute(array(':id' => $row['id'])); + $detail_row = $stmt->fetch(PDO::FETCH_ASSOC); + $sender = !empty($detail_row['sender']) ? $detail_row['sender'] : 'sender-unknown@rspamd'; + if (!empty(gethostbynamel('postfix-mailcow'))) { + $postfix = 'postfix-mailcow'; + } + if (!empty(gethostbynamel('postfix'))) { + $postfix = 'postfix'; + } + else { + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', 'Cannot determine Postfix host') + ) + ))); + return false; + } + try { + $release_format = $redis->Get('Q_RELEASE_FORMAT'); + } + catch (RedisException $e) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ) + ))); + return false; + } + if ($release_format == 'attachment') { + try { + $mail = new PHPMailer(true); + $mail->isSMTP(); + $mail->SMTPDebug = 0; + $mail->SMTPOptions = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ); + if (!empty(gethostbynamel('postfix-mailcow'))) { + $postfix = 'postfix-mailcow'; + } + if (!empty(gethostbynamel('postfix'))) { + $postfix = 'postfix'; + } + else { + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', 'Cannot determine Postfix host') + ) + ))); + return false; + } + $mail->Host = $postfix; + $mail->Port = 590; + $mail->setFrom($sender); + $mail->CharSet = 'UTF-8'; + $mail->Subject = sprintf($lang['quarantine']['release_subject'], $detail_row['qid']); + $mail->addAddress($detail_row['rcpt']); + $mail->IsHTML(false); + $msg_tmpf = tempnam("/tmp", $detail_row['qid']); + file_put_contents($msg_tmpf, $detail_row['msg']); + $mail->addAttachment($msg_tmpf, $detail_row['qid'] . '.eml'); + $mail->Body = sprintf($lang['quarantine']['release_body']); + $mail->send(); + unlink($msg_tmpf); + } + catch (phpmailerException $e) { + unlink($msg_tmpf); + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', $e->errorMessage()) + ) + ))); + return false; + } + } + elseif ($release_format == 'raw') { + $detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $detail_row['msg']); + // dot-stuffing per RFC5321 4.5.2: escape leading dots + $detail_row['msg'] = preg_replace('~^\.~m', '..', $detail_row['msg']); + $postfix_talk = array( + array('220', 'HELO quarantine' . chr(10)), + array('250', 'MAIL FROM: ' . $sender . chr(10)), + array('250', 'RCPT TO: ' . $detail_row['rcpt'] . chr(10)), + array('250', 'DATA' . chr(10)), + array('354', $detail_row['msg'] . chr(10) . '.' . chr(10)), + array('250', 'QUIT' . chr(10)), + array('221', '') + ); + // Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php + $smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1); + if (!$smtp_connection) { + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'Cannot connect to Postfix' + ) + ))); + return false; + } + for ($i=0; $i < count($postfix_talk); $i++) { + $smtp_resource = fgets($smtp_connection, 256); + if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) { + $ret = substr($smtp_resource, 0, 3); + $ret = (empty($ret)) ? '-' : $ret; + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'Postfix returned SMTP code ' . $smtp_resource . ', expected ' . $postfix_talk[$i][0] + ) + ))); + return false; + } + if ($postfix_talk[$i][1] !== '') { + fputs($smtp_connection, $postfix_talk[$i][1]); + } + } + fclose($smtp_connection); + } + $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE id = :id"); + $stmt->execute(array( + ':id' => $row['id'] + )); + } + logger(array('return' => array( + array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('item_released', $hash) + ) + ))); + break; case 'delete': if (!is_array($_data['id'])) { $ids = array(); @@ -74,12 +295,56 @@ function quarantine($_action, $_data = null) { return false; } $retention_size = $_data['retention_size']; + if ($_data['release_format'] == 'attachment' || $_data['release_format'] == 'raw') { + $release_format = $_data['release_format']; + } + else { + $release_format = 'raw'; + } $max_size = $_data['max_size']; + if ($_data['max_score'] == "") { + $max_score = ''; + } + else { + $max_score = floatval($_data['max_score']); + } + $max_age = intval($_data['max_age']); + $subject = $_data['subject']; + if (!filter_var($_data['bcc'], FILTER_VALIDATE_EMAIL)) { + $bcc = ''; + } + else { + $bcc = $_data['bcc']; + } + if (!filter_var($_data['redirect'], FILTER_VALIDATE_EMAIL)) { + $redirect = ''; + } + else { + $redirect = $_data['redirect']; + } + if (!filter_var($_data['sender'], FILTER_VALIDATE_EMAIL)) { + $sender = ''; + } + else { + $sender = $_data['sender']; + } + $html = $_data['html_tmpl']; + if ($max_age <= 0) { + $max_age = 365; + } $exclude_domains = (array)$_data['exclude_domains']; try { $redis->Set('Q_RETENTION_SIZE', intval($retention_size)); $redis->Set('Q_MAX_SIZE', intval($max_size)); + $redis->Set('Q_MAX_SCORE', $max_score); + $redis->Set('Q_MAX_AGE', $max_age); $redis->Set('Q_EXCLUDE_DOMAINS', json_encode($exclude_domains)); + $redis->Set('Q_RELEASE_FORMAT', $release_format); + $redis->Set('Q_SENDER', $sender); + $redis->Set('Q_BCC', $bcc); + $redis->Set('Q_REDIRECT', $redirect); + $redis->Set('Q_SUBJ', $subject); + $redis->Set('Q_HTML', $html); } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -96,7 +361,7 @@ function quarantine($_action, $_data = null) { ); } // Release item - elseif ($_data['action'] == 'release') { + elseif ($_data['action'] == 'release' || $_data['action'] == 'learnham') { if (!is_array($_data['id'])) { $ids = array(); $ids[] = $_data['id']; @@ -113,84 +378,241 @@ function quarantine($_action, $_data = null) { ); continue; } - $stmt = $pdo->prepare('SELECT `msg`, `qid`, `sender`, `rcpt` FROM `quarantine` WHERE `id` = :id'); + $stmt = $pdo->prepare('SELECT `msg`, `action`, `qid`, `sender`, `rcpt` FROM `quarantine` WHERE `id` = :id'); $stmt->execute(array(':id' => $id)); $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt'])) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) && $_SESSION['mailcow_cc_role'] != 'admin' || empty($row['rcpt'])) { $_SESSION['return'][] = array( 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), 'msg' => 'access_denied' ); continue; } - $sender = (isset($row['sender'])) ? $row['sender'] : 'sender-unknown@rspamd'; + $sender = !empty($row['sender']) ? $row['sender'] : 'sender-unknown@rspamd'; + if (!empty(gethostbynamel('postfix-mailcow'))) { + $postfix = 'postfix-mailcow'; + } + if (!empty(gethostbynamel('postfix'))) { + $postfix = 'postfix'; + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', 'Cannot determine Postfix host') + ); + continue; + } try { - $mail = new PHPMailer(true); - $mail->isSMTP(); - $mail->SMTPDebug = 0; - $mail->SMTPOptions = array( - 'ssl' => array( - 'verify_peer' => false, - 'verify_peer_name' => false, - 'allow_self_signed' => true - ) + $release_format = $redis->Get('Q_RELEASE_FORMAT'); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) ); - if (!empty(gethostbynamel('postfix-mailcow'))) { - $postfix = 'postfix-mailcow'; - } - if (!empty(gethostbynamel('postfix'))) { - $postfix = 'postfix'; + return false; + } + if ($release_format == 'attachment') { + try { + $mail = new PHPMailer(true); + $mail->isSMTP(); + $mail->SMTPDebug = 0; + $mail->SMTPOptions = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ); + if (!empty(gethostbynamel('postfix-mailcow'))) { + $postfix = 'postfix-mailcow'; + } + if (!empty(gethostbynamel('postfix'))) { + $postfix = 'postfix'; + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', 'Cannot determine Postfix host') + ); + continue; + } + $mail->Host = $postfix; + $mail->Port = 590; + $mail->setFrom($sender); + $mail->CharSet = 'UTF-8'; + $mail->Subject = sprintf($lang['quarantine']['release_subject'], $row['qid']); + $mail->addAddress($row['rcpt']); + $mail->IsHTML(false); + $msg_tmpf = tempnam("/tmp", $row['qid']); + file_put_contents($msg_tmpf, $row['msg']); + $mail->addAttachment($msg_tmpf, $row['qid'] . '.eml'); + $mail->Body = sprintf($lang['quarantine']['release_body']); + $mail->send(); + unlink($msg_tmpf); } - else { + catch (phpmailerException $e) { + unlink($msg_tmpf); $_SESSION['return'][] = array( 'type' => 'warning', 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('release_send_failed', 'Cannot determine Postfix host') + 'msg' => array('release_send_failed', $e->errorMessage()) ); continue; } - $mail->Host = $postfix; - $mail->Port = 590; - $mail->setFrom($sender); - $mail->CharSet = 'UTF-8'; - $mail->Subject = sprintf($lang['quarantine']['release_subject'], $row['qid']); - $mail->addAddress($row['rcpt']); - $mail->IsHTML(false); - $msg_tmpf = tempnam("/tmp", $row['qid']); - file_put_contents($msg_tmpf, $row['msg']); - $mail->addAttachment($msg_tmpf, $row['qid'] . '.eml'); - $mail->Body = sprintf($lang['quarantine']['release_body']); - $mail->send(); - unlink($msg_tmpf); } - catch (phpmailerException $e) { - unlink($msg_tmpf); + elseif ($release_format == 'raw') { + $row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $row['msg']); + // dot-stuffing per RFC5321 4.5.2: escape leading dots + $row['msg'] = preg_replace('~^\.~m', '..', $row['msg']); + $postfix_talk = array( + array('220', 'HELO quarantine' . chr(10)), + array('250', 'MAIL FROM: ' . $sender . chr(10)), + array('250', 'RCPT TO: ' . $row['rcpt'] . chr(10)), + array('250', 'DATA' . chr(10)), + array('354', $row['msg'] . chr(10) . '.' . chr(10)), + array('250', 'QUIT' . chr(10)), + array('221', '') + ); + // Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php + $smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1); + if (!$smtp_connection) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'Cannot connect to Postfix' + ); + return false; + } + for ($i=0; $i < count($postfix_talk); $i++) { + $smtp_resource = fgets($smtp_connection, 256); + if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) { + $ret = substr($smtp_resource, 0, 3); + $ret = (empty($ret)) ? '-' : $ret; + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'Postfix returned SMTP code ' . $smtp_resource . ', expected ' . $postfix_talk[$i][0] + ); + return false; + } + if ($postfix_talk[$i][1] !== '') { + fputs($smtp_connection, $postfix_talk[$i][1]); + } + } + fclose($smtp_connection); + } + $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('item_released', $id) + ); + // Item was released and deleted from quarantine, now learning ham + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_TIMEOUT, 30); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain')); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/learnham"); + curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); + $response = curl_exec($curl); + if (!curl_errno($curl)) { + $response = json_decode($response, true); + if (isset($response['error'])) { + if (stripos($response['error'], 'already learned') === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('ham_learn_error', $response['error']) + ); + continue; + } + } + curl_close($curl); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_TIMEOUT, 30); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 11')); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzydel"); + curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); + // It is most likely not a spam hash, so we ignore any error/warning response + // $response = curl_exec($curl); + if (!curl_errno($curl)) { + curl_close($curl); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_TIMEOUT, 30); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 13')); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzyadd"); + curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); + $response = curl_exec($curl); + curl_exec($curl); + if (!curl_errno($curl)) { + $response = json_decode($response, true); + if (isset($response['error'])) { + if (stripos($response['error'], 'No content to generate fuzzy') === false) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('fuzzy_learn_error', $response['error']) + ); + } + } + } + curl_close($curl); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('learned_ham', $id) + ); + continue; + } + else { + curl_close($curl); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('ham_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl))) + ); + continue; + } + curl_close($curl); $_SESSION['return'][] = array( - 'type' => 'warning', + 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('release_send_failed', $e->errorMessage()) + 'msg' => array('ham_learn_error', 'unknown') ); continue; } - try { - $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id"); - $stmt->execute(array( - ':id' => $id - )); - } - catch (PDOException $e) { + else { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) + 'msg' => array('ham_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl))) ); + curl_close($curl); continue; } + curl_close($curl); $_SESSION['return'][] = array( - 'type' => 'success', + 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('item_released', $id) + 'msg' => array('ham_learn_error', 'unknown') ); + continue; } } elseif ($_data['action'] == 'learnspam') { @@ -210,16 +632,21 @@ function quarantine($_action, $_data = null) { ); continue; } - $stmt = $pdo->prepare('SELECT `msg`, `rcpt` FROM `quarantine` WHERE `id` = :id'); + $stmt = $pdo->prepare('SELECT `msg`, `rcpt`, `action` FROM `quarantine` WHERE `id` = :id'); $stmt->execute(array(':id' => $id)); $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) && $_SESSION['mailcow_cc_role'] != 'admin') { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) && $_SESSION['mailcow_cc_role'] != 'admin' || empty($row['rcpt'])) { $_SESSION['return'][] = array( 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), 'msg' => 'access_denied' ); continue; } + $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id + )); $curl = curl_init(); curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); @@ -235,7 +662,7 @@ function quarantine($_action, $_data = null) { if (stripos($response['error'], 'already learned') === false) { $_SESSION['return'][] = array( 'type' => 'danger', - 'log' => array(__FUNCTION__), + 'log' => array(__FUNCTION__, $_action, $_data_log), 'msg' => array('spam_learn_error', $response['error']) ); continue; @@ -247,37 +674,39 @@ function quarantine($_action, $_data = null) { curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POST, 1); curl_setopt($curl, CURLOPT_TIMEOUT, 30); - curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 11')); - curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzyadd"); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 13')); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzydel"); curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); - $response = curl_exec($curl); + // It is most likely not a spam hash, so we ignore any error/warning response + // $response = curl_exec($curl); if (!curl_errno($curl)) { - $response = json_decode($response, true); - if (isset($response['error'])) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__), - 'msg' => array('fuzzy_learn_error', $response['error']) - ); - } curl_close($curl); - try { - $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id"); - $stmt->execute(array( - ':id' => $id - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_TIMEOUT, 30); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 11')); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzyadd"); + curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); + $response = curl_exec($curl); + curl_exec($curl); + if (!curl_errno($curl)) { + $response = json_decode($response, true); + if (isset($response['error'])) { + if (stripos($response['error'], 'No content to generate fuzzy') === false) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('fuzzy_learn_error', $response['error']) + ); + } + } } + curl_close($curl); $_SESSION['return'][] = array( 'type' => 'success', - 'log' => array(__FUNCTION__), + 'log' => array(__FUNCTION__, $_action, $_data_log), 'msg' => array('qlearn_spam', $id) ); continue; @@ -286,7 +715,7 @@ function quarantine($_action, $_data = null) { curl_close($curl); $_SESSION['return'][] = array( 'type' => 'danger', - 'log' => array(__FUNCTION__), + 'log' => array(__FUNCTION__, $_action, $_data_log), 'msg' => array('spam_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl))) ); continue; @@ -294,15 +723,15 @@ function quarantine($_action, $_data = null) { curl_close($curl); $_SESSION['return'][] = array( 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => array('learn_spam_error', 'unknown') + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('spam_learn_error', 'unknown') ); continue; } else { $_SESSION['return'][] = array( 'type' => 'danger', - 'log' => array(__FUNCTION__), + 'log' => array(__FUNCTION__, $_action, $_data_log), 'msg' => array('spam_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl))) ); curl_close($curl); @@ -311,8 +740,8 @@ function quarantine($_action, $_data = null) { curl_close($curl); $_SESSION['return'][] = array( 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => array('learn_spam_error', 'unknown') + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('spam_learn_error', 'unknown') ); continue; } @@ -321,7 +750,7 @@ function quarantine($_action, $_data = null) { break; case 'get': if ($_SESSION['mailcow_cc_role'] == "user") { - $stmt = $pdo->prepare('SELECT `id`, `qid`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine` WHERE `rcpt` = :mbox'); + $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `score`, `rcpt`, `sender`, `action`, UNIX_TIMESTAMP(`created`) AS `created`, `notified` FROM `quarantine` WHERE `rcpt` = :mbox'); $stmt->execute(array(':mbox' => $_SESSION['mailcow_cc_username'])); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { @@ -329,7 +758,7 @@ function quarantine($_action, $_data = null) { } } elseif ($_SESSION['mailcow_cc_role'] == "admin") { - $stmt = $pdo->query('SELECT `id`, `qid`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine`'); + $stmt = $pdo->query('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `score`, `rcpt`, `sender`, `action`, UNIX_TIMESTAMP(`created`) AS `created`, `notified` FROM `quarantine`'); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { $q_meta[] = $row; @@ -338,7 +767,7 @@ function quarantine($_action, $_data = null) { else { $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); foreach ($domains as $domain) { - $stmt = $pdo->prepare('SELECT `id`, `qid`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantine` WHERE `rcpt` REGEXP :domain'); + $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `score`, `rcpt`, `sender`, `action`, UNIX_TIMESTAMP(`created`) AS `created`, `notified` FROM `quarantine` WHERE `rcpt` REGEXP :domain'); $stmt->execute(array(':domain' => '@' . $domain . '$')); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { @@ -349,18 +778,23 @@ function quarantine($_action, $_data = null) { return $q_meta; break; case 'settings': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } try { - $settings['exclude_domains'] = json_decode($redis->Get('Q_EXCLUDE_DOMAINS'), true); + if ($_SESSION['mailcow_cc_role'] == "admin") { + $settings['exclude_domains'] = json_decode($redis->Get('Q_EXCLUDE_DOMAINS'), true); + } $settings['max_size'] = $redis->Get('Q_MAX_SIZE'); + $settings['max_score'] = $redis->Get('Q_MAX_SCORE'); + $settings['max_age'] = $redis->Get('Q_MAX_AGE'); $settings['retention_size'] = $redis->Get('Q_RETENTION_SIZE'); + $settings['release_format'] = $redis->Get('Q_RELEASE_FORMAT'); + $settings['subject'] = $redis->Get('Q_SUBJ'); + $settings['sender'] = $redis->Get('Q_SENDER'); + $settings['bcc'] = $redis->Get('Q_BCC'); + $settings['redirect'] = $redis->Get('Q_REDIRECT'); + $settings['html_tmpl'] = htmlspecialchars($redis->Get('Q_HTML')); + if (empty($settings['html_tmpl'])) { + $settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quarantine.tpl")); + } } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -376,13 +810,36 @@ function quarantine($_action, $_data = null) { if (!is_numeric($_data) || empty($_data)) { return false; } - $stmt = $pdo->prepare('SELECT `rcpt`, `symbols`, `msg`, `domain` FROM `quarantine` WHERE `id`= :id'); + $stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `id`= :id'); $stmt->execute(array(':id' => $_data)); $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt'])) { + if (hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) || $_SESSION['mailcow_cc_role'] == 'admin') { return $row; } + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); return false; break; + case 'hash_details': + $hash = trim($_data); + if (preg_match("/^([a-f0-9]{64})$/", $hash) === false) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return false; + } + $stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `qhash` = :hash'); + $stmt->execute(array(':hash' => $hash)); + return $stmt->fetch(PDO::FETCH_ASSOC); + break; } } diff --git a/data/web/inc/functions.quota_notification.inc.php b/data/web/inc/functions.quota_notification.inc.php new file mode 100644 index 0000000000..9f58bfb4ac --- /dev/null +++ b/data/web/inc/functions.quota_notification.inc.php @@ -0,0 +1,150 @@ + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + switch ($_action) { + case 'edit': + $retention_size = $_data['retention_size']; + if ($_data['release_format'] == 'attachment' || $_data['release_format'] == 'raw') { + $release_format = $_data['release_format']; + } + else { + $release_format = 'raw'; + } + $subject = $_data['subject']; + $sender = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $_data['sender']); + if (filter_var($sender, FILTER_VALIDATE_EMAIL) === false) { + $sender = ''; + } + $html = $_data['html_tmpl']; + try { + $redis->Set('QW_SENDER', $sender); + $redis->Set('QW_SUBJ', $subject); + $redis->Set('QW_HTML', $html); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'saved_settings' + ); + break; + case 'get': + try { + $settings['subject'] = $redis->Get('QW_SUBJ'); + $settings['sender'] = $redis->Get('QW_SENDER'); + $settings['html_tmpl'] = htmlspecialchars($redis->Get('QW_HTML')); + if (empty($settings['html_tmpl'])) { + $settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quota.tpl")); + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return $settings; + break; + } +} +function quota_notification_bcc($_action, $_data = null) { + global $redis; + $_data_log = $_data; + if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + switch ($_action) { + case 'edit': + $domain = $_data['domain']; + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $active = intval($_data['active']); + $bcc_rcpts = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['bcc_rcpt'])); + foreach ($bcc_rcpts as $i => &$rcpt) { + $rcpt = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $rcpt); + if (!empty($rcpt) && filter_var($rcpt, FILTER_VALIDATE_EMAIL) === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('goto_invalid', htmlspecialchars($rcpt)) + ); + unset($bcc_rcpts[$i]); + continue; + } + } + $bcc_rcpts = array_unique($bcc_rcpts); + $bcc_rcpts = array_filter($bcc_rcpts); + if (empty($bcc_rcpts)) { + $active = 0; + + } + try { + $redis->hSet('QW_BCC', $domain, json_encode(array('bcc_rcpts' => $bcc_rcpts, 'active' => $active))); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'saved_settings' + ); + break; + case 'get': + $domain = $_data; + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + try { + return json_decode($redis->hGet('QW_BCC', $domain), true); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + break; + } +} diff --git a/data/web/inc/functions.ratelimit.inc.php b/data/web/inc/functions.ratelimit.inc.php index 7fac01e463..91ac999e29 100644 --- a/data/web/inc/functions.ratelimit.inc.php +++ b/data/web/inc/functions.ratelimit.inc.php @@ -1,11 +1,10 @@ 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -25,7 +24,7 @@ function ratelimit($_action, $_scope, $_data = null) { foreach ($objects as $object) { $rl_value = intval($_data['rl_value']); $rl_frame = $_data['rl_frame']; - if (!in_array($rl_frame, array('s', 'm', 'h'))) { + if (!in_array($rl_frame, array('s', 'm', 'h', 'd'))) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), @@ -85,7 +84,7 @@ function ratelimit($_action, $_scope, $_data = null) { foreach ($objects as $object) { $rl_value = intval($_data['rl_value']); $rl_frame = $_data['rl_frame']; - if (!in_array($rl_frame, array('s', 'm', 'h'))) { + if (!in_array($rl_frame, array('s', 'm', 'h', 'd'))) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), @@ -94,7 +93,7 @@ function ratelimit($_action, $_scope, $_data = null) { continue; } if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object) - || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) { + || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin' && $_SESSION['access_all_exception'] != '1')) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), @@ -192,5 +191,52 @@ function ratelimit($_action, $_scope, $_data = null) { break; } break; + case 'delete': + $data['hash'] = $_data; + if ($_SESSION['mailcow_cc_role'] != 'admin' || !preg_match('/^RL[0-9A-Za-z=]+$/i', trim($data['hash']))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + try { + $data_rllog = $redis->lRange('RL_LOG', 0, -1); + if ($data_rllog) { + foreach ($data_rllog as $json_line) { + if (preg_match('/' . $data['hash'] . '/i', $json_line)) { + $redis->lRem('RL_LOG', $json_line, 0); + } + } + } + if ($redis->type($data['hash']) == Redis::REDIS_HASH) { + $redis->delete($data['hash']); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'hash_deleted' + ); + return true; + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'hash_not_found' + ); + return false; + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return false; + break; } } \ No newline at end of file diff --git a/data/web/inc/functions.relayhost.inc.php b/data/web/inc/functions.relayhost.inc.php deleted file mode 100644 index a3e1ffdaf9..0000000000 --- a/data/web/inc/functions.relayhost.inc.php +++ /dev/null @@ -1,174 +0,0 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $hostname = trim($_data['hostname']); - $username = str_replace(':', '\:', trim($_data['username'])); - $password = str_replace(':', '\:', trim($_data['password'])); - if (empty($hostname)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('invalid_host', htmlspecialchars($host)) - ); - return false; - } - try { - $stmt = $pdo->prepare("INSERT INTO `relayhosts` (`hostname`, `username` ,`password`, `active`) - VALUES (:hostname, :username, :password, :active)"); - $stmt->execute(array( - ':hostname' => $hostname, - ':username' => $username, - ':password' => str_replace(':', '\:', $password), - ':active' => '1' - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', $hosts))) - ); - break; - case 'edit': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - $is_now = relayhost('details', $id); - if (!empty($is_now)) { - $hostname = (!empty($_data['hostname'])) ? trim($_data['hostname']) : $is_now['hostname']; - $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username']; - $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password']; - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_invalid', $id) - ); - continue; - } - try { - $stmt = $pdo->prepare("UPDATE `relayhosts` SET - `hostname` = :hostname, - `username` = :username, - `password` = :password, - `active` = :active - WHERE `id` = :id"); - $stmt->execute(array( - ':id' => $id, - ':hostname' => $hostname, - ':username' => $username, - ':password' => $password, - ':active' => $active - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_modified', htmlspecialchars(implode(', ', $hostnames))) - ); - } - break; - case 'delete': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - try { - $stmt = $pdo->prepare("DELETE FROM `relayhosts` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - $stmt = $pdo->prepare("UPDATE `domain` SET `relayhost` = '0' WHERE `relayhost`= :id"); - $stmt->execute(array(':id' => $id)); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_removed', htmlspecialchars($id)) - ); - } - break; - case 'get': - if ($_SESSION['mailcow_cc_role'] != "admin") { - return false; - } - $relayhosts = array(); - $stmt = $pdo->query("SELECT `id`, `hostname`, `username` FROM `relayhosts`"); - $relayhosts = $stmt->fetchAll(PDO::FETCH_ASSOC); - return $relayhosts; - break; - case 'details': - if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { - return false; - } - $relayhostdata = array(); - $stmt = $pdo->prepare("SELECT `id`, - `hostname`, - `username`, - `password`, - `active` AS `active_int`, - CONCAT(LEFT(`password`, 3), '...') AS `password_short`, - CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` - FROM `relayhosts` - WHERE `id` = :id"); - $stmt->execute(array(':id' => $_data)); - $relayhostdata = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($relayhostdata)) { - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`domain` SEPARATOR ', ') AS `used_by_domains` FROM `domain` WHERE `relayhost` = :id"); - $stmt->execute(array(':id' => $_data)); - $used_by_domains = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_domains']; - $used_by_domains = (empty($used_by_domains)) ? '' : $used_by_domains; - $relayhostdata['used_by_domains'] = $used_by_domains; - } - return $relayhostdata; - break; - } -} \ No newline at end of file diff --git a/data/web/inc/functions.rsettings.inc.php b/data/web/inc/functions.rsettings.inc.php deleted file mode 100644 index fbf8922bab..0000000000 --- a/data/web/inc/functions.rsettings.inc.php +++ /dev/null @@ -1,160 +0,0 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $content = $_data['content']; - $desc = $_data['desc']; - $active = intval($_data['active']); - if (empty($content)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'map_content_empty' - ); - return false; - } - try { - $stmt = $pdo->prepare("INSERT INTO `settingsmap` (`content`, `desc`, `active`) - VALUES (:content, :desc, :active)"); - $stmt->execute(array( - ':content' => $content, - ':desc' => $desc, - ':active' => $active - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'settings_map_added' - ); - break; - case 'edit': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - $is_now = rsettings('details', $id); - if (!empty($is_now)) { - $content = (!empty($_data['content'])) ? $_data['content'] : $is_now['content']; - $desc = (!empty($_data['desc'])) ? $_data['desc'] : $is_now['desc']; - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('settings_map_invalid', $id) - ); - continue; - } - $content = trim($content); - try { - $stmt = $pdo->prepare("UPDATE `settingsmap` SET - `content` = :content, - `desc` = :desc, - `active` = :active - WHERE `id` = :id"); - $stmt->execute(array( - ':content' => $content, - ':desc' => $desc, - ':active' => $active, - ':id' => $id - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_modified', htmlspecialchars($ids)) - ); - } - break; - case 'delete': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - try { - $stmt = $pdo->prepare("DELETE FROM `settingsmap` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('settings_map_removed', htmlspecialchars($id)) - ); - } - break; - case 'get': - if ($_SESSION['mailcow_cc_role'] != "admin") { - return false; - } - $settingsmaps = array(); - $stmt = $pdo->query("SELECT `id`, `desc`, `active` FROM `settingsmap`"); - $settingsmaps = $stmt->fetchAll(PDO::FETCH_ASSOC); - return $settingsmaps; - break; - case 'details': - if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { - return false; - } - $settingsmapdata = array(); - $stmt = $pdo->prepare("SELECT `id`, - `desc`, - `content`, - `active` AS `active_int`, - CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` - FROM `settingsmap` - WHERE `id` = :id"); - $stmt->execute(array(':id' => $_data)); - $settingsmapdata = $stmt->fetch(PDO::FETCH_ASSOC); - return $settingsmapdata; - break; - } -} \ No newline at end of file diff --git a/data/web/inc/functions.rspamd.inc.php b/data/web/inc/functions.rspamd.inc.php new file mode 100644 index 0000000000..c2444d99d1 --- /dev/null +++ b/data/web/inc/functions.rspamd.inc.php @@ -0,0 +1,220 @@ + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $content = $_data['content']; + $desc = $_data['desc']; + $active = intval($_data['active']); + if (empty($content)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'map_content_empty' + ); + return false; + } + $stmt = $pdo->prepare("INSERT INTO `settingsmap` (`content`, `desc`, `active`) + VALUES (:content, :desc, :active)"); + $stmt->execute(array( + ':content' => $content, + ':desc' => $desc, + ':active' => $active + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'settings_map_added' + ); + break; + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = rsettings('details', $id); + if (!empty($is_now)) { + $content = (!empty($_data['content'])) ? $_data['content'] : $is_now['content']; + $desc = (!empty($_data['desc'])) ? $_data['desc'] : $is_now['desc']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('settings_map_invalid', $id) + ); + continue; + } + $content = trim($content); + $stmt = $pdo->prepare("UPDATE `settingsmap` SET + `content` = :content, + `desc` = :desc, + `active` = :active + WHERE `id` = :id"); + $stmt->execute(array( + ':content' => $content, + ':desc' => $desc, + ':active' => $active, + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars(implode(',', $ids))) + ); + } + break; + case 'delete': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $stmt = $pdo->prepare("DELETE FROM `settingsmap` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('settings_map_removed', htmlspecialchars($id)) + ); + } + break; + case 'get': + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + $settingsmaps = array(); + $stmt = $pdo->query("SELECT `id`, `desc`, `active` FROM `settingsmap`"); + $settingsmaps = $stmt->fetchAll(PDO::FETCH_ASSOC); + return $settingsmaps; + break; + case 'details': + if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { + return false; + } + $settingsmapdata = array(); + $stmt = $pdo->prepare("SELECT `id`, + `desc`, + `content`, + `active` + FROM `settingsmap` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $settingsmapdata = $stmt->fetch(PDO::FETCH_ASSOC); + return $settingsmapdata; + break; + } +} +function rspamd_maps($_action, $_data = null) { + global $pdo; + global $lang; + global $RSPAMD_MAPS; + $_data_log = $_data; + switch ($_action) { + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, '-'), + 'msg' => 'access_denied' + ); + return false; + } + $maps = (array)$_data['map']; + $valid_maps = array(); + foreach ($maps as $map) { + $is_valid = false; + foreach ($RSPAMD_MAPS as $rspamd_map_type) { + if (in_array($map, $rspamd_map_type)) { + $is_valid = true; + break; + } + } + if ($is_valid) { + array_push($valid_maps, $map); + } else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, '-'), + 'msg' => array('global_map_invalid', $map) + ); + } + } + foreach ($valid_maps as $map) { + try { + if (file_exists('/rspamd_custom_maps/' . $map)) { + $map_content = trim($_data['rspamd_map_data']); + $map_handle = fopen('/rspamd_custom_maps/' . $map, 'w'); + if (!$map_handle) { + throw new Exception($lang['danger']['file_open_error']); + } + fwrite($map_handle, $map_content . PHP_EOL); + fclose($map_handle); + sleep(1.5); + touch('/rspamd_custom_maps/' . $map); + } + } + catch (Exception $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, '-'), + 'msg' => array('global_map_write_error', htmlspecialchars($map), htmlspecialchars($e->getMessage())) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, '-'), + 'msg' => array('object_modified', htmlspecialchars($map)) + ); + } + break; + } +} +function rspamd_actions() { + if (isset($_SESSION["mailcow_cc_role"]) && $_SESSION["mailcow_cc_role"] == "admin") { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/stat"); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + $data = curl_exec($curl); + if ($data) { + $return = array(); + $stats_array = json_decode($data, true)['actions']; + $stats_array['soft reject'] = $stats_array['soft reject'] + $stats_array['greylist']; + unset($stats_array['greylist']); + foreach ($stats_array as $action => $count) { + $return[] = array($action, $count); + } + return $return; + } + else { + return false; + } + } + else { + return false; + } +} diff --git a/data/web/inc/functions.tls_policy_maps.inc.php b/data/web/inc/functions.tls_policy_maps.inc.php index 6581b60d4c..9d42b2c8e9 100644 --- a/data/web/inc/functions.tls_policy_maps.inc.php +++ b/data/web/inc/functions.tls_policy_maps.inc.php @@ -7,9 +7,17 @@ function tls_policy_maps($_action, $_data = null, $attr = null) { } switch ($_action) { case 'add': - $dest = idn_to_ascii(trim($_data['dest'])); + $dest = idn_to_ascii(trim($_data['dest']), 0, INTL_IDNA_VARIANT_UTS46); $policy = strtolower(trim($_data['policy'])); $parameters = (isset($_data['parameters']) && !empty($_data['parameters'])) ? $_data['parameters'] : ''; + if (empty($dest) || in_array($dest, array('.', '*', '@'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'tls_policy_map_dest_invalid' + ); + return false; + } if (!empty($parameters)) { foreach (explode(' ', $parameters) as $parameter) { if (!preg_match('/(.+)\=(.+)/i', $parameter)) { @@ -53,7 +61,7 @@ function tls_policy_maps($_action, $_data = null, $attr = null) { foreach ($ids as $id) { $is_now = tls_policy_maps('details', $id); if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; $dest = (!empty($_data['dest'])) ? $_data['dest'] : $is_now['dest']; $policy = (!empty($_data['policy'])) ? $_data['policy'] : $is_now['policy']; $parameters = (isset($_data['parameters'])) ? $_data['parameters'] : $is_now['parameters']; @@ -66,6 +74,14 @@ function tls_policy_maps($_action, $_data = null, $attr = null) { ); continue; } + if (empty($dest) || in_array($dest, array('.', '*', '@'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'tls_policy_map_dest_invalid' + ); + return false; + } if (!empty($parameters)) { foreach (explode(' ', $parameters) as $parameter) { if (!preg_match('/(.+)\=(.+)/i', $parameter)) { @@ -117,8 +133,7 @@ function tls_policy_maps($_action, $_data = null, $attr = null) { `dest`, `policy`, `parameters`, - `active` AS `active_int`, - CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, + `active` AS `active`, `created`, `modified` FROM `tls_policy_override` WHERE `id` = :id"); diff --git a/data/web/inc/functions.transports.inc.php b/data/web/inc/functions.transports.inc.php new file mode 100644 index 0000000000..05ad25d859 --- /dev/null +++ b/data/web/inc/functions.transports.inc.php @@ -0,0 +1,508 @@ + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $hostname = trim($_data['hostname']); + $username = str_replace(':', '\:', trim($_data['username'])); + $password = str_replace(':', '\:', trim($_data['password'])); + if (empty($hostname)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_host', htmlspecialchars($host)) + ); + return false; + } + try { + $stmt = $pdo->prepare("INSERT INTO `relayhosts` (`hostname`, `username` ,`password`, `active`) + VALUES (:hostname, :username, :password, :active)"); + $stmt->execute(array( + ':hostname' => $hostname, + ':username' => $username, + ':password' => str_replace(':', '\:', $password), + ':active' => '1' + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', (array)$hosts))) + ); + break; + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = relayhost('details', $id); + if (!empty($is_now)) { + $hostname = (!empty($_data['hostname'])) ? trim($_data['hostname']) : $is_now['hostname']; + $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username']; + $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_invalid', $id) + ); + continue; + } + try { + $stmt = $pdo->prepare("UPDATE `relayhosts` SET + `hostname` = :hostname, + `username` = :username, + `password` = :password, + `active` = :active + WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id, + ':hostname' => $hostname, + ':username' => $username, + ':password' => $password, + ':active' => $active + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars(implode(', ', (array)$hostnames))) + ); + } + break; + case 'delete': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + try { + $stmt = $pdo->prepare("DELETE FROM `relayhosts` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $stmt = $pdo->prepare("UPDATE `domain` SET `relayhost` = '0' WHERE `relayhost`= :id"); + $stmt->execute(array(':id' => $id)); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_removed', htmlspecialchars($id)) + ); + } + break; + case 'get': + if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { + return false; + } + $relayhosts = array(); + $stmt = $pdo->query("SELECT `id`, `hostname`, `username`, `active` FROM `relayhosts`"); + $relayhosts = $stmt->fetchAll(PDO::FETCH_ASSOC); + return $relayhosts; + break; + case 'details': + if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { + return false; + } + $relayhostdata = array(); + $stmt = $pdo->prepare("SELECT `id`, + `hostname`, + `username`, + `password`, + `active`, + CONCAT(LEFT(`password`, 3), '...') AS `password_short` + FROM `relayhosts` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $relayhostdata = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($relayhostdata)) { + $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`domain` SEPARATOR ', ') AS `used_by_domains` FROM `domain` WHERE `relayhost` = :id"); + $stmt->execute(array(':id' => $_data)); + $used_by_domains = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_domains']; + $used_by_domains = (empty($used_by_domains)) ? '' : $used_by_domains; + $relayhostdata['used_by_domains'] = $used_by_domains; + $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`username` SEPARATOR ', ') AS `used_by_mailboxes` FROM `mailbox` WHERE JSON_VALUE(`attributes`, '$.relayhost') = :id"); + $stmt->execute(array(':id' => $_data)); + $used_by_mailboxes = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_mailboxes']; + $used_by_mailboxes = (empty($used_by_mailboxes)) ? '' : $used_by_mailboxes; + $relayhostdata['used_by_mailboxes'] = $used_by_mailboxes; + } + return $relayhostdata; + break; + } +} +function transport($_action, $_data = null) { + global $pdo; + global $lang; + $_data_log = $_data; + switch ($_action) { + case 'add': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $destinations = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['destination'])); + $active = intval($_data['active']); + $is_mx_based = intval($_data['is_mx_based']); + $nexthop = trim($_data['nexthop']); + if (filter_var($nexthop, FILTER_VALIDATE_IP)) { + $nexthop = '[' . $nexthop . ']'; + } + preg_match('/\[(.+)\].*/', $nexthop, $next_hop_matches); + $next_hop_clean = (isset($next_hop_matches[1])) ? $next_hop_matches[1] : $nexthop; + $username = str_replace(':', '\:', trim($_data['username'])); + $password = str_replace(':', '\:', trim($_data['password'])); + if (empty($nexthop)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_nexthop') + ); + return false; + } + $transports = transport('get'); + if (!empty($transports)) { + foreach ($transports as $transport) { + $transport_data = transport('details', $transport['id']); + $existing_nh[] = $transport_data['nexthop']; + preg_match('/\[(.+)\].*/', $transport_data['nexthop'], $existing_clean_nh[]); + if (($transport_data['nexthop'] == $nexthop || $transport_data['nexthop'] == $next_hop_clean) && $transport_data['username'] != $username) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'invalid_nexthop_authenticated' + ); + return false; + } + foreach ($destinations as $d_ix => &$dest) { + if (empty($dest)) { + unset($destinations[$d_ix]); + continue; + } + if ($transport_data['destination'] == $dest) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('transport_dest_exists', $dest) + ); + unset($destinations[$d_ix]); + continue; + } + // ".domain" is a valid destination, "..domain" is not + if ($is_mx_based == 0 && (empty($dest) || (is_valid_domain_name(preg_replace('/^' . preg_quote('.', '/') . '/', '', $dest)) === false && $dest != '*' && filter_var($dest, FILTER_VALIDATE_EMAIL) === false))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_destination', $dest) + ); + unset($destinations[$d_ix]); + continue; + } + if ($is_mx_based == 1 && (empty($dest) || @preg_match('/' . $dest . '/', null) === false)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_destination', $dest) + ); + unset($destinations[$d_ix]); + continue; + } + } + } + } + $destinations = array_filter(array_values(array_unique($destinations))); + if (empty($destinations)) { return false; } + if (isset($next_hop_matches[1])) { + if ($existing_nh !== null && in_array($next_hop_clean, $existing_nh)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('next_hop_interferes', $next_hop_clean, $nexthop) + ); + return false; + } + } + else { + foreach ($existing_clean_nh as $existing_clean_nh_each) { + if ($existing_clean_nh_each[1] == $nexthop) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('next_hop_interferes_any', $nexthop) + ); + return false; + } + } + } + foreach ($destinations as $insert_dest) { + $stmt = $pdo->prepare("INSERT INTO `transports` (`nexthop`, `destination`, `is_mx_based`, `username` , `password`, `active`) + VALUES (:nexthop, :destination, :is_mx_based, :username, :password, :active)"); + $stmt->execute(array( + ':nexthop' => $nexthop, + ':destination' => $insert_dest, + ':is_mx_based' => $is_mx_based, + ':username' => $username, + ':password' => str_replace(':', '\:', $password), + ':active' => $active + )); + } + $stmt = $pdo->prepare("UPDATE `transports` SET + `username` = :username, + `password` = :password + WHERE `nexthop` = :nexthop"); + $stmt->execute(array( + ':nexthop' => $nexthop, + ':username' => $username, + ':password' => $password + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', (array)$hosts))) + ); + break; + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = transport('details', $id); + if (!empty($is_now)) { + $destination = (!empty($_data['destination'])) ? trim($_data['destination']) : $is_now['destination']; + $nexthop = (!empty($_data['nexthop'])) ? trim($_data['nexthop']) : $is_now['nexthop']; + $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username']; + $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password']; + $is_mx_based = (isset($_data['is_mx_based']) && $_data['is_mx_based'] != '') ? intval($_data['is_mx_based']) : $is_now['is_mx_based']; + $active = (isset($_data['active']) && $_data['active'] != '') ? intval($_data['active']) : $is_now['active']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_invalid', $id) + ); + continue; + } + preg_match('/\[(.+)\].*/', $nexthop, $next_hop_matches); + if (filter_var($nexthop, FILTER_VALIDATE_IP)) { + $nexthop = '[' . $nexthop . ']'; + } + $next_hop_clean = (isset($next_hop_matches[1])) ? $next_hop_matches[1] : $nexthop; + $transports = transport('get'); + if (!empty($transports)) { + foreach ($transports as $transport) { + $transport_data = transport('details', $transport['id']); + if ($transport['id'] == $id) { + continue; + } + $existing_nh[] = $transport_data['nexthop']; + preg_match('/\[(.+)\].*/', $transport_data['nexthop'], $existing_clean_nh[]); + if ($transport_data['destination'] == $destination) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'transport_dest_exists' + ); + return false; + } + } + } + if ($is_mx_based == 0 && (empty($destination) || (is_valid_domain_name(preg_replace('/^' . preg_quote('.', '/') . '/', '', $destination)) === false && $destination != '*' && filter_var($destination, FILTER_VALIDATE_EMAIL) === false))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_destination', $destination) + ); + return false; + } + if ($is_mx_based == 1 && (empty($destination) || @preg_match('/' . $destination . '/', null) === false)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_destination', $destination) + ); + return false; + } + if (isset($next_hop_matches[1])) { + if ($existing_nh !== null && in_array($next_hop_clean, $existing_nh)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('next_hop_interferes', $next_hop_clean, $nexthop) + ); + return false; + } + } + else { + foreach ($existing_clean_nh as $existing_clean_nh_each) { + if ($existing_clean_nh_each[1] == $nexthop) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('next_hop_interferes_any', $nexthop) + ); + return false; + } + } + } + if (empty($username)) { + $password = ''; + } + try { + $stmt = $pdo->prepare("UPDATE `transports` SET + `destination` = :destination, + `is_mx_based` = :is_mx_based, + `nexthop` = :nexthop, + `username` = :username, + `password` = :password, + `active` = :active + WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id, + ':destination' => $destination, + ':is_mx_based' => $is_mx_based, + ':nexthop' => $nexthop, + ':username' => $username, + ':password' => $password, + ':active' => $active + )); + $stmt = $pdo->prepare("UPDATE `transports` SET + `username` = :username, + `password` = :password + WHERE `nexthop` = :nexthop"); + $stmt->execute(array( + ':nexthop' => $nexthop, + ':username' => $username, + ':password' => $password + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars(implode(', ', (array)$hostnames))) + ); + } + break; + case 'delete': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + try { + $stmt = $pdo->prepare("DELETE FROM `transports` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_removed', htmlspecialchars($id)) + ); + } + break; + case 'get': + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + $transports = array(); + $stmt = $pdo->query("SELECT `id`, `is_mx_based`, `destination`, `nexthop`, `username` FROM `transports`"); + $transports = $stmt->fetchAll(PDO::FETCH_ASSOC); + return $transports; + break; + case 'details': + if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { + return false; + } + $transportdata = array(); + $stmt = $pdo->prepare("SELECT `id`, + `is_mx_based`, + `destination`, + `nexthop`, + `username`, + `password`, + `active`, + CONCAT(LEFT(`password`, 3), '...') AS `password_short` + FROM `transports` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $transportdata = $stmt->fetch(PDO::FETCH_ASSOC); + return $transportdata; + break; + } +} diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php index 15dc4f022e..e5737b63ce 100644 --- a/data/web/inc/header.inc.php +++ b/data/web/inc/header.inc.php @@ -1,145 +1,97 @@ - - - - - - - - - <?=$UI_TEXTS['title_name'];?> - - - - - - - - - - - - - - - - - - ' : null; ?> - ' : null; ?> - ' : null; ?> - ' : null; ?> - ' : null; ?> - ' : null; ?> - - - - -
    - -
    +add('/web/css/site/mailbox.css'); +} +if (preg_match("/admin/i", $_SERVER['REQUEST_URI'])) { + $css_minifier->add('/web/css/site/admin.css'); +} +if (preg_match("/user/i", $_SERVER['REQUEST_URI'])) { + $css_minifier->add('/web/css/site/user.css'); +} +if (preg_match("/edit/i", $_SERVER['REQUEST_URI'])) { + $css_minifier->add('/web/css/site/edit.css'); +} +if (preg_match("/(quarantine|qhandler)/i", $_SERVER['REQUEST_URI'])) { + $css_minifier->add('/web/css/site/quarantine.css'); +} +if (preg_match("/debug/i", $_SERVER['REQUEST_URI'])) { + $css_minifier->add('/web/css/site/debug.css'); +} +if ($_SERVER['REQUEST_URI'] == '/') { + $css_minifier->add('/web/css/site/index.css'); +} + +$hash = $css_minifier->getDataHash(); +$CSSPath = '/tmp/' . $hash . '.css'; +if(!file_exists($CSSPath)) { + $css_minifier->minify($CSSPath); + cleanupCSS($hash); +} + +$mailcow_apps_processed = $MAILCOW_APPS; +$app_links = customize('get', 'app_links'); +$app_links_processed = $app_links; +$hide_mailcow_apps = true; +for ($i = 0; $i < count($mailcow_apps_processed); $i++) { + if ($hide_mailcow_apps && !$mailcow_apps_processed[$i]['hide']){ + $hide_mailcow_apps = false; + } + if (!empty($_SESSION['mailcow_cc_username'])){ + if ($app_links_processed[$i]['user_link']) { + $mailcow_apps_processed[$i]['user_link'] = str_replace('%u', $_SESSION['mailcow_cc_username'], $mailcow_apps_processed[$i]['user_link']); + } else { + $mailcow_apps_processed[$i]['user_link'] = $mailcow_apps_processed[$i]['link']; + } + } +} +if ($app_links_processed){ + for ($i = 0; $i < count($app_links_processed); $i++) { + $key = array_key_first($app_links_processed[$i]); + if ($hide_mailcow_apps && !$app_links_processed[$i][$key]['hide']){ + $hide_mailcow_apps = false; + } + if (!empty($_SESSION['mailcow_cc_username'])){ + if ($app_links_processed[$i][$key]['user_link']) { + $app_links_processed[$i][$key]['user_link'] = str_replace('%u', $_SESSION['mailcow_cc_username'], $app_links_processed[$i][$key]['user_link']); + } else { + $app_links_processed[$i][$key]['user_link'] = $app_links_processed[$i][$key]['link']; + } + } + } +} + +// Workaround to get text with
    straight to twig. +// Using "nl2br" doesn't work with Twig as it would escape everything by default. +if (isset($UI_TEXTS["ui_footer"])) { + $UI_TEXTS["ui_footer"] = nl2br($UI_TEXTS["ui_footer"]); +} + +$globalVariables = [ + 'mailcow_hostname' => getenv('MAILCOW_HOSTNAME'), + 'mailcow_locale' => @$_SESSION['mailcow_locale'], + 'mailcow_cc_role' => @$_SESSION['mailcow_cc_role'], + 'mailcow_cc_username' => @$_SESSION['mailcow_cc_username'], + 'is_master' => preg_match('/y|yes/i', getenv('MASTER')), + 'dual_login' => @$_SESSION['dual-login'], + 'ui_texts' => $UI_TEXTS, + 'css_path' => '/cache/'.basename($CSSPath), + 'logo' => customize('get', 'main_logo'), + 'logo_dark' => customize('get', 'main_logo_dark'), + 'available_languages' => $AVAILABLE_LANGUAGES, + 'lang' => $lang, + 'skip_sogo' => (getenv('SKIP_SOGO') == 'y'), + 'allow_admin_email_login' => (getenv('ALLOW_ADMIN_EMAIL_LOGIN') == 'n'), + 'hide_mailcow_apps' => $hide_mailcow_apps, + 'mailcow_apps' => $MAILCOW_APPS, + 'mailcow_apps_processed' => $mailcow_apps_processed, + 'app_links' => $app_links, + 'app_links_processed' => $app_links_processed, + 'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'), + 'uri' => parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/', +]; + +foreach ($globalVariables as $globalVariableName => $globalVariableValue) { + $twig->addGlobal($globalVariableName, $globalVariableValue); +} diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index f2db0f1a1f..23aa5e77f0 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -1,1018 +1,1583 @@ -query("SHOW TABLES LIKE 'versions'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $stmt = $pdo->query("SELECT `version` FROM `versions`"); - if ($stmt->fetch(PDO::FETCH_ASSOC)['version'] == $db_version) { - return true; - } - } - - $views = array( - "grouped_mail_aliases" => "CREATE VIEW grouped_mail_aliases (username, aliases) AS - SELECT goto, IFNULL(GROUP_CONCAT(address SEPARATOR ' '), '') AS address FROM alias - WHERE address!=goto - AND active = '1' - AND address NOT LIKE '@%' - GROUP BY goto;", - "grouped_sender_acl" => "CREATE VIEW grouped_sender_acl (username, send_as_acl) AS - SELECT logged_in_as, IFNULL(GROUP_CONCAT(send_as SEPARATOR ' '), '') AS send_as_acl FROM sender_acl - WHERE send_as NOT LIKE '@%' - GROUP BY logged_in_as;", - "grouped_domain_alias_address" => "CREATE VIEW grouped_domain_alias_address (username, ad_alias) AS - SELECT username, IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ' '), '') AS ad_alias FROM mailbox - LEFT OUTER JOIN alias_domain ON target_domain=domain - GROUP BY username;", - "sieve_before" => "CREATE VIEW sieve_before (id, username, script_name, script_data) AS - SELECT md5(script_data), username, script_name, script_data FROM sieve_filters - WHERE filter_type = 'prefilter';", - "sieve_after" => "CREATE VIEW sieve_after (id, username, script_name, script_data) AS - SELECT md5(script_data), username, script_name, script_data FROM sieve_filters - WHERE filter_type = 'postfilter';" - ); - - $tables = array( - "versions" => array( - "cols" => array( - "application" => "VARCHAR(255) NOT NULL", - "version" => "VARCHAR(100) NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - ), - "keys" => array( - "primary" => array( - "" => array("application") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "admin" => array( - "cols" => array( - "username" => "VARCHAR(255) NOT NULL", - "password" => "VARCHAR(255) NOT NULL", - "superadmin" => "TINYINT(1) NOT NULL DEFAULT '0'", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE NOW(0)", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("username") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "_sogo_static_view" => array( - "cols" => array( - "c_uid" => "VARCHAR(255) NOT NULL", - "domain" => "VARCHAR(255) NOT NULL", - "c_name" => "VARCHAR(255) NOT NULL", - "c_password" => "VARCHAR(255) NOT NULL DEFAULT ''", - "c_cn" => "VARCHAR(255)", - "mail" => "VARCHAR(255) NOT NULL", - // TODO -> use TEXT and check if SOGo login breaks on empty aliases - "aliases" => "TEXT NOT NULL", - "ad_aliases" => "VARCHAR(6144) NOT NULL DEFAULT ''", - "home" => "VARCHAR(255)", - "kind" => "VARCHAR(100) NOT NULL DEFAULT ''", - "multiple_bookings" => "INT NOT NULL DEFAULT -1" - ), - "keys" => array( - "primary" => array( - "" => array("c_uid") - ), - "key" => array( - "domain" => array("domain") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "relayhosts" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "hostname" => "VARCHAR(255) NOT NULL", - "username" => "VARCHAR(255) NOT NULL", - "password" => "VARCHAR(255) NOT NULL", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "key" => array( - "hostname" => array("hostname") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "alias" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "address" => "VARCHAR(255) NOT NULL", - "goto" => "TEXT NOT NULL", - "domain" => "VARCHAR(255) NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "unique" => array( - "address" => array("address") - ), - "key" => array( - "domain" => array("domain") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "api" => array( - "cols" => array( - "api_key" => "VARCHAR(255) NOT NULL", - "allow_from" => "VARCHAR(512) NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE NOW(0)", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("api_key") - ), - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sender_acl" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "logged_in_as" => "VARCHAR(255) NOT NULL", - "send_as" => "VARCHAR(255) NOT NULL" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "domain" => array( - "cols" => array( - "domain" => "VARCHAR(255) NOT NULL", - "description" => "VARCHAR(255)", - "aliases" => "INT(10) NOT NULL DEFAULT '0'", - "mailboxes" => "INT(10) NOT NULL DEFAULT '0'", - "maxquota" => "BIGINT(20) NOT NULL DEFAULT '0'", - "quota" => "BIGINT(20) NOT NULL DEFAULT '102400'", - "relayhost" => "VARCHAR(255) NOT NULL DEFAULT '0'", - "backupmx" => "TINYINT(1) NOT NULL DEFAULT '0'", - "relay_all_recipients" => "TINYINT(1) NOT NULL DEFAULT '0'", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("domain") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "tls_policy_override" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "dest" => "VARCHAR(255) NOT NULL", - "policy" => "ENUM('none', 'may', 'encrypt', 'dane', 'dane-only', 'fingerprint', 'verify', 'secure') NOT NULL", - "parameters" => "VARCHAR(255) DEFAULT ''", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "unique" => array( - "dest" => array("dest") - ), - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "quarantine" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "qid" => "VARCHAR(30) NOT NULL", - "score" => "FLOAT(8,2)", - "ip" => "VARBINARY(16)", - "action" => "CHAR(20) NOT NULL DEFAULT 'unknown'", - "symbols" => "JSON", - "sender" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'", - "rcpt" => "VARCHAR(255)", - "msg" => "LONGTEXT", - "domain" => "VARCHAR(255)", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "user" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'", - ), - "keys" => array( - "primary" => array( - "" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "mailbox" => array( - "cols" => array( - "username" => "VARCHAR(255) NOT NULL", - "password" => "VARCHAR(255) NOT NULL", - "name" => "VARCHAR(255)", - "maildir" => "VARCHAR(255) NOT NULL", - "quota" => "BIGINT(20) NOT NULL DEFAULT '102400'", - "local_part" => "VARCHAR(255) NOT NULL", - "domain" => "VARCHAR(255) NOT NULL", - "attributes" => "JSON", - "kind" => "VARCHAR(100) NOT NULL DEFAULT ''", - "multiple_bookings" => "INT NOT NULL DEFAULT -1", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("username") - ), - "key" => array( - "domain" => array("domain") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sieve_filters" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "username" => "VARCHAR(255) NOT NULL", - "script_desc" => "VARCHAR(255) NOT NULL", - "script_name" => "ENUM('active','inactive')", - "script_data" => "TEXT NOT NULL", - "filter_type" => "ENUM('postfilter','prefilter')", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "key" => array( - "username" => array("username"), - "script_desc" => array("script_desc") - ), - "fkey" => array( - "fk_username_sieve_global_before" => array( - "col" => "username", - "ref" => "mailbox.username", - "delete" => "CASCADE", - "update" => "NO ACTION" - ) - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "user_acl" => array( - "cols" => array( - "username" => "VARCHAR(255) NOT NULL", - "spam_alias" => "TINYINT(1) NOT NULL DEFAULT '1'", - "tls_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", - "spam_score" => "TINYINT(1) NOT NULL DEFAULT '1'", - "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", - "delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'", - "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'", - "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", - "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", - ), - "keys" => array( - "primary" => array( - "" => array("username") - ), - "fkey" => array( - "fk_username" => array( - "col" => "username", - "ref" => "mailbox.username", - "delete" => "CASCADE", - "update" => "NO ACTION" - ) - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "alias_domain" => array( - "cols" => array( - "alias_domain" => "VARCHAR(255) NOT NULL", - "target_domain" => "VARCHAR(255) NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("alias_domain") - ), - "key" => array( - "active" => array("active"), - "target_domain" => array("target_domain") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "spamalias" => array( - "cols" => array( - "address" => "VARCHAR(255) NOT NULL", - "goto" => "TEXT NOT NULL", - "validity" => "INT(11) NOT NULL" - ), - "keys" => array( - "primary" => array( - "" => array("address") - ), - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "filterconf" => array( - "cols" => array( - "object" => "VARCHAR(255) NOT NULL DEFAULT ''", - "option" => "VARCHAR(50) NOT NULL DEFAULT ''", - "value" => "VARCHAR(100) NOT NULL DEFAULT ''", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "prefid" => "INT(11) NOT NULL AUTO_INCREMENT" - ), - "keys" => array( - "primary" => array( - "" => array("prefid") - ), - "key" => array( - "object" => array("object") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "settingsmap" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "desc" => "VARCHAR(255) NOT NULL", - "content" => "LONGTEXT NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "logs" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "task" => "CHAR(32) NOT NULL DEFAULT '000000'", - "type" => "VARCHAR(32) DEFAULT ''", - "msg" => "TEXT", - "call" => "TEXT", - "user" => "VARCHAR(64) NOT NULL", - "role" => "VARCHAR(32) NOT NULL", - "remote" => "VARCHAR(39) NOT NULL", - "time" => "INT(11) NOT NULL" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "quota2" => array( - "cols" => array( - "username" => "VARCHAR(255) NOT NULL", - "bytes" => "BIGINT(20) NOT NULL DEFAULT '0'", - "messages" => "BIGINT(20) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("username") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "domain_admins" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "username" => "VARCHAR(255) NOT NULL", - "domain" => "VARCHAR(255) NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "active" => "TINYINT(1) NOT NULL DEFAULT '1'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "key" => array( - "username" => array("username") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "da_acl" => array( - "cols" => array( - "username" => "VARCHAR(255) NOT NULL", - "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'", - "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", - "login_as" => "TINYINT(1) NOT NULL DEFAULT '1'", - "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'", - "filters" => "TINYINT(1) NOT NULL DEFAULT '1'", - "ratelimit" => "TINYINT(1) NOT NULL DEFAULT '1'", - "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", - ), - "keys" => array( - "primary" => array( - "" => array("username") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "imapsync" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "user2" => "VARCHAR(255) NOT NULL", - "host1" => "VARCHAR(255) NOT NULL", - "authmech1" => "ENUM('PLAIN','LOGIN','CRAM-MD5') DEFAULT 'PLAIN'", - "regextrans2" => "VARCHAR(255) DEFAULT ''", - "authmd51" => "TINYINT(1) NOT NULL DEFAULT 0", - "domain2" => "VARCHAR(255) NOT NULL DEFAULT ''", - "subfolder2" => "VARCHAR(255) NOT NULL DEFAULT ''", - "user1" => "VARCHAR(255) NOT NULL", - "password1" => "VARCHAR(255) NOT NULL", - "exclude" => "VARCHAR(500) NOT NULL DEFAULT ''", - "maxage" => "SMALLINT NOT NULL DEFAULT '0'", - "mins_interval" => "VARCHAR(50) NOT NULL DEFAULT '0'", - "maxbytespersecond" => "VARCHAR(50) NOT NULL DEFAULT '0'", - "port1" => "SMALLINT NOT NULL", - "enc1" => "ENUM('TLS','SSL','PLAIN') DEFAULT 'TLS'", - "delete2duplicates" => "TINYINT(1) NOT NULL DEFAULT '1'", - "delete1" => "TINYINT(1) NOT NULL DEFAULT '0'", - "delete2" => "TINYINT(1) NOT NULL DEFAULT '0'", - "automap" => "TINYINT(1) NOT NULL DEFAULT '0'", - "skipcrossduplicates" => "TINYINT(1) NOT NULL DEFAULT '0'", - "custom_params" => "VARCHAR(512) NOT NULL DEFAULT ''", - "timeout1" => "SMALLINT NOT NULL DEFAULT '600'", - "timeout2" => "SMALLINT NOT NULL DEFAULT '600'", - "subscribeall" => "TINYINT(1) NOT NULL DEFAULT '1'", - "is_running" => "TINYINT(1) NOT NULL DEFAULT '0'", - "returned_text" => "MEDIUMTEXT", - "last_run" => "TIMESTAMP NULL DEFAULT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "bcc_maps" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "local_dest" => "VARCHAR(255) NOT NULL", - "bcc_dest" => "VARCHAR(255) NOT NULL", - "domain" => "VARCHAR(255) NOT NULL", - "type" => "ENUM('sender','rcpt')", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "key" => array( - "local_dest" => array("local_dest"), - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "recipient_maps" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "old_dest" => "VARCHAR(255) NOT NULL", - "new_dest" => "VARCHAR(255) NOT NULL", - "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", - "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "active" => "TINYINT(1) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "key" => array( - "local_dest" => array("old_dest"), - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "tfa" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "key_id" => "VARCHAR(255) NOT NULL", - "username" => "VARCHAR(255) NOT NULL", - "authmech" => "ENUM('yubi_otp', 'u2f', 'hotp', 'totp')", - "secret" => "VARCHAR(255) DEFAULT NULL", - "keyHandle" => "VARCHAR(255) DEFAULT NULL", - "publicKey" => "VARCHAR(255) DEFAULT NULL", - "counter" => "INT NOT NULL DEFAULT '0'", - "certificate" => "TEXT", - "active" => "TINYINT(1) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "forwarding_hosts" => array( - "cols" => array( - "host" => "VARCHAR(255) NOT NULL", - "source" => "VARCHAR(255) NOT NULL", - "filter_spam" => "TINYINT(1) NOT NULL DEFAULT '0'" - ), - "keys" => array( - "primary" => array( - "" => array("host") - ), - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_acl" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "c_folder_id" => "INT NOT NULL", - "c_object" => "VARCHAR(255) NOT NULL", - "c_uid" => "VARCHAR(255) NOT NULL", - "c_role" => "VARCHAR(80) NOT NULL" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ), - "key" => array( - "sogo_acl_c_folder_id_idx" => array("c_folder_id"), - "sogo_acl_c_uid_idx" => array("c_uid") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_alarms_folder" => array( - "cols" => array( - "id" => "INT NOT NULL AUTO_INCREMENT", - "c_path" => "VARCHAR(255) NOT NULL", - "c_name" => "VARCHAR(255) NOT NULL", - "c_uid" => "VARCHAR(255) NOT NULL", - "c_recurrence_id" => "INT(11) DEFAULT NULL", - "c_alarm_number" => "INT(11) NOT NULL", - "c_alarm_date" => "INT(11) NOT NULL" - ), - "keys" => array( - "primary" => array( - "" => array("id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_cache_folder" => array( - "cols" => array( - "c_uid" => "VARCHAR(255) NOT NULL", - "c_path" => "VARCHAR(255) NOT NULL", - "c_parent_path" => "VARCHAR(255) DEFAULT NULL", - "c_type" => "TINYINT(3) unsigned NOT NULL", - "c_creationdate" => "INT(11) NOT NULL", - "c_lastmodified" => "INT(11) NOT NULL", - "c_version" => "INT(11) NOT NULL DEFAULT '0'", - "c_deleted" => "TINYINT(4) NOT NULL DEFAULT '0'", - "c_content" => "LONGTEXT" - ), - "keys" => array( - "primary" => array( - "" => array("c_uid", "c_path") - ), - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_folder_info" => array( - "cols" => array( - "c_folder_id" => "BIGINT(20) unsigned NOT NULL AUTO_INCREMENT", - "c_path" => "VARCHAR(255) NOT NULL", - "c_path1" => "VARCHAR(255) NOT NULL", - "c_path2" => "VARCHAR(255) DEFAULT NULL", - "c_path3" => "VARCHAR(255) DEFAULT NULL", - "c_path4" => "VARCHAR(255) DEFAULT NULL", - "c_foldername" => "VARCHAR(255) NOT NULL", - "c_location" => "VARCHAR(2048) DEFAULT NULL", - "c_quick_location" => "VARCHAR(2048) DEFAULT NULL", - "c_acl_location" => "VARCHAR(2048) DEFAULT NULL", - "c_folder_type" => "VARCHAR(255) NOT NULL" - ), - "keys" => array( - "primary" => array( - "" => array("c_path") - ), - "unique" => array( - "c_folder_id" => array("c_folder_id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_quick_appointment" => array( - "cols" => array( - "c_folder_id" => "INT NOT NULL", - "c_name" => "VARCHAR(255) NOT NULL", - "c_uid" => "VARCHAR(255) NOT NULL", - "c_startdate" => "INT", - "c_enddate" => "INT", - "c_cycleenddate" => "INT", - "c_title" => "VARCHAR(1000) NOT NULL", - "c_participants" => "TEXT", - "c_isallday" => "INT", - "c_iscycle" => "INT", - "c_cycleinfo" => "TEXT", - "c_classification" => "INT NOT NULL", - "c_isopaque" => "INT NOT NULL", - "c_status" => "INT NOT NULL", - "c_priority" => "INT", - "c_location" => "VARCHAR(255)", - "c_orgmail" => "VARCHAR(255)", - "c_partmails" => "TEXT", - "c_partstates" => "TEXT", - "c_category" => "VARCHAR(255)", - "c_sequence" => "INT", - "c_component" => "VARCHAR(10) NOT NULL", - "c_nextalarm" => "INT", - "c_description" => "TEXT" - ), - "keys" => array( - "primary" => array( - "" => array("c_folder_id", "c_name") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_quick_contact" => array( - "cols" => array( - "c_folder_id" => "INT NOT NULL", - "c_name" => "VARCHAR(255) NOT NULL", - "c_givenname" => "VARCHAR(255)", - "c_cn" => "VARCHAR(255)", - "c_sn" => "VARCHAR(255)", - "c_screenname" => "VARCHAR(255)", - "c_l" => "VARCHAR(255)", - "c_mail" => "TEXT", - "c_o" => "VARCHAR(255)", - "c_ou" => "VARCHAR(255)", - "c_telephonenumber" => "VARCHAR(255)", - "c_categories" => "VARCHAR(255)", - "c_component" => "VARCHAR(10) NOT NULL", - "c_hascertificate" => "INT4 DEFAULT 0" - ), - "keys" => array( - "primary" => array( - "" => array("c_folder_id", "c_name") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_sessions_folder" => array( - "cols" => array( - "c_id" => "VARCHAR(255) NOT NULL", - "c_value" => "VARCHAR(255) NOT NULL", - "c_creationdate" => "INT(11) NOT NULL", - "c_lastseen" => "INT(11) NOT NULL" - ), - "keys" => array( - "primary" => array( - "" => array("c_id") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_store" => array( - "cols" => array( - "c_folder_id" => "INT NOT NULL", - "c_name" => "VARCHAR(255) NOT NULL", - "c_content" => "MEDIUMTEXT NOT NULL", - "c_creationdate" => "INT NOT NULL", - "c_lastmodified" => "INT NOT NULL", - "c_version" => "INT NOT NULL", - "c_deleted" => "INT" - ), - "keys" => array( - "primary" => array( - "" => array("c_folder_id", "c_name") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ), - "sogo_user_profile" => array( - "cols" => array( - "c_uid" => "VARCHAR(255) NOT NULL", - "c_defaults" => "LONGTEXT", - "c_settings" => "LONGTEXT" - ), - "keys" => array( - "primary" => array( - "" => array("c_uid") - ) - ), - "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" - ) - ); - - foreach ($tables as $table => $properties) { - // Migrate to quarantine - if ($table == 'quarantine') { - $stmt = $pdo->query("SHOW TABLES LIKE 'quarantaine'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $stmt = $pdo->query("SHOW TABLES LIKE 'quarantine'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results == 0) { - $pdo->query("RENAME TABLE `quarantaine` TO `quarantine`"); - } - } - } - // Migrate tls_enforce_* options - if ($table == 'mailbox') { - $stmt = $pdo->query("SHOW TABLES LIKE 'mailbox'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE '%tls_enforce%'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $stmt = $pdo->query("SELECT `username`, `tls_enforce_in`, `tls_enforce_out` FROM `mailbox`"); - $tls_options_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($tls_options_rows)) { - $tls_options[$row['username']] = array('tls_enforce_in' => $row['tls_enforce_in'], 'tls_enforce_out' => $row['tls_enforce_out']); - } - } - } - } - $stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $stmt = $pdo->prepare("SELECT CONCAT('ALTER TABLE ', `table_schema`, '.', `table_name`, ' DROP FOREIGN KEY ', `constraint_name`, ';') AS `FKEY_DROP` FROM `information_schema`.`table_constraints` - WHERE `constraint_type` = 'FOREIGN KEY' AND `table_name` = :table;"); - $stmt->execute(array(':table' => $table)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($rows)) { - $pdo->query($row['FKEY_DROP']); - } - foreach($properties['cols'] as $column => $type) { - $stmt = $pdo->query("SHOW COLUMNS FROM `" . $table . "` LIKE '" . $column . "'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results == 0) { - if (strpos($type, 'AUTO_INCREMENT') !== false) { - $type = $type . ' PRIMARY KEY '; - // Adding an AUTO_INCREMENT key, need to drop primary keys first, if exists - $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $pdo->query("ALTER TABLE `" . $table . "` DROP PRIMARY KEY"); - } - } - $pdo->query("ALTER TABLE `" . $table . "` ADD `" . $column . "` " . $type); - } - else { - $pdo->query("ALTER TABLE `" . $table . "` MODIFY COLUMN `" . $column . "` " . $type); - } - } - foreach($properties['keys'] as $key_type => $key_content) { - if (strtolower($key_type) == 'primary') { - foreach ($key_content as $key_values) { - $fields = "`" . implode("`, `", $key_values) . "`"; - $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - $is_drop = ($num_results != 0) ? "DROP PRIMARY KEY, " : ""; - $pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD PRIMARY KEY (" . $fields . ")"); - } - } - if (strtolower($key_type) == 'key') { - foreach ($key_content as $key_name => $key_values) { - $fields = "`" . implode("`, `", $key_values) . "`"; - $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - $is_drop = ($num_results != 0) ? "DROP INDEX `" . $key_name . "`, " : ""; - $pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD KEY `" . $key_name . "` (" . $fields . ")"); - } - } - if (strtolower($key_type) == 'unique') { - foreach ($key_content as $key_name => $key_values) { - $fields = "`" . implode("`, `", $key_values) . "`"; - $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - $is_drop = ($num_results != 0) ? "DROP INDEX `" . $key_name . "`, " : ""; - $pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD UNIQUE KEY `" . $key_name . "` (" . $fields . ")"); - } - } - if (strtolower($key_type) == 'fkey') { - foreach ($key_content as $key_name => $key_values) { - $fields = "`" . implode("`, `", $key_values) . "`"; - $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $pdo->query("ALTER TABLE `" . $table . "` DROP INDEX `" . $key_name . "`"); - } - @list($table_ref, $field_ref) = explode('.', $key_values['ref']); - $pdo->query("ALTER TABLE `" . $table . "` ADD FOREIGN KEY `" . $key_name . "` (" . $key_values['col'] . ") REFERENCES `" . $table_ref . "` (`" . $field_ref . "`) - ON DELETE " . $key_values['delete'] . " ON UPDATE " . $key_values['update']); - } - } - } - // Drop all vanished columns - $stmt = $pdo->query("SHOW COLUMNS FROM `" . $table . "`"); - $cols_in_table = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($cols_in_table)) { - if (!array_key_exists($row['Field'], $properties['cols'])) { - $pdo->query("ALTER TABLE `" . $table . "` DROP COLUMN `" . $row['Field'] . "`;"); - } - } - - // Step 1: Get all non-primary keys, that currently exist and those that should exist - $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE `Key_name` != 'PRIMARY'"); - $keys_in_table = $stmt->fetchAll(PDO::FETCH_ASSOC); - $keys_to_exist = array(); - if (isset($properties['keys']['unique']) && is_array($properties['keys']['unique'])) { - foreach ($properties['keys']['unique'] as $key_name => $key_values) { - $keys_to_exist[] = $key_name; - } - } - if (isset($properties['keys']['key']) && is_array($properties['keys']['key'])) { - foreach ($properties['keys']['key'] as $key_name => $key_values) { - $keys_to_exist[] = $key_name; - } - } - // Index for foreign key must exist - if (isset($properties['keys']['fkey']) && is_array($properties['keys']['fkey'])) { - foreach ($properties['keys']['fkey'] as $key_name => $key_values) { - $keys_to_exist[] = $key_name; - } - } - // Step 2: Drop all vanished indexes - while ($row = array_shift($keys_in_table)) { - if (!in_array($row['Key_name'], $keys_to_exist)) { - $pdo->query("ALTER TABLE `" . $table . "` DROP INDEX `" . $row['Key_name'] . "`"); - } - } - // Step 3: Drop all vanished primary keys - if (!isset($properties['keys']['primary'])) { - $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $pdo->query("ALTER TABLE `" . $table . "` DROP PRIMARY KEY"); - } - } - } - else { - // Create table if it is missing - $sql = "CREATE TABLE IF NOT EXISTS `" . $table . "` ("; - foreach($properties['cols'] as $column => $type) { - $sql .= "`" . $column . "` " . $type . ","; - } - foreach($properties['keys'] as $key_type => $key_content) { - if (strtolower($key_type) == 'primary') { - foreach ($key_content as $key_values) { - $fields = "`" . implode("`, `", $key_values) . "`"; - $sql .= "PRIMARY KEY (" . $fields . ")" . ","; - } - } - elseif (strtolower($key_type) == 'key') { - foreach ($key_content as $key_name => $key_values) { - $fields = "`" . implode("`, `", $key_values) . "`"; - $sql .= "KEY `" . $key_name . "` (" . $fields . ")" . ","; - } - } - elseif (strtolower($key_type) == 'unique') { - foreach ($key_content as $key_name => $key_values) { - $fields = "`" . implode("`, `", $key_values) . "`"; - $sql .= "UNIQUE KEY `" . $key_name . "` (" . $fields . ")" . ","; - } - } - elseif (strtolower($key_type) == 'fkey') { - foreach ($key_content as $key_name => $key_values) { - @list($table_ref, $field_ref) = explode('.', $key_values['ref']); - $fields = "`" . implode("`, `", $key_values) . "`"; - $sql .= "FOREIGN KEY `" . $key_name . "` (" . $key_values['col'] . ") REFERENCES `" . $table_ref . "` (`" . $field_ref . "`) - ON DELETE " . $key_values['delete'] . " ON UPDATE " . $key_values['update'] . ","; - } - } - } - $sql = rtrim($sql, ","); - $sql .= ") " . $properties['attr']; - $pdo->query($sql); - } - // Reset table attributes - $pdo->query("ALTER TABLE `" . $table . "` " . $properties['attr'] . ";"); - - } - - // Recreate SQL views - foreach ($views as $view => $create) { - $pdo->query("DROP VIEW IF EXISTS `" . $view . "`;"); - $pdo->query($create); - } - - // Create events to clean database - $events[] = 'DROP EVENT IF EXISTS clean_spamalias; -DELIMITER // -CREATE EVENT clean_spamalias -ON SCHEDULE EVERY 1 DAY DO -BEGIN - DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP(); -END; -// -DELIMITER ;'; - foreach ($events as $event) { - $pdo->exec($event); - } - - // Inject admin if not exists - $stmt = $pdo->query("SELECT NULL FROM `admin`"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results == 0) { - $stmt = $pdo->query("INSERT INTO `admin` (`username`, `password`, `superadmin`, `created`, `modified`, `active`) - VALUES ('admin', '{SSHA256}K8eVJ6YsZbQCfuJvSUbaQRLr0HPLz5rC9IAp0PAFl0tmNDBkMDc0NDAyOTAxN2Rk', 1, NOW(), NOW(), 1)"); - $stmt = $pdo->query("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) - SELECT `username`, 'ALL', NOW(), 1 FROM `admin` - WHERE superadmin='1' AND `username` NOT IN (SELECT `username` FROM `domain_admins`);"); - $stmt = $pdo->query("DELETE FROM `admin` WHERE `username` NOT IN (SELECT `username` FROM `domain_admins`);"); - } - // Insert new DB schema version - $stmt = $pdo->query("REPLACE INTO `versions` (`application`, `version`) VALUES ('db_schema', '" . $db_version . "');"); - - // Migrate tls_enforce_* options and add force_pw_update attribute - $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` IS NULL;"); - $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.force_pw_update', 0) WHERE JSON_EXTRACT(`attributes`, '$.force_pw_update') IS NULL;"); - foreach($tls_options as $tls_user => $tls_options) { - $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_enforce_in), - `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_enforce_out) - WHERE `username` = :username"); - $stmt->execute(array(':tls_enforce_in' => $tls_options['tls_enforce_in'], ':tls_enforce_out' => $tls_options['tls_enforce_out'], ':username' => $tls_user)); - } - if (php_sapi_name() == "cli") { - echo "DB initialization completed" . PHP_EOL; - } else { - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__), - 'msg' => 'db_init_complete' - ); - } - // Fix ACL - $stmt = $pdo->query("INSERT INTO `user_acl` (`username`) SELECT `username` FROM `mailbox` WHERE `kind` = '' AND NOT EXISTS (SELECT `username` FROM `user_acl`);"); - $stmt = $pdo->query("INSERT INTO `da_acl` (`username`) SELECT DISTINCT `username` FROM `domain_admins` WHERE `username` != 'admin' AND NOT EXISTS (SELECT `username` FROM `da_acl`);"); - // Fix domain_admins - $stmt = $pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';"); - } - catch (PDOException $e) { - if (php_sapi_name() == "cli") { - echo "DB initialization failed: " . print_r($e, true) . PHP_EOL; - } else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => array('mysql_error', $e) - ); - } - } -} -if (php_sapi_name() == "cli") { - include '/web/inc/vars.inc.php'; - $now = new DateTime(); - $mins = $now->getOffset() / 60; - $sgn = ($mins < 0 ? -1 : 1); - $mins = abs($mins); - $hrs = floor($mins / 60); - $mins -= $hrs * 60; - $offset = sprintf('%+d:%02d', $hrs*$sgn, $mins); - $dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; - $opt = [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => false, - PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;", - ]; - $pdo = new PDO($dsn, $database_user, $database_pass, $opt); - init_db_schema(); -} +query("SHOW TABLES LIKE 'versions'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $stmt = $pdo->query("SELECT `version` FROM `versions` WHERE `application` = 'db_schema'"); + if ($stmt->fetch(PDO::FETCH_ASSOC)['version'] == $db_version) { + return true; + } + if (!preg_match('/y|yes/i', getenv('MASTER'))) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__), + 'msg' => 'Database not initialized: not running db_init on slave.' + ); + return true; + } + } + + $views = array( + "grouped_mail_aliases" => "CREATE VIEW grouped_mail_aliases (username, aliases) AS + SELECT goto, IFNULL(GROUP_CONCAT(address ORDER BY address SEPARATOR ' '), '') AS address FROM alias + WHERE address!=goto + AND active = '1' + AND sogo_visible = '1' + AND address NOT LIKE '@%' + GROUP BY goto;", + // START + // Unused at the moment - we cannot allow to show a foreign mailbox as sender address in SOGo, as SOGo does not like this + // We need to create delegation in SOGo AND set a sender_acl in mailcow to allow to send as user X + "grouped_sender_acl" => "CREATE VIEW grouped_sender_acl (username, send_as_acl) AS + SELECT logged_in_as, IFNULL(GROUP_CONCAT(send_as SEPARATOR ' '), '') AS send_as_acl FROM sender_acl + WHERE send_as NOT LIKE '@%' + GROUP BY logged_in_as;", + // END + "grouped_sender_acl_external" => "CREATE VIEW grouped_sender_acl_external (username, send_as_acl) AS + SELECT logged_in_as, IFNULL(GROUP_CONCAT(send_as SEPARATOR ' '), '') AS send_as_acl FROM sender_acl + WHERE send_as NOT LIKE '@%' AND external = '1' + GROUP BY logged_in_as;", + "grouped_domain_alias_address" => "CREATE VIEW grouped_domain_alias_address (username, ad_alias) AS + SELECT username, IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ' '), '') AS ad_alias FROM mailbox + LEFT OUTER JOIN alias_domain ON target_domain=domain + GROUP BY username;", + "sieve_before" => "CREATE VIEW sieve_before (id, username, script_name, script_data) AS + SELECT md5(script_data), username, script_name, script_data FROM sieve_filters + WHERE filter_type = 'prefilter';", + "sieve_after" => "CREATE VIEW sieve_after (id, username, script_name, script_data) AS + SELECT md5(script_data), username, script_name, script_data FROM sieve_filters + WHERE filter_type = 'postfilter';" + ); + + $tables = array( + "versions" => array( + "cols" => array( + "application" => "VARCHAR(255) NOT NULL", + "version" => "VARCHAR(100) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + ), + "keys" => array( + "primary" => array( + "" => array("application") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "admin" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "password" => "VARCHAR(255) NOT NULL", + "superadmin" => "TINYINT(1) NOT NULL DEFAULT '0'", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE NOW(0)", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'", + "attributes" => "JSON" + ), + "keys" => array( + "primary" => array( + "" => array("username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "fido2" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "friendlyName" => "VARCHAR(255)", + "rpId" => "VARCHAR(255) NOT NULL", + "credentialPublicKey" => "TEXT NOT NULL", + "certificateChain" => "TEXT", + // Can be null for format "none" + "certificate" => "TEXT", + "certificateIssuer" => "VARCHAR(255)", + "certificateSubject" => "VARCHAR(255)", + "signatureCounter" => "INT", + "AAGUID" => "BLOB", + "credentialId" => "BLOB NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE NOW(0)", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "_sogo_static_view" => array( + "cols" => array( + "c_uid" => "VARCHAR(255) NOT NULL", + "domain" => "VARCHAR(255) NOT NULL", + "c_name" => "VARCHAR(255) NOT NULL", + "c_password" => "VARCHAR(255) NOT NULL DEFAULT ''", + "c_cn" => "VARCHAR(255)", + "c_l" => "VARCHAR(255)", + "c_o" => "VARCHAR(255)", + "c_ou" => "VARCHAR(255)", + "c_telephonenumber" => "VARCHAR(255)", + "mail" => "VARCHAR(255) NOT NULL", + // TODO -> use TEXT and check if SOGo login breaks on empty aliases + "aliases" => "TEXT NOT NULL", + "ad_aliases" => "VARCHAR(6144) NOT NULL DEFAULT ''", + "ext_acl" => "VARCHAR(6144) NOT NULL DEFAULT ''", + "kind" => "VARCHAR(100) NOT NULL DEFAULT ''", + "multiple_bookings" => "INT NOT NULL DEFAULT -1" + ), + "keys" => array( + "primary" => array( + "" => array("c_uid") + ), + "key" => array( + "domain" => array("domain") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "relayhosts" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "hostname" => "VARCHAR(255) NOT NULL", + "username" => "VARCHAR(255) NOT NULL", + "password" => "VARCHAR(255) NOT NULL", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "hostname" => array("hostname") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "transports" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "destination" => "VARCHAR(255) NOT NULL", + "nexthop" => "VARCHAR(255) NOT NULL", + "username" => "VARCHAR(255) NOT NULL DEFAULT ''", + "password" => "VARCHAR(255) NOT NULL DEFAULT ''", + "is_mx_based" => "TINYINT(1) NOT NULL DEFAULT '0'", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "destination" => array("destination"), + "nexthop" => array("nexthop"), + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "alias" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "address" => "VARCHAR(255) NOT NULL", + "goto" => "TEXT NOT NULL", + "domain" => "VARCHAR(255) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "private_comment" => "TEXT", + "public_comment" => "TEXT", + "sogo_visible" => "TINYINT(1) NOT NULL DEFAULT '1'", + "internal" => "TINYINT(1) NOT NULL DEFAULT '0'", + "sender_allowed" => "TINYINT(1) NOT NULL DEFAULT '1'", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "unique" => array( + "address" => array("address") + ), + "key" => array( + "domain" => array("domain") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "api" => array( + "cols" => array( + "api_key" => "VARCHAR(255) NOT NULL", + "allow_from" => "VARCHAR(512) NOT NULL", + "skip_ip_check" => "TINYINT(1) NOT NULL DEFAULT '0'", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE NOW(0)", + "access" => "ENUM('ro', 'rw') NOT NULL DEFAULT 'rw'", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("api_key") + ), + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sender_acl" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "logged_in_as" => "VARCHAR(255) NOT NULL", + "send_as" => "VARCHAR(255) NOT NULL", + "external" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "templates" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "template" => "VARCHAR(255) NOT NULL", + "type" => "VARCHAR(255) NOT NULL", + "attributes" => "JSON", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "domain" => array( + // Todo: Move some attributes to json + "cols" => array( + "domain" => "VARCHAR(255) NOT NULL", + "description" => "VARCHAR(255)", + "aliases" => "INT(10) NOT NULL DEFAULT '0'", + "mailboxes" => "INT(10) NOT NULL DEFAULT '0'", + "defquota" => "BIGINT(20) NOT NULL DEFAULT '3072'", + "maxquota" => "BIGINT(20) NOT NULL DEFAULT '102400'", + "quota" => "BIGINT(20) NOT NULL DEFAULT '102400'", + "relayhost" => "VARCHAR(255) NOT NULL DEFAULT '0'", + "backupmx" => "TINYINT(1) NOT NULL DEFAULT '0'", + "gal" => "TINYINT(1) NOT NULL DEFAULT '1'", + "relay_all_recipients" => "TINYINT(1) NOT NULL DEFAULT '0'", + "relay_unknown_only" => "TINYINT(1) NOT NULL DEFAULT '0'", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("domain") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "domain_wide_footer" => array( + "cols" => array( + "domain" => "VARCHAR(255) NOT NULL", + "html" => "LONGTEXT", + "plain" => "LONGTEXT", + "mbox_exclude" => "JSON NOT NULL DEFAULT ('[]')", + "alias_domain_exclude" => "JSON NOT NULL DEFAULT ('[]')", + "skip_replies" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("domain") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "tags_domain" => array( + "cols" => array( + "tag_name" => "VARCHAR(255) NOT NULL", + "domain" => "VARCHAR(255) NOT NULL" + ), + "keys" => array( + "fkey" => array( + "fk_tags_domain" => array( + "col" => "domain", + "ref" => "domain.domain", + "delete" => "CASCADE", + "update" => "NO ACTION" + ) + ), + "unique" => array( + "tag_name" => array("tag_name", "domain") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "tls_policy_override" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "dest" => "VARCHAR(255) NOT NULL", + "policy" => "ENUM('none', 'may', 'encrypt', 'dane', 'dane-only', 'fingerprint', 'verify', 'secure') NOT NULL", + "parameters" => "VARCHAR(255) DEFAULT ''", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "unique" => array( + "dest" => array("dest") + ), + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "quarantine" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "qid" => "VARCHAR(30) NOT NULL", + "subject" => "VARCHAR(500)", + "score" => "FLOAT(8,2)", + "ip" => "VARCHAR(50)", + "action" => "CHAR(20) NOT NULL DEFAULT 'unknown'", + "symbols" => "JSON", + "fuzzy_hashes" => "JSON", + "sender" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'", + "rcpt" => "VARCHAR(255)", + "msg" => "LONGTEXT", + "domain" => "VARCHAR(255)", + "notified" => "TINYINT(1) NOT NULL DEFAULT '0'", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "user" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'", + "qhash" => "VARCHAR(64)", + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "qhash" => array("qhash") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "mailbox" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "password" => "VARCHAR(255) NOT NULL", + "name" => "VARCHAR(255)", + "description" => "VARCHAR(255)", + // mailbox_path_prefix is followed by domain/local_part/ + "mailbox_path_prefix" => "VARCHAR(150) DEFAULT '/var/vmail/'", + "quota" => "BIGINT(20) NOT NULL DEFAULT '102400'", + "local_part" => "VARCHAR(255) NOT NULL", + "domain" => "VARCHAR(255) NOT NULL", + "attributes" => "JSON", + "custom_attributes" => "JSON NOT NULL DEFAULT ('{}')", + "kind" => "VARCHAR(100) NOT NULL DEFAULT ''", + "multiple_bookings" => "INT NOT NULL DEFAULT -1", + "authsource" => "ENUM('mailcow', 'keycloak', 'generic-oidc', 'ldap') DEFAULT 'mailcow'", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("username") + ), + "key" => array( + "domain" => array("domain"), + "kind" => array("kind") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "tags_mailbox" => array( + "cols" => array( + "tag_name" => "VARCHAR(255) NOT NULL", + "username" => "VARCHAR(255) NOT NULL" + ), + "keys" => array( + "fkey" => array( + "fk_tags_mailbox" => array( + "col" => "username", + "ref" => "mailbox.username", + "delete" => "CASCADE", + "update" => "NO ACTION" + ) + ), + "unique" => array( + "tag_name" => array("tag_name", "username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sieve_filters" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "username" => "VARCHAR(255) NOT NULL", + "script_desc" => "VARCHAR(255) NOT NULL", + "script_name" => "ENUM('active','inactive')", + "script_data" => "TEXT NOT NULL", + "filter_type" => "ENUM('postfilter','prefilter')", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "username" => array("username"), + "script_desc" => array("script_desc") + ), + "fkey" => array( + "fk_username_sieve_global_before" => array( + "col" => "username", + "ref" => "mailbox.username", + "delete" => "CASCADE", + "update" => "NO ACTION" + ) + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "app_passwd" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "name" => "VARCHAR(255) NOT NULL", + "mailbox" => "VARCHAR(255) NOT NULL", + "domain" => "VARCHAR(255) NOT NULL", + "password" => "VARCHAR(255) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "imap_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "smtp_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "dav_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "eas_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "pop3_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "sieve_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "mailbox" => array("mailbox"), + "password" => array("password"), + "domain" => array("domain"), + ), + "fkey" => array( + "fk_username_app_passwd" => array( + "col" => "mailbox", + "ref" => "mailbox.username", + "delete" => "CASCADE", + "update" => "NO ACTION" + ) + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "mta_sts" => array( + "cols" => array( + "id" => "BIGINT NOT NULL", + "domain" => "VARCHAR(255) NOT NULL", + "version" => "VARCHAR(255) NOT NULL", + "mode" => "VARCHAR(255) NOT NULL", + "mx" => "VARCHAR(255) NOT NULL", + "max_age" => "VARCHAR(255) NOT NULL", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("domain") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "user_acl" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "spam_alias" => "TINYINT(1) NOT NULL DEFAULT '1'", + "tls_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", + "spam_score" => "TINYINT(1) NOT NULL DEFAULT '1'", + "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", + "delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'", + "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '0'", + "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", + "sogo_profile_reset" => "TINYINT(1) NOT NULL DEFAULT '0'", + "pushover" => "TINYINT(1) NOT NULL DEFAULT '1'", + // quarantine is for quarantine actions, todo: rename + "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", + "quarantine_attachments" => "TINYINT(1) NOT NULL DEFAULT '1'", + "quarantine_notification" => "TINYINT(1) NOT NULL DEFAULT '1'", + "quarantine_category" => "TINYINT(1) NOT NULL DEFAULT '1'", + "app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'", + "pw_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", + ), + "keys" => array( + "primary" => array( + "" => array("username") + ), + "fkey" => array( + "fk_username" => array( + "col" => "username", + "ref" => "mailbox.username", + "delete" => "CASCADE", + "update" => "NO ACTION" + ) + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "alias_domain" => array( + "cols" => array( + "alias_domain" => "VARCHAR(255) NOT NULL", + "target_domain" => "VARCHAR(255) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("alias_domain") + ), + "key" => array( + "active" => array("active"), + "target_domain" => array("target_domain") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "spamalias" => array( + "cols" => array( + "address" => "VARCHAR(255) NOT NULL", + "goto" => "TEXT NOT NULL", + "description" => "TEXT NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "validity" => "INT(11)", + "permanent" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("address") + ), + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "filterconf" => array( + "cols" => array( + "object" => "VARCHAR(255) NOT NULL DEFAULT ''", + "option" => "VARCHAR(50) NOT NULL DEFAULT ''", + "value" => "VARCHAR(100) NOT NULL DEFAULT ''", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "prefid" => "INT(11) NOT NULL AUTO_INCREMENT" + ), + "keys" => array( + "primary" => array( + "" => array("prefid") + ), + "key" => array( + "object" => array("object") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "settingsmap" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "desc" => "VARCHAR(255) NOT NULL", + "content" => "LONGTEXT NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "identity_provider" => array( + "cols" => array( + "key" => "VARCHAR(255) NOT NULL", + "value" => "TEXT NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP" + ), + "keys" => array( + "primary" => array( + "" => array("key") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "logs" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "task" => "CHAR(32) NOT NULL DEFAULT '000000'", + "type" => "VARCHAR(32) DEFAULT ''", + "msg" => "TEXT", + "call" => "TEXT", + "user" => "VARCHAR(64) NOT NULL", + "role" => "VARCHAR(32) NOT NULL", + "remote" => "VARCHAR(39) NOT NULL", + "time" => "INT(11) NOT NULL" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sasl_log" => array( + "cols" => array( + "service" => "VARCHAR(32) NOT NULL DEFAULT ''", + "app_password" => "INT", + "username" => "VARCHAR(255) NOT NULL", + "real_rip" => "VARCHAR(64) NOT NULL", + "datetime" => "DATETIME(0) NOT NULL DEFAULT NOW(0)" + ), + "keys" => array( + "primary" => array( + "" => array("service", "real_rip", "username") + ), + "key" => array( + "username" => array("username"), + "service" => array("service"), + "datetime" => array("datetime"), + "real_rip" => array("real_rip") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "quota2" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "bytes" => "BIGINT(20) NOT NULL DEFAULT '0'", + "messages" => "BIGINT(20) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "quota2replica" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "bytes" => "BIGINT(20) NOT NULL DEFAULT '0'", + "messages" => "BIGINT(20) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "domain_admins" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "username" => "VARCHAR(255) NOT NULL", + "domain" => "VARCHAR(255) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "username" => array("username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "da_acl" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'", + "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", + "login_as" => "TINYINT(1) NOT NULL DEFAULT '1'", + "sogo_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'", + "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'", + "pushover" => "TINYINT(1) NOT NULL DEFAULT '0'", + "filters" => "TINYINT(1) NOT NULL DEFAULT '1'", + "ratelimit" => "TINYINT(1) NOT NULL DEFAULT '1'", + "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", + "extend_sender_acl" => "TINYINT(1) NOT NULL DEFAULT '0'", + "unlimited_quota" => "TINYINT(1) NOT NULL DEFAULT '0'", + "protocol_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "smtp_ip_access" => "TINYINT(1) NOT NULL DEFAULT '1'", + "alias_domains" => "TINYINT(1) NOT NULL DEFAULT '0'", + "mailbox_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'", + "domain_relayhost" => "TINYINT(1) NOT NULL DEFAULT '1'", + "domain_desc" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "da_sso" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "token" => "VARCHAR(255) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + ), + "keys" => array( + "primary" => array( + "" => array("token", "created") + ), + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "reset_password" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "token" => "VARCHAR(255) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + ), + "keys" => array( + "primary" => array( + "" => array("token", "created") + ), + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "imapsync" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "user2" => "VARCHAR(255) NOT NULL", + "host1" => "VARCHAR(255) NOT NULL", + "authmech1" => "ENUM('PLAIN','LOGIN','CRAM-MD5') DEFAULT 'PLAIN'", + "regextrans2" => "VARCHAR(255) DEFAULT ''", + "authmd51" => "TINYINT(1) NOT NULL DEFAULT 0", + "domain2" => "VARCHAR(255) NOT NULL DEFAULT ''", + "subfolder2" => "VARCHAR(255) NOT NULL DEFAULT ''", + "user1" => "VARCHAR(255) NOT NULL", + "password1" => "VARCHAR(255) NOT NULL", + "exclude" => "VARCHAR(500) NOT NULL DEFAULT ''", + "maxage" => "SMALLINT NOT NULL DEFAULT '0'", + "mins_interval" => "SMALLINT UNSIGNED NOT NULL DEFAULT '0'", + "maxbytespersecond" => "VARCHAR(50) NOT NULL DEFAULT '0'", + "port1" => "SMALLINT UNSIGNED NOT NULL", + "enc1" => "ENUM('TLS','SSL','PLAIN') DEFAULT 'TLS'", + "delete2duplicates" => "TINYINT(1) NOT NULL DEFAULT '1'", + "delete1" => "TINYINT(1) NOT NULL DEFAULT '0'", + "delete2" => "TINYINT(1) NOT NULL DEFAULT '0'", + "automap" => "TINYINT(1) NOT NULL DEFAULT '0'", + "skipcrossduplicates" => "TINYINT(1) NOT NULL DEFAULT '0'", + "custom_params" => "VARCHAR(512) NOT NULL DEFAULT ''", + "timeout1" => "SMALLINT NOT NULL DEFAULT '600'", + "timeout2" => "SMALLINT NOT NULL DEFAULT '600'", + "subscribeall" => "TINYINT(1) NOT NULL DEFAULT '1'", + "dry" => "TINYINT(1) NOT NULL DEFAULT '0'", + "is_running" => "TINYINT(1) NOT NULL DEFAULT '0'", + "returned_text" => "LONGTEXT", + "last_run" => "TIMESTAMP NULL DEFAULT NULL", + "success" => "TINYINT(1) UNSIGNED DEFAULT NULL", + "exit_status" => "VARCHAR(50) DEFAULT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "bcc_maps" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "local_dest" => "VARCHAR(255) NOT NULL", + "bcc_dest" => "VARCHAR(255) NOT NULL", + "domain" => "VARCHAR(255) NOT NULL", + "type" => "ENUM('sender','rcpt')", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "local_dest" => array("local_dest"), + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "recipient_maps" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "old_dest" => "VARCHAR(255) NOT NULL", + "new_dest" => "VARCHAR(255) NOT NULL", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "local_dest" => array("old_dest"), + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "tfa" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "key_id" => "VARCHAR(255) NOT NULL", + "username" => "VARCHAR(255) NOT NULL", + "authmech" => "ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')", + "secret" => "VARCHAR(255) DEFAULT NULL", + "keyHandle" => "VARCHAR(1023) DEFAULT NULL", + "publicKey" => "VARCHAR(4096) DEFAULT NULL", + "counter" => "INT NOT NULL DEFAULT '0'", + "certificate" => "TEXT", + "active" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "forwarding_hosts" => array( + "cols" => array( + "host" => "VARCHAR(255) NOT NULL", + "source" => "VARCHAR(255) NOT NULL", + "filter_spam" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("host") + ), + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_acl" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "c_folder_id" => "INT NOT NULL", + "c_object" => "VARCHAR(255) NOT NULL", + "c_uid" => "VARCHAR(255) NOT NULL", + "c_role" => "VARCHAR(80) NOT NULL" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "sogo_acl_c_folder_id_idx" => array("c_folder_id"), + "sogo_acl_c_uid_idx" => array("c_uid") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_alarms_folder" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "c_path" => "VARCHAR(255) NOT NULL", + "c_name" => "VARCHAR(255) NOT NULL", + "c_uid" => "VARCHAR(255) NOT NULL", + "c_recurrence_id" => "INT(11) DEFAULT NULL", + "c_alarm_number" => "INT(11) NOT NULL", + "c_alarm_date" => "INT(11) NOT NULL" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_cache_folder" => array( + "cols" => array( + "c_uid" => "VARCHAR(255) NOT NULL", + "c_path" => "VARCHAR(255) NOT NULL", + "c_parent_path" => "VARCHAR(255) DEFAULT NULL", + "c_type" => "TINYINT(3) unsigned NOT NULL", + "c_creationdate" => "INT(11) NOT NULL", + "c_lastmodified" => "INT(11) NOT NULL", + "c_version" => "INT(11) NOT NULL DEFAULT '0'", + "c_deleted" => "TINYINT(4) NOT NULL DEFAULT '0'", + "c_content" => "LONGTEXT" + ), + "keys" => array( + "primary" => array( + "" => array("c_uid", "c_path") + ), + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_folder_info" => array( + "cols" => array( + "c_folder_id" => "BIGINT(20) unsigned NOT NULL AUTO_INCREMENT", + "c_path" => "VARCHAR(255) NOT NULL", + "c_path1" => "VARCHAR(255) NOT NULL", + "c_path2" => "VARCHAR(255) DEFAULT NULL", + "c_path3" => "VARCHAR(255) DEFAULT NULL", + "c_path4" => "VARCHAR(255) DEFAULT NULL", + "c_foldername" => "VARCHAR(255) NOT NULL", + "c_location" => "VARCHAR(2048) DEFAULT NULL", + "c_quick_location" => "VARCHAR(2048) DEFAULT NULL", + "c_acl_location" => "VARCHAR(2048) DEFAULT NULL", + "c_folder_type" => "VARCHAR(255) NOT NULL" + ), + "keys" => array( + "primary" => array( + "" => array("c_path") + ), + "unique" => array( + "c_folder_id" => array("c_folder_id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_quick_appointment" => array( + "cols" => array( + "c_folder_id" => "INT NOT NULL", + "c_name" => "VARCHAR(255) NOT NULL", + "c_uid" => "VARCHAR(1000) NOT NULL", + "c_startdate" => "INT", + "c_enddate" => "INT", + "c_cycleenddate" => "INT", + "c_title" => "VARCHAR(1000) NOT NULL", + "c_participants" => "TEXT", + "c_isallday" => "INT", + "c_iscycle" => "INT", + "c_cycleinfo" => "TEXT", + "c_classification" => "INT NOT NULL", + "c_isopaque" => "INT NOT NULL", + "c_status" => "INT NOT NULL", + "c_priority" => "INT", + "c_location" => "VARCHAR(255)", + "c_orgmail" => "VARCHAR(255)", + "c_partmails" => "TEXT", + "c_partstates" => "TEXT", + "c_category" => "VARCHAR(255)", + "c_sequence" => "INT", + "c_component" => "VARCHAR(10) NOT NULL", + "c_nextalarm" => "INT", + "c_description" => "TEXT" + ), + "keys" => array( + "primary" => array( + "" => array("c_folder_id", "c_name") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_quick_contact" => array( + "cols" => array( + "c_folder_id" => "INT NOT NULL", + "c_name" => "VARCHAR(255) NOT NULL", + "c_givenname" => "VARCHAR(255)", + "c_cn" => "VARCHAR(255)", + "c_sn" => "VARCHAR(255)", + "c_screenname" => "VARCHAR(255)", + "c_l" => "VARCHAR(255)", + "c_mail" => "TEXT", + "c_o" => "VARCHAR(500)", + "c_ou" => "VARCHAR(255)", + "c_telephonenumber" => "VARCHAR(255)", + "c_categories" => "VARCHAR(255)", + "c_component" => "VARCHAR(10) NOT NULL", + "c_hascertificate" => "INT4 DEFAULT 0" + ), + "keys" => array( + "primary" => array( + "" => array("c_folder_id", "c_name") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_sessions_folder" => array( + "cols" => array( + "c_id" => "VARCHAR(255) NOT NULL", + "c_value" => "VARCHAR(4096) NOT NULL", + "c_creationdate" => "INT(11) NOT NULL", + "c_lastseen" => "INT(11) NOT NULL" + ), + "keys" => array( + "primary" => array( + "" => array("c_id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_store" => array( + "cols" => array( + "c_folder_id" => "INT NOT NULL", + "c_name" => "VARCHAR(255) NOT NULL", + "c_content" => "MEDIUMTEXT NOT NULL", + "c_creationdate" => "INT NOT NULL", + "c_lastmodified" => "INT NOT NULL", + "c_version" => "INT NOT NULL", + "c_deleted" => "INT" + ), + "keys" => array( + "primary" => array( + "" => array("c_folder_id", "c_name") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_admin" => array( + "cols" => array( + "c_key" => "VARCHAR(255) NOT NULL DEFAULT ''", + "c_content" => "mediumtext NOT NULL", + ), + "keys" => array( + "primary" => array( + "" => array("c_key") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "pushover" => array( + "cols" => array( + "username" => "VARCHAR(255) NOT NULL", + "key" => "VARCHAR(255) NOT NULL", + "token" => "VARCHAR(255) NOT NULL", + "attributes" => "JSON", + "title" => "TEXT", + "text" => "TEXT", + "senders" => "TEXT", + "senders_regex" => "TEXT", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("username") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "sogo_user_profile" => array( + "cols" => array( + "c_uid" => "VARCHAR(255) NOT NULL", + "c_defaults" => "LONGTEXT", + "c_settings" => "LONGTEXT" + ), + "keys" => array( + "primary" => array( + "" => array("c_uid") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "oauth_clients" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "client_id" => "VARCHAR(80) NOT NULL", + "client_secret" => "VARCHAR(80)", + "redirect_uri" => "VARCHAR(2000)", + "grant_types" => "VARCHAR(80)", + "scope" => "VARCHAR(4000)", + "user_id" => "VARCHAR(80)" + ), + "keys" => array( + "primary" => array( + "" => array("client_id") + ), + "unique" => array( + "id" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "oauth_access_tokens" => array( + "cols" => array( + "access_token" => "VARCHAR(40) NOT NULL", + "client_id" => "VARCHAR(80) NOT NULL", + "user_id" => "VARCHAR(80)", + "expires" => "TIMESTAMP NOT NULL", + "scope" => "VARCHAR(4000)" + ), + "keys" => array( + "primary" => array( + "" => array("access_token") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "oauth_authorization_codes" => array( + "cols" => array( + "authorization_code" => "VARCHAR(40) NOT NULL", + "client_id" => "VARCHAR(80) NOT NULL", + "user_id" => "VARCHAR(80)", + "redirect_uri" => "VARCHAR(2000)", + "expires" => "TIMESTAMP NOT NULL", + "scope" => "VARCHAR(4000)", + "id_token" => "VARCHAR(1000)" + ), + "keys" => array( + "primary" => array( + "" => array("authorization_code") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "oauth_refresh_tokens" => array( + "cols" => array( + "refresh_token" => "VARCHAR(40) NOT NULL", + "client_id" => "VARCHAR(80) NOT NULL", + "user_id" => "VARCHAR(80)", + "expires" => "TIMESTAMP NOT NULL", + "scope" => "VARCHAR(4000)" + ), + "keys" => array( + "primary" => array( + "" => array("refresh_token") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ) + ); + + foreach ($tables as $table => $properties) { + // Migrate to quarantine + if ($table == 'quarantine') { + $stmt = $pdo->query("SHOW TABLES LIKE 'quarantaine'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $stmt = $pdo->query("SHOW TABLES LIKE 'quarantine'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $pdo->query("RENAME TABLE `quarantaine` TO `quarantine`"); + } + } + } + + // Migrate tls_enforce_* options + if ($table == 'mailbox') { + $stmt = $pdo->query("SHOW TABLES LIKE 'mailbox'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE '%tls_enforce%'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $stmt = $pdo->query("SELECT `username`, `tls_enforce_in`, `tls_enforce_out` FROM `mailbox`"); + $tls_options_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($tls_options_rows)) { + $tls_options[$row['username']] = array('tls_enforce_in' => $row['tls_enforce_in'], 'tls_enforce_out' => $row['tls_enforce_out']); + } + } + } + } + + $stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $stmt = $pdo->prepare("SELECT CONCAT('ALTER TABLE `', `table_schema`, '`.', `table_name`, ' DROP FOREIGN KEY ', `constraint_name`, ';') AS `FKEY_DROP` FROM `information_schema`.`table_constraints` + WHERE `constraint_type` = 'FOREIGN KEY' AND `table_name` = :table;"); + $stmt->execute(array(':table' => $table)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows)) { + $pdo->query($row['FKEY_DROP']); + } + foreach ($properties['cols'] as $column => $type) { + $stmt = $pdo->query("SHOW COLUMNS FROM `" . $table . "` LIKE '" . $column . "'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + if (strpos($type, 'AUTO_INCREMENT') !== false) { + $type = $type . ' PRIMARY KEY '; + // Adding an AUTO_INCREMENT key, need to drop primary keys first, if exists + $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $pdo->query("ALTER TABLE `" . $table . "` DROP PRIMARY KEY"); + } + } + $pdo->query("ALTER TABLE `" . $table . "` ADD `" . $column . "` " . $type); + } else { + $pdo->query("ALTER TABLE `" . $table . "` MODIFY COLUMN `" . $column . "` " . $type); + } + } + foreach ($properties['keys'] as $key_type => $key_content) { + if (strtolower($key_type) == 'primary') { + foreach ($key_content as $key_values) { + $fields = "`" . implode("`, `", $key_values) . "`"; + $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + $is_drop = ($num_results != 0) ? "DROP PRIMARY KEY, " : ""; + $pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD PRIMARY KEY (" . $fields . ")"); + } + } + if (strtolower($key_type) == 'key') { + foreach ($key_content as $key_name => $key_values) { + $fields = "`" . implode("`, `", $key_values) . "`"; + $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + $is_drop = ($num_results != 0) ? "DROP INDEX `" . $key_name . "`, " : ""; + $pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD KEY `" . $key_name . "` (" . $fields . ")"); + } + } + if (strtolower($key_type) == 'unique') { + foreach ($key_content as $key_name => $key_values) { + $fields = "`" . implode("`, `", $key_values) . "`"; + $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + $is_drop = ($num_results != 0) ? "DROP INDEX `" . $key_name . "`, " : ""; + $pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD UNIQUE KEY `" . $key_name . "` (" . $fields . ")"); + } + } + if (strtolower($key_type) == 'fkey') { + foreach ($key_content as $key_name => $key_values) { + $fields = "`" . implode("`, `", $key_values) . "`"; + $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $pdo->query("ALTER TABLE `" . $table . "` DROP INDEX `" . $key_name . "`"); + } + @list($table_ref, $field_ref) = explode('.', $key_values['ref']); + $pdo->query("ALTER TABLE `" . $table . "` ADD FOREIGN KEY `" . $key_name . "` (" . $key_values['col'] . ") REFERENCES `" . $table_ref . "` (`" . $field_ref . "`) + ON DELETE " . $key_values['delete'] . " ON UPDATE " . $key_values['update']); + } + } + } + // Drop all vanished columns + $stmt = $pdo->query("SHOW COLUMNS FROM `" . $table . "`"); + $cols_in_table = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($cols_in_table)) { + if (!array_key_exists($row['Field'], $properties['cols'])) { + $pdo->query("ALTER TABLE `" . $table . "` DROP COLUMN `" . $row['Field'] . "`;"); + } + } + + // Step 1: Get all non-primary keys, that currently exist and those that should exist + $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE `Key_name` != 'PRIMARY'"); + $keys_in_table = $stmt->fetchAll(PDO::FETCH_ASSOC); + $keys_to_exist = array(); + if (isset($properties['keys']['unique']) && is_array($properties['keys']['unique'])) { + foreach ($properties['keys']['unique'] as $key_name => $key_values) { + $keys_to_exist[] = $key_name; + } + } + if (isset($properties['keys']['key']) && is_array($properties['keys']['key'])) { + foreach ($properties['keys']['key'] as $key_name => $key_values) { + $keys_to_exist[] = $key_name; + } + } + // Index for foreign key must exist + if (isset($properties['keys']['fkey']) && is_array($properties['keys']['fkey'])) { + foreach ($properties['keys']['fkey'] as $key_name => $key_values) { + $keys_to_exist[] = $key_name; + } + } + // Step 2: Drop all vanished indexes + while ($row = array_shift($keys_in_table)) { + if (!in_array($row['Key_name'], $keys_to_exist)) { + $pdo->query("ALTER TABLE `" . $table . "` DROP INDEX `" . $row['Key_name'] . "`"); + } + } + // Step 3: Drop all vanished primary keys + if (!isset($properties['keys']['primary'])) { + $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $pdo->query("ALTER TABLE `" . $table . "` DROP PRIMARY KEY"); + } + } + } else { + // Create table if it is missing + $sql = "CREATE TABLE IF NOT EXISTS `" . $table . "` ("; + foreach ($properties['cols'] as $column => $type) { + $sql .= "`" . $column . "` " . $type . ","; + } + foreach ($properties['keys'] as $key_type => $key_content) { + if (strtolower($key_type) == 'primary') { + foreach ($key_content as $key_values) { + $fields = "`" . implode("`, `", $key_values) . "`"; + $sql .= "PRIMARY KEY (" . $fields . ")" . ","; + } + } elseif (strtolower($key_type) == 'key') { + foreach ($key_content as $key_name => $key_values) { + $fields = "`" . implode("`, `", $key_values) . "`"; + $sql .= "KEY `" . $key_name . "` (" . $fields . ")" . ","; + } + } elseif (strtolower($key_type) == 'unique') { + foreach ($key_content as $key_name => $key_values) { + $fields = "`" . implode("`, `", $key_values) . "`"; + $sql .= "UNIQUE KEY `" . $key_name . "` (" . $fields . ")" . ","; + } + } elseif (strtolower($key_type) == 'fkey') { + foreach ($key_content as $key_name => $key_values) { + @list($table_ref, $field_ref) = explode('.', $key_values['ref']); + $sql .= "FOREIGN KEY `" . $key_name . "` (" . $key_values['col'] . ") REFERENCES `" . $table_ref . "` (`" . $field_ref . "`) + ON DELETE " . $key_values['delete'] . " ON UPDATE " . $key_values['update'] . ","; + } + } + } + $sql = rtrim($sql, ","); + $sql .= ") " . $properties['attr']; + $pdo->query($sql); + } + // Reset table attributes + $pdo->query("ALTER TABLE `" . $table . "` " . $properties['attr'] . ";"); + } + + // Recreate SQL views + foreach ($views as $view => $create) { + $pdo->query("DROP VIEW IF EXISTS `" . $view . "`;"); + $pdo->query($create); + } + + // Clear old app_passwd log entries + $pdo->exec("DELETE FROM logs + WHERE role != 'unauthenticated' + AND JSON_EXTRACT(`call`, '$[0]') = 'app_passwd' + AND JSON_EXTRACT(`call`, '$[1]') = 'edit' + AND (JSON_CONTAINS_PATH(`call`, 'one', '$[2].password') + OR JSON_CONTAINS_PATH(`call`, 'one', '$[2].password2'));"); + + // Mitigate imapsync argument injection issue + $pdo->query("UPDATE `imapsync` SET `custom_params` = '' + WHERE `custom_params` LIKE '%pipemess%' + OR custom_params LIKE '%skipmess%' + OR custom_params LIKE '%delete2foldersonly%' + OR custom_params LIKE '%delete2foldersbutnot%' + OR custom_params LIKE '%regexflag%' + OR custom_params LIKE '%pipemess%' + OR custom_params LIKE '%regextrans2%' + OR custom_params LIKE '%maxlinelengthcmd%';"); + + // Migrate webauthn tfa + $stmt = $pdo->query("ALTER TABLE `tfa` MODIFY COLUMN `authmech` ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')"); + + // Inject admin if not exists + $stmt = $pdo->query("SELECT NULL FROM `admin`"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $pdo->query("INSERT INTO `admin` (`username`, `password`, `superadmin`, `created`, `modified`, `active`) + VALUES ('admin', '{SSHA256}K8eVJ6YsZbQCfuJvSUbaQRLr0HPLz5rC9IAp0PAFl0tmNDBkMDc0NDAyOTAxN2Rk', 1, NOW(), NOW(), 1)"); + $pdo->query("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) + SELECT `username`, 'ALL', NOW(), 1 FROM `admin` + WHERE superadmin='1' AND `username` NOT IN (SELECT `username` FROM `domain_admins`);"); + $pdo->query("DELETE FROM `admin` WHERE `username` NOT IN (SELECT `username` FROM `domain_admins`);"); + } + // Insert new DB schema version + $pdo->query("REPLACE INTO `versions` (`application`, `version`) VALUES ('db_schema', '" . $db_version . "');"); + + // Fix dangling domain admins + $pdo->query("DELETE FROM `admin` WHERE `superadmin` = 0 AND `username` NOT IN (SELECT `username`FROM `domain_admins`);"); + $pdo->query("DELETE FROM `da_acl` WHERE `username` NOT IN (SELECT `username`FROM `domain_admins`);"); + + // Migrate attributes + // pushover + $pdo->query("UPDATE `pushover` SET `attributes` = '{}' WHERE `attributes` = '' OR `attributes` IS NULL;"); + $pdo->query("UPDATE `pushover` SET `attributes` = JSON_SET(`attributes`, '$.evaluate_x_prio', \"0\") WHERE JSON_VALUE(`attributes`, '$.evaluate_x_prio') IS NULL;"); + $pdo->query("UPDATE `pushover` SET `attributes` = JSON_SET(`attributes`, '$.only_x_prio', \"0\") WHERE JSON_VALUE(`attributes`, '$.only_x_prio') IS NULL;"); + $pdo->query("UPDATE `pushover` SET `attributes` = JSON_SET(`attributes`, '$.sound', \"pushover\") WHERE JSON_VALUE(`attributes`, '$.sound') IS NULL;"); + // mailbox + $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` = '' OR `attributes` IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.passwd_update', \"0\") WHERE JSON_VALUE(`attributes`, '$.passwd_update') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.relayhost', \"0\") WHERE JSON_VALUE(`attributes`, '$.relayhost') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.force_pw_update', \"0\") WHERE JSON_VALUE(`attributes`, '$.force_pw_update') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.force_tfa', \"0\") WHERE JSON_VALUE(`attributes`, '$.force_tfa') IS NULL;"); + // admin attributes + $pdo->query("UPDATE `admin` SET `attributes` = '{}' WHERE `attributes` = '' OR `attributes` IS NULL;"); + $pdo->query("UPDATE `admin` SET `attributes` = JSON_SET(`attributes`, '$.force_tfa', \"0\") WHERE JSON_VALUE(`attributes`, '$.force_tfa') IS NULL;"); + $pdo->query("UPDATE `admin` SET `attributes` = JSON_SET(`attributes`, '$.force_pw_update', \"0\") WHERE JSON_VALUE(`attributes`, '$.force_pw_update') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sieve_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.sieve_access') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sogo_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.sogo_access') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.imap_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.imap_access') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.pop3_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.pop3_access') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.smtp_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.smtp_access') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.eas_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.eas_access') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.dav_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.dav_access') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.mailbox_format', \"maildir:\") WHERE JSON_VALUE(`attributes`, '$.mailbox_format') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', \"never\") WHERE JSON_VALUE(`attributes`, '$.quarantine_notification') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_category', \"reject\") WHERE JSON_VALUE(`attributes`, '$.quarantine_category') IS NULL;"); + foreach ($tls_options as $tls_user => $tls_options) { + $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_enforce_in), + `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_enforce_out) + WHERE `username` = :username"); + $stmt->execute(array(':tls_enforce_in' => $tls_options['tls_enforce_in'], ':tls_enforce_out' => $tls_options['tls_enforce_out'], ':username' => $tls_user)); + } + // Set tls_enforce_* if still missing (due to deleted attrs for example) + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', \"1\") WHERE JSON_VALUE(`attributes`, '$.tls_enforce_out') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', \"1\") WHERE JSON_VALUE(`attributes`, '$.tls_enforce_in') IS NULL;"); + // Fix ACL + $pdo->query("INSERT INTO `user_acl` (`username`) SELECT `username` FROM `mailbox` WHERE `kind` = '' AND NOT EXISTS (SELECT `username` FROM `user_acl`);"); + $pdo->query("INSERT INTO `da_acl` (`username`) SELECT DISTINCT `username` FROM `domain_admins` WHERE `username` != 'admin' AND NOT EXISTS (SELECT `username` FROM `da_acl`);"); + // Fix domain_admins + $pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';"); + + // add default templates + $default_domain_template = array( + "template" => "Default", + "type" => "domain", + "attributes" => array( + "tags" => array(), + "max_num_aliases_for_domain" => 400, + "max_num_mboxes_for_domain" => 10, + "def_quota_for_mbox" => 3072 * 1048576, + "max_quota_for_mbox" => 10240 * 1048576, + "max_quota_for_domain" => 10240 * 1048576, + "rl_frame" => "s", + "rl_value" => "", + "active" => 1, + "gal" => 1, + "backupmx" => 0, + "relay_all_recipients" => 0, + "relay_unknown_only" => 0, + "dkim_selector" => "dkim", + "key_size" => 2048, + "max_quota_for_domain" => 10240 * 1048576, + ) + ); + $default_mailbox_template = array( + "template" => "Default", + "type" => "mailbox", + "attributes" => array( + "tags" => array(), + "quota" => 0, + "quarantine_notification" => strval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['quarantine_notification']), + "quarantine_category" => strval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['quarantine_category']), + "rl_frame" => "s", + "rl_value" => "", + "force_pw_update" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['force_pw_update']), + "force_tfa" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['force_tfa']), + "sogo_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sogo_access']), + "active" => 1, + "tls_enforce_in" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_in']), + "tls_enforce_out" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_out']), + "imap_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['imap_access']), + "pop3_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['pop3_access']), + "smtp_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['smtp_access']), + "sieve_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sieve_access']), + "eas_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['eas_access']), + "dav_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['dav_access']), + "acl_spam_alias" => 1, + "acl_tls_policy" => 1, + "acl_spam_score" => 1, + "acl_spam_policy" => 1, + "acl_delimiter_action" => 1, + "acl_syncjobs" => 0, + "acl_eas_reset" => 1, + "acl_sogo_profile_reset" => 0, + "acl_pushover" => 1, + "acl_quarantine" => 1, + "acl_quarantine_attachments" => 1, + "acl_quarantine_notification" => 1, + "acl_quarantine_category" => 1, + "acl_app_passwds" => 1, + ) + ); + $stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template"); + $stmt->execute(array( + ":type" => "domain", + ":template" => $default_domain_template["template"] + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row)) { + $stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`) + VALUES (:type, :template, :attributes)"); + $stmt->execute(array( + ":type" => "domain", + ":template" => $default_domain_template["template"], + ":attributes" => json_encode($default_domain_template["attributes"]) + )); + } + $stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template"); + $stmt->execute(array( + ":type" => "mailbox", + ":template" => $default_mailbox_template["template"] + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row)) { + $stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`) + VALUES (:type, :template, :attributes)"); + $stmt->execute(array( + ":type" => "mailbox", + ":template" => $default_mailbox_template["template"], + ":attributes" => json_encode($default_mailbox_template["attributes"]) + )); + } + + // remove old sogo views and triggers + $pdo->query("DROP TRIGGER IF EXISTS sogo_update_password"); + + if (php_sapi_name() == "cli") { + echo "DB initialization completed" . PHP_EOL; + } else { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__), + 'msg' => 'db_init_complete' + ); + } + + // fill quarantine.qhash + $pdo->query("UPDATE `quarantine` SET `qhash` = SHA2(CONCAT(`id`, `qid`), 256) WHERE ISNULL(`qhash`)"); + + } catch (PDOException $e) { + if (php_sapi_name() == "cli") { + echo "DB initialization failed: " . print_r($e, true) . PHP_EOL; + } else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => array('mysql_error', $e) + ); + } + } +} +if (php_sapi_name() == "cli") { + include '/web/inc/vars.inc.php'; + include '/web/inc/functions.inc.php'; + include '/web/inc/functions.docker.inc.php'; + // $now = new DateTime(); + // $mins = $now->getOffset() / 60; + // $sgn = ($mins < 0 ? -1 : 1); + // $mins = abs($mins); + // $hrs = floor($mins / 60); + // $mins -= $hrs * 60; + // $offset = sprintf('%+d:%02d', $hrs*$sgn, $mins); + $dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; + $opt = [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, + //PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;", + ]; + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); + $stmt = $pdo->query("SELECT COUNT('OK') AS OK_C FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view' OR TABLE_NAME = '_sogo_static_view';"); + $res = $stmt->fetch(PDO::FETCH_ASSOC); + if (intval($res['OK_C']) === 2) { + // Be more precise when replacing into _sogo_static_view, col orders may change + try { + update_sogo_static_view(); + echo "Fixed _sogo_static_view" . PHP_EOL; + } catch (Exception $e) { + // Dunno + } + } + try { + $m = new Memcached(); + $m->addServer('memcached', 11211); + $m->flush(); + echo "Cleaned up memcached" . PHP_EOL; + } catch (Exception $e) { + // Dunno + } + init_db_schema(); +} diff --git a/data/web/inc/languages.min.css b/data/web/inc/languages.min.css deleted file mode 100644 index 7293e8886b..0000000000 --- a/data/web/inc/languages.min.css +++ /dev/null @@ -1 +0,0 @@ -.lang-lg,.lang-sm,.lang-xs{background-repeat:no-repeat;display:inline-block;background-image:url(languages.png)}.lang-sm,.lang-sm:after,.lang-xs,.lang-xs:after{position:relative}.lang-xs{background-position:0 -484px;min-width:14px;height:11px;min-height:11px;max-height:11px}.lang-sm{background-position:0 -1199px;min-width:22px;height:16px;min-height:16px;max-height:16px}.lang-lg{background-position:0 -2134px;min-width:30px;height:22px;min-height:22px;max-height:22px}.lang-xs[lang=ar]{background-position:0 0}.lang-xs[lang=be]{background-position:0 -11px}.lang-xs[lang=bg]{background-position:0 -22px}.lang-xs[lang=cs]{background-position:0 -33px}.lang-xs[lang=da]{background-position:0 -44px}.lang-xs[lang=de]{background-position:0 -55px}.lang-xs[lang=el]{background-position:0 -66px}.lang-xs[lang=en]{background-position:0 -77px}.lang-xs[lang=es]{background-position:0 -88px}.lang-xs[lang=et]{background-position:0 -99px}.lang-xs[lang=fi]{background-position:0 -110px}.lang-xs[lang=fr]{background-position:0 -121px}.lang-xs[lang=ga]{background-position:0 -132px}.lang-xs[lang=hi]{background-position:0 -143px}.lang-xs[lang=hr]{background-position:0 -154px}.lang-xs[lang=hu]{background-position:0 -165px}.lang-xs[lang=in]{background-position:0 -176px}.lang-xs[lang=is]{background-position:0 -187px}.lang-xs[lang=it]{background-position:0 -198px}.lang-xs[lang=iw]{background-position:0 -209px}.lang-xs[lang=ja]{background-position:0 -220px}.lang-xs[lang=ko]{background-position:0 -231px}.lang-xs[lang=lt]{background-position:0 -242px}.lang-xs[lang=lv]{background-position:0 -253px}.lang-xs[lang=mk]{background-position:0 -264px}.lang-xs[lang=ms]{background-position:0 -275px}.lang-xs[lang=mt]{background-position:0 -286px}.lang-xs[lang=nl]{background-position:0 -297px}.lang-xs[lang=no]{background-position:0 -308px}.lang-xs[lang=pl]{background-position:0 -319px}.lang-xs[lang=pt]{background-position:0 -330px}.lang-xs[lang=ro]{background-position:0 -341px}.lang-xs[lang=ru]{background-position:0 -352px}.lang-xs[lang=sk]{background-position:0 -363px}.lang-xs[lang=sl]{background-position:0 -374px}.lang-xs[lang=sq]{background-position:0 -385px}.lang-xs[lang=sr]{background-position:0 -396px}.lang-xs[lang=sv]{background-position:0 -407px}.lang-xs[lang=th]{background-position:0 -418px}.lang-xs[lang=tr]{background-position:0 -429px}.lang-xs[lang=uk]{background-position:0 -440px}.lang-xs[lang=vi]{background-position:0 -451px}.lang-xs[lang=zh]{background-position:0 -462px}.lang-xs[lang=ca]{background-position:0 -473px}.lang-sm[lang=ar]{background-position:0 -495px}.lang-sm[lang=be]{background-position:0 -511px}.lang-sm[lang=bg]{background-position:0 -527px}.lang-sm[lang=cs]{background-position:0 -543px}.lang-sm[lang=da]{background-position:0 -559px}.lang-sm[lang=de]{background-position:0 -575px}.lang-sm[lang=el]{background-position:0 -591px}.lang-sm[lang=en]{background-position:0 -607px}.lang-sm[lang=es]{background-position:0 -623px}.lang-sm[lang=et]{background-position:0 -639px}.lang-sm[lang=fi]{background-position:0 -655px}.lang-sm[lang=fr]{background-position:0 -671px}.lang-sm[lang=ga]{background-position:0 -687px}.lang-sm[lang=hi]{background-position:0 -703px}.lang-sm[lang=hr]{background-position:0 -719px}.lang-sm[lang=hu]{background-position:0 -735px}.lang-sm[lang=in]{background-position:0 -751px}.lang-sm[lang=is]{background-position:0 -767px}.lang-sm[lang=it]{background-position:0 -783px}.lang-sm[lang=iw]{background-position:0 -799px}.lang-sm[lang=ja]{background-position:0 -815px}.lang-sm[lang=ko]{background-position:0 -831px}.lang-sm[lang=lt]{background-position:0 -847px}.lang-sm[lang=lv]{background-position:0 -863px}.lang-sm[lang=mk]{background-position:0 -879px}.lang-sm[lang=ms]{background-position:0 -895px}.lang-sm[lang=mt]{background-position:0 -911px}.lang-sm[lang=nl]{background-position:0 -927px}.lang-sm[lang=no]{background-position:0 -943px}.lang-sm[lang=pl]{background-position:0 -959px}.lang-sm[lang=pt]{background-position:0 -975px}.lang-sm[lang=ro]{background-position:0 -991px}.lang-sm[lang=ru]{background-position:0 -1007px}.lang-sm[lang=sk]{background-position:0 -1023px}.lang-sm[lang=sl]{background-position:0 -1039px}.lang-sm[lang=sq]{background-position:0 -1055px}.lang-sm[lang=sr]{background-position:0 -1071px}.lang-sm[lang=sv]{background-position:0 -1087px}.lang-sm[lang=th]{background-position:0 -1103px}.lang-sm[lang=tr]{background-position:0 -1119px}.lang-sm[lang=uk]{background-position:0 -1135px}.lang-sm[lang=vi]{background-position:0 -1151px}.lang-sm[lang=zh]{background-position:0 -1167px}.lang-sm[lang=ca]{background-position:0 -1183px}.lang-lg[lang=ar]{background-position:0 -1188px}.lang-lg[lang=be]{background-position:0 -1210px}.lang-lg[lang=bg]{background-position:0 -1232px}.lang-lg[lang=cs]{background-position:0 -1254px}.lang-lg[lang=da]{background-position:0 -1276px}.lang-lg[lang=de]{background-position:0 -1298px}.lang-lg[lang=el]{background-position:0 -1320px}.lang-lg[lang=en]{background-position:0 -1342px}.lang-lg[lang=es]{background-position:0 -1364px}.lang-lg[lang=et]{background-position:0 -1386px}.lang-lg[lang=fi]{background-position:0 -1408px}.lang-lg[lang=fr]{background-position:0 -1430px}.lang-lg[lang=ga]{background-position:0 -1452px}.lang-lg[lang=hi]{background-position:0 -1474px}.lang-lg[lang=hr]{background-position:0 -1496px}.lang-lg[lang=hu]{background-position:0 -1518px}.lang-lg[lang=in]{background-position:0 -1540px}.lang-lg[lang=is]{background-position:0 -1562px}.lang-lg[lang=it]{background-position:0 -1584px}.lang-lg[lang=iw]{background-position:0 -1606px}.lang-lg[lang=ja]{background-position:0 -1628px}.lang-lg[lang=ko]{background-position:0 -1650px}.lang-lg[lang=lt]{background-position:0 -1672px}.lang-lg[lang=lv]{background-position:0 -1694px}.lang-lg[lang=mk]{background-position:0 -1716px}.lang-lg[lang=ms]{background-position:0 -1738px}.lang-lg[lang=mt]{background-position:0 -1760px}.lang-lg[lang=nl]{background-position:0 -1782px}.lang-lg[lang=no]{background-position:0 -1804px}.lang-lg[lang=pl]{background-position:0 -1826px}.lang-lg[lang=pt]{background-position:0 -1848px}.lang-lg[lang=ro]{background-position:0 -1870px}.lang-lg[lang=ru]{background-position:0 -1892px}.lang-lg[lang=sk]{background-position:0 -1914px}.lang-lg[lang=sl]{background-position:0 -1936px}.lang-lg[lang=sq]{background-position:0 -1958px}.lang-lg[lang=sr]{background-position:0 -1980px}.lang-lg[lang=sv]{background-position:0 -2002px}.lang-lg[lang=th]{background-position:0 -2024px}.lang-lg[lang=tr]{background-position:0 -2046px}.lang-lg[lang=uk]{background-position:0 -2068px}.lang-lg[lang=vi]{background-position:0 -2090px}.lang-lg[lang=zh]{background-position:0 -2112px}.lang-lbl-en:after,.lang-lbl-full:after,.lang-lbl:after{content:"Unknown language"}.lang-lbl[lang=ar]:after{content:"\000627\000644\000639\000631\000628\00064A\000629"}.lang-lbl[lang=be]:after{content:"\000411\000435\00043B\000430\000440\000443\000441\00043A\000456"}.lang-lbl[lang=bg]:after{content:"\000411\00044A\00043B\000433\000430\000440\000441\00043A\000438"}.lang-lbl[lang=ca]:after{content:"Catal\0000E0"}.lang-lbl[lang=cs]:after{content:"\00010Ce\000161tina"}.lang-lbl[lang=da]:after{content:"Dansk"}.lang-lbl[lang=de]:after{content:"Deutsch"}.lang-lbl[lang=el]:after{content:"\000395\0003BB\0003BB\0003B7\0003BD\0003B9\0003BA\0003AC"}.lang-lbl[lang=en]:after{content:"English"}.lang-lbl[lang=es]:after{content:"Espa\0000F1ol"}.lang-lbl[lang=et]:after{content:"Eesti"}.lang-lbl[lang=fi]:after{content:"Suomi"}.lang-lbl[lang=fr]:after{content:"Fran\0000E7ais"}.lang-lbl[lang=ga]:after{content:"Gaeilge"}.lang-lbl[lang=hi]:after{content:"\000939\00093F\000902\000926\000940"}.lang-lbl[lang=hr]:after{content:"Hrvatski"}.lang-lbl[lang=hu]:after{content:"Magyar"}.lang-lbl[lang=in]:after{content:"Bahasa\000020indonesia"}.lang-lbl[lang=is]:after{content:"\0000CDslenska"}.lang-lbl[lang=it]:after{content:"Italiano"}.lang-lbl[lang=iw]:after{content:"\0005E2\0005D1\0005E8\0005D9\0005EA"}.lang-lbl[lang=ja]:after{content:"\0065E5\00672C\008A9E"}.lang-lbl[lang=ko]:after{content:"\00D55C\00AD6D\00C5B4"}.lang-lbl[lang=lt]:after{content:"Lietuvi\000173"}.lang-lbl[lang=lv]:after{content:"Latvie\000161u"}.lang-lbl[lang=mk]:after{content:"\00041C\000430\00043A\000435\000434\00043E\00043D\000441\00043A\000438"}.lang-lbl[lang=ms]:after{content:"Bahasa\000020melayu"}.lang-lbl[lang=mt]:after{content:"Malti"}.lang-lbl[lang=nl]:after{content:"Nederlands"}.lang-lbl[lang=no]:after{content:"Norsk"}.lang-lbl[lang=pl]:after{content:"Polski"}.lang-lbl[lang=pt]:after{content:"Portugu\0000EAs"}.lang-lbl[lang=ro]:after{content:"Rom\0000E2n\000103"}.lang-lbl[lang=ru]:after{content:"\000420\000443\000441\000441\00043A\000438\000439"}.lang-lbl[lang=sk]:after{content:"Sloven\00010Dina"}.lang-lbl[lang=sl]:after{content:"Sloven\000161\00010Dina"}.lang-lbl[lang=sq]:after{content:"Shqipe"}.lang-lbl[lang=sr]:after{content:"\000421\000440\00043F\000441\00043A\000438"}.lang-lbl[lang=sv]:after{content:"Svenska"}.lang-lbl[lang=th]:after{content:"\000E44\000E17\000E22"}.lang-lbl[lang=tr]:after{content:"T\0000FCrk\0000E7e"}.lang-lbl[lang=uk]:after{content:"\000423\00043A\000440\000430\000457\00043D\000441\00044C\00043A\000430"}.lang-lbl[lang=vi]:after{content:"Ti\001EBFng\000020vi\001EC7t"}.lang-lbl[lang=zh]:after{content:"\004E2D\006587"}.lang-lbl-en[lang=ar]:after{content:"Arabic"}.lang-lbl-en[lang=be]:after{content:"Belarusian"}.lang-lbl-en[lang=bg]:after{content:"Bulgarian"}.lang-lbl-en[lang=ca]:after{content:"Catalan"}.lang-lbl-en[lang=cs]:after{content:"Czech"}.lang-lbl-en[lang=da]:after{content:"Danish"}.lang-lbl-en[lang=de]:after{content:"German"}.lang-lbl-en[lang=el]:after{content:"Greek"}.lang-lbl-en[lang=en]:after{content:"English"}.lang-lbl-en[lang=es]:after{content:"Spanish"}.lang-lbl-en[lang=et]:after{content:"Estonian"}.lang-lbl-en[lang=fi]:after{content:"Finnish"}.lang-lbl-en[lang=fr]:after{content:"French"}.lang-lbl-en[lang=ga]:after{content:"Irish"}.lang-lbl-en[lang=hi]:after{content:"Hindi"}.lang-lbl-en[lang=hr]:after{content:"Croatian"}.lang-lbl-en[lang=hu]:after{content:"Hungarian"}.lang-lbl-en[lang=in]:after{content:"Indonesian"}.lang-lbl-en[lang=is]:after{content:"Icelandic"}.lang-lbl-en[lang=it]:after{content:"Italian"}.lang-lbl-en[lang=iw]:after{content:"Hebrew"}.lang-lbl-en[lang=ja]:after{content:"Japanese"}.lang-lbl-en[lang=ko]:after{content:"Korean"}.lang-lbl-en[lang=lt]:after{content:"Lithuanian"}.lang-lbl-en[lang=lv]:after{content:"Latvian"}.lang-lbl-en[lang=mk]:after{content:"Macedonian"}.lang-lbl-en[lang=ms]:after{content:"Malay"}.lang-lbl-en[lang=mt]:after{content:"Maltese"}.lang-lbl-en[lang=nl]:after{content:"Dutch"}.lang-lbl-en[lang=no]:after{content:"Norwegian"}.lang-lbl-en[lang=pl]:after{content:"Polish"}.lang-lbl-en[lang=pt]:after{content:"Portuguese"}.lang-lbl-en[lang=ro]:after{content:"Romanian"}.lang-lbl-en[lang=ru]:after{content:"Russian"}.lang-lbl-en[lang=sk]:after{content:"Slovak"}.lang-lbl-en[lang=sl]:after{content:"Slovenian"}.lang-lbl-en[lang=sq]:after{content:"Albanian"}.lang-lbl-en[lang=sr]:after{content:"Serbian"}.lang-lbl-en[lang=sv]:after{content:"Swedish"}.lang-lbl-en[lang=th]:after{content:"Thai"}.lang-lbl-en[lang=tr]:after{content:"Turkish"}.lang-lbl-en[lang=uk]:after{content:"Ukrainian"}.lang-lbl-en[lang=vi]:after{content:"Vietnamese"}.lang-lbl-en[lang=zh]:after{content:"Chinese"}.lang-lbl-full[lang=ar]:after{content:"\000627\000644\000639\000631\000628\00064A\000629\0000A0/\0000A0Arabic"}.lang-lbl-full[lang=be]:after{content:"\000411\000435\00043B\000430\000440\000443\000441\00043A\000456\0000A0/\0000A0Belarusian"}.lang-lbl-full[lang=bg]:after{content:"\000411\00044A\00043B\000433\000430\000440\000441\00043A\000438\0000A0/\0000A0Bulgarian"}.lang-lbl-full[lang=ca]:after{content:"Catal\0000E0\0000A0/\0000A0Catalan"}.lang-lbl-full[lang=cs]:after{content:"\00010Ce\000161tina\0000A0/\0000A0Czech"}.lang-lbl-full[lang=da]:after{content:"Dansk\0000A0/\0000A0Danish"}.lang-lbl-full[lang=de]:after{content:"Deutsch\0000A0/\0000A0German"}.lang-lbl-full[lang=el]:after{content:"\000395\0003BB\0003BB\0003B7\0003BD\0003B9\0003BA\0003AC\0000A0/\0000A0Greek"}.lang-lbl-full[lang=en]:after{content:"English\0000A0/\0000A0English"}.lang-lbl-full[lang=es]:after{content:"Espa\0000F1ol\0000A0/\0000A0Spanish"}.lang-lbl-full[lang=et]:after{content:"Eesti\0000A0/\0000A0Estonian"}.lang-lbl-full[lang=fi]:after{content:"Suomi\0000A0/\0000A0Finnish"}.lang-lbl-full[lang=fr]:after{content:"Fran\0000E7ais\0000A0/\0000A0French"}.lang-lbl-full[lang=ga]:after{content:"Gaeilge\0000A0/\0000A0Irish"}.lang-lbl-full[lang=hi]:after{content:"\000939\00093F\000902\000926\000940\0000A0/\0000A0Hindi"}.lang-lbl-full[lang=hr]:after{content:"Hrvatski\0000A0/\0000A0Croatian"}.lang-lbl-full[lang=hu]:after{content:"Magyar\0000A0/\0000A0Hungarian"}.lang-lbl-full[lang=in]:after{content:"Bahasa\000020indonesia\0000A0/\0000A0Indonesian"}.lang-lbl-full[lang=is]:after{content:"\0000CDslenska\0000A0/\0000A0Icelandic"}.lang-lbl-full[lang=it]:after{content:"Italiano\0000A0/\0000A0Italian"}.lang-lbl-full[lang=iw]:after{content:"\0005E2\0005D1\0005E8\0005D9\0005EA\0000A0/\0000A0Hebrew"}.lang-lbl-full[lang=ja]:after{content:"\0065E5\00672C\008A9E\0000A0/\0000A0Japanese"}.lang-lbl-full[lang=ko]:after{content:"\00D55C\00AD6D\00C5B4\0000A0/\0000A0Korean"}.lang-lbl-full[lang=lt]:after{content:"Lietuvi\000173\0000A0/\0000A0Lithuanian"}.lang-lbl-full[lang=lv]:after{content:"Latvie\000161u\0000A0/\0000A0Latvian"}.lang-lbl-full[lang=mk]:after{content:"\00041C\000430\00043A\000435\000434\00043E\00043D\000441\00043A\000438\0000A0/\0000A0Macedonian"}.lang-lbl-full[lang=ms]:after{content:"Bahasa\000020melayu\0000A0/\0000A0Malay"}.lang-lbl-full[lang=mt]:after{content:"Malti\0000A0/\0000A0Maltese"}.lang-lbl-full[lang=nl]:after{content:"Nederlands\0000A0/\0000A0Dutch"}.lang-lbl-full[lang=no]:after{content:"Norsk\0000A0/\0000A0Norwegian"}.lang-lbl-full[lang=pl]:after{content:"Polski\0000A0/\0000A0Polish"}.lang-lbl-full[lang=pt]:after{content:"Portugu\0000EAs\0000A0/\0000A0Portuguese"}.lang-lbl-full[lang=ro]:after{content:"Rom\0000E2n\000103\0000A0/\0000A0Romanian"}.lang-lbl-full[lang=ru]:after{content:"\000420\000443\000441\000441\00043A\000438\000439\0000A0/\0000A0Russian"}.lang-lbl-full[lang=sk]:after{content:"Sloven\00010Dina\0000A0/\0000A0Slovak"}.lang-lbl-full[lang=sl]:after{content:"Sloven\000161\00010Dina\0000A0/\0000A0Slovenian"}.lang-lbl-full[lang=sq]:after{content:"Shqipe\0000A0/\0000A0Albanian"}.lang-lbl-full[lang=sr]:after{content:"\000421\000440\00043F\000441\00043A\000438\0000A0/\0000A0Serbian"}.lang-lbl-full[lang=sv]:after{content:"Svenska\0000A0/\0000A0Swedish"}.lang-lbl-full[lang=th]:after{content:"\000E44\000E17\000E22\0000A0/\0000A0Thai"}.lang-lbl-full[lang=tr]:after{content:"T\0000FCrk\0000E7e\0000A0/\0000A0Turkish"}.lang-lbl-full[lang=uk]:after{content:"\000423\00043A\000440\000430\000457\00043D\000441\00044C\00043A\000430\0000A0/\0000A0Ukrainian"}.lang-lbl-full[lang=vi]:after{content:"Ti\001EBFng\000020vi\001EC7t\0000A0/\0000A0Vietnamese"}.lang-lbl-full[lang=zh]:after{content:"\004E2D\006587\0000A0/\0000A0Chinese"}.lang-lg:before,.lang-sm:before,.lang-xs:before{content:'\0000A0'}.lang-xs.lang-lbl,.lang-xs.lang-lbl-en,.lang-xs.lang-lbl-full{padding-left:16px}.lang-sm.lang-lbl,.lang-sm.lang-lbl-en,.lang-sm.lang-lbl-full{padding-left:24px}.lang-lg.lang-lbl,.lang-lg.lang-lbl-en,.lang-lg.lang-lbl-full{padding-left:32px}.lang-lg.lang-lbl-en:before,.lang-lg.lang-lbl-full:before,.lang-lg.lang-lbl:before,.lang-sm.lang-lbl-en:before,.lang-sm.lang-lbl-full:before,.lang-sm.lang-lbl:before,.lang-xs.lang-lbl-en:before,.lang-xs.lang-lbl-full:before,.lang-xs.lang-lbl:before{content:''}.lang-lg,.lang-lg:after{top:0;position:relative}.lang-sm{top:1px}.lang-sm:after{top:-1px}.lang-xs{top:4px}.lang-xs:after{top:-4px}.lead>.lang-lg{top:2px}.lead>.lang-lg:after{top:-2px}.lead>.lang-sm{top:6px}.lead>.lang-sm:after{top:-6px}.lead>.lang-xs{top:8px}.lead>.lang-xs:after{top:-8px}small>.lang-sm{top:-1px}small>.lang-sm:after{top:1px}small>.lang-xs{top:2px}small>.lang-xs:after{top:-2px}h1>.lang-lg{top:9px}h1>.lang-lg:after{top:-9px}h1>.lang-sm{top:12px}h1>.lang-sm:after{top:-12px}h1>.lang-xs{top:14px}h1>.lang-xs:after{top:-14px}h2>.lang-lg{top:5px}h2>.lang-lg:after{top:-5px}h2>.lang-sm{top:8px}h2>.lang-sm:after{top:-8px}h2>.lang-xs{top:10px}h2>.lang-xs:after{top:-10px}h3>.lang-lg{top:1px}h3>.lang-lg:after{top:-1px}h3>.lang-sm{top:5px}h3>.lang-sm:after{top:-5px}h3>.lang-xs{top:8px}h3>.lang-xs:after{top:-8px}h4>.lang-lg{top:-1px}h4>.lang-lg:after,h4>.lang-sm{top:1px}h4>.lang-sm:after{top:-1px}h4>.lang-xs{top:4px}h4>.lang-xs:after{top:-4px}h5>.lang-sm,h5>.lang-sm:after{top:0}h5>.lang-xs{top:2px}h5>.lang-xs:after{top:-2px}h6>.lang-sm,h6>.lang-sm:after{top:0}h6>.lang-xs{top:1px}h6>.lang-xs:after{top:-1px}.btn>.lang-sm{top:2px}.btn>.lang-sm:after{top:-2px}.btn>.lang-xs{top:4px}.btn>.lang-xs:after{top:-4px}.btn.btn-xs>.lang-sm,.btn.btn-xs>.lang-sm:after{top:0}.btn.btn-xs>.lang-xs{top:3px}.btn.btn-xs>.lang-xs:after{top:-3px}.btn.btn-sm>.lang-sm,.btn.btn-sm>.lang-sm:after{top:0}.btn.btn-sm>.lang-xs{top:3px}.btn.btn-sm>.lang-xs:after{top:-3px}.btn.btn-lg>.lang-lg{top:1px}.btn.btn-lg>.lang-lg:after{top:-1px}.btn.btn-lg>.lang-sm{top:3px}.btn.btn-lg>.lang-sm:after{top:-3px}.btn.btn-lg>.lang-xs{top:6px}.btn.btn-lg>.lang-xs:after{top:-6px} \ No newline at end of file diff --git a/data/web/inc/languages.png b/data/web/inc/languages.png deleted file mode 100644 index f5f1a2f0d5..0000000000 Binary files a/data/web/inc/languages.png and /dev/null differ diff --git a/data/web/inc/lib/CSSminifierExtended.php b/data/web/inc/lib/CSSminifierExtended.php new file mode 100644 index 0000000000..125381c476 --- /dev/null +++ b/data/web/inc/lib/CSSminifierExtended.php @@ -0,0 +1,18 @@ +accessProtected($this,'data'))); + } + + private function accessProtected($obj, $prop) { + $reflection = new ReflectionClass($obj); + $property = $reflection->getProperty($prop); + $property->setAccessible(true); + return $property->getValue($obj); + } + +} \ No newline at end of file diff --git a/data/web/inc/lib/JSminifierExtended.php b/data/web/inc/lib/JSminifierExtended.php new file mode 100644 index 0000000000..c891cb81ac --- /dev/null +++ b/data/web/inc/lib/JSminifierExtended.php @@ -0,0 +1,18 @@ +accessProtected($this,'data'))); + } + + private function accessProtected($obj, $prop) { + $reflection = new ReflectionClass($obj); + $property = $reflection->getProperty($prop); + $property->setAccessible(true); + return $property->getValue($obj); + } + +} \ No newline at end of file diff --git a/data/web/inc/lib/WebAuthn/Attestation/AttestationObject.php b/data/web/inc/lib/WebAuthn/Attestation/AttestationObject.php new file mode 100644 index 0000000000..115d7878eb --- /dev/null +++ b/data/web/inc/lib/WebAuthn/Attestation/AttestationObject.php @@ -0,0 +1,171 @@ +_authenticatorData = new AuthenticatorData($enc['authData']->getBinaryString()); + $this->_attestationFormatName = $enc['fmt']; + + // Format ok? + if (!in_array($this->_attestationFormatName, $allowedFormats)) { + throw new WebAuthnException('invalid atttestation format: ' . $this->_attestationFormatName, WebAuthnException::INVALID_DATA); + } + + + switch ($this->_attestationFormatName) { + case 'android-key': $this->_attestationFormat = new Format\AndroidKey($enc, $this->_authenticatorData); break; + case 'android-safetynet': $this->_attestationFormat = new Format\AndroidSafetyNet($enc, $this->_authenticatorData); break; + case 'apple': $this->_attestationFormat = new Format\Apple($enc, $this->_authenticatorData); break; + case 'fido-u2f': $this->_attestationFormat = new Format\U2f($enc, $this->_authenticatorData); break; + case 'none': $this->_attestationFormat = new Format\None($enc, $this->_authenticatorData); break; + case 'packed': $this->_attestationFormat = new Format\Packed($enc, $this->_authenticatorData); break; + case 'tpm': $this->_attestationFormat = new Format\Tpm($enc, $this->_authenticatorData); break; + default: throw new WebAuthnException('invalid attestation format: ' . $enc['fmt'], WebAuthnException::INVALID_DATA); + } + } + + /** + * returns the attestation format name + * @return string + */ + public function getAttestationFormatName() { + return $this->_attestationFormatName; + } + + /** + * returns the attestation public key in PEM format + * @return AuthenticatorData + */ + public function getAuthenticatorData() { + return $this->_authenticatorData; + } + + /** + * returns the certificate chain as PEM + * @return string|null + */ + public function getCertificateChain() { + return $this->_attestationFormat->getCertificateChain(); + } + + /** + * return the certificate issuer as string + * @return string + */ + public function getCertificateIssuer() { + $pem = $this->getCertificatePem(); + $issuer = ''; + if ($pem) { + $certInfo = \openssl_x509_parse($pem); + if (\is_array($certInfo) && \array_key_exists('issuer', $certInfo) && \is_array($certInfo['issuer'])) { + + $cn = $certInfo['issuer']['CN'] ?? ''; + $o = $certInfo['issuer']['O'] ?? ''; + $ou = $certInfo['issuer']['OU'] ?? ''; + + if ($cn) { + $issuer .= $cn; + } + if ($issuer && ($o || $ou)) { + $issuer .= ' (' . trim($o . ' ' . $ou) . ')'; + } else { + $issuer .= trim($o . ' ' . $ou); + } + } + } + + return $issuer; + } + + /** + * return the certificate subject as string + * @return string + */ + public function getCertificateSubject() { + $pem = $this->getCertificatePem(); + $subject = ''; + if ($pem) { + $certInfo = \openssl_x509_parse($pem); + if (\is_array($certInfo) && \array_key_exists('subject', $certInfo) && \is_array($certInfo['subject'])) { + + $cn = $certInfo['subject']['CN'] ?? ''; + $o = $certInfo['subject']['O'] ?? ''; + $ou = $certInfo['subject']['OU'] ?? ''; + + if ($cn) { + $subject .= $cn; + } + if ($subject && ($o || $ou)) { + $subject .= ' (' . trim($o . ' ' . $ou) . ')'; + } else { + $subject .= trim($o . ' ' . $ou); + } + } + } + + return $subject; + } + + /** + * returns the key certificate in PEM format + * @return string + */ + public function getCertificatePem() { + return $this->_attestationFormat->getCertificatePem(); + } + + /** + * checks validity of the signature + * @param string $clientDataHash + * @return bool + * @throws WebAuthnException + */ + public function validateAttestation($clientDataHash) { + return $this->_attestationFormat->validateAttestation($clientDataHash); + } + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + return $this->_attestationFormat->validateRootCertificate($rootCas); + } + + /** + * checks if the RpId-Hash is valid + * @param string$rpIdHash + * @return bool + */ + public function validateRpIdHash($rpIdHash) { + return $rpIdHash === $this->_authenticatorData->getRpIdHash(); + } +} diff --git a/data/web/inc/lib/WebAuthn/Attestation/AuthenticatorData.php b/data/web/inc/lib/WebAuthn/Attestation/AuthenticatorData.php new file mode 100644 index 0000000000..1e1212ea27 --- /dev/null +++ b/data/web/inc/lib/WebAuthn/Attestation/AuthenticatorData.php @@ -0,0 +1,423 @@ +_binary = $binary; + + // Read infos from binary + // https://www.w3.org/TR/webauthn/#sec-authenticator-data + + // RP ID + $this->_rpIdHash = \substr($binary, 0, 32); + + // flags (1 byte) + $flags = \unpack('Cflags', \substr($binary, 32, 1))['flags']; + $this->_flags = $this->_readFlags($flags); + + // signature counter: 32-bit unsigned big-endian integer. + $this->_signCount = \unpack('Nsigncount', \substr($binary, 33, 4))['signcount']; + + $offset = 37; + // https://www.w3.org/TR/webauthn/#sec-attested-credential-data + if ($this->_flags->attestedDataIncluded) { + $this->_attestedCredentialData = $this->_readAttestData($binary, $offset); + } + + if ($this->_flags->extensionDataIncluded) { + $this->_readExtensionData(\substr($binary, $offset)); + } + } + + /** + * Authenticator Attestation Globally Unique Identifier, a unique number + * that identifies the model of the authenticator (not the specific instance + * of the authenticator) + * The aaguid may be 0 if the user is using a old u2f device and/or if + * the browser is using the fido-u2f format. + * @return string + * @throws WebAuthnException + */ + public function getAAGUID() { + if (!($this->_attestedCredentialData instanceof \stdClass)) { + throw new WebAuthnException('credential data not included in authenticator data', WebAuthnException::INVALID_DATA); + } + return $this->_attestedCredentialData->aaguid; + } + + /** + * returns the authenticatorData as binary + * @return string + */ + public function getBinary() { + return $this->_binary; + } + + /** + * returns the credentialId + * @return string + * @throws WebAuthnException + */ + public function getCredentialId() { + if (!($this->_attestedCredentialData instanceof \stdClass)) { + throw new WebAuthnException('credential id not included in authenticator data', WebAuthnException::INVALID_DATA); + } + return $this->_attestedCredentialData->credentialId; + } + + /** + * returns the public key in PEM format + * @return string + */ + public function getPublicKeyPem() { + $der = null; + switch ($this->_attestedCredentialData->credentialPublicKey->kty) { + case self::$_EC2_TYPE: $der = $this->_getEc2Der(); break; + case self::$_RSA_TYPE: $der = $this->_getRsaDer(); break; + default: throw new WebAuthnException('invalid key type', WebAuthnException::INVALID_DATA); + } + + $pem = '-----BEGIN PUBLIC KEY-----' . "\n"; + $pem .= \chunk_split(\base64_encode($der), 64, "\n"); + $pem .= '-----END PUBLIC KEY-----' . "\n"; + return $pem; + } + + /** + * returns the public key in U2F format + * @return string + * @throws WebAuthnException + */ + public function getPublicKeyU2F() { + if (!($this->_attestedCredentialData instanceof \stdClass)) { + throw new WebAuthnException('credential data not included in authenticator data', WebAuthnException::INVALID_DATA); + } + return "\x04" . // ECC uncompressed + $this->_attestedCredentialData->credentialPublicKey->x . + $this->_attestedCredentialData->credentialPublicKey->y; + } + + /** + * returns the SHA256 hash of the relying party id (=hostname) + * @return string + */ + public function getRpIdHash() { + return $this->_rpIdHash; + } + + /** + * returns the sign counter + * @return int + */ + public function getSignCount() { + return $this->_signCount; + } + + /** + * returns true if the user is present + * @return boolean + */ + public function getUserPresent() { + return $this->_flags->userPresent; + } + + /** + * returns true if the user is verified + * @return boolean + */ + public function getUserVerified() { + return $this->_flags->userVerified; + } + + // ----------------------------------------------- + // PRIVATE + // ----------------------------------------------- + + /** + * Returns DER encoded EC2 key + * @return string + */ + private function _getEc2Der() { + return $this->_der_sequence( + $this->_der_sequence( + $this->_der_oid("\x2A\x86\x48\xCE\x3D\x02\x01") . // OID 1.2.840.10045.2.1 ecPublicKey + $this->_der_oid("\x2A\x86\x48\xCE\x3D\x03\x01\x07") // 1.2.840.10045.3.1.7 prime256v1 + ) . + $this->_der_bitString($this->getPublicKeyU2F()) + ); + } + + /** + * Returns DER encoded RSA key + * @return string + */ + private function _getRsaDer() { + return $this->_der_sequence( + $this->_der_sequence( + $this->_der_oid("\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01") . // OID 1.2.840.113549.1.1.1 rsaEncryption + $this->_der_nullValue() + ) . + $this->_der_bitString( + $this->_der_sequence( + $this->_der_unsignedInteger($this->_attestedCredentialData->credentialPublicKey->n) . + $this->_der_unsignedInteger($this->_attestedCredentialData->credentialPublicKey->e) + ) + ) + ); + } + + /** + * reads the flags from flag byte + * @param string $binFlag + * @return \stdClass + */ + private function _readFlags($binFlag) { + $flags = new \stdClass(); + + $flags->bit_0 = !!($binFlag & 1); + $flags->bit_1 = !!($binFlag & 2); + $flags->bit_2 = !!($binFlag & 4); + $flags->bit_3 = !!($binFlag & 8); + $flags->bit_4 = !!($binFlag & 16); + $flags->bit_5 = !!($binFlag & 32); + $flags->bit_6 = !!($binFlag & 64); + $flags->bit_7 = !!($binFlag & 128); + + // named flags + $flags->userPresent = $flags->bit_0; + $flags->userVerified = $flags->bit_2; + $flags->attestedDataIncluded = $flags->bit_6; + $flags->extensionDataIncluded = $flags->bit_7; + return $flags; + } + + /** + * read attested data + * @param string $binary + * @param int $endOffset + * @return \stdClass + * @throws WebAuthnException + */ + private function _readAttestData($binary, &$endOffset) { + $attestedCData = new \stdClass(); + if (\strlen($binary) <= 55) { + throw new WebAuthnException('Attested data should be present but is missing', WebAuthnException::INVALID_DATA); + } + + // The AAGUID of the authenticator + $attestedCData->aaguid = \substr($binary, 37, 16); + + //Byte length L of Credential ID, 16-bit unsigned big-endian integer. + $length = \unpack('nlength', \substr($binary, 53, 2))['length']; + $attestedCData->credentialId = \substr($binary, 55, $length); + + // set end offset + $endOffset = 55 + $length; + + // extract public key + $attestedCData->credentialPublicKey = $this->_readCredentialPublicKey($binary, 55 + $length, $endOffset); + + return $attestedCData; + } + + /** + * reads COSE key-encoded elliptic curve public key in EC2 format + * @param string $binary + * @param int $endOffset + * @return \stdClass + * @throws WebAuthnException + */ + private function _readCredentialPublicKey($binary, $offset, &$endOffset) { + $enc = CborDecoder::decodeInPlace($binary, $offset, $endOffset); + + // COSE key-encoded elliptic curve public key in EC2 format + $credPKey = new \stdClass(); + $credPKey->kty = $enc[self::$_COSE_KTY]; + $credPKey->alg = $enc[self::$_COSE_ALG]; + + switch ($credPKey->alg) { + case self::$_EC2_ES256: $this->_readCredentialPublicKeyES256($credPKey, $enc); break; + case self::$_RSA_RS256: $this->_readCredentialPublicKeyRS256($credPKey, $enc); break; + } + + return $credPKey; + } + + /** + * extract ES256 informations from cose + * @param \stdClass $credPKey + * @param \stdClass $enc + * @throws WebAuthnException + */ + private function _readCredentialPublicKeyES256(&$credPKey, $enc) { + $credPKey->crv = $enc[self::$_COSE_CRV]; + $credPKey->x = $enc[self::$_COSE_X] instanceof ByteBuffer ? $enc[self::$_COSE_X]->getBinaryString() : null; + $credPKey->y = $enc[self::$_COSE_Y] instanceof ByteBuffer ? $enc[self::$_COSE_Y]->getBinaryString() : null; + unset ($enc); + + // Validation + if ($credPKey->kty !== self::$_EC2_TYPE) { + throw new WebAuthnException('public key not in EC2 format', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if ($credPKey->alg !== self::$_EC2_ES256) { + throw new WebAuthnException('signature algorithm not ES256', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if ($credPKey->crv !== self::$_EC2_P256) { + throw new WebAuthnException('curve not P-256', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if (\strlen($credPKey->x) !== 32) { + throw new WebAuthnException('Invalid X-coordinate', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if (\strlen($credPKey->y) !== 32) { + throw new WebAuthnException('Invalid Y-coordinate', WebAuthnException::INVALID_PUBLIC_KEY); + } + } + + /** + * extract RS256 informations from COSE + * @param \stdClass $credPKey + * @param \stdClass $enc + * @throws WebAuthnException + */ + private function _readCredentialPublicKeyRS256(&$credPKey, $enc) { + $credPKey->n = $enc[self::$_COSE_N] instanceof ByteBuffer ? $enc[self::$_COSE_N]->getBinaryString() : null; + $credPKey->e = $enc[self::$_COSE_E] instanceof ByteBuffer ? $enc[self::$_COSE_E]->getBinaryString() : null; + unset ($enc); + + // Validation + if ($credPKey->kty !== self::$_RSA_TYPE) { + throw new WebAuthnException('public key not in RSA format', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if ($credPKey->alg !== self::$_RSA_RS256) { + throw new WebAuthnException('signature algorithm not ES256', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if (\strlen($credPKey->n) !== 256) { + throw new WebAuthnException('Invalid RSA modulus', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if (\strlen($credPKey->e) !== 3) { + throw new WebAuthnException('Invalid RSA public exponent', WebAuthnException::INVALID_PUBLIC_KEY); + } + + } + + /** + * reads cbor encoded extension data. + * @param string $binary + * @return array + * @throws WebAuthnException + */ + private function _readExtensionData($binary) { + $ext = CborDecoder::decode($binary); + if (!\is_array($ext)) { + throw new WebAuthnException('invalid extension data', WebAuthnException::INVALID_DATA); + } + + return $ext; + } + + + // --------------- + // DER functions + // --------------- + + private function _der_length($len) { + if ($len < 128) { + return \chr($len); + } + $lenBytes = ''; + while ($len > 0) { + $lenBytes = \chr($len % 256) . $lenBytes; + $len = \intdiv($len, 256); + } + return \chr(0x80 | \strlen($lenBytes)) . $lenBytes; + } + + private function _der_sequence($contents) { + return "\x30" . $this->_der_length(\strlen($contents)) . $contents; + } + + private function _der_oid($encoded) { + return "\x06" . $this->_der_length(\strlen($encoded)) . $encoded; + } + + private function _der_bitString($bytes) { + return "\x03" . $this->_der_length(\strlen($bytes) + 1) . "\x00" . $bytes; + } + + private function _der_nullValue() { + return "\x05\x00"; + } + + private function _der_unsignedInteger($bytes) { + $len = \strlen($bytes); + + // Remove leading zero bytes + for ($i = 0; $i < ($len - 1); $i++) { + if (\ord($bytes[$i]) !== 0) { + break; + } + } + if ($i !== 0) { + $bytes = \substr($bytes, $i); + } + + // If most significant bit is set, prefix with another zero to prevent it being seen as negative number + if ((\ord($bytes[0]) & 0x80) !== 0) { + $bytes = "\x00" . $bytes; + } + + return "\x02" . $this->_der_length(\strlen($bytes)) . $bytes; + } +} diff --git a/data/web/inc/lib/WebAuthn/Attestation/Format/AndroidKey.php b/data/web/inc/lib/WebAuthn/Attestation/Format/AndroidKey.php new file mode 100644 index 0000000000..4581272cea --- /dev/null +++ b/data/web/inc/lib/WebAuthn/Attestation/Format/AndroidKey.php @@ -0,0 +1,96 @@ +_attestationObject['attStmt']; + + if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) { + throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) { + throw new WebAuthnException('no signature found', WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('x5c', $attStmt) || !\is_array($attStmt['x5c']) || \count($attStmt['x5c']) < 1) { + throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); + } + + if (!\is_object($attStmt['x5c'][0]) || !($attStmt['x5c'][0] instanceof ByteBuffer)) { + throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); + } + + $this->_alg = $attStmt['alg']; + $this->_signature = $attStmt['sig']->getBinaryString(); + $this->_x5c = $attStmt['x5c'][0]->getBinaryString(); + + if (count($attStmt['x5c']) > 1) { + for ($i=1; $i_x5c_chain[] = $attStmt['x5c'][$i]->getBinaryString(); + } + unset ($i); + } + } + + + /* + * returns the key certificate in PEM format + * @return string + */ + public function getCertificatePem() { + return $this->_createCertificatePem($this->_x5c); + } + + /** + * @param string $clientDataHash + */ + public function validateAttestation($clientDataHash) { + $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); + + if ($publicKey === false) { + throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); + } + + // Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash + // using the attestation public key in attestnCert with the algorithm specified in alg. + $dataToVerify = $this->_authenticatorData->getBinary(); + $dataToVerify .= $clientDataHash; + + $coseAlgorithm = $this->_getCoseAlgorithm($this->_alg); + + // check certificate + return \openssl_verify($dataToVerify, $this->_signature, $publicKey, $coseAlgorithm->openssl) === 1; + } + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + $chainC = $this->_createX5cChainFile(); + if ($chainC) { + $rootCas[] = $chainC; + } + + $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); + if ($v === -1) { + throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); + } + return $v; + } +} + diff --git a/data/web/inc/lib/WebAuthn/Attestation/Format/AndroidSafetyNet.php b/data/web/inc/lib/WebAuthn/Attestation/Format/AndroidSafetyNet.php new file mode 100644 index 0000000000..a027ca9e7e --- /dev/null +++ b/data/web/inc/lib/WebAuthn/Attestation/Format/AndroidSafetyNet.php @@ -0,0 +1,141 @@ +_attestationObject['attStmt']; + + if (!\array_key_exists('ver', $attStmt) || !$attStmt['ver']) { + throw new WebAuthnException('invalid Android Safety Net Format', WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('response', $attStmt) || !($attStmt['response'] instanceof ByteBuffer)) { + throw new WebAuthnException('invalid Android Safety Net Format', WebAuthnException::INVALID_DATA); + } + + $response = $attStmt['response']->getBinaryString(); + + // Response is a JWS [RFC7515] object in Compact Serialization. + // JWSs have three segments separated by two period ('.') characters + $parts = \explode('.', $response); + unset ($response); + if (\count($parts) !== 3) { + throw new WebAuthnException('invalid JWS data', WebAuthnException::INVALID_DATA); + } + + $header = $this->_base64url_decode($parts[0]); + $payload = $this->_base64url_decode($parts[1]); + $this->_signature = $this->_base64url_decode($parts[2]); + $this->_signedValue = $parts[0] . '.' . $parts[1]; + unset ($parts); + + $header = \json_decode($header); + $payload = \json_decode($payload); + + if (!($header instanceof \stdClass)) { + throw new WebAuthnException('invalid JWS header', WebAuthnException::INVALID_DATA); + } + if (!($payload instanceof \stdClass)) { + throw new WebAuthnException('invalid JWS payload', WebAuthnException::INVALID_DATA); + } + + if (!$header->x5c || !is_array($header->x5c) || count($header->x5c) === 0) { + throw new WebAuthnException('No X.509 signature in JWS Header', WebAuthnException::INVALID_DATA); + } + + // algorithm + if (!\in_array($header->alg, array('RS256', 'ES256'))) { + throw new WebAuthnException('invalid JWS algorithm ' . $header->alg, WebAuthnException::INVALID_DATA); + } + + $this->_x5c = \base64_decode($header->x5c[0]); + $this->_payload = $payload; + + if (count($header->x5c) > 1) { + for ($i=1; $ix5c); $i++) { + $this->_x5c_chain[] = \base64_decode($header->x5c[$i]); + } + unset ($i); + } + } + + + /* + * returns the key certificate in PEM format + * @return string + */ + public function getCertificatePem() { + return $this->_createCertificatePem($this->_x5c); + } + + /** + * @param string $clientDataHash + */ + public function validateAttestation($clientDataHash) { + $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); + + // Verify that the nonce in the response is identical to the Base64 encoding + // of the SHA-256 hash of the concatenation of authenticatorData and clientDataHash. + if (!$this->_payload->nonce || $this->_payload->nonce !== \base64_encode(\hash('SHA256', $this->_authenticatorData->getBinary() . $clientDataHash, true))) { + throw new WebAuthnException('invalid nonce in JWS payload', WebAuthnException::INVALID_DATA); + } + + // Verify that attestationCert is issued to the hostname "attest.android.com" + $certInfo = \openssl_x509_parse($this->getCertificatePem()); + if (!\is_array($certInfo) || !$certInfo['subject'] || $certInfo['subject']['CN'] !== 'attest.android.com') { + throw new WebAuthnException('invalid certificate CN in JWS (' . $certInfo['subject']['CN']. ')', WebAuthnException::INVALID_DATA); + } + + // Verify that the ctsProfileMatch attribute in the payload of response is true. + if (!$this->_payload->ctsProfileMatch) { + throw new WebAuthnException('invalid ctsProfileMatch in payload', WebAuthnException::INVALID_DATA); + } + + // check certificate + return \openssl_verify($this->_signedValue, $this->_signature, $publicKey, OPENSSL_ALGO_SHA256) === 1; + } + + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + $chainC = $this->_createX5cChainFile(); + if ($chainC) { + $rootCas[] = $chainC; + } + + $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); + if ($v === -1) { + throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); + } + return $v; + } + + + /** + * decode base64 url + * @param string $data + * @return string + */ + private function _base64url_decode($data) { + return \base64_decode(\strtr($data, '-_', '+/') . \str_repeat('=', 3 - (3 + \strlen($data)) % 4)); + } +} + diff --git a/data/web/inc/lib/WebAuthn/Attestation/Format/Apple.php b/data/web/inc/lib/WebAuthn/Attestation/Format/Apple.php new file mode 100644 index 0000000000..e1f19f09e8 --- /dev/null +++ b/data/web/inc/lib/WebAuthn/Attestation/Format/Apple.php @@ -0,0 +1,139 @@ +_attestationObject['attStmt']; + + + // certificate for validation + if (\array_key_exists('x5c', $attStmt) && \is_array($attStmt['x5c']) && \count($attStmt['x5c']) > 0) { + + // The attestation certificate attestnCert MUST be the first element in the array + $attestnCert = array_shift($attStmt['x5c']); + + if (!($attestnCert instanceof ByteBuffer)) { + throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); + } + + $this->_x5c = $attestnCert->getBinaryString(); + + // certificate chain + foreach ($attStmt['x5c'] as $chain) { + if ($chain instanceof ByteBuffer) { + $this->_x5c_chain[] = $chain->getBinaryString(); + } + } + } else { + throw new WebAuthnException('invalid Apple attestation statement: missing x5c', WebAuthnException::INVALID_DATA); + } + } + + + /* + * returns the key certificate in PEM format + * @return string|null + */ + public function getCertificatePem() { + return $this->_createCertificatePem($this->_x5c); + } + + /** + * @param string $clientDataHash + */ + public function validateAttestation($clientDataHash) { + return $this->_validateOverX5c($clientDataHash); + } + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + $chainC = $this->_createX5cChainFile(); + if ($chainC) { + $rootCas[] = $chainC; + } + + $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); + if ($v === -1) { + throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); + } + return $v; + } + + /** + * validate if x5c is present + * @param string $clientDataHash + * @return bool + * @throws WebAuthnException + */ + protected function _validateOverX5c($clientDataHash) { + $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); + + if ($publicKey === false) { + throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); + } + + // Concatenate authenticatorData and clientDataHash to form nonceToHash. + $nonceToHash = $this->_authenticatorData->getBinary(); + $nonceToHash .= $clientDataHash; + + // Perform SHA-256 hash of nonceToHash to produce nonce + $nonce = hash('SHA256', $nonceToHash, true); + + $credCert = openssl_x509_read($this->getCertificatePem()); + if ($credCert === false) { + throw new WebAuthnException('invalid x5c certificate: ' . \openssl_error_string(), WebAuthnException::INVALID_DATA); + } + + $keyData = openssl_pkey_get_details(openssl_pkey_get_public($credCert)); + $key = is_array($keyData) && array_key_exists('key', $keyData) ? $keyData['key'] : null; + + + // Verify that nonce equals the value of the extension with OID ( 1.2.840.113635.100.8.2 ) in credCert. + $parsedCredCert = openssl_x509_parse($credCert); + $nonceExtension = isset($parsedCredCert['extensions']['1.2.840.113635.100.8.2']) ? $parsedCredCert['extensions']['1.2.840.113635.100.8.2'] : ''; + + // nonce padded by ASN.1 string: 30 24 A1 22 04 20 + // 30 โ€” type tag indicating sequence + // 24 โ€” 36 byte following + // A1 โ€” Enumerated [1] + // 22 โ€” 34 byte following + // 04 โ€” type tag indicating octet string + // 20 โ€” 32 byte following + + $asn1Padding = "\x30\x24\xA1\x22\x04\x20"; + if (substr($nonceExtension, 0, strlen($asn1Padding)) === $asn1Padding) { + $nonceExtension = substr($nonceExtension, strlen($asn1Padding)); + } + + if ($nonceExtension !== $nonce) { + throw new WebAuthnException('nonce doesn\'t equal the value of the extension with OID 1.2.840.113635.100.8.2', WebAuthnException::INVALID_DATA); + } + + // Verify that the credential public key equals the Subject Public Key of credCert. + $authKeyData = openssl_pkey_get_details(openssl_pkey_get_public($this->_authenticatorData->getPublicKeyPem())); + $authKey = is_array($authKeyData) && array_key_exists('key', $authKeyData) ? $authKeyData['key'] : null; + + if ($key === null || $key !== $authKey) { + throw new WebAuthnException('credential public key doesn\'t equal the Subject Public Key of credCert', WebAuthnException::INVALID_DATA); + } + + return true; + } + +} + diff --git a/data/web/inc/lib/WebAuthn/Attestation/Format/FormatBase.php b/data/web/inc/lib/WebAuthn/Attestation/Format/FormatBase.php new file mode 100644 index 0000000000..b2f1dfd7d8 --- /dev/null +++ b/data/web/inc/lib/WebAuthn/Attestation/Format/FormatBase.php @@ -0,0 +1,184 @@ +_attestationObject = $AttestionObject; + $this->_authenticatorData = $authenticatorData; + } + + /** + * + */ + public function __destruct() { + // delete X.509 chain certificate file after use + if ($this->_x5c_tempFile && \is_file($this->_x5c_tempFile)) { + \unlink($this->_x5c_tempFile); + } + } + + /** + * returns the certificate chain in PEM format + * @return string|null + */ + public function getCertificateChain() { + if ($this->_x5c_tempFile && \is_file($this->_x5c_tempFile)) { + return \file_get_contents($this->_x5c_tempFile); + } + return null; + } + + /** + * returns the key X.509 certificate in PEM format + * @return string + */ + public function getCertificatePem() { + // need to be overwritten + return null; + } + + /** + * checks validity of the signature + * @param string $clientDataHash + * @return bool + * @throws WebAuthnException + */ + public function validateAttestation($clientDataHash) { + // need to be overwritten + return false; + } + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + // need to be overwritten + return false; + } + + + /** + * create a PEM encoded certificate with X.509 binary data + * @param string $x5c + * @return string + */ + protected function _createCertificatePem($x5c) { + $pem = '-----BEGIN CERTIFICATE-----' . "\n"; + $pem .= \chunk_split(\base64_encode($x5c), 64, "\n"); + $pem .= '-----END CERTIFICATE-----' . "\n"; + return $pem; + } + + /** + * creates a PEM encoded chain file + * @return type + */ + protected function _createX5cChainFile() { + $content = ''; + if (\is_array($this->_x5c_chain) && \count($this->_x5c_chain) > 0) { + foreach ($this->_x5c_chain as $x5c) { + $certInfo = \openssl_x509_parse($this->_createCertificatePem($x5c)); + // check if issuer = subject (self signed) + if (\is_array($certInfo) && \is_array($certInfo['issuer']) && \is_array($certInfo['subject'])) { + $selfSigned = true; + foreach ($certInfo['issuer'] as $k => $v) { + if ($certInfo['subject'][$k] !== $v) { + $selfSigned = false; + break; + } + } + + if (!$selfSigned) { + $content .= "\n" . $this->_createCertificatePem($x5c) . "\n"; + } + } + } + } + + if ($content) { + $this->_x5c_tempFile = \sys_get_temp_dir() . '/x5c_chain_' . \base_convert(\rand(), 10, 36) . '.pem'; + if (\file_put_contents($this->_x5c_tempFile, $content) !== false) { + return $this->_x5c_tempFile; + } + } + + return null; + } + + + /** + * returns the name and openssl key for provided cose number. + * @param int $coseNumber + * @return \stdClass|null + */ + protected function _getCoseAlgorithm($coseNumber) { + // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + $coseAlgorithms = array( + array( + 'hash' => 'SHA1', + 'openssl' => OPENSSL_ALGO_SHA1, + 'cose' => array( + -65535 // RS1 + )), + + array( + 'hash' => 'SHA256', + 'openssl' => OPENSSL_ALGO_SHA256, + 'cose' => array( + -257, // RS256 + -37, // PS256 + -7, // ES256 + 5 // HMAC256 + )), + + array( + 'hash' => 'SHA384', + 'openssl' => OPENSSL_ALGO_SHA384, + 'cose' => array( + -258, // RS384 + -38, // PS384 + -35, // ES384 + 6 // HMAC384 + )), + + array( + 'hash' => 'SHA512', + 'openssl' => OPENSSL_ALGO_SHA512, + 'cose' => array( + -259, // RS512 + -39, // PS512 + -36, // ES512 + 7 // HMAC512 + )) + ); + + foreach ($coseAlgorithms as $coseAlgorithm) { + if (\in_array($coseNumber, $coseAlgorithm['cose'], true)) { + $return = new \stdClass(); + $return->hash = $coseAlgorithm['hash']; + $return->openssl = $coseAlgorithm['openssl']; + return $return; + } + } + + return null; + } +} diff --git a/data/web/inc/lib/WebAuthn/Attestation/Format/None.php b/data/web/inc/lib/WebAuthn/Attestation/Format/None.php new file mode 100644 index 0000000000..ba95e40f0f --- /dev/null +++ b/data/web/inc/lib/WebAuthn/Attestation/Format/None.php @@ -0,0 +1,41 @@ +_attestationObject['attStmt']; + + if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) { + throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) { + throw new WebAuthnException('no signature found', WebAuthnException::INVALID_DATA); + } + + $this->_alg = $attStmt['alg']; + $this->_signature = $attStmt['sig']->getBinaryString(); + + // certificate for validation + if (\array_key_exists('x5c', $attStmt) && \is_array($attStmt['x5c']) && \count($attStmt['x5c']) > 0) { + + // The attestation certificate attestnCert MUST be the first element in the array + $attestnCert = array_shift($attStmt['x5c']); + + if (!($attestnCert instanceof ByteBuffer)) { + throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); + } + + $this->_x5c = $attestnCert->getBinaryString(); + + // certificate chain + foreach ($attStmt['x5c'] as $chain) { + if ($chain instanceof ByteBuffer) { + $this->_x5c_chain[] = $chain->getBinaryString(); + } + } + } + } + + + /* + * returns the key certificate in PEM format + * @return string|null + */ + public function getCertificatePem() { + if (!$this->_x5c) { + return null; + } + return $this->_createCertificatePem($this->_x5c); + } + + /** + * @param string $clientDataHash + */ + public function validateAttestation($clientDataHash) { + if ($this->_x5c) { + return $this->_validateOverX5c($clientDataHash); + } else { + return $this->_validateSelfAttestation($clientDataHash); + } + } + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + if (!$this->_x5c) { + return false; + } + + $chainC = $this->_createX5cChainFile(); + if ($chainC) { + $rootCas[] = $chainC; + } + + $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); + if ($v === -1) { + throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); + } + return $v; + } + + /** + * validate if x5c is present + * @param string $clientDataHash + * @return bool + * @throws WebAuthnException + */ + protected function _validateOverX5c($clientDataHash) { + $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); + + if ($publicKey === false) { + throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); + } + + // Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash + // using the attestation public key in attestnCert with the algorithm specified in alg. + $dataToVerify = $this->_authenticatorData->getBinary(); + $dataToVerify .= $clientDataHash; + + $coseAlgorithm = $this->_getCoseAlgorithm($this->_alg); + + // check certificate + return \openssl_verify($dataToVerify, $this->_signature, $publicKey, $coseAlgorithm->openssl) === 1; + } + + /** + * validate if self attestation is in use + * @param string $clientDataHash + * @return bool + */ + protected function _validateSelfAttestation($clientDataHash) { + // Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash + // using the credential public key with alg. + $dataToVerify = $this->_authenticatorData->getBinary(); + $dataToVerify .= $clientDataHash; + + $publicKey = $this->_authenticatorData->getPublicKeyPem(); + + // check certificate + return \openssl_verify($dataToVerify, $this->_signature, $publicKey, OPENSSL_ALGO_SHA256) === 1; + } +} + diff --git a/data/web/inc/lib/WebAuthn/Attestation/Format/Tpm.php b/data/web/inc/lib/WebAuthn/Attestation/Format/Tpm.php new file mode 100644 index 0000000000..338cd45c32 --- /dev/null +++ b/data/web/inc/lib/WebAuthn/Attestation/Format/Tpm.php @@ -0,0 +1,180 @@ +_attestationObject['attStmt']; + + if (!\array_key_exists('ver', $attStmt) || $attStmt['ver'] !== '2.0') { + throw new WebAuthnException('invalid tpm version: ' . $attStmt['ver'], WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) { + throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) { + throw new WebAuthnException('signature not found', WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('certInfo', $attStmt) || !\is_object($attStmt['certInfo']) || !($attStmt['certInfo'] instanceof ByteBuffer)) { + throw new WebAuthnException('certInfo not found', WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('pubArea', $attStmt) || !\is_object($attStmt['pubArea']) || !($attStmt['pubArea'] instanceof ByteBuffer)) { + throw new WebAuthnException('pubArea not found', WebAuthnException::INVALID_DATA); + } + + $this->_alg = $attStmt['alg']; + $this->_signature = $attStmt['sig']->getBinaryString(); + $this->_certInfo = $attStmt['certInfo']; + $this->_pubArea = $attStmt['pubArea']; + + // certificate for validation + if (\array_key_exists('x5c', $attStmt) && \is_array($attStmt['x5c']) && \count($attStmt['x5c']) > 0) { + + // The attestation certificate attestnCert MUST be the first element in the array + $attestnCert = array_shift($attStmt['x5c']); + + if (!($attestnCert instanceof ByteBuffer)) { + throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); + } + + $this->_x5c = $attestnCert->getBinaryString(); + + // certificate chain + foreach ($attStmt['x5c'] as $chain) { + if ($chain instanceof ByteBuffer) { + $this->_x5c_chain[] = $chain->getBinaryString(); + } + } + + } else { + throw new WebAuthnException('no x5c certificate found', WebAuthnException::INVALID_DATA); + } + } + + + /* + * returns the key certificate in PEM format + * @return string|null + */ + public function getCertificatePem() { + if (!$this->_x5c) { + return null; + } + return $this->_createCertificatePem($this->_x5c); + } + + /** + * @param string $clientDataHash + */ + public function validateAttestation($clientDataHash) { + return $this->_validateOverX5c($clientDataHash); + } + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + if (!$this->_x5c) { + return false; + } + + $chainC = $this->_createX5cChainFile(); + if ($chainC) { + $rootCas[] = $chainC; + } + + $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); + if ($v === -1) { + throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); + } + return $v; + } + + /** + * validate if x5c is present + * @param string $clientDataHash + * @return bool + * @throws WebAuthnException + */ + protected function _validateOverX5c($clientDataHash) { + $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); + + if ($publicKey === false) { + throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); + } + + // Concatenate authenticatorData and clientDataHash to form attToBeSigned. + $attToBeSigned = $this->_authenticatorData->getBinary(); + $attToBeSigned .= $clientDataHash; + + // Validate that certInfo is valid: + + // Verify that magic is set to TPM_GENERATED_VALUE. + if ($this->_certInfo->getBytes(0, 4) !== $this->_TPM_GENERATED_VALUE) { + throw new WebAuthnException('tpm magic not TPM_GENERATED_VALUE', WebAuthnException::INVALID_DATA); + } + + // Verify that type is set to TPM_ST_ATTEST_CERTIFY. + if ($this->_certInfo->getBytes(4, 2) !== $this->_TPM_ST_ATTEST_CERTIFY) { + throw new WebAuthnException('tpm type not TPM_ST_ATTEST_CERTIFY', WebAuthnException::INVALID_DATA); + } + + $offset = 6; + $qualifiedSigner = $this->_tpmReadLengthPrefixed($this->_certInfo, $offset); + $extraData = $this->_tpmReadLengthPrefixed($this->_certInfo, $offset); + $coseAlg = $this->_getCoseAlgorithm($this->_alg); + + // Verify that extraData is set to the hash of attToBeSigned using the hash algorithm employed in "alg". + if ($extraData->getBinaryString() !== \hash($coseAlg->hash, $attToBeSigned, true)) { + throw new WebAuthnException('certInfo:extraData not hash of attToBeSigned', WebAuthnException::INVALID_DATA); + } + + // Verify the sig is a valid signature over certInfo using the attestation + // public key in aikCert with the algorithm specified in alg. + return \openssl_verify($this->_certInfo->getBinaryString(), $this->_signature, $publicKey, $coseAlg->openssl) === 1; + } + + + /** + * returns next part of ByteBuffer + * @param ByteBuffer $buffer + * @param int $offset + * @return ByteBuffer + */ + protected function _tpmReadLengthPrefixed(ByteBuffer $buffer, &$offset) { + $len = $buffer->getUint16Val($offset); + $data = $buffer->getBytes($offset + 2, $len); + $offset += (2 + $len); + + return new ByteBuffer($data); + } + +} + diff --git a/data/web/inc/lib/WebAuthn/Attestation/Format/U2f.php b/data/web/inc/lib/WebAuthn/Attestation/Format/U2f.php new file mode 100644 index 0000000000..2b51ba8b75 --- /dev/null +++ b/data/web/inc/lib/WebAuthn/Attestation/Format/U2f.php @@ -0,0 +1,93 @@ +_attestationObject['attStmt']; + + if (\array_key_exists('alg', $attStmt) && $attStmt['alg'] !== $this->_alg) { + throw new WebAuthnException('u2f only accepts algorithm -7 ("ES256"), but got ' . $attStmt['alg'], WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) { + throw new WebAuthnException('no signature found', WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('x5c', $attStmt) || !\is_array($attStmt['x5c']) || \count($attStmt['x5c']) !== 1) { + throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); + } + + if (!\is_object($attStmt['x5c'][0]) || !($attStmt['x5c'][0] instanceof ByteBuffer)) { + throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); + } + + $this->_signature = $attStmt['sig']->getBinaryString(); + $this->_x5c = $attStmt['x5c'][0]->getBinaryString(); + } + + + /* + * returns the key certificate in PEM format + * @return string + */ + public function getCertificatePem() { + $pem = '-----BEGIN CERTIFICATE-----' . "\n"; + $pem .= \chunk_split(\base64_encode($this->_x5c), 64, "\n"); + $pem .= '-----END CERTIFICATE-----' . "\n"; + return $pem; + } + + /** + * @param string $clientDataHash + */ + public function validateAttestation($clientDataHash) { + $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); + + if ($publicKey === false) { + throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); + } + + // Let verificationData be the concatenation of (0x00 || rpIdHash || clientDataHash || credentialId || publicKeyU2F) + $dataToVerify = "\x00"; + $dataToVerify .= $this->_authenticatorData->getRpIdHash(); + $dataToVerify .= $clientDataHash; + $dataToVerify .= $this->_authenticatorData->getCredentialId(); + $dataToVerify .= $this->_authenticatorData->getPublicKeyU2F(); + + $coseAlgorithm = $this->_getCoseAlgorithm($this->_alg); + + // check certificate + return \openssl_verify($dataToVerify, $this->_signature, $publicKey, $coseAlgorithm->openssl) === 1; + } + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + $chainC = $this->_createX5cChainFile(); + if ($chainC) { + $rootCas[] = $chainC; + } + + $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); + if ($v === -1) { + throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); + } + return $v; + } +} diff --git a/data/web/inc/lib/WebAuthn/Binary/ByteBuffer.php b/data/web/inc/lib/WebAuthn/Binary/ByteBuffer.php new file mode 100644 index 0000000000..6da4c4ae97 --- /dev/null +++ b/data/web/inc/lib/WebAuthn/Binary/ByteBuffer.php @@ -0,0 +1,293 @@ +_data = $binaryData; + $this->_length = \strlen($binaryData); + } + + + // ----------------------- + // PUBLIC STATIC + // ----------------------- + + /** + * create a ByteBuffer from a base64 url encoded string + * @param string $base64url + * @return ByteBuffer + */ + public static function fromBase64Url($base64url) { + $bin = self::_base64url_decode($base64url); + if ($bin === false) { + throw new WebAuthnException('ByteBuffer: Invalid base64 url string', WebAuthnException::BYTEBUFFER); + } + return new ByteBuffer($bin); + } + + /** + * create a ByteBuffer from a base64 url encoded string + * @param string $hex + * @return ByteBuffer + */ + public static function fromHex($hex) { + $bin = \hex2bin($hex); + if ($bin === false) { + throw new WebAuthnException('ByteBuffer: Invalid hex string', WebAuthnException::BYTEBUFFER); + } + return new ByteBuffer($bin); + } + + /** + * create a random ByteBuffer + * @param string $length + * @return ByteBuffer + */ + public static function randomBuffer($length) { + if (\function_exists('random_bytes')) { // >PHP 7.0 + return new ByteBuffer(\random_bytes($length)); + + } else if (\function_exists('openssl_random_pseudo_bytes')) { + return new ByteBuffer(\openssl_random_pseudo_bytes($length)); + + } else { + throw new WebAuthnException('ByteBuffer: cannot generate random bytes', WebAuthnException::BYTEBUFFER); + } + } + + // ----------------------- + // PUBLIC + // ----------------------- + + public function getBytes($offset, $length) { + if ($offset < 0 || $length < 0 || ($offset + $length > $this->_length)) { + throw new WebAuthnException('ByteBuffer: Invalid offset or length', WebAuthnException::BYTEBUFFER); + } + return \substr($this->_data, $offset, $length); + } + + public function getByteVal($offset) { + if ($offset < 0 || $offset >= $this->_length) { + throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); + } + return \ord(\substr($this->_data, $offset, 1)); + } + + public function getJson($jsonFlags=0) { + $data = \json_decode($this->getBinaryString(), null, 512, $jsonFlags); + if (\json_last_error() !== JSON_ERROR_NONE) { + throw new WebAuthnException(\json_last_error_msg(), WebAuthnException::BYTEBUFFER); + } + return $data; + } + + public function getLength() { + return $this->_length; + } + + public function getUint16Val($offset) { + if ($offset < 0 || ($offset + 2) > $this->_length) { + throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); + } + return unpack('n', $this->_data, $offset)[1]; + } + + public function getUint32Val($offset) { + if ($offset < 0 || ($offset + 4) > $this->_length) { + throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); + } + $val = unpack('N', $this->_data, $offset)[1]; + + // Signed integer overflow causes signed negative numbers + if ($val < 0) { + throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER); + } + return $val; + } + + public function getUint64Val($offset) { + if (PHP_INT_SIZE < 8) { + throw new WebAuthnException('ByteBuffer: 64-bit values not supported by this system', WebAuthnException::BYTEBUFFER); + } + if ($offset < 0 || ($offset + 8) > $this->_length) { + throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); + } + $val = unpack('J', $this->_data, $offset)[1]; + + // Signed integer overflow causes signed negative numbers + if ($val < 0) { + throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER); + } + + return $val; + } + + public function getHalfFloatVal($offset) { + //FROM spec pseudo decode_half(unsigned char *halfp) + $half = $this->getUint16Val($offset); + + $exp = ($half >> 10) & 0x1f; + $mant = $half & 0x3ff; + + if ($exp === 0) { + $val = $mant * (2 ** -24); + } elseif ($exp !== 31) { + $val = ($mant + 1024) * (2 ** ($exp - 25)); + } else { + $val = ($mant === 0) ? INF : NAN; + } + + return ($half & 0x8000) ? -$val : $val; + } + + public function getFloatVal($offset) { + if ($offset < 0 || ($offset + 4) > $this->_length) { + throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); + } + return unpack('G', $this->_data, $offset)[1]; + } + + public function getDoubleVal($offset) { + if ($offset < 0 || ($offset + 8) > $this->_length) { + throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); + } + return unpack('E', $this->_data, $offset)[1]; + } + + /** + * @return string + */ + public function getBinaryString() { + return $this->_data; + } + + /** + * @param string $buffer + * @return bool + */ + public function equals($buffer) { + return is_string($this->_data) && $this->_data === $buffer->data; + } + + /** + * @return string + */ + public function getHex() { + return \bin2hex($this->_data); + } + + /** + * @return bool + */ + public function isEmpty() { + return $this->_length === 0; + } + + + /** + * jsonSerialize interface + * return binary data in RFC 1342-Like serialized string + * @return string + */ + public function jsonSerialize() { + if (ByteBuffer::$useBase64UrlEncoding) { + return self::_base64url_encode($this->_data); + + } else { + return '=?BINARY?B?' . \base64_encode($this->_data) . '?='; + } + } + + /** + * Serializable-Interface + * @return string + */ + public function serialize() { + return \serialize($this->_data); + } + + /** + * Serializable-Interface + * @param string $serialized + */ + public function unserialize($serialized) { + $this->_data = \unserialize($serialized); + $this->_length = \strlen($this->_data); + } + + /** + * (PHP 8 deprecates Serializable-Interface) + * @return array + */ + public function __serialize() { + return [ + 'data' => \serialize($this->_data) + ]; + } + + /** + * object to string + * @return string + */ + public function __toString() { + return $this->getHex(); + } + + /** + * (PHP 8 deprecates Serializable-Interface) + * @param array $data + * @return void + */ + public function __unserialize($data) { + if ($data && isset($data['data'])) { + $this->_data = \unserialize($data['data']); + $this->_length = \strlen($this->_data); + } + } + + // ----------------------- + // PROTECTED STATIC + // ----------------------- + + /** + * base64 url decoding + * @param string $data + * @return string + */ + protected static function _base64url_decode($data) { + return \base64_decode(\strtr($data, '-_', '+/') . \str_repeat('=', 3 - (3 + \strlen($data)) % 4)); + } + + /** + * base64 url encoding + * @param string $data + * @return string + */ + protected static function _base64url_encode($data) { + return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '='); + } +} diff --git a/data/web/inc/lib/WebAuthn/CBOR/CborDecoder.php b/data/web/inc/lib/WebAuthn/CBOR/CborDecoder.php new file mode 100644 index 0000000000..e6b5427d91 --- /dev/null +++ b/data/web/inc/lib/WebAuthn/CBOR/CborDecoder.php @@ -0,0 +1,220 @@ +getLength()) { + throw new WebAuthnException('Unused bytes after data item.', WebAuthnException::CBOR); + } + return $result; + } + + /** + * @param ByteBuffer|string $bufOrBin + * @param int $startOffset + * @param int|null $endOffset + * @return mixed + */ + public static function decodeInPlace($bufOrBin, $startOffset, &$endOffset = null) { + $buf = $bufOrBin instanceof ByteBuffer ? $bufOrBin : new ByteBuffer($bufOrBin); + + $offset = $startOffset; + $data = self::_parseItem($buf, $offset); + $endOffset = $offset; + return $data; + } + + // --------------------- + // protected + // --------------------- + + /** + * @param ByteBuffer $buf + * @param int $offset + * @return mixed + */ + protected static function _parseItem(ByteBuffer $buf, &$offset) { + $first = $buf->getByteVal($offset++); + $type = $first >> 5; + $val = $first & 0b11111; + + if ($type === self::CBOR_MAJOR_FLOAT_SIMPLE) { + return self::_parseFloatSimple($val, $buf, $offset); + } + + $val = self::_parseExtraLength($val, $buf, $offset); + + return self::_parseItemData($type, $val, $buf, $offset); + } + + protected static function _parseFloatSimple($val, ByteBuffer $buf, &$offset) { + switch ($val) { + case 24: + $val = $buf->getByteVal($offset); + $offset++; + return self::_parseSimple($val); + + case 25: + $floatValue = $buf->getHalfFloatVal($offset); + $offset += 2; + return $floatValue; + + case 26: + $floatValue = $buf->getFloatVal($offset); + $offset += 4; + return $floatValue; + + case 27: + $floatValue = $buf->getDoubleVal($offset); + $offset += 8; + return $floatValue; + + case 28: + case 29: + case 30: + throw new WebAuthnException('Reserved value used.', WebAuthnException::CBOR); + + case 31: + throw new WebAuthnException('Indefinite length is not supported.', WebAuthnException::CBOR); + } + + return self::_parseSimple($val); + } + + /** + * @param int $val + * @return mixed + * @throws WebAuthnException + */ + protected static function _parseSimple($val) { + if ($val === 20) { + return false; + } + if ($val === 21) { + return true; + } + if ($val === 22) { + return null; + } + throw new WebAuthnException(sprintf('Unsupported simple value %d.', $val), WebAuthnException::CBOR); + } + + protected static function _parseExtraLength($val, ByteBuffer $buf, &$offset) { + switch ($val) { + case 24: + $val = $buf->getByteVal($offset); + $offset++; + break; + + case 25: + $val = $buf->getUint16Val($offset); + $offset += 2; + break; + + case 26: + $val = $buf->getUint32Val($offset); + $offset += 4; + break; + + case 27: + $val = $buf->getUint64Val($offset); + $offset += 8; + break; + + case 28: + case 29: + case 30: + throw new WebAuthnException('Reserved value used.', WebAuthnException::CBOR); + + case 31: + throw new WebAuthnException('Indefinite length is not supported.', WebAuthnException::CBOR); + } + + return $val; + } + + protected static function _parseItemData($type, $val, ByteBuffer $buf, &$offset) { + switch ($type) { + case self::CBOR_MAJOR_UNSIGNED_INT: // uint + return $val; + + case self::CBOR_MAJOR_NEGATIVE_INT: + return -1 - $val; + + case self::CBOR_MAJOR_BYTE_STRING: + $data = $buf->getBytes($offset, $val); + $offset += $val; + return new ByteBuffer($data); // bytes + + case self::CBOR_MAJOR_TEXT_STRING: + $data = $buf->getBytes($offset, $val); + $offset += $val; + return $data; // UTF-8 + + case self::CBOR_MAJOR_ARRAY: + return self::_parseArray($buf, $offset, $val); + + case self::CBOR_MAJOR_MAP: + return self::_parseMap($buf, $offset, $val); + + case self::CBOR_MAJOR_TAG: + return self::_parseItem($buf, $offset); // 1 embedded data item + } + + // This should never be reached + throw new WebAuthnException(sprintf('Unknown major type %d.', $type), WebAuthnException::CBOR); + } + + protected static function _parseMap(ByteBuffer $buf, &$offset, $count) { + $map = array(); + + for ($i = 0; $i < $count; $i++) { + $mapKey = self::_parseItem($buf, $offset); + $mapVal = self::_parseItem($buf, $offset); + + if (!\is_int($mapKey) && !\is_string($mapKey)) { + throw new WebAuthnException('Can only use strings or integers as map keys', WebAuthnException::CBOR); + } + + $map[$mapKey] = $mapVal; // todo dup + } + return $map; + } + + protected static function _parseArray(ByteBuffer $buf, &$offset, $count) { + $arr = array(); + for ($i = 0; $i < $count; $i++) { + $arr[] = self::_parseItem($buf, $offset); + } + + return $arr; + } +} diff --git a/data/web/inc/lib/WebAuthn/WebAuthn.php b/data/web/inc/lib/WebAuthn/WebAuthn.php new file mode 100644 index 0000000000..10596ea5b1 --- /dev/null +++ b/data/web/inc/lib/WebAuthn/WebAuthn.php @@ -0,0 +1,593 @@ +_rpName = $rpName; + $this->_rpId = $rpId; + $this->_rpIdHash = \hash('sha256', $rpId, true); + ByteBuffer::$useBase64UrlEncoding = !!$useBase64UrlEncoding; + $supportedFormats = array('android-key', 'android-safetynet', 'apple', 'fido-u2f', 'none', 'packed', 'tpm'); + + if (!\function_exists('\openssl_open')) { + throw new WebAuthnException('OpenSSL-Module not installed');; + } + + if (!\in_array('SHA256', \array_map('\strtoupper', \openssl_get_md_methods()))) { + throw new WebAuthnException('SHA256 not supported by this openssl installation.'); + } + + // default: all format + if (!is_array($allowedFormats)) { + $allowedFormats = $supportedFormats; + } + $this->_formats = $allowedFormats; + + // validate formats + $invalidFormats = \array_diff($this->_formats, $supportedFormats); + if (!$this->_formats || $invalidFormats) { + throw new WebAuthnException('invalid formats on construct: ' . implode(', ', $invalidFormats)); + } + } + + /** + * add a root certificate to verify new registrations + * @param string $path file path of / directory with root certificates + * @param array|null $certFileExtensions if adding a direction, all files with provided extension are added. default: pem, crt, cer, der + */ + public function addRootCertificates($path, $certFileExtensions=null) { + if (!\is_array($this->_caFiles)) { + $this->_caFiles = array(); + } + if ($certFileExtensions === null) { + $certFileExtensions = array('pem', 'crt', 'cer', 'der'); + } + $path = \rtrim(\trim($path), '\\/'); + if (\is_dir($path)) { + foreach (\scandir($path) as $ca) { + if (\is_file($path . DIRECTORY_SEPARATOR . $ca) && \in_array(\strtolower(\pathinfo($ca, PATHINFO_EXTENSION)), $certFileExtensions)) { + $this->addRootCertificates($path . DIRECTORY_SEPARATOR . $ca); + } + } + } else if (\is_file($path) && !\in_array(\realpath($path), $this->_caFiles)) { + $this->_caFiles[] = \realpath($path); + } + } + + /** + * Returns the generated challenge to save for later validation + * @return ByteBuffer + */ + public function getChallenge() { + return $this->_challenge; + } + + /** + * generates the object for a key registration + * provide this data to navigator.credentials.create + * @param string $userId + * @param string $userName + * @param string $userDisplayName + * @param int $timeout timeout in seconds + * @param bool $requireResidentKey true, if the key should be stored by the authentication device + * @param bool|string $requireUserVerification indicates that you require user verification and will fail the operation + * if the response does not have the UV flag set. + * Valid values: + * true = required + * false = preferred + * string 'required' 'preferred' 'discouraged' + * @param bool|null $crossPlatformAttachment true for cross-platform devices (eg. fido usb), + * false for platform devices (eg. windows hello, android safetynet), + * null for both + * @param array $excludeCredentialIds a array of ids, which are already registered, to prevent re-registration + * @return \stdClass + */ + public function getCreateArgs($userId, $userName, $userDisplayName, $timeout=20, $requireResidentKey=false, $requireUserVerification=false, $crossPlatformAttachment=null, $excludeCredentialIds=array()) { + + // validate User Verification Requirement + if (\is_bool($requireUserVerification)) { + $requireUserVerification = $requireUserVerification ? 'required' : 'preferred'; + } else if (\is_string($requireUserVerification) && \in_array(\strtolower($requireUserVerification), ['required', 'preferred', 'discouraged'])) { + $requireUserVerification = \strtolower($requireUserVerification); + } else { + $requireUserVerification = 'preferred'; + } + + $args = new \stdClass(); + $args->publicKey = new \stdClass(); + + // relying party + $args->publicKey->rp = new \stdClass(); + $args->publicKey->rp->name = $this->_rpName; + $args->publicKey->rp->id = $this->_rpId; + + $args->publicKey->authenticatorSelection = new \stdClass(); + $args->publicKey->authenticatorSelection->userVerification = $requireUserVerification; + if ($requireResidentKey) { + $args->publicKey->authenticatorSelection->requireResidentKey = true; + } + if (is_bool($crossPlatformAttachment)) { + $args->publicKey->authenticatorSelection->authenticatorAttachment = $crossPlatformAttachment ? 'cross-platform' : 'platform'; + } + + // user + $args->publicKey->user = new \stdClass(); + $args->publicKey->user->id = new ByteBuffer($userId); // binary + $args->publicKey->user->name = $userName; + $args->publicKey->user->displayName = $userDisplayName; + + $args->publicKey->pubKeyCredParams = array(); + $tmp = new \stdClass(); + $tmp->type = 'public-key'; + $tmp->alg = -7; // ES256 + $args->publicKey->pubKeyCredParams[] = $tmp; + unset ($tmp); + + $tmp = new \stdClass(); + $tmp->type = 'public-key'; + $tmp->alg = -257; // RS256 + $args->publicKey->pubKeyCredParams[] = $tmp; + unset ($tmp); + + // if there are root certificates added, we need direct attestation to validate + // against the root certificate. If there are no root-certificates added, + // anonymization ca are also accepted, because we can't validate the root anyway. + $attestation = 'indirect'; + if (\is_array($this->_caFiles)) { + $attestation = 'direct'; + } + + $args->publicKey->attestation = \count($this->_formats) === 1 && \in_array('none', $this->_formats) ? 'none' : $attestation; + $args->publicKey->extensions = new \stdClass(); + $args->publicKey->extensions->exts = true; + $args->publicKey->timeout = $timeout * 1000; // microseconds + $args->publicKey->challenge = $this->_createChallenge(); // binary + + //prevent re-registration by specifying existing credentials + $args->publicKey->excludeCredentials = array(); + + if (is_array($excludeCredentialIds)) { + foreach ($excludeCredentialIds as $id) { + $tmp = new \stdClass(); + $tmp->id = $id instanceof ByteBuffer ? $id : new ByteBuffer($id); // binary + $tmp->type = 'public-key'; + $tmp->transports = array('usb', 'ble', 'nfc', 'internal'); + $args->publicKey->excludeCredentials[] = $tmp; + unset ($tmp); + } + } + + return $args; + } + + /** + * generates the object for key validation + * Provide this data to navigator.credentials.get + * @param array $credentialIds binary + * @param int $timeout timeout in seconds + * @param bool $allowUsb allow removable USB + * @param bool $allowNfc allow Near Field Communication (NFC) + * @param bool $allowBle allow Bluetooth + * @param bool $allowInternal allow client device-specific transport. These authenticators are not removable from the client device. + * @param bool|string $requireUserVerification indicates that you require user verification and will fail the operation + * if the response does not have the UV flag set. + * Valid values: + * true = required + * false = preferred + * string 'required' 'preferred' 'discouraged' + * @return \stdClass + */ + public function getGetArgs($credentialIds=array(), $timeout=20, $allowUsb=true, $allowNfc=true, $allowBle=true, $allowInternal=true, $requireUserVerification=false) { + + // validate User Verification Requirement + if (\is_bool($requireUserVerification)) { + $requireUserVerification = $requireUserVerification ? 'required' : 'preferred'; + } else if (\is_string($requireUserVerification) && \in_array(\strtolower($requireUserVerification), ['required', 'preferred', 'discouraged'])) { + $requireUserVerification = \strtolower($requireUserVerification); + } else { + $requireUserVerification = 'preferred'; + } + + $args = new \stdClass(); + $args->publicKey = new \stdClass(); + $args->publicKey->timeout = $timeout * 1000; // microseconds + $args->publicKey->challenge = $this->_createChallenge(); // binary + $args->publicKey->userVerification = $requireUserVerification; + $args->publicKey->rpId = $this->_rpId; + + if (\is_array($credentialIds) && \count($credentialIds) > 0) { + $args->publicKey->allowCredentials = array(); + + foreach ($credentialIds as $id) { + $tmp = new \stdClass(); + $tmp->id = $id instanceof ByteBuffer ? $id : new ByteBuffer($id); // binary + $tmp->transports = array(); + + if ($allowUsb) { + $tmp->transports[] = 'usb'; + } + if ($allowNfc) { + $tmp->transports[] = 'nfc'; + } + if ($allowBle) { + $tmp->transports[] = 'ble'; + } + if ($allowInternal) { + $tmp->transports[] = 'internal'; + } + + $tmp->type = 'public-key'; + $args->publicKey->allowCredentials[] = $tmp; + unset ($tmp); + } + } + + return $args; + } + + /** + * returns the new signature counter value. + * returns null if there is no counter + * @return ?int + */ + public function getSignatureCounter() { + return \is_int($this->_signatureCounter) ? $this->_signatureCounter : null; + } + + /** + * process a create request and returns data to save for future logins + * @param string $clientDataJSON binary from browser + * @param string $attestationObject binary from browser + * @param string|ByteBuffer $challenge binary used challange + * @param bool $requireUserVerification true, if the device must verify user (e.g. by biometric data or pin) + * @param bool $requireUserPresent false, if the device must NOT check user presence (e.g. by pressing a button) + * @param bool $failIfRootMismatch false, if there should be no error thrown if root certificate doesn't match + * @return \stdClass + * @throws WebAuthnException + */ + public function processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true) { + $clientDataHash = \hash('sha256', $clientDataJSON, true); + $clientData = \json_decode($clientDataJSON); + $challenge = $challenge instanceof ByteBuffer ? $challenge : new ByteBuffer($challenge); + + // security: https://www.w3.org/TR/webauthn/#registering-a-new-credential + + // 2. Let C, the client data claimed as collected during the credential creation, + // be the result of running an implementation-specific JSON parser on JSONtext. + if (!\is_object($clientData)) { + throw new WebAuthnException('invalid client data', WebAuthnException::INVALID_DATA); + } + + // 3. Verify that the value of C.type is webauthn.create. + if (!\property_exists($clientData, 'type') || $clientData->type !== 'webauthn.create') { + throw new WebAuthnException('invalid type', WebAuthnException::INVALID_TYPE); + } + + // 4. Verify that the value of C.challenge matches the challenge that was sent to the authenticator in the create() call. + if (!\property_exists($clientData, 'challenge') || ByteBuffer::fromBase64Url($clientData->challenge)->getBinaryString() !== $challenge->getBinaryString()) { + throw new WebAuthnException('invalid challenge', WebAuthnException::INVALID_CHALLENGE); + } + + // 5. Verify that the value of C.origin matches the Relying Party's origin. + if (!\property_exists($clientData, 'origin') || !$this->_checkOrigin($clientData->origin)) { + throw new WebAuthnException('invalid origin', WebAuthnException::INVALID_ORIGIN); + } + + // Attestation + $attestationObject = new Attestation\AttestationObject($attestationObject, $this->_formats); + + // 9. Verify that the RP ID hash in authData is indeed the SHA-256 hash of the RP ID expected by the RP. + if (!$attestationObject->validateRpIdHash($this->_rpIdHash)) { + throw new WebAuthnException('invalid rpId hash', WebAuthnException::INVALID_RELYING_PARTY); + } + + // 14. Verify that attStmt is a correct attestation statement, conveying a valid attestation signature + if (!$attestationObject->validateAttestation($clientDataHash)) { + throw new WebAuthnException('invalid certificate signature', WebAuthnException::INVALID_SIGNATURE); + } + + // 15. If validation is successful, obtain a list of acceptable trust anchors + $rootValid = is_array($this->_caFiles) ? $attestationObject->validateRootCertificate($this->_caFiles) : null; + if ($failIfRootMismatch && is_array($this->_caFiles) && !$rootValid) { + throw new WebAuthnException('invalid root certificate', WebAuthnException::CERTIFICATE_NOT_TRUSTED); + } + + // 10. Verify that the User Present bit of the flags in authData is set. + $userPresent = $attestationObject->getAuthenticatorData()->getUserPresent(); + if ($requireUserPresent && !$userPresent) { + throw new WebAuthnException('user not present during authentication', WebAuthnException::USER_PRESENT); + } + + // 11. If user verification is required for this registration, verify that the User Verified bit of the flags in authData is set. + $userVerified = $attestationObject->getAuthenticatorData()->getUserVerified(); + if ($requireUserVerification && !$userVerified) { + throw new WebAuthnException('user not verified during authentication', WebAuthnException::USER_VERIFICATED); + } + + $signCount = $attestationObject->getAuthenticatorData()->getSignCount(); + if ($signCount > 0) { + $this->_signatureCounter = $signCount; + } + + // prepare data to store for future logins + $data = new \stdClass(); + $data->rpId = $this->_rpId; + $data->attestationFormat = $attestationObject->getAttestationFormatName(); + $data->credentialId = $attestationObject->getAuthenticatorData()->getCredentialId(); + $data->credentialPublicKey = $attestationObject->getAuthenticatorData()->getPublicKeyPem(); + $data->certificateChain = $attestationObject->getCertificateChain(); + $data->certificate = $attestationObject->getCertificatePem(); + $data->certificateIssuer = $attestationObject->getCertificateIssuer(); + $data->certificateSubject = $attestationObject->getCertificateSubject(); + $data->signatureCounter = $this->_signatureCounter; + $data->AAGUID = $attestationObject->getAuthenticatorData()->getAAGUID(); + $data->rootValid = $rootValid; + $data->userPresent = $userPresent; + $data->userVerified = $userVerified; + return $data; + } + + + /** + * process a get request + * @param string $clientDataJSON binary from browser + * @param string $authenticatorData binary from browser + * @param string $signature binary from browser + * @param string $credentialPublicKey string PEM-formated public key from used credentialId + * @param string|ByteBuffer $challenge binary from used challange + * @param int $prevSignatureCnt signature count value of the last login + * @param bool $requireUserVerification true, if the device must verify user (e.g. by biometric data or pin) + * @param bool $requireUserPresent true, if the device must check user presence (e.g. by pressing a button) + * @return boolean true if get is successful + * @throws WebAuthnException + */ + public function processGet($clientDataJSON, $authenticatorData, $signature, $credentialPublicKey, $challenge, $prevSignatureCnt=null, $requireUserVerification=false, $requireUserPresent=true) { + $authenticatorObj = new Attestation\AuthenticatorData($authenticatorData); + $clientDataHash = \hash('sha256', $clientDataJSON, true); + $clientData = \json_decode($clientDataJSON); + $challenge = $challenge instanceof ByteBuffer ? $challenge : new ByteBuffer($challenge); + + // https://www.w3.org/TR/webauthn/#verifying-assertion + + // 1. If the allowCredentials option was given when this authentication ceremony was initiated, + // verify that credential.id identifies one of the public key credentials that were listed in allowCredentials. + // -> TO BE VERIFIED BY IMPLEMENTATION + + // 2. If credential.response.userHandle is present, verify that the user identified + // by this value is the owner of the public key credential identified by credential.id. + // -> TO BE VERIFIED BY IMPLEMENTATION + + // 3. Using credentialโ€™s id attribute (or the corresponding rawId, if base64url encoding is + // inappropriate for your use case), look up the corresponding credential public key. + // -> TO BE LOOKED UP BY IMPLEMENTATION + + // 5. Let JSONtext be the result of running UTF-8 decode on the value of cData. + if (!\is_object($clientData)) { + throw new WebAuthnException('invalid client data', WebAuthnException::INVALID_DATA); + } + + // 7. Verify that the value of C.type is the string webauthn.get. + if (!\property_exists($clientData, 'type') || $clientData->type !== 'webauthn.get') { + throw new WebAuthnException('invalid type', WebAuthnException::INVALID_TYPE); + } + + // 8. Verify that the value of C.challenge matches the challenge that was sent to the + // authenticator in the PublicKeyCredentialRequestOptions passed to the get() call. + if (!\property_exists($clientData, 'challenge') || ByteBuffer::fromBase64Url($clientData->challenge)->getBinaryString() !== $challenge->getBinaryString()) { + throw new WebAuthnException('invalid challenge', WebAuthnException::INVALID_CHALLENGE); + } + + // 9. Verify that the value of C.origin matches the Relying Party's origin. + if (!\property_exists($clientData, 'origin') || !$this->_checkOrigin($clientData->origin)) { + throw new WebAuthnException('invalid origin', WebAuthnException::INVALID_ORIGIN); + } + + // 11. Verify that the rpIdHash in authData is the SHA-256 hash of the RP ID expected by the Relying Party. + if ($authenticatorObj->getRpIdHash() !== $this->_rpIdHash) { + throw new WebAuthnException('invalid rpId hash', WebAuthnException::INVALID_RELYING_PARTY); + } + + // 12. Verify that the User Present bit of the flags in authData is set + if ($requireUserPresent && !$authenticatorObj->getUserPresent()) { + throw new WebAuthnException('user not present during authentication', WebAuthnException::USER_PRESENT); + } + + // 13. If user verification is required for this assertion, verify that the User Verified bit of the flags in authData is set. + if ($requireUserVerification && !$authenticatorObj->getUserVerified()) { + throw new WebAuthnException('user not verificated during authentication', WebAuthnException::USER_VERIFICATED); + } + + // 14. Verify the values of the client extension outputs + // (extensions not implemented) + + // 16. Using the credential public key looked up in step 3, verify that sig is a valid signature + // over the binary concatenation of authData and hash. + $dataToVerify = ''; + $dataToVerify .= $authenticatorData; + $dataToVerify .= $clientDataHash; + + $publicKey = \openssl_pkey_get_public($credentialPublicKey); + if ($publicKey === false) { + throw new WebAuthnException('public key invalid', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if (\openssl_verify($dataToVerify, $signature, $publicKey, OPENSSL_ALGO_SHA256) !== 1) { + throw new WebAuthnException('invalid signature', WebAuthnException::INVALID_SIGNATURE); + } + + // 17. If the signature counter value authData.signCount is nonzero, + // if less than or equal to the signature counter value stored, + // is a signal that the authenticator may be cloned + $signatureCounter = $authenticatorObj->getSignCount(); + if ($signatureCounter > 0) { + $this->_signatureCounter = $signatureCounter; + if ($prevSignatureCnt !== null && $prevSignatureCnt >= $signatureCounter) { + throw new WebAuthnException('signature counter not valid', WebAuthnException::SIGNATURE_COUNTER); + } + } + + return true; + } + + /** + * Downloads root certificates from FIDO Alliance Metadata Service (MDS) to a specific folder + * https://fidoalliance.org/metadata/ + * @param string $certFolder Folder path to save the certificates in PEM format. + * @param bool $deleteCerts=true + * @return int number of cetificates + * @throws WebAuthnException + */ + public function queryFidoMetaDataService($certFolder, $deleteCerts=true) { + $url = 'https://mds.fidoalliance.org/'; + $raw = null; + if (\function_exists('curl_init')) { + $ch = \curl_init($url); + \curl_setopt($ch, CURLOPT_HEADER, false); + \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + \curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + \curl_setopt($ch, CURLOPT_USERAGENT, 'github.com/lbuchs/WebAuthn - A simple PHP WebAuthn server library'); + $raw = \curl_exec($ch); + \curl_close($ch); + } else { + $raw = \file_get_contents($url); + } + + $certFolder = \rtrim(\realpath($certFolder), '\\/'); + if (!is_dir($certFolder)) { + throw new WebAuthnException('Invalid folder path for query FIDO Alliance Metadata Service'); + } + + if (!\is_string($raw)) { + throw new WebAuthnException('Unable to query FIDO Alliance Metadata Service'); + } + + $jwt = \explode('.', $raw); + if (\count($jwt) !== 3) { + throw new WebAuthnException('Invalid JWT from FIDO Alliance Metadata Service'); + } + + if ($deleteCerts) { + foreach (\scandir($certFolder) as $ca) { + if (\substr($ca, -4) === '.pem') { + if (\unlink($certFolder . DIRECTORY_SEPARATOR . $ca) === false) { + throw new WebAuthnException('Cannot delete certs in folder for FIDO Alliance Metadata Service'); + } + } + } + } + + list($header, $payload, $hash) = $jwt; + $payload = Binary\ByteBuffer::fromBase64Url($payload)->getJson(); + + $count = 0; + if (\is_object($payload) && \property_exists($payload, 'entries') && \is_array($payload->entries)) { + foreach ($payload->entries as $entry) { + if (\is_object($entry) && \property_exists($entry, 'metadataStatement') && \is_object($entry->metadataStatement)) { + $description = $entry->metadataStatement->description ?? null; + $attestationRootCertificates = $entry->metadataStatement->attestationRootCertificates ?? null; + + if ($description && $attestationRootCertificates) { + + // create filename + $certFilename = \preg_replace('/[^a-z0-9]/i', '_', $description); + $certFilename = \trim(\preg_replace('/\_{2,}/i', '_', $certFilename),'_') . '.pem'; + $certFilename = \strtolower($certFilename); + + // add certificate + $certContent = $description . "\n"; + $certContent .= \str_repeat('-', \mb_strlen($description)) . "\n"; + + foreach ($attestationRootCertificates as $attestationRootCertificate) { + $count++; + $certContent .= "\n-----BEGIN CERTIFICATE-----\n"; + $certContent .= \chunk_split(\trim($attestationRootCertificate), 64, "\n"); + $certContent .= "-----END CERTIFICATE-----\n"; + } + + if (\file_put_contents($certFolder . DIRECTORY_SEPARATOR . $certFilename, $certContent) === false) { + throw new WebAuthnException('unable to save certificate from FIDO Alliance Metadata Service'); + } + } + } + } + } + + return $count; + } + + // ----------------------------------------------- + // PRIVATE + // ----------------------------------------------- + + /** + * checks if the origin matchs the RP ID + * @param string $origin + * @return boolean + * @throws WebAuthnException + */ + private function _checkOrigin($origin) { + // https://www.w3.org/TR/webauthn/#rp-id + + // The origin's scheme must be https + if ($this->_rpId !== 'localhost' && \parse_url($origin, PHP_URL_SCHEME) !== 'https') { + return false; + } + + // extract host from origin + $host = \parse_url($origin, PHP_URL_HOST); + $host = \trim($host, '.'); + + // The RP ID must be equal to the origin's effective domain, or a registrable + // domain suffix of the origin's effective domain. + return \preg_match('/' . \preg_quote($this->_rpId) . '$/i', $host) === 1; + } + + /** + * generates a new challange + * @param int $length + * @return string + * @throws WebAuthnException + */ + private function _createChallenge($length = 32) { + if (!$this->_challenge) { + $this->_challenge = ByteBuffer::randomBuffer($length); + } + return $this->_challenge; + } +} diff --git a/data/web/inc/lib/WebAuthn/WebAuthnException.php b/data/web/inc/lib/WebAuthn/WebAuthnException.php new file mode 100644 index 0000000000..e399bd3b07 --- /dev/null +++ b/data/web/inc/lib/WebAuthn/WebAuthnException.php @@ -0,0 +1,27 @@ +, Olov Danielson - * @copyright 2007-2015 Yubico AB - * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @copyright 2007-2020 Yubico AB + * @license https://opensource.org/licenses/bsd-license.php New BSD License * @version 2.0 - * @link http://www.yubico.com/ + * @link https://www.yubico.com/ */ require_once 'PEAR.php'; @@ -50,12 +50,6 @@ class Auth_Yubico */ var $_key; - /** - * URL part of validation server - * @var string - */ - var $_url; - /** * List with URL part of validation servers * @var array @@ -81,10 +75,10 @@ class Auth_Yubico var $_response; /** - * Flag whether to use https or not. - * @var boolean + * Number of times we retried in our last validation + * @var int */ - var $_https; + var $_retries; /** * Flag whether to verify HTTPS server certificates or not. @@ -92,74 +86,56 @@ class Auth_Yubico */ var $_httpsverify; + /** + * Maximum number of times we will retry transient HTTP errors + * @var int + */ + var $_max_retries; + /** * Constructor * * Sets up the object * @param string $id The client identity * @param string $key The client MAC key (optional) - * @param boolean $https Flag whether to use https (optional) + * @param boolean $https noop * @param boolean $httpsverify Flag whether to use verify HTTPS * server certificates (optional, * default true) * @access public */ - function __construct($id, $key = '', $https = 0, $httpsverify = 1) + public function __construct($id, $key = '', $https = 0, $httpsverify = 1, $max_retries = 3) { $this->_id = $id; $this->_key = base64_decode($key); - $this->_https = $https; $this->_httpsverify = $httpsverify; - } - - function Auth_Yubico($id, $key = '', $https = 0, $httpsverify = 1) - { - self::__construct(); + $this->_max_retries = $max_retries; } /** * Specify to use a different URL part for verification. - * The default is "api.yubico.com/wsapi/verify". + * The default is "https://api.yubico.com/wsapi/2.0/verify". * * @param string $url New server URL part to use * @access public + * @deprecated */ function setURLpart($url) { - $this->_url = $url; + $this->_url_list = array($url); } - /** - * Get URL part to use for validation. - * - * @return string Server URL part - * @access public - */ - function getURLpart() - { - if ($this->_url) { - return $this->_url; - } else { - return "api.yubico.com/wsapi/verify"; - } - } - - /** * Get next URL part from list to use for validation. * - * @return mixed string with URL part of false if no more URLs in list + * @return mixed string with URL part or false if no more URLs in list * @access public */ function getNextURLpart() { if ($this->_url_list) $url_list=$this->_url_list; - else $url_list=array('api.yubico.com/wsapi/2.0/verify', - 'api2.yubico.com/wsapi/2.0/verify', - 'api3.yubico.com/wsapi/2.0/verify', - 'api4.yubico.com/wsapi/2.0/verify', - 'api5.yubico.com/wsapi/2.0/verify'); - + else $url_list=array('https://api.yubico.com/wsapi/2.0/verify'); + if ($this->_url_index>=count($url_list)) return false; else return $url_list[$this->_url_index++]; } @@ -206,6 +182,17 @@ function getLastResponse() return $this->_response; } + /** + * Return the number of retries that were used in the last validation + * + * @return int Number of retries + * @access public + */ + function getRetries() + { + return $this->_retries; + } + /** * Parse input string into password, yubikey prefix, * ciphertext, and OTP. @@ -266,6 +253,26 @@ function getParameters($parameters) return $param_array; } + function _make_curl_handle($query, $timeout=null) + { + flush(); + $handle = curl_init($query); + curl_setopt($handle, CURLOPT_USERAGENT, "PEAR Auth_Yubico"); + curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1); + if (!$this->_httpsverify) { + curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, 0); + curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, 0); + } + curl_setopt($handle, CURLOPT_FAILONERROR, true); + /* If timeout is set, we better apply it here as well + * in case the validation server fails to follow it. */ + if ($timeout) { + curl_setopt($handle, CURLOPT_TIMEOUT, $timeout); + } + + return $handle; + } + /** * Verify Yubico OTP against multiple URLs * Protocol specification 2.0 is used to construct validation requests @@ -280,12 +287,19 @@ function getParameters($parameters) * and 100 or "fast" or "secure". * @param int $timeout Max number of seconds to wait * for responses + * @param int $max_retries Max number of times we will retry on + * transient errors. * @return mixed PEAR error on error, true otherwise * @access public */ function verify($token, $use_timestamp=null, $wait_for_all=False, - $sl=null, $timeout=null) + $sl=null, $timeout=null, $max_retries=null) { + /* If maximum retries is not set, default from instance */ + if (is_null($max_retries)) { + $max_retries = $this->_max_retries; + } + /* Construct parameters string */ $ret = $this->parsePasswordOTP($token); if (!$ret) { @@ -312,38 +326,25 @@ function verify($token, $use_timestamp=null, $wait_for_all=False, } /* Generate and prepare request. */ - $this->_lastquery=null; + $this->_lastquery = null; + $this->_retries = 0; $this->URLreset(); + $mh = curl_multi_init(); $ch = array(); + $retries = array(); while($URLpart=$this->getNextURLpart()) { - /* Support https. */ - if ($this->_https) { - $query = "https://"; - } else { - $query = "http://"; - } - $query .= $URLpart . "?" . $parameters; + $query = $URLpart . "?" . $parameters; if ($this->_lastquery) { $this->_lastquery .= " "; } $this->_lastquery .= $query; - $handle = curl_init($query); - curl_setopt($handle, CURLOPT_USERAGENT, "PEAR Auth_Yubico"); - curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1); - if (!$this->_httpsverify) { - curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, 0); - curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, 0); - } - curl_setopt($handle, CURLOPT_FAILONERROR, true); - /* If timeout is set, we better apply it here as well - in case the validation server fails to follow it. - */ - if ($timeout) curl_setopt($handle, CURLOPT_TIMEOUT, $timeout); + $handle = $this->_make_curl_handle($query, $timeout); curl_multi_add_handle($mh, $handle); $ch[(int)$handle] = $handle; + $retries[$query] = 0; } /* Execute and read request. */ @@ -353,19 +354,20 @@ function verify($token, $use_timestamp=null, $wait_for_all=False, do { /* Let curl do its work. */ while (($mrc = curl_multi_exec($mh, $active)) - == CURLM_CALL_MULTI_PERFORM) - ; + == CURLM_CALL_MULTI_PERFORM) { + curl_multi_select($mh); + } while ($info = curl_multi_info_read($mh)) { + $cinfo = curl_getinfo ($info['handle']); if ($info['result'] == CURLE_OK) { - /* We have a complete response from one server. */ $str = curl_multi_getcontent($info['handle']); - $cinfo = curl_getinfo ($info['handle']); if ($wait_for_all) { # Better debug info - $this->_response .= 'URL=' . $cinfo['url'] ."\n" + $this->_response .= 'URL=' . $cinfo['url'] . ' HTTP_CODE=' + . $cinfo['http_code'] . "\n" . $str . "\n"; } @@ -392,7 +394,7 @@ function verify($token, $use_timestamp=null, $wait_for_all=False, /* Case 2. Verify signature first */ $rows = explode("\r\n", trim($str)); $response=array(); - while (list($key, $val) = each($rows)) { + foreach ($rows as $key => $val) { /* = is also used in BASE64 encoding so we only replace the first = by # which is not used in BASE64 */ $val = preg_replace('/=/', '#', $val, 1); $row = explode("#", $val); @@ -435,7 +437,7 @@ function verify($token, $use_timestamp=null, $wait_for_all=False, } } } - if (!$wait_for_all && ($valid || $replay)) + if (!$wait_for_all && ($valid || $replay)) { /* We have status=OK or status=REPLAYED_OTP, return. */ foreach ($ch as $h) { @@ -447,12 +449,35 @@ function verify($token, $use_timestamp=null, $wait_for_all=False, if ($valid) return true; return PEAR::raiseError($status); } - - curl_multi_remove_handle($mh, $info['handle']); - curl_close($info['handle']); - unset ($ch[(int)$info['handle']]); + } else { + /* Some kind of error, but def. not a 200 response */ + /* No status= in response body */ + $http_status_code = (int)$cinfo['http_code']; + $query = $cinfo['url']; + if ($http_status_code == 400 || + ($http_status_code >= 500 && $http_status_code < 600)) { + /* maybe retry */ + if ($retries[$query] < $max_retries) { + $retries[$query]++; // for this server + $this->_retries++; // for this validation attempt + + $newhandle = $this->_make_curl_handle($query, $timeout); + + curl_multi_add_handle($mh, $newhandle); + $ch[(int)$newhandle] = $newhandle; + + // Loop back up to curl_multi_exec, even if this + // was the last handle and curl_multi_exec _was_ + // no longer active, it's active again now we've + // added a retry. + $active = true; + } + } } - curl_multi_select($mh); + /* Done with this handle */ + curl_multi_remove_handle($mh, $info['handle']); + curl_close($info['handle']); + unset ($ch[(int)$info['handle']]); } } while ($active); diff --git a/data/web/inc/lib/array_merge_real.php b/data/web/inc/lib/array_merge_real.php new file mode 100644 index 0000000000..e2a1fbc93a --- /dev/null +++ b/data/web/inc/lib/array_merge_real.php @@ -0,0 +1,14 @@ + $value) { + $output[$key] = isset($output[$key]) ? + array_merge($output[$key], $value) : $value; + } + } + + return $output; +} diff --git a/data/web/inc/lib/composer.json b/data/web/inc/lib/composer.json index 6107bac735..3998065cb4 100644 --- a/data/web/inc/lib/composer.json +++ b/data/web/inc/lib/composer.json @@ -1,9 +1,17 @@ { "require": { "robthree/twofactorauth": "^1.6", - "yubico/u2flib-server": "^1.0", - "phpmailer/phpmailer": "^5.2", - "php-mime-mail-parser/php-mime-mail-parser": "^2.9", - "soundasleep/html2text": "^0.5.0" + "phpmailer/phpmailer": "^6.1", + "php-mime-mail-parser/php-mime-mail-parser": "^7", + "soundasleep/html2text": "^0.5.0", + "ddeboer/imap": "^1.5", + "matthiasmullie/minify": "^1.3", + "bshaffer/oauth2-server-php": "^1.11", + "mustangostang/spyc": "^0.6.3", + "directorytree/ldaprecord": "^3.3", + "twig/twig": "^3.0", + "stevenmaguire/oauth2-keycloak": "^4.0", + "league/oauth2-client": "^2.7", + "bacon/bacon-qr-code": "^2.0" } } diff --git a/data/web/inc/lib/composer.lock b/data/web/inc/lib/composer.lock index 841f2e9332..4254deffb1 100644 --- a/data/web/inc/lib/composer.lock +++ b/data/web/inc/lib/composer.lock @@ -4,40 +4,153 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3edeec2e3fa875d4f9d5e7f22a8179be", + "content-hash": "50fb4a320500820e36f30eabc45222a0", "packages": [ { - "name": "php-mime-mail-parser/php-mime-mail-parser", - "version": "2.11.1", + "name": "bacon/bacon-qr-code", + "version": "2.0.8", "source": { "type": "git", - "url": "https://github.com/php-mime-mail-parser/php-mime-mail-parser.git", - "reference": "4769e942ed0dbbdd7882fc390b119d625463c8af" + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/4769e942ed0dbbdd7882fc390b119d625463c8af", - "reference": "4769e942ed0dbbdd7882fc390b119d625463c8af", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22", "shasum": "" }, "require": { - "ext-mailparse": "*", - "php": "^5.4.0 || ^7.0" + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^7.1 || ^8.0" }, - "replace": { - "exorus/php-mime-mail-parser": "*", - "messaged/php-mime-mail-parser": "*" + "require-dev": { + "phly/keep-a-changelog": "^2.1", + "phpunit/phpunit": "^7 | ^8 | ^9", + "spatie/phpunit-snapshot-assertions": "^4.2.9", + "squizlabs/php_codesniffer": "^3.4" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8" + }, + "time": "2022-12-07T17:46:57+00:00" + }, + { + "name": "bshaffer/oauth2-server-php", + "version": "v1.11.1", + "source": { + "type": "git", + "url": "https://github.com/bshaffer/oauth2-server-php.git", + "reference": "5a0c8000d4763b276919e2106f54eddda6bc50fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bshaffer/oauth2-server-php/zipball/5a0c8000d4763b276919e2106f54eddda6bc50fa", + "reference": "5a0c8000d4763b276919e2106f54eddda6bc50fa", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "aws/aws-sdk-php": "~2.8", + "firebase/php-jwt": "~2.2", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^4.0", + "predis/predis": "dev-master", + "thobbs/phpcassa": "dev-master" + }, + "suggest": { + "aws/aws-sdk-php": "~2.8 is required to use DynamoDB storage", + "firebase/php-jwt": "~2.2 is required to use JWT features", + "mongodb/mongodb": "^1.1 is required to use MongoDB storage", + "predis/predis": "Required to use Redis storage", + "thobbs/phpcassa": "Required to use Cassandra storage" + }, + "type": "library", + "autoload": { + "psr-0": { + "OAuth2": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Shaffer", + "email": "bshafs@gmail.com", + "homepage": "http://brentertainment.com" + } + ], + "description": "OAuth2 Server for PHP", + "homepage": "http://github.com/bshaffer/oauth2-server-php", + "keywords": [ + "auth", + "oauth", + "oauth2" + ], + "support": { + "issues": "https://github.com/bshaffer/oauth2-server-php/issues", + "source": "https://github.com/bshaffer/oauth2-server-php/tree/master" + }, + "time": "2018-12-04T00:29:32+00:00" + }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" }, "require-dev": { - "phpunit/php-token-stream": "^1.3.0", - "phpunit/phpunit": "^4.0 || ^5.0", - "satooshi/php-coveralls": "0.*", - "squizlabs/php_codesniffer": "2.*" + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" }, "type": "library", "autoload": { "psr-4": { - "PhpMimeMailParser\\": "src/" + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" } }, "notification-url": "https://packagist.org/downloads/", @@ -46,147 +159,199 @@ ], "authors": [ { - "name": "bucabay", - "email": "gabe@fijiwebdesign.com", - "homepage": "http://www.fijiwebdesign.com", - "role": "Developer" - }, + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ { - "name": "eXorus", - "email": "exorus.spam@gmail.com", - "homepage": "https://github.com/eXorus/", - "role": "Developer" + "url": "https://github.com/kylekatarnls", + "type": "github" }, { - "name": "M.Valinskis", - "email": "M.Valins@gmail.com", - "homepage": "https://code.google.com/p/php-mime-mail-parser", - "role": "Developer" + "url": "https://opencollective.com/Carbon", + "type": "open_collective" }, { - "name": "eugene.emmett.wood", - "email": "gene_w@cementhorizon.com", - "homepage": "https://code.google.com/p/php-mime-mail-parser", - "role": "Developer" - }, + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-02-09T16:56:22+00:00" + }, + { + "name": "dasprid/enum", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "shasum": "" + }, + "require": { + "php": ">=7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ { - "name": "alknetso", - "email": "alkne@gmail.com", - "homepage": "https://code.google.com/p/php-mime-mail-parser", + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", "role": "Developer" } ], - "description": "Fully Tested Mailparse Extension Wrapper for PHP 5.4+", - "homepage": "https://github.com/php-mime-mail-parser/php-mime-mail-parser", + "description": "PHP 7.1 enum implementation", "keywords": [ - "MimeMailParser", - "mail", - "mailparse", - "mime" + "enum", + "map" ], - "time": "2018-04-30T05:55:59+00:00" + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.7" + }, + "time": "2025-09-16T12:23:56+00:00" }, { - "name": "phpmailer/phpmailer", - "version": "v5.2.26", + "name": "ddeboer/imap", + "version": "1.13.1", "source": { "type": "git", - "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "70362997bda4376378be7d92d81e2200550923f7" + "url": "https://github.com/ddeboer/imap.git", + "reference": "8b772d04b1deadb5df13782fb78c4b648f77496e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/70362997bda4376378be7d92d81e2200550923f7", - "reference": "70362997bda4376378be7d92d81e2200550923f7", + "url": "https://api.github.com/repos/ddeboer/imap/zipball/8b772d04b1deadb5df13782fb78c4b648f77496e", + "reference": "8b772d04b1deadb5df13782fb78c4b648f77496e", "shasum": "" }, "require": { - "ext-ctype": "*", - "php": ">=5.0.0" + "ext-iconv": "*", + "ext-imap": "*", + "ext-mbstring": "*", + "php": "^8.0.1" }, "require-dev": { - "doctrine/annotations": "1.2.*", - "jms/serializer": "0.16.*", - "phpdocumentor/phpdocumentor": "2.*", - "phpunit/phpunit": "4.8.*", - "symfony/debug": "2.8.*", - "symfony/filesystem": "2.8.*", - "symfony/translation": "2.8.*", - "symfony/yaml": "2.8.*", - "zendframework/zend-cache": "2.5.1", - "zendframework/zend-config": "2.5.1", - "zendframework/zend-eventmanager": "2.5.1", - "zendframework/zend-filter": "2.5.1", - "zendframework/zend-i18n": "2.5.1", - "zendframework/zend-json": "2.5.1", - "zendframework/zend-math": "2.5.1", - "zendframework/zend-serializer": "2.5.*", - "zendframework/zend-servicemanager": "2.5.*", - "zendframework/zend-stdlib": "2.5.1" - }, - "suggest": { - "league/oauth2-google": "Needed for Google XOAUTH2 authentication" + "friendsofphp/php-cs-fixer": "^v3.4.0", + "laminas/laminas-mail": "^2.15.1", + "malukenho/mcbumpface": "^1.1.5", + "phpstan/phpstan": "^1.3.3", + "phpstan/phpstan-phpunit": "^1.0.0", + "phpstan/phpstan-strict-rules": "^1.1.0", + "phpunit/phpunit": "^9.5.11" }, "type": "library", "autoload": { - "classmap": [ - "class.phpmailer.php", - "class.phpmaileroauth.php", - "class.phpmaileroauthgoogle.php", - "class.smtp.php", - "class.pop3.php", - "extras/EasyPeasyICS.php", - "extras/ntlm_sasl_client.php" - ] + "psr-4": { + "Ddeboer\\Imap\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-2.1" + "MIT" ], "authors": [ { - "name": "Jim Jagielski", - "email": "jimjag@gmail.com" + "name": "David de Boer", + "email": "david@ddeboer.nl" }, { - "name": "Marcus Bointon", - "email": "phpmailer@synchromedia.co.uk" + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com" }, { - "name": "Andy Prevost", - "email": "codeworxtech@users.sourceforge.net" + "name": "Community contributors", + "homepage": "https://github.com/ddeboer/imap/graphs/contributors" + } + ], + "description": "Object-oriented IMAP for PHP", + "keywords": [ + "email", + "imap", + "mail" + ], + "support": { + "issues": "https://github.com/ddeboer/imap/issues", + "source": "https://github.com/ddeboer/imap/tree/1.13.1" + }, + "funding": [ + { + "url": "https://github.com/Slamdunk", + "type": "github" }, { - "name": "Brent R. Matzelle" + "url": "https://github.com/ddeboer", + "type": "github" } ], - "description": "PHPMailer is a full-featured email creation and transfer class for PHP", - "time": "2017-11-04T09:26:05+00:00" + "time": "2022-01-10T10:53:05+00:00" }, { - "name": "robthree/twofactorauth", - "version": "1.6.5", + "name": "directorytree/ldaprecord", + "version": "v3.8.5", "source": { "type": "git", - "url": "https://github.com/RobThree/TwoFactorAuth.git", - "reference": "f5f58a4c62d0336a0e6175856894a51f3565dad2" + "url": "https://github.com/DirectoryTree/LdapRecord.git", + "reference": "00e5f088f8c4028d5f398783cccc2e8119a27a65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/f5f58a4c62d0336a0e6175856894a51f3565dad2", - "reference": "f5f58a4c62d0336a0e6175856894a51f3565dad2", + "url": "https://api.github.com/repos/DirectoryTree/LdapRecord/zipball/00e5f088f8c4028d5f398783cccc2e8119a27a65", + "reference": "00e5f088f8c4028d5f398783cccc2e8119a27a65", "shasum": "" }, "require": { - "php": ">=5.3.0" + "ext-iconv": "*", + "ext-json": "*", + "ext-ldap": "*", + "illuminate/collections": "^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0", + "nesbot/carbon": "*", + "php": ">=8.1", + "psr/log": "*", + "psr/simple-cache": "^1.0|^2.0|^3.0" }, "require-dev": { - "phpunit/phpunit": "@stable" + "fakerphp/faker": "^1.21", + "laravel/pint": "^1.6", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^9.0", + "spatie/ray": "^1.24" }, "type": "library", "autoload": { "psr-4": { - "RobThree\\Auth\\": "lib" + "LdapRecord\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -195,117 +360,2438 @@ ], "authors": [ { - "name": "Rob Janssen", - "homepage": "http://robiii.me", + "name": "Steve Bauman", + "email": "steven_bauman@outlook.com", "role": "Developer" } ], - "description": "Two Factor Authentication", - "homepage": "https://github.com/RobThree/TwoFactorAuth", + "description": "A fully-featured LDAP ORM.", + "homepage": "https://www.ldaprecord.com", "keywords": [ - "Authentication", - "MFA", - "Multi Factor Authentication", - "Two Factor Authentication", - "authenticator", - "authy", - "php", - "tfa" + "active directory", + "ad", + "adLDAP", + "adldap2", + "directory", + "ldap", + "ldaprecord", + "orm", + "windows" + ], + "support": { + "docs": "https://ldaprecord.com", + "email": "steven_bauman@outlook.com", + "issues": "https://github.com/DirectoryTree/LdapRecord/issues", + "source": "https://github.com/DirectoryTree/LdapRecord" + }, + "funding": [ + { + "url": "https://github.com/stevebauman", + "type": "github" + } ], - "time": "2018-06-09T10:09:59+00:00" + "time": "2025-10-06T02:22:34+00:00" }, { - "name": "soundasleep/html2text", - "version": "0.5.0", + "name": "firebase/php-jwt", + "version": "v6.5.0", "source": { "type": "git", - "url": "https://github.com/soundasleep/html2text.git", - "reference": "cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad" + "url": "https://github.com/firebase/php-jwt.git", + "reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/soundasleep/html2text/zipball/cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad", - "reference": "cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/e94e7353302b0c11ec3cfff7180cd0b1743975d2", + "reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "php": ">=5.3.2" + "php": "^7.4||^8.0" }, "require-dev": { - "phpunit/phpunit": ">=4.0", - "soundasleep/component-tests": "dev-master" + "guzzlehttp/guzzle": "^6.5||^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^1.0||^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" }, "type": "library", "autoload": { "psr-4": { - "Html2Text\\": "src" + "Firebase\\JWT\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "EPL-1.0" + "BSD-3-Clause" ], "authors": [ { - "name": "Jevon Wright", - "homepage": "https://jevon.org", + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", "role": "Developer" } ], - "description": "A PHP script to convert HTML into a plain text format", - "homepage": "https://github.com/soundasleep/html2text", + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", "keywords": [ - "email", - "html", - "php", - "text" + "jwt", + "php" ], - "time": "2017-04-19T22:01:50+00:00" + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.5.0" + }, + "time": "2023-05-12T15:47:07+00:00" }, { - "name": "yubico/u2flib-server", - "version": "1.0.1", + "name": "guzzlehttp/guzzle", + "version": "7.5.0", "source": { "type": "git", - "url": "https://github.com/Yubico/php-u2flib-server.git", - "reference": "dc318c80b59e62921c210f31b014def26ceebbab" + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yubico/php-u2flib-server/zipball/dc318c80b59e62921c210f31b014def26ceebbab", - "reference": "dc318c80b59e62921c210f31b014def26ceebbab", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", "shasum": "" }, "require": { - "ext-openssl": "*", - "php": ">=5.6" + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.9 || ^2.4", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "~5.7" + "bamarni/composer-bin-plugin": "^1.8.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" }, "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "7.5-dev" + } + }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-2-Clause" + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2022-08-28T15:39:27+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "b94b2807d85443f9719887892882d0329d1e2598" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", + "reference": "b94b2807d85443f9719887892882d0329d1e2598", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2022-08-28T14:55:35+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.4.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "0454e12ef0cd597ccd2adb036f7bda4e7fface66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/0454e12ef0cd597ccd2adb036f7bda4e7fface66", + "reference": "0454e12ef0cd597ccd2adb036f7bda4e7fface66", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.4.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-04-17T16:00:45+00:00" + }, + { + "name": "illuminate/collections", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "6ae9c74fa92d4e1824d1b346cd435e8eacdc3232" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/6ae9c74fa92d4e1824d1b346cd435e8eacdc3232", + "reference": "6ae9c74fa92d4e1824d1b346cd435e8eacdc3232", + "shasum": "" + }, + "require": { + "illuminate/conditionable": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/macroable": "^10.0", + "php": "^8.1" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^6.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-09-08T19:05:53+00:00" + }, + { + "name": "illuminate/conditionable", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/conditionable.git", + "reference": "47c700320b7a419f0d188d111f3bbed978fcbd3f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/conditionable/zipball/47c700320b7a419f0d188d111f3bbed978fcbd3f", + "reference": "47c700320b7a419f0d188d111f3bbed978fcbd3f", + "shasum": "" + }, + "require": { + "php": "^8.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Conditionable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-24T11:47:24+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v10.44.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac", + "reference": "8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac", + "shasum": "" + }, + "require": { + "php": "^8.1", + "psr/container": "^1.1.1|^2.0.1", + "psr/simple-cache": "^1.0|^2.0|^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-01-15T18:52:32+00:00" + }, + { + "name": "illuminate/macroable", + "version": "v10.49.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/dff667a46ac37b634dcf68909d9d41e94dc97c27", + "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2023-06-05T12:46:42+00:00" + }, + { + "name": "league/oauth2-client", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "160d6274b03562ebeb55ed18399281d8118b76c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/160d6274b03562ebeb55ed18399281d8118b76c8", + "reference": "160d6274b03562ebeb55ed18399281d8118b76c8", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "paragonie/random_compat": "^1 || ^2 || ^9.99", + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.5", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", + "squizlabs/php_codesniffer": "^2.3 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-client/issues", + "source": "https://github.com/thephpleague/oauth2-client/tree/2.7.0" + }, + "time": "2023-04-16T18:19:15+00:00" + }, + { + "name": "matthiasmullie/minify", + "version": "1.3.66", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/minify.git", + "reference": "45fd3b0f1dfa2c965857c6d4a470bea52adc31a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/45fd3b0f1dfa2c965857c6d4a470bea52adc31a6", + "reference": "45fd3b0f1dfa2c965857c6d4a470bea52adc31a6", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "matthiasmullie/path-converter": "~1.1", + "php": ">=5.3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.0", + "matthiasmullie/scrapbook": "dev-master", + "phpunit/phpunit": ">=4.8" + }, + "suggest": { + "psr/cache-implementation": "Cache implementation to use with Minify::cache" + }, + "bin": [ + "bin/minifycss", + "bin/minifyjs" + ], + "type": "library", + "autoload": { + "psr-4": { + "MatthiasMullie\\Minify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "minify@mullie.eu", + "homepage": "http://www.mullie.eu", + "role": "Developer" + } + ], + "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", + "homepage": "http://www.minifier.org", + "keywords": [ + "JS", + "css", + "javascript", + "minifier", + "minify" + ], + "support": { + "issues": "https://github.com/matthiasmullie/minify/issues", + "source": "https://github.com/matthiasmullie/minify/tree/1.3.66" + }, + "funding": [ + { + "url": "https://github.com/[user1", + "type": "github" + }, + { + "url": "https://github.com/matthiasmullie] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g.", + "type": "github" + }, + { + "url": "https://github.com/user2", + "type": "github" + } + ], + "time": "2021-01-06T15:18:10+00:00" + }, + { + "name": "matthiasmullie/path-converter", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/path-converter.git", + "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/e7d13b2c7e2f2268e1424aaed02085518afa02d9", + "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "MatthiasMullie\\PathConverter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "pathconverter@mullie.eu", + "homepage": "http://www.mullie.eu", + "role": "Developer" + } + ], + "description": "Relative path converter", + "homepage": "http://github.com/matthiasmullie/path-converter", + "keywords": [ + "converter", + "path", + "paths", + "relative" + ], + "support": { + "issues": "https://github.com/matthiasmullie/path-converter/issues", + "source": "https://github.com/matthiasmullie/path-converter/tree/1.1.3" + }, + "time": "2019-02-05T23:41:09+00:00" + }, + { + "name": "mustangostang/spyc", + "version": "0.6.3", + "source": { + "type": "git", + "url": "git@github.com:mustangostang/spyc.git", + "reference": "4627c838b16550b666d15aeae1e5289dd5b77da0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mustangostang/spyc/zipball/4627c838b16550b666d15aeae1e5289dd5b77da0", + "reference": "4627c838b16550b666d15aeae1e5289dd5b77da0", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "4.3.*@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, + "autoload": { + "files": [ + "Spyc.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + } + ], + "description": "A simple YAML loader/dumper class for PHP", + "homepage": "https://github.com/mustangostang/spyc/", + "keywords": [ + "spyc", + "yaml", + "yml" + ], + "time": "2019-09-10T13:16:29+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.72.3", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "0c6fd108360c562f6e4fd1dedb8233b423e91c83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/0c6fd108360c562f6e4fd1dedb8233b423e91c83", + "reference": "0c6fd108360c562f6e4fd1dedb8233b423e91c83", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "*", + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "psr/clock": "^1.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0", + "doctrine/orm": "^2.7 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "*", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.x-dev", + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2024-01-25T10:35:09+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, + { + "name": "php-mime-mail-parser/php-mime-mail-parser", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-mime-mail-parser/php-mime-mail-parser.git", + "reference": "9d09a017f3f103fec8456211a4a538b80e0eca0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/9d09a017f3f103fec8456211a4a538b80e0eca0d", + "reference": "9d09a017f3f103fec8456211a4a538b80e0eca0d", + "shasum": "" + }, + "require": { + "ext-mailparse": "*", + "php": "^7.2|^8.0" + }, + "replace": { + "exorus/php-mime-mail-parser": "*", + "messaged/php-mime-mail-parser": "*" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^8.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpMimeMailParser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "eXorus", + "email": "exorus.spam@gmail.com", + "homepage": "https://github.com/eXorus/", + "role": "Developer" + }, + { + "name": "M.Valinskis", + "email": "M.Valins@gmail.com", + "homepage": "https://code.google.com/p/php-mime-mail-parser", + "role": "Developer" + }, + { + "name": "eugene.emmett.wood", + "email": "gene_w@cementhorizon.com", + "homepage": "https://code.google.com/p/php-mime-mail-parser", + "role": "Developer" + }, + { + "name": "alknetso", + "email": "alkne@gmail.com", + "homepage": "https://code.google.com/p/php-mime-mail-parser", + "role": "Developer" + }, + { + "name": "bucabay", + "email": "gabe@fijiwebdesign.com", + "homepage": "http://www.fijiwebdesign.com", + "role": "Developer" + } + ], + "description": "A fully tested email parser for PHP 7.2+ (mailparse extension wrapper).", + "homepage": "https://github.com/php-mime-mail-parser/php-mime-mail-parser", + "keywords": [ + "MimeMailParser", + "mail", + "mailparse", + "mime", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/php-mime-mail-parser/php-mime-mail-parser/issues", + "source": "https://github.com/php-mime-mail-parser/php-mime-mail-parser/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/eXorus", + "type": "github" + } + ], + "time": "2021-02-25T17:21:57+00:00" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.6.0", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "e43bac82edc26ca04b36143a48bde1c051cfd5b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/e43bac82edc26ca04b36143a48bde1c051cfd5b1", + "reference": "e43bac82edc26ca04b36143a48bde1c051cfd5b1", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.2", + "php-parallel-lint/php-console-highlighter": "^0.5.0", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.6.2", + "yoast/phpunit-polyfills": "^1.0.0" + }, + "suggest": { + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.6.0" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2022-02-28T15:31:21+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "psr/simple-cache", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/8707bf3cea6f710bf6ef05491234e3ab06f6432a", + "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/2.0.0" + }, + "time": "2021-10-29T13:22:09+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "robthree/twofactorauth", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/RobThree/TwoFactorAuth.git", + "reference": "5afcb45282f1c75562a48d479ecd1732c9bdb11b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/5afcb45282f1c75562a48d479ecd1732c9bdb11b", + "reference": "5afcb45282f1c75562a48d479ecd1732c9bdb11b", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpunit/phpunit": "@stable" + }, + "suggest": { + "bacon/bacon-qr-code": "Needed for BaconQrCodeProvider provider", + "endroid/qr-code": "Needed for EndroidQrCodeProvider" + }, + "type": "library", + "autoload": { + "psr-4": { + "RobThree\\Auth\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Janssen", + "homepage": "http://robiii.me", + "role": "Developer" + } + ], + "description": "Two Factor Authentication", + "homepage": "https://github.com/RobThree/TwoFactorAuth", + "keywords": [ + "Authentication", + "MFA", + "Multi Factor Authentication", + "Two Factor Authentication", + "authenticator", + "authy", + "php", + "tfa" + ], + "support": { + "issues": "https://github.com/RobThree/TwoFactorAuth/issues", + "source": "https://github.com/RobThree/TwoFactorAuth" + }, + "funding": [ + { + "url": "https://paypal.me/robiii", + "type": "custom" + }, + { + "url": "https://github.com/RobThree", + "type": "github" + } + ], + "time": "2021-10-20T12:19:55+00:00" + }, + { + "name": "soundasleep/html2text", + "version": "0.5.0", + "source": { + "type": "git", + "url": "https://github.com/soundasleep/html2text.git", + "reference": "cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/soundasleep/html2text/zipball/cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad", + "reference": "cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": ">=4.0", + "soundasleep/component-tests": "dev-master" + }, + "type": "library", + "autoload": { + "psr-4": { + "Html2Text\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "EPL-1.0" + ], + "authors": [ + { + "name": "Jevon Wright", + "homepage": "https://jevon.org", + "role": "Developer" + } + ], + "description": "A PHP script to convert HTML into a plain text format", + "homepage": "https://github.com/soundasleep/html2text", + "keywords": [ + "email", + "html", + "php", + "text" + ], + "support": { + "email": "support@jevon.org", + "issues": "https://github.com/soundasleep/html2text/issues", + "source": "https://github.com/soundasleep/html2text/tree/master" + }, + "time": "2017-04-19T22:01:50+00:00" + }, + { + "name": "stevenmaguire/oauth2-keycloak", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/stevenmaguire/oauth2-keycloak.git", + "reference": "05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d", + "reference": "05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "^4.0 || ^5.0 || ^6.0", + "league/oauth2-client": "^2.0", + "php": "~7.2 || ~8.0" + }, + "require-dev": { + "mockery/mockery": "~1.5.0", + "phpunit/phpunit": "~9.6.4", + "squizlabs/php_codesniffer": "~3.7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Stevenmaguire\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Steven Maguire", + "email": "stevenmaguire@gmail.com", + "homepage": "https://github.com/stevenmaguire" + } + ], + "description": "Keycloak OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "authorisation", + "authorization", + "client", + "keycloak", + "oauth", + "oauth2" + ], + "support": { + "issues": "https://github.com/stevenmaguire/oauth2-keycloak/issues", + "source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/4.0.0" + }, + "time": "2023-03-14T09:43:47+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.24.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "30885182c981ab175d4d034db0f6f469898070ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-20T20:35:02+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/translation", + "version": "v6.4.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "637c51191b6b184184bbf98937702bcf554f7d04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/637c51191b6b184184bbf98937702bcf554f7d04", + "reference": "637c51191b6b184184bbf98937702bcf554f7d04", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "conflict": { + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.18|^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v6.4.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T13:11:52+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "06450585bf65e978026bda220cdebca3f867fde7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/06450585bf65e978026bda220cdebca3f867fde7", + "reference": "06450585bf65e978026bda220cdebca3f867fde7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.4.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-12-26T14:02:43+00:00" + }, + { + "name": "twig/twig", + "version": "v3.14.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72", + "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php81": "^1.29" + }, + "require-dev": { + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.14.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } ], - "description": "Library for U2F implementation", - "homepage": "https://developers.yubico.com/php-u2flib-server", - "time": "2017-05-09T07:33:58+00:00" + "time": "2024-09-09T17:55:12+00:00" } ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/data/web/inc/lib/sieve/extensions/duplicate.xml b/data/web/inc/lib/sieve/extensions/duplicate.xml new file mode 100644 index 0000000000..1108d2c9ce --- /dev/null +++ b/data/web/inc/lib/sieve/extensions/duplicate.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/data/web/inc/lib/sieve/extensions/enotify.xml b/data/web/inc/lib/sieve/extensions/enotify.xml new file mode 100644 index 0000000000..b4686a3330 --- /dev/null +++ b/data/web/inc/lib/sieve/extensions/enotify.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/web/inc/lib/sieve/extensions/mime.xml b/data/web/inc/lib/sieve/extensions/mime.xml new file mode 100644 index 0000000000..d7e200f029 --- /dev/null +++ b/data/web/inc/lib/sieve/extensions/mime.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/web/inc/lib/sieve/extensions/vnd.dovecot.execute.xml b/data/web/inc/lib/sieve/extensions/vnd.dovecot.execute.xml new file mode 100644 index 0000000000..bce1ea9054 --- /dev/null +++ b/data/web/inc/lib/sieve/extensions/vnd.dovecot.execute.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/data/web/inc/lib/sieve/extensions/vnd.dovecot.filter.xml b/data/web/inc/lib/sieve/extensions/vnd.dovecot.filter.xml new file mode 100644 index 0000000000..45bf236b83 --- /dev/null +++ b/data/web/inc/lib/sieve/extensions/vnd.dovecot.filter.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/data/web/inc/lib/sieve/extensions/vnd.dovecot.pipe.xml b/data/web/inc/lib/sieve/extensions/vnd.dovecot.pipe.xml new file mode 100644 index 0000000000..6bbcfc3c05 --- /dev/null +++ b/data/web/inc/lib/sieve/extensions/vnd.dovecot.pipe.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/data/web/inc/lib/ssp.class.php b/data/web/inc/lib/ssp.class.php new file mode 100644 index 0000000000..ea6109ac3b --- /dev/null +++ b/data/web/inc/lib/ssp.class.php @@ -0,0 +1,622 @@ + 'utf8'` - you might need this depending on your PHP / MySQL config + * @return resource PDO connection + */ + static function db ( $conn ) + { + if ( is_array( $conn ) ) { + return self::sql_connect( $conn ); + } + + return $conn; + } + + + /** + * Paging + * + * Construct the LIMIT clause for server-side processing SQL query + * + * @param array $request Data sent to server by DataTables + * @param array $columns Column information array + * @return string SQL limit clause + */ + static function limit ( $request, $columns ) + { + $limit = ''; + + if ( isset($request['start']) && $request['length'] != -1 ) { + $limit = "LIMIT ".intval($request['start']).", ".intval($request['length']); + } + + return $limit; + } + + + /** + * Ordering + * + * Construct the ORDER BY clause for server-side processing SQL query + * + * @param array $request Data sent to server by DataTables + * @param array $columns Column information array + * @return string SQL order by clause + */ + static function order ( $tableAS, $request, $columns ) + { + $select = ''; + $order = ''; + + if ( isset($request['order']) && count($request['order']) ) { + $selects = []; + $orderBy = []; + $dtColumns = self::pluck( $columns, 'dt' ); + + for ( $i=0, $ien=count($request['order']) ; $i<$ien ; $i++ ) { + // Convert the column index into the column data property + $columnIdx = intval($request['order'][$i]['column']); + $requestColumn = $request['columns'][$columnIdx]; + + $columnIdx = array_search( $columnIdx, $dtColumns ); + $column = $columns[ $columnIdx ]; + + if ( $requestColumn['orderable'] == 'true' ) { + $dir = $request['order'][$i]['dir'] === 'asc' ? + 'ASC' : + 'DESC'; + + if(isset($column['order_subquery'])) { + $selects[] = '('.$column['order_subquery'].') AS `'.$column['db'].'_count`'; + $orderBy[] = '`'.$column['db'].'_count` '.$dir; + } else { + $orderBy[] = '`'.$tableAS.'`.`'.$column['db'].'` '.$dir; + } + } + } + + if ( count( $selects ) ) { + $select = ', '.implode(', ', $selects); + } + + if ( count( $orderBy ) ) { + $order = 'ORDER BY '.implode(', ', $orderBy); + } + } + + return [$select, $order]; + } + + + /** + * Searching / Filtering + * + * Construct the WHERE clause for server-side processing SQL query. + * + * NOTE this does not match the built-in DataTables filtering which does it + * word by word on any field. It's possible to do here performance on large + * databases would be very poor + * + * @param array $request Data sent to server by DataTables + * @param array $columns Column information array + * @param array $bindings Array of values for PDO bindings, used in the + * sql_exec() function + * @return string SQL where clause + */ + static function filter ( $tablesAS, $request, $columns, &$bindings ) + { + $globalSearch = array(); + $columnSearch = array(); + $joins = array(); + $dtColumns = self::pluck( $columns, 'dt' ); + + if ( isset($request['search']) && $request['search']['value'] != '' ) { + $str = $request['search']['value']; + + for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) { + $requestColumn = $request['columns'][$i]; + $columnIdx = array_search( $i, $dtColumns ); + $column = $columns[ $columnIdx ]; + + if ( $requestColumn['searchable'] == 'true' ) { + if(!empty($column['db'])){ + $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR ); + + if(isset($column['search']['join'])) { + $joins[] = $column['search']['join']; + $globalSearch[] = $column['search']['where_column'].' LIKE '.$binding; + } else { + $globalSearch[] = "`".$tablesAS."`.`".$column['db']."` LIKE ".$binding; + } + } + } + } + } + + // Individual column filtering + if ( isset( $request['columns'] ) ) { + for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) { + $requestColumn = $request['columns'][$i]; + $columnIdx = array_search( $requestColumn['data'], $dtColumns ); + $column = $columns[ $columnIdx ]; + + $str = $requestColumn['search']['value']; + + if ( $requestColumn['searchable'] == 'true' && + $str != '' ) { + if(!empty($column['db'])){ + $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR ); + $columnSearch[] = "`".$tablesAS."`.`".$column['db']."` LIKE ".$binding; + } + } + } + } + + // Combine the filters into a single string + $where = ''; + + if ( count( $globalSearch ) ) { + $where = '('.implode(' OR ', $globalSearch).')'; + } + + if ( count( $columnSearch ) ) { + $where = $where === '' ? + implode(' AND ', $columnSearch) : + $where .' AND '. implode(' AND ', $columnSearch); + } + + $join = ''; + if( count($joins) ) { + $join = implode(' ', $joins); + } + + if ( $where !== '' ) { + $where = 'WHERE '.$where; + } + + return [$join, $where]; + } + + + /** + * Perform the SQL queries needed for an server-side processing requested, + * utilising the helper functions of this class, limit(), order() and + * filter() among others. The returned array is ready to be encoded as JSON + * in response to an SSP request, or can be modified if needed before + * sending back to the client. + * + * @param array $request Data sent to server by DataTables + * @param array|PDO $conn PDO connection resource or connection parameters array + * @param string $table SQL table to query + * @param string $primaryKey Primary key of the table + * @param array $columns Column information array + * @return array Server-side processing response array + */ + static function simple ( $request, $conn, $table, $primaryKey, $columns ) + { + $bindings = array(); + $db = self::db( $conn ); + + // Allow for a JSON string to be passed in + if (isset($request['json'])) { + $request = json_decode($request['json'], true); + } + + // table AS + $tablesAS = null; + if(is_array($table)) { + $tablesAS = $table[1]; + $table = $table[0]; + } + + // Build the SQL query string from the request + list($select, $order) = self::order( $tablesAS, $request, $columns ); + $limit = self::limit( $request, $columns ); + list($join, $where) = self::filter( $tablesAS, $request, $columns, $bindings ); + + // Main query to actually get the data + $data = self::sql_exec( $db, $bindings, + "SELECT `$tablesAS`.`".implode("`, `$tablesAS`.`", self::pluck($columns, 'db'))."` + $select + FROM `$table` AS `$tablesAS` + $join + $where + GROUP BY `{$tablesAS}`.`{$primaryKey}` + $order + $limit" + ); + + // Data set length after filtering + $resFilterLength = self::sql_exec( $db, $bindings, + "SELECT COUNT(DISTINCT `{$tablesAS}`.`{$primaryKey}`) + FROM `$table` AS `$tablesAS` + $join + $where" + ); + $recordsFiltered = $resFilterLength[0][0]; + + // Total data set length + $resTotalLength = self::sql_exec( $db, + "SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`) + FROM `$table` AS `$tablesAS`" + ); + $recordsTotal = $resTotalLength[0][0]; + + /* + * Output + */ + return array( + "draw" => isset ( $request['draw'] ) ? + intval( $request['draw'] ) : + 0, + "recordsTotal" => intval( $recordsTotal ), + "recordsFiltered" => intval( $recordsFiltered ), + "data" => self::data_output( $columns, $data ) + ); + } + + + /** + * The difference between this method and the `simple` one, is that you can + * apply additional `where` conditions to the SQL queries. These can be in + * one of two forms: + * + * * 'Result condition' - This is applied to the result set, but not the + * overall paging information query - i.e. it will not effect the number + * of records that a user sees they can have access to. This should be + * used when you want apply a filtering condition that the user has sent. + * * 'All condition' - This is applied to all queries that are made and + * reduces the number of records that the user can access. This should be + * used in conditions where you don't want the user to ever have access to + * particular records (for example, restricting by a login id). + * + * In both cases the extra condition can be added as a simple string, or if + * you are using external values, as an assoc. array with `condition` and + * `bindings` parameters. The `condition` is a string with the SQL WHERE + * condition and `bindings` is an assoc. array of the binding names and + * values. + * + * @param array $request Data sent to server by DataTables + * @param array|PDO $conn PDO connection resource or connection parameters array + * @param string|array $table SQL table to query, if array second key is AS + * @param string $primaryKey Primary key of the table + * @param array $columns Column information array + * @param string $join JOIN sql string + * @param string|array $whereResult WHERE condition to apply to the result set + * @return array Server-side processing response array + */ + static function complex ( + $request, + $conn, + $table, + $primaryKey, + $columns, + $join=null, + $whereResult=null + ) { + $bindings = array(); + $db = self::db( $conn ); + + // table AS + $tablesAS = null; + if(is_array($table)) { + $tablesAS = $table[1]; + $table = $table[0]; + } + + // Build the SQL query string from the request + list($select, $order) = self::order( $tablesAS, $request, $columns ); + $limit = self::limit( $request, $columns ); + list($join_filter, $where) = self::filter( $tablesAS, $request, $columns, $bindings ); + + // whereResult can be a simple string, or an assoc. array with a + // condition and bindings + if ( $whereResult ) { + $str = $whereResult; + + if ( is_array($whereResult) ) { + $str = $whereResult['condition']; + + if ( isset($whereResult['bindings']) ) { + self::add_bindings($bindings, $whereResult); + } + } + + $where = $where ? + $where .' AND '.$str : + 'WHERE '.$str; + } + + // Main query to actually get the data + $data = self::sql_exec( $db, $bindings, + "SELECT `$tablesAS`.`".implode("`, `$tablesAS`.`", self::pluck($columns, 'db'))."` + $select + FROM `$table` AS `$tablesAS` + $join + $join_filter + $where + GROUP BY `{$tablesAS}`.`{$primaryKey}` + $order + $limit" + ); + + // Data set length after filtering + $resFilterLength = self::sql_exec( $db, $bindings, + "SELECT COUNT(DISTINCT `{$tablesAS}`.`{$primaryKey}`) + FROM `$table` AS `$tablesAS` + $join + $join_filter + $where" + ); + $recordsFiltered = (isset($resFilterLength[0])) ? $resFilterLength[0][0] : 0; + + // Total data set length + $resTotalLength = self::sql_exec( $db, $bindings, + "SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`) + FROM `$table` AS `$tablesAS` + $join + $join_filter + $where" + ); + $recordsTotal = (isset($resTotalLength[0])) ? $resTotalLength[0][0] : 0; + + /* + * Output + */ + return array( + "draw" => isset ( $request['draw'] ) ? + intval( $request['draw'] ) : + 0, + "recordsTotal" => intval( $recordsTotal ), + "recordsFiltered" => intval( $recordsFiltered ), + "data" => self::data_output( $columns, $data ) + ); + } + + + /** + * Connect to the database + * + * @param array $sql_details SQL server connection details array, with the + * properties: + * * host - host name + * * db - database name + * * user - user name + * * pass - user password + * @return resource Database connection handle + */ + static function sql_connect ( $sql_details ) + { + try { + $db = @new PDO( + "mysql:host={$sql_details['host']};dbname={$sql_details['db']}", + $sql_details['user'], + $sql_details['pass'], + array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ) + ); + } + catch (PDOException $e) { + self::fatal( + "An error occurred while connecting to the database. ". + "The error reported by the server was: ".$e->getMessage() + ); + } + + return $db; + } + + + /** + * Execute an SQL query on the database + * + * @param resource $db Database handler + * @param array $bindings Array of PDO binding values from bind() to be + * used for safely escaping strings. Note that this can be given as the + * SQL query string if no bindings are required. + * @param string $sql SQL query to execute. + * @return array Result from the query (all rows) + */ + static function sql_exec ( $db, $bindings, $sql=null ) + { + // Argument shifting + if ( $sql === null ) { + $sql = $bindings; + } + + $stmt = $db->prepare( $sql ); + + // Bind parameters + if ( is_array( $bindings ) ) { + for ( $i=0, $ien=count($bindings) ; $i<$ien ; $i++ ) { + $binding = $bindings[$i]; + $stmt->bindValue( $binding['key'], $binding['val'], $binding['type'] ); + } + } + + // Execute + try { + $stmt->execute(); + } + catch (PDOException $e) { + self::fatal( "An SQL error occurred: ".$e->getMessage() ); + } + + // Return all + return $stmt->fetchAll( PDO::FETCH_BOTH ); + } + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Internal methods + */ + + /** + * Throw a fatal error. + * + * This writes out an error message in a JSON string which DataTables will + * see and show to the user in the browser. + * + * @param string $msg Message to send to the client + */ + static function fatal ( $msg ) + { + echo json_encode( array( + "error" => $msg + ) ); + + exit(0); + } + + /** + * Create a PDO binding key which can be used for escaping variables safely + * when executing a query with sql_exec() + * + * @param array &$a Array of bindings + * @param * $val Value to bind + * @param int $type PDO field type + * @return string Bound key to be used in the SQL where this parameter + * would be used. + */ + static function bind ( &$a, $val, $type ) + { + $key = ':binding_'.count( $a ); + + $a[] = array( + 'key' => $key, + 'val' => $val, + 'type' => $type + ); + + return $key; + } + + static function add_bindings(&$bindings, $vals) + { + foreach($vals['bindings'] as $key => $value) { + $bindings[] = array( + 'key' => $key, + 'val' => $value, + 'type' => PDO::PARAM_STR + ); + } + } + + + /** + * Pull a particular property from each assoc. array in a numeric array, + * returning and array of the property values from each item. + * + * @param array $a Array to get data from + * @param string $prop Property to read + * @return array Array of property values + */ + static function pluck ( $a, $prop ) + { + $out = array(); + + for ( $i=0, $len=count($a) ; $i<$len ; $i++ ) { + if ( empty($a[$i][$prop]) && $a[$i][$prop] !== 0 ) { + continue; + } + if ( $prop == 'db' && isset($a[$i]['dummy']) && $a[$i]['dummy'] === true ) { + continue; + } + + //removing the $out array index confuses the filter method in doing proper binding, + //adding it ensures that the array data are mapped correctly + $out[$i] = $a[$i][$prop]; + } + + return $out; + } + + + /** + * Return a string from an array or a string + * + * @param array|string $a Array to join + * @param string $join Glue for the concatenation + * @return string Joined string + */ + static function _flatten ( $a, $join = ' AND ' ) + { + if ( ! $a ) { + return ''; + } + else if ( $a && is_array($a) ) { + return implode( $join, $a ); + } + return $a; + } +} + diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/.gitattributes b/data/web/inc/lib/vendor/adldap2/adldap2/.gitattributes new file mode 100644 index 0000000000..84b904de52 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/.gitattributes @@ -0,0 +1,2 @@ +examples/ export-ignore +tests/ export-ignore diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/.github/issue_template.md b/data/web/inc/lib/vendor/adldap2/adldap2/.github/issue_template.md new file mode 100644 index 0000000000..f9aba6bdae --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/.github/issue_template.md @@ -0,0 +1,10 @@ +- Adldap2 Version: #.# +- LDAP Type: +- PHP Version: #.# + + + +### Description: + + +### Steps To Reproduce: diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/.gitignore b/data/web/inc/lib/vendor/adldap2/adldap2/.gitignore new file mode 100644 index 0000000000..2130d09788 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/.gitignore @@ -0,0 +1,3 @@ +/.idea +/vendor +composer.lock diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/.scrutinizer.yml b/data/web/inc/lib/vendor/adldap2/adldap2/.scrutinizer.yml new file mode 100644 index 0000000000..9dbf8c1105 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/.scrutinizer.yml @@ -0,0 +1,10 @@ +filter: + excluded_paths: + - tests/* + - src/Schemas/* +build: + nodes: + analysis: + tests: + override: + - command: php-scrutinizer-run diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/.styleci.yml b/data/web/inc/lib/vendor/adldap2/adldap2/.styleci.yml new file mode 100644 index 0000000000..8be488dfc2 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/.styleci.yml @@ -0,0 +1,7 @@ +preset: recommended + +enabled: + - length_ordered_imports + +disabled: + - alpha_ordered_imports \ No newline at end of file diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/.travis.yml b/data/web/inc/lib/vendor/adldap2/adldap2/.travis.yml new file mode 100644 index 0000000000..2723d6878b --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/.travis.yml @@ -0,0 +1,19 @@ +language: php + +php: + - 7.1 + - 7.2 + - 7.3 + - 7.4 + +before_script: + - travis_retry composer self-update + - travis_retry composer install --prefer-source --no-interaction + +script: ./vendor/bin/phpunit + +branches: + only: + - master + - v9.0 + - v8.0 diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/composer.json b/data/web/inc/lib/vendor/adldap2/adldap2/composer.json new file mode 100644 index 0000000000..26c1534716 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/composer.json @@ -0,0 +1,57 @@ +{ + "name": "adldap2/adldap2", + "type": "library", + "description": "A PHP LDAP Package for humans.", + "keywords": [ + "active directory", + "directory", + "ad", + "ldap", + "windows", + "adldap", + "adldap2" + ], + "license": "MIT", + "support": { + "docs": "https://github.com/Adldap2/Adldap2/blob/master/readme.md", + "issues": "https://github.com/Adldap2/Adldap2/issues", + "source": "https://github.com/Adldap2/Adldap2", + "email": "steven_bauman@outlook.com" + }, + "authors": [ + { + "name": "Steve Bauman", + "email": "steven_bauman@outlook.com", + "role": "Developer" + } + ], + "require": { + "php": ">=7.0", + "ext-ldap": "*", + "ext-json": "*", + "psr/log": "~1.0", + "psr/simple-cache": "~1.0", + "tightenco/collect": "~5.0|~6.0|~7.0|~8.0", + "illuminate/contracts": "~5.0|~6.0|~7.0|~8.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0|~7.0|~8.0", + "mockery/mockery": "~1.0" + }, + "suggest": { + "ext-fileinfo": "fileinfo is required when retrieving user encoded thumbnails" + }, + "archive": { + "exclude": ["/examples", "/tests"] + }, + "autoload": { + "psr-4": { + "Adldap\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Adldap\\Tests\\": "tests/" + } + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/.nojekyll b/data/web/inc/lib/vendor/adldap2/adldap2/docs/.nojekyll new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/_coverpage.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/_coverpage.md new file mode 100644 index 0000000000..fc8ec0b9da --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/_coverpage.md @@ -0,0 +1,16 @@ + + +# Adldap2 + +

    + + + + + +

    + +> Working with LDAP doesn't need to be hard. + + +![](media/bg.svg) diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/_sidebar.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/_sidebar.md new file mode 100644 index 0000000000..0284653101 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/_sidebar.md @@ -0,0 +1,27 @@ + + +* Getting Started + + * [Introduction](/) + * [Installation](installation.md) + * [Setup](setup.md) + +* Usage + + * [Searching](searching.md) + * [Creating & Updating](models/model.md) + * [Events](events.md) + * [Logging](logging.md) + * [Working With Distiguished Names](distinguished-names.md) + * [Troubleshooting](troubleshooting.md) + +* Models + * [Model (Base)](models/model.md) + * [Computer](models/computer.md) + * [Contact](models/contact.md) + * [Container](models/container.md) + * [Group](models/group.md) + * [Organizational Unit](models/ou.md) + * [Printer](models/printer.md) + * [RootDse](models/root-dse.md) + * [User](models/user.md) diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/distinguished-names.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/distinguished-names.md new file mode 100644 index 0000000000..490b99b01d --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/distinguished-names.md @@ -0,0 +1,167 @@ +## Working With Distinguished Names + +Working with DN strings are a pain, but they're about to get easier. Adldap includes a DN builder for easily modifying and +creating DN strings. + +> **Note**: All values inserted into DN methods are escaped. You do not need to escape **any** values before hand. + +#### Creating a New DN + +To create a new DN, construct a new `Adldap\Models\Attributes\DistinguishedName` instance: + +```php +$dn = new Adldap\Models\Attributes\DistinguishedName(); +``` + +You can also pass in a current DN string and start modifying it: + +```php +$currentDn = 'cn=John Doe,ou=Accounting,dc=corp,dc=acme,dc=org'; + +$dn = new Adldap\Models\Attributes\DistinguishedName($currentDn); +``` + +#### Adding / Removing a Domain Component + +```php +// Add Domain Component +$dn->addDc('corp'); + +// Remove Domain Component +$dn->removeDc('corp'); +``` + +#### Adding / Removing an Organizational Unit + +```php +// Add Organizational Unit +$dn->addOu('Accounting'); + +// Remove Organizational Unit +$dn->removeOu('Accounting'); +``` + +#### Adding / Removing Common Names + +```php +// Add Common Name +$dn->addCn('John Doe'); + +// Remove Common Name +$dn->removeCn('John Doe'); +``` + +#### Setting a base + +If you'd like to set the base DN, such as a domain component RDN, use the `setBase()` method: + +```php +$base = 'dc=corp,dc=acme,dc=org'; + +$dn->setBase($base); +``` + +#### Creating a DN From A Model + +When you're creating a new LDAP record, you'll need to create a distinguished name as well. Let's go through an example of +creating a new user. + +```php +$user = $provider->make()->user(); + +$user->setCommonName('John Doe'); +$user->setFirstName('John'); +$user->setLastName('Doe'); +``` + +So we've set the basic information on the user, but we run into trouble when we want to put the user into a certain container +(such as 'Accounting') which is done through the DN. Let's go through this example: + +```php +$dn = $user->getDnBuilder(); + +$dn->addCn($user->getCommonName()); +$dn->addOu('Accounting'); +$dn->addDc('corp'); +$dn->addDc('acme'); +$dn->addDc('org'); + +// Returns 'cn=John Doe,ou=Accounting,dc=corp,dc=acme,dc=org' +echo $dn->get(); + +// The DistinguishedName object also contains the __toString() magic method +// so you can also just echo the object itself +echo $dn; +``` + +Now we've built a DN, and all we have to do is set it on the new user: + +```php +$user->setDn($dn); + +$user->save(); +``` + +#### Modifying a DN From A Model + +When you've received a model from a search result, you can build and modify the models DN like so: + +```php +$user = $ad->users()->find('jdoe'); + +$dn = $user->getDnBuilder(); + +$dn->addOu('Users'); + +$user->setDn($dn)->save(); +``` + +#### Retrieving the RDN components + +To retrieve all of the RDN components of a Distinguished Name, call `getComponents()`: + +```php +$dn = new Adldap\Models\Attributes\DistinguishedName( + 'cn=John Doe,ou=Accounting,dc=corp,dc=acme,dc=org' +); + +$components = $dn->getComponents(); + +var_dump($components); + +// Output: +// array:5 [โ–ผ +// "cn" => array:1 [โ–ผ +// 0 => "John Doe" +// ] +// "uid" => [] +// "ou" => array:1 [โ–ผ +// 0 => "Accounting" +// ] +// "dc" => array:3 [โ–ผ +// 0 => "corp" +// 1 => "acme" +// 2 => "org" +// ] +// "o" => [] +// ] +``` + +You can also specify a component you would like returned by supplying it as an argument: + +```php +$dn = new Adldap\Models\Attributes\DistinguishedName( + 'cn=John Doe,ou=Accounting,dc=corp,dc=acme,dc=org' +); + +$dcs = $dn->getComponents('dc'); + +var_dump($dcs); + +// Output: +// array:3 [โ–ผ +// 0 => "corp" +// 1 => "acme" +// 2 => "org" +// ] +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/events.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/events.md new file mode 100644 index 0000000000..81756958b0 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/events.md @@ -0,0 +1,175 @@ +# Events + +Adldap2 events provide a method of listening for certain LDAP actions +that are called and execute tasks for that specific event. + +> **Note**: The Adldap2 event dispatcher was actually derived from the +> [Laravel Framework](https://github.com/laravel/framework) with +> Broadcasting & Queuing omitted to remove extra dependencies +> that would be required with implementing those features. +> +> If you've utilized Laravel's events before, this will feel very familiar. + +## Registering Listeners + +> **Note**: Before we get to registering listeners, it's crucial to know that events throughout +> Adldap2 are fired irrespective of the current connection or provider in use. +> +> This means that when using multiple LDAP connections, the same events will be fired. +> +> This allows you to set listeners on events that occur for all LDAP connections you utilize. +> +> If you are required to determine which events are fired from alternate connections, see [below](#determining-the-connection). + +To register a listener on an event, retrieve the event dispatcher and call the `listen()` method: + +```php +use Adldap\Auth\Events\Binding; + +$dispatcher = \Adldap\Adldap::getEventDispatcher(); + +$dispatcher->listen(Binding::class, function (Binding $event) { + // Do something with the Binding event information: + + $event->connection; // Adldap\Connections\Ldap instance + $event->username; // 'jdoe@acme.org' + $event->password; // 'super-secret' +}); +``` + +The first argument is the event name you would like to listen for, and the +second is either a closure or class name that should handle the event: + +Using a class: + +> **Note**: When using just a class name, the class must contain a public `handle()` method that will handle the event. + +```php +use Adldap\Adldap; +use Adldap\Auth\Events\Binding; + +$dispatcher = Adldap::getEventDispatcher(); + +$dispatcher->listen(Binding::class, MyApp\BindingEventHandler::class); +``` + +```php +namespace MyApp; + +use Adldap\Auth\Events\Binding; + +class BindingEventHandler +{ + public function handle(Binding $event) + { + // Handle the event... + } +} +``` + +## Model Events + +Model events are handled the same way as authentication events. + +Simply call the event dispatcher `listen()` method with the model event you are wanting to listen for: + +```php +use Adldap\Models\Events\Saving; + +$dispatcher = \Adldap\Adldap::getEventDispatcher(); + +$dispatcher->listen(Saving::class, function (Saving $event) { + // Do something with the Saving event information: + + // Returns the model instance being saved eg. `Adldap\Models\Entry` + $event->getModel(); +}); +``` + +## Wildcard Event Listeners + +You can register listeners using the `*` as a wildcard parameter to catch multiple events with the same listener. + +Wildcard listeners will receive the event name as their first argument, and the entire event data array as their second argument: + +```php +$dispatcher = Adldap::getEventDispatcher(); + +// Listen for all model events. +$dispatcher->listen('Adldap\Models\Events\*', function ($eventName, array $data) { + echo $eventName; // Returns 'Adldap\Models\Events\Updating' + var_dump($data); // Returns [0] => (object) Adldap\Models\Events\Updating; +}); + +$user = $provider->search()->users()->find('jdoe'); + +$user->setTelephoneNumber('555 555-5555'); + +$user->save(); +``` + +## Determining the Connection + +If you're using multiple LDAP connections and you require the ability to determine which events belong +to a certain connection, you can do so by verifying the host of the LDAP connection. + +Here's an example: + +```php +$dispatcher = Adldap::getEventDispatcher(); + +$dispatcher->listen(\Adldap\Models\Events\Creating::class, function ($event) { + $connection = $event->model->getConnection(); + + $host = $connection->getHost(); + + echo $host; // Displays 'ldap://192.168.1.1:386' +}); +``` + +Another example with auth events: + +```php +$dispatcher = Adldap::getEventDispatcher(); + +$dispatcher->listen(\Adldap\Auth\Events\Binding::class, function ($event) { + $connection = $event->connection; + + $host = $connection->getHost(); + + echo $host; // Displays 'ldap://192.168.1.1:386' +}); +``` + +## List of Events + +### Authentication Events + +There are several events that are fired during initial and subsequent binds to your configured LDAP server. + +Here is a list of all events that are fired: + +| Event| Description | +|---|---| +| Adldap\Auth\Events\Attempting | When any authentication attempt is called via: `$provider->auth()->attempt()` | +| Adldap\Auth\Events\Passed | When any authentication attempts pass via: `$provider->auth()->attempt()` | +| Adldap\Auth\Events\Failed | When any authentication attempts fail via: `$provider->auth()->attempt()` *Or* `$provider->auth()->bind()` | +| Adldap\Auth\Events\Binding | When any LDAP bind attempts occur via: `$provider->auth()->attempt()` *Or* `$provider->auth()->bind()` | +| Adldap\Auth\Events\Bound | When any LDAP bind attempts are successful via: `$provider->auth()->attempt()` *Or* `$provider->auth()->bind()` | + +### Model Events + +There are several events that are fired during the creation, updating and deleting of all models. + +Here is a list of all events that are fired: + +| Event | Description | +|---|---| +| Adldap\Models\Events\Saving | When a model is in the process of being saved via: `$model->save()` | +| Adldap\Models\Events\Saved | When a model has been successfully saved via: `$model->save()` | +| Adldap\Models\Events\Creating | When a model is being created via: `$model->save()` *Or* `$model->create()` | +| Adldap\Models\Events\Created | When a model has been successfully created via: `$model->save()` *Or* `$model->create()` | +| Adldap\Models\Events\Updating | When a model is being updated via: `$model->save()` *Or* `$model->update()` | +| Adldap\Models\Events\Updated | When a model has been successfully updated via: `$model->save()` *Or* `$model->update()` | +| Adldap\Models\Events\Deleting | When a model is being deleted via: `$model->delete()` | +| Adldap\Models\Events\Deleted | When a model has been successfully deleted via: `$model->delete()` | \ No newline at end of file diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/index.html b/data/web/inc/lib/vendor/adldap2/adldap2/docs/index.html new file mode 100644 index 0000000000..adf939bd92 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/index.html @@ -0,0 +1,35 @@ + + + + + Adldap2 Documentation + + + + + + + + +
    + + + + + + + + + diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/installation.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/installation.md new file mode 100644 index 0000000000..de21239e50 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/installation.md @@ -0,0 +1,29 @@ +# Requirements + +Adldap2 requires the following: + +- PHP 7.0 or greater +- LDAP extension enabled in PHP +- An LDAP server (ActiveDirectory, OpenLDAP, FreeIPA etc.) + +# Composer + +Adldap2 uses [Composer](https://getcomposer.org) for installation. + +Once you have composer installed, run the following command in the root directory of your project: + +```bash +composer require adldap2/adldap2 +``` + +Then, if your application doesn't already require Composer's autoload, you will need to do it manually. + +Insert this line at the top of your projects PHP script (usually `index.php`): + +```php +require __DIR__ . '/vendor/autoload.php'; +``` + +You're all set! + +Now, head over to the [setup guide](setup.md) to get up and running. diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/logging.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/logging.md new file mode 100644 index 0000000000..b49bcd3a3b --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/logging.md @@ -0,0 +1,74 @@ +# Logging + +Adldap2 includes an implementation of PSR's widely supported [Logger](https://github.com/php-fig/log) interface. + +By default, all of Adldap2's [events](events.md) will call the logger you have set to utilize. + +> **Note**: Adldap2 does not include a file / text logger. You must implement your own. + +## Registering & Enabling a Logger + +To register a logger call `Adldap::setLogger()`. The logger must implement the `Psr\Log\LoggerInterface`. + +>**Note**: Be sure to set the logger prior to creating a new `Adldap` instance. This +> ensures all events throughout the lifecycle of the request use your logger. + +```php +use Adldap\Adldap; + +Adldap::setLogger($myLogger); + +$config = ['...']; + +$ad = new Adldap(); + +$ad->addProvider($config); +``` + +## Disabling Logging + +If you need to disable the event logger after a certain set of operations, simply pass in `null` and logging will be disabled: + +```php +use Adldap\Adldap; + +Adldap::setLogger($myLogger); + +$config = ['...']; + +$ad = new Adldap(); + +$ad->addProvider($config); + +try { + $ad->connect(); + + // Disable logging anything else. + Adldap::setLogger(null); +} catch (\Adldap\Connections\BindException $e) { + // +} +``` + +## Logged Information + +Here is a list of events that are logged along with the information included: + +| Authentication Events | Logged | +|---|---| +| `Adldap\Auth\Events\Attempting` | `LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Attempting - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org` | +| `Adldap\Auth\Events\Binding` |` LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Binding - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org` | +| `Adldap\Auth\Events\Bound` | `LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Bound - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org` | +| `Adldap\Auth\Events\Passed` | `LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Passed - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org` | +| `Adldap\Auth\Events\Failed` | `LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Failed - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org - Result: Invalid Credentials` | + +| Model Events | Logged | +|---|---| +| `Adldap\Models\Events\Saving` | `LDAP (ldap://192.168.1.1:389) - Operation: Saving - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` | +| `Adldap\Models\Events\Saved` | `LDAP (ldap://192.168.1.1:389) - Operation: Saved - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` | +| `Adldap\Models\Events\Creating` | `LDAP (ldap://192.168.1.1:389) - Operation: Creating - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` | +| `Adldap\Models\Events\Created` | `LDAP (ldap://192.168.1.1:389) - Operation: Created - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` | +| `Adldap\Models\Events\Updating` | `LDAP (ldap://192.168.1.1:389) - Operation: Updating - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` | +| `Adldap\Models\Events\Updated` | `LDAP (ldap://192.168.1.1:389) - Operation: Updated - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` | +| `Adldap\Models\Events\Deleting` | `LDAP (ldap://192.168.1.1:389) - Operation: Deleting - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` | +| `Adldap\Models\Events\Deleted` | `LDAP (ldap://192.168.1.1:389) - Operation: Deleted - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` | diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/media/bg.svg b/data/web/inc/lib/vendor/adldap2/adldap2/docs/media/bg.svg new file mode 100644 index 0000000000..e70e86334b --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/media/bg.svg @@ -0,0 +1 @@ +๏ปฟ \ No newline at end of file diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/computer.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/computer.md new file mode 100644 index 0000000000..e3057abf74 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/computer.md @@ -0,0 +1,32 @@ +# The Computer Model + +> **Note**: This model contains the traits `HasDescription`, `HasLastLogonAndLogOff` & `HasCriticalSystemObject`. +> For more information, visit the documentation: +> +> [HasDescription](/models/traits/has-description.md), +> [HasLastLogonAndLogOff](/models/traits/has-last-login-last-logoff.md), +> [HasCriticalSystemObject](/models/traits/has-critical-system-object.md) + +## Methods + +```php +$computer = $provider->search()->computers()->find('ACME-EXCHANGE'); + +// Returns 'Windows Server 2003' +$computer->getOperatingSystem(); + +// Returns '5.2 (3790)'; +$computer->getOperatingSystemVersion(); + +// Returns 'Service Pack 1'; +$computer->getOperatingSystemServicePack(); + +// Returns 'ACME-DESKTOP001.corp.acme.org' +$computer->getDnsHostName(); + +$computer->getLastLogOff(); + +$computer->getLastLogon(); + +$computer->getLastLogonTimestamp(); +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/contact.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/contact.md new file mode 100644 index 0000000000..e42c8f7b7e --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/contact.md @@ -0,0 +1,13 @@ +# The Contact Model + +The Contact model extends from the base `Adldap\Models\Model` class and contains +no specific methods / attributes that are limited to it. + +## Creation + +```php +// Adldap\Models\Contact +$contact = $provider->make()->contact([ + 'cn' => 'Suzy Doe', +]); +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/container.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/container.md new file mode 100644 index 0000000000..132b549835 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/container.md @@ -0,0 +1,24 @@ +# The Container Model + +> **Note**: This model contains the trait `HasDescription` & `HasCriticalSystemObject`. +> For more information, visit the documentation: +> +> [HasDescription](/models/traits/has-description.md), +> [HasCriticalSystemObject](/models/traits/has-critical-system-object.md), + +## Creation + +```php +// Adldap\Models\Container +$container = $provider->make()->container([ + 'cn' => 'VPN Users', +]); +``` + +## Methods + +The `Container` model contains only one unique method. + +```php +$flags = $container->getSystemFlags(); +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/group.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/group.md new file mode 100644 index 0000000000..cf7c6d4f67 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/group.md @@ -0,0 +1,253 @@ +# The Group Model + +> **Note**: This model contains the trait `HasMemberOf`. +> For more information, visit the documentation: +> +> [HasMemberOf](/models/traits/has-member-of.md) + +## Creation + +```php +// Adldap\Models\Group +$group = $provider->make()->group([ + 'cn' => 'Managers', +]); + +// Create group's DN through the DN Builder: +$group = $provider->make()->group(); + +$dn = $group->getDnBuilder(); + +$dn->addOu('Workstation Computers'); + +$dn->addCn("Managers"); + +$group->setDn($dn); + +// Or set the DN manually: +$ou->setDn('cn=Managers,ou=Workstation Computers,dc=test,dc=local,dc=com'); + +$group->save(); +``` + +## Getting a groups members + +When you receive a `Group` model instance, it will contain a `member` +attribute which contains the distinguished names of all +the members inside the group. + +```php +$group = $provider->search()->groups()->first(); + +foreach ($group->members as $member) { + echo $member; // 'cn=John Doe,dc=corp,dc=acme,dc=org' +} +``` + +But this might not be useful, since we might actually want the models for each member. + +This can be easily done with the `getMembers()` method on the group. + +```php +$group = $provider->search()->groups()->first(); + +foreach ($group->getMembers() as $member) { + echo get_class($member); // Instance of `Adldap\Models\Model` + + echo $member->getCommonName(); +} +``` + +> **Note**: You should be aware however, that calling the `getMembers()` method will +> query your `AD` server for **every** member contained in the group to retrieve +> its model. For larger group sets it may be worth paginating them. + + +### Paginating Group Members + +The group you're looking for might contain hundreds / thousands of members. + +In this case, your server might only return you a portion of the groups members. + +To get around this limit, you need to ask your server to paginate the groups members through a select: + +```php +$group = $provider->search()->groups()->select('member;range=0-500')->first(); + +foreach ($group->members as $member) { + // We'll only have 500 members in this query. +} +``` + +Now, when we have the group instance, we'll only have the first `500` members inside this group. +However, calling the `getMembers()` method will automatically retrieve the rest of the members for you: + +```php +$group = $provider->search()->groups()->select('member;range=0-500')->first(); + +foreach ($group->getMembers() as $member) { + // Adldap will automatically retrieve the next 500 + // records until it's retrieved all records. + $member->getCommonName(); +} +``` + +> **Note**: Groups containing large amounts of users (1000+) will require +> more memory assigned to PHP. Your mileage will vary. + +#### Paginating large sets of Group Members + +When requesting group members from groups that contain a large amount of members +(typically over 1000), you may receive PHP memory limit errors due to +the large amount of the objects being created in the request. + +To resolve this, you will need to retrieve the members manually. However using +this route you will only be able to retrieve the members distinguished names. + +```php +$from = 0; +$to = 500; +$range = "member;range=$from-$to"; + +// Retrieve the group. +$group = $provider->search()->select($range)->raw()->find('Accounting'); + +// Remove the count from the member array. +unset($group[$range]['count']); + +// The array of group members distinguished names. +$members = $group[$range]; + +foreach ($members as $member) { + echo $member; // 'cn=John Doe,dc=acme,dc=org' +} +``` + +You can then encapsulate the above example into a recursive function to retrieve the remaining group members. + +## Getting only a groups member names + +To retrieve only the names of the members contained in a group, call the `getMemberNames()` method: + +```php +foreach ($group->getMemberNames() as $name) { + // Returns 'John Doe' + echo $name; +} +``` + +> **Note**: This method does not query your server for each member to retrieve its name. It +> only parses the distinguished names from the groups `member` attribute. This means that +> if you have paginated group members, you will need to perform another query yourself +> to retrieve the rest of the member names (or just call the `getMembers()` method). + +## Setting Group Members + +To set members that are apart of the group, you can perform this in two ways: + +> **Note**: Remember, this will remove **all** pre-existing members, and set the new given members on the group. + +```php +$members = [ + 'cn=John Doe,dc=corp,dc=acme,dc=org', + 'cn=Jane Doe,dc=corp,dc=acme,dc=org', +]; + +$group->setMembers($members); + +$group->save(); +``` + +Or manually: + +```php +$group->member = [ + 'cn=John Doe,dc=corp,dc=acme,dc=org', + 'cn=Jane Doe,dc=corp,dc=acme,dc=org', +]; + +$group->save(); +``` + +## Adding One Member + +To add a single member to a group, use the `addMember()` method: + +> **Note**: You do not need to call the `save()` method after adding a +> member. It's automatically called so you can determine +> if the member was successfully added. + +```php +// We can provide a model, or just a plain DN of the new member +$user = $provider->search()->users()->first(); + +if ($group->addMember($user)) { + // User was successfully added to the group! +} + +// Or + +$user = 'cn=John Doe,dc=corp,dc=acme,dc=org'; + +if ($group->addMember($user)) { + // +} +``` + +## Adding Multiple Group Members + +To add multiple members to a group, use the `addMembers()` method: + +> **Note**: You do not need to call the `save()` method after adding +> members. It's automatically called so you can determine +> if the members were successfully added. + +```php +$members = [ + 'cn=John Doe,dc=corp,dc=acme,dc=org', + 'cn=Jane Doe,dc=corp,dc=acme,dc=org', +]; + +$group->addMembers($members); + +// Or + +$user = $provider->search()->users()->first(); + +if ($group->addMembers($user)) { + // +} +``` + +## Removing One Member + +To remove a single member to a group, use the `removeMember()` method: + +```php +// We can provide a model, or just a plain DN of the existing member +$group = $provider->search()->groups()->first(); + +$member = $group->getMembers()->first(); + +if ($group->removeMember($member)) { + // Member was successfully removed from the group! +} + +// Or + +$user = 'cn=John Doe,dc=corp,dc=acme,dc=org'; + +if ($group->removeMember($user)) { + // +} +``` + +## Removing All Members + +To remove all members, use the `removeMembers()` method: + +```php +if ($group->removeMembers()) { + // All members were successfully removed! +} +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/model.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/model.md new file mode 100644 index 0000000000..fbcecc8b96 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/model.md @@ -0,0 +1,655 @@ +# Creating / Updating + +## Introduction + +Adldap2 implements the [ActiveRecord](https://en.wikipedia.org/wiki/Active_record_pattern) pattern. +This means that each LDAP record in your directory is represented as it's own model instance. + +## Creating + +Creating LDAP entries manually is always a pain, but Adldap2 makes it effortless. Let's get started. + +When you have a provider instance, call the `make()` method. This returns an `Adldap\Models\Factory` instance: + +```php +$factory = $provider->make(); +``` + +Or you can chain all methods if you'd prefer: + +```php +$user = $provider->make()->user(); +``` + +### Available Make Methods + +When calling a make method, all of them accept an `$attributes` parameter +to fill the model with your specified attributes. + +```php +// Adldap\Models\User +$user = $provider->make()->user([ + 'cn' => 'John Doe', +]); + +// Adldap\Models\Computer +$computer = $provider->make()->computer([ + 'cn' => 'COMP-101', +]); + +// Adldap\Models\Contact +$contact = $provider->make()->contact([ + 'cn' => 'Suzy Doe', +]); + +// Adldap\Models\Container +$container = $provider->make()->container([ + 'cn' => 'VPN Users', +]); + +// Adldap\Models\Group +$group = $provider->make()->group([ + 'cn' => 'Managers', +]); + +// Adldap\Models\OrganizationalUnit +$ou = $provider->make()->ou([ + 'name' => 'Acme', +]); +``` + +## Saving + +When you have any model instance, you can call the `save()` method to persist the +changes to your server. This method returns a `boolean`. For example: + +```php +$user = $provider->make()->user([ + 'cn' => 'New User', +]); + +if ($user->save()) { + // User was saved. +} else { + // There was an issue saving this user. +} +``` + +> **Note**: When a model is saved successfully (whether created or updated), the +> models attributes are re-synced in the background from your LDAP server. +> +> This allows you to perform other operations during the same +> request that require an existing model. + +### Creating (Manually) + +If you are sure the model **does not exist** already inside your LDAP directory, you can use the `create()` method: + +```php +$user = $provider->make()->user([ + 'cn' => 'New User', +]); + +if ($user->create()) { + // User was created. +} else { + // There was an issue creating this user. +} +``` + +> **Note**: When you call the create method, if the model does not have a +> distinguished name, one will automatically be generated for you using your +> `base_dn` set in your configuration and the models common name. + +### Updating (Manually) + +If you are sure the model **does exist** already inside your LDAP directory, you can use the `update()` method: + +```php +$user = $provider->search()->whereEquals('cn', 'John Doe')->firstOrFail(); + +$user->displayName = 'Suzy Doe'; + +if ($user->update()) { + // User was updated. +} else { + // There was an issue updating this user. +} +``` + +## Checking Existence + +If you need to check the existence of a model, use the property `exists`. + +How does it know if the model exists in your LDAP directory? Well, when models are constructed from +search results, the `exists` property on the model is set to `true`. + +```php +$user = $provider->search()->find('jdoe'); + +$user->exists; // Returns true. + +if ($user->delete()) { + $user->exists; // Returns false. +} +``` + +If a model is created successfully, the `exists` property is set to `true`: + +```php +$user = $provider->make()->user([ + 'cn' => 'John Doe', +]); + +$user->exists; // Returns false. + +if ($user->save()) { + $user->exists; // Returns true. +} +``` + +## Attributes + +Due to LDAPs multi-valued nature, all LDAP attributes inside a model have their own array. + +For example, a models attributes may contain the following: + +```php +var_dump($user->getAttributes()); + +// Returns: +/* +[ + 'cn' => [ + 0 => 'John Doe', + ], + 'sn' => [ + 0 => 'Doe', + ], + 'givenname' => [ + 0 => 'John' + ], + 'useraccountcontrol' => [ + 0 => 512 + ], + 'mail' => [ + 0 => 'jdoe@acme.org', + 1 => 'john-doe@acme.org', + ], + 'memberof' => [ + 0 => 'cn=Accountants,ou=Groups,dc=acme,dc=org', + 1 => 'cn=Employees,ou=Groups,dc=acme,dc=org', + 2 => 'cn=Users,ou=Groups,dc=acme,dc=org', + ], +] +*/ +``` + +You can notice in the above dumped array that each attribute contains +its own array with a value assigned to the first key. + +Since all models extend from the base class `Adldap\Models\Model`, there +are many useful methods that you can use on every model to easily +retrieve these attributes you're looking for. + +### Getting Attributes + +You can get attributes in a few ways: + +```php +// Returns an array all of the users attributes. +$user->getAttributes(); + +// Returns an array of all the users email addresses. +// Returns `null` if non-existent. +$user->getAttribute('mail'); + +// Returns the users first email address. +// Returns `null` if non-existent. +$user->getAttribute('mail', 0); + +// Returns the users first email address. +// Returns `null` if non-existent. +$user->getFirstAttribute('mail'); + +// Returns an array of all the users email addresses. +$user->mail; + +// Returns the users first email address. +$user->mail[0]; +``` + +#### Using a Getter + +Some attributes have methods for easier retrieval so you don't need to look up the LDAP attribute name. + +For example, to retrieve a users email address, use the method `getEmail()`: + +```php +$user->getEmail(); +``` + +##### Other Methods + +The following methods are available on all returned models: + +```php +// Returns the model's 'name' attribute. +$model->getName(); + +// Returns the model's 'cn' attribute. +$model->getCommonName(); + +// Returns the model's 'displayname' attribute. +$model->getDisplayName(); + +// Returns the model's 'samaccountname' attriubte. +$model->getAccountName(); + +// Returns the model's 'samaccounttype` attribute. +$model->getAccountType(); + +// Returns the model's 'whencreated` attribute. +$model->getCreatedAt(); + +// Returns the model's 'whencreated` attribute in a MySQL timestamp format. +$model->getCreatedAtDate(); + +// Returns the model's 'whencreated' attribute in unix time. +$model->getCreatedAtTimestamp(); + +// Returns the model's 'whenchanged` attribute. +$model->getUpdatedAt(); + +// Returns the model's 'whenchanged` attribute in a MySQL timestamp format. +$model->getUpdatedAtDate(); + +// Returns the model's 'whenchanged` attribute in unix time. +$model->getUpdatedAtTimestamp(); + +// Returns the model's 'objectclass' attribute. +$model->getObjectClass(); + +// Returns the model's root object category string. +$model->getObjectCategory(); + +// Returns the model's object category in an array. +$model->getObjectCategoryArray(); + +// Returns the model's object category distinguished name. +$model->getObjectCategoryDn(); + +// Returns the model's SID in binary. +$model->getObjectSid(); + +// Returns the model's GUID in binary. +$model->getObjectGuid(); + +// Returns the model's SID in a string. +$model->getConvertedSid(); + +// Returns the model's GUID in a string. +$model->getConvertedGuid(); + +// Returns the model's primary group ID. +$model->getPrimaryGroupId(); + +// Returns the model's 'instancetype' attribute. +$model->getInstanceType(); + +// Returns the model's 'maxpwdage' attribute. +$model->getMaxPasswordAge(); +``` + +For more documentation on specific getters, please take a look at the relevant model documentation. + +#### Getting Dirty (Modified) Attributes + +You can get a models modified attributes using the `getDirty()` method: + +```php +$user = $provider->search()->users()->find('john'); + +// Returns array [0 => 'John Doe'] +var_dump($user->cn); + +$user->setAttribute('cn', 'Jane Doe'); + +// Returns array ['cn' => [0 => 'Jane Doe']] +var_dump($user->getDirty()); + +// The attribute has been modified - returns array [0 => 'Jane Doe'] +var_dump($user->cn); +``` + +The method returns an array with the key being the modified attribute, +and the array being the new values of the attribute. + +#### Getting Original (Unmodified) Attributes + +You can get a models original attributes using the `getOriginal()` method: + +```php +$user = $provider->search()->users()->find('john'); + +// Returns array [0 => 'John Doe'] +var_dump($user->cn); + +$user->setAttribute('cn', 'Jane Doe'); + +// The attribute has been modified - returns array [0 => 'Jane Doe'] +var_dump($user->cn); + +// Retrieving the original value - returns array [0 => 'John Doe'] +var_dump($user->getOriginal()['cn']); +``` + +> **Note**: Keep in mind, when you `save()` a model, the models original +> attributes will be re-synchronized to the models new attributes. + +### Setting Attributes + +Just like getting model attributes, there's multiple ways of setting attributes as well: + +```php +// Setting via method: +$user->setAttribute('cn', 'John Doe'); + +// Specifying a subkey for overwriting specific attributes: +$user->setAttribute('mail', 'other-mail@mail.com', 0); + +// Setting the first attribute: +$user->setFirstAttribute('mail', 'jdoe@mail.com'); + +// Setting via property: +$user->cn = 'John Doe'; + +// Mass setting attributes: +$user->fill([ + 'cn' => 'John Doe', + 'mail' => 'jdoe@mail.com', +]); +``` + +#### Setting Boolean Attributes + +When setting boolean attribute values, you cannot use `0` / `1` / `true` / `false` as these +are simply converted to integer values when saving and your LDAP server will +likely return an error for doing so on certain attributes. + +You will need to use the string versions of the boolean (`'TRUE'` / `'FALSE'`) for the +boolean attribute to be set properly on your LDAP server. + +Here's an example: + +```php +$user->setFirstAttribute('msExchHideFromAddressLists', 'TRUE'); + +$user->save(); +``` + +### Creating Attributes + +To create an attribute that does not exist on the model, you can set it like a regular property: + +```php +$user = $provider->search()->whereEquals('cn', 'John Doe')->firstOrFail(); + +$user->new = 'New Attribute'; + +$user->save(); +``` + +If the set attribute does not exist on the model already, +it will automatically be created when you call the `save()` method. + +If you'd like manually create new attributes individually, call the `createAttribute($attribute, $value)` method: + +```php +if ($user->createAttribute('new', 'New Attribute')) { + // Attribute created. +} +``` + +### Updating Attributes + +To modify an attribute you can either use a setter method, or by setting it manually: + +> **Note**: You can also utilize setters to create new attributes if your model does not already have the attribute. + +```php +$user = $provider->search()->whereEquals('cn', 'John Doe')->firstOrFail(); + +$user->cn = 'New Name'; + +// Or use a setter: + +$user->setCommonName('New Name'); + +$user->save(); +``` + +If you'd like to update attributes individually, call the `updateAttribute($attribute, $value)` method: + +```php +if ($user->updateAttribute('cn', 'New Name')) { + // Successfully updated attribute. +} +``` + +### Removing Attributes + +To remove attributes, set the attribute to `NULL`: + +```php +$user->cn = null; + +$user->save(); +``` + +Or, you can call the `deleteAttribute($attribute)` method: + +```php +if ($user->deleteAttribute('cn')) { + // Attribute has been deleted. +} +``` + +### Checking Attributes + +#### Checking Existence of Attributes + +To see if a model contains an attribute, use the method `hasAttribute()`: + +```php +// Checking if a base attribute exists: +if ($user->hasAttribute('mail')) { + // This user contains an email address. +} + +// Checking if a sub attribute exists, by key: +if ($user->hasAttribute('mail', 1)) { + // This user contains a second email address. +} +``` + +#### Counting the Models Attributes + +To retrieve the total number of attributes, use the method `countAttributes()`: + +```php +$count = $user->countAttributes(); + +var_dump($count); // Returns int +``` + +#### Checking if a Model is contained in an OU + +To check if a model is located inside an OU, use the `inOu()` method: + +```php +if ($model->inOu('User Accounts')) { + // This model is inside the 'User Accounts' OU. +} +``` + +You can also use an OU model instance: + +```php +$serviceAccounts = $provider->search()->ous()->find('Service Accounts'); + +if ($model->inOu($serviceAccounts)) { + // This model is inside the 'Service Accounts' OU. +} +``` + +#### Checking if a Model is Writable + +To check if the model can be written to, use the method `isWritable()`: + +```php +if ($model->isWritable()) { + // You can modify this model. +} +``` + +### Force Re-Syncing A Models Attributes + +If you need to forcefully re-sync a models attributes, use the method `syncRaw()`: + +```php +$user->syncRaw(); +``` + +> **Note**: This will query your LDAP server for the current model, and re-synchronize +> it's attributes. This is only recommended if your creating / updating / deleting +> attributes manually through your LDAP connection. + +## Moving / Renaming + +To move a user from one DN or OU to another, use the `move()` method: + +> **Note**: The `move()` method is actually an alias for the `rename()` method. + +```php +// New parent distiguished name. +$newParentDn = 'OU=New Ou,DC=corp,DC=local'; + +if ($user->move($newParentDn)) { + // User was successfully moved to the new OU. +} +``` + +You can also provide a model to move the child model into: + +```php +// New parent OU. +$newParentOu = $provider->search()->ous()->find('Accounting'); + +if ($user->move($newParentOu)) { + // User was successfully moved to the new OU. +} +``` + +If you would like to keep the models old RDN along side their new RDN, pass in false in the second parameter: + +```php +// New parent distiguished name. +$newParentDn = 'OU=New Ou,DC=corp,DC=local'; + +if ($user->move($newParentDn, $deleteOldRdn = false)) { + // User was successfully moved to the new OU, + // and their old RDN has been left in-tact. +} +``` + +To rename a users DN, just pass in their new relative distinguished name in the `rename()` method: + +```php +$newRdn = 'cn=New Name'; + +if ($user->rename($newRdn)) { + // User was successfully renamed. +} +``` + +## Deleting + +To delete a model, just call the `delete()` method: + +```php +$user = $provider->search()->whereEquals('cn', 'John Doe')->firstOrFail(); + +echo $user->exists; // Returns true. + +if ($user->delete()) { + // Successfully deleted user. + + echo $user->exists; // Returns false. +} +``` + +## Extending + +> **Note**: This feature was introduced in `v8.0.0`. + +To use your own models, you will need to create a new [Schema](../schema.md). + +Once you have created your own schema, you must insert it inside the construct of your provider. + +Let's walk through this process. + +First we'll create our model we'd like to extend / override: + +> **Note**: Your custom model **must** extend from an existing Adldap2 model. +> This is due to methods and attributes that only exist on these classes. + +```php +namespace App\Ldap\Models; + +use Adldap\Models\User as Model; + +class User extends Model +{ + public function getCommonName() + { + // Overriding model method. + } +} +``` + +Now, we'll create our custom schema and return our models class name: + +```php +namespace App\Ldap\Schemas; + +use App\Ldap\Models\User; + +class LdapSchema extends ActiveDirectory +{ + public function userModel() + { + return User::class; + } +} +``` + +Finally, when we create a provider, we need to insert our Schema into the configuration: + +```php +$config = [ + 'hosts' => ['...'], + + 'username' => 'admin', + 'password' => 'P@ssword', + + 'schema' => MyApp\LdapSchema::class, +]; + +$ad = new Adldap($config); + +$provider = $ad->connect(); + +// If `jdoe` exists, your custom model will be returned. +$user = $provider->search()->users()->find('jdoe'); +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/organization.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/organization.md new file mode 100644 index 0000000000..57c00e33d3 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/organization.md @@ -0,0 +1,19 @@ +# The Organization Model + +The Organization model extends from the base `Adldap\Models\Model` class and contains +no specific methods / attributes that are limited to it. + +## Creation + +```php +// Adldap\Models\Organization +$org = $provider->make()->organization([ + 'o' => 'Some Company', +]); + +// Set the DN manually: + +$org->setDn('o=Some Company,dc=test,dc=local,dc=com'); + +$org->save(); +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/ou.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/ou.md new file mode 100644 index 0000000000..ec473b1a19 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/ou.md @@ -0,0 +1,27 @@ +# The OrganizationalUnit Model + +The OrganizationalUnit model extends from the base `Adldap\Models\Model` class and contains +no specific methods / attributes that are limited to it. + +## Creation + +```php +// Adldap\Models\OrganizationalUnit +$ou = $provider->make()->ou([ + 'name' => 'Workstation Computers', +]); + +// Generate the OU's DN through the DN Builder: + +$dn = $ou->getDnBuilder(); + +$dn->addOu('Workstation Computers'); + +$ou->setDn($dn); + +// Or set the DN manually: + +$ou->setDn('ou=Workstation Computers,dc=test,dc=local,dc=com'); + +$ou->save(); +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/printer.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/printer.md new file mode 100644 index 0000000000..4adc6664a0 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/printer.md @@ -0,0 +1,49 @@ +# The Printer Model + +## Methods + +```php +$printer->getPrinterName(); + +$printer->getPrinterShareName(); + +$printer->getMemory(); + +$printer->getUrl(); + +$printer->getLocation(); + +$printer->getServerName(); + +$printer->getColorSupported(); + +$printer->getDuplexSupported(); + +$printer->getMediaSupported(); + +$printer->getStaplingSupported(); + +$printer->getPrintBinNames(); + +$printer->getPrintMaxResolution(); + +$printer->getPrintOrientations(); + +$printer->getDriverName(); + +$printer->getDriverVersion(); + +$printer->getPriority(); + +$printer->getPrintStartTime(); + +$printer->getPrintEndTime(); + +$printer->getPortName(); + +$printer->getVersionNumber(); + +$printer->getPrintRate(); + +$printer->getPrintRateUnit(); +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/root-dse.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/root-dse.md new file mode 100644 index 0000000000..e798c0eb1f --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/root-dse.md @@ -0,0 +1,33 @@ +# The RootDse Model + +## Getting the Root DSE + +To get the Root DSE of your LDAP server, call the `getRootDse()` method off a new search: + +```php +$rootDse = $provider->search()->getRootDse(); +``` + +## Getting the schema naming context + +To get the Root DSE schema naming context, call the `getSchemaNamingContext()`: + +```php +$rootDse = $provider->search()->getRootDse(); + +$context = $rootDse->getSchemaNamingContext(); + +// Returns 'cn=Schema,cn=Configuration,dc=corp,dc=acme,dc=org' +echo $context; +``` + +## Getting the root domain naming context + +To get the Root DSE domain naming context, call the `getRootDomainNamingContext()`: + +```php +$context = $rootDse->getRootDomainNamingContext(); + +// Returns 'dc=corp,dc=acme,dc=org' +echo $context; +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-critical-system-object.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-critical-system-object.md new file mode 100644 index 0000000000..095acad6f2 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-critical-system-object.md @@ -0,0 +1,13 @@ +# HasCriticalSystemObject Trait + +Models that contain this trait, have the `isCriticalSystemObject` attribute. + +There is only one method that accompanies this trait: + +```php +if ($model->isCriticalSystemObject()) { + + // + +} +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-description.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-description.md new file mode 100644 index 0000000000..5c26d02b52 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-description.md @@ -0,0 +1,11 @@ +# HasDescription Trait + +Models that contain this trait, have the `description` attribute. + +There are only two methods that accompany this trait: + +```php +$model->getDescription(); + +$model->setDescription('The models description'); +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-last-login-last-logoff.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-last-login-last-logoff.md new file mode 100644 index 0000000000..76078c5121 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-last-login-last-logoff.md @@ -0,0 +1,16 @@ +# HasLastLoginAndLastLogoff Trait + +Models that contain this trait have the `lastlogoff`, `lastlogon` and `lastlogontimestamp` attributes. + +## Methods + +```php +// Returns the models's last log off attribute. +$computer->getLastLogOff(); + +// Returns the models's last log on attribute. +$computer->getLastLogon(); + +// Returns the models's last log on timestamp attribute. +$computer->getLastLogonTimestamp(); +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-member-of.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-member-of.md new file mode 100644 index 0000000000..d3063df9e3 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-member-of.md @@ -0,0 +1,166 @@ +# HasMemberOf Trait + +Models that contain this trait, have the ability to be apart of a group. + +There's many helpful methods to assist you in all of the operations related to group membership, let's get started! + +## Retrieving Groups + +To retrieve the groups that a model is apart of, call the `getGroups()` method: + +```php +$user = $provider->search()->users()->find('jdoe'); + +$groups = $user->getGroups(); + +foreach ($groups as $group) { + + $group->getCommonName(); // ex. 'Accounting' + +} +``` + +We can also pass in specific fields we need from the returned groups to speed up our queries. + +For example, if we only need the groups common name: + +```php +// Group models will be returned with only their common name. +$groups = $user->getGroups(['cn']); +``` + +However, calling `getGroups()` will only retrieve the models immediate groups (non-recursive). + +To retrieve nested groups, pass in `true` into the second parameter: + +```php +$groups = $user->getGroups([], $recursive = true); +``` + +## Retrieve Group Names + +If you only want the models group names, call the `getGroupNames()` method: + +```php +$names = $user->getGroupNames(); + +foreach ($names as $name) { + + echo $name; // ex. 'Accounting' + +} +``` + +However, this method will also retrieve only the immediate groups names +much like the `getGroups()` method. You'll need to pass in `true` in +the first parameter to retrieve results recursively. + +```php +$names = $user->getGroupNames($recursive = true); +``` + +## Checking if the Model is apart of a Group + +To check if a model is apart of a certain group, use the `inGroup()` method: + +```php +$group = $provider->search()->groups()->find('Office'); + +if ($user->inGroup($group)) { + + // + +} +``` + +You can also check for multiple memberships by passing in an array of groups: + +```php +$groups = $provider->search()->findManyBy('cn', ['Accounting', 'Office'])); + +if ($user->inGroup($groups->toArray()) { + + // This user is apart of the 'Accounting' and 'Office' group! + +} +``` + +> **Note**: Much like the other methods above, you'll need to provide a `$recursive` +> flag to the `inGroup()` method if you'd like recursive results included. + +We can also provide distinguished names instead of Group model instances: + +```php +$dns = [ + 'cn=Accounting,ou=Groups,dc=acme,dc=org', + 'cn=Office,ou=Groups,dc=acme,dc=org', +]; + +if ($user->inGroup($dns, $recursive = true)) { + + // + +} +``` + +Or, we can also just provide the name(s) of the group(s). + +```php +$names = [ + 'Accounting', + 'Office', +]; + +if ($user->inGroup($names, $recursive = true)) { + + // + +} +``` + +## Adding a Group + +To add the model to a specific group, call the `addGroup()` method: + +```php +$group = $provider->search()->groups()->find('Accounting'); + +// You can either provide a Group model: +if ($user->addGroup($group)) { + + // + +} + +// Or a Groups DN: +if ($user->addGroup('cn=Accounting,ou=Groups,dc=acme,dc=org')) { + + // + +} +``` + +> **Note**: You do not need to call the `save()` method for adding / removing groups. +> This is done automatically so you can perform clean `if` statements on the method. + +## Removing a Group + +To remove the model from a specific group, call the `removeGroup()` method: + +```php +$group = $user->getGroups()->first(); + +// You can either provide a Group model: +if ($user->removeGroup($group)) { + + // + +} + +// Or the groups DN: +if ($user->removeGroup('cn=Accounting,ou=Office Groups,dc=acme,dc=org')) { + + // + +} +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/user.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/user.md new file mode 100644 index 0000000000..65576e8dd6 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/models/user.md @@ -0,0 +1,180 @@ +# The User Model + +> **Note**: This model contains the trait `HasMemberOf`. For more information, visit the documentation: +> [HasMemberOfTrait](/models/traits/has-member-of.md) + +## Creating + +> **Note**: If you need to create users with passwords, SSL or TLS **must** be enabled on your configured connection. +> +> The password you enter for the user **must** also obey your LDAP servers password requirements, +> otherwise you will receive a "Server is unwilling to perform" LDAP exception upon saving. + +```php +// Construct a new User model instance. +$user = $provider->make()->user(); + +// Create the users distinguished name. +// We're adding an OU onto the users base DN to have it be saved in the specified OU. +$dn = $user->getDnBuilder()->addOu('Users'); // Built DN will be: "CN=John Doe,OU=Users,DC=acme,DC=org"; + +// Set the users DN, account name. +$user->setDn($dn); +$user->setAccountName('jdoe'); +$user->setCommonName('John Doe'); + +// Set the users password. +// NOTE: This password must obey your AD servers password requirements +// (including password history, length, special characters etc.) +// otherwise saving will fail and you will receive an +// "LDAP Server is unwilling to perform" message. +$user->setPassword('correct-horse-battery-staple'); + +// Get a new account control object for the user. +$ac = $user->getUserAccountControlObject(); + +// Mark the account as enabled (normal). +$ac->accountIsNormal(); + +// Set the account control on the user and save it. +$user->setUserAccountControl($ac); + +// Save the user. +$user->save(); + +// All done! An enabled user will be created and is ready for use. +``` + +## Methods + +There's a ton of available methods for the User model. Below is a list for a quick reference. + +> **Note**: Don't see a method for an LDAP attribute? Create an issue and let us know! + +```php +// Get the users display name. +$user->getDisplayName(); + +// Get the users first email address. +$user->getEmail(); + +// Get the users title. +$user->getTitle(); + +// Get the users department. +$user->getDepartment(); + +// Get the users first name. +$user->getFirstName(); + +// Get the users last name. +$user->getLastName(); + +// Get the users info. +$user->getInfo(); + +// Get the users initials. +$user->getInitials(); + +// Get the users country. +$user->getCountry(); + +// Get the users street address. +$user->getStreetAddress(); + +// Get the users postal code. +$user->getPostalCode(); + +// Get the users physical delivery office name. +$user->getPhysicalDeliveryOfficeName(); + +// Get the users phone number. +$user->getTelephoneNumber(); + +// Get the users locale. +$user->getLocale(); + +// Get the users company. +$user->getCompany(); + +// Get the users other email addresses. +$user->getOtherMailbox(); + +// Get the users home mailbox database location (stored as a distinguished name). +$user->getHomeMdb(); + +// Get the users email nickname. +$user->getMailNickname(); + +// Get the users principal name. +$user->getUserPrincipalName(); + +// Get the users proxy email addresses. +$user->getProxyAddresses(); + +// Get the users failed login attempts. +$user->getBadPasswordCount(); + +// Get the users last failed login attempt timestamp. +$user->getBadPasswordTime(); + +// Get the users last password change timestamp. +$user->getPasswordLastSet(); + +// Get the users last password change timestamp in unix time. +$user->getPasswordLastSetTimestamp(); + +// Get the users last password change timestamp in MySQL date format. +$user->getPasswordLastSetDate(); + +// Get the users lockout time. +$user->getLockoutTime(); + +// Get the users user account control integer. +$user->getUserAccountControl(); + +// Get the users roaming profile path. +$user->getProfilePath(); + +// Get the users legacy exchange distinguished name. +$user->getLegacyExchangeDn(); + +// Get the users account expiry timestamp. +$user->getAccountExpiry(); + +// Get the boolean that determines whether to show this user in the global address book. +$user->getShowInAddressBook(); + +// Get the users thumbnail photo. +$user->getThumbnail(); + +// Get the users thumbnail photo (base64 encoded for HTML tags). +$user->getThumbnailEncoded(); + +// Get the users jpeg photo. +$user->getJpegPhoto(); + +// Get the users jpeg photo (base64 encoded for HTML tags). +$user->getJpegPhotoEncoded(); + +// Get the users manager. +$user->getManager(); + +// Get the users employee ID. +$user->getEmployeeId(); + +// Get the users employee number. +$user->getEmployeeNumber(); + +// Get the users employee type +$user->getEmployeeType(); + +// Get the users room number. +$user->getRoomNumber(); + +// Get the users department number. +$user->getDepartmentNumber(); + +// Get the users personal title. +$user->getPersonalTitle(); +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/readme.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/readme.md new file mode 100644 index 0000000000..00eaab7c84 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/readme.md @@ -0,0 +1,115 @@ +# Introduction + +## What is Adldap2? + +Adldap2 is a PHP LDAP package that allows you to: + +1. Easily manage multiple LDAP connections at once +2. Perform authentication +3. Search your LDAP directory with a fluent and easy to use query builder +4. Create / Update / Delete LDAP entities with ease +5. And more + +## History of Adldap2 + +Adldap2 was originally created as a fork of the original LDAP library [adLDAP](https://github.com/adldap/adLDAP) due to bugs, and it being completely abandoned. + +Adldap2 contains absolutely no similarities to the original repository, and was built to be as easily accessible as possible, with great documentation, and easily understandable syntax. + +Much of the API was constructed with Ruby's ActiveRecord and Laravel's Eloquent in mind, and to be an answer to the question: + +> _Why can't we use LDAP like we use a database?_ + +## Why should you use Adldap2? + +Working with LDAP in PHP can be a messy and confusing endeavor, especially when using multiple connections, creating and managing entities, performing moves, resetting passwords, and performing ACL modifications to user accounts. + +Wrapper classes for LDAP are usually always created in PHP applications. + +Adldap2 allows you to easily manage the above problems without reinventing the wheel for every project. + +## Implementations + +- [Laravel](https://github.com/Adldap2/Adldap2-Laravel) + +## Quick Start + +Install the package via `composer`: + +``` +composer require adldap2/adldap2 +``` + +Use Adldap2: + +```php +// Construct new Adldap instance. +$ad = new \Adldap\Adldap(); + +// Create a configuration array. +$config = [ + // An array of your LDAP hosts. You can use either + // the host name or the IP address of your host. + 'hosts' => ['ACME-DC01.corp.acme.org', '192.168.1.1'], + + // The base distinguished name of your domain to perform searches upon. + 'base_dn' => 'dc=corp,dc=acme,dc=org', + + // The account to use for querying / modifying LDAP records. This + // does not need to be an admin account. This can also + // be a full distinguished name of the user account. + 'username' => 'admin@corp.acme.org', + 'password' => 'password', +]; + +// Add a connection provider to Adldap. +$ad->addProvider($config); + +try { + // If a successful connection is made to your server, the provider will be returned. + $provider = $ad->connect(); + + // Performing a query. + $results = $provider->search()->where('cn', '=', 'John Doe')->get(); + + // Finding a record. + $user = $provider->search()->find('jdoe'); + + // Creating a new LDAP entry. You can pass in attributes into the make methods. + $user = $provider->make()->user([ + 'cn' => 'John Doe', + 'title' => 'Accountant', + 'description' => 'User Account', + ]); + + // Setting a model's attribute. + $user->cn = 'John Doe'; + + // Saving the changes to your LDAP server. + if ($user->save()) { + // User was saved! + } +} catch (\Adldap\Auth\BindException $e) { + + // There was an issue binding / connecting to the server. + +} +``` + +## Versioning + +Adldap2 is versioned under the [Semantic Versioning](http://semver.org/) guidelines as much as possible. + +Releases will be numbered with the following format: + +`..` + +And constructed with the following guidelines: + +* Breaking backward compatibility bumps the major and resets the minor and patch. +* New additions without breaking backward compatibility bumps the minor and resets the patch. +* Bug fixes and misc changes bumps the patch. + +Minor versions are not maintained individually, and you're encouraged to upgrade through to the next minor version. + +Major versions are maintained individually through separate branches. diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/searching.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/searching.md new file mode 100644 index 0000000000..2837767e66 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/searching.md @@ -0,0 +1,662 @@ +# Searching + +## Introduction + +Using the Adldap2 query builder makes building LDAP queries feel effortless. + +It allows you to generate LDAP filters using a fluent and +convenient interface, similar to Eloquent in Laravel. + +> **Note:** The Adldap2 query builder escapes all fields & values +> given to its `where()` methods. There is no need to clean or +> escape strings before passing them into the query builder. + +## Creating a new Query + +To create a new search query, call the `search()` method on your connection provider instance: + +```php +$search = $provider->search(); +``` + +Or you can chain all your methods if you'd prefer: + +```php +$results = $provider->search()->where('cn', '=', 'John Doe')->get(); +``` + +## Selects + +> **Note:** Fields are case in-sensitive. For example, you can +> insert `CN`, `cn` or `cN`, they will return the same result. + +#### Selecting attributes + +Selecting only the LDAP attributes you need will increase the speed of your queries. + +```php +// Passing in an array of attributes +$search->select(['cn', 'samaccountname', 'telephone', 'mail']); + +// Passing in each attribute as an argument +$search->select('cn', 'samaccountname', 'telephone', 'mail'); +``` + +## Executing Searches + +#### Finding a specific record + +If you're trying to find a single record, but not sure what the record might be, use the `find()` method: + +```php +$record = $search->find('John Doe'); + +if ($record) { + // Record was found! +} else { + // Hmm, looks like we couldn't find anything... +} +``` + +> **Note**: Using the `find()` method will search for LDAP records using ANR +> (ambiguous name resolution) and return the first result. +> +> Since ActiveDirectory is the only LDAP distribution that supports ANR, +> an equivalent query will be created for other LDAP distributions +> that are not compatible. +> +> For a more fine-tuned search, use the `findBy()` method below. + +##### Finding a record (or failing) + +If you'd like to try and find a single record and throw an exception when it hasn't been +found, use the `findOrFail()` method: + +```php +try { + + $record = $search->findOrFail('John Doe'); + +} catch (Adldap\Models\ModelNotFoundException $e) { + // Record wasn't found! +} +``` + +#### Finding a record by a specific attribute + +If you're looking for a single record with a specific attribute, use the `findBy()` method: + +```php +// We're looking for a record with the 'samaccountname' of 'jdoe'. +$record = $search->findBy('samaccountname', 'jdoe'); +``` + +##### Finding a record by a specific attribute (or failing) + +If you'd like to try and find a single record by a specific attribute and throw +an exception when it cannot be found, use the `findByOrFail()` method: + +```php +try { + + $record = $search->findByOrFail('samaccountname', 'jdoe'); + +} catch (Adldap\Models\ModelNotFoundException $e) { + // Record wasn't found! +} +``` + +#### Finding a record by its distinguished name + +If you're looking for a single record with a specific DN, use the `findByDn()` method: + +```php +$record = $search->findByDn('cn=John Doe,dc=corp,dc=org'); +``` + +###### Finding a record by its distinguished name (or failing) + +If you'd like to try and find a single record by a specific DN and throw +an exception when it hasn't been found, use the `findByDnOrFail()` method: + +```php +try { + + $record = $search->findByDnOrFail('cn=John Doe,dc=corp,dc=org'); + +} catch (Adldap\Models\ModelNotFoundException $e) { + // Record wasn't found! +} +``` + +#### Retrieving results + +To get the results from a search, simply call the `get()` method: + +```php +$results = $search->select(['cn', 'samaccountname'])->get(); +``` + +> **Note**: Executed searches via the `get()` method will return them inside an +> `Illuminate\Support\Collection` instance (a glorified array), with allows +> you to utilize [some extremely handy methods](https://laravel.com/docs/collections). +> +> Executed searches via the `first()` method will return **a model instance only**. + +##### Retrieving the first record + +To retrieve the first record of a search, call the `first()` method: + +```php +$record = $search->first(); +``` + +> **Note**: If you are using `sortBy()`, calling `first()` will not take this into account. Sorts +> are performed **after** retrieving query results. If you would like the first record of +> a sorted result set, call `first()` on a `Collection` of returned models. + +###### Retrieving the first record (or failing) + +To retrieve the first record of a search or throw an exception when one isn't found, call the `firstOrFail()` method: + +```php +try { + + $record = $search->firstOrFail(); + +} catch (Adldap\Models\ModelNotFoundException $e) { + // Record wasn't found! +} +``` + +## Limit + +To limit the results records returned from your LDAP server and increase the +speed of your queries, you can use the `limit()` method: + +```php +// This will only return 5 records that contain the name of 'John': +$records = $search->where('cn', 'contains', 'John')->limit(5)->get(); +``` + +## Wheres + +To perform a where clause on the search object, use the `where()` function: + +```php +$search->where('cn', '=', 'John Doe'); +``` + +This query would look for a record with the common name of 'John Doe' and return the results. + +We can also perform a 'where equals' without including the operator: + +```php +$search->whereEquals('cn', 'John Doe'); +``` + +We can also supply an array of key - value pairs to quickly add multiple wheres: + +```php +$wheres = [ + 'cn' => 'John Doe', + 'samaccountname' => 'jdoe', +]; + +$search->where($wheres); +``` + +Or, if you require conditionals, you can quickly add multiple wheres with nested arrays: + +```php +$search->where([ + ['cn', '=', 'John Doe'], + ['manager', '!', 'Suzy Doe'], +]); +``` + +#### Where Starts With + +We could also perform a search for all objects beginning with the common name of 'John' using the `starts_with` operator: + +```php +$results = $provider->search()->where('cn', 'starts_with', 'John')->get(); + +// Or use the method whereStartsWith($attribute, $value): + +$results = $provider->search()->whereStartsWith('cn', 'John')->get(); +``` + +#### Where Ends With + +We can also search for all objects that end with the common name of `Doe` using the `ends_with` operator: + +```php +$results = $provider->search()->where('cn', 'ends_with', 'Doe')->get(); + +// Or use the method whereEndsWith($attribute, $value): + +$results = $provider->search()->whereEndsWith('cn', 'Doe')->get(); +``` + +#### Where Between + +To search for records between two values, use the `whereBetween` method. + +For the example below, we'll retrieve all users who were created between two dates: + +```php +$from = (new DateTime('October 1st 2016'))->format('YmdHis.0\Z'); +$to = (new DateTime('January 1st 2017'))->format('YmdHis.0\Z'); + +$users = $provider->search() + ->users() + ->whereBetween('whencreated', [$from, $to]) + ->get(); +``` + +#### Where Contains + +We can also search for all objects with a common name that contains `John Doe` using the `contains` operator: + +```php +$results = $provider->search()->where('cn', 'contains', 'John Doe')->get(); + +// Or use the method whereContains($attribute, $value): + +$results = $provider->search()->whereContains('cn', 'John Doe')->get(); +``` + +##### Where Not Contains + +You can use a 'where not contains' to perform the inverse of a 'where contains': + +```php +$results = $provider->search()->where('cn', 'not_contains', 'John Doe')->get(); + +// Or use the method whereNotContains($attribute, $value): + +$results = $provider->search()->whereNotContains('cn', 'John Doe'); +``` + +#### Where Has + +Or we can retrieve all objects that have a common name attribute using the wildcard operator (`*`): + +```php +$results = $provider->search()->where('cn', '*')->get(); + +// Or use the method whereHas($field): + +$results = $provider->search()->whereHas('cn')->get(); +``` + +This type of filter syntax allows you to clearly see what your searching for. + +##### Where Not Has + +You can use a 'where not has' to perform the inverse of a 'where has': + +```php +$results = $provider->search->where('cn', '!*')->get(); + +// Or use the method whereNotHas($field): + +$results = $provider->search()->whereNotHas($field)->get(); +``` + +## Or Wheres + +To perform an `or where` clause on the search object, use the `orWhere()` method. However, +please be aware this function performs differently than it would on a database. + +For example: + +```php +$results = $search + ->where('cn', '=', 'John Doe') + ->orWhere('cn', '=', 'Suzy Doe') + ->get(); +``` + +This query would return no results. Since we're already defining that the common name (`cn`) must equal `John Doe`, applying +the `orWhere()` does not amount to 'Look for an object with the common name as "John Doe" OR "Suzy Doe"'. This query would +actually amount to 'Look for an object with the common name that equals "John Doe" OR "Suzy Doe" + +To solve the above problem, we would use `orWhere()` for both fields. For example: + +```php +$results = $search + ->orWhere('cn', '=', 'John Doe') + ->orWhere('cn', '=', 'Suzy Doe') + ->get(); +``` + +Now, we'll retrieve both John and Suzy's LDAP records, because the common name can equal either. + +> **Note**: You can also use all `where` methods as an or where, for example: +> `orWhereHas()`, `orWhereContains()`, `orWhereStartsWith()`, `orWhereEndsWith()` + +## Dynamic Wheres + +To perform a dynamic where, simply suffix a `where` with the field you're looking for. + +This feature was directly ported from Laravel's Eloquent. + +Here's an example: + +```php +// This query: +$result = $search->where('cn', '=', 'John Doe')->first(); + +// Can be converted to: +$result = $search->whereCn('John Doe')->first(); +``` + +You can perform this on **any** attribute: + +```php +$result = $search->whereTelephonenumber('555-555-5555')->first(); +``` + +You can also chain them: + +```php +$result = $search + ->whereTelephonenumber('555-555-5555') + ->whereGivenname('John Doe') + ->whereSn('Doe') + ->first(); +``` + +You can even perform multiple dynamic wheres by separating your fields by an `And`: + +```php +// This would perform a search for a user with the +// first name of 'John' and last name of 'Doe'. +$result = $search->whereGivennameAndSn('John', 'Doe')->first(); +``` + +## Nested Filters + +By default, the Adldap2 query builder automatically wraps your queries in `and` / `or` filters for you. +However, if any further complexity is required, nested filters allow you +to construct any query fluently and easily. + +#### andFilter + +The `andFilter` method accepts a closure which allows you to construct a query inside of an `and` LDAP filter: + +```php +$query = $provider->search()->newQuery(); + +// Creates the filter: (&(givenname=John)(sn=Doe)) +$results = $query->andFilter(function (Adldap\Query\Builder $q) { + + $q->where('givenname', '=', 'John') + ->where('sn', '=', 'Doe'); + +})->get(); +``` + +The above query would return records that contain the first name `John` **and** the last name `Doe`. + +#### orFilter + +The `orFilter` method accepts a closure which allows you to construct a query inside of an `or` LDAP filter: + +```php +$query = $provider->search()->newQuery(); + + +// Creates the filter: (|(givenname=John)(sn=Doe)) +$results = $query->orFilter(function (Adldap\Query\Builder $q) { + + $q->where('givenname', '=', 'John') + ->where('sn', '=', 'Doe'); + +})->get(); +``` + +The above query would return records that contain the first name `John` **or** the last name `Doe`. + +#### notFilter + +The `notFilter` method accepts a closure which allows you to construct a query inside a `not` LDAP filter: + +```php +$query = $provider->search()->newQuery(); + +// Creates the filter: (!(givenname=John)(sn=Doe)) +$results = $query->notFilter(function (Adldap\Query\Builder $q) { + + $q->where('givenname', '=', 'John') + ->where('sn', '=', 'Doe'); + +})->get(); +``` + +The above query would return records that **do not** contain the first name `John` **or** the last name `Doe`. + +#### Complex Nesting + +The above methods `andFilter` / `orFilter` can be chained together and nested +as many times as you'd like for larger complex queries: + +```php +$query = $provider->search()->newQuery(); + +$query = $query->orFilter(function (Adldap\Query\Builder $q) { + $q->where('givenname', '=', 'John')->where('sn', '=', 'Doe'); +})->andFilter(function (Adldap\Query\Builder $q) { + $q->where('department', '=', 'Accounting')->where('title', '=', 'Manager'); +})->getUnescapedQuery(); + +echo $query; // Returns '(&(|(givenname=John)(sn=Doe))(&(department=Accounting)(title=Manager)))' +``` + +## Raw Filters + +> **Note**: Raw filters are not escaped. **Do not** accept user input into the raw filter method. + +Sometimes you might just want to add a raw filter without using the query builder. +You can do so by using the `rawFilter()` method: + +```php +$filter = '(samaccountname=jdoe)'; + +$results = $search->rawFilter($filter)->get(); + +// Or use an array +$filters = [ + '(samaccountname=jdoe)', + '(surname=Doe)', +]; + +$results = $search->rawFilter($filters)->get(); + +// Or use multiple arguments +$results = $search->rawFilter($filters[0], $filters[1])->get(); + +// Multiple raw filters will be automatically wrapped into an `and` filter: +$query = $search->getUnescapedQuery(); + +echo $query; // Returns (&(samaccountname=jdoe)(surname=Doe)) +``` + +## Sorting + +Sorting is really useful when your displaying tabular LDAP results. You can +easily perform sorts on any LDAP attribute by using the `sortBy()` method: + +```php +$results = $search->whereHas('cn')->sortBy('cn', 'asc')->get(); +``` + +You can also sort paginated results: + +```php +$results = $search->whereHas('cn')->sortBy('cn', 'asc')->paginate(25); +``` + +> **Note**: Sorting occurs *after* results are returned. This is due +> to PHP not having the functionality of sorting records on +> the server side before they are returned. + +## Paginating + +Paginating your search results will allow you to return more results than +your LDAP cap (usually 1000) and display your results in pages. + +> **Note**: Calling `paginate()` will retrieve **all** records from your LDAP server for the current query. +> +> This **does not** operate the same way pagination occurs in a database. Pagination of +> an LDAP query simply allows you to return a larger result set than your +> LDAP servers configured maximum (usually 1000). +> +> The pagination object is simply a collection that allows you to iterate +> through all the resulting records easily and intuitively. + +To perform this, call the `paginate()` method instead of the `get()` method: + +```php +$recordsPerPage = 50; + +$currentPage = $_GET['page']; + +// This would retrieve all records from your LDAP server inside a new Adldap\Objects\Paginator instance. +$paginator = $search->paginate($recordsPerPage, $currentPage); + +// Returns total number of pages, int +$paginator->getPages(); + +// Returns current page number, int +$paginator->getCurrentPage(); + +// Returns the amount of entries allowed per page, int +$paginator->getPerPage(); + +// Returns all of the results in the entire paginated result +$paginator->getResults(); + +// Returns the total amount of retrieved entries, int +$paginator->count(); + +// Iterate over the results like normal +foreach($paginator as $result) +{ + echo $result->getCommonName(); +} +``` + +## Scopes + +Search scopes allow you to easily retrieve common models of a particular 'scope'. + +Each scope simply applies the required filters to the search object +that (when executed) will only return the relevant models. + +Here is a list of all available scopes: + +```php +// Retrieve all users (Adldap\Models\User). +$results = $search->users()->get(); + +// Retrieve all printers (Adldap\Models\Printer). +$results = $search->printers()->get(); + +// Retrieve all organizational units (Adldap\Models\OrganizationalUnit). +$results = $search->ous()->get(); + +// Retrieve all organizational units (Adldap\Models\OrganizationalUnit). +$results = $search->organizations()->get(); + +// Retrieve all groups (Adldap\Models\Group). +$results = $search->groups()->get(); + +// Retrieve all containers (Adldap\Models\Container). +$results = $search->containers()->get(); + +// Retrieve all contacts (Adldap\Models\Contact). +$results = $search->contacts()->get(); + +// Retrieve all computers (Adldap\Models\Computer). +$results = $search->computers()->get(); +``` + +## Base DN + +To set the base DN of your search you can use one of two methods: + +```php +// Using the `in()` method: +$results = $provider->search()->in('ou=Accounting,dc=acme,dc=org')->get(); + +// Using the `setDn()` method: +$results = $provider->search()->setDn('ou=Accounting,dc=acme,dc=org')->get(); + +// You can also include `in()` with the scope +$results = $provider->search()->organizations()->in('ou=Accounting,dc=acme,dc=org')->get() + +``` + +Either option will return the same results. Use which ever method you prefer to be more readable. + +## Search Options + +#### Recursive + +By default, all searches performed are recursive. + +If you'd like to disable recursive search and perform a single level search, use the `listing()` method: + +```php +$result = $provider->search()->listing()->get(); +``` + +This would perform an `ldap_listing()` instead of an `ldap_search()`. + +#### Read + +If you'd like to perform a read instead of a listing or a recursive search, use the `read()` method: + +```php +$result = $provider->search()->read()->where('objectClass', '*')->get(); +``` + +This would perform an `ldap_read()` instead of an `ldap_listing()` or an `ldap_search()`. + +> **Note**: Performing a `read()` will always return *one* record in your result. + +#### Raw + +If you'd like to retrieve the raw LDAP results, use the `raw()` method: + +```php +$rawResults = $provider->search()->raw()->where('cn', '=', 'John Doe')->get(); + +var_dump($rawResults); // Returns an array +``` + +## Retrieving the ran query + +If you'd like to retrieve the current query to save or run it at another +time, use the `getQuery()` method on the query builder. + +This will return the escaped filter. + +```php +$query = $provider->search()->where('cn', '=', 'John Doe')->getQuery(); + +echo $query; // Returns '(cn=\4a\6f\68\6e\20\44\6f\65)' +``` + +To retrieve the unescaped filter, call the `getUnescapedQuery()` method: + +```php +$query = $provider->search()->where('cn', '=', 'John Doe')->getUnescapedQuery(); + +echo $query; // Returns '(cn=John Doe)' +``` + +Now that you know how to search your directory, lets move onto [creating / modifying LDAP records](models/model.md). diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/setup.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/setup.md new file mode 100644 index 0000000000..312165c1cc --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/setup.md @@ -0,0 +1,552 @@ +# Setup + +## Configuration + +To configure your LDAP connections, you can use two methods: + +1. Using an array +2. Using a `Adldap\Configuration\DomainConfiguration` object + +Either or will produce the same results. Use whichever you feel most comfortable with. + +### Using an array + +```php +$config = [ + 'hosts' => [ + 'DC-01.corp.acme.org', + ], + '...' +]; +``` + +### Using a `DomainConfiguration` object + +```php +// Setting options via first argument: +$config = new Adldap\Configuration\DomainConfiguration([ + 'hosts' => [ + 'DC-01.corp.acme.org', + ], +]); + +// Setting via the `set()` method: +$config->set('hosts', [ + 'DC-01.corp.acme.org', +]); +``` + +### Options + +#### Array Example With All Options + +```php +// Create the configuration array. +$config = [ + // Mandatory Configuration Options + 'hosts' => ['corp-dc1.corp.acme.org', 'corp-dc2.corp.acme.org'], + 'base_dn' => 'dc=corp,dc=acme,dc=org', + 'username' => 'admin', + 'password' => 'password', + + // Optional Configuration Options + 'schema' => Adldap\Schemas\ActiveDirectory::class, + 'account_prefix' => 'ACME-', + 'account_suffix' => '@acme.org', + 'port' => 389, + 'follow_referrals' => false, + 'use_ssl' => false, + 'use_tls' => false, + 'version' => 3, + 'timeout' => 5, + + // Custom LDAP Options + 'custom_options' => [ + // See: http://php.net/ldap_set_option + LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD + ] +]; +``` + +#### Required Options + +##### Hosts + +The hosts option is an array of IP addresses or hostnames located +on your network that serve Active Directory. + +You insert as many servers or as little as you'd like depending on your forest (with the minimum of one of course). + +> **Note:** Do not append your port to your IP addresses or hostnames. Use the `port` configuration option instead. + +##### Base Distinguished Name + +The base distinguished name is the base distinguished name you'd like to perform operations on. + +An example base DN would be `DC=corp,DC=acme,DC=org`. + +If one is not defined, you will not retrieve any search results. + +> **Note**: Your base DN is **case insensitive**. You do not need to worry about incorrect casing. + +##### Username & Password + +To connect to your LDAP server, a username and password is required to be able to query and run operations on your server(s). + +You can use any account that has these permissions. + +> **Note**: To run administration level operations, such as resetting passwords, +> this account **must** have permissions to do so on your directory. + +#### Optional Options + +##### Schema + +The schema option allows you to configure which directory you're connecting to. + +This is a somewhat optional, however this **must** be changed if you're connecting +to an alternate LDAP variant such as OpenLDAP or FreeIPA. + +Below are available schemas: + +- `Adldap\Schemas\ActiveDirectory` +- `Adldap\Schemas\OpenLDAP` +- `Adldap\Schemas\FreeIPA` + +By default, this option is set to the `Adldap\Schemas\ActiveDirectory` schema. + +##### Account Prefix + +The account prefix option is a string to *prepend* to all usernames that go through the `Guard::attempt()` method. + +This option is just for convenience. + +It is usually not needed (if utilizing the account suffix), however the functionality is +in place if you would like to only allow certain users with the specified prefix +to login, or add a domain so your users do not have to specify one. + +##### Account Suffix + +The account suffix option is a string to *append* to all usernames that go +through the `Adldap\Auth\Guard::attempt()` method. + +This option is just for convenience. + +An example use case for this would be inserting your LDAP users `userPrincipalName` suffix so you don't need to append it manually. + +For example, with a `account_suffix` in your configuration set to `@corp.acme.org`: + +```php +$username = 'jdoe'; +$password = 'password'; + +// Here, an `ldap_bind()` will be called with a username of 'jdoe@corp.acme.org` +$provider->auth()->attempt($username, $password); +``` + +##### Port + +The port option is used for authenticating and binding to your LDAP server. + +The default ports are already used for non SSL and SSL connections (389 and 636). + +Only insert a port if your LDAP server uses a unique port. + +##### Follow Referrals + +The follow referrals option is a boolean to tell active directory to follow a referral to another server on your network if the server queried knows the information your asking for exists, but does not yet contain a copy of it locally. + +This option is defaulted to false. + +Disable this option if you're experiencing search / connectivity issues. + +For more information, visit: https://technet.microsoft.com/en-us/library/cc978014.aspx + +##### SSL & TLS + +These Boolean options enable an SSL or TLS connection to your LDAP server. + +Only **one** can be set to `true`. You must chose either or. + +> **Note**: You **must** enable SSL or TLS to reset passwords in ActiveDirectory. + +These options are definitely recommended if you have the ability to connect to your server securely. + +> **Note**: TLS is recommended over SSL, as SSL is now labelled as a depreciated mechanism for securely running LDAP operations. + +##### Version + +The LDAP version to use for your connection. + +Must be an integer and can either be `2` or `3`. + +##### Timeout + +The timeout option allows you to configure the amount of seconds to wait until +your application receives a response from your LDAP server. + +The default is 5 seconds. + +##### Custom Options + +Arbitrary options can be set for the connection to fine-tune TLS and connection behavior. + +Please note that `LDAP_OPT_PROTOCOL_VERSION`, `LDAP_OPT_NETWORK_TIMEOUT` and `LDAP_OPT_REFERRALS` will be ignored if set. + +These are set above with the `version`, `timeout` and `follow_referrals` keys respectively. + +Valid options are listed in the [PHP documentation for ldap_set_option](http://php.net/ldap_set_option). + +## Getting Started + +Each LDAP connection you have will be contained inside the `Adldap` instance as its own **connection provider**. + +There are a couple of ways you can easily add each of your LDAP connections. Let's walk through them: + +**Using a configuration array:** +```php +$config = ['...']; + +$ad = new Adldap\Adldap(); + +$ad->addProvider($config); + +// You can also specify the name of the +// connection as the second argument: +$ad->addProvider($config, 'connection-one'); +``` + +**Using a DomainConfiguration object:** +```php +$ad = new Adldap\Adldap(); + +$config = new Adldap\Configuration\DomainConfiguration(['...']); + +$ad->addProvider($config, 'connection-one'); +``` + +**Using the constructor:** + +> **Note**: When inserting your configuration into a new `Adldap` instance, you +> need to set a key for each connection. **This will be its connection name**. + +```php +$connections = [ + 'connection1' => [ + 'hosts' => ['...'], + ], + 'connection2' => [ + 'hosts' => ['...'], + ], +]; + +$ad = new Adldap\Adldap($connections); +``` + +## Connecting + +The easiest way to get connected is to call the `connect($name)` method on your `Adldap` instance. + +Its first argument accepts the name of your configured connection. + +This method will return you a connected **connection provider** when +successful, and throw an exception when unsuccessful: + +```php +$ad = new Adldap\Adldap(); + +$config = ['...']; + +$connectionName = 'my-connection'; + +$ad->addProvider($config, $connectionName); + +try { + $provider = $ad->connect($connectionName); + + // Great, we're connected! +} catch (Adldap\Auth\BindException $e) { + // Failed to connect. +} +``` + +### Using an alternate username / password + +If you'd like to connect to your configured connection using a different username and password than your configuration, then simply provide them in the second and third arguments: + +```php +$username = 'server-admin'; +$password = 'my-super-secret-password'; + +$provider = $ad->connect($connectionName, $username, $password); +``` + +### Dynamically Connecting + +If you're like me and like chainable (fluent) API's in PHP, then dynamically connecting is a nice option to have. + +To dynamically connect, simply call any connection provider method on your `Adldap` instance. + +> **Note**: Your default connection will be used when dynamically connecting. +> More on this below. + +Here's an example: + +```php +$ad = new Adldap\Adldap(); + +$ad->addProvider($config = ['...']); + +try { + $users = $ad->search()->users()->get(); +} catch (Adldap\Auth\BindException $e) { + // Failed to connect. +} +``` + +### Anonymously Binding + +If you'd like to anonymously bind, set your `username` and `password` configuration to `null`: + +```php +$ad = new Adldap\Adldap(); + +$config = [ + 'username' => null, + 'password' => null, +]; + +$ad->addProvider($config); + +try { + $provider = $ad->connect(); + + // ... +} catch (BindException $e) { + // Failed. +} +``` + +Or, manually bind your provider and don't pass in a `username` or `password` parameter: + +```php +$config = [ + 'hosts' => ['...'], +]; + +$ad->addProvider($config); + +$provider = $ad->getDefaultProvider(); + +try { + $provider->auth()->bind(); + + // Successfully bound. +} catch (BindException $e) { + // Failed. +} +``` + +### Setting a Default Connection + +Setting a default LDAP connection is used for dynamically connecting. + +To set your default connection, call the `setDefaultProvider($name)` method: + +```php +$ad->setDefaultProvider('my-connection'); + +$computers = $ad->search()->computers()->get(); +``` + +## Authenticating + +If you're looking to authenticate (bind) users using your LDAP connection, call +the `auth()->attempt()` method on your provider instance: + +```php +$username = 'jdoe'; +$password = 'Password@1'; + +try { + if ($provider->auth()->attempt($username, $password)) { + // Passed. + } else { + // Failed. + } +} catch (Adldap\Auth\UsernameRequiredException $e) { + // The user didn't supply a username. +} catch (Adldap\Auth\PasswordRequiredException $e) { + // The user didn't supply a password. +} +``` + +If you'd like all LDAP operations during the same request to be ran under the +authenticated user, pass in `true` into the last paramter: + +```php +if ($provider->auth()->attempt($username, $password, $bindAsUser = true)) { + // Passed. +} else { + // Failed. +} +``` + +--- + +Now that you've learned the basics of configuration and +getting yourself connected, continue on to learn +[how to search your LDAP directory](searching.md). + +## Using Other LDAP Servers (OpenLDAP / FreeIPA / etc.) + +Alternate LDAP server variants such as OpenLDAP or FreeIPA contain +some different attribute names than ActiveDirectory. + +The Adldap2 schema offers an attribute map for each available LDAP attribute, and +is completely configurable and customizable. + +If you're using an alternate LDAP server variant such as OpenLDAP or FreeIPA, you **must** change the default schema inside your configuration array. If you do not, you won't receive the correct model instances for results, and you won't be +able to utilize some standard methods available on these models. + +By default, Adldap2 is configured to be used with **Microsoft ActiveDirectory**. + +When creating your configuration array, set your schema using the `schema` key: + + +**Using configuration array:** +```php +$ad = new Adldap\Adldap(); + +$config = [ + '...', + 'schema' => Adldap\Schemas\OpenLDAP::class +]; + +$ad->addProvider($config); +``` + +**Using configuration object:** +```php +$ad = new Adldap\Adldap(); + +$config = new Adldap\Configuration\DomainConfiguration(); + +$config->set('schema', Adldap\Schemas\OpenLDAP::class); + +$ad->addProvider($config); +``` + +Once you've set the schema of your connection provider, you can use the same API interacting with different LDAP servers. + +Continue onto the [searching](searching.md) documentation to learn how to begin querying your LDAP server(s). + +## Using G-Suite Secure LDAP Service + +G-Suite LDAP service only uses client certificates and no username + password, make sure yo match base_dn with your domian. + +```php +$ad = new \Adldap\Adldap(); + +// Create a configuration array. +$config = [ + 'hosts' => ['ldap.google.com'], + 'base_dn' => 'dc=your-domain,dc=com', + 'use_tls' => true, + 'version' => 3, + 'schema' => Adldap\Schemas\GSuite::class, + 'custom_options' => [ + LDAP_OPT_X_TLS_CERTFILE => 'Google_2023_02_05_35779.crt', + LDAP_OPT_X_TLS_KEYFILE => 'Google_2023_02_05_35779.key', + ] +]; + +$ad->addProvider($config); + +try { + $provider = $ad->connect(); + + $results = $provider->search()->ous()->get(); + + echo 'OUs:'."\r\n"; + echo '==============='."\r\n"; + foreach($results as $ou) { + echo $ou->getDn()."\r\n"; + } + + echo "\r\n"; + + $results = $provider->search()->users()->get(); + + echo 'Users:'."\r\n"; + echo '==============='."\r\n"; + foreach($results as $user) { + + echo $user->getAccountName()."\r\n"; + } + + echo "\r\n"; + + $results = $provider->search()->groups()->get(); + + echo 'Groups:'."\r\n"; + echo '==============='."\r\n"; + foreach($results as $group) { + echo $group->getCommonName().' | '.$group->getDisplayName()."\r\n"; + } + +} catch (\Adldap\Auth\BindException $e) { + + echo 'Error: '.$e->getMessage()."\r\n"; +} +``` + +## Raw Operations + +### Introduction + +If you want to connect to your LDAP server without utilizing Adldap's models (old fashion way), and want to get back the data in a raw format you can easily do so. + +If you call `getConnection()` on your connected provider instance, you can perform all LDAP functions on a container class that encapsulates all of PHP's LDAP methods. + +You can view all methods avaialble by browsing the LDAP class [here](https://github.com/Adldap2/Adldap2/blob/master/src/Connections/Ldap.php). + +Now for some examples: + +### Examples + +```php +$ad = new Adldap\Adldap(); + +$config = ['...']; + +$ad->addProvider($config); + +$provider = $ad->connect(); + +$rawConnection = $provider->getConnection(); + +// Performing a raw search. +$result = $rawConnection->search($basedn = 'dc=corp,dc=acme,dc=org', $filter = "cn=johndoe", $selectedAttributes = ['cn', 'department']); + +$dn = "cn=John Smith,ou=Wizards,dc=example,dc=com"; + +// Adding a new LDAP record. +$result = $rawConnection->add($dn, $entry); + +// Batch modifying an LDAP record. +$modifs = [ + [ + "attrib" => "telephoneNumber", + "modtype" => LDAP_MODIFY_BATCH_ADD, + "values" => ["+1 555 555 1717"], + ], +]; + +$result = $rawConnection->modifyBatch($dn, $modifs); + +// Deleting an LDAP record. +$result = $rawConnection->delete($dn); + +// .. etc +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/docs/troubleshooting.md b/data/web/inc/lib/vendor/adldap2/adldap2/docs/troubleshooting.md new file mode 100644 index 0000000000..c1e9f2a847 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/docs/troubleshooting.md @@ -0,0 +1,122 @@ +# Troubleshooting + +#### Creating and Setting a Users Password + +To set a users password when you've created a new one, you need to enable their account, **then** set their password. + +For example: + +```php +// Construct a new user instance. +$user = $provider->make()->user(); + +// Set the user profile details. +$user->setAccountName('jdoe'); +$user->setFirstName('John'); +$user->setLastName('Doe'); +$user->setCompany('ACME'); +$user->setEmail('jdoe@acme.com'); + +// Save the new user. +if ($user->save()) { + // Enable the new user (using user account control). + $user->setUserAccountControl(512); + + // Set new user password + $user->setPassword('Password123'); + + // Save the user. + if($user->save()) { + // The password was saved successfully. + } +} +``` + +#### Determining and Troubleshooting a Binding Failure + +> **Note**: The below guide is using ActiveDirectory. Your mileage will vary using other LDAP distributions. + +To determine the reason why a bind attempt failed, you can use the event dispatcher to listen for +the `Failed` event, and retrieve the errors that were returned from your LDAP server: + +```php +use Adldap\Adldap; +use Adldap\Auth\Events\Failed; + +$d = Adldap::getEventDispatcher(); + +$d->listen(Failed::class, function (Failed $event) { + $conn = $event->connection; + + echo $conn->getLastError(); // 'Invalid credentials' + echo $conn->getDiagnosticMessage(); // '80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 532, v3839' + + if ($error = $conn->getDetailedError()) { + $error->getErrorCode(); // 49 + $error->getErrorMessage(); // 'Invalid credentials' + $error->getDiagnosticMessage(); // '80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 532, v3839' + } +}); +``` + +The above diagnostic message can be parsed down further if needed. The error code after the 'data' string +in the above message indicates several things about the bind failure. Here is a list: + +- 525 - user not found +- 52e - invalid credentials +- 530 - not permitted to logon at this time +- 531 - not permitted to logon at this workstation +- 532 - password expired +- 533 - account disabled +- 701 - account expired +- 773 - user must reset password +- 775 - user account locked + +From the example above, you can see that the authenticating account has their password expired, due to "532" error code. + +#### Retrieving All Records Inside a Group + +To retrieve all records inside a particular group (including nested groups), use the `rawFilter()` method: + +```php +// The `memberof:1.2.840.113556.1.4.1941:` string indicates +// that we want all nested group records as well. +$filter = '(memberof:1.2.840.113556.1.4.1941:=CN=MyGroup,DC=example,DC=com)'; + +$users = $provider->search()->rawFilter($filter)->get(); +``` + +#### I'm connected but not getting any search results! + +The first thing you need to ensure is your `base_dn` in your configuration. + +Your `base_dn` needs to identical to the base DN on your domain. Even one mistyped character will result in no search results. + +If you also include an `ou` in your base DN (ex. `ou=Accounting,dc=corp,dc=acme,dc=org`), you will only receive results inside the `Accounting` OU. + +Once you're connected to your LDAP server, retrieve the Root DSE record. + +Here's a full example: + +```php +$providers = [ + 'default' => [ + 'base_dn' => '', + '...', + ] +]; + +$ad = new Adldap\Adldap($providers); + +try { + $provider = $ad->connect(); + + $root = $provider->search()->getRootDse(); + + // ex. Returns 'dc=corp,dc=acme,dc=org' + die($root->getRootDomainNamingContext()); + +} catch (Adldap\Auth\BindException $e) { + // +} +``` diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/license.md b/data/web/inc/lib/vendor/adldap2/adldap2/license.md new file mode 100644 index 0000000000..c25dc6032b --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/license.md @@ -0,0 +1,8 @@ +The MIT License (MIT) +Copyright ยฉ Steve Bauman + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the โ€œSoftwareโ€), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED โ€œAS ISโ€, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/phpunit.xml b/data/web/inc/lib/vendor/adldap2/adldap2/phpunit.xml new file mode 100644 index 0000000000..4f327fae00 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/phpunit.xml @@ -0,0 +1,22 @@ + + + + + ./tests/ + + + + + ./src + + + diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/readme.md b/data/web/inc/lib/vendor/adldap2/adldap2/readme.md new file mode 100644 index 0000000000..1311332c70 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/readme.md @@ -0,0 +1,47 @@ +

    +:wave: Hey there! Looking for something even easier to use for LDAP integration in your PHP applications? +
    +

    + ๐ŸŽ‰ Introducing LdapRecord ๐ŸŽ‰ +

    +

    + +

    + + LdapRecord is the successor to Adldap2 - and comes with a ton of new features. +
    Adldap2 will continue to be supported with bug fixes, but will not receive new features. +

    + +

    + + Read Why + +

    + +
    + +

    Adldap2

    + +

    + + + + + +

    + +

    + Adldap2 is a PHP package that provides LDAP authentication and directory management tools using the Active Record pattern. +

    + +

    + Quickstart + ยท + Documentation +

    + +- **Up and running in minutes.** Effortlessly connect to your LDAP servers and start running queries & operations in a matter of minutes. + +- **Fluent query builder.** Building LDAP queries has never been so easy. Find the records you're looking for in a couple lines or less with a fluent interface. + +- **Supercharged Active Record.** Create and modify LDAP records with ease. All LDAP records are individual models. Simply modify the attributes on the model and save it to persist the changes to your LDAP server. diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Adldap.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Adldap.php new file mode 100644 index 0000000000..5e606978fe --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Adldap.php @@ -0,0 +1,194 @@ + $config) { + $this->addProvider($config, $name); + } + + if ($default = key($providers)) { + $this->setDefaultProvider($default); + } + + $this->initEventLogger(); + } + + /** + * {@inheritdoc} + */ + public function addProvider($config, $name = 'default', ConnectionInterface $connection = null) + { + if ($this->isValidConfig($config)) { + $config = new Provider($config, $connection ?? new Ldap($name)); + } + + if ($config instanceof ProviderInterface) { + $this->providers[$name] = $config; + + return $this; + } + + throw new InvalidArgumentException( + "You must provide a configuration array or an instance of Adldap\Connections\ProviderInterface." + ); + } + + /** + * Determines if the given config is valid. + * + * @param mixed $config + * + * @return bool + */ + protected function isValidConfig($config) + { + return is_array($config) || $config instanceof DomainConfiguration; + } + + /** + * {@inheritdoc} + */ + public function getProviders() + { + return $this->providers; + } + + /** + * {@inheritdoc} + */ + public function getProvider($name) + { + if (array_key_exists($name, $this->providers)) { + return $this->providers[$name]; + } + + throw new AdldapException("The connection provider '$name' does not exist."); + } + + /** + * {@inheritdoc} + */ + public function setDefaultProvider($name = 'default') + { + if ($this->getProvider($name) instanceof ProviderInterface) { + $this->default = $name; + } + } + + /** + * {@inheritdoc} + */ + public function getDefaultProvider() + { + return $this->getProvider($this->default); + } + + /** + * {@inheritdoc} + */ + public function removeProvider($name) + { + unset($this->providers[$name]); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function connect($name = null, $username = null, $password = null) + { + $provider = $name ? $this->getProvider($name) : $this->getDefaultProvider(); + + return $provider->connect($username, $password); + } + + /** + * {@inheritdoc} + */ + public function __call($method, $parameters) + { + $provider = $this->getDefaultProvider(); + + if (! $provider->getConnection()->isBound()) { + $provider->connect(); + } + + return call_user_func_array([$provider, $method], $parameters); + } + + /** + * Initializes the event logger. + * + * @return void + */ + public function initEventLogger() + { + $dispatcher = static::getEventDispatcher(); + + $logger = $this->newEventLogger(); + + // We will go through each of our event wildcards and register their listener. + foreach ($this->listen as $event) { + $dispatcher->listen($event, function ($eventName, $events) use ($logger) { + foreach ($events as $event) { + $logger->log($event); + } + }); + } + } + + /** + * Returns a new event logger instance. + * + * @return EventLogger + */ + protected function newEventLogger() + { + return new EventLogger(static::getLogger()); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/AdldapException.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/AdldapException.php new file mode 100644 index 0000000000..0939cac08a --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/AdldapException.php @@ -0,0 +1,8 @@ +detailedError = $error; + + return $this; + } + + /** + * Returns the detailed error. + * + * @return DetailedError|null + */ + public function getDetailedError() + { + return $this->detailedError; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Auth/Events/Attempting.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Auth/Events/Attempting.php new file mode 100644 index 0000000000..06eb839edb --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Auth/Events/Attempting.php @@ -0,0 +1,8 @@ +connection = $connection; + $this->username = $username; + $this->password = $password; + } + + /** + * Returns the events connection. + * + * @return ConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Returns the authentication events username. + * + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Returns the authentication events password. + * + * @return string + */ + public function getPassword() + { + return $this->password; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Auth/Events/Failed.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Auth/Events/Failed.php new file mode 100644 index 0000000000..e96746efc9 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Auth/Events/Failed.php @@ -0,0 +1,8 @@ +connection = $connection; + $this->configuration = $configuration; + } + + /** + * {@inheritdoc} + */ + public function attempt($username, $password, $bindAsUser = false) + { + $this->validateCredentials($username, $password); + + $this->fireAttemptingEvent($username, $password); + + try { + $this->bind( + $this->applyPrefixAndSuffix($username), + $password + ); + + $result = true; + + $this->firePassedEvent($username, $password); + } catch (BindException $e) { + // We'll catch the BindException here to allow + // developers to use a simple if / else + // using the attempt method. + $result = false; + } + + // If we're not allowed to bind as the user, + // we'll rebind as administrator. + if ($bindAsUser === false) { + // We won't catch any BindException here so we can + // catch rebind failures. However this shouldn't + // occur if our credentials are correct + // in the first place. + $this->bindAsAdministrator(); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function bind($username = null, $password = null) + { + $this->fireBindingEvent($username, $password); + + try { + if (@$this->connection->bind($username, $password) === true) { + $this->fireBoundEvent($username, $password); + } else { + throw new Exception($this->connection->getLastError(), $this->connection->errNo()); + } + } catch (Throwable $e) { + $this->fireFailedEvent($username, $password); + + throw (new BindException($e->getMessage(), $e->getCode(), $e)) + ->setDetailedError($this->connection->getDetailedError()); + } + } + + /** + * {@inheritdoc} + */ + public function bindAsAdministrator() + { + $this->bind( + $this->configuration->get('username'), + $this->configuration->get('password') + ); + } + + /** + * Get the event dispatcher instance. + * + * @return DispatcherInterface + */ + public function getDispatcher() + { + return $this->events; + } + + /** + * Sets the event dispatcher instance. + * + * @param DispatcherInterface $dispatcher + * + * @return void + */ + public function setDispatcher(DispatcherInterface $dispatcher) + { + $this->events = $dispatcher; + } + + /** + * Applies the prefix and suffix to the given username. + * + * @param string $username + * + * @throws \Adldap\Configuration\ConfigurationException If account_suffix or account_prefix do not + * exist in the providers domain configuration + * + * @return string + */ + protected function applyPrefixAndSuffix($username) + { + $prefix = $this->configuration->get('account_prefix'); + $suffix = $this->configuration->get('account_suffix'); + + return $prefix.$username.$suffix; + } + + /** + * Validates the specified username and password from being empty. + * + * @param string $username + * @param string $password + * + * @throws PasswordRequiredException When the given password is empty. + * @throws UsernameRequiredException When the given username is empty. + */ + protected function validateCredentials($username, $password) + { + if (empty($username)) { + // Check for an empty username. + throw new UsernameRequiredException('A username must be specified.'); + } + + if (empty($password)) { + // Check for an empty password. + throw new PasswordRequiredException('A password must be specified.'); + } + } + + /** + * Fire the attempting event. + * + * @param string $username + * @param string $password + * + * @return void + */ + protected function fireAttemptingEvent($username, $password) + { + if (isset($this->events)) { + $this->events->fire(new Attempting($this->connection, $username, $password)); + } + } + + /** + * Fire the passed event. + * + * @param string $username + * @param string $password + * + * @return void + */ + protected function firePassedEvent($username, $password) + { + if (isset($this->events)) { + $this->events->fire(new Passed($this->connection, $username, $password)); + } + } + + /** + * Fire the failed event. + * + * @param string $username + * @param string $password + * + * @return void + */ + protected function fireFailedEvent($username, $password) + { + if (isset($this->events)) { + $this->events->fire(new Failed($this->connection, $username, $password)); + } + } + + /** + * Fire the binding event. + * + * @param string $username + * @param string $password + * + * @return void + */ + protected function fireBindingEvent($username, $password) + { + if (isset($this->events)) { + $this->events->fire(new Binding($this->connection, $username, $password)); + } + } + + /** + * Fire the bound event. + * + * @param string $username + * @param string $password + * + * @return void + */ + protected function fireBoundEvent($username, $password) + { + if (isset($this->events)) { + $this->events->fire(new Bound($this->connection, $username, $password)); + } + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Auth/GuardInterface.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Auth/GuardInterface.php new file mode 100644 index 0000000000..e10da3740f --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Auth/GuardInterface.php @@ -0,0 +1,55 @@ + [], + + // The global LDAP operation timeout limit in seconds. + 'timeout' => 5, + + // The LDAP version to utilize. + 'version' => 3, + + // The port to use for connecting to your hosts. + 'port' => ConnectionInterface::PORT, + + // The schema to use for your LDAP connection. + 'schema' => ActiveDirectory::class, + + // The base distinguished name of your domain. + 'base_dn' => '', + + // The username to connect to your hosts with. + 'username' => '', + + // The password that is utilized with the above user. + 'password' => '', + + // The account prefix to use when authenticating users. + 'account_prefix' => null, + + // The account suffix to use when authenticating users. + 'account_suffix' => null, + + // Whether or not to use SSL when connecting to your hosts. + 'use_ssl' => false, + + // Whether or not to use TLS when connecting to your hosts. + 'use_tls' => false, + + // Whether or not follow referrals is enabled when performing LDAP operations. + 'follow_referrals' => false, + + // Custom LDAP options that you'd like to utilize. + 'custom_options' => [], + ]; + + /** + * Constructor. + * + * @param array $options + * + * @throws ConfigurationException When an option value given is an invalid type. + */ + public function __construct(array $options = []) + { + foreach ($options as $key => $value) { + $this->set($key, $value); + } + } + + /** + * Sets a configuration option. + * + * Throws an exception if the specified option does + * not exist, or if it's an invalid type. + * + * @param string $key + * @param mixed $value + * + * @throws ConfigurationException When an option value given is an invalid type. + */ + public function set($key, $value) + { + if ($this->validate($key, $value)) { + $this->options[$key] = $value; + } + } + + /** + * Returns the value for the specified configuration options. + * + * Throws an exception if the specified option does not exist. + * + * @param string $key + * + * @throws ConfigurationException When the option specified does not exist. + * + * @return mixed + */ + public function get($key) + { + if ($this->has($key)) { + return $this->options[$key]; + } + + throw new ConfigurationException("Option {$key} does not exist."); + } + + /** + * Checks if a configuration option exists. + * + * @param string $key + * + * @return bool + */ + public function has($key) + { + return array_key_exists($key, $this->options); + } + + /** + * Validates the new configuration option against its + * default value to ensure it's the correct type. + * + * If an invalid type is given, an exception is thrown. + * + * @param string $key + * @param mixed $value + * + * @throws ConfigurationException When an option value given is an invalid type. + * + * @return bool + */ + protected function validate($key, $value) + { + $default = $this->get($key); + + if (is_array($default)) { + $validator = new Validators\ArrayValidator($key, $value); + } elseif (is_int($default)) { + $validator = new Validators\IntegerValidator($key, $value); + } elseif (is_bool($default)) { + $validator = new Validators\BooleanValidator($key, $value); + } elseif (class_exists($default)) { + $validator = new Validators\ClassValidator($key, $value); + } else { + $validator = new Validators\StringOrNullValidator($key, $value); + } + + return $validator->validate(); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/ArrayValidator.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/ArrayValidator.php new file mode 100644 index 0000000000..ec5bdb0c27 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/ArrayValidator.php @@ -0,0 +1,25 @@ +value)) { + throw new ConfigurationException("Option {$this->key} must be an array."); + } + + return true; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/BooleanValidator.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/BooleanValidator.php new file mode 100644 index 0000000000..23b48469c0 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/BooleanValidator.php @@ -0,0 +1,25 @@ +value)) { + throw new ConfigurationException("Option {$this->key} must be a boolean."); + } + + return true; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/ClassValidator.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/ClassValidator.php new file mode 100644 index 0000000000..924c35f48b --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/ClassValidator.php @@ -0,0 +1,24 @@ +value)) { + throw new ConfigurationException("Option {$this->key} must be a valid class."); + } + + return true; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/IntegerValidator.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/IntegerValidator.php new file mode 100644 index 0000000000..cd783c4b7b --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/IntegerValidator.php @@ -0,0 +1,25 @@ +value)) { + throw new ConfigurationException("Option {$this->key} must be an integer."); + } + + return true; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/StringOrNullValidator.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/StringOrNullValidator.php new file mode 100644 index 0000000000..717c95237d --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/StringOrNullValidator.php @@ -0,0 +1,25 @@ +value) || is_null($this->value)) { + return true; + } + + throw new ConfigurationException("Option {$this->key} must be a string or null."); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/Validator.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/Validator.php new file mode 100644 index 0000000000..983c98cb30 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Configuration/Validators/Validator.php @@ -0,0 +1,46 @@ +key = $key; + $this->value = $value; + } + + /** + * Validates the configuration value. + * + * @throws \Adldap\Configuration\ConfigurationException When the value given fails validation. + * + * @return bool + */ + abstract public function validate(); +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Connections/ConnectionException.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Connections/ConnectionException.php new file mode 100644 index 0000000000..d8004e6cc2 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Connections/ConnectionException.php @@ -0,0 +1,10 @@ +errorCode = $errorCode; + $this->errorMessage = $errorMessage; + $this->diagnosticMessage = $diagnosticMessage; + } + + /** + * Returns the LDAP error code. + * + * @return int + */ + public function getErrorCode() + { + return $this->errorCode; + } + + /** + * Returns the LDAP error message. + * + * @return string + */ + public function getErrorMessage() + { + return $this->errorMessage; + } + + /** + * Returns the LDAP diagnostic message. + * + * @return string + */ + public function getDiagnosticMessage() + { + return $this->diagnosticMessage; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Connections/Ldap.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Connections/Ldap.php new file mode 100644 index 0000000000..2ce83fd244 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Connections/Ldap.php @@ -0,0 +1,545 @@ +name = $name; + } + + /** + * {@inheritdoc} + */ + public function isUsingSSL() + { + return $this->useSSL; + } + + /** + * {@inheritdoc} + */ + public function isUsingTLS() + { + return $this->useTLS; + } + + /** + * {@inheritdoc} + */ + public function isBound() + { + return $this->bound; + } + + /** + * {@inheritdoc} + */ + public function canChangePasswords() + { + return $this->isUsingSSL() || $this->isUsingTLS(); + } + + /** + * {@inheritdoc} + */ + public function ssl($enabled = true) + { + $this->useSSL = $enabled; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function tls($enabled = true) + { + $this->useTLS = $enabled; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getHost() + { + return $this->host; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getConnection() + { + return $this->connection; + } + + /** + * {@inheritdoc} + */ + public function getEntries($searchResults) + { + return ldap_get_entries($this->connection, $searchResults); + } + + /** + * {@inheritdoc} + */ + public function getFirstEntry($searchResults) + { + return ldap_first_entry($this->connection, $searchResults); + } + + /** + * {@inheritdoc} + */ + public function getNextEntry($entry) + { + return ldap_next_entry($this->connection, $entry); + } + + /** + * {@inheritdoc} + */ + public function getAttributes($entry) + { + return ldap_get_attributes($this->connection, $entry); + } + + /** + * {@inheritdoc} + */ + public function countEntries($searchResults) + { + return ldap_count_entries($this->connection, $searchResults); + } + + /** + * {@inheritdoc} + */ + public function compare($dn, $attribute, $value) + { + return ldap_compare($this->connection, $dn, $attribute, $value); + } + + /** + * {@inheritdoc} + */ + public function getLastError() + { + return ldap_error($this->connection); + } + + /** + * {@inheritdoc} + */ + public function getDetailedError() + { + // If the returned error number is zero, the last LDAP operation + // succeeded. We won't return a detailed error. + if ($number = $this->errNo()) { + ldap_get_option($this->connection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $message); + + return new DetailedError($number, $this->err2Str($number), $message); + } + } + + /** + * {@inheritdoc} + */ + public function getValuesLen($entry, $attribute) + { + return ldap_get_values_len($this->connection, $entry, $attribute); + } + + /** + * {@inheritdoc} + */ + public function setOption($option, $value) + { + return ldap_set_option($this->connection, $option, $value); + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options = []) + { + foreach ($options as $option => $value) { + $this->setOption($option, $value); + } + } + + /** + * {@inheritdoc} + */ + public function setRebindCallback(callable $callback) + { + return ldap_set_rebind_proc($this->connection, $callback); + } + + /** + * {@inheritdoc} + */ + public function startTLS() + { + try { + return ldap_start_tls($this->connection); + } catch (\ErrorException $e) { + throw new ConnectionException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function connect($hosts = [], $port = 389) + { + $this->host = $this->getConnectionString($hosts, $this->getProtocol(), $port); + + // Reset the bound status if reinitializing the connection. + $this->bound = false; + + return $this->connection = ldap_connect($this->host); + } + + /** + * {@inheritdoc} + */ + public function close() + { + $connection = $this->connection; + + $result = is_resource($connection) ? ldap_close($connection) : false; + + $this->bound = false; + + return $result; + } + + /** + * {@inheritdoc} + */ + public function search($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0) + { + return ldap_search($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time); + } + + /** + * {@inheritdoc} + */ + public function listing($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0) + { + return ldap_list($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time); + } + + /** + * {@inheritdoc} + */ + public function read($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0) + { + return ldap_read($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time); + } + + /** + * Extract information from an LDAP result. + * + * @link https://www.php.net/manual/en/function.ldap-parse-result.php + * + * @param resource $result + * @param int $errorCode + * @param string $dn + * @param string $errorMessage + * @param array $referrals + * @param array $serverControls + * + * @return bool + */ + public function parseResult($result, &$errorCode, &$dn, &$errorMessage, &$referrals, &$serverControls = []) + { + return $this->supportsServerControlsInMethods() && !empty($serverControls) ? + ldap_parse_result($this->connection, $result, $errorCode, $dn, $errorMessage, $referrals, $serverControls) : + ldap_parse_result($this->connection, $result, $errorCode, $dn, $errorMessage, $referrals); + } + + /** + * {@inheritdoc} + */ + public function bind($username, $password, $sasl = false) + { + // Prior to binding, we will upgrade our connectivity to TLS on our current + // connection and ensure we are not already bound before upgrading. + // This is to prevent subsequent upgrading on several binds. + if ($this->isUsingTLS() && !$this->isBound()) { + $this->startTLS(); + } + + if ($sasl) { + return $this->bound = ldap_sasl_bind($this->connection, null, null, 'GSSAPI'); + } + + return $this->bound = ldap_bind( + $this->connection, + $username, + html_entity_decode($password) + ); + } + + /** + * {@inheritdoc} + */ + public function add($dn, array $entry) + { + return ldap_add($this->connection, $dn, $entry); + } + + /** + * {@inheritdoc} + */ + public function delete($dn) + { + return ldap_delete($this->connection, $dn); + } + + /** + * {@inheritdoc} + */ + public function rename($dn, $newRdn, $newParent, $deleteOldRdn = false) + { + return ldap_rename($this->connection, $dn, $newRdn, $newParent, $deleteOldRdn); + } + + /** + * {@inheritdoc} + */ + public function modify($dn, array $entry) + { + return ldap_modify($this->connection, $dn, $entry); + } + + /** + * {@inheritdoc} + */ + public function modifyBatch($dn, array $values) + { + return ldap_modify_batch($this->connection, $dn, $values); + } + + /** + * {@inheritdoc} + */ + public function modAdd($dn, array $entry) + { + return ldap_mod_add($this->connection, $dn, $entry); + } + + /** + * {@inheritdoc} + */ + public function modReplace($dn, array $entry) + { + return ldap_mod_replace($this->connection, $dn, $entry); + } + + /** + * {@inheritdoc} + */ + public function modDelete($dn, array $entry) + { + return ldap_mod_del($this->connection, $dn, $entry); + } + + /** + * {@inheritdoc} + */ + public function controlPagedResult($pageSize = 1000, $isCritical = false, $cookie = '') + { + return ldap_control_paged_result($this->connection, $pageSize, $isCritical, $cookie); + } + + /** + * {@inheritdoc} + */ + public function controlPagedResultResponse($result, &$cookie) + { + return ldap_control_paged_result_response($this->connection, $result, $cookie); + } + + /** + * {@inheritdoc} + */ + public function freeResult($result) + { + return ldap_free_result($result); + } + + /** + * {@inheritdoc} + */ + public function errNo() + { + return ldap_errno($this->connection); + } + + /** + * {@inheritdoc} + */ + public function getExtendedError() + { + return $this->getDiagnosticMessage(); + } + + /** + * {@inheritdoc} + */ + public function getExtendedErrorHex() + { + if (preg_match("/(?<=data\s).*?(?=\,)/", $this->getExtendedError(), $code)) { + return $code[0]; + } + } + + /** + * {@inheritdoc} + */ + public function getExtendedErrorCode() + { + return $this->extractDiagnosticCode($this->getExtendedError()); + } + + /** + * {@inheritdoc} + */ + public function err2Str($number) + { + return ldap_err2str($number); + } + + /** + * {@inheritdoc} + */ + public function getDiagnosticMessage() + { + ldap_get_option($this->connection, LDAP_OPT_ERROR_STRING, $message); + + return $message; + } + + /** + * {@inheritdoc} + */ + public function extractDiagnosticCode($message) + { + preg_match('/^([\da-fA-F]+):/', $message, $matches); + + return isset($matches[1]) ? $matches[1] : false; + } + + /** + * Returns the LDAP protocol to utilize for the current connection. + * + * @return string + */ + public function getProtocol() + { + return $this->isUsingSSL() ? $this::PROTOCOL_SSL : $this::PROTOCOL; + } + + /** + * Determine if the current PHP version supports server controls. + * + * @return bool + */ + public function supportsServerControlsInMethods() + { + return version_compare(PHP_VERSION, '7.3.0') >= 0; + } + + /** + * Generates an LDAP connection string for each host given. + * + * @param string|array $hosts + * @param string $protocol + * @param string $port + * + * @return string + */ + protected function getConnectionString($hosts, $protocol, $port) + { + // If we are using SSL and using the default port, we + // will override it to use the default SSL port. + if ($this->isUsingSSL() && $port == 389) { + $port = self::PORT_SSL; + } + + // Normalize hosts into an array. + $hosts = is_array($hosts) ? $hosts : [$hosts]; + + $hosts = array_map(function ($host) use ($protocol, $port) { + return "{$protocol}{$host}:{$port}"; + }, $hosts); + + return implode(' ', $hosts); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Connections/Provider.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Connections/Provider.php new file mode 100644 index 0000000000..c8db38706e --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Connections/Provider.php @@ -0,0 +1,291 @@ +setConfiguration($configuration) + ->setConnection($connection); + } + + /** + * Does nothing. Implemented in order to remain backwards compatible. + * + * @deprecated since v10.3.0 + */ + public function __destruct() + { + // + } + + /** + * {@inheritdoc} + */ + public function setConfiguration($configuration = []) + { + if (is_array($configuration)) { + $configuration = new DomainConfiguration($configuration); + } + + if ($configuration instanceof DomainConfiguration) { + $this->configuration = $configuration; + + $schema = $configuration->get('schema'); + + // We will update our schema here when our configuration is set. + $this->setSchema(new $schema()); + + return $this; + } + + $class = DomainConfiguration::class; + + throw new InvalidArgumentException( + "Configuration must be array or instance of $class" + ); + } + + /** + * {@inheritdoc} + */ + public function setConnection(ConnectionInterface $connection = null) + { + // We will create a standard connection if one isn't given. + $this->connection = $connection ?: new Ldap(); + + // Prepare the connection. + $this->prepareConnection(); + + // Instantiate the LDAP connection. + $this->connection->connect( + $this->configuration->get('hosts'), + $this->configuration->get('port') + ); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setSchema(SchemaInterface $schema = null) + { + $this->schema = $schema ?: new ActiveDirectory(); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setGuard(GuardInterface $guard) + { + $this->guard = $guard; + + return $this; + } + + /** + * Sets the cache store. + * + * @param CacheInterface $store + * + * @return $this + */ + public function setCache(CacheInterface $store) + { + $this->cache = new Cache($store); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() + { + return $this->configuration; + } + + /** + * {@inheritdoc} + */ + public function getConnection() + { + return $this->connection; + } + + /** + * {@inheritdoc} + */ + public function getSchema() + { + return $this->schema; + } + + /** + * {@inheritdoc} + */ + public function getGuard() + { + if (!$this->guard instanceof GuardInterface) { + $this->setGuard($this->getDefaultGuard($this->connection, $this->configuration)); + } + + return $this->guard; + } + + /** + * {@inheritdoc} + */ + public function getDefaultGuard(ConnectionInterface $connection, DomainConfiguration $configuration) + { + $guard = new Guard($connection, $configuration); + + $guard->setDispatcher(Adldap::getEventDispatcher()); + + return $guard; + } + + /** + * {@inheritdoc} + */ + public function make() + { + return new ModelFactory( + $this->search()->newQuery() + ); + } + + /** + * {@inheritdoc} + */ + public function search() + { + $factory = new SearchFactory( + $this->connection, + $this->schema, + $this->configuration->get('base_dn') + ); + + if ($this->cache) { + $factory->setCache($this->cache); + } + + return $factory; + } + + /** + * {@inheritdoc} + */ + public function auth() + { + return $this->getGuard(); + } + + /** + * {@inheritdoc} + */ + public function connect($username = null, $password = null) + { + // Get the default guard instance. + $guard = $this->getGuard(); + + if (is_null($username) && is_null($password)) { + // If both the username and password are null, we'll connect to the server + // using the configured administrator username and password. + $guard->bindAsAdministrator(); + } else { + // Bind to the server with the specified username and password otherwise. + $guard->bind($username, $password); + } + + return $this; + } + + /** + * Prepares the connection by setting configured parameters. + * + * @throws \Adldap\Configuration\ConfigurationException When configuration options requested do not exist + * + * @return void + */ + protected function prepareConnection() + { + if ($this->configuration->get('use_ssl')) { + $this->connection->ssl(); + } elseif ($this->configuration->get('use_tls')) { + $this->connection->tls(); + } + + $options = array_replace( + $this->configuration->get('custom_options'), + [ + LDAP_OPT_PROTOCOL_VERSION => $this->configuration->get('version'), + LDAP_OPT_NETWORK_TIMEOUT => $this->configuration->get('timeout'), + LDAP_OPT_REFERRALS => $this->configuration->get('follow_referrals'), + ] + ); + + $this->connection->setOptions($options); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Connections/ProviderInterface.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Connections/ProviderInterface.php new file mode 100644 index 0000000000..20749a7e08 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Connections/ProviderInterface.php @@ -0,0 +1,129 @@ +setupWildcardListen($event, $listener); + } else { + $this->listeners[$event][] = $this->makeListener($listener); + } + } + } + + /** + * Setup a wildcard listener callback. + * + * @param string $event + * @param mixed $listener + * + * @return void + */ + protected function setupWildcardListen($event, $listener) + { + $this->wildcards[$event][] = $this->makeListener($listener, true); + + $this->wildcardsCache = []; + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName) + { + return isset($this->listeners[$eventName]) || isset($this->wildcards[$eventName]); + } + + /** + * {@inheritdoc} + */ + public function until($event, $payload = []) + { + return $this->dispatch($event, $payload, true); + } + + /** + * {@inheritdoc} + */ + public function fire($event, $payload = [], $halt = false) + { + return $this->dispatch($event, $payload, $halt); + } + + /** + * {@inheritdoc} + */ + public function dispatch($event, $payload = [], $halt = false) + { + // When the given "event" is actually an object we will assume it is an event + // object and use the class as the event name and this event itself as the + // payload to the handler, which makes object based events quite simple. + list($event, $payload) = $this->parseEventAndPayload( + $event, + $payload + ); + + $responses = []; + + foreach ($this->getListeners($event) as $listener) { + $response = $listener($event, $payload); + + // If a response is returned from the listener and event halting is enabled + // we will just return this response, and not call the rest of the event + // listeners. Otherwise we will add the response on the response list. + if ($halt && !is_null($response)) { + return $response; + } + + // If a boolean false is returned from a listener, we will stop propagating + // the event to any further listeners down in the chain, else we keep on + // looping through the listeners and firing every one in our sequence. + if ($response === false) { + break; + } + + $responses[] = $response; + } + + return $halt ? null : $responses; + } + + /** + * Parse the given event and payload and prepare them for dispatching. + * + * @param mixed $event + * @param mixed $payload + * + * @return array + */ + protected function parseEventAndPayload($event, $payload) + { + if (is_object($event)) { + list($payload, $event) = [[$event], get_class($event)]; + } + + return [$event, Arr::wrap($payload)]; + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName) + { + $listeners = $this->listeners[$eventName] ?? []; + + $listeners = array_merge( + $listeners, + $this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName) + ); + + return class_exists($eventName, false) + ? $this->addInterfaceListeners($eventName, $listeners) + : $listeners; + } + + /** + * Get the wildcard listeners for the event. + * + * @param string $eventName + * + * @return array + */ + protected function getWildcardListeners($eventName) + { + $wildcards = []; + + foreach ($this->wildcards as $key => $listeners) { + if ($this->wildcardContainsEvent($key, $eventName)) { + $wildcards = array_merge($wildcards, $listeners); + } + } + + return $this->wildcardsCache[$eventName] = $wildcards; + } + + /** + * Determine if the wildcard matches or contains the given event. + * + * This function is a direct excerpt from Laravel's Str::is(). + * + * @param string $wildcard + * @param string $eventName + * + * @return bool + */ + protected function wildcardContainsEvent($wildcard, $eventName) + { + $patterns = Arr::wrap($wildcard); + + if (empty($patterns)) { + return false; + } + + foreach ($patterns as $pattern) { + // If the given event is an exact match we can of course return true right + // from the beginning. Otherwise, we will translate asterisks and do an + // actual pattern match against the two strings to see if they match. + if ($pattern == $eventName) { + return true; + } + + $pattern = preg_quote($pattern, '#'); + + // Asterisks are translated into zero-or-more regular expression wildcards + // to make it convenient to check if the strings starts with the given + // pattern such as "library/*", making any string check convenient. + $pattern = str_replace('\*', '.*', $pattern); + + if (preg_match('#^'.$pattern.'\z#u', $eventName) === 1) { + return true; + } + } + + return false; + } + + /** + * Add the listeners for the event's interfaces to the given array. + * + * @param string $eventName + * @param array $listeners + * + * @return array + */ + protected function addInterfaceListeners($eventName, array $listeners = []) + { + foreach (class_implements($eventName) as $interface) { + if (isset($this->listeners[$interface])) { + foreach ($this->listeners[$interface] as $names) { + $listeners = array_merge($listeners, (array) $names); + } + } + } + + return $listeners; + } + + /** + * Register an event listener with the dispatcher. + * + * @param \Closure|string $listener + * @param bool $wildcard + * + * @return \Closure + */ + public function makeListener($listener, $wildcard = false) + { + if (is_string($listener)) { + return $this->createClassListener($listener, $wildcard); + } + + return function ($event, $payload) use ($listener, $wildcard) { + if ($wildcard) { + return $listener($event, $payload); + } + + return $listener(...array_values($payload)); + }; + } + + /** + * Create a class based listener. + * + * @param string $listener + * @param bool $wildcard + * + * @return \Closure + */ + protected function createClassListener($listener, $wildcard = false) + { + return function ($event, $payload) use ($listener, $wildcard) { + if ($wildcard) { + return call_user_func($this->parseListenerCallback($listener), $event, $payload); + } + + return call_user_func_array( + $this->parseListenerCallback($listener), + $payload + ); + }; + } + + /** + * Parse the class listener into class and method. + * + * @param string $listener + * + * @return array + */ + protected function parseListenerCallback($listener) + { + return strpos($listener, '@') !== false ? + explode('@', $listener, 2) : + [$listener, 'handle']; + } + + /** + * {@inheritdoc} + */ + public function forget($event) + { + if (strpos($event, '*') !== false) { + unset($this->wildcards[$event]); + } else { + unset($this->listeners[$event]); + } + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Events/DispatcherInterface.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Events/DispatcherInterface.php new file mode 100644 index 0000000000..e014543f5b --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Events/DispatcherInterface.php @@ -0,0 +1,75 @@ +logger = $logger; + } + + /** + * Logs the given event. + * + * @param mixed $event + */ + public function log($event) + { + if ($event instanceof AuthEvent) { + $this->auth($event); + } elseif ($event instanceof ModelEvent) { + $this->model($event); + } elseif ($event instanceof QueryEvent) { + $this->query($event); + } + } + + /** + * Logs an authentication event. + * + * @param AuthEvent $event + * + * @return void + */ + public function auth(AuthEvent $event) + { + if (isset($this->logger)) { + $connection = $event->getConnection(); + + $message = "LDAP ({$connection->getHost()})" + ." - Connection: {$connection->getName()}" + ." - Operation: {$this->getOperationName($event)}" + ." - Username: {$event->getUsername()}"; + + $result = null; + $type = 'info'; + + if (is_a($event, Failed::class)) { + $type = 'warning'; + $result = " - Reason: {$connection->getLastError()}"; + } + + $this->logger->$type($message.$result); + } + } + + /** + * Logs a model event. + * + * @param ModelEvent $event + * + * @return void + */ + public function model(ModelEvent $event) + { + if (isset($this->logger)) { + $model = $event->getModel(); + + $on = get_class($model); + + $connection = $model->getQuery()->getConnection(); + + $message = "LDAP ({$connection->getHost()})" + ." - Connection: {$connection->getName()}" + ." - Operation: {$this->getOperationName($event)}" + ." - On: {$on}" + ." - Distinguished Name: {$model->getDn()}"; + + $this->logger->info($message); + } + } + + /** + * Logs a query event. + * + * @param QueryEvent $event + * + * @return void + */ + public function query(QueryEvent $event) + { + if (isset($this->logger)) { + $query = $event->getQuery(); + + $connection = $query->getConnection(); + + $selected = implode(',', $query->getSelects()); + + $message = "LDAP ({$connection->getHost()})" + ." - Connection: {$connection->getName()}" + ." - Operation: {$this->getOperationName($event)}" + ." - Base DN: {$query->getDn()}" + ." - Filter: {$query->getUnescapedQuery()}" + ." - Selected: ({$selected})" + ." - Time Elapsed: {$event->getTime()}"; + + $this->logger->info($message); + } + } + + /** + * Returns the operational name of the given event. + * + * @param mixed $event + * + * @return string + */ + protected function getOperationName($event) + { + return (new ReflectionClass($event))->getShortName(); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Log/LogsInformation.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Log/LogsInformation.php new file mode 100644 index 0000000000..d6f5191cc7 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Log/LogsInformation.php @@ -0,0 +1,47 @@ +apply($flag); + } + } + + /** + * Get the value when casted to string. + * + * @return string + */ + public function __toString() + { + return (string) $this->getValue(); + } + + /** + * Get the value when casted to int. + * + * @return int + */ + public function __toInt() + { + return $this->getValue(); + } + + /** + * Add the value to the account control values. + * + * @param int $value + * + * @return AccountControl + */ + public function add($value) + { + // Use the value as a key so if the same value + // is used, it will always be overwritten + $this->values[$value] = $value; + + return $this; + } + + /** + * Remove the value from the account control. + * + * @param int $value + * + * @return $this + */ + public function remove($value) + { + unset($this->values[$value]); + + return $this; + } + + /** + * Extract and apply the flag. + * + * @param int $flag + */ + public function apply($flag) + { + $this->setValues($this->extractFlags($flag)); + } + + /** + * Determine if the current AccountControl object contains the given UAC flag(s). + * + * @param int $flag + * + * @return bool + */ + public function has($flag) + { + // We'll extract the given flag into an array of possible flags, and + // see if our AccountControl object contains any of them. + $flagsUsed = array_intersect($this->extractFlags($flag), $this->values); + + return in_array($flag, $flagsUsed); + } + + /** + * The logon script will be run. + * + * @return AccountControl + */ + public function runLoginScript() + { + return $this->add(static::SCRIPT); + } + + /** + * The user account is locked. + * + * @return AccountControl + */ + public function accountIsLocked() + { + return $this->add(static::LOCKOUT); + } + + /** + * The user account is disabled. + * + * @return AccountControl + */ + public function accountIsDisabled() + { + return $this->add(static::ACCOUNTDISABLE); + } + + /** + * This is an account for users whose primary account is in another domain. + * + * This account provides user access to this domain, but not to any domain that + * trusts this domain. This is sometimes referred to as a local user account. + * + * @return AccountControl + */ + public function accountIsTemporary() + { + return $this->add(static::TEMP_DUPLICATE_ACCOUNT); + } + + /** + * This is a default account type that represents a typical user. + * + * @return AccountControl + */ + public function accountIsNormal() + { + return $this->add(static::NORMAL_ACCOUNT); + } + + /** + * This is a permit to trust an account for a system domain that trusts other domains. + * + * @return AccountControl + */ + public function accountIsForInterdomain() + { + return $this->add(static::INTERDOMAIN_TRUST_ACCOUNT); + } + + /** + * This is a computer account for a computer that is running Microsoft + * Windows NT 4.0 Workstation, Microsoft Windows NT 4.0 Server, Microsoft + * Windows 2000 Professional, or Windows 2000 Server and is a member of this domain. + * + * @return AccountControl + */ + public function accountIsForWorkstation() + { + return $this->add(static::WORKSTATION_TRUST_ACCOUNT); + } + + /** + * This is a computer account for a domain controller that is a member of this domain. + * + * @return AccountControl + */ + public function accountIsForServer() + { + return $this->add(static::SERVER_TRUST_ACCOUNT); + } + + /** + * This is an MNS logon account. + * + * @return AccountControl + */ + public function accountIsMnsLogon() + { + return $this->add(static::MNS_LOGON_ACCOUNT); + } + + /** + * (Windows 2000/Windows Server 2003) This account does + * not require Kerberos pre-authentication for logging on. + * + * @return AccountControl + */ + public function accountDoesNotRequirePreAuth() + { + return $this->add(static::DONT_REQ_PREAUTH); + } + + /** + * When this flag is set, it forces the user to log on by using a smart card. + * + * @return AccountControl + */ + public function accountRequiresSmartCard() + { + return $this->add(static::SMARTCARD_REQUIRED); + } + + /** + * (Windows Server 2008/Windows Server 2008 R2) The account is a read-only domain controller (RODC). + * + * This is a security-sensitive setting. Removing this setting from an RODC compromises security on that server. + * + * @return AccountControl + */ + public function accountIsReadOnly() + { + return $this->add(static::PARTIAL_SECRETS_ACCOUNT); + } + + /** + * The home folder is required. + * + * @return AccountControl + */ + public function homeFolderIsRequired() + { + return $this->add(static::HOMEDIR_REQUIRED); + } + + /** + * No password is required. + * + * @return AccountControl + */ + public function passwordIsNotRequired() + { + return $this->add(static::PASSWD_NOTREQD); + } + + /** + * The user cannot change the password. This is a permission on the user's object. + * + * For information about how to programmatically set this permission, visit the following link: + * + * @link http://msdn2.microsoft.com/en-us/library/aa746398.aspx + * + * @return AccountControl + */ + public function passwordCannotBeChanged() + { + return $this->add(static::PASSWD_NOTREQD); + } + + /** + * Represents the password, which should never expire on the account. + * + * @return AccountControl + */ + public function passwordDoesNotExpire() + { + return $this->add(static::DONT_EXPIRE_PASSWORD); + } + + /** + * (Windows 2000/Windows Server 2003) The user's password has expired. + * + * @return AccountControl + */ + public function passwordIsExpired() + { + return $this->add(static::PASSWORD_EXPIRED); + } + + /** + * The user can send an encrypted password. + * + * @return AccountControl + */ + public function allowEncryptedTextPassword() + { + return $this->add(static::ENCRYPTED_TEXT_PWD_ALLOWED); + } + + /** + * When this flag is set, the service account (the user or computer account) + * under which a service runs is trusted for Kerberos delegation. + * + * Any such service can impersonate a client requesting the service. + * + * To enable a service for Kerberos delegation, you must set this + * flag on the userAccountControl property of the service account. + * + * @return AccountControl + */ + public function trustForDelegation() + { + return $this->add(static::TRUSTED_FOR_DELEGATION); + } + + /** + * (Windows 2000/Windows Server 2003) The account is enabled for delegation. + * + * This is a security-sensitive setting. Accounts that have this option enabled + * should be tightly controlled. This setting lets a service that runs under the + * account assume a client's identity and authenticate as that user to other remote + * servers on the network. + * + * @return AccountControl + */ + public function trustToAuthForDelegation() + { + return $this->add(static::TRUSTED_TO_AUTH_FOR_DELEGATION); + } + + /** + * When this flag is set, the security context of the user is not delegated to a + * service even if the service account is set as trusted for Kerberos delegation. + * + * @return AccountControl + */ + public function doNotTrustForDelegation() + { + return $this->add(static::NOT_DELEGATED); + } + + /** + * (Windows 2000/Windows Server 2003) Restrict this principal to + * use only Data Encryption Standard (DES) encryption types for keys. + * + * @return AccountControl + */ + public function useDesKeyOnly() + { + return $this->add(static::USE_DES_KEY_ONLY); + } + + /** + * Get the account control value. + * + * @return int + */ + public function getValue() + { + return array_sum($this->values); + } + + /** + * Get the account control flag values. + * + * @return array + */ + public function getValues() + { + return $this->values; + } + + /** + * Set the account control values. + * + * @param array $flags + */ + public function setValues(array $flags) + { + $this->values = $flags; + } + + /** + * Get all possible account control flags. + * + * @return array + */ + public function getAllFlags() + { + return (new ReflectionClass(__CLASS__))->getConstants(); + } + + /** + * Extracts the given flag into an array of flags used. + * + * @param int $flag + * + * @return array + */ + public function extractFlags($flag) + { + $flags = []; + + for ($i = 0; $i <= 26; $i++) { + if ((int) $flag & (1 << $i)) { + $flags[1 << $i] = 1 << $i; + } + } + + return $flags; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/DistinguishedName.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/DistinguishedName.php new file mode 100644 index 0000000000..e610dbfda2 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/DistinguishedName.php @@ -0,0 +1,312 @@ + [], + 'uid' => [], + 'ou' => [], + 'dc' => [], + 'o' => [], + ]; + + /** + * Constructor. + * + * @param mixed $baseDn + */ + public function __construct($baseDn = null) + { + $this->setBase($baseDn); + } + + /** + * Returns the complete distinguished name. + * + * @return string + */ + public function __toString() + { + return $this->get(); + } + + /** + * Returns the complete distinguished name by assembling the RDN components. + * + * @return string + */ + public function get() + { + $components = []; + + // We'll go through each component type and assemble its RDN. + foreach ($this->components as $component => $values) { + array_map(function ($value) use ($component, &$components) { + // Assemble the component and escape the value. + $components[] = sprintf('%s=%s', $component, ldap_escape($value, '', 2)); + }, $values); + } + + return implode(',', $components); + } + + /** + * Adds a domain component. + * + * @param string $dc + * + * @return DistinguishedName + */ + public function addDc($dc) + { + $this->addComponent('dc', $dc); + + return $this; + } + + /** + * Removes a domain component. + * + * @param string $dc + * + * @return DistinguishedName + */ + public function removeDc($dc) + { + $this->removeComponent('dc', $dc); + + return $this; + } + + /** + * Adds an organization name. + * + * @param string $o + * + * @return $this + */ + public function addO($o) + { + $this->addComponent('o', $o); + + return $this; + } + + /** + * Removes an organization name. + * + * @param string $o + * + * @return DistinguishedName + */ + public function removeO($o) + { + $this->removeComponent('o', $o); + + return $this; + } + + /** + * Add a user identifier. + * + * @param string $uid + * + * @return DistinguishedName + */ + public function addUid($uid) + { + $this->addComponent('uid', $uid); + + return $this; + } + + /** + * Removes a user identifier. + * + * @param string $uid + * + * @return DistinguishedName + */ + public function removeUid($uid) + { + $this->removeComponent('uid', $uid); + + return $this; + } + + /** + * Adds a common name. + * + * @param string $cn + * + * @return DistinguishedName + */ + public function addCn($cn) + { + $this->addComponent('cn', $cn); + + return $this; + } + + /** + * Removes a common name. + * + * @param string $cn + * + * @return DistinguishedName + */ + public function removeCn($cn) + { + $this->removeComponent('cn', $cn); + + return $this; + } + + /** + * Adds an organizational unit. + * + * @param string $ou + * + * @return DistinguishedName + */ + public function addOu($ou) + { + $this->addComponent('ou', $ou); + + return $this; + } + + /** + * Removes an organizational unit. + * + * @param string $ou + * + * @return DistinguishedName + */ + public function removeOu($ou) + { + $this->removeComponent('ou', $ou); + + return $this; + } + + /** + * Sets the base RDN of the distinguished name. + * + * @param string|DistinguishedName $base + * + * @return DistinguishedName + */ + public function setBase($base) + { + // Typecast base to string in case we've been given + // an instance of the distinguished name object. + $base = (string) $base; + + // If the base DN isn't null we'll try to explode it. + $base = Utilities::explodeDn($base, false) ?: []; + + // Remove the count key from the exploded distinguished name. + unset($base['count']); + + foreach ($base as $key => $rdn) { + // We'll break the RDN into pieces + $pieces = explode('=', $rdn) ?: []; + + // If there's exactly 2 pieces, then we can work with it. + if (count($pieces) === 2) { + $attribute = ucfirst(strtolower($pieces[0])); + + $method = 'add'.$attribute; + + if (method_exists($this, $method)) { + // We see what type of RDN it is and add each accordingly. + call_user_func_array([$this, $method], [$pieces[1]]); + } + } + } + + return $this; + } + + /** + * Returns an array of all components in the distinguished name. + * + * If a component name is given ('cn', 'dc' for example) then + * the values of that component will be returned. + * + * @param string|null $component The component to retrieve values of + * + * @return array + */ + public function getComponents($component = null) + { + if (is_null($component)) { + return $this->components; + } + + $this->validateComponentExists($component); + + return $this->components[$component]; + } + + /** + * Adds a component to the distinguished name. + * + * @param string $component + * @param string $value + * + * @throws \UnexpectedValueException When the given name does not exist. + */ + protected function addComponent($component, $value) + { + $this->validateComponentExists($component); + + // We need to make sure the value we're given isn't empty before adding it into our components. + if (!empty($value)) { + $this->components[$component][] = $value; + } + } + + /** + * Removes the given value from the given component. + * + * @param string $component + * @param string $value + * + * @throws \UnexpectedValueException When the given component does not exist. + * + * @return void + */ + protected function removeComponent($component, $value) + { + $this->validateComponentExists($component); + + $this->components[$component] = array_diff($this->components[$component], [$value]); + } + + /** + * Validates that the given component exists in the available components. + * + * @param string $component The name of the component to validate. + * + * @throws \UnexpectedValueException When the given component does not exist. + * + * @return void + */ + protected function validateComponentExists($component) + { + if (!array_key_exists($component, $this->components)) { + throw new \UnexpectedValueException("The RDN component '$component' does not exist."); + } + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/Guid.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/Guid.php new file mode 100644 index 0000000000..6c6a600766 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/Guid.php @@ -0,0 +1,157 @@ + + * + * @link https://github.com/ldaptools/ldaptools + * + * @var array + */ + protected $guidSections = [ + [[-26, 2], [-28, 2], [-30, 2], [-32, 2]], + [[-22, 2], [-24, 2]], + [[-18, 2], [-20, 2]], + [[-16, 4]], + [[-12, 12]], + ]; + + /** + * The hexadecimal octet order based on string position. + * + * @author Chad Sikorra + * + * @link https://github.com/ldaptools/ldaptools + * + * @var array + */ + protected $octetSections = [ + [6, 4, 2, 0], + [10, 8], + [14, 12], + [16, 18, 20, 22, 24, 26, 28, 30], + ]; + + /** + * Determines if the specified GUID is valid. + * + * @param string $guid + * + * @return bool + */ + public static function isValid($guid) + { + return Utilities::isValidGuid($guid); + } + + /** + * Constructor. + * + * @param mixed $value + * + * @throws InvalidArgumentException + */ + public function __construct($value) + { + if (static::isValid($value)) { + $this->value = $value; + } elseif ($value = $this->binaryGuidToString($value)) { + $this->value = $value; + } else { + throw new InvalidArgumentException('Invalid Binary / String GUID.'); + } + } + + /** + * Returns the string value of the GUID. + * + * @return string + */ + public function __toString() + { + return $this->getValue(); + } + + /** + * Returns the string value of the SID. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Get the binary representation of the GUID string. + * + * @return string + */ + public function getBinary() + { + $data = ''; + + $guid = str_replace('-', '', $this->value); + + foreach ($this->octetSections as $section) { + $data .= $this->parseSection($guid, $section, true); + } + + return hex2bin($data); + } + + /** + * Returns the string variant of a binary GUID. + * + * @param string $binary + * + * @return string|null + */ + protected function binaryGuidToString($binary) + { + return Utilities::binaryGuidToString($binary); + } + + /** + * Return the specified section of the hexadecimal string. + * + * @author Chad Sikorra + * + * @link https://github.com/ldaptools/ldaptools + * + * @param string $hex The full hex string. + * @param array $sections An array of start and length (unless octet is true, then length is always 2). + * @param bool $octet Whether this is for octet string form. + * + * @return string The concatenated sections in upper-case. + */ + protected function parseSection($hex, array $sections, $octet = false) + { + $parsedString = ''; + + foreach ($sections as $section) { + $start = $octet ? $section : $section[0]; + + $length = $octet ? 2 : $section[1]; + + $parsedString .= substr($hex, $start, $length); + } + + return $parsedString; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/MbString.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/MbString.php new file mode 100644 index 0000000000..54a0db8e1c --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/MbString.php @@ -0,0 +1,80 @@ +value = $value; + } elseif ($value = $this->binarySidToString($value)) { + $this->value = $value; + } else { + throw new InvalidArgumentException('Invalid Binary / String SID.'); + } + } + + /** + * Returns the string value of the SID. + * + * @return string + */ + public function __toString() + { + return $this->getValue(); + } + + /** + * Returns the string value of the SID. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the binary variant of the SID. + * + * @return string + */ + public function getBinary() + { + $sid = explode('-', ltrim($this->value, 'S-')); + + $level = (int) array_shift($sid); + + $authority = (int) array_shift($sid); + + $subAuthorities = array_map('intval', $sid); + + $params = array_merge( + ['C2xxNV*', $level, count($subAuthorities), $authority], + $subAuthorities + ); + + return call_user_func_array('pack', $params); + } + + /** + * Returns the string variant of a binary SID. + * + * @param string $binary + * + * @return string|null + */ + protected function binarySidToString($binary) + { + return Utilities::binarySidToString($binary); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/TSProperty.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/TSProperty.php new file mode 100644 index 0000000000..aa3d63ca34 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/TSProperty.php @@ -0,0 +1,396 @@ + ['001011', '011010'], + 'Y' => ['001110', '011010'], + ]; + + /** + * The nibble header. + */ + const NIBBLE_HEADER = '1110'; + + /** + * Conversion factor needed for time values in the TSPropertyArray (stored in microseconds). + */ + const TIME_CONVERSION = 60 * 1000; + + /** + * A simple map to help determine how the property needs to be decoded/encoded from/to its binary value. + * + * There are some names that are simple repeats but have 'W' at the end. Not sure as to what that signifies. I + * cannot find any information on them in Microsoft documentation. However, their values appear to stay in sync with + * their non 'W' counterparts. But not doing so when manipulating the data manually does not seem to affect anything. + * This probably needs more investigation. + * + * @var array + */ + protected $propTypes = [ + 'string' => [ + 'CtxWFHomeDir', + 'CtxWFHomeDirW', + 'CtxWFHomeDirDrive', + 'CtxWFHomeDirDriveW', + 'CtxInitialProgram', + 'CtxInitialProgramW', + 'CtxWFProfilePath', + 'CtxWFProfilePathW', + 'CtxWorkDirectory', + 'CtxWorkDirectoryW', + 'CtxCallbackNumber', + ], + 'time' => [ + 'CtxMaxDisconnectionTime', + 'CtxMaxConnectionTime', + 'CtxMaxIdleTime', + ], + 'int' => [ + 'CtxCfgFlags1', + 'CtxCfgPresent', + 'CtxKeyboardLayout', + 'CtxMinEncryptionLevel', + 'CtxNWLogonServer', + 'CtxShadow', + ], + ]; + + /** + * The property name. + * + * @var string + */ + protected $name; + + /** + * The property value. + * + * @var string|int + */ + protected $value; + + /** + * The property value type. + * + * @var int + */ + protected $valueType = 1; + + /** + * Pass binary TSProperty data to construct its object representation. + * + * @param string|null $value + */ + public function __construct($value = null) + { + if ($value) { + $this->decode(bin2hex($value)); + } + } + + /** + * Set the name for the TSProperty. + * + * @param string $name + * + * @return TSProperty + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * Get the name for the TSProperty. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set the value for the TSProperty. + * + * @param string|int $value + * + * @return TSProperty + */ + public function setValue($value) + { + $this->value = $value; + + return $this; + } + + /** + * Get the value for the TSProperty. + * + * @return string|int + */ + public function getValue() + { + return $this->value; + } + + /** + * Convert the TSProperty name/value back to its binary + * representation for the userParameters blob. + * + * @return string + */ + public function toBinary() + { + $name = bin2hex($this->name); + + $binValue = $this->getEncodedValueForProp($this->name, $this->value); + + $valueLen = strlen(bin2hex($binValue)) / 3; + + $binary = hex2bin( + $this->dec2hex(strlen($name)) + .$this->dec2hex($valueLen) + .$this->dec2hex($this->valueType) + .$name + ); + + return $binary.$binValue; + } + + /** + * Given a TSProperty blob, decode the name/value/type/etc. + * + * @param string $tsProperty + */ + protected function decode($tsProperty) + { + $nameLength = hexdec(substr($tsProperty, 0, 2)); + + // 1 data byte is 3 encoded bytes + $valueLength = hexdec(substr($tsProperty, 2, 2)) * 3; + + $this->valueType = hexdec(substr($tsProperty, 4, 2)); + $this->name = pack('H*', substr($tsProperty, 6, $nameLength)); + $this->value = $this->getDecodedValueForProp($this->name, substr($tsProperty, 6 + $nameLength, $valueLength)); + } + + /** + * Based on the property name/value in question, get its encoded form. + * + * @param string $propName + * @param string|int $propValue + * + * @return string + */ + protected function getEncodedValueForProp($propName, $propValue) + { + if (in_array($propName, $this->propTypes['string'])) { + // Simple strings are null terminated. Unsure if this is + // needed or simply a product of how ADUC does stuff? + $value = $this->encodePropValue($propValue."\0", true); + } elseif (in_array($propName, $this->propTypes['time'])) { + // Needs to be in microseconds (assuming it is in minute format)... + $value = $this->encodePropValue($propValue * self::TIME_CONVERSION); + } else { + $value = $this->encodePropValue($propValue); + } + + return $value; + } + + /** + * Based on the property name in question, get its actual value from the binary blob value. + * + * @param string $propName + * @param string $propValue + * + * @return string|int + */ + protected function getDecodedValueForProp($propName, $propValue) + { + if (in_array($propName, $this->propTypes['string'])) { + // Strip away null terminators. I think this should + // be desired, otherwise it just ends in confusion. + $value = str_replace("\0", '', $this->decodePropValue($propValue, true)); + } elseif (in_array($propName, $this->propTypes['time'])) { + // Convert from microseconds to minutes (how ADUC displays + // it anyway, and seems the most practical). + $value = hexdec($this->decodePropValue($propValue)) / self::TIME_CONVERSION; + } elseif (in_array($propName, $this->propTypes['int'])) { + $value = hexdec($this->decodePropValue($propValue)); + } else { + $value = $this->decodePropValue($propValue); + } + + return $value; + } + + /** + * Decode the property by inspecting the nibbles of each blob, checking + * the control, and adding up the results into a final value. + * + * @param string $hex + * @param bool $string Whether or not this is simple string data. + * + * @return string + */ + protected function decodePropValue($hex, $string = false) + { + $decodePropValue = ''; + + $blobs = str_split($hex, 6); + + foreach ($blobs as $blob) { + $bin = decbin(hexdec($blob)); + + $controlY = substr($bin, 4, 6); + $nibbleY = substr($bin, 10, 4); + $controlX = substr($bin, 14, 6); + $nibbleX = substr($bin, 20, 4); + + $byte = $this->nibbleControl($nibbleX, $controlX).$this->nibbleControl($nibbleY, $controlY); + + if ($string) { + $decodePropValue .= MbString::chr(bindec($byte)); + } else { + $decodePropValue = $this->dec2hex(bindec($byte)).$decodePropValue; + } + } + + return $decodePropValue; + } + + /** + * Get the encoded property value as a binary blob. + * + * @param string $value + * @param bool $string + * + * @return string + */ + protected function encodePropValue($value, $string = false) + { + // An int must be properly padded. (then split and reversed). + // For a string, we just split the chars. This seems + // to be the easiest way to handle UTF-8 characters + // instead of trying to work with their hex values. + $chars = $string ? MbString::split($value) : array_reverse(str_split($this->dec2hex($value, 8), 2)); + + $encoded = ''; + + foreach ($chars as $char) { + // Get the bits for the char. Using this method to ensure it is fully padded. + $bits = sprintf('%08b', $string ? MbString::ord($char) : hexdec($char)); + $nibbleX = substr($bits, 0, 4); + $nibbleY = substr($bits, 4, 4); + + // Construct the value with the header, high nibble, then low nibble. + $value = self::NIBBLE_HEADER; + + foreach (['Y' => $nibbleY, 'X' => $nibbleX] as $nibbleType => $nibble) { + $value .= $this->getNibbleWithControl($nibbleType, $nibble); + } + + // Convert it back to a binary bit stream + foreach ([0, 8, 16] as $start) { + $encoded .= $this->packBitString(substr($value, $start, 8), 8); + } + } + + return $encoded; + } + + /** + * PHP's pack() function has no 'b' or 'B' template. This is + * a workaround that turns a literal bit-string into a + * packed byte-string with 8 bits per byte. + * + * @param string $bits + * @param bool $len + * + * @return string + */ + protected function packBitString($bits, $len) + { + $bits = substr($bits, 0, $len); + // Pad input with zeros to next multiple of 4 above $len + $bits = str_pad($bits, 4 * (int) (($len + 3) / 4), '0'); + + // Split input into chunks of 4 bits, convert each to hex and pack them + $nibbles = str_split($bits, 4); + foreach ($nibbles as $i => $nibble) { + $nibbles[$i] = base_convert($nibble, 2, 16); + } + + return pack('H*', implode('', $nibbles)); + } + + /** + * Based on the control, adjust the nibble accordingly. + * + * @param string $nibble + * @param string $control + * + * @return string + */ + protected function nibbleControl($nibble, $control) + { + // This control stays constant for the low/high nibbles, + // so it doesn't matter which we compare to + if ($control == self::NIBBLE_CONTROL['X'][1]) { + $dec = bindec($nibble); + $dec += 9; + $nibble = str_pad(decbin($dec), 4, '0', STR_PAD_LEFT); + } + + return $nibble; + } + + /** + * Get the nibble value with the control prefixed. + * + * If the nibble dec is <= 9, the control X equals 001011 and Y equals 001110, otherwise if the nibble dec is > 9 + * the control for X or Y equals 011010. Additionally, if the dec value of the nibble is > 9, then the nibble value + * must be subtracted by 9 before the final value is constructed. + * + * @param string $nibbleType Either X or Y + * @param string $nibble + * + * @return string + */ + protected function getNibbleWithControl($nibbleType, $nibble) + { + $dec = bindec($nibble); + + if ($dec > 9) { + $dec -= 9; + $control = self::NIBBLE_CONTROL[$nibbleType][1]; + } else { + $control = self::NIBBLE_CONTROL[$nibbleType][0]; + } + + return $control.sprintf('%04d', decbin($dec)); + } + + /** + * Need to make sure hex values are always an even length, so pad as needed. + * + * @param int $int + * @param int $padLength The hex string must be padded to this length (with zeros). + * + * @return string + */ + protected function dec2hex($int, $padLength = 2) + { + return str_pad(dechex($int), $padLength, 0, STR_PAD_LEFT); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/TSPropertyArray.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/TSPropertyArray.php new file mode 100644 index 0000000000..4eac74b3be --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Attributes/TSPropertyArray.php @@ -0,0 +1,295 @@ + 2953518677, + 'CtxWFProfilePath' => '', + 'CtxWFProfilePathW' => '', + 'CtxWFHomeDir' => '', + 'CtxWFHomeDirW' => '', + 'CtxWFHomeDirDrive' => '', + 'CtxWFHomeDirDriveW' => '', + 'CtxShadow' => 1, + 'CtxMaxDisconnectionTime' => 0, + 'CtxMaxConnectionTime' => 0, + 'CtxMaxIdleTime' => 0, + 'CtxWorkDirectory' => '', + 'CtxWorkDirectoryW' => '', + 'CtxCfgFlags1' => 2418077696, + 'CtxInitialProgram' => '', + 'CtxInitialProgramW' => '', + ]; + + /** + * @var string The default data that occurs before the TSPropertyArray (CtxCfgPresent with a bunch of spaces...?) + */ + protected $defaultPreBinary = '43747843666750726573656e742020202020202020202020202020202020202020202020202020202020202020202020'; + + /** + * @var TSProperty[] + */ + protected $tsProperty = []; + + /** + * @var string + */ + protected $signature = self::VALID_SIGNATURE; + + /** + * Binary data that occurs before the TSPropertyArray data in userParameters. + * + * @var string + */ + protected $preBinary = ''; + + /** + * Binary data that occurs after the TSPropertyArray data in userParameters. + * + * @var string + */ + protected $postBinary = ''; + + /** + * Construct in one of the following ways:. + * + * - Pass an array of TSProperty key => value pairs (See DEFAULTS constant). + * - Pass the userParameters binary value. The object representation of that will be decoded and constructed. + * - Pass nothing and a default set of TSProperty key => value pairs will be used (See DEFAULTS constant). + * + * @param mixed $tsPropertyArray + */ + public function __construct($tsPropertyArray = null) + { + $this->preBinary = hex2bin($this->defaultPreBinary); + + if (is_null($tsPropertyArray) || is_array($tsPropertyArray)) { + $tsPropertyArray = $tsPropertyArray ?: self::DEFAULTS; + + foreach ($tsPropertyArray as $key => $value) { + $tsProperty = new TSProperty(); + + $this->tsProperty[$key] = $tsProperty->setName($key)->setValue($value); + } + } else { + $this->decodeUserParameters($tsPropertyArray); + } + } + + /** + * Check if a specific TSProperty exists by its property name. + * + * @param string $propName + * + * @return bool + */ + public function has($propName) + { + return array_key_exists(strtolower($propName), array_change_key_case($this->tsProperty)); + } + + /** + * Get a TSProperty object by its property name (ie. CtxWFProfilePath). + * + * @param string $propName + * + * @return TSProperty + */ + public function get($propName) + { + $this->validateProp($propName); + + return $this->getTsPropObj($propName); + } + + /** + * Add a TSProperty object. If it already exists, it will be overwritten. + * + * @param TSProperty $tsProperty + * + * @return $this + */ + public function add(TSProperty $tsProperty) + { + $this->tsProperty[$tsProperty->getName()] = $tsProperty; + + return $this; + } + + /** + * Remove a TSProperty by its property name (ie. CtxMinEncryptionLevel). + * + * @param string $propName + * + * @return $this + */ + public function remove($propName) + { + foreach (array_keys($this->tsProperty) as $property) { + if (strtolower($propName) == strtolower($property)) { + unset($this->tsProperty[$property]); + } + } + + return $this; + } + + /** + * Set the value for a specific TSProperty by its name. + * + * @param string $propName + * @param mixed $propValue + * + * @return $this + */ + public function set($propName, $propValue) + { + $this->validateProp($propName); + + $this->getTsPropObj($propName)->setValue($propValue); + + return $this; + } + + /** + * Get the full binary representation of the userParameters containing the TSPropertyArray data. + * + * @return string + */ + public function toBinary() + { + $binary = $this->preBinary; + + $binary .= hex2bin(str_pad(dechex(MbString::ord($this->signature)), 2, 0, STR_PAD_LEFT)); + + $binary .= hex2bin(str_pad(dechex(count($this->tsProperty)), 2, 0, STR_PAD_LEFT)); + + foreach ($this->tsProperty as $tsProperty) { + $binary .= $tsProperty->toBinary(); + } + + return $binary.$this->postBinary; + } + + /** + * Get a simple associative array containing of all TSProperty names and values. + * + * @return array + */ + public function toArray() + { + $userParameters = []; + + foreach ($this->tsProperty as $property => $tsPropObj) { + $userParameters[$property] = $tsPropObj->getValue(); + } + + return $userParameters; + } + + /** + * Get all TSProperty objects. + * + * @return TSProperty[] + */ + public function getTSProperties() + { + return $this->tsProperty; + } + + /** + * Validates that the given property name exists. + * + * @param string $propName + */ + protected function validateProp($propName) + { + if (!$this->has($propName)) { + throw new InvalidArgumentException(sprintf('TSProperty for "%s" does not exist.', $propName)); + } + } + + /** + * @param string $propName + * + * @return TSProperty + */ + protected function getTsPropObj($propName) + { + return array_change_key_case($this->tsProperty)[strtolower($propName)]; + } + + /** + * Get an associative array with all of the userParameters property names and values. + * + * @param string $userParameters + * + * @return void + */ + protected function decodeUserParameters($userParameters) + { + $userParameters = bin2hex($userParameters); + + // Save the 96-byte array of reserved data, so as to not ruin anything that may be stored there. + $this->preBinary = hex2bin(substr($userParameters, 0, 96)); + // The signature is a 2-byte unicode character at the front + $this->signature = MbString::chr(hexdec(substr($userParameters, 96, 2))); + // This asserts the validity of the tsPropertyArray data. For some reason 'P' means valid... + if ($this->signature != self::VALID_SIGNATURE) { + throw new InvalidArgumentException('Invalid TSPropertyArray data'); + } + + // The property count is a 2-byte unsigned integer indicating the number of elements for the tsPropertyArray + // It starts at position 98. The actual variable data begins at position 100. + $length = $this->addTSPropData(substr($userParameters, 100), hexdec(substr($userParameters, 98, 2))); + + // Reserved data length + (count and sig length == 4) + the added lengths of the TSPropertyArray + // This saves anything after that variable TSPropertyArray data, so as to not squash anything stored there + if (strlen($userParameters) > (96 + 4 + $length)) { + $this->postBinary = hex2bin(substr($userParameters, (96 + 4 + $length))); + } + } + + /** + * Given the start of TSPropertyArray hex data, and the count for the number + * of TSProperty structures in contains, parse and split out the + * individual TSProperty structures. Return the full length + * of the TSPropertyArray data. + * + * @param string $tsPropertyArray + * @param int $tsPropCount + * + * @return int The length of the data in the TSPropertyArray + */ + protected function addTSPropData($tsPropertyArray, $tsPropCount) + { + $length = 0; + + for ($i = 0; $i < $tsPropCount; $i++) { + // Prop length = name length + value length + type length + the space for the length data. + $propLength = hexdec(substr($tsPropertyArray, $length, 2)) + (hexdec(substr($tsPropertyArray, $length + 2, 2)) * 3) + 6; + + $tsProperty = new TSProperty(hex2bin(substr($tsPropertyArray, $length, $propLength))); + + $this->tsProperty[$tsProperty->getName()] = $tsProperty; + + $length += $propLength; + } + + return $length; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/BatchModification.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/BatchModification.php new file mode 100644 index 0000000000..08ddca847a --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/BatchModification.php @@ -0,0 +1,270 @@ +setAttribute($attribute) + ->setType($type) + ->setValues($values); + } + + /** + * Sets the original value of the attribute before modification. + * + * @param mixed $original + * + * @return $this + */ + public function setOriginal($original = null) + { + $this->original = $original; + + return $this; + } + + /** + * Returns the original value of the attribute before modification. + * + * @return mixed + */ + public function getOriginal() + { + return $this->original; + } + + /** + * Sets the attribute of the modification. + * + * @param string $attribute + * + * @return $this + */ + public function setAttribute($attribute) + { + $this->attribute = $attribute; + + return $this; + } + + /** + * Returns the attribute of the modification. + * + * @return string + */ + public function getAttribute() + { + return $this->attribute; + } + + /** + * Sets the values of the modification. + * + * @param array $values + * + * @return $this + */ + public function setValues(array $values = []) + { + $this->values = array_map(function ($value) { + // We need to make sure all values given to a batch modification are + // strings, otherwise we'll receive an LDAP exception when + // we try to process the modification. + return (string) $value; + }, $values); + + return $this; + } + + /** + * Returns the values of the modification. + * + * @return array + */ + public function getValues() + { + return $this->values; + } + + /** + * Sets the type of the modification. + * + * @param int|null $type + * + * @return $this + */ + public function setType($type = null) + { + if (!is_null($type) && !$this->isValidType($type)) { + throw new InvalidArgumentException('Given batch modification type is invalid.'); + } + + $this->type = $type; + + return $this; + } + + /** + * Returns the type of the modification. + * + * @return int + */ + public function getType() + { + return $this->type; + } + + /** + * Determines if the batch modification + * is valid in its current state. + * + * @return bool + */ + public function isValid() + { + return !is_null($this->get()); + } + + /** + * Builds the type of modification automatically + * based on the current and original values. + * + * @return $this + */ + public function build() + { + $filtered = array_diff( + array_map('trim', $this->values), + [''] + ); + + if (is_null($this->original)) { + // If the original value is null, we'll assume + // that the attribute doesn't exist yet. + if (!empty($filtered)) { + // If the filtered array is not empty, we can + // assume we're looking to add values + // to the current attribute. + $this->setType(LDAP_MODIFY_BATCH_ADD); + } + + // If the filtered array is empty and there is no original + // value, then we can ignore this attribute since + // we can't push null values to the server. + } else { + if (empty($filtered)) { + // If there's an original value and the array is + // empty then we can assume we are looking + // to completely remove all values + // of the current attribute. + $this->setType(LDAP_MODIFY_BATCH_REMOVE_ALL); + } else { + // If the array isn't empty then we can assume + // we're looking to replace all attributes. + $this->setType(LDAP_MODIFY_BATCH_REPLACE); + } + } + + return $this; + } + + /** + * Returns the built batch modification array. + * + * @return array|null + */ + public function get() + { + switch ($this->type) { + case LDAP_MODIFY_BATCH_REMOVE_ALL: + // A values key cannot be provided when + // a remove all type is selected. + return [ + static::KEY_ATTRIB => $this->attribute, + static::KEY_MODTYPE => $this->type, + ]; + case LDAP_MODIFY_BATCH_REMOVE: + // Fallthrough. + case LDAP_MODIFY_BATCH_ADD: + // Fallthrough. + case LDAP_MODIFY_BATCH_REPLACE: + return [ + static::KEY_ATTRIB => $this->attribute, + static::KEY_MODTYPE => $this->type, + static::KEY_VALUES => $this->values, + ]; + default: + // If the modtype isn't recognized, we'll return null. + return; + } + } + + /** + * Determines if the given modtype is valid. + * + * @param int $type + * + * @return bool + */ + protected function isValidType($type) + { + return in_array($type, [ + LDAP_MODIFY_BATCH_REMOVE_ALL, + LDAP_MODIFY_BATCH_REMOVE, + LDAP_MODIFY_BATCH_REPLACE, + LDAP_MODIFY_BATCH_ADD, + ]); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Computer.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Computer.php new file mode 100644 index 0000000000..e00f259a3a --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Computer.php @@ -0,0 +1,87 @@ +getFirstAttribute($this->schema->operatingSystem()); + } + + /** + * Returns the computers operating system version. + * + * @link https://msdn.microsoft.com/en-us/library/ms679079(v=vs.85).aspx + * + * @return string + */ + public function getOperatingSystemVersion() + { + return $this->getFirstAttribute($this->schema->operatingSystemVersion()); + } + + /** + * Returns the computers operating system service pack. + * + * @link https://msdn.microsoft.com/en-us/library/ms679078(v=vs.85).aspx + * + * @return string + */ + public function getOperatingSystemServicePack() + { + return $this->getFirstAttribute($this->schema->operatingSystemServicePack()); + } + + /** + * Returns the computers DNS host name. + * + * @return string + */ + public function getDnsHostName() + { + return $this->getFirstAttribute($this->schema->dnsHostName()); + } + + /** + * Returns the computers bad password time. + * + * @link https://msdn.microsoft.com/en-us/library/ms675243(v=vs.85).aspx + * + * @return int + */ + public function getBadPasswordTime() + { + return $this->getFirstAttribute($this->schema->badPasswordTime()); + } + + /** + * Returns the computers account expiry date. + * + * @link https://msdn.microsoft.com/en-us/library/ms675098(v=vs.85).aspx + * + * @return int + */ + public function getAccountExpiry() + { + return $this->getFirstAttribute($this->schema->accountExpires()); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasAttributes.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasAttributes.php new file mode 100644 index 0000000000..2235bbe2dc --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasAttributes.php @@ -0,0 +1,345 @@ +getAttribute($key); + } + + /** + * Dynamically set attributes on the object. + * + * @param mixed $key + * @param mixed $value + * + * @return $this + */ + public function __set($key, $value) + { + return $this->setAttribute($key, $value); + } + + /** + * Synchronizes the models original attributes + * with the model's current attributes. + * + * @return $this + */ + public function syncOriginal() + { + $this->original = $this->attributes; + + return $this; + } + + /** + * Returns the models attribute with the specified key. + * + * If a sub-key is specified, it will try and + * retrieve it from the parent keys array. + * + * @param int|string $key + * @param int|string $subKey + * + * @return mixed + */ + public function getAttribute($key, $subKey = null) + { + if (!$key) { + return; + } + + // We'll normalize the given key to prevent case sensitivity issues. + $key = $this->normalizeAttributeKey($key); + + if (is_null($subKey) && $this->hasAttribute($key)) { + return $this->attributes[$key]; + } elseif ($this->hasAttribute($key, $subKey)) { + return $this->attributes[$key][$subKey]; + } + } + + /** + * Returns the first attribute by the specified key. + * + * @param string $key + * + * @return mixed + */ + public function getFirstAttribute($key) + { + return $this->getAttribute($key, 0); + } + + /** + * Returns all of the models attributes. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Fills the entry with the supplied attributes. + * + * @param array $attributes + * + * @return $this + */ + public function fill(array $attributes = []) + { + foreach ($attributes as $key => $value) { + $this->setAttribute($key, $value); + } + + return $this; + } + + /** + * Sets an attributes value by the specified key and sub-key. + * + * @param int|string $key + * @param mixed $value + * @param int|string $subKey + * + * @return $this + */ + public function setAttribute($key, $value, $subKey = null) + { + // Normalize key. + $key = $this->normalizeAttributeKey($key); + + // If the key is equal to 'dn', we'll automatically + // change it to the full attribute name. + $key = ($key == 'dn' ? $this->schema->distinguishedName() : $key); + + if (is_null($subKey)) { + // We need to ensure all attributes are set as arrays so all + // of our model methods retrieve attributes correctly. + $this->attributes[$key] = is_array($value) ? $value : [$value]; + } else { + $this->attributes[$key][$subKey] = $value; + } + + return $this; + } + + /** + * Sets the first attributes value by the specified key. + * + * @param int|string $key + * @param mixed $value + * + * @return $this + */ + public function setFirstAttribute($key, $value) + { + return $this->setAttribute($key, $value, 0); + } + + /** + * Sets the attributes property. + * + * Used when constructing an existing LDAP record. + * + * @param array $attributes + * + * @return $this + */ + public function setRawAttributes(array $attributes = []) + { + // We'll filter out those annoying 'count' keys returned with LDAP results, + // and lowercase all root array keys to prevent any casing issues. + $this->attributes = array_change_key_case($this->filterRawAttributes($attributes), CASE_LOWER); + + // We'll pull out the distinguished name from our raw attributes + // and set it into our attributes array with the full attribute + // definition. This allows us to normalize distinguished + // names across different LDAP variants. + if ($dn = Arr::get($attributes, 'dn')) { + // In some LDAP variants, the distinguished + // name is returned as an array. + if (is_array($dn)) { + $dn = Arr::first($dn); + } + + $this->setDistinguishedName($dn); + } + + $this->syncOriginal(); + + // Set exists to true since raw attributes are only + // set in the case of attributes being loaded by + // query results. + $this->exists = true; + + return $this; + } + + /** + * Filters the count key recursively from raw LDAP attributes. + * + * @param array $attributes + * @param array|string $keys + * + * @return array + */ + public function filterRawAttributes(array $attributes = [], $keys = ['count', 'dn']) + { + $attributes = Arr::except($attributes, $keys); + + array_walk($attributes, function (&$value) use ($keys) { + $value = is_array($value) ? + $this->filterRawAttributes($value, $keys) : + $value; + }); + + return $attributes; + } + + /** + * Returns true / false if the specified attribute + * exists in the attributes array. + * + * @param int|string $key + * @param int|string $subKey + * + * @return bool + */ + public function hasAttribute($key, $subKey = null) + { + // Normalize key. + $key = $this->normalizeAttributeKey($key); + + if (is_null($subKey)) { + return Arr::has($this->attributes, $key); + } + + return Arr::has($this->attributes, "$key.$subKey"); + } + + /** + * Returns the number of attributes inside + * the attributes property. + * + * @return int + */ + public function countAttributes() + { + return count($this->getAttributes()); + } + + /** + * Returns the models original attributes. + * + * @return array + */ + public function getOriginal() + { + return $this->original; + } + + /** + * Get the attributes that have been changed since last sync. + * + * @return array + */ + public function getDirty() + { + $dirty = []; + + foreach ($this->attributes as $key => $value) { + if (!$this->originalIsEquivalent($key)) { + // We need to reset the array's indices using array_values due to + // LDAP requiring consecutive indices (0, 1, 2 etc.) + $dirty[$key] = array_values($value); + } + } + + return $dirty; + } + + /** + * Returns a normalized attribute key. + * + * @param string $key + * + * @return string + */ + protected function normalizeAttributeKey($key) + { + return strtolower($key); + } + + /** + * Determine if the new and old values for a given key are equivalent. + * + * @param string $key + * + * @return bool + */ + protected function originalIsEquivalent($key) + { + if (!array_key_exists($key, $this->original)) { + return false; + } + + $current = $this->attributes[$key]; + + $original = $this->original[$key]; + + if ($current === $original) { + return true; + } + + return is_numeric($current) && + is_numeric($original) && + strcmp((string) $current, (string) $original) === 0; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasCriticalSystemObject.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasCriticalSystemObject.php new file mode 100644 index 0000000000..e85cac16ad --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasCriticalSystemObject.php @@ -0,0 +1,18 @@ +getFirstAttribute($this->schema->isCriticalSystemObject()); + + return $this->convertStringToBool($attribute); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasDescription.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasDescription.php new file mode 100644 index 0000000000..dfc05ed31b --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasDescription.php @@ -0,0 +1,30 @@ +getFirstAttribute($this->schema->description()); + } + + /** + * Sets the models's description. + * + * @param string $description + * + * @return $this + */ + public function setDescription($description) + { + return $this->setFirstAttribute($this->schema->description(), $description); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasEvents.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasEvents.php new file mode 100644 index 0000000000..37abb81633 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasEvents.php @@ -0,0 +1,21 @@ +fire($event); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasLastLogonAndLogOff.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasLastLogonAndLogOff.php new file mode 100644 index 0000000000..3aee2a5cc7 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasLastLogonAndLogOff.php @@ -0,0 +1,42 @@ +getFirstAttribute($this->schema->lastLogOff()); + } + + /** + * Returns the models's last log on date. + * + * @link https://msdn.microsoft.com/en-us/library/ms676823(v=vs.85).aspx + * + * @return string + */ + public function getLastLogon() + { + return $this->getFirstAttribute($this->schema->lastLogOn()); + } + + /** + * Returns the models's last log on timestamp. + * + * @link https://msdn.microsoft.com/en-us/library/ms676824(v=vs.85).aspx + * + * @return string + */ + public function getLastLogonTimestamp() + { + return $this->getFirstAttribute($this->schema->lastLogOnTimestamp()); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasMemberOf.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasMemberOf.php new file mode 100644 index 0000000000..3c1b85edd6 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasMemberOf.php @@ -0,0 +1,260 @@ +getAttribute($this->schema->memberOf()); + + // Normalize returned distinguished names if the attribute is null. + return is_array($dns) ? $dns : []; + } + + /** + * Adds the current model to the specified group. + * + * @param string|Group $group + * + * @return bool + */ + public function addGroup($group) + { + if (is_string($group)) { + // If the group is a string, we'll assume the dev is passing + // in a DN string of the group. We'll try to locate it. + $group = $this->query->newInstance()->findByDn($group); + } + + if ($group instanceof Group) { + // If the group is Group model instance, we can + // add the current models DN to the group. + return $group->addMember($this->getDn()); + } + + return false; + } + + /** + * Removes the current model from the specified group. + * + * @param string|Group $group + * + * @return bool + */ + public function removeGroup($group) + { + if (is_string($group)) { + // If the group is a string, we'll assume the dev is passing + // in a DN string of the group. We'll try to locate it. + $group = $this->query->newInstance()->findByDn($group); + } + + if ($group instanceof Group) { + // If the group is Group model instance, we can + // remove the current models DN from the group. + return $group->removeMember($this->getDn()); + } + + return false; + } + + /** + * Removes the current model from all groups. + * + * @return array The group distinguished names that were successfully removed + */ + public function removeAllGroups() + { + $removed = []; + + foreach ($this->getMemberOf() as $group) { + if ($this->removeGroup($group)) { + $removed[] = $group; + } + } + + return $removed; + } + + /** + * Returns the models groups that it is apart of. + * + * If a recursive option is given, groups of groups + * are retrieved and then merged with + * the resulting collection. + * + * @link https://msdn.microsoft.com/en-us/library/ms677099(v=vs.85).aspx + * + * @param array $fields + * @param bool $recursive + * @param array $visited + * + * @return Collection + */ + public function getGroups(array $fields = ['*'], $recursive = false, array $visited = []) + { + if (!in_array($this->schema->memberOf(), $fields)) { + // We want to make sure that we always select the memberof + // field in case developers want recursive members. + $fields = array_merge($fields, [$this->schema->memberOf()]); + } + + $groups = $this->getGroupsByNames($this->getMemberOf(), $fields); + + // We need to check if we're working with a User model. Only users + // contain a primary group. If we are, we'll merge the users + // primary group into the resulting collection. + if ($this instanceof User && $primary = $this->getPrimaryGroup()) { + $groups->push($primary); + } + + // If recursive results are requested, we'll ask each group + // for their groups, and merge the resulting collection. + if ($recursive) { + /** @var Group $group */ + foreach ($groups as $group) { + // We need to validate that we haven't already queried + // for this group's members so we don't allow + // infinite recursion in case of circular + // group dependencies in LDAP. + if (!in_array($group->getDn(), $visited)) { + $visited[] = $group->getDn(); + + $members = $group->getGroups($fields, $recursive, $visited); + + /** @var Group $member */ + foreach ($members as $member) { + $visited[] = $member->getDn(); + } + + $groups = $groups->merge($members); + } + } + } + + return $groups; + } + + /** + * Returns the models groups names in a single dimension array. + * + * If a recursive option is given, groups of groups + * are retrieved and then merged with + * the resulting collection. + * + * @param bool $recursive + * + * @return array + */ + public function getGroupNames($recursive = false) + { + $fields = [$this->schema->commonName(), $this->schema->memberOf()]; + + $names = $this->getGroups($fields, $recursive)->map(function (Group $group) { + return $group->getCommonName(); + })->toArray(); + + return array_unique($names); + } + + /** + * Determine if the current model is a member of the specified group(s). + * + * @param mixed $group + * @param bool $recursive + * + * @return bool + */ + public function inGroup($group, $recursive = false) + { + $memberOf = $this->getGroups(['cn'], $recursive); + + if ($group instanceof Collection) { + // If we've been given a collection then we'll convert + // it to an array to normalize the value. + $group = $group->toArray(); + } + + $groups = is_array($group) ? $group : [$group]; + + foreach ($groups as $group) { + // We need to iterate through each given group that the + // model must be apart of, then go through the models + // actual groups and perform validation. + $exists = $memberOf->filter(function (Group $parent) use ($group) { + return $this->groupIsParent($group, $parent); + })->count() !== 0; + + if (!$exists) { + // If the current group isn't at all contained + // in the memberOf collection, we'll + // return false here. + return false; + } + } + + return true; + } + + /** + * Retrieves groups by their distinguished name. + * + * @param array $dns + * @param array $fields + * + * @return Collection + */ + protected function getGroupsByNames(array $dns = [], $fields = []) + { + $query = $this->query->newInstance(); + + return $query->newCollection($dns)->map(function ($dn) use ($query, $fields) { + return $query->select($fields)->clearFilters()->findByDn($dn); + })->filter(function ($group) { + return $group instanceof Group; + }); + } + + /** + * Validates if the specified group is the given parent instance. + * + * @param Group|string $group + * @param Group $parent + * + * @return bool + */ + protected function groupIsParent($group, Group $parent) + { + if ($group instanceof Group) { + // We've been given a group instance, we'll compare their DNs. + return $parent->getDn() === $group->getDn(); + } + + if (Utilities::explodeDn($group)) { + // We've been given a DN, we'll compare it to the parents. + return $parent->getDn() === $group; + } + + if (!empty($group)) { + // We've been given just a string, we'll + // compare it to the parents name. + return $parent->getCommonName() === $group; + } + + return false; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasUserAccountControl.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasUserAccountControl.php new file mode 100644 index 0000000000..857378b8b6 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasUserAccountControl.php @@ -0,0 +1,60 @@ +getFirstAttribute($this->schema->userAccountControl()); + } + + /** + * Returns the users user account control as an AccountControl object. + * + * @return AccountControl + */ + public function getUserAccountControlObject() + { + return new AccountControl($this->getUserAccountControl()); + } + + /** + * Sets the users account control property. + * + * @param int|string|AccountControl $accountControl + * + * @return $this + */ + public function setUserAccountControl($accountControl) + { + return $this->setAttribute($this->schema->userAccountControl(), (string) $accountControl); + } + + /** + * Returns if the user is disabled. + * + * @return bool + */ + public function isDisabled() + { + return ($this->getUserAccountControl() & AccountControl::ACCOUNTDISABLE) === AccountControl::ACCOUNTDISABLE; + } + + /** + * Returns if the user is enabled. + * + * @return bool + */ + public function isEnabled() + { + return $this->getUserAccountControl() === null ? false : !$this->isDisabled(); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasUserProperties.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasUserProperties.php new file mode 100644 index 0000000000..0d1c54137d --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Concerns/HasUserProperties.php @@ -0,0 +1,453 @@ +getFirstAttribute($this->schema->country()); + } + + /** + * Sets the users country. + * + * @param string $country + * + * @return $this + */ + public function setCountry($country) + { + return $this->setFirstAttribute($this->schema->country(), $country); + } + + /** + * Returns the users department. + * + * @link https://msdn.microsoft.com/en-us/library/ms675490(v=vs.85).aspx + * + * @return string|null + */ + public function getDepartment() + { + return $this->getFirstAttribute($this->schema->department()); + } + + /** + * Sets the users department. + * + * @param string $department + * + * @return $this + */ + public function setDepartment($department) + { + return $this->setFirstAttribute($this->schema->department(), $department); + } + + /** + * Returns the users email address. + * + * @link https://msdn.microsoft.com/en-us/library/ms676855(v=vs.85).aspx + * + * @return string|null + */ + public function getEmail() + { + return $this->getFirstAttribute($this->schema->email()); + } + + /** + * Sets the users email. + * + * Keep in mind this will remove all other + * email addresses the user currently has. + * + * @param string $email + * + * @return $this + */ + public function setEmail($email) + { + return $this->setFirstAttribute($this->schema->email(), $email); + } + + /** + * Returns the users facsimile number. + * + * @link https://msdn.microsoft.com/en-us/library/ms675675(v=vs.85).aspx + * + * @return string|null + */ + public function getFacsimileNumber() + { + return $this->getFirstAttribute($this->schema->facsimile()); + } + + /** + * Sets the users facsimile number. + * + * @param string $number + * + * @return $this + */ + public function setFacsimileNumber($number) + { + return $this->setFirstAttribute($this->schema->facsimile(), $number); + } + + /** + * Returns the users first name. + * + * @link https://msdn.microsoft.com/en-us/library/ms675719(v=vs.85).aspx + * + * @return string|null + */ + public function getFirstName() + { + return $this->getFirstAttribute($this->schema->firstName()); + } + + /** + * Sets the users first name. + * + * @param string $firstName + * + * @return $this + */ + public function setFirstName($firstName) + { + return $this->setFirstAttribute($this->schema->firstName(), $firstName); + } + + /** + * Returns the users initials. + * + * @return string|null + */ + public function getInitials() + { + return $this->getFirstAttribute($this->schema->initials()); + } + + /** + * Sets the users initials. + * + * @param string $initials + * + * @return $this + */ + public function setInitials($initials) + { + return $this->setFirstAttribute($this->schema->initials(), $initials); + } + + /** + * Returns the users IP Phone. + * + * @return string|null + */ + public function getIpPhone() + { + return $this->getFirstAttribute($this->schema->ipPhone()); + } + + /** + * Sets the users IP phone. + * + * @param string $ip + * + * @return $this + */ + public function setIpPhone($ip) + { + return $this->setFirstAttribute($this->schema->ipPhone(), $ip); + } + + /** + * Returns the users last name. + * + * @link https://msdn.microsoft.com/en-us/library/ms679872(v=vs.85).aspx + * + * @return string|null + */ + public function getLastName() + { + return $this->getFirstAttribute($this->schema->lastName()); + } + + /** + * Sets the users last name. + * + * @param string $lastName + * + * @return $this + */ + public function setLastName($lastName) + { + return $this->setFirstAttribute($this->schema->lastName(), $lastName); + } + + /** + * Returns the users postal code. + * + * @return string|null + */ + public function getPostalCode() + { + return $this->getFirstAttribute($this->schema->postalCode()); + } + + /** + * Sets the users postal code. + * + * @param string $postalCode + * + * @return $this + */ + public function setPostalCode($postalCode) + { + return $this->setFirstAttribute($this->schema->postalCode(), $postalCode); + } + + /** + * Get the users post office box. + * + * @return string|null + */ + public function getPostOfficeBox() + { + return $this->getFirstAttribute($this->schema->postOfficeBox()); + } + + /** + * Sets the users post office box. + * + * @param string|int $box + * + * @return $this + */ + public function setPostOfficeBox($box) + { + return $this->setFirstAttribute($this->schema->postOfficeBox(), $box); + } + + /** + * Sets the users proxy addresses. + * + * This will remove all proxy addresses on the user and insert the specified addresses. + * + * @link https://msdn.microsoft.com/en-us/library/ms679424(v=vs.85).aspx + * + * @param array $addresses + * + * @return $this + */ + public function setProxyAddresses(array $addresses = []) + { + return $this->setAttribute($this->schema->proxyAddresses(), $addresses); + } + + /** + * Add's a single proxy address to the user. + * + * @param string $address + * + * @return $this + */ + public function addProxyAddress($address) + { + $addresses = $this->getProxyAddresses(); + + $addresses[] = $address; + + return $this->setAttribute($this->schema->proxyAddresses(), $addresses); + } + + /** + * Returns the users proxy addresses. + * + * @link https://msdn.microsoft.com/en-us/library/ms679424(v=vs.85).aspx + * + * @return array + */ + public function getProxyAddresses() + { + return $this->getAttribute($this->schema->proxyAddresses()) ?? []; + } + + /** + * Returns the users street address. + * + * @return string|null + */ + public function getStreetAddress() + { + return $this->getFirstAttribute($this->schema->streetAddress()); + } + + /** + * Sets the users street address. + * + * @param string $address + * + * @return $this + */ + public function setStreetAddress($address) + { + return $this->setFirstAttribute($this->schema->streetAddress(), $address); + } + + /** + * Returns the users title. + * + * @link https://msdn.microsoft.com/en-us/library/ms680037(v=vs.85).aspx + * + * @return string|null + */ + public function getTitle() + { + return $this->getFirstAttribute($this->schema->title()); + } + + /** + * Sets the users title. + * + * @param string $title + * + * @return $this + */ + public function setTitle($title) + { + return $this->setFirstAttribute($this->schema->title(), $title); + } + + /** + * Returns the users telephone number. + * + * @link https://msdn.microsoft.com/en-us/library/ms680027(v=vs.85).aspx + * + * @return string|null + */ + public function getTelephoneNumber() + { + return $this->getFirstAttribute($this->schema->telephone()); + } + + /** + * Sets the users telephone number. + * + * @param string $number + * + * @return $this + */ + public function setTelephoneNumber($number) + { + return $this->setFirstAttribute($this->schema->telephone(), $number); + } + + /** + * Returns the users primary mobile phone number. + * + * @return string|null + */ + public function getMobileNumber() + { + return $this->getFirstAttribute($this->schema->mobile()); + } + + /** + * Sets the users primary mobile phone number. + * + * @param string $number + * + * @return $this + */ + public function setMobileNumber($number) + { + return $this->setFirstAttribute($this->schema->mobile(), $number); + } + + /** + * Returns the users secondary (other) mobile phone number. + * + * @return string|null + */ + public function getOtherMobileNumber() + { + return $this->getFirstAttribute($this->schema->otherMobile()); + } + + /** + * Sets the users secondary (other) mobile phone number. + * + * @param string $number + * + * @return $this + */ + public function setOtherMobileNumber($number) + { + return $this->setFirstAttribute($this->schema->otherMobile(), $number); + } + + /** + * Returns the users other mailbox attribute. + * + * @link https://msdn.microsoft.com/en-us/library/ms679091(v=vs.85).aspx + * + * @return array + */ + public function getOtherMailbox() + { + return $this->getAttribute($this->schema->otherMailbox()); + } + + /** + * Sets the users other mailboxes. + * + * @param array $otherMailbox + * + * @return $this + */ + public function setOtherMailbox($otherMailbox = []) + { + return $this->setAttribute($this->schema->otherMailbox(), $otherMailbox); + } + + /** + * Returns the distinguished name of the user who is the user's manager. + * + * @return string|null + */ + public function getManager() + { + return $this->getFirstAttribute($this->schema->manager()); + } + + /** + * Sets the distinguished name of the user who is the user's manager. + * + * @param string $managerDn + * + * @return $this + */ + public function setManager($managerDn) + { + return $this->setFirstAttribute($this->schema->manager(), $managerDn); + } + + /** + * Returns the users mail nickname. + * + * @return string|null + */ + public function getMailNickname() + { + return $this->getFirstAttribute($this->schema->emailNickname()); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Contact.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Contact.php new file mode 100644 index 0000000000..517a35be11 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Contact.php @@ -0,0 +1,14 @@ +getFirstAttribute($this->schema->systemFlags()); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Entry.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Entry.php new file mode 100644 index 0000000000..be6ef1d902 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Entry.php @@ -0,0 +1,13 @@ +model = $model; + } + + /** + * Returns the model that generated the event. + * + * @return Model + */ + public function getModel() + { + return $this->model; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Events/Saved.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Events/Saved.php new file mode 100644 index 0000000000..f775e2e5c7 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Events/Saved.php @@ -0,0 +1,8 @@ +setQuery($builder) + ->setSchema($builder->getSchema()); + } + + /** + * Sets the current query builder. + * + * @param Builder $builder + * + * @return $this + */ + public function setQuery(Builder $builder) + { + $this->query = $builder; + + return $this; + } + + /** + * Sets the current schema. + * + * If null is given, a default ActiveDirectory schema is set. + * + * @param SchemaInterface|null $schema + * + * @return $this + */ + public function setSchema(SchemaInterface $schema = null) + { + $this->schema = $schema ?: new ActiveDirectory(); + + return $this; + } + + /** + * Creates a new generic LDAP entry instance. + * + * @param array $attributes + * + * @return Entry + */ + public function entry(array $attributes = []) + { + $model = $this->schema->entryModel(); + + return new $model($attributes, $this->query); + } + + /** + * Creates a new user instance. + * + * @param array $attributes + * + * @return User + */ + public function user(array $attributes = []) + { + $model = $this->schema->userModel(); + + return (new $model($attributes, $this->query)) + ->setAttribute($this->schema->objectClass(), $this->schema->userObjectClasses()); + } + + /** + * Creates a new organizational unit instance. + * + * @param array $attributes + * + * @return OrganizationalUnit + */ + public function ou(array $attributes = []) + { + $model = $this->schema->organizationalUnitModel(); + + return (new $model($attributes, $this->query)) + ->setAttribute($this->schema->objectClass(), [ + $this->schema->top(), + $this->schema->organizationalUnit(), + ]); + } + + /** + * Creates a new organizational unit instance. + * + * @param array $attributes + * + * @return Organization + */ + public function organization(array $attributes = []) + { + $model = $this->schema->organizationModel(); + + return (new $model($attributes, $this->query)) + ->setAttribute($this->schema->objectClass(), [ + $this->schema->top(), + $this->schema->organization(), + ]); + } + + /** + * Creates a new group instance. + * + * @param array $attributes + * + * @return Group + */ + public function group(array $attributes = []) + { + $model = $this->schema->groupModel(); + + return (new $model($attributes, $this->query)) + ->setAttribute($this->schema->objectClass(), [ + $this->schema->top(), + $this->schema->objectCategoryGroup(), + ]); + } + + /** + * Creates a new organizational unit instance. + * + * @param array $attributes + * + * @return Container + */ + public function container(array $attributes = []) + { + $model = $this->schema->containerModel(); + + return (new $model($attributes, $this->query)) + ->setAttribute($this->schema->objectClass(), $this->schema->objectClassContainer()); + } + + /** + * Creates a new user instance as a contact. + * + * @param array $attributes + * + * @return User + */ + public function contact(array $attributes = []) + { + $model = $this->schema->contactModel(); + + return (new $model($attributes, $this->query)) + ->setAttribute($this->schema->objectClass(), [ + $this->schema->top(), + $this->schema->person(), + $this->schema->organizationalPerson(), + $this->schema->contact(), + ]); + } + + /** + * Creates a new computer instance. + * + * @param array $attributes + * + * @return Computer + */ + public function computer(array $attributes = []) + { + $model = $this->schema->computerModel(); + + return (new $model($attributes, $this->query)) + ->setAttribute($this->schema->objectClass(), [ + $this->schema->top(), + $this->schema->person(), + $this->schema->organizationalPerson(), + $this->schema->user(), + $this->schema->computer(), + ]); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/ForeignSecurityPrincipal.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/ForeignSecurityPrincipal.php new file mode 100644 index 0000000000..b0d15d8f78 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/ForeignSecurityPrincipal.php @@ -0,0 +1,13 @@ +getMembersFromAttribute($this->schema->member()); + + if (count($members) === 0) { + $members = $this->getPaginatedMembers(); + } + + return $this->newCollection($members); + } + + /** + * Returns the group's member names only. + * + * @return array + */ + public function getMemberNames() + { + $members = []; + + $dns = $this->getAttribute($this->schema->member()) ?: []; + + foreach ($dns as $dn) { + $exploded = Utilities::explodeDn($dn); + + if (array_key_exists(0, $exploded)) { + $members[] = $exploded[0]; + } + } + + return $members; + } + + /** + * Sets the groups members using an array of user DNs. + * + * @param array $entries + * + * @return $this + */ + public function setMembers(array $entries) + { + return $this->setAttribute($this->schema->member(), $entries); + } + + /** + * Adds multiple entries to the current group. + * + * @param array $members + * + * @return bool + */ + public function addMembers(array $members) + { + $members = array_map(function ($member) { + return $member instanceof Model + ? $member->getDn() + : $member; + }, $members); + + $mod = $this->newBatchModification( + $this->schema->member(), + LDAP_MODIFY_BATCH_ADD, + $members + ); + + return $this->addModification($mod)->save(); + } + + /** + * Adds an entry to the current group. + * + * @param string|Entry $member + * + * @throws InvalidArgumentException When the given entry is empty or contains no distinguished name. + * + * @return bool + */ + public function addMember($member) + { + $member = ($member instanceof Model ? $member->getDn() : $member); + + if (is_null($member)) { + throw new InvalidArgumentException( + 'Cannot add member to group. The members distinguished name cannot be null.' + ); + } + + $mod = $this->newBatchModification( + $this->schema->member(), + LDAP_MODIFY_BATCH_ADD, + [$member] + ); + + return $this->addModification($mod)->save(); + } + + /** + * Removes an entry from the current group. + * + * @param string|Entry $member + * + * @throws InvalidArgumentException + * + * @return bool + */ + public function removeMember($member) + { + $member = ($member instanceof Model ? $member->getDn() : $member); + + if (is_null($member)) { + throw new InvalidArgumentException( + 'Cannot remove member to group. The members distinguished name cannot be null.' + ); + } + + $mod = $this->newBatchModification( + $this->schema->member(), + LDAP_MODIFY_BATCH_REMOVE, + [$member] + ); + + return $this->addModification($mod)->save(); + } + + /** + * Removes all members from the current group. + * + * @return bool + */ + public function removeMembers() + { + $mod = $this->newBatchModification( + $this->schema->member(), + LDAP_MODIFY_BATCH_REMOVE_ALL + ); + + return $this->addModification($mod)->save(); + } + + /** + * Returns the group type integer. + * + * @link https://msdn.microsoft.com/en-us/library/ms675935(v=vs.85).aspx + * + * @return string + */ + public function getGroupType() + { + return $this->getFirstAttribute($this->schema->groupType()); + } + + /** + * Retrieves group members by the specified model attribute. + * + * @param $attribute + * + * @return array + */ + protected function getMembersFromAttribute($attribute) + { + $members = []; + + $entries = $this->getAttribute($attribute) ?: []; + + $query = $this->query->newInstance(); + + // Retrieving the member identifier to allow + // compatibility with LDAP variants. + $identifier = $this->schema->memberIdentifier(); + + foreach ($entries as $entry) { + // If our identifier is a distinguished name, then we need to + // use an alternate query method, as we can't locate records + // by distinguished names using an LDAP filter. + if ($identifier == 'dn' || $identifier == 'distinguishedname') { + $member = $query->findByDn($entry); + } else { + // We'll ensure we clear our filters when retrieving each member, + // so we can continue fetching the next one in line. + $member = $query->clearFilters()->findBy($identifier, $entry); + } + + // We'll double check that we've received a model from + // our query before adding it into our results. + if ($member instanceof Model) { + $members[] = $member; + } + } + + return $members; + } + + /** + * Retrieves members that are contained in a member range. + * + * @return array + */ + protected function getPaginatedMembers() + { + $members = []; + + $keys = array_keys($this->attributes); + + // We need to filter out the model attributes so + // we only retrieve the member range. + $attributes = array_values(array_filter($keys, function ($key) { + return strpos($key, 'member;range') !== false; + })); + + // We'll grab the member range key so we can run a + // regex on it to determine the range. + $key = reset($attributes); + + preg_match_all( + '/member;range\=([0-9]{1,4})-([0-9*]{1,4})/', + $key, + $matches + ); + + if ($key && count($matches) == 3) { + // Retrieve the ending range number. + $to = $matches[2][0]; + + // Retrieve the current groups members from the + // current range string (ex. 'member;0-50'). + $members = $this->getMembersFromAttribute($key); + + // If the query already included all member results (indicated + // by the '*'), then we can return here. Otherwise we need + // to continue on and retrieve the rest. + if ($to === '*') { + return $members; + } + + // Determine the amount of members we're requesting per query. + $range = $to - $matches[1][0]; + + // Set our starting range to our last end range plus one. + $from = $to + 1; + + // We'll determine the new end range by adding the + // total range to our new starting range. + $to = $from + $range; + + // We'll need to query for the current model again but with + // a new range to retrieve the other members. + /** @var Group $group */ + $group = $this->query->newInstance()->findByDn( + $this->getDn(), + [$this->query->getSchema()->memberRange($from, $to)] + ); + + // Finally, we'll merge our current members + // with the newly returned members. + $members = array_merge( + $members, + $group->getMembers()->toArray() + ); + } + + return $members; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Model.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Model.php new file mode 100644 index 0000000000..3bad2ea39c --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Model.php @@ -0,0 +1,1266 @@ +setQuery($builder) + ->setSchema($builder->getSchema()) + ->fill($attributes); + } + + /** + * Returns the models distinguished name when the model is converted to a string. + * + * @return null|string + */ + public function __toString() + { + return $this->getDn(); + } + + /** + * Sets the current query builder. + * + * @param Builder $builder + * + * @return $this + */ + public function setQuery(Builder $builder) + { + $this->query = $builder; + + return $this; + } + + /** + * Returns the current query builder. + * + * @return Builder + */ + public function getQuery() + { + return $this->query; + } + + /** + * Returns a new query builder instance. + * + * @return Builder + */ + public function newQuery() + { + return $this->query->newInstance(); + } + + /** + * Returns a new batch modification. + * + * @param string|null $attribute + * @param string|int|null $type + * @param array $values + * + * @return BatchModification + */ + public function newBatchModification($attribute = null, $type = null, $values = []) + { + return new BatchModification($attribute, $type, $values); + } + + /** + * Returns a new collection with the specified items. + * + * @param mixed $items + * + * @return Collection + */ + public function newCollection($items = []) + { + return new Collection($items); + } + + /** + * Sets the current model schema. + * + * @param SchemaInterface $schema + * + * @return $this + */ + public function setSchema(SchemaInterface $schema) + { + $this->schema = $schema; + + return $this; + } + + /** + * Returns the current model schema. + * + * @return SchemaInterface + */ + public function getSchema() + { + return $this->schema; + } + + /** + * Determine if the given offset exists. + * + * @param string $offset + * + * @return bool + */ + public function offsetExists($offset) + { + return !is_null($this->getAttribute($offset)); + } + + /** + * Get the value for a given offset. + * + * @param string $offset + * + * @return mixed + */ + public function offsetGet($offset) + { + return $this->getAttribute($offset); + } + + /** + * Set the value at the given offset. + * + * @param string $offset + * @param mixed $value + * + * @return void + */ + public function offsetSet($offset, $value) + { + $this->setAttribute($offset, $value); + } + + /** + * Unset the value at the given offset. + * + * @param string $offset + * + * @return void + */ + public function offsetUnset($offset) + { + unset($this->attributes[$offset]); + } + + /** + * Determine if an attribute exists on the model. + * + * @param string $key + * + * @return bool + */ + public function __isset($key) + { + return $this->offsetExists($key); + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + public function jsonSerialize() + { + $attributes = $this->getAttributes(); + + array_walk_recursive($attributes, function (&$val) { + if (MbString::isLoaded()) { + // If we're able to detect the attribute + // encoding, we'll encode only the + // attributes that need to be. + if (!MbString::isUtf8($val)) { + $val = utf8_encode($val); + } + } else { + // If the mbstring extension is not loaded, we'll + // encode all attributes to make sure + // they are encoded properly. + $val = utf8_encode($val); + } + }); + + // We'll replace the binary GUID and SID with + // their string equivalents for convenience. + return array_replace($attributes, [ + $this->schema->objectGuid() => $this->getConvertedGuid(), + $this->schema->objectSid() => $this->getConvertedSid(), + ]); + } + + /** + * Reload a fresh model instance from the directory. + * + * @return static|null + */ + public function fresh() + { + $model = $this->query->newInstance()->findByDn($this->getDn()); + + return $model instanceof self ? $model : null; + } + + /** + * Synchronizes the current models attributes with the directory values. + * + * @return bool + */ + public function syncRaw() + { + if ($model = $this->fresh()) { + $this->setRawAttributes($model->getAttributes()); + + return true; + } + + return false; + } + + /** + * Returns the models batch modifications to be processed. + * + * @return array + */ + public function getModifications() + { + $this->buildModificationsFromDirty(); + + return $this->modifications; + } + + /** + * Sets the models modifications array. + * + * @param array $modifications + * + * @return $this + */ + public function setModifications(array $modifications = []) + { + $this->modifications = $modifications; + + return $this; + } + + /** + * Adds a batch modification to the models modifications array. + * + * @param array|BatchModification $mod + * + * @throws InvalidArgumentException + * + * @return $this + */ + public function addModification($mod = []) + { + if ($mod instanceof BatchModification) { + $mod = $mod->get(); + } + + if ($this->isValidModification($mod)) { + $this->modifications[] = $mod; + + return $this; + } + + throw new InvalidArgumentException( + "The batch modification array does not include the mandatory 'attrib' or 'modtype' keys." + ); + } + + /** + * Returns the model's distinguished name string. + * + * @link https://msdn.microsoft.com/en-us/library/aa366101(v=vs.85).aspx + * + * @return string|null + */ + public function getDistinguishedName() + { + return $this->getFirstAttribute($this->schema->distinguishedName()); + } + + /** + * Sets the model's distinguished name attribute. + * + * @param string|DistinguishedName $dn + * + * @return $this + */ + public function setDistinguishedName($dn) + { + $this->setFirstAttribute($this->schema->distinguishedName(), (string) $dn); + + return $this; + } + + /** + * Returns the model's distinguished name string. + * + * (Alias for getDistinguishedName()) + * + * @link https://msdn.microsoft.com/en-us/library/aa366101(v=vs.85).aspx + * + * @return string|null + */ + public function getDn() + { + return $this->getDistinguishedName(); + } + + /** + * Returns a DistinguishedName object for modifying the current models DN. + * + * @return DistinguishedName + */ + public function getDnBuilder() + { + // If we currently don't have a distinguished name, we'll set + // it to our base, otherwise we'll use our query's base DN. + $dn = $this->getDistinguishedName() ?: $this->query->getDn(); + + return $this->getNewDnBuilder($dn); + } + + /** + * Returns the models distinguished name components. + * + * @param bool $removeAttributePrefixes + * + * @return array + */ + public function getDnComponents($removeAttributePrefixes = true) + { + if ($components = Utilities::explodeDn($this->getDn(), $removeAttributePrefixes)) { + unset($components['count']); + + return $components; + } + + return []; + } + + /** + * Returns the distinguished name that the model is a leaf of. + * + * @return string + */ + public function getDnRoot() + { + $components = $this->getDnComponents(false); + + // Shift off the beginning of the array; + // This contains the models RDN. + array_shift($components); + + return implode(',', $components); + } + + /** + * Returns a new DistinguishedName object for building onto. + * + * @param string $baseDn + * + * @return DistinguishedName + */ + public function getNewDnBuilder($baseDn = '') + { + return new DistinguishedName($baseDn); + } + + /** + * Sets the model's distinguished name attribute. + * + * (Alias for setDistinguishedName()) + * + * @param string $dn + * + * @return $this + */ + public function setDn($dn) + { + return $this->setDistinguishedName($dn); + } + + /** + * Returns the model's hex object SID. + * + * @link https://msdn.microsoft.com/en-us/library/ms679024(v=vs.85).aspx + * + * @return string + */ + public function getObjectSid() + { + return $this->getFirstAttribute($this->schema->objectSid()); + } + + /** + * Returns the model's binary object GUID. + * + * @link https://msdn.microsoft.com/en-us/library/ms679021(v=vs.85).aspx + * + * @return string + */ + public function getObjectGuid() + { + return $this->getFirstAttribute($this->schema->objectGuid()); + } + + /** + * Returns the model's GUID. + * + * @return string|null + */ + public function getConvertedGuid() + { + try { + return (string) new Guid($this->getObjectGuid()); + } catch (InvalidArgumentException $e) { + return; + } + } + + /** + * Returns the model's SID. + * + * @return string|null + */ + public function getConvertedSid() + { + try { + return (string) new Sid($this->getObjectSid()); + } catch (InvalidArgumentException $e) { + return; + } + } + + /** + * Returns the model's common name. + * + * @link https://msdn.microsoft.com/en-us/library/ms675449(v=vs.85).aspx + * + * @return string + */ + public function getCommonName() + { + return $this->getFirstAttribute($this->schema->commonName()); + } + + /** + * Sets the model's common name. + * + * @param string $name + * + * @return $this + */ + public function setCommonName($name) + { + return $this->setFirstAttribute($this->schema->commonName(), $name); + } + + /** + * Returns the model's name. An LDAP alias for the CN attribute. + * + * @link https://msdn.microsoft.com/en-us/library/ms675449(v=vs.85).aspx + * + * @return string + */ + public function getName() + { + return $this->getFirstAttribute($this->schema->name()); + } + + /** + * Sets the model's name. + * + * @param string $name + * + * @return Model + */ + public function setName($name) + { + return $this->setFirstAttribute($this->schema->name(), $name); + } + + /** + * Returns the model's display name. + * + * @return string + */ + public function getDisplayName() + { + return $this->getFirstAttribute($this->schema->displayName()); + } + + /** + * Sets the model's display name. + * + * @param string $displayName + * + * @return $this + */ + public function setDisplayName($displayName) + { + return $this->setFirstAttribute($this->schema->displayName(), $displayName); + } + + /** + * Returns the model's samaccountname. + * + * @link https://msdn.microsoft.com/en-us/library/ms679635(v=vs.85).aspx + * + * @return string + */ + public function getAccountName() + { + return $this->getFirstAttribute($this->schema->accountName()); + } + + /** + * Sets the model's samaccountname. + * + * @param string $accountName + * + * @return Model + */ + public function setAccountName($accountName) + { + return $this->setFirstAttribute($this->schema->accountName(), $accountName); + } + + /** + * Returns the model's userPrincipalName. + * + * @link https://docs.microsoft.com/en-us/windows/win32/adschema/a-userprincipalname + * + * @return string + */ + public function getUserPrincipalName() + { + return $this->getFirstAttribute($this->schema->userPrincipalName()); + } + + /** + * Sets the model's userPrincipalName. + * + * @param string $upn + * + * @return Model + */ + public function setUserPrincipalName($upn) + { + return $this->setFirstAttribute($this->schema->userPrincipalName(), $upn); + } + + /** + * Returns the model's samaccounttype. + * + * @link https://msdn.microsoft.com/en-us/library/ms679637(v=vs.85).aspx + * + * @return string + */ + public function getAccountType() + { + return $this->getFirstAttribute($this->schema->accountType()); + } + + /** + * Returns the model's `whenCreated` time. + * + * @link https://msdn.microsoft.com/en-us/library/ms680924(v=vs.85).aspx + * + * @return string + */ + public function getCreatedAt() + { + return $this->getFirstAttribute($this->schema->createdAt()); + } + + /** + * Returns the created at time in a mysql formatted date. + * + * @return string + */ + public function getCreatedAtDate() + { + return (new DateTime())->setTimestamp($this->getCreatedAtTimestamp())->format($this->dateFormat); + } + + /** + * Returns the created at time in a unix timestamp format. + * + * @return float + */ + public function getCreatedAtTimestamp() + { + return DateTime::createFromFormat($this->timestampFormat, $this->getCreatedAt())->getTimestamp(); + } + + /** + * Returns the model's `whenChanged` time. + * + * @link https://msdn.microsoft.com/en-us/library/ms680921(v=vs.85).aspx + * + * @return string + */ + public function getUpdatedAt() + { + return $this->getFirstAttribute($this->schema->updatedAt()); + } + + /** + * Returns the updated at time in a mysql formatted date. + * + * @return string + */ + public function getUpdatedAtDate() + { + return (new DateTime())->setTimestamp($this->getUpdatedAtTimestamp())->format($this->dateFormat); + } + + /** + * Returns the updated at time in a unix timestamp format. + * + * @return float + */ + public function getUpdatedAtTimestamp() + { + return DateTime::createFromFormat($this->timestampFormat, $this->getUpdatedAt())->getTimestamp(); + } + + /** + * Returns the Container of the current Model. + * + * @link https://msdn.microsoft.com/en-us/library/ms679012(v=vs.85).aspx + * + * @return Container|Entry|bool + */ + public function getObjectClass() + { + return $this->query->findByDn($this->getObjectCategoryDn()); + } + + /** + * Returns the CN of the model's object category. + * + * @return null|string + */ + public function getObjectCategory() + { + $category = $this->getObjectCategoryArray(); + + if (is_array($category) && array_key_exists(0, $category)) { + return $category[0]; + } + } + + /** + * Returns the model's object category DN in an exploded array. + * + * @return array|false + */ + public function getObjectCategoryArray() + { + return Utilities::explodeDn($this->getObjectCategoryDn()); + } + + /** + * Returns the model's object category DN string. + * + * @return null|string + */ + public function getObjectCategoryDn() + { + return $this->getFirstAttribute($this->schema->objectCategory()); + } + + /** + * Returns the model's primary group ID. + * + * @link https://msdn.microsoft.com/en-us/library/ms679375(v=vs.85).aspx + * + * @return string + */ + public function getPrimaryGroupId() + { + return $this->getFirstAttribute($this->schema->primaryGroupId()); + } + + /** + * Returns the model's instance type. + * + * @link https://msdn.microsoft.com/en-us/library/ms676204(v=vs.85).aspx + * + * @return int + */ + public function getInstanceType() + { + return $this->getFirstAttribute($this->schema->instanceType()); + } + + /** + * Returns the distinguished name of the user who is assigned to manage this object. + * + * @return string|null + */ + public function getManagedBy() + { + return $this->getFirstAttribute($this->schema->managedBy()); + } + + /** + * Returns the user model of the user who is assigned to manage this object. + * + * Returns false otherwise. + * + * @return User|bool + */ + public function getManagedByUser() + { + if ($dn = $this->getManagedBy()) { + return $this->query->newInstance()->findByDn($dn); + } + + return false; + } + + /** + * Sets the user who is assigned to managed this object. + * + * @param Model|string $dn + * + * @return $this + */ + public function setManagedBy($dn) + { + if ($dn instanceof self) { + $dn = $dn->getDn(); + } + + return $this->setFirstAttribute($this->schema->managedBy(), $dn); + } + + /** + * Returns the model's max password age. + * + * @return string + */ + public function getMaxPasswordAge() + { + return $this->getFirstAttribute($this->schema->maxPasswordAge()); + } + + /** + * Returns the model's max password age in days. + * + * @return int + */ + public function getMaxPasswordAgeDays() + { + $age = $this->getMaxPasswordAge(); + + return (int) (abs($age) / 10000000 / 60 / 60 / 24); + } + + /** + * Determine if the current model is located inside the given OU. + * + * If a model instance is given, the strict parameter is ignored. + * + * @param Model|string $ou The organizational unit to check. + * @param bool $strict Whether the check is case-sensitive. + * + * @return bool + */ + public function inOu($ou, $strict = false) + { + if ($ou instanceof self) { + // If we've been given an OU model, we can + // just check if the OU's DN is inside + // the current models DN. + return (bool) strpos($this->getDn(), $ou->getDn()); + } + + $suffix = $strict ? '' : 'i'; + + return (bool) preg_grep("/{$ou}/{$suffix}", $this->getDnBuilder()->getComponents('ou')); + } + + /** + * Returns true / false if the current model is writable + * by checking its instance type integer. + * + * @return bool + */ + public function isWritable() + { + return (int) $this->getInstanceType() === 4; + } + + /** + * Saves the changes to LDAP and returns the results. + * + * @param array $attributes The attributes to update or create for the current entry. + * + * @return bool + */ + public function save(array $attributes = []) + { + $this->fireModelEvent(new Events\Saving($this)); + + $saved = $this->exists ? $this->update($attributes) : $this->create($attributes); + + if ($saved) { + $this->fireModelEvent(new Events\Saved($this)); + } + + return $saved; + } + + /** + * Updates the model. + * + * @param array $attributes The attributes to update for the current entry. + * + * @return bool + */ + public function update(array $attributes = []) + { + $this->fill($attributes); + + $modifications = $this->getModifications(); + + if (count($modifications) > 0) { + $this->fireModelEvent(new Events\Updating($this)); + + // Push the update. + if ($this->query->getConnection()->modifyBatch($this->getDn(), $modifications)) { + // Re-sync attributes. + $this->syncRaw(); + + $this->fireModelEvent(new Events\Updated($this)); + + // Re-set the models modifications. + $this->modifications = []; + + return true; + } + + // Modification failed, return false. + return false; + } + + // We need to return true here because modify batch will + // return false if no modifications are made + // but this may not always be the case. + return true; + } + + /** + * Creates the entry in LDAP. + * + * @param array $attributes The attributes for the new entry. + * + * @throws UnexpectedValueException + * + * @return bool + */ + public function create(array $attributes = []) + { + $this->fill($attributes); + + if (empty($this->getDn())) { + // If the model doesn't currently have a distinguished + // name set, we'll create one automatically using + // the current query builders base DN. + $dn = $this->getCreatableDn(); + + // If the dn we receive is the same as our queries base DN, we need + // to throw an exception. The LDAP object must have a valid RDN. + if ($dn->get() == $this->query->getDn()) { + throw new UnexpectedValueException("An LDAP object must have a valid RDN to be created. '$dn' given."); + } + + $this->setDn($dn); + } + + $this->fireModelEvent(new Events\Creating($this)); + + // Create the entry. + $created = $this->query->getConnection()->add($this->getDn(), $this->getCreatableAttributes()); + + if ($created) { + // If the entry was created we'll re-sync + // the models attributes from the server. + $this->syncRaw(); + + $this->fireModelEvent(new Events\Created($this)); + + return true; + } + + return false; + } + + /** + * Creates an attribute on the current model. + * + * @param string $attribute The attribute to create + * @param mixed $value The value of the new attribute + * @param bool $sync Whether to re-sync all attributes + * + * @return bool + */ + public function createAttribute($attribute, $value, $sync = true) + { + if ( + $this->exists && + $this->query->getConnection()->modAdd($this->getDn(), [$attribute => $value]) + ) { + if ($sync) { + $this->syncRaw(); + } + + return true; + } + + return false; + } + + /** + * Updates the specified attribute with the specified value. + * + * @param string $attribute The attribute to modify + * @param mixed $value The new value for the attribute + * @param bool $sync Whether to re-sync all attributes + * + * @return bool + */ + public function updateAttribute($attribute, $value, $sync = true) + { + if ( + $this->exists && + $this->query->getConnection()->modReplace($this->getDn(), [$attribute => $value]) + ) { + if ($sync) { + $this->syncRaw(); + } + + return true; + } + + return false; + } + + /** + * Deletes an attribute on the current entry. + * + * @param string|array $attributes The attribute(s) to delete + * @param bool $sync Whether to re-sync all attributes + * + * Delete specific values in attributes: + * + * ["memberuid" => "username"] + * + * Delete an entire attribute: + * + * ["memberuid" => []] + * + * @return bool + */ + public function deleteAttribute($attributes, $sync = true) + { + // If we've been given a string, we'll assume we're removing a + // single attribute. Otherwise, we'll assume it's + // an array of attributes to remove. + $attributes = is_string($attributes) ? [$attributes => []] : $attributes; + + if ( + $this->exists && + $this->query->getConnection()->modDelete($this->getDn(), $attributes) + ) { + if ($sync) { + $this->syncRaw(); + } + + return true; + } + + return false; + } + + /** + * Deletes the current entry. + * + * Throws a ModelNotFoundException if the current model does + * not exist or does not contain a distinguished name. + * + * @param bool $recursive Whether to recursively delete leaf nodes (models that are children). + * + * @throws ModelDoesNotExistException + * + * @return bool + */ + public function delete($recursive = false) + { + $dn = $this->getDn(); + + if ($this->exists === false || empty($dn)) { + // Make sure the record exists before we can delete it. + // Otherwise, we'll throw an exception. + throw (new ModelDoesNotExistException())->setModel(get_class($this)); + } + + $this->fireModelEvent(new Events\Deleting($this)); + + if ($recursive) { + // If recursive is requested, we'll retrieve all direct leaf nodes + // by executing a 'listing' and delete each resulting model. + $this->newQuery()->listing()->in($this->getDn())->get()->each(function (self $model) use ($recursive) { + $model->delete($recursive); + }); + } + + if ($this->query->getConnection()->delete($dn)) { + // If the deletion was successful, we'll mark the model + // as non-existing and fire the deleted event. + $this->exists = false; + + $this->fireModelEvent(new Events\Deleted($this)); + + return true; + } + + return false; + } + + /** + * Moves the current model into the given new parent. + * + * For example: $user->move($ou); + * + * @param Model|string $newParentDn The new parent of the current model. + * @param bool $deleteOldRdn Whether to delete the old models relative distinguished name once renamed / moved. + * + * @return bool + */ + public function move($newParentDn, $deleteOldRdn = true) + { + // First we'll explode the current models distinguished name and keep their attributes prefixes. + $parts = Utilities::explodeDn($this->getDn(), $removeAttrPrefixes = false); + + // If the current model has an empty RDN, we can't move it. + if ((int) Arr::first($parts) === 0) { + throw new UnexpectedValueException('Current model does not contain an RDN to move.'); + } + + // Looks like we have a DN. We'll retrieve the leftmost RDN (the identifier). + $rdn = Arr::get($parts, 0); + + return $this->rename($rdn, $newParentDn, $deleteOldRdn); + } + + /** + * Renames the current model to a new RDN and new parent. + * + * @param string $rdn The models new relative distinguished name. Example: "cn=JohnDoe" + * @param Model|string|null $newParentDn The models new parent distinguished name (if moving). Leave this null if you are only renaming. Example: "ou=MovedUsers,dc=acme,dc=org" + * @param bool|true $deleteOldRdn Whether to delete the old models relative distinguished name once renamed / moved. + * + * @return bool + */ + public function rename($rdn, $newParentDn = null, $deleteOldRdn = true) + { + if ($newParentDn instanceof self) { + $newParentDn = $newParentDn->getDn(); + } + + $moved = $this->query->getConnection()->rename($this->getDn(), $rdn, $newParentDn, $deleteOldRdn); + + if ($moved) { + // If the model was successfully moved, we'll set its + // new DN so we can sync it's attributes properly. + $this->setDn("{$rdn},{$newParentDn}"); + + $this->syncRaw(); + + return true; + } + + return false; + } + + /** + * Constructs a new distinguished name that is creatable in the directory. + * + * @return DistinguishedName|string + */ + protected function getCreatableDn() + { + return $this->getDnBuilder()->addCn($this->getCommonName()); + } + + /** + * Returns the models creatable attributes. + * + * @return mixed + */ + protected function getCreatableAttributes() + { + return Arr::except($this->getAttributes(), [$this->schema->distinguishedName()]); + } + + /** + * Determines if the given modification is valid. + * + * @param mixed $mod + * + * @return bool + */ + protected function isValidModification($mod) + { + return is_array($mod) && + array_key_exists(BatchModification::KEY_MODTYPE, $mod) && + array_key_exists(BatchModification::KEY_ATTRIB, $mod); + } + + /** + * Builds the models modifications from its dirty attributes. + * + * @return array + */ + protected function buildModificationsFromDirty() + { + foreach ($this->getDirty() as $attribute => $values) { + // Make sure values is always an array. + $values = (is_array($values) ? $values : [$values]); + + // Create a new modification. + $modification = $this->newBatchModification($attribute, null, $values); + + if (array_key_exists($attribute, $this->original)) { + // If the attribute we're modifying has an original value, we'll give the + // BatchModification object its values to automatically determine + // which type of LDAP operation we need to perform. + $modification->setOriginal($this->original[$attribute]); + } + + // Build the modification from its + // possible original values. + $modification->build(); + + if ($modification->isValid()) { + // Finally, we'll add the modification to the model. + $this->addModification($modification); + } + } + + return $this->modifications; + } + + /** + * Validates that the current LDAP connection is secure. + * + * @throws ConnectionException + * + * @return void + */ + protected function validateSecureConnection() + { + if (!$this->query->getConnection()->canChangePasswords()) { + throw new ConnectionException( + 'You must be connected to your LDAP server with TLS or SSL to perform this operation.' + ); + } + } + + /** + * Converts the inserted string boolean to a PHP boolean. + * + * @param string $bool + * + * @return null|bool + */ + protected function convertStringToBool($bool) + { + $bool = strtoupper($bool); + + if ($bool === strtoupper($this->schema->false())) { + return false; + } elseif ($bool === strtoupper($this->schema->true())) { + return true; + } else { + return; + } + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/ModelDoesNotExistException.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/ModelDoesNotExistException.php new file mode 100644 index 0000000000..393561052a --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/ModelDoesNotExistException.php @@ -0,0 +1,36 @@ +model = $model; + + $this->message = "Model [{$model}] does not exist."; + + return $this; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/ModelNotFoundException.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/ModelNotFoundException.php new file mode 100644 index 0000000000..6cc3334af5 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/ModelNotFoundException.php @@ -0,0 +1,45 @@ +query = $query; + $this->baseDn = $baseDn; + + $this->message = "No LDAP query results for filter: [{$query}] in: [{$baseDn}]"; + + return $this; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Organization.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Organization.php new file mode 100644 index 0000000000..88129d01e2 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Organization.php @@ -0,0 +1,31 @@ +getFirstAttribute($this->schema->organizationName()); + } + + /** + * {@inheritdoc} + */ + protected function getCreatableDn() + { + return $this->getDnBuilder()->addO($this->getOrganization()); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/OrganizationalUnit.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/OrganizationalUnit.php new file mode 100644 index 0000000000..0571e4d407 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/OrganizationalUnit.php @@ -0,0 +1,31 @@ +getFirstAttribute($this->schema->organizationalUnitShort()); + } + + /** + * {@inheritdoc} + */ + protected function getCreatableDn() + { + return $this->getDnBuilder()->addOU($this->getOu()); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Printer.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Printer.php new file mode 100644 index 0000000000..e05586634a --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Printer.php @@ -0,0 +1,286 @@ +getFirstAttribute($this->schema->printerName()); + } + + /** + * Returns the printers share name. + * + * @link https://msdn.microsoft.com/en-us/library/ms679408(v=vs.85).aspx + * + * @return string + */ + public function getPrinterShareName() + { + return $this->getFirstAttribute($this->schema->printerShareName()); + } + + /** + * Returns the printers memory. + * + * @link https://msdn.microsoft.com/en-us/library/ms679396(v=vs.85).aspx + * + * @return string + */ + public function getMemory() + { + return $this->getFirstAttribute($this->schema->printerMemory()); + } + + /** + * Returns the printers URL. + * + * @return string + */ + public function getUrl() + { + return $this->getFirstAttribute($this->schema->url()); + } + + /** + * Returns the printers location. + * + * @link https://msdn.microsoft.com/en-us/library/ms676839(v=vs.85).aspx + * + * @return string + */ + public function getLocation() + { + return $this->getFirstAttribute($this->schema->location()); + } + + /** + * Returns the server name that the + * current printer is connected to. + * + * @link https://msdn.microsoft.com/en-us/library/ms679772(v=vs.85).aspx + * + * @return string + */ + public function getServerName() + { + return $this->getFirstAttribute($this->schema->serverName()); + } + + /** + * Returns true / false if the printer can print in color. + * + * @link https://msdn.microsoft.com/en-us/library/ms679382(v=vs.85).aspx + * + * @return null|bool + */ + public function getColorSupported() + { + return $this->convertStringToBool( + $this->getFirstAttribute( + $this->schema->printerColorSupported() + ) + ); + } + + /** + * Returns true / false if the printer supports duplex printing. + * + * @link https://msdn.microsoft.com/en-us/library/ms679383(v=vs.85).aspx + * + * @return null|bool + */ + public function getDuplexSupported() + { + return $this->convertStringToBool( + $this->getFirstAttribute( + $this->schema->printerDuplexSupported() + ) + ); + } + + /** + * Returns an array of printer paper types that the printer supports. + * + * @link https://msdn.microsoft.com/en-us/library/ms679395(v=vs.85).aspx + * + * @return array + */ + public function getMediaSupported() + { + return $this->getAttribute($this->schema->printerMediaSupported()); + } + + /** + * Returns true / false if the printer supports stapling. + * + * @link https://msdn.microsoft.com/en-us/library/ms679410(v=vs.85).aspx + * + * @return null|bool + */ + public function getStaplingSupported() + { + return $this->convertStringToBool( + $this->getFirstAttribute( + $this->schema->printerStaplingSupported() + ) + ); + } + + /** + * Returns an array of the printers bin names. + * + * @link https://msdn.microsoft.com/en-us/library/ms679380(v=vs.85).aspx + * + * @return array + */ + public function getPrintBinNames() + { + return $this->getAttribute($this->schema->printerBinNames()); + } + + /** + * Returns the printers maximum resolution. + * + * @link https://msdn.microsoft.com/en-us/library/ms679391(v=vs.85).aspx + * + * @return string + */ + public function getPrintMaxResolution() + { + return $this->getFirstAttribute($this->schema->printerMaxResolutionSupported()); + } + + /** + * Returns the printers orientations supported. + * + * @link https://msdn.microsoft.com/en-us/library/ms679402(v=vs.85).aspx + * + * @return string + */ + public function getPrintOrientations() + { + return $this->getFirstAttribute($this->schema->printerOrientationSupported()); + } + + /** + * Returns the driver name of the printer. + * + * @link https://msdn.microsoft.com/en-us/library/ms675652(v=vs.85).aspx + * + * @return string + */ + public function getDriverName() + { + return $this->getFirstAttribute($this->schema->driverName()); + } + + /** + * Returns the printer drivers version number. + * + * @link https://msdn.microsoft.com/en-us/library/ms675653(v=vs.85).aspx + * + * @return string + */ + public function getDriverVersion() + { + return $this->getFirstAttribute($this->schema->driverVersion()); + } + + /** + * Returns the priority number of the printer. + * + * @link https://msdn.microsoft.com/en-us/library/ms679413(v=vs.85).aspx + * + * @return string + */ + public function getPriority() + { + return $this->getFirstAttribute($this->schema->priority()); + } + + /** + * Returns the printers start time. + * + * @link https://msdn.microsoft.com/en-us/library/ms679411(v=vs.85).aspx + * + * @return string + */ + public function getPrintStartTime() + { + return $this->getFirstAttribute($this->schema->printerStartTime()); + } + + /** + * Returns the printers end time. + * + * @link https://msdn.microsoft.com/en-us/library/ms679384(v=vs.85).aspx + * + * @return string + */ + public function getPrintEndTime() + { + return $this->getFirstAttribute($this->schema->printerEndTime()); + } + + /** + * Returns the port name of printer. + * + * @link https://msdn.microsoft.com/en-us/library/ms679131(v=vs.85).aspx + * + * @return string + */ + public function getPortName() + { + return $this->getFirstAttribute($this->schema->portName()); + } + + /** + * Returns the printers version number. + * + * @link https://msdn.microsoft.com/en-us/library/ms680897(v=vs.85).aspx + * + * @return string + */ + public function getVersionNumber() + { + return $this->getFirstAttribute($this->schema->versionNumber()); + } + + /** + * Returns the print rate. + * + * @link https://msdn.microsoft.com/en-us/library/ms679405(v=vs.85).aspx + * + * @return string + */ + public function getPrintRate() + { + return $this->getFirstAttribute($this->schema->printerPrintRate()); + } + + /** + * Returns the print rate unit. + * + * @link https://msdn.microsoft.com/en-us/library/ms679406(v=vs.85).aspx + * + * @return string + */ + public function getPrintRateUnit() + { + return $this->getFirstAttribute($this->schema->printerPrintRateUnit()); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/RootDse.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/RootDse.php new file mode 100644 index 0000000000..31156c16f1 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/RootDse.php @@ -0,0 +1,85 @@ +getFirstAttribute($this->schema->currentTime()); + + return DateTime::createFromFormat($this->timestampFormat, $time)->getTimestamp(); + } + + /** + * Returns the hosts current time in the models date format. + * + * @return string + */ + public function getCurrentTimeDate() + { + return (new DateTime())->setTimestamp($this->getCurrentTime())->format($this->dateFormat); + } + + /** + * Returns the hosts configuration naming context. + * + * @return string + */ + public function getConfigurationNamingContext() + { + return $this->getFirstAttribute($this->schema->configurationNamingContext()); + } + + /** + * Returns the hosts schema naming context. + * + * @return string + */ + public function getSchemaNamingContext() + { + return $this->getFirstAttribute($this->schema->schemaNamingContext()); + } + + /** + * Returns the hosts DNS name. + * + * @return string + */ + public function getDnsHostName() + { + return $this->getFirstAttribute($this->schema->dnsHostName()); + } + + /** + * Returns the current hosts server name. + * + * @return string + */ + public function getServerName() + { + return $this->getFirstAttribute($this->schema->serverName()); + } + + /** + * Returns the DN of the root domain NC for this DC's forest. + * + * @return mixed + */ + public function getRootDomainNamingContext() + { + return $this->getFirstAttribute($this->schema->rootDomainNamingContext()); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/User.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/User.php new file mode 100644 index 0000000000..14c33435e5 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/User.php @@ -0,0 +1,1054 @@ +schema->objectGuid(); + } + + /** + * Get the unique identifier for the user. + * + * @return mixed + */ + public function getAuthIdentifier() + { + return $this->getConvertedGuid(); + } + + /** + * Get the password for the user. + * + * @return string + */ + public function getAuthPassword() + { + } + + /** + * Get the token value for the "remember me" session. + * + * @return string + */ + public function getRememberToken() + { + } + + /** + * Set the token value for the "remember me" session. + * + * @param string $value + * + * @return void + */ + public function setRememberToken($value) + { + } + + /** + * Get the column name for the "remember me" token. + * + * @return string + */ + public function getRememberTokenName() + { + } + + /** + * Returns the department number. + * + * @return string + */ + public function getDepartmentNumber() + { + return $this->getFirstAttribute($this->schema->departmentNumber()); + } + + /** + * Sets the department number. + * + * @param string $number + * + * @return $this + */ + public function setDepartmentNumber($number) + { + return $this->setFirstAttribute($this->schema->departmentNumber(), $number); + } + + /** + * Returns the users info. + * + * @return mixed + */ + public function getInfo() + { + return $this->getFirstAttribute($this->schema->info()); + } + + /** + * Sets the users info. + * + * @param string $info + * + * @return $this + */ + public function setInfo($info) + { + return $this->setFirstAttribute($this->schema->info(), $info); + } + + /** + * Returns the users physical delivery office name. + * + * @return string + */ + public function getPhysicalDeliveryOfficeName() + { + return $this->getFirstAttribute($this->schema->physicalDeliveryOfficeName()); + } + + /** + * Sets the users physical delivery office name. + * + * @param string $deliveryOffice + * + * @return $this + */ + public function setPhysicalDeliveryOfficeName($deliveryOffice) + { + return $this->setFirstAttribute($this->schema->physicalDeliveryOfficeName(), $deliveryOffice); + } + + /** + * Returns the users locale. + * + * @return string + */ + public function getLocale() + { + return $this->getFirstAttribute($this->schema->locale()); + } + + /** + * Sets the users locale. + * + * @param string $locale + * + * @return $this + */ + public function setLocale($locale) + { + return $this->setFirstAttribute($this->schema->locale(), $locale); + } + + /** + * Returns the users company. + * + * @link https://msdn.microsoft.com/en-us/library/ms675457(v=vs.85).aspx + * + * @return string + */ + public function getCompany() + { + return $this->getFirstAttribute($this->schema->company()); + } + + /** + * Sets the users company. + * + * @param string $company + * + * @return $this + */ + public function setCompany($company) + { + return $this->setFirstAttribute($this->schema->company(), $company); + } + + /** + * Returns the users mailbox store DN. + * + * @link https://msdn.microsoft.com/en-us/library/aa487565(v=exchg.65).aspx + * + * @return string + */ + public function getHomeMdb() + { + return $this->getFirstAttribute($this->schema->homeMdb()); + } + + /** + * Sets the users home drive. + * + * @link https://msdn.microsoft.com/en-us/library/ms676191(v=vs.85).aspx + * + * @return $this + */ + public function setHomeDrive($drive) + { + return $this->setAttribute($this->schema->homeDrive(), $drive); + } + + /** + * Specifies the drive letter to which to map the UNC path specified by homeDirectory. + * + * @link https://msdn.microsoft.com/en-us/library/ms676191(v=vs.85).aspx + * + * @return string|null + */ + public function getHomeDrive() + { + return $this->getFirstAttribute($this->schema->homeDrive()); + } + + /** + * Sets the users home directory. + * + * @link https://msdn.microsoft.com/en-us/library/ms676190(v=vs.85).aspx + * + * @param string $directory + * + * @return $this + */ + public function setHomeDirectory($directory) + { + return $this->setAttribute($this->schema->homeDirectory(), $directory); + } + + /** + * The home directory for the account. + * + * @link https://msdn.microsoft.com/en-us/library/ms676190(v=vs.85).aspx + * + * @return string|null + */ + public function getHomeDirectory() + { + return $this->getFirstAttribute($this->schema->homeDirectory()); + } + + /** + * The user's main home phone number. + * + * @link https://docs.microsoft.com/en-us/windows/desktop/ADSchema/a-homephone + * + * @return string|null + */ + public function getHomePhone() + { + return $this->getFirstAttribute($this->schema->homePhone()); + } + + /** + * Returns the users principal name. + * + * This is usually their email address. + * + * @link https://msdn.microsoft.com/en-us/library/ms680857(v=vs.85).aspx + * + * @return string + */ + public function getUserPrincipalName() + { + return $this->getFirstAttribute($this->schema->userPrincipalName()); + } + + /** + * Sets the users user principal name. + * + * @param string $userPrincipalName + * + * @return $this + */ + public function setUserPrincipalName($userPrincipalName) + { + return $this->setFirstAttribute($this->schema->userPrincipalName(), $userPrincipalName); + } + + /** + * Returns an array of workstations the user is assigned to. + * + * @return array + */ + public function getUserWorkstations() + { + $workstations = $this->getFirstAttribute($this->schema->userWorkstations()); + + return array_filter(explode(',', $workstations)); + } + + /** + * Sets the workstations the user can login to. + * + * @param string|array $workstations The names of the workstations the user can login to. + * Must be an array of names, or a comma separated + * list of names. + * + * @return $this + */ + public function setUserWorkstations($workstations = []) + { + if (is_array($workstations)) { + $workstations = implode(',', $workstations); + } + + return $this->setFirstAttribute($this->schema->userWorkstations(), $workstations); + } + + /** + * Returns the users script path if the user has one. + * + * @link https://msdn.microsoft.com/en-us/library/ms679656(v=vs.85).aspx + * + * @return string + */ + public function getScriptPath() + { + return $this->getFirstAttribute($this->schema->scriptPath()); + } + + /** + * Sets the users script path. + * + * @param string $path + * + * @return $this + */ + public function setScriptPath($path) + { + return $this->setFirstAttribute($this->schema->scriptPath(), $path); + } + + /** + * Returns the users bad password count. + * + * @return string + */ + public function getBadPasswordCount() + { + return $this->getFirstAttribute($this->schema->badPasswordCount()); + } + + /** + * Returns the users bad password time. + * + * @return string + */ + public function getBadPasswordTime() + { + return $this->getFirstAttribute($this->schema->badPasswordTime()); + } + + /** + * Returns the bad password time unix timestamp. + * + * @return float|null + */ + public function getBadPasswordTimestamp() + { + if ($time = $this->getBadPasswordTime()) { + return Utilities::convertWindowsTimeToUnixTime($time); + } + } + + /** + * Returns the formatted timestamp of the bad password date. + * + * @throws \Exception + * + * @return string|null + */ + public function getBadPasswordDate() + { + if ($timestamp = $this->getBadPasswordTimestamp()) { + return (new DateTime())->setTimestamp($timestamp)->format($this->dateFormat); + } + } + + /** + * Returns the time when the users password was set last. + * + * @return string + */ + public function getPasswordLastSet() + { + return $this->getFirstAttribute($this->schema->passwordLastSet()); + } + + /** + * Returns the password last set unix timestamp. + * + * @return float|null + */ + public function getPasswordLastSetTimestamp() + { + if ($time = $this->getPasswordLastSet()) { + return Utilities::convertWindowsTimeToUnixTime($time); + } + } + + /** + * Returns the formatted timestamp of the password last set date. + * + * @throws \Exception + * + * @return string|null + */ + public function getPasswordLastSetDate() + { + if ($timestamp = $this->getPasswordLastSetTimestamp()) { + return (new DateTime())->setTimestamp($timestamp)->format($this->dateFormat); + } + } + + /** + * Returns the users lockout time. + * + * @return string + */ + public function getLockoutTime() + { + return $this->getFirstAttribute($this->schema->lockoutTime()); + } + + /** + * Returns the users lockout unix timestamp. + * + * @return float|null + */ + public function getLockoutTimestamp() + { + if ($time = $this->getLockoutTime()) { + return Utilities::convertWindowsTimeToUnixTime($time); + } + } + + /** + * Returns the formatted timestamp of the lockout date. + * + * @throws \Exception + * + * @return string|null + */ + public function getLockoutDate() + { + if ($timestamp = $this->getLockoutTimestamp()) { + return (new DateTime())->setTimestamp($timestamp)->format($this->dateFormat); + } + } + + /** + * Clears the accounts lockout time, unlocking the account. + * + * @return $this + */ + public function setClearLockoutTime() + { + return $this->setFirstAttribute($this->schema->lockoutTime(), 0); + } + + /** + * Returns the users profile file path. + * + * @return string + */ + public function getProfilePath() + { + return $this->getFirstAttribute($this->schema->profilePath()); + } + + /** + * Sets the users profile path. + * + * @param string $path + * + * @return $this + */ + public function setProfilePath($path) + { + return $this->setFirstAttribute($this->schema->profilePath(), $path); + } + + /** + * Returns the users legacy exchange distinguished name. + * + * @return string + */ + public function getLegacyExchangeDn() + { + return $this->getFirstAttribute($this->schema->legacyExchangeDn()); + } + + /** + * Sets the users account expiry date. + * + * If no expiry time is given, the account is set to never expire. + * + * @link https://msdn.microsoft.com/en-us/library/ms675098(v=vs.85).aspx + * + * @param float $expiryTime + * + * @return $this + */ + public function setAccountExpiry($expiryTime) + { + $time = is_null($expiryTime) ? '9223372036854775807' : (string) Utilities::convertUnixTimeToWindowsTime($expiryTime); + + return $this->setFirstAttribute($this->schema->accountExpires(), $time); + } + + /** + * Returns an array of address book DNs + * that the user is listed to be shown in. + * + * @return array + */ + public function getShowInAddressBook() + { + return $this->getAttribute($this->schema->showInAddressBook()); + } + + /** + * Returns the users thumbnail photo base 64 encoded. + * + * Suitable for inserting into an HTML image element. + * + * @return string|null + */ + public function getThumbnailEncoded() + { + if ($data = base64_decode($this->getThumbnail(), $strict = true)) { + // In case we don't have the file info extension enabled, + // we'll set the jpeg mime type as default. + $mime = 'image/jpeg'; + + $image = base64_encode($data); + + if (function_exists('finfo_open')) { + $finfo = finfo_open(); + + $mime = finfo_buffer($finfo, $data, FILEINFO_MIME_TYPE); + + return "data:$mime;base64,$image"; + } + + return "data:$mime;base64,$image"; + } + } + + /** + * Returns the users thumbnail photo. + * + * @return mixed + */ + public function getThumbnail() + { + return $this->getFirstAttribute($this->schema->thumbnail()); + } + + /** + * Sets the users thumbnail photo. + * + * @param string $data + * @param bool $encode + * + * @return $this + */ + public function setThumbnail($data, $encode = true) + { + if ($encode && !base64_decode($data, $strict = true)) { + // If the string we're given is not base 64 encoded, then + // we will encode it before setting it on the user. + $data = base64_encode($data); + } + + return $this->setAttribute($this->schema->thumbnail(), $data); + } + + /** + * Returns the users JPEG photo. + * + * @return null|string + */ + public function getJpegPhotoEncoded() + { + $jpeg = $this->getJpegPhoto(); + + return is_null($jpeg) ? $jpeg : 'data:image/jpeg;base64,'.base64_encode($jpeg); + } + + /** + * Returns the users JPEG photo. + * + * @return mixed + */ + public function getJpegPhoto() + { + return $this->getFirstAttribute($this->schema->jpegPhoto()); + } + + /** + * Sets the users JPEG photo. + * + * @param string $string + * + * @return $this + */ + public function setJpegPhoto($string) + { + if (!base64_decode($string, $strict = true)) { + $string = base64_encode($string); + } + + return $this->setAttribute($this->schema->jpegPhoto(), $string); + } + + /** + * Return the employee ID. + * + * @return string + */ + public function getEmployeeId() + { + return $this->getFirstAttribute($this->schema->employeeId()); + } + + /** + * Sets the employee ID. + * + * @param string $employeeId + * + * @return $this + */ + public function setEmployeeId($employeeId) + { + return $this->setFirstAttribute($this->schema->employeeId(), $employeeId); + } + + /** + * Returns the employee type. + * + * @return string|null + */ + public function getEmployeeType() + { + return $this->getFirstAttribute($this->schema->employeeType()); + } + + /** + * Sets the employee type. + * + * @param string $type + * + * @return $this + */ + public function setEmployeeType($type) + { + return $this->setFirstAttribute($this->schema->employeeType(), $type); + } + + /** + * Returns the employee number. + * + * @return string + */ + public function getEmployeeNumber() + { + return $this->getFirstAttribute($this->schema->employeeNumber()); + } + + /** + * Sets the employee number. + * + * @param string $number + * + * @return $this + */ + public function setEmployeeNumber($number) + { + return $this->setFirstAttribute($this->schema->employeeNumber(), $number); + } + + /** + * Returns the room number. + * + * @return string + */ + public function getRoomNumber() + { + return $this->getFirstAttribute($this->schema->roomNumber()); + } + + /** + * Sets the room number. + * + * @param string $number + * + * @return $this + */ + public function setRoomNumber($number) + { + return $this->setFirstAttribute($this->schema->roomNumber(), $number); + } + + /** + * Return the personal title. + * + * @return $this + */ + public function getPersonalTitle() + { + return $this->getFirstAttribute($this->schema->personalTitle()); + } + + /** + * Sets the personal title. + * + * @param string $personalTitle + * + * @return $this + */ + public function setPersonalTitle($personalTitle) + { + return $this->setFirstAttribute($this->schema->personalTitle(), $personalTitle); + } + + /** + * Return the user parameters. + * + * @return TSPropertyArray + */ + public function getUserParameters() + { + return new TSPropertyArray($this->getFirstAttribute('userparameters')); + } + + /** + * Sets the user parameters. + * + * @param TSPropertyArray $userParameters + * + * @return $this + */ + public function setUserParameters(TSPropertyArray $userParameters) + { + return $this->setFirstAttribute('userparameters', $userParameters->toBinary()); + } + + /** + * Retrieves the primary group of the current user. + * + * @return Model|bool + */ + public function getPrimaryGroup() + { + $groupSid = preg_replace('/\d+$/', $this->getPrimaryGroupId(), $this->getConvertedSid()); + + return $this->query->newInstance()->findBySid($groupSid); + } + + /** + * Sets the password on the current user. + * + * @param string $password + * + * @throws AdldapException When no SSL or TLS secured connection is present. + * + * @return $this + */ + public function setPassword($password) + { + $this->validateSecureConnection(); + + $encodedPassword = call_user_func(static::getPasswordStrategy(), $password); + + if ($this->exists) { + // If the record exists, we need to add a batch replace + // modification, otherwise we'll receive a "type or + // value" exists exception from our LDAP server. + return $this->addModification( + $this->newBatchModification( + $this->schema->unicodePassword(), + LDAP_MODIFY_BATCH_REPLACE, + [$encodedPassword] + ) + ); + } else { + // Otherwise, we are creating a new record + // and we can set the attribute normally. + return $this->setFirstAttribute( + $this->schema->unicodePassword(), + $encodedPassword + ); + } + } + + /** + * Sets the option to force the password change at the next logon. + * + * Does not work if the "Password never expires" option is enabled. + * + * @return $this + */ + public function setEnableForcePasswordChange() + { + return $this->setFirstAttribute($this->schema->passwordLastSet(), 0); + } + + /** + * Sets the option to disable forcing a password change at the next logon. + * + * @return $this + */ + public function setDisableForcePasswordChange() + { + return $this->setFirstAttribute($this->schema->passwordLastSet(), -1); + } + + /** + * Change the password of the current user. This must be performed over SSL / TLS. + * + * Throws an exception on failure. + * + * @param string $oldPassword The new password + * @param string $newPassword The old password + * @param bool $replaceNotRemove Alternative password change method. Set to true if you're receiving 'CONSTRAINT' + * errors. + * + * @throws UserPasswordPolicyException When the new password does not match your password policy. + * @throws UserPasswordIncorrectException When the old password is incorrect. + * @throws AdldapException When an unknown cause of failure occurs. + * + * @return true + */ + public function changePassword($oldPassword, $newPassword, $replaceNotRemove = false) + { + $this->validateSecureConnection(); + + $attribute = $this->schema->unicodePassword(); + + $modifications = []; + + if ($replaceNotRemove) { + $modifications[] = $this->newBatchModification( + $attribute, + LDAP_MODIFY_BATCH_REPLACE, + [call_user_func(static::getPasswordStrategy(), $newPassword)] + ); + } else { + // Create batch modification for removing the old password. + $modifications[] = $this->newBatchModification( + $attribute, + LDAP_MODIFY_BATCH_REMOVE, + [call_user_func(static::getPasswordStrategy(), $oldPassword)] + ); + + // Create batch modification for adding the new password. + $modifications[] = $this->newBatchModification( + $attribute, + LDAP_MODIFY_BATCH_ADD, + [call_user_func(static::getPasswordStrategy(), $newPassword)] + ); + } + + // Add the modifications. + foreach ($modifications as $modification) { + $this->addModification($modification); + } + + $result = @$this->update(); + + if (!$result) { + // If the user failed to update, we'll see if we can + // figure out why by retrieving the extended error. + $error = $this->query->getConnection()->getExtendedError(); + $code = $this->query->getConnection()->getExtendedErrorCode(); + + switch ($code) { + case '0000052D': + throw new UserPasswordPolicyException( + "Error: $code. Your new password does not match the password policy." + ); + case '00000056': + throw new UserPasswordIncorrectException( + "Error: $code. Your old password is incorrect." + ); + default: + throw new AdldapException($error); + } + } + + return $result; + } + + /** + * Return true / false if LDAP User is active (enabled & not expired). + * + * @return bool + */ + public function isActive() + { + return $this->isEnabled() && !$this->isExpired(); + } + + /** + * Return true / false if the LDAP User is expired. + * + * @param DateTime $date Optional date + * + * @return bool + */ + public function isExpired(DateTime $date = null) + { + // Here we'll determine if the account expires by checking is expiration date. + if ($expirationDate = $this->expirationDate()) { + $date = $date ?: new DateTime(); + + return $expirationDate <= $date; + } + + // The account has no expiry date. + return false; + } + + /** + * Return the expiration date of the user account. + * + * @throws \Exception + * + * @return DateTime|null + */ + public function expirationDate() + { + $accountExpiry = $this->getAccountExpiry(); + + // If the account expiry is zero or the expiry is equal to + // ActiveDirectory's 'never expire' value, + // then we'll return null here. + if ($accountExpiry == 0 || $accountExpiry == $this->getSchema()->neverExpiresDate()) { + return; + } + + $unixTime = Utilities::convertWindowsTimeToUnixTime($accountExpiry); + + return (new DateTime())->setTimestamp($unixTime); + } + + /** + * Returns the users account expiry date. + * + * @return string + */ + public function getAccountExpiry() + { + return $this->getFirstAttribute($this->schema->accountExpires()); + } + + /** + * Returns true / false if the users password is expired. + * + * @return bool + */ + public function passwordExpired() + { + // First we'll check the users userAccountControl to see if + // it contains the 'password does not expire' flag. + if ($this->getUserAccountControlObject()->has(AccountControl::DONT_EXPIRE_PASSWORD)) { + return false; + } + + $lastSet = (int) $this->getPasswordLastSet(); + + if ($lastSet === 0) { + // If the users last set time is zero, the password has + // been manually expired by an administrator. + return true; + } + + // We'll check if we're using the ActiveDirectory schema to retrieve + // the max password age, as this is an AD-only feature. + if ($this->schema instanceof ActiveDirectory) { + $query = $this->query->newInstance(); + + // We need to get the root domain object to be able to + // retrieve the max password age on the domain. + $rootDomainObject = $query->select($this->schema->maxPasswordAge()) + ->whereHas($this->schema->objectClass()) + ->first(); + + $maxPasswordAge = $rootDomainObject->getMaxPasswordAge(); + + if (empty($maxPasswordAge)) { + // There is not a max password age set on the LDAP server. + return false; + } + + // convert from 100 nanosecond ticks to seconds + $maxPasswordAgeSeconds = $maxPasswordAge / 10000000; + + $lastSetUnixEpoch = Utilities::convertWindowsTimeToUnixTime($lastSet); + $passwordExpiryTime = $lastSetUnixEpoch - $maxPasswordAgeSeconds; + + $expiresAt = (new DateTime())->setTimestamp($passwordExpiryTime); + + // If our current time is greater than the users password + // expiry time, the users password has expired. + return (new DateTime())->getTimestamp() >= $expiresAt->getTimestamp(); + } + + return false; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/UserPasswordIncorrectException.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/UserPasswordIncorrectException.php new file mode 100644 index 0000000000..44cb39bfcc --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Models/UserPasswordIncorrectException.php @@ -0,0 +1,16 @@ + [], + 'or' => [], + 'raw' => [], + ]; + + /** + * The size limit of the query. + * + * @var int + */ + public $limit = 0; + + /** + * Determines whether the current query is paginated. + * + * @var bool + */ + public $paginated = false; + + /** + * The field to sort search results by. + * + * @var string + */ + protected $sortByField = ''; + + /** + * The direction to sort the results by. + * + * @var string + */ + protected $sortByDirection = ''; + + /** + * The sort flags for sorting query results. + * + * @var int + */ + protected $sortByFlags; + + /** + * The distinguished name to perform searches upon. + * + * @var string|null + */ + protected $dn; + + /** + * The default query type. + * + * @var string + */ + protected $type = 'search'; + + /** + * Determines whether or not to return LDAP results in their raw array format. + * + * @var bool + */ + protected $raw = false; + + /** + * Determines whether the query is nested. + * + * @var bool + */ + protected $nested = false; + + /** + * Determines whether the query should be cached. + * + * @var bool + */ + protected $caching = false; + + /** + * How long the query should be cached until. + * + * @var \DateTimeInterface|null + */ + protected $cacheUntil = null; + + /** + * Determines whether the query cache must be flushed. + * + * @var bool + */ + protected $flushCache = false; + + /** + * The current connection instance. + * + * @var ConnectionInterface + */ + protected $connection; + + /** + * The current grammar instance. + * + * @var Grammar + */ + protected $grammar; + + /** + * The current schema instance. + * + * @var SchemaInterface + */ + protected $schema; + + /** + * The current cache instance. + * + * @var Cache|null + */ + protected $cache; + + /** + * Constructor. + * + * @param ConnectionInterface $connection + * @param Grammar|null $grammar + * @param SchemaInterface|null $schema + */ + public function __construct(ConnectionInterface $connection, Grammar $grammar = null, SchemaInterface $schema = null) + { + $this->setConnection($connection) + ->setGrammar($grammar) + ->setSchema($schema); + } + + /** + * Sets the current connection. + * + * @param ConnectionInterface $connection + * + * @return Builder + */ + public function setConnection(ConnectionInterface $connection) + { + $this->connection = $connection; + + return $this; + } + + /** + * Sets the current filter grammar. + * + * @param Grammar|null $grammar + * + * @return Builder + */ + public function setGrammar(Grammar $grammar = null) + { + $this->grammar = $grammar ?: new Grammar(); + + return $this; + } + + /** + * Sets the current schema. + * + * @param SchemaInterface|null $schema + * + * @return Builder + */ + public function setSchema(SchemaInterface $schema = null) + { + $this->schema = $schema ?: new ActiveDirectory(); + + return $this; + } + + /** + * Returns the current schema. + * + * @return SchemaInterface + */ + public function getSchema() + { + return $this->schema; + } + + /** + * Sets the cache to store query results. + * + * @param Cache|null $cache + */ + public function setCache(Cache $cache = null) + { + $this->cache = $cache; + + return $this; + } + + /** + * Returns a new Query Builder instance. + * + * @param string $baseDn + * + * @return Builder + */ + public function newInstance($baseDn = null) + { + // We'll set the base DN of the new Builder so + // developers don't need to do this manually. + $dn = is_null($baseDn) ? $this->getDn() : $baseDn; + + return (new static($this->connection, $this->grammar, $this->schema)) + ->setDn($dn); + } + + /** + * Returns a new nested Query Builder instance. + * + * @param Closure|null $closure + * + * @return $this + */ + public function newNestedInstance(Closure $closure = null) + { + $query = $this->newInstance()->nested(); + + if ($closure) { + call_user_func($closure, $query); + } + + return $query; + } + + /** + * Returns the current query. + * + * @return Collection|array + */ + public function get() + { + // We'll mute any warnings / errors here. We just need to + // know if any query results were returned. + return @$this->query($this->getQuery()); + } + + /** + * Compiles and returns the current query string. + * + * @return string + */ + public function getQuery() + { + // We need to ensure we have at least one filter, as + // no query results will be returned otherwise. + if (count(array_filter($this->filters)) === 0) { + $this->whereHas($this->schema->objectClass()); + } + + return $this->grammar->compile($this); + } + + /** + * Returns the unescaped query. + * + * @return string + */ + public function getUnescapedQuery() + { + return Utilities::unescape($this->getQuery()); + } + + /** + * Returns the current Grammar instance. + * + * @return Grammar + */ + public function getGrammar() + { + return $this->grammar; + } + + /** + * Returns the current Connection instance. + * + * @return ConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Returns the builders DN to perform searches upon. + * + * @return string + */ + public function getDn() + { + return $this->dn; + } + + /** + * Sets the DN to perform searches upon. + * + * @param string|Model|null $dn + * + * @return Builder + */ + public function setDn($dn = null) + { + $this->dn = $dn instanceof Model ? $dn->getDn() : $dn; + + return $this; + } + + /** + * Alias for setting the base DN of the query. + * + * @param string|Model|null $dn + * + * @return Builder + */ + public function in($dn = null) + { + return $this->setDn($dn); + } + + /** + * Sets the size limit of the current query. + * + * @param int $limit + * + * @return Builder + */ + public function limit($limit = 0) + { + $this->limit = $limit; + + return $this; + } + + /** + * Performs the specified query on the current LDAP connection. + * + * @param string $query + * + * @return \Adldap\Query\Collection|array + */ + public function query($query) + { + $start = microtime(true); + + // Here we will create the execution callback. This allows us + // to only execute an LDAP request if caching is disabled + // or if no cache of the given query exists yet. + $callback = function () use ($query) { + return $this->parse($this->run($query)); + }; + + // If caching is enabled and we have a cache instance available, + // we will try to retrieve the cached results instead. + // Otherwise, we will simply execute the callback. + if ($this->caching && $this->cache) { + $results = $this->getCachedResponse($this->getCacheKey($query), $callback); + } else { + $results = $callback(); + } + + // Log the query. + $this->logQuery($this, $this->type, $this->getElapsedTime($start)); + + // Process & return the results. + return $this->newProcessor()->process($results); + } + + /** + * Paginates the current LDAP query. + * + * @param int $perPage + * @param int $currentPage + * @param bool $isCritical + * + * @return Paginator + */ + public function paginate($perPage = 1000, $currentPage = 0, $isCritical = true) + { + $this->paginated = true; + + $start = microtime(true); + + $query = $this->getQuery(); + + // Here we will create the pagination callback. This allows us + // to only execute an LDAP request if caching is disabled + // or if no cache of the given query exists yet. + $callback = function () use ($query, $perPage, $isCritical) { + return $this->runPaginate($query, $perPage, $isCritical); + }; + + // If caching is enabled and we have a cache instance available, + // we will try to retrieve the cached results instead. + if ($this->caching && $this->cache) { + $pages = $this->getCachedResponse($this->getCacheKey($query), $callback); + } else { + $pages = $callback(); + } + + // Log the query. + $this->logQuery($this, 'paginate', $this->getElapsedTime($start)); + + // Process & return the results. + return $this->newProcessor()->processPaginated($pages, $perPage, $currentPage); + } + + /** + * Get the cached response or execute and cache the callback value. + * + * @param string $key + * @param Closure $callback + * + * @return mixed + */ + protected function getCachedResponse($key, Closure $callback) + { + if ($this->flushCache) { + $this->cache->delete($key); + } + + return $this->cache->remember($key, $this->cacheUntil, $callback); + } + + /** + * Runs the query operation with the given filter. + * + * @param string $filter + * + * @return resource + */ + protected function run($filter) + { + return $this->connection->{$this->type}( + $this->getDn(), + $filter, + $this->getSelects(), + $onlyAttributes = false, + $this->limit + ); + } + + /** + * Runs the paginate operation with the given filter. + * + * @param string $filter + * @param int $perPage + * @param bool $isCritical + * + * @return array + */ + protected function runPaginate($filter, $perPage, $isCritical) + { + return $this->connection->supportsServerControlsInMethods() ? + $this->compatiblePaginationCallback($filter, $perPage, $isCritical) : + $this->deprecatedPaginationCallback($filter, $perPage, $isCritical); + } + + /** + * Create a deprecated pagination callback compatible with PHP 7.2. + * + * @param string $filter + * @param int $perPage + * @param bool $isCritical + * + * @return array + */ + protected function deprecatedPaginationCallback($filter, $perPage, $isCritical) + { + $pages = []; + + $cookie = ''; + + do { + $this->connection->controlPagedResult($perPage, $isCritical, $cookie); + + if (! $resource = $this->run($filter)) { + break; + } + + // If we have been given a valid resource, we will retrieve the next + // pagination cookie to send for our next pagination request. + $this->connection->controlPagedResultResponse($resource, $cookie); + + $pages[] = $this->parse($resource); + } while (!empty($cookie)); + + // Reset paged result on the current connection. We won't pass in the current $perPage + // parameter since we want to reset the page size to the default '1000'. Sending '0' + // eliminates any further opportunity for running queries in the same request, + // even though that is supposed to be the correct usage. + $this->connection->controlPagedResult(); + + return $pages; + } + + /** + * Create a compatible pagination callback compatible with PHP 7.3 and greater. + * + * @param string $filter + * @param int $perPage + * @param bool $isCritical + * + * @return array + */ + protected function compatiblePaginationCallback($filter, $perPage, $isCritical) + { + $pages = []; + + // Setup our paged results control. + $controls = [ + LDAP_CONTROL_PAGEDRESULTS => [ + 'oid' => LDAP_CONTROL_PAGEDRESULTS, + 'isCritical' => $isCritical, + 'value' => [ + 'size' => $perPage, + 'cookie' => '', + ], + ], + ]; + + do { + // Update the server controls. + $this->connection->setOption(LDAP_OPT_SERVER_CONTROLS, $controls); + + if (! $resource = $this->run($filter)) { + break; + } + + $errorCode = $dn = $errorMessage = $refs = null; + + // Update the server controls with the servers response. + $this->connection->parseResult($resource, $errorCode, $dn, $errorMessage, $refs, $controls); + + $pages[] = $this->parse($resource); + + // Reset paged result on the current connection. We won't pass in the current $perPage + // parameter since we want to reset the page size to the default '1000'. Sending '0' + // eliminates any further opportunity for running queries in the same request, + // even though that is supposed to be the correct usage. + $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['size'] = $perPage; + } while (!empty($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])); + + // After running the query, we will clear the LDAP server controls. This + // allows the controls to be automatically reset before each new query + // that is conducted on the same connection during each request. + $this->connection->setOption(LDAP_OPT_SERVER_CONTROLS, []); + + return $pages; + } + + /** + * Parses the given LDAP resource by retrieving its entries. + * + * @param resource $resource + * + * @return array + */ + protected function parse($resource) + { + // Normalize entries. Get entries returns false on failure. + // We'll always want an array in this situation. + $entries = $this->connection->getEntries($resource) ?: []; + + // Free up memory. + if (is_resource($resource)) { + $this->connection->freeResult($resource); + } + + return $entries; + } + + /** + * Returns the cache key. + * + * @param string $query + * + * @return string + */ + protected function getCacheKey($query) + { + $key = $this->connection->getHost() + .$this->type + .$this->getDn() + .$query + .implode('', $this->getSelects()) + .$this->limit + .$this->paginated; + + return md5($key); + } + + /** + * Returns the first entry in a search result. + * + * @param array|string $columns + * + * @return Model|array|null + */ + public function first($columns = []) + { + $results = $this->select($columns)->limit(1)->get(); + + // Since results may be returned inside an array if `raw()` + // is specified, then we'll use our array helper + // to retrieve the first result. + return Arr::get($results, 0); + } + + /** + * Returns the first entry in a search result. + * + * If no entry is found, an exception is thrown. + * + * @param array|string $columns + * + * @throws ModelNotFoundException + * + * @return Model|array + */ + public function firstOrFail($columns = []) + { + $record = $this->first($columns); + + if (!$record) { + throw (new ModelNotFoundException()) + ->setQuery($this->getUnescapedQuery(), $this->getDn()); + } + + return $record; + } + + /** + * Finds a record by the specified attribute and value. + * + * @param string $attribute + * @param string $value + * @param array|string $columns + * + * @return Model|array|false + */ + public function findBy($attribute, $value, $columns = []) + { + try { + return $this->findByOrFail($attribute, $value, $columns); + } catch (ModelNotFoundException $e) { + return; + } + } + + /** + * Finds a record by the specified attribute and value. + * + * If no record is found an exception is thrown. + * + * @param string $attribute + * @param string $value + * @param array|string $columns + * + * @throws ModelNotFoundException + * + * @return Model|array + */ + public function findByOrFail($attribute, $value, $columns = []) + { + return $this->whereEquals($attribute, $value)->firstOrFail($columns); + } + + /** + * Finds a record using ambiguous name resolution. + * + * @param string|array $value + * @param array|string $columns + * + * @return Model|array|null + */ + public function find($value, $columns = []) + { + if (is_array($value)) { + return $this->findMany($value, $columns); + } + + // If we're not using ActiveDirectory, we can't use ANR. We'll make our own query. + if (!is_a($this->schema, ActiveDirectory::class)) { + return $this->prepareAnrEquivalentQuery($value)->first($columns); + } + + return $this->findBy($this->schema->anr(), $value, $columns); + } + + /** + * Finds multiple records using ambiguous name resolution. + * + * @param array $values + * @param array $columns + * + * @return \Adldap\Query\Collection|array + */ + public function findMany(array $values = [], $columns = []) + { + $this->select($columns); + + if (!is_a($this->schema, ActiveDirectory::class)) { + $query = $this; + + foreach ($values as $value) { + $query->prepareAnrEquivalentQuery($value); + } + + return $query->get(); + } + + return $this->findManyBy($this->schema->anr(), $values); + } + + /** + * Creates an ANR equivalent query for LDAP distributions that do not support ANR. + * + * @param string $value + * + * @return Builder + */ + protected function prepareAnrEquivalentQuery($value) + { + return $this->orFilter(function (self $query) use ($value) { + $locateBy = [ + $this->schema->name(), + $this->schema->email(), + $this->schema->userId(), + $this->schema->lastName(), + $this->schema->firstName(), + $this->schema->commonName(), + $this->schema->displayName(), + ]; + + foreach ($locateBy as $attribute) { + $query->whereEquals($attribute, $value); + } + }); + } + + /** + * Finds many records by the specified attribute. + * + * @param string $attribute + * @param array $values + * @param array $columns + * + * @return \Adldap\Query\Collection|array + */ + public function findManyBy($attribute, array $values = [], $columns = []) + { + $query = $this->select($columns); + + foreach ($values as $value) { + $query->orWhere([$attribute => $value]); + } + + return $query->get(); + } + + /** + * Finds a record using ambiguous name resolution. + * + * If a record is not found, an exception is thrown. + * + * @param string $value + * @param array|string $columns + * + * @throws ModelNotFoundException + * + * @return Model|array + */ + public function findOrFail($value, $columns = []) + { + $entry = $this->find($value, $columns); + + // Make sure we check if the result is an entry or an array before + // we throw an exception in case the user wants raw results. + if (!$entry instanceof Model && !is_array($entry)) { + throw (new ModelNotFoundException()) + ->setQuery($this->getUnescapedQuery(), $this->getDn()); + } + + return $entry; + } + + /** + * Finds a record by its distinguished name. + * + * @param string $dn + * @param array|string $columns + * + * @return bool|Model + */ + public function findByDn($dn, $columns = []) + { + try { + return $this->findByDnOrFail($dn, $columns); + } catch (ModelNotFoundException $e) { + return; + } + } + + /** + * Finds a record by its distinguished name. + * + * Fails upon no records returned. + * + * @param string $dn + * @param array|string $columns + * + * @throws ModelNotFoundException + * + * @return Model|array + */ + public function findByDnOrFail($dn, $columns = []) + { + // Since we're setting our base DN to be able to retrieve a model + // by its distinguished name, we need to set it back to + // our configured base so it is not overwritten. + $base = $this->getDn(); + + $model = $this->setDn($dn) + ->read() + ->whereHas($this->schema->objectClass()) + ->firstOrFail($columns); + + // Reset the models query builder (in case a model is returned). + // Otherwise, we must be requesting a raw result. + if ($model instanceof Model) { + $model->setQuery($this->in($base)); + } + + return $model; + } + + /** + * Finds a record by its string GUID. + * + * @param string $guid + * @param array|string $columns + * + * @return Model|array|false + */ + public function findByGuid($guid, $columns = []) + { + try { + return $this->findByGuidOrFail($guid, $columns); + } catch (ModelNotFoundException $e) { + return; + } + } + + /** + * Finds a record by its string GUID. + * + * Fails upon no records returned. + * + * @param string $guid + * @param array|string $columns + * + * @throws ModelNotFoundException + * + * @return Model|array + */ + public function findByGuidOrFail($guid, $columns = []) + { + if ($this->schema->objectGuidRequiresConversion()) { + $guid = Utilities::stringGuidToHex($guid); + } + + return $this->select($columns)->whereRaw([ + $this->schema->objectGuid() => $guid, + ])->firstOrFail(); + } + + /** + * Finds a record by its Object SID. + * + * @param string $sid + * @param array|string $columns + * + * @return Model|array|false + */ + public function findBySid($sid, $columns = []) + { + try { + return $this->findBySidOrFail($sid, $columns); + } catch (ModelNotFoundException $e) { + return; + } + } + + /** + * Finds a record by its Object SID. + * + * Fails upon no records returned. + * + * @param string $sid + * @param array|string $columns + * + * @throws ModelNotFoundException + * + * @return Model|array + */ + public function findBySidOrFail($sid, $columns = []) + { + return $this->findByOrFail($this->schema->objectSid(), $sid, $columns); + } + + /** + * Finds the Base DN of your domain controller. + * + * @return string|bool + */ + public function findBaseDn() + { + $result = $this->setDn(null) + ->read() + ->raw() + ->whereHas($this->schema->objectClass()) + ->first(); + + $key = $this->schema->defaultNamingContext(); + + if (is_array($result) && array_key_exists($key, $result)) { + if (array_key_exists(0, $result[$key])) { + return $result[$key][0]; + } + } + + return false; + } + + /** + * Adds the inserted fields to query on the current LDAP connection. + * + * @param array|string $columns + * + * @return Builder + */ + public function select($columns = []) + { + $columns = is_array($columns) ? $columns : func_get_args(); + + if (!empty($columns)) { + $this->columns = $columns; + } + + return $this; + } + + /** + * Adds a raw filter to the current query. + * + * @param array|string $filters + * + * @return Builder + */ + public function rawFilter($filters = []) + { + $filters = is_array($filters) ? $filters : func_get_args(); + + foreach ($filters as $filter) { + $this->filters['raw'][] = $filter; + } + + return $this; + } + + /** + * Adds a nested 'and' filter to the current query. + * + * @param Closure $closure + * + * @return Builder + */ + public function andFilter(Closure $closure) + { + $query = $this->newNestedInstance($closure); + + $filter = $this->grammar->compileAnd($query->getQuery()); + + return $this->rawFilter($filter); + } + + /** + * Adds a nested 'or' filter to the current query. + * + * @param Closure $closure + * + * @return Builder + */ + public function orFilter(Closure $closure) + { + $query = $this->newNestedInstance($closure); + + $filter = $this->grammar->compileOr($query->getQuery()); + + return $this->rawFilter($filter); + } + + /** + * Adds a nested 'not' filter to the current query. + * + * @param Closure $closure + * + * @return Builder + */ + public function notFilter(Closure $closure) + { + $query = $this->newNestedInstance($closure); + + $filter = $this->grammar->compileNot($query->getQuery()); + + return $this->rawFilter($filter); + } + + /** + * Adds a where clause to the current query. + * + * @param string|array $field + * @param string $operator + * @param string $value + * @param string $boolean + * @param bool $raw + * + * @throws InvalidArgumentException + * + * @return Builder + */ + public function where($field, $operator = null, $value = null, $boolean = 'and', $raw = false) + { + if (is_array($field)) { + // If the column is an array, we will assume it is an array of + // key-value pairs and can add them each as a where clause. + return $this->addArrayOfWheres($field, $boolean, $raw); + } + + // We'll bypass the 'has' and 'notHas' operator since they + // only require two arguments inside the where method. + $bypass = [Operator::$has, Operator::$notHas]; + + // Here we will make some assumptions about the operator. If only + // 2 values are passed to the method, we will assume that + // the operator is 'equals' and keep going. + if (func_num_args() === 2 && in_array($operator, $bypass) === false) { + list($value, $operator) = [$operator, '=']; + } + + if (!in_array($operator, Operator::all())) { + throw new InvalidArgumentException("Invalid where operator: {$operator}"); + } + + // We'll escape the value if raw isn't requested. + $value = $raw ? $value : $this->escape($value); + + $field = $this->escape($field, $ignore = null, 3); + + $this->addFilter($boolean, compact('field', 'operator', 'value')); + + return $this; + } + + /** + * Adds a raw where clause to the current query. + * + * Values given to this method are not escaped. + * + * @param string|array $field + * @param string $operator + * @param string $value + * + * @return Builder + */ + public function whereRaw($field, $operator = null, $value = null) + { + return $this->where($field, $operator, $value, 'and', true); + } + + /** + * Adds a 'where equals' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function whereEquals($field, $value) + { + return $this->where($field, Operator::$equals, $value); + } + + /** + * Adds a 'where not equals' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function whereNotEquals($field, $value) + { + return $this->where($field, Operator::$doesNotEqual, $value); + } + + /** + * Adds a 'where approximately equals' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function whereApproximatelyEquals($field, $value) + { + return $this->where($field, Operator::$approximatelyEquals, $value); + } + + /** + * Adds a 'where has' clause to the current query. + * + * @param string $field + * + * @return Builder + */ + public function whereHas($field) + { + return $this->where($field, Operator::$has); + } + + /** + * Adds a 'where not has' clause to the current query. + * + * @param string $field + * + * @return Builder + */ + public function whereNotHas($field) + { + return $this->where($field, Operator::$notHas); + } + + /** + * Adds a 'where contains' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function whereContains($field, $value) + { + return $this->where($field, Operator::$contains, $value); + } + + /** + * Adds a 'where contains' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function whereNotContains($field, $value) + { + return $this->where($field, Operator::$notContains, $value); + } + + /** + * Query for entries that match any of the values provided for the given field. + * + * @param string $field + * @param array $values + * + * @return Builder + */ + public function whereIn($field, array $values) + { + return $this->orFilter(function (self $query) use ($field, $values) { + foreach ($values as $value) { + $query->whereEquals($field, $value); + } + }); + } + + /** + * Adds a 'between' clause to the current query. + * + * @param string $field + * @param array $values + * + * @return Builder + */ + public function whereBetween($field, array $values) + { + return $this->where([ + [$field, '>=', $values[0]], + [$field, '<=', $values[1]], + ]); + } + + /** + * Adds a 'where starts with' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function whereStartsWith($field, $value) + { + return $this->where($field, Operator::$startsWith, $value); + } + + /** + * Adds a 'where *not* starts with' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function whereNotStartsWith($field, $value) + { + return $this->where($field, Operator::$notStartsWith, $value); + } + + /** + * Adds a 'where ends with' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function whereEndsWith($field, $value) + { + return $this->where($field, Operator::$endsWith, $value); + } + + /** + * Adds a 'where *not* ends with' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function whereNotEndsWith($field, $value) + { + return $this->where($field, Operator::$notEndsWith, $value); + } + + /** + * Adds a enabled filter to the current query. + * + * @return Builder + */ + public function whereEnabled() + { + return $this->rawFilter($this->schema->filterEnabled()); + } + + /** + * Adds a disabled filter to the current query. + * + * @return Builder + */ + public function whereDisabled() + { + return $this->rawFilter($this->schema->filterDisabled()); + } + + /** + * Adds a 'member of' filter to the current query. + * + * @param string $dn + * + * @return Builder + */ + public function whereMemberOf($dn) + { + return $this->whereEquals($this->schema->memberOfRecursive(), $dn); + } + + /** + * Adds an 'or where' clause to the current query. + * + * @param array|string $field + * @param string|null $operator + * @param string|null $value + * + * @return Builder + */ + public function orWhere($field, $operator = null, $value = null) + { + return $this->where($field, $operator, $value, 'or'); + } + + /** + * Adds a raw or where clause to the current query. + * + * Values given to this method are not escaped. + * + * @param string $field + * @param string $operator + * @param string $value + * + * @return Builder + */ + public function orWhereRaw($field, $operator = null, $value = null) + { + return $this->where($field, $operator, $value, 'or', true); + } + + /** + * Adds an 'or where has' clause to the current query. + * + * @param string $field + * + * @return Builder + */ + public function orWhereHas($field) + { + return $this->orWhere($field, Operator::$has); + } + + /** + * Adds a 'where not has' clause to the current query. + * + * @param string $field + * + * @return Builder + */ + public function orWhereNotHas($field) + { + return $this->orWhere($field, Operator::$notHas); + } + + /** + * Adds an 'or where equals' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function orWhereEquals($field, $value) + { + return $this->orWhere($field, Operator::$equals, $value); + } + + /** + * Adds an 'or where not equals' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function orWhereNotEquals($field, $value) + { + return $this->orWhere($field, Operator::$doesNotEqual, $value); + } + + /** + * Adds a 'or where approximately equals' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function orWhereApproximatelyEquals($field, $value) + { + return $this->orWhere($field, Operator::$approximatelyEquals, $value); + } + + /** + * Adds an 'or where contains' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function orWhereContains($field, $value) + { + return $this->orWhere($field, Operator::$contains, $value); + } + + /** + * Adds an 'or where *not* contains' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function orWhereNotContains($field, $value) + { + return $this->orWhere($field, Operator::$notContains, $value); + } + + /** + * Adds an 'or where starts with' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function orWhereStartsWith($field, $value) + { + return $this->orWhere($field, Operator::$startsWith, $value); + } + + /** + * Adds an 'or where *not* starts with' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function orWhereNotStartsWith($field, $value) + { + return $this->orWhere($field, Operator::$notStartsWith, $value); + } + + /** + * Adds an 'or where ends with' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function orWhereEndsWith($field, $value) + { + return $this->orWhere($field, Operator::$endsWith, $value); + } + + /** + * Adds an 'or where *not* ends with' clause to the current query. + * + * @param string $field + * @param string $value + * + * @return Builder + */ + public function orWhereNotEndsWith($field, $value) + { + return $this->orWhere($field, Operator::$notEndsWith, $value); + } + + /** + * Adds an 'or where member of' filter to the current query. + * + * @param string $dn + * + * @return Builder + */ + public function orWhereMemberOf($dn) + { + return $this->orWhereEquals($this->schema->memberOfRecursive(), $dn); + } + + /** + * Adds a filter onto the current query. + * + * @param string $type The type of filter to add. + * @param array $bindings The bindings of the filter. + * + * @throws InvalidArgumentException + * + * @return $this + */ + public function addFilter($type, array $bindings) + { + // Here we will ensure we have been given a proper filter type. + if (!array_key_exists($type, $this->filters)) { + throw new InvalidArgumentException("Invalid filter type: {$type}."); + } + + // The required filter key bindings. + $required = ['field', 'operator', 'value']; + + // Here we will ensure the proper key bindings are given. + if (count(array_intersect_key(array_flip($required), $bindings)) !== count($required)) { + // Retrieve the keys that are missing in the bindings array. + $missing = implode(', ', array_diff($required, array_flip($bindings))); + + throw new InvalidArgumentException("Invalid filter bindings. Missing: {$missing} keys."); + } + + $this->filters[$type][] = $bindings; + + return $this; + } + + /** + * Clear the query builders filters. + * + * @return $this + */ + public function clearFilters() + { + foreach ($this->filters as $type => $filters) { + $this->filters[$type] = []; + } + + return $this; + } + + /** + * Returns true / false depending if the current object + * contains selects. + * + * @return bool + */ + public function hasSelects() + { + return count($this->getSelects()) > 0; + } + + /** + * Returns the current selected fields to retrieve. + * + * @return array + */ + public function getSelects() + { + $selects = $this->columns; + + // If the asterisk is not provided in the selected columns, we need to + // ensure we always select the object class and category, as these + // are used for constructing models. The asterisk indicates that + // we want all attributes returned for LDAP records. + if (!in_array('*', $selects)) { + $selects[] = $this->schema->objectCategory(); + $selects[] = $this->schema->objectClass(); + } + + return $selects; + } + + /** + * Sorts the LDAP search results by the specified field and direction. + * + * @param string $field + * @param string $direction + * @param int|null $flags + * + * @return Builder + */ + public function sortBy($field, $direction = 'asc', $flags = null) + { + $this->sortByField = $field; + + // Normalize direction. + $direction = strtolower($direction); + + if ($direction === 'asc' || $direction === 'desc') { + $this->sortByDirection = $direction; + } + + if (is_null($flags)) { + $this->sortByFlags = SORT_NATURAL + SORT_FLAG_CASE; + } + + return $this; + } + + /** + * Set the query to search on the base distinguished name. + * + * This will result in one record being returned. + * + * @return Builder + */ + public function read() + { + $this->type = 'read'; + + return $this; + } + + /** + * Set the query to search one level on the base distinguished name. + * + * @return Builder + */ + public function listing() + { + $this->type = 'listing'; + + return $this; + } + + /** + * Sets the query to search the entire directory on the base distinguished name. + * + * @return Builder + */ + public function recursive() + { + $this->type = 'search'; + + return $this; + } + + /** + * Whether to return the LDAP results in their raw format. + * + * @param bool $raw + * + * @return Builder + */ + public function raw($raw = true) + { + $this->raw = (bool) $raw; + + return $this; + } + + /** + * Whether the current query is nested. + * + * @param bool $nested + * + * @return Builder + */ + public function nested($nested = true) + { + $this->nested = (bool) $nested; + + return $this; + } + + /** + * Enables caching on the current query until the given date. + * + * If flushing is enabled, the query cache will be flushed and then re-cached. + * + * @param \DateTimeInterface $until When to expire the query cache. + * @param bool $flush Whether to force-flush the query cache. + * + * @return $this + */ + public function cache(\DateTimeInterface $until = null, $flush = false) + { + $this->caching = true; + $this->cacheUntil = $until; + $this->flushCache = $flush; + + return $this; + } + + /** + * Returns an escaped string for use in an LDAP filter. + * + * @param string $value + * @param string $ignore + * @param int $flags + * + * @return string + */ + public function escape($value, $ignore = '', $flags = 0) + { + return ldap_escape($value, $ignore, $flags); + } + + /** + * Returns the query builders sort by field. + * + * @return string + */ + public function getSortByField() + { + return $this->sortByField; + } + + /** + * Returns the query builders sort by direction. + * + * @return string + */ + public function getSortByDirection() + { + return $this->sortByDirection; + } + + /** + * Returns the query builders sort by flags. + * + * @return int + */ + public function getSortByFlags() + { + return $this->sortByFlags; + } + + /** + * Returns true / false if the current query is nested. + * + * @return bool + */ + public function isNested() + { + return $this->nested === true; + } + + /** + * Returns bool that determines whether the current + * query builder will return raw results. + * + * @return bool + */ + public function isRaw() + { + return $this->raw; + } + + /** + * Returns bool that determines whether the current + * query builder will return paginated results. + * + * @return bool + */ + public function isPaginated() + { + return $this->paginated; + } + + /** + * Returns bool that determines whether the current + * query builder will return sorted results. + * + * @return bool + */ + public function isSorted() + { + return $this->sortByField ? true : false; + } + + /** + * Handle dynamic method calls on the query builder object to be directed to the query processor. + * + * @param string $method + * @param array $parameters + * + * @return mixed + */ + public function __call($method, $parameters) + { + // We'll check if the beginning of the method being called contains + // 'where'. If so, we'll assume it's a dynamic 'where' clause. + if (substr($method, 0, 5) === 'where') { + return $this->dynamicWhere($method, $parameters); + } + + return call_user_func_array([$this->newProcessor(), $method], $parameters); + } + + /** + * Handles dynamic "where" clauses to the query. + * + * @param string $method + * @param array $parameters + * + * @return Builder + */ + public function dynamicWhere($method, $parameters) + { + $finder = substr($method, 5); + + $segments = preg_split('/(And|Or)(?=[A-Z])/', $finder, -1, PREG_SPLIT_DELIM_CAPTURE); + + // The connector variable will determine which connector will be used for the + // query condition. We will change it as we come across new boolean values + // in the dynamic method strings, which could contain a number of these. + $connector = 'and'; + + $index = 0; + + foreach ($segments as $segment) { + // If the segment is not a boolean connector, we can assume it is a column's name + // and we will add it to the query as a new constraint as a where clause, then + // we can keep iterating through the dynamic method string's segments again. + if ($segment != 'And' && $segment != 'Or') { + $this->addDynamic($segment, $connector, $parameters, $index); + + $index++; + } + + // Otherwise, we will store the connector so we know how the next where clause we + // find in the query should be connected to the previous ones, meaning we will + // have the proper boolean connector to connect the next where clause found. + else { + $connector = $segment; + } + } + + return $this; + } + + /** + * Adds an array of wheres to the current query. + * + * @param array $wheres + * @param string $boolean + * @param bool $raw + * + * @return Builder + */ + protected function addArrayOfWheres($wheres, $boolean, $raw) + { + foreach ($wheres as $key => $value) { + if (is_numeric($key) && is_array($value)) { + // If the key is numeric and the value is an array, we'll + // assume we've been given an array with conditionals. + list($field, $condition) = $value; + + // Since a value is optional for some conditionals, we will + // try and retrieve the third parameter from the array, + // but is entirely optional. + $value = Arr::get($value, 2); + + $this->where($field, $condition, $value, $boolean); + } else { + // If the value is not an array, we will assume an equals clause. + $this->where($key, Operator::$equals, $value, $boolean, $raw); + } + } + + return $this; + } + + /** + * Add a single dynamic where clause statement to the query. + * + * @param string $segment + * @param string $connector + * @param array $parameters + * @param int $index + * + * @return void + */ + protected function addDynamic($segment, $connector, $parameters, $index) + { + // We'll format the 'where' boolean and field here to avoid casing issues. + $bool = strtolower($connector); + $field = strtolower($segment); + + $this->where($field, '=', $parameters[$index], $bool); + } + + /** + * Logs the given executed query information by firing its query event. + * + * @param Builder $query + * @param string $type + * @param null|float $time + */ + protected function logQuery($query, $type, $time = null) + { + $args = [$query, $time]; + + switch ($type) { + case 'listing': + $event = new Events\Listing(...$args); + break; + case 'read': + $event = new Events\Read(...$args); + break; + case 'paginate': + $event = new Events\Paginate(...$args); + break; + default: + $event = new Events\Search(...$args); + break; + } + + $this->fireQueryEvent($event); + } + + /** + * Fires the given query event. + * + * @param QueryExecuted $event + */ + protected function fireQueryEvent(QueryExecuted $event) + { + Adldap::getEventDispatcher()->fire($event); + } + + /** + * Get the elapsed time since a given starting point. + * + * @param int $start + * + * @return float + */ + protected function getElapsedTime($start) + { + return round((microtime(true) - $start) * 1000, 2); + } + + /** + * Returns a new query Processor instance. + * + * @return Processor + */ + protected function newProcessor() + { + return new Processor($this); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Cache.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Cache.php new file mode 100644 index 0000000000..9b0206a021 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Cache.php @@ -0,0 +1,95 @@ +store = $store; + } + + /** + * Get an item from the cache. + * + * @param string $key + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * + * @return mixed + */ + public function get($key) + { + return $this->store->get($key); + } + + /** + * Store an item in the cache. + * + * @param string $key + * @param mixed $value + * @param \DateTimeInterface|\DateInterval|int|null $ttl + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * + * @return bool + */ + public function put($key, $value, $ttl = null) + { + return $this->store->set($key, $value, $ttl); + } + + /** + * Get an item from the cache, or execute the given Closure and store the result. + * + * @param string $key + * @param \DateTimeInterface|\DateInterval|int|null $ttl + * @param Closure $callback + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * + * @return mixed + */ + public function remember($key, $ttl, Closure $callback) + { + $value = $this->get($key); + + if (!is_null($value)) { + return $value; + } + + $this->put($key, $value = $callback(), $ttl); + + return $value; + } + + /** + * Delete an item from the cache. + * + * @param string $key + * + * @throws \Psr\Cache\InvalidArgumentException + * @throws \Psr\SimpleCache\InvalidArgumentException + * + * @return bool + */ + public function delete($key) + { + return $this->store->delete($key); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Collection.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Collection.php new file mode 100644 index 0000000000..b9b6616ef0 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Collection.php @@ -0,0 +1,27 @@ +useAsCallable($value)) { + return $value; + } + + return function ($item) use ($value) { + if ($item instanceof Model) { + return $item->getFirstAttribute($value); + } + + return data_get($item, $value); + }; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Events/Listing.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Events/Listing.php new file mode 100644 index 0000000000..0ca05b77a5 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Events/Listing.php @@ -0,0 +1,8 @@ +query = $query; + $this->time = $time; + } + + /** + * Returns the LDAP filter that was used for the query. + * + * @return Builder + */ + public function getQuery() + { + return $this->query; + } + + /** + * Returns the number of milliseconds it took to execute the query. + * + * @return float|null + */ + public function getTime() + { + return $this->time; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Events/Read.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Events/Read.php new file mode 100644 index 0000000000..4e0e65de96 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Events/Read.php @@ -0,0 +1,8 @@ +setConnection($connection) + ->setSchema($schema) + ->setBaseDn($baseDn); + } + + /** + * Sets the connection property. + * + * @param ConnectionInterface $connection + * + * @return $this + */ + public function setConnection(ConnectionInterface $connection) + { + $this->connection = $connection; + + return $this; + } + + /** + * Sets the schema property. + * + * @param SchemaInterface|null $schema + * + * @return $this + */ + public function setSchema(SchemaInterface $schema = null) + { + $this->schema = $schema ?: new ActiveDirectory(); + + return $this; + } + + /** + * Sets the base distinguished name to perform searches upon. + * + * @param string $base + * + * @return $this + */ + public function setBaseDn($base = '') + { + $this->base = $base; + + return $this; + } + + /** + * Sets the cache for storing query results. + * + * @param Cache $cache + * + * @return $this + */ + public function setCache(Cache $cache) + { + $this->cache = $cache; + + return $this; + } + + /** + * Returns a new query builder instance. + * + * @return Builder + */ + public function newQuery() + { + return $this->newBuilder()->in($this->base); + } + + /** + * Performs a global 'all' search query on the current + * connection by performing a search for all entries + * that contain a common name attribute. + * + * @return \Adldap\Query\Collection|array + */ + public function get() + { + return $this->newQuery()->whereHas($this->schema->commonName())->get(); + } + + /** + * Returns a query builder scoped to users. + * + * @return Builder + */ + public function users() + { + $wheres = [ + [$this->schema->objectClass(), Operator::$equals, $this->schema->objectClassUser()], + [$this->schema->objectCategory(), Operator::$equals, $this->schema->objectCategoryPerson()], + ]; + + // OpenLDAP doesn't like specifying the omission of user objectclasses + // equal to `contact`. We'll make sure we're working with + // ActiveDirectory before adding this filter. + if (is_a($this->schema, ActiveDirectory::class)) { + $wheres[] = [$this->schema->objectClass(), Operator::$doesNotEqual, $this->schema->objectClassContact()]; + } + + return $this->where($wheres); + } + + /** + * Returns a query builder scoped to printers. + * + * @return Builder + */ + public function printers() + { + return $this->where([ + $this->schema->objectClass() => $this->schema->objectClassPrinter(), + ]); + } + + /** + * Returns a query builder scoped to organizational units. + * + * @return Builder + */ + public function ous() + { + return $this->where([ + $this->schema->objectClass() => $this->schema->objectClassOu(), + ]); + } + + /** + * Returns a query builder scoped to organizations. + * + * @return Builder + */ + public function organizations() + { + return $this->where([ + $this->schema->objectClass() => $this->schema->objectClassOrganization(), + ]); + } + + /** + * Returns a query builder scoped to groups. + * + * @return Builder + */ + public function groups() + { + return $this->where([ + $this->schema->objectClass() => $this->schema->objectClassGroup(), + ]); + } + + /** + * Returns a query builder scoped to containers. + * + * @return Builder + */ + public function containers() + { + return $this->where([ + $this->schema->objectClass() => $this->schema->objectClassContainer(), + ]); + } + + /** + * Returns a query builder scoped to contacts. + * + * @return Builder + */ + public function contacts() + { + return $this->where([ + $this->schema->objectClass() => $this->schema->objectClassContact(), + ]); + } + + /** + * Returns a query builder scoped to computers. + * + * @return Builder + */ + public function computers() + { + return $this->where([ + $this->schema->objectClass() => $this->schema->objectClassComputer(), + ]); + } + + /** + * Returns the root DSE record. + * + * @return RootDse|null + */ + public function getRootDse() + { + $query = $this->newQuery(); + + $root = $query->in('')->read()->whereHas($this->schema->objectClass())->first(); + + if ($root) { + return (new RootDse([], $query)) + ->setRawAttributes($root->getAttributes()); + } + } + + /** + * Handle dynamic method calls on the query builder object. + * + * @param string $method + * @param array $parameters + * + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array([$this->newQuery(), $method], $parameters); + } + + /** + * Returns a new query grammar instance. + * + * @return Grammar + */ + protected function newGrammar() + { + return new Grammar(); + } + + /** + * Returns a new query builder instance. + * + * @return Builder + */ + protected function newBuilder() + { + $builder = new Builder($this->connection, $this->newGrammar(), $this->schema); + + $builder->setCache($this->cache); + + return $builder; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Grammar.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Grammar.php new file mode 100644 index 0000000000..dcda3d7337 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Grammar.php @@ -0,0 +1,390 @@ +filters['and']; + $ors = $builder->filters['or']; + $raws = $builder->filters['raw']; + + $query = $this->concatenate($raws); + + $query = $this->compileWheres($ands, $query); + + $query = $this->compileOrWheres($ors, $query); + + // We need to check if the query is already nested, otherwise + // we'll nest it here and return the result. + if (!$builder->isNested()) { + $total = count($ands) + count($raws); + + // Make sure we wrap the query in an 'and' if using + // multiple filters. We also need to check if only + // one where is used with multiple orWheres, that + // we wrap it in an `and` query. + if ($total > 1 || (count($ands) === 1 && count($ors) > 0)) { + $query = $this->compileAnd($query); + } + } + + return $query; + } + + /** + * Concatenates filters into a single string. + * + * @param array $bindings + * + * @return string + */ + public function concatenate(array $bindings = []) + { + // Filter out empty query segments. + $bindings = array_filter($bindings, function ($value) { + return (string) $value !== ''; + }); + + return implode('', $bindings); + } + + /** + * Returns a query string for equals. + * + * Produces: (field=value) + * + * @param string $field + * @param string $value + * + * @return string + */ + public function compileEquals($field, $value) + { + return $this->wrap($field.Operator::$equals.$value); + } + + /** + * Returns a query string for does not equal. + * + * Produces: (!(field=value)) + * + * @param string $field + * @param string $value + * + * @return string + */ + public function compileDoesNotEqual($field, $value) + { + return $this->compileNot($this->compileEquals($field, $value)); + } + + /** + * Alias for does not equal operator (!=) operator. + * + * Produces: (!(field=value)) + * + * @param string $field + * @param string $value + * + * @return string + */ + public function compileDoesNotEqualAlias($field, $value) + { + return $this->compileDoesNotEqual($field, $value); + } + + /** + * Returns a query string for greater than or equals. + * + * Produces: (field>=value) + * + * @param string $field + * @param string $value + * + * @return string + */ + public function compileGreaterThanOrEquals($field, $value) + { + return $this->wrap($field.Operator::$greaterThanOrEquals.$value); + } + + /** + * Returns a query string for less than or equals. + * + * Produces: (field<=value) + * + * @param string $field + * @param string $value + * + * @return string + */ + public function compileLessThanOrEquals($field, $value) + { + return $this->wrap($field.Operator::$lessThanOrEquals.$value); + } + + /** + * Returns a query string for approximately equals. + * + * Produces: (field~=value) + * + * @param string $field + * @param string $value + * + * @return string + */ + public function compileApproximatelyEquals($field, $value) + { + return $this->wrap($field.Operator::$approximatelyEquals.$value); + } + + /** + * Returns a query string for starts with. + * + * Produces: (field=value*) + * + * @param string $field + * @param string $value + * + * @return string + */ + public function compileStartsWith($field, $value) + { + return $this->wrap($field.Operator::$equals.$value.Operator::$has); + } + + /** + * Returns a query string for does not start with. + * + * Produces: (!(field=*value)) + * + * @param string $field + * @param string $value + * + * @return string + */ + public function compileNotStartsWith($field, $value) + { + return $this->compileNot($this->compileStartsWith($field, $value)); + } + + /** + * Returns a query string for ends with. + * + * Produces: (field=*value) + * + * @param string $field + * @param string $value + * + * @return string + */ + public function compileEndsWith($field, $value) + { + return $this->wrap($field.Operator::$equals.Operator::$has.$value); + } + + /** + * Returns a query string for does not end with. + * + * Produces: (!(field=value*)) + * + * @param string $field + * @param string $value + * + * @return string + */ + public function compileNotEndsWith($field, $value) + { + return $this->compileNot($this->compileEndsWith($field, $value)); + } + + /** + * Returns a query string for contains. + * + * Produces: (field=*value*) + * + * @param string $field + * @param string $value + * + * @return string + */ + public function compileContains($field, $value) + { + return $this->wrap($field.Operator::$equals.Operator::$has.$value.Operator::$has); + } + + /** + * Returns a query string for does not contain. + * + * Produces: (!(field=*value*)) + * + * @param string $field + * @param string $value + * + * @return string + */ + public function compileNotContains($field, $value) + { + return $this->compileNot($this->compileContains($field, $value)); + } + + /** + * Returns a query string for a where has. + * + * Produces: (field=*) + * + * @param string $field + * + * @return string + */ + public function compileHas($field) + { + return $this->wrap($field.Operator::$equals.Operator::$has); + } + + /** + * Returns a query string for a where does not have. + * + * Produces: (!(field=*)) + * + * @param string $field + * + * @return string + */ + public function compileNotHas($field) + { + return $this->compileNot($this->compileHas($field)); + } + + /** + * Wraps the inserted query inside an AND operator. + * + * Produces: (&query) + * + * @param string $query + * + * @return string + */ + public function compileAnd($query) + { + return $query ? $this->wrap($query, '(&') : ''; + } + + /** + * Wraps the inserted query inside an OR operator. + * + * Produces: (|query) + * + * @param string $query + * + * @return string + */ + public function compileOr($query) + { + return $query ? $this->wrap($query, '(|') : ''; + } + + /** + * Wraps the inserted query inside an NOT operator. + * + * @param string $query + * + * @return string + */ + public function compileNot($query) + { + return $query ? $this->wrap($query, '(!') : ''; + } + + /** + * Assembles all where clauses in the current wheres property. + * + * @param array $wheres + * @param string $query + * + * @return string + */ + protected function compileWheres(array $wheres = [], $query = '') + { + foreach ($wheres as $where) { + $query .= $this->compileWhere($where); + } + + return $query; + } + + /** + * Assembles all or where clauses in the current orWheres property. + * + * @param array $orWheres + * @param string $query + * + * @return string + */ + protected function compileOrWheres(array $orWheres = [], $query = '') + { + $or = ''; + + foreach ($orWheres as $where) { + $or .= $this->compileWhere($where); + } + + // Make sure we wrap the query in an 'or' if using multiple + // orWheres. For example (|(QUERY)(ORWHEREQUERY)). + if (($query && count($orWheres) > 0) || count($orWheres) > 1) { + $query .= $this->compileOr($or); + } else { + $query .= $or; + } + + return $query; + } + + /** + * Assembles a single where query based + * on its operator and returns it. + * + * @param array $where + * + * @return string|null + */ + protected function compileWhere(array $where) + { + // Get the name of the operator. + if ($name = array_search($where['operator'], Operator::all())) { + // If the name was found we'll camel case it + // to run it through the compile method. + $method = 'compile'.ucfirst($name); + + // Make sure the compile method exists for the operator. + if (method_exists($this, $method)) { + return $this->{$method}($where['field'], $where['value']); + } + } + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Operator.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Operator.php new file mode 100644 index 0000000000..75b32890b8 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Operator.php @@ -0,0 +1,116 @@ +='; + + /** + * The less than or equal to operator. + * + * @var string + */ + public static $lessThanOrEquals = '<='; + + /** + * The approximately equal to operator. + * + * @var string + */ + public static $approximatelyEquals = '~='; + + /** + * The custom starts with operator. + * + * @var string + */ + public static $startsWith = 'starts_with'; + + /** + * The custom not starts with operator. + * + * @var string + */ + public static $notStartsWith = 'not_starts_with'; + + /** + * The custom ends with operator. + * + * @var string + */ + public static $endsWith = 'ends_with'; + + /** + * The custom not ends with operator. + * + * @var string + */ + public static $notEndsWith = 'not_ends_with'; + + /** + * The custom contains operator. + * + * @var string + */ + public static $contains = 'contains'; + + /** + * The custom not contains operator. + * + * @var string + */ + public static $notContains = 'not_contains'; + + /** + * Returns all available operators. + * + * @return array + */ + public static function all() + { + return (new ReflectionClass(new static()))->getStaticProperties(); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Paginator.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Paginator.php new file mode 100644 index 0000000000..0f61fe232f --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Paginator.php @@ -0,0 +1,206 @@ +setResults($results) + ->setPerPage($perPage) + ->setCurrentPage($currentPage) + ->setPages($pages) + ->setCurrentOffset(($this->getCurrentPage() * $this->getPerPage())); + } + + /** + * Get an iterator for the entries. + * + * @return ArrayIterator + */ + public function getIterator() + { + $entries = array_slice($this->getResults(), $this->getCurrentOffset(), $this->getPerPage(), true); + + return new ArrayIterator($entries); + } + + /** + * Returns the complete results array. + * + * @return array + */ + public function getResults() + { + return $this->results; + } + + /** + * Returns the total amount of pages + * in a paginated result. + * + * @return int + */ + public function getPages() + { + return $this->pages; + } + + /** + * Returns the total amount of entries + * allowed per page. + * + * @return int + */ + public function getPerPage() + { + return $this->perPage; + } + + /** + * Returns the current page number. + * + * @return int + */ + public function getCurrentPage() + { + return $this->currentPage; + } + + /** + * Returns the current offset number. + * + * @return int + */ + public function getCurrentOffset() + { + return $this->currentOffset; + } + + /** + * Returns the total amount of results. + * + * @return int + */ + public function count() + { + return count($this->results); + } + + /** + * Sets the results array property. + * + * @param array $results + * + * @return Paginator + */ + protected function setResults(array $results) + { + $this->results = $results; + + return $this; + } + + /** + * Sets the total number of pages. + * + * @param int $pages + * + * @return Paginator + */ + protected function setPages($pages = 0) + { + $this->pages = (int) $pages; + + return $this; + } + + /** + * Sets the number of entries per page. + * + * @param int $perPage + * + * @return Paginator + */ + protected function setPerPage($perPage = 50) + { + $this->perPage = (int) $perPage; + + return $this; + } + + /** + * Sets the current page number. + * + * @param int $currentPage + * + * @return Paginator + */ + protected function setCurrentPage($currentPage = 0) + { + $this->currentPage = (int) $currentPage; + + return $this; + } + + /** + * Sets the current offset number. + * + * @param int $offset + * + * @return Paginator + */ + protected function setCurrentOffset($offset = 0) + { + $this->currentOffset = (int) $offset; + + return $this; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Processor.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Processor.php new file mode 100644 index 0000000000..e4e5329c14 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Processor.php @@ -0,0 +1,211 @@ +builder = $builder; + $this->schema = $builder->getSchema(); + $this->connection = $builder->getConnection(); + } + + /** + * Processes LDAP search results and constructs their model instances. + * + * @param array $entries The LDAP entries to process. + * + * @return Collection|array + */ + public function process($entries) + { + if ($this->builder->isRaw()) { + // If the builder is asking for a raw + // LDAP result, we can return here. + return $entries; + } + + $models = []; + + if (array_key_exists('count', $entries)) { + for ($i = 0; $i < $entries['count']; $i++) { + // We'll go through each entry and construct a new + // model instance with the raw LDAP attributes. + $models[] = $this->newLdapEntry($entries[$i]); + } + } + + // If the query contains paginated results, we'll return them here. + if ($this->builder->isPaginated()) { + return $models; + } + + // If the query is requested to be sorted, we'll perform + // that here and return the resulting collection. + if ($this->builder->isSorted()) { + return $this->processSort($models); + } + + // Otherwise, we'll return a regular unsorted collection. + return $this->newCollection($models); + } + + /** + * Processes paginated LDAP results. + * + * @param array $pages + * @param int $perPage + * @param int $currentPage + * + * @return Paginator + */ + public function processPaginated(array $pages = [], $perPage = 50, $currentPage = 0) + { + $models = []; + + foreach ($pages as $entries) { + // Go through each page and process the results into an objects array. + $models = array_merge($models, $this->process($entries)); + } + + $models = $this->processSort($models)->toArray(); + + return $this->newPaginator($models, $perPage, $currentPage, count($pages)); + } + + /** + * Returns a new LDAP Entry instance. + * + * @param array $attributes + * + * @return Entry + */ + public function newLdapEntry(array $attributes = []) + { + $objectClass = $this->schema->objectClass(); + + // We need to ensure the record contains an object class to be able to + // determine its type. Otherwise, we create a default Entry model. + if (array_key_exists($objectClass, $attributes) && array_key_exists(0, $attributes[$objectClass])) { + // Retrieve all of the object classes from the LDAP + // entry and lowercase them for comparisons. + $classes = array_map('strtolower', $attributes[$objectClass]); + + // Retrieve the model mapping. + $models = $this->schema->objectClassModelMap(); + + // Retrieve the object class mappings (with strtolower keys). + $mappings = array_map('strtolower', array_keys($models)); + + // Retrieve the model from the map using the entry's object class. + $map = array_intersect($mappings, $classes); + + if (count($map) > 0) { + // Retrieve the model using the object class. + $model = $models[current($map)]; + + // Construct and return a new model. + return $this->newModel([], $model) + ->setRawAttributes($attributes); + } + } + + // A default entry model if the object class isn't found. + return $this->newModel()->setRawAttributes($attributes); + } + + /** + * Creates a new model instance. + * + * @param array $attributes + * @param string|null $model + * + * @throws InvalidArgumentException + * + * @return mixed|Entry + */ + public function newModel($attributes = [], $model = null) + { + $model = (class_exists($model) ? $model : $this->schema->entryModel()); + + if (!is_subclass_of($model, $base = Model::class)) { + throw new InvalidArgumentException("The given model class '{$model}' must extend the base model class '{$base}'"); + } + + return new $model($attributes, $this->builder->newInstance()); + } + + /** + * Returns a new Paginator object instance. + * + * @param array $models + * @param int $perPage + * @param int $currentPage + * @param int $pages + * + * @return Paginator + */ + public function newPaginator(array $models = [], $perPage = 25, $currentPage = 0, $pages = 1) + { + return new Paginator($models, $perPage, $currentPage, $pages); + } + + /** + * Returns a new collection instance. + * + * @param array $items + * + * @return Collection + */ + public function newCollection(array $items = []) + { + return new Collection($items); + } + + /** + * Sorts LDAP search results. + * + * @param array $models + * + * @return Collection + */ + protected function processSort(array $models = []) + { + $field = $this->builder->getSortByField(); + + $flags = $this->builder->getSortByFlags(); + + $direction = $this->builder->getSortByDirection(); + + $desc = ($direction === 'desc' ? true : false); + + return $this->newCollection($models)->sortBy($field, $flags, $desc); + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/ActiveDirectory.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/ActiveDirectory.php new file mode 100644 index 0000000000..09f03149ed --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/ActiveDirectory.php @@ -0,0 +1,94 @@ +lockoutTime()); + } + + /** + * {@inheritdoc} + */ + public function filterDisabled() + { + return sprintf('(%s=*)', $this->lockoutTime()); + } + + /** + * {@inheritdoc} + */ + public function lockoutTime() + { + return 'pwdAccountLockedTime'; + } + + /** + * {@inheritdoc} + */ + public function objectCategory() + { + return 'objectclass'; + } + + /** + * {@inheritdoc} + */ + public function objectClassGroup() + { + return 'groupofnames'; + } + + /** + * {@inheritdoc} + */ + public function objectClassOu() + { + return 'organizationalUnit'; + } + + /** + * {@inheritdoc} + */ + public function objectClassPerson() + { + return 'inetorgperson'; + } + + /** + * {@inheritdoc} + */ + public function objectClassUser() + { + return 'inetorgperson'; + } + + /** + * {@inheritdoc} + */ + public function objectGuid() + { + return 'nsuniqueid'; + } + + /** + * {@inheritdoc} + */ + public function objectGuidRequiresConversion() + { + return false; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/EDirectory.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/EDirectory.php new file mode 100644 index 0000000000..db31011715 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/EDirectory.php @@ -0,0 +1,110 @@ +lockoutTime()); + } + + /** + * {@inheritdoc} + */ + public function filterDisabled() + { + return sprintf('(%s=*)', $this->lockoutTime()); + } + + /** + * {@inheritdoc} + */ + public function lockoutTime() + { + return 'pwdAccountLockedTime'; + } + + /** + * {@inheritdoc} + */ + public function objectCategory() + { + return 'objectclass'; + } + + /** + * {@inheritdoc} + */ + public function objectClassGroup() + { + return 'groupofnames'; + } + + /** + * {@inheritdoc} + */ + public function objectClassOu() + { + return 'organizationalUnit'; + } + + /** + * {@inheritdoc} + */ + public function objectClassPerson() + { + return 'inetorgperson'; + } + + /** + * {@inheritdoc} + */ + public function objectClassUser() + { + return 'inetorgperson'; + } + + /** + * {@inheritdoc} + */ + public function objectGuid() + { + return 'guid'; + } + + /** + * {@inheritdoc} + */ + public function objectGuidRequiresConversion() + { + return false; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/FreeIPA.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/FreeIPA.php new file mode 100644 index 0000000000..410f6f9209 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/FreeIPA.php @@ -0,0 +1,126 @@ +lockoutTime()); + } + + /** + * {@inheritdoc} + */ + public function filterDisabled() + { + return sprintf('(%s=*)', $this->lockoutTime()); + } + + /** + * {@inheritdoc} + */ + public function lockoutTime() + { + return 'pwdAccountLockedTime'; + } + + /** + * {@inheritdoc} + */ + public function objectCategory() + { + return 'objectclass'; + } + + /** + * {@inheritdoc} + */ + public function objectClassGroup() + { + return 'groupofnames'; + } + + /** + * {@inheritdoc} + */ + public function objectClassOu() + { + return 'organizationalUnit'; + } + + /** + * {@inheritdoc} + */ + public function objectClassPerson() + { + return 'inetorgperson'; + } + + /** + * {@inheritdoc} + */ + public function objectClassUser() + { + return 'inetorgperson'; + } + + /** + * {@inheritdoc} + */ + public function objectGuid() + { + return 'entryuuid'; + } + + /** + * {@inheritdoc} + */ + public function objectGuidRequiresConversion() + { + return false; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/Schema.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/Schema.php new file mode 100644 index 0000000000..ac76452382 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/Schema.php @@ -0,0 +1,1264 @@ +member().";range={$from}-{$to}"; + } + + /** + * {@inheritdoc} + */ + public function messageTrackingEnabled() + { + return 'messagetrackingenabled'; + } + + /** + * {@inheritdoc} + */ + public function msExchangeServer() + { + return 'ms-exch-exchange-server'; + } + + /** + * {@inheritdoc} + */ + public function name() + { + return 'name'; + } + + /** + * {@inheritdoc} + */ + public function neverExpiresDate() + { + return '9223372036854775807'; + } + + /** + * {@inheritdoc} + */ + public function objectCategoryComputer() + { + return 'computer'; + } + + /** + * {@inheritdoc} + */ + public function objectCategoryContainer() + { + return 'container'; + } + + /** + * {@inheritdoc} + */ + public function objectCategoryExchangePrivateMdb() + { + return 'msexchprivatemdb'; + } + + /** + * {@inheritdoc} + */ + public function objectCategoryExchangeServer() + { + return 'msExchExchangeServer'; + } + + /** + * {@inheritdoc} + */ + public function objectCategoryExchangeStorageGroup() + { + return 'msExchStorageGroup'; + } + + /** + * {@inheritdoc} + */ + public function objectCategoryGroup() + { + return 'group'; + } + + /** + * {@inheritdoc} + */ + public function objectCategoryOrganizationalUnit() + { + return 'organizational-unit'; + } + + /** + * {@inheritdoc} + */ + public function objectCategoryPerson() + { + return 'person'; + } + + /** + * {@inheritdoc} + */ + public function objectCategoryPrinter() + { + return 'print-queue'; + } + + /** + * {@inheritdoc} + */ + public function objectClass() + { + return 'objectclass'; + } + + /** + * {@inheritdoc} + */ + public function objectClassComputer() + { + return 'computer'; + } + + /** + * {@inheritdoc} + */ + public function objectClassContact() + { + return 'contact'; + } + + /** + * {@inheritdoc} + */ + public function objectClassContainer() + { + return 'container'; + } + + /** + * {@inheritdoc} + */ + public function objectClassOrganization() + { + return 'organization'; + } + + /** + * {@inheritdoc} + */ + public function objectClassPrinter() + { + return 'printqueue'; + } + + /** + * {@inheritdoc} + */ + public function objectClassUser() + { + return 'user'; + } + + /** + * {@inheritdoc} + */ + public function objectClassForeignSecurityPrincipal() + { + return 'foreignsecurityprincipal'; + } + + /** + * {@inheritdoc} + */ + public function objectClassModelMap() + { + return [ + $this->objectClassComputer() => $this->computerModel(), + $this->objectClassContact() => $this->contactModel(), + $this->objectClassPerson() => $this->userModel(), + $this->objectClassGroup() => $this->groupModel(), + $this->objectClassContainer() => $this->containerModel(), + $this->objectClassPrinter() => $this->printerModel(), + $this->objectClassOrganization() => $this->organizationModel(), + $this->objectClassOu() => $this->organizationalUnitModel(), + $this->objectClassForeignSecurityPrincipal() => $this->foreignSecurityPrincipalModel(), + ]; + } + + /** + * {@inheritdoc} + */ + public function objectSid() + { + return 'objectsid'; + } + + /** + * {@inheritdoc} + */ + public function objectSidRequiresConversion() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function operatingSystem() + { + return 'operatingsystem'; + } + + /** + * {@inheritdoc} + */ + public function operatingSystemServicePack() + { + return 'operatingsystemservicepack'; + } + + /** + * {@inheritdoc} + */ + public function operatingSystemVersion() + { + return 'operatingsystemversion'; + } + + /** + * {@inheritdoc} + */ + public function organization() + { + return 'organization'; + } + + /** + * {@inheritdoc} + */ + public function organizationName() + { + return 'o'; + } + + /** + * {@inheritdoc} + */ + public function organizationalPerson() + { + return 'organizationalperson'; + } + + /** + * {@inheritdoc} + */ + public function organizationalUnit() + { + return 'organizationalunit'; + } + + /** + * {@inheritdoc} + */ + public function organizationalUnitModel() + { + return OrganizationalUnit::class; + } + + /** + * {@inheritdoc} + */ + public function organizationModel() + { + return Organization::class; + } + + /** + * {@inheritdoc} + */ + public function organizationalUnitShort() + { + return 'ou'; + } + + /** + * {@inheritdoc} + */ + public function otherMailbox() + { + return 'othermailbox'; + } + + /** + * {@inheritdoc} + */ + public function passwordLastSet() + { + return 'pwdlastset'; + } + + /** + * {@inheritdoc} + */ + public function person() + { + return 'person'; + } + + /** + * {@inheritdoc} + */ + public function personalTitle() + { + return 'personaltitle'; + } + + /** + * {@inheritdoc} + */ + public function physicalDeliveryOfficeName() + { + return 'physicaldeliveryofficename'; + } + + /** + * {@inheritdoc} + */ + public function portName() + { + return 'portname'; + } + + /** + * {@inheritdoc} + */ + public function postalCode() + { + return 'postalcode'; + } + + /** + * {@inheritdoc} + */ + public function postOfficeBox() + { + return 'postofficebox'; + } + + /** + * {@inheritdoc} + */ + public function primaryGroupId() + { + return 'primarygroupid'; + } + + /** + * {@inheritdoc} + */ + public function printerBinNames() + { + return 'printbinnames'; + } + + /** + * {@inheritdoc} + */ + public function printerColorSupported() + { + return 'printcolor'; + } + + /** + * {@inheritdoc} + */ + public function printerDuplexSupported() + { + return 'printduplexsupported'; + } + + /** + * {@inheritdoc} + */ + public function printerEndTime() + { + return 'printendtime'; + } + + /** + * {@inheritdoc} + */ + public function printerMaxResolutionSupported() + { + return 'printmaxresolutionsupported'; + } + + /** + * {@inheritdoc} + */ + public function printerMediaSupported() + { + return 'printmediasupported'; + } + + /** + * {@inheritdoc} + */ + public function printerMemory() + { + return 'printmemory'; + } + + /** + * {@inheritdoc} + */ + public function printerModel() + { + return Printer::class; + } + + /** + * {@inheritdoc} + */ + public function printerName() + { + return 'printername'; + } + + /** + * {@inheritdoc} + */ + public function printerOrientationSupported() + { + return 'printorientationssupported'; + } + + /** + * {@inheritdoc} + */ + public function printerPrintRate() + { + return 'printrate'; + } + + /** + * {@inheritdoc} + */ + public function printerPrintRateUnit() + { + return 'printrateunit'; + } + + /** + * {@inheritdoc} + */ + public function printerShareName() + { + return 'printsharename'; + } + + /** + * {@inheritdoc} + */ + public function printerStaplingSupported() + { + return 'printstaplingsupported'; + } + + /** + * {@inheritdoc} + */ + public function printerStartTime() + { + return 'printstarttime'; + } + + /** + * {@inheritdoc} + */ + public function priority() + { + return 'priority'; + } + + /** + * {@inheritdoc} + */ + public function profilePath() + { + return 'profilepath'; + } + + /** + * {@inheritdoc} + */ + public function proxyAddresses() + { + return 'proxyaddresses'; + } + + /** + * {@inheritdoc} + */ + public function roomNumber() + { + return 'roomnumber'; + } + + /** + * {@inheritdoc} + */ + public function rootDomainNamingContext() + { + return 'rootdomainnamingcontext'; + } + + /** + * {@inheritdoc} + */ + public function schemaNamingContext() + { + return 'schemanamingcontext'; + } + + /** + * {@inheritdoc} + */ + public function scriptPath() + { + return 'scriptpath'; + } + + /** + * {@inheritdoc} + */ + public function serialNumber() + { + return 'serialnumber'; + } + + /** + * {@inheritdoc} + */ + public function serverName() + { + return 'servername'; + } + + /** + * {@inheritdoc} + */ + public function showInAddressBook() + { + return 'showinaddressbook'; + } + + /** + * {@inheritdoc} + */ + public function street() + { + return 'street'; + } + + /** + * {@inheritdoc} + */ + public function streetAddress() + { + return 'streetaddress'; + } + + /** + * {@inheritdoc} + */ + public function systemFlags() + { + return 'systemflags'; + } + + /** + * {@inheritdoc} + */ + public function telephone() + { + return 'telephonenumber'; + } + + /** + * {@inheritdoc} + */ + public function mobile() + { + return 'mobile'; + } + + /** + * {@inheritdoc} + */ + public function otherMobile() + { + return 'othermobile'; + } + + /** + * {@inheritdoc} + */ + public function facsimile() + { + return 'facsimiletelephonenumber'; + } + + /** + * {@inheritdoc} + */ + public function thumbnail() + { + return 'thumbnailphoto'; + } + + /** + * {@inheritdoc} + */ + public function title() + { + return 'title'; + } + + /** + * {@inheritdoc} + */ + public function top() + { + return 'top'; + } + + /** + * {@inheritdoc} + */ + public function true() + { + return 'TRUE'; + } + + /** + * {@inheritdoc} + */ + public function unicodePassword() + { + return 'unicodepwd'; + } + + /** + * {@inheritdoc} + */ + public function updatedAt() + { + return 'whenchanged'; + } + + /** + * {@inheritdoc} + */ + public function url() + { + return 'url'; + } + + /** + * {@inheritdoc} + */ + public function user() + { + return 'user'; + } + + /** + * {@inheritdoc} + */ + public function userAccountControl() + { + return 'useraccountcontrol'; + } + + /** + * {@inheritdoc} + */ + public function userId() + { + return 'uid'; + } + + /** + * {@inheritdoc} + */ + public function userModel() + { + return User::class; + } + + /** + * {@inheritdoc} + */ + public function userObjectClasses(): array + { + return [ + $this->top(), + $this->person(), + $this->organizationalPerson(), + $this->objectClassUser(), + ]; + } + + /** + * {@inheritdoc} + */ + public function userPrincipalName() + { + return 'userprincipalname'; + } + + /** + * {@inheritdoc} + */ + public function userWorkstations() + { + return 'userworkstations'; + } + + /** + * {@inheritdoc} + */ + public function versionNumber() + { + return 'versionnumber'; + } + + /** + * {@inheritdoc} + */ + public function foreignSecurityPrincipalModel() + { + return ForeignSecurityPrincipal::class; + } +} diff --git a/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/SchemaInterface.php b/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/SchemaInterface.php new file mode 100644 index 0000000000..023df74df2 --- /dev/null +++ b/data/web/inc/lib/vendor/adldap2/adldap2/src/Schemas/SchemaInterface.php @@ -0,0 +1,1421 @@ + $value) { + $dn[$rdn] = self::unescape($value); + } + } + + return $dn; + } + + /** + * Un-escapes a hexadecimal string into + * its original string representation. + * + * @param string $value + * + * @return string + */ + public static function unescape($value) + { + return preg_replace_callback('/\\\([0-9A-Fa-f]{2})/', function ($matches) { + return chr(hexdec($matches[1])); + }, $value); + } + + /** + * Convert a binary SID to a string SID. + * + * @author Chad Sikorra + * + * @link https://github.com/ChadSikorra + * @link https://stackoverflow.com/questions/39533560/php-ldap-get-user-sid + * + * @param string $value The Binary SID + * + * @return string|null + */ + public static function binarySidToString($value) + { + // Revision - 8bit unsigned int (C1) + // Count - 8bit unsigned int (C1) + // 2 null bytes + // ID - 32bit unsigned long, big-endian order + $sid = @unpack('C1rev/C1count/x2/N1id', $value); + + if (!isset($sid['id']) || !isset($sid['rev'])) { + return; + } + + $revisionLevel = $sid['rev']; + + $identifierAuthority = $sid['id']; + + $subs = isset($sid['count']) ? $sid['count'] : 0; + + $sidHex = $subs ? bin2hex($value) : ''; + + $subAuthorities = []; + + // The sub-authorities depend on the count, so only get as + // many as the count, regardless of data beyond it. + for ($i = 0; $i < $subs; $i++) { + $data = implode('', array_reverse( + str_split( + substr($sidHex, 16 + ($i * 8), 8), + 2 + ) + )); + + $subAuthorities[] = hexdec($data); + } + + // Tack on the 'S-' and glue it all together... + return 'S-'.$revisionLevel.'-'.$identifierAuthority.implode( + preg_filter('/^/', '-', $subAuthorities) + ); + } + + /** + * Convert a binary GUID to a string GUID. + * + * @param string $binGuid + * + * @return string|null + */ + public static function binaryGuidToString($binGuid) + { + if (trim($binGuid) == '' || is_null($binGuid)) { + return; + } + + $hex = unpack('H*hex', $binGuid)['hex']; + + $hex1 = substr($hex, -26, 2).substr($hex, -28, 2).substr($hex, -30, 2).substr($hex, -32, 2); + $hex2 = substr($hex, -22, 2).substr($hex, -24, 2); + $hex3 = substr($hex, -18, 2).substr($hex, -20, 2); + $hex4 = substr($hex, -16, 4); + $hex5 = substr($hex, -12, 12); + + $guid = sprintf('%s-%s-%s-%s-%s', $hex1, $hex2, $hex3, $hex4, $hex5); + + return $guid; + } + + /** + * Converts a string GUID to it's hex variant. + * + * @param string $string + * + * @return string + */ + public static function stringGuidToHex($string) + { + $hex = '\\'.substr($string, 6, 2).'\\'.substr($string, 4, 2).'\\'.substr($string, 2, 2).'\\'.substr($string, 0, 2); + $hex = $hex.'\\'.substr($string, 11, 2).'\\'.substr($string, 9, 2); + $hex = $hex.'\\'.substr($string, 16, 2).'\\'.substr($string, 14, 2); + $hex = $hex.'\\'.substr($string, 19, 2).'\\'.substr($string, 21, 2); + $hex = $hex.'\\'.substr($string, 24, 2).'\\'.substr($string, 26, 2).'\\'.substr($string, 28, 2).'\\'.substr($string, 30, 2).'\\'.substr($string, 32, 2).'\\'.substr($string, 34, 2); + + return $hex; + } + + /** + * Encode a password for transmission over LDAP. + * + * @param string $password The password to encode + * + * @return string + */ + public static function encodePassword($password) + { + return iconv('UTF-8', 'UTF-16LE', '"'.$password.'"'); + } + + /** + * Salt and hash a password to make its SSHA OpenLDAP version. + * + * @param string $password The password to create + * + * @return string + */ + public static function makeSSHAPassword($password) + { + mt_srand((float) microtime() * 1000000); + $salt = pack('CCCC', mt_rand(), mt_rand(), mt_rand(), mt_rand()); + + return '{SSHA}'.base64_encode(pack('H*', sha1($password.$salt)).$salt); + } + + /** + * Round a Windows timestamp down to seconds and remove + * the seconds between 1601-01-01 and 1970-01-01. + * + * @param float $windowsTime + * + * @return float + */ + public static function convertWindowsTimeToUnixTime($windowsTime) + { + return round($windowsTime / 10000000) - 11644473600; + } + + /** + * Convert a Unix timestamp to Windows timestamp. + * + * @param float $unixTime + * + * @return float + */ + public static function convertUnixTimeToWindowsTime($unixTime) + { + return ($unixTime + 11644473600) * 10000000; + } + + /** + * Validates that the inserted string is an object SID. + * + * @param string $sid + * + * @return bool + */ + public static function isValidSid($sid) + { + return (bool) preg_match("/^S-\d(-\d{1,10}){1,16}$/i", $sid); + } + + /** + * Validates that the inserted string is an object GUID. + * + * @param string $guid + * + * @return bool + */ + public static function isValidGuid($guid) + { + return (bool) preg_match('/^([0-9a-fA-F]){8}(-([0-9a-fA-F]){4}){3}-([0-9a-fA-F]){12}$|^([0-9a-fA-F]{8}-){3}[0-9a-fA-F]{8}$/', $guid); + } + + /** + * Converts an ignore string into an array. + * + * @param string $ignore + * + * @return array + */ + protected static function ignoreStrToArray($ignore) + { + $ignore = trim($ignore); + + return $ignore ? str_split($ignore) : []; + } +} diff --git a/data/web/inc/lib/vendor/autoload.php b/data/web/inc/lib/vendor/autoload.php index 011c527785..a0fb8f4acc 100644 --- a/data/web/inc/lib/vendor/autoload.php +++ b/data/web/inc/lib/vendor/autoload.php @@ -2,6 +2,24 @@ // autoload.php @generated by Composer +if (PHP_VERSION_ID < 50600) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, $err); + } elseif (!headers_sent()) { + echo $err; + } + } + trigger_error( + $err, + E_USER_ERROR + ); +} + require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInit873464e4bd965a3168f133248b1b218b::getLoader(); diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/LICENSE b/data/web/inc/lib/vendor/bacon/bacon-qr-code/LICENSE new file mode 100644 index 0000000000..d45a35647a --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2017, Ben Scholzen 'DASPRiD' +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/README.md b/data/web/inc/lib/vendor/bacon/bacon-qr-code/README.md new file mode 100644 index 0000000000..9c099fefd5 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/README.md @@ -0,0 +1,39 @@ +# QR Code generator + +[![PHP CI](https://github.com/Bacon/BaconQrCode/actions/workflows/ci.yml/badge.svg)](https://github.com/Bacon/BaconQrCode/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/Bacon/BaconQrCode/branch/master/graph/badge.svg?token=rD0HcAiEEx)](https://codecov.io/gh/Bacon/BaconQrCode) +[![Latest Stable Version](https://poser.pugx.org/bacon/bacon-qr-code/v/stable)](https://packagist.org/packages/bacon/bacon-qr-code) +[![Total Downloads](https://poser.pugx.org/bacon/bacon-qr-code/downloads)](https://packagist.org/packages/bacon/bacon-qr-code) +[![License](https://poser.pugx.org/bacon/bacon-qr-code/license)](https://packagist.org/packages/bacon/bacon-qr-code) + + +## Introduction +BaconQrCode is a port of QR code portion of the ZXing library. It currently +only features the encoder part, but could later receive the decoder part as +well. + +As the Reed Solomon codec implementation of the ZXing library performs quite +slow in PHP, it was exchanged with the implementation by Phil Karn. + + +## Example usage +```php +use BaconQrCode\Renderer\ImageRenderer; +use BaconQrCode\Renderer\Image\ImagickImageBackEnd; +use BaconQrCode\Renderer\RendererStyle\RendererStyle; +use BaconQrCode\Writer; + +$renderer = new ImageRenderer( + new RendererStyle(400), + new ImagickImageBackEnd() +); +$writer = new Writer($renderer); +$writer->writeFile('Hello World!', 'qrcode.png'); +``` + +## Available image renderer back ends +BaconQrCode comes with multiple back ends for rendering images. Currently included are the following: + +- `ImagickImageBackEnd`: renders raster images using the Imagick library +- `SvgImageBackEnd`: renders SVG files using XMLWriter +- `EpsImageBackEnd`: renders EPS files diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/composer.json b/data/web/inc/lib/vendor/bacon/bacon-qr-code/composer.json new file mode 100644 index 0000000000..7f193daf0b --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/composer.json @@ -0,0 +1,44 @@ +{ + "name": "bacon/bacon-qr-code", + "description": "BaconQrCode is a QR code generator for PHP.", + "license" : "BSD-2-Clause", + "homepage": "https://github.com/Bacon/BaconQrCode", + "require": { + "php": "^7.1 || ^8.0", + "ext-iconv": "*", + "dasprid/enum": "^1.0.3" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "require-dev": { + "phpunit/phpunit": "^7 | ^8 | ^9", + "spatie/phpunit-snapshot-assertions": "^4.2.9", + "squizlabs/php_codesniffer": "^3.4", + "phly/keep-a-changelog": "^2.1" + }, + "config": { + "allow-plugins": { + "ocramius/package-versions": true + } + }, + "archive": { + "exclude": [ + "/test", + "/phpunit.xml.dist" + ] + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/phpunit.xml.dist b/data/web/inc/lib/vendor/bacon/bacon-qr-code/phpunit.xml.dist new file mode 100644 index 0000000000..d9e4d57bcb --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/phpunit.xml.dist @@ -0,0 +1,13 @@ + + + + + src + + + + + ./test + + + diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/BitArray.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/BitArray.php new file mode 100644 index 0000000000..158384fe88 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/BitArray.php @@ -0,0 +1,372 @@ + + */ + private $bits; + + /** + * Size of the bit array in bits. + * + * @var int + */ + private $size; + + /** + * Creates a new bit array with a given size. + */ + public function __construct(int $size = 0) + { + $this->size = $size; + $this->bits = SplFixedArray::fromArray(array_fill(0, ($this->size + 31) >> 3, 0)); + } + + /** + * Gets the size in bits. + */ + public function getSize() : int + { + return $this->size; + } + + /** + * Gets the size in bytes. + */ + public function getSizeInBytes() : int + { + return ($this->size + 7) >> 3; + } + + /** + * Ensures that the array has a minimum capacity. + */ + public function ensureCapacity(int $size) : void + { + if ($size > count($this->bits) << 5) { + $this->bits->setSize(($size + 31) >> 5); + } + } + + /** + * Gets a specific bit. + */ + public function get(int $i) : bool + { + return 0 !== ($this->bits[$i >> 5] & (1 << ($i & 0x1f))); + } + + /** + * Sets a specific bit. + */ + public function set(int $i) : void + { + $this->bits[$i >> 5] = $this->bits[$i >> 5] | 1 << ($i & 0x1f); + } + + /** + * Flips a specific bit. + */ + public function flip(int $i) : void + { + $this->bits[$i >> 5] ^= 1 << ($i & 0x1f); + } + + /** + * Gets the next set bit position from a given position. + */ + public function getNextSet(int $from) : int + { + if ($from >= $this->size) { + return $this->size; + } + + $bitsOffset = $from >> 5; + $currentBits = $this->bits[$bitsOffset]; + $bitsLength = count($this->bits); + $currentBits &= ~((1 << ($from & 0x1f)) - 1); + + while (0 === $currentBits) { + if (++$bitsOffset === $bitsLength) { + return $this->size; + } + + $currentBits = $this->bits[$bitsOffset]; + } + + $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits); + return $result > $this->size ? $this->size : $result; + } + + /** + * Gets the next unset bit position from a given position. + */ + public function getNextUnset(int $from) : int + { + if ($from >= $this->size) { + return $this->size; + } + + $bitsOffset = $from >> 5; + $currentBits = ~$this->bits[$bitsOffset]; + $bitsLength = count($this->bits); + $currentBits &= ~((1 << ($from & 0x1f)) - 1); + + while (0 === $currentBits) { + if (++$bitsOffset === $bitsLength) { + return $this->size; + } + + $currentBits = ~$this->bits[$bitsOffset]; + } + + $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits); + return $result > $this->size ? $this->size : $result; + } + + /** + * Sets a bulk of bits. + */ + public function setBulk(int $i, int $newBits) : void + { + $this->bits[$i >> 5] = $newBits; + } + + /** + * Sets a range of bits. + * + * @throws InvalidArgumentException if end is smaller than start + */ + public function setRange(int $start, int $end) : void + { + if ($end < $start) { + throw new InvalidArgumentException('End must be greater or equal to start'); + } + + if ($end === $start) { + return; + } + + --$end; + + $firstInt = $start >> 5; + $lastInt = $end >> 5; + + for ($i = $firstInt; $i <= $lastInt; ++$i) { + $firstBit = $i > $firstInt ? 0 : $start & 0x1f; + $lastBit = $i < $lastInt ? 31 : $end & 0x1f; + + if (0 === $firstBit && 31 === $lastBit) { + $mask = 0x7fffffff; + } else { + $mask = 0; + + for ($j = $firstBit; $j < $lastBit; ++$j) { + $mask |= 1 << $j; + } + } + + $this->bits[$i] = $this->bits[$i] | $mask; + } + } + + /** + * Clears the bit array, unsetting every bit. + */ + public function clear() : void + { + $bitsLength = count($this->bits); + + for ($i = 0; $i < $bitsLength; ++$i) { + $this->bits[$i] = 0; + } + } + + /** + * Checks if a range of bits is set or not set. + + * @throws InvalidArgumentException if end is smaller than start + */ + public function isRange(int $start, int $end, bool $value) : bool + { + if ($end < $start) { + throw new InvalidArgumentException('End must be greater or equal to start'); + } + + if ($end === $start) { + return true; + } + + --$end; + + $firstInt = $start >> 5; + $lastInt = $end >> 5; + + for ($i = $firstInt; $i <= $lastInt; ++$i) { + $firstBit = $i > $firstInt ? 0 : $start & 0x1f; + $lastBit = $i < $lastInt ? 31 : $end & 0x1f; + + if (0 === $firstBit && 31 === $lastBit) { + $mask = 0x7fffffff; + } else { + $mask = 0; + + for ($j = $firstBit; $j <= $lastBit; ++$j) { + $mask |= 1 << $j; + } + } + + if (($this->bits[$i] & $mask) !== ($value ? $mask : 0)) { + return false; + } + } + + return true; + } + + /** + * Appends a bit to the array. + */ + public function appendBit(bool $bit) : void + { + $this->ensureCapacity($this->size + 1); + + if ($bit) { + $this->bits[$this->size >> 5] = $this->bits[$this->size >> 5] | (1 << ($this->size & 0x1f)); + } + + ++$this->size; + } + + /** + * Appends a number of bits (up to 32) to the array. + + * @throws InvalidArgumentException if num bits is not between 0 and 32 + */ + public function appendBits(int $value, int $numBits) : void + { + if ($numBits < 0 || $numBits > 32) { + throw new InvalidArgumentException('Num bits must be between 0 and 32'); + } + + $this->ensureCapacity($this->size + $numBits); + + for ($numBitsLeft = $numBits; $numBitsLeft > 0; $numBitsLeft--) { + $this->appendBit((($value >> ($numBitsLeft - 1)) & 0x01) === 1); + } + } + + /** + * Appends another bit array to this array. + */ + public function appendBitArray(self $other) : void + { + $otherSize = $other->getSize(); + $this->ensureCapacity($this->size + $other->getSize()); + + for ($i = 0; $i < $otherSize; ++$i) { + $this->appendBit($other->get($i)); + } + } + + /** + * Makes an exclusive-or comparision on the current bit array. + * + * @throws InvalidArgumentException if sizes don't match + */ + public function xorBits(self $other) : void + { + $bitsLength = count($this->bits); + $otherBits = $other->getBitArray(); + + if ($bitsLength !== count($otherBits)) { + throw new InvalidArgumentException('Sizes don\'t match'); + } + + for ($i = 0; $i < $bitsLength; ++$i) { + $this->bits[$i] = $this->bits[$i] ^ $otherBits[$i]; + } + } + + /** + * Converts the bit array to a byte array. + * + * @return SplFixedArray + */ + public function toBytes(int $bitOffset, int $numBytes) : SplFixedArray + { + $bytes = new SplFixedArray($numBytes); + + for ($i = 0; $i < $numBytes; ++$i) { + $byte = 0; + + for ($j = 0; $j < 8; ++$j) { + if ($this->get($bitOffset)) { + $byte |= 1 << (7 - $j); + } + + ++$bitOffset; + } + + $bytes[$i] = $byte; + } + + return $bytes; + } + + /** + * Gets the internal bit array. + * + * @return SplFixedArray + */ + public function getBitArray() : SplFixedArray + { + return $this->bits; + } + + /** + * Reverses the array. + */ + public function reverse() : void + { + $newBits = new SplFixedArray(count($this->bits)); + + for ($i = 0; $i < $this->size; ++$i) { + if ($this->get($this->size - $i - 1)) { + $newBits[$i >> 5] = $newBits[$i >> 5] | (1 << ($i & 0x1f)); + } + } + + $this->bits = $newBits; + } + + /** + * Returns a string representation of the bit array. + */ + public function __toString() : string + { + $result = ''; + + for ($i = 0; $i < $this->size; ++$i) { + if (0 === ($i & 0x07)) { + $result .= ' '; + } + + $result .= $this->get($i) ? 'X' : '.'; + } + + return $result; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/BitMatrix.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/BitMatrix.php new file mode 100644 index 0000000000..10bf8fe209 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/BitMatrix.php @@ -0,0 +1,313 @@ + + */ + private $bits; + + /** + * @throws InvalidArgumentException if a dimension is smaller than zero + */ + public function __construct(int $width, int $height = null) + { + if (null === $height) { + $height = $width; + } + + if ($width < 1 || $height < 1) { + throw new InvalidArgumentException('Both dimensions must be greater than zero'); + } + + $this->width = $width; + $this->height = $height; + $this->rowSize = ($width + 31) >> 5; + $this->bits = SplFixedArray::fromArray(array_fill(0, $this->rowSize * $height, 0)); + } + + /** + * Gets the requested bit, where true means black. + */ + public function get(int $x, int $y) : bool + { + $offset = $y * $this->rowSize + ($x >> 5); + return 0 !== (BitUtils::unsignedRightShift($this->bits[$offset], ($x & 0x1f)) & 1); + } + + /** + * Sets the given bit to true. + */ + public function set(int $x, int $y) : void + { + $offset = $y * $this->rowSize + ($x >> 5); + $this->bits[$offset] = $this->bits[$offset] | (1 << ($x & 0x1f)); + } + + /** + * Flips the given bit. + */ + public function flip(int $x, int $y) : void + { + $offset = $y * $this->rowSize + ($x >> 5); + $this->bits[$offset] = $this->bits[$offset] ^ (1 << ($x & 0x1f)); + } + + /** + * Clears all bits (set to false). + */ + public function clear() : void + { + $max = count($this->bits); + + for ($i = 0; $i < $max; ++$i) { + $this->bits[$i] = 0; + } + } + + /** + * Sets a square region of the bit matrix to true. + * + * @throws InvalidArgumentException if left or top are negative + * @throws InvalidArgumentException if width or height are smaller than 1 + * @throws InvalidArgumentException if region does not fit into the matix + */ + public function setRegion(int $left, int $top, int $width, int $height) : void + { + if ($top < 0 || $left < 0) { + throw new InvalidArgumentException('Left and top must be non-negative'); + } + + if ($height < 1 || $width < 1) { + throw new InvalidArgumentException('Width and height must be at least 1'); + } + + $right = $left + $width; + $bottom = $top + $height; + + if ($bottom > $this->height || $right > $this->width) { + throw new InvalidArgumentException('The region must fit inside the matrix'); + } + + for ($y = $top; $y < $bottom; ++$y) { + $offset = $y * $this->rowSize; + + for ($x = $left; $x < $right; ++$x) { + $index = $offset + ($x >> 5); + $this->bits[$index] = $this->bits[$index] | (1 << ($x & 0x1f)); + } + } + } + + /** + * A fast method to retrieve one row of data from the matrix as a BitArray. + */ + public function getRow(int $y, BitArray $row = null) : BitArray + { + if (null === $row || $row->getSize() < $this->width) { + $row = new BitArray($this->width); + } + + $offset = $y * $this->rowSize; + + for ($x = 0; $x < $this->rowSize; ++$x) { + $row->setBulk($x << 5, $this->bits[$offset + $x]); + } + + return $row; + } + + /** + * Sets a row of data from a BitArray. + */ + public function setRow(int $y, BitArray $row) : void + { + $bits = $row->getBitArray(); + + for ($i = 0; $i < $this->rowSize; ++$i) { + $this->bits[$y * $this->rowSize + $i] = $bits[$i]; + } + } + + /** + * This is useful in detecting the enclosing rectangle of a 'pure' barcode. + * + * @return int[]|null + */ + public function getEnclosingRectangle() : ?array + { + $left = $this->width; + $top = $this->height; + $right = -1; + $bottom = -1; + + for ($y = 0; $y < $this->height; ++$y) { + for ($x32 = 0; $x32 < $this->rowSize; ++$x32) { + $bits = $this->bits[$y * $this->rowSize + $x32]; + + if (0 !== $bits) { + if ($y < $top) { + $top = $y; + } + + if ($y > $bottom) { + $bottom = $y; + } + + if ($x32 * 32 < $left) { + $bit = 0; + + while (($bits << (31 - $bit)) === 0) { + $bit++; + } + + if (($x32 * 32 + $bit) < $left) { + $left = $x32 * 32 + $bit; + } + } + } + + if ($x32 * 32 + 31 > $right) { + $bit = 31; + + while (0 === BitUtils::unsignedRightShift($bits, $bit)) { + --$bit; + } + + if (($x32 * 32 + $bit) > $right) { + $right = $x32 * 32 + $bit; + } + } + } + } + + $width = $right - $left; + $height = $bottom - $top; + + if ($width < 0 || $height < 0) { + return null; + } + + return [$left, $top, $width, $height]; + } + + /** + * Gets the most top left set bit. + * + * This is useful in detecting a corner of a 'pure' barcode. + * + * @return int[]|null + */ + public function getTopLeftOnBit() : ?array + { + $bitsOffset = 0; + + while ($bitsOffset < count($this->bits) && 0 === $this->bits[$bitsOffset]) { + ++$bitsOffset; + } + + if (count($this->bits) === $bitsOffset) { + return null; + } + + $x = intdiv($bitsOffset, $this->rowSize); + $y = ($bitsOffset % $this->rowSize) << 5; + + $bits = $this->bits[$bitsOffset]; + $bit = 0; + + while (0 === ($bits << (31 - $bit))) { + ++$bit; + } + + $x += $bit; + + return [$x, $y]; + } + + /** + * Gets the most bottom right set bit. + * + * This is useful in detecting a corner of a 'pure' barcode. + * + * @return int[]|null + */ + public function getBottomRightOnBit() : ?array + { + $bitsOffset = count($this->bits) - 1; + + while ($bitsOffset >= 0 && 0 === $this->bits[$bitsOffset]) { + --$bitsOffset; + } + + if ($bitsOffset < 0) { + return null; + } + + $x = intdiv($bitsOffset, $this->rowSize); + $y = ($bitsOffset % $this->rowSize) << 5; + + $bits = $this->bits[$bitsOffset]; + $bit = 0; + + while (0 === BitUtils::unsignedRightShift($bits, $bit)) { + --$bit; + } + + $x += $bit; + + return [$x, $y]; + } + + /** + * Gets the width of the matrix, + */ + public function getWidth() : int + { + return $this->width; + } + + /** + * Gets the height of the matrix. + */ + public function getHeight() : int + { + return $this->height; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/BitUtils.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/BitUtils.php new file mode 100644 index 0000000000..0c575b493c --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/BitUtils.php @@ -0,0 +1,41 @@ +>>" in other + * languages. + */ + public static function unsignedRightShift(int $a, int $b) : int + { + return ( + $a >= 0 + ? $a >> $b + : (($a & 0x7fffffff) >> $b) | (0x40000000 >> ($b - 1)) + ); + } + + /** + * Gets the number of trailing zeros. + */ + public static function numberOfTrailingZeros(int $i) : int + { + $lastPos = strrpos(str_pad(decbin($i), 32, '0', STR_PAD_LEFT), '1'); + return $lastPos === false ? 32 : 31 - $lastPos; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/CharacterSetEci.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/CharacterSetEci.php new file mode 100644 index 0000000000..9049ccb307 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/CharacterSetEci.php @@ -0,0 +1,183 @@ +|null + */ + private static $valueToEci; + + /** + * @var array|null + */ + private static $nameToEci; + + /** + * @param int[] $values + */ + public function __construct(array $values, string ...$otherEncodingNames) + { + $this->values = $values; + $this->otherEncodingNames = $otherEncodingNames; + } + + /** + * Returns the primary value. + */ + public function getValue() : int + { + return $this->values[0]; + } + + /** + * Gets character set ECI by value. + * + * Returns the representing ECI of a given value, or null if it is legal but unsupported. + * + * @throws InvalidArgumentException if value is not between 0 and 900 + */ + public static function getCharacterSetEciByValue(int $value) : ?self + { + if ($value < 0 || $value >= 900) { + throw new InvalidArgumentException('Value must be between 0 and 900'); + } + + $valueToEci = self::valueToEci(); + + if (! array_key_exists($value, $valueToEci)) { + return null; + } + + return $valueToEci[$value]; + } + + /** + * Returns character set ECI by name. + * + * Returns the representing ECI of a given name, or null if it is legal but unsupported + */ + public static function getCharacterSetEciByName(string $name) : ?self + { + $nameToEci = self::nameToEci(); + $name = strtolower($name); + + if (! array_key_exists($name, $nameToEci)) { + return null; + } + + return $nameToEci[$name]; + } + + private static function valueToEci() : array + { + if (null !== self::$valueToEci) { + return self::$valueToEci; + } + + self::$valueToEci = []; + + foreach (self::values() as $eci) { + foreach ($eci->values as $value) { + self::$valueToEci[$value] = $eci; + } + } + + return self::$valueToEci; + } + + private static function nameToEci() : array + { + if (null !== self::$nameToEci) { + return self::$nameToEci; + } + + self::$nameToEci = []; + + foreach (self::values() as $eci) { + self::$nameToEci[strtolower($eci->name())] = $eci; + + foreach ($eci->otherEncodingNames as $name) { + self::$nameToEci[strtolower($name)] = $eci; + } + } + + return self::$nameToEci; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/EcBlock.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/EcBlock.php new file mode 100644 index 0000000000..a9a1d07d76 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/EcBlock.php @@ -0,0 +1,49 @@ +count = $count; + $this->dataCodewords = $dataCodewords; + } + + /** + * Returns how many times the block is used. + */ + public function getCount() : int + { + return $this->count; + } + + /** + * Returns the number of data codewords. + */ + public function getDataCodewords() : int + { + return $this->dataCodewords; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/EcBlocks.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/EcBlocks.php new file mode 100644 index 0000000000..172b5f2786 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/EcBlocks.php @@ -0,0 +1,74 @@ +ecCodewordsPerBlock = $ecCodewordsPerBlock; + $this->ecBlocks = $ecBlocks; + } + + /** + * Returns the number of EC codewords per block. + */ + public function getEcCodewordsPerBlock() : int + { + return $this->ecCodewordsPerBlock; + } + + /** + * Returns the total number of EC block appearances. + */ + public function getNumBlocks() : int + { + $total = 0; + + foreach ($this->ecBlocks as $ecBlock) { + $total += $ecBlock->getCount(); + } + + return $total; + } + + /** + * Returns the total count of EC codewords. + */ + public function getTotalEcCodewords() : int + { + return $this->ecCodewordsPerBlock * $this->getNumBlocks(); + } + + /** + * Returns the EC blocks included in this collection. + * + * @return EcBlock[] + */ + public function getEcBlocks() : array + { + return $this->ecBlocks; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/ErrorCorrectionLevel.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/ErrorCorrectionLevel.php new file mode 100644 index 0000000000..9bbf440682 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/ErrorCorrectionLevel.php @@ -0,0 +1,63 @@ +bits = $bits; + } + + /** + * @throws OutOfBoundsException if number of bits is invalid + */ + public static function forBits(int $bits) : self + { + switch ($bits) { + case 0: + return self::M(); + + case 1: + return self::L(); + + case 2: + return self::H(); + + case 3: + return self::Q(); + } + + throw new OutOfBoundsException('Invalid number of bits'); + } + + /** + * Returns the two bits used to encode this error correction level. + */ + public function getBits() : int + { + return $this->bits; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/FormatInformation.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/FormatInformation.php new file mode 100644 index 0000000000..38295fc690 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/FormatInformation.php @@ -0,0 +1,203 @@ +ecLevel = ErrorCorrectionLevel::forBits(($formatInfo >> 3) & 0x3); + $this->dataMask = $formatInfo & 0x7; + } + + /** + * Checks how many bits are different between two integers. + */ + public static function numBitsDiffering(int $a, int $b) : int + { + $a ^= $b; + + return ( + self::BITS_SET_IN_HALF_BYTE[$a & 0xf] + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 4) & 0xf)] + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 8) & 0xf)] + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 12) & 0xf)] + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 16) & 0xf)] + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 20) & 0xf)] + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 24) & 0xf)] + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 28) & 0xf)] + ); + } + + /** + * Decodes format information. + */ + public static function decodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self + { + $formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2); + + if (null !== $formatInfo) { + return $formatInfo; + } + + // Should return null, but, some QR codes apparently do not mask this info. Try again by actually masking the + // pattern first. + return self::doDecodeFormatInformation( + $maskedFormatInfo1 ^ self::FORMAT_INFO_MASK_QR, + $maskedFormatInfo2 ^ self::FORMAT_INFO_MASK_QR + ); + } + + /** + * Internal method for decoding format information. + */ + private static function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self + { + $bestDifference = PHP_INT_MAX; + $bestFormatInfo = 0; + + foreach (self::FORMAT_INFO_DECODE_LOOKUP as $decodeInfo) { + $targetInfo = $decodeInfo[0]; + + if ($targetInfo === $maskedFormatInfo1 || $targetInfo === $maskedFormatInfo2) { + // Found an exact match + return new self($decodeInfo[1]); + } + + $bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo); + + if ($bitsDifference < $bestDifference) { + $bestFormatInfo = $decodeInfo[1]; + $bestDifference = $bitsDifference; + } + + if ($maskedFormatInfo1 !== $maskedFormatInfo2) { + // Also try the other option + $bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo); + + if ($bitsDifference < $bestDifference) { + $bestFormatInfo = $decodeInfo[1]; + $bestDifference = $bitsDifference; + } + } + } + + // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match. + if ($bestDifference <= 3) { + return new self($bestFormatInfo); + } + + return null; + } + + /** + * Returns the error correction level. + */ + public function getErrorCorrectionLevel() : ErrorCorrectionLevel + { + return $this->ecLevel; + } + + /** + * Returns the data mask. + */ + public function getDataMask() : int + { + return $this->dataMask; + } + + /** + * Hashes the code of the EC level. + */ + public function hashCode() : int + { + return ($this->ecLevel->getBits() << 3) | $this->dataMask; + } + + /** + * Verifies if this instance equals another one. + */ + public function equals(self $other) : bool + { + return ( + $this->ecLevel === $other->ecLevel + && $this->dataMask === $other->dataMask + ); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/Mode.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/Mode.php new file mode 100644 index 0000000000..af5a113f4d --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/Mode.php @@ -0,0 +1,79 @@ +characterCountBitsForVersions = $characterCountBitsForVersions; + $this->bits = $bits; + } + + /** + * Returns the number of bits used in a specific QR code version. + */ + public function getCharacterCountBits(Version $version) : int + { + $number = $version->getVersionNumber(); + + if ($number <= 9) { + $offset = 0; + } elseif ($number <= 26) { + $offset = 1; + } else { + $offset = 2; + } + + return $this->characterCountBitsForVersions[$offset]; + } + + /** + * Returns the four bits used to encode this mode. + */ + public function getBits() : int + { + return $this->bits; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/ReedSolomonCodec.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/ReedSolomonCodec.php new file mode 100644 index 0000000000..a5aad0b513 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/ReedSolomonCodec.php @@ -0,0 +1,468 @@ + 8) { + throw new InvalidArgumentException('Symbol size must be between 0 and 8'); + } + + if ($firstRoot < 0 || $firstRoot >= (1 << $symbolSize)) { + throw new InvalidArgumentException('First root must be between 0 and ' . (1 << $symbolSize)); + } + + if ($numRoots < 0 || $numRoots >= (1 << $symbolSize)) { + throw new InvalidArgumentException('Num roots must be between 0 and ' . (1 << $symbolSize)); + } + + if ($padding < 0 || $padding >= ((1 << $symbolSize) - 1 - $numRoots)) { + throw new InvalidArgumentException( + 'Padding must be between 0 and ' . ((1 << $symbolSize) - 1 - $numRoots) + ); + } + + $this->symbolSize = $symbolSize; + $this->blockSize = (1 << $symbolSize) - 1; + $this->padding = $padding; + $this->alphaTo = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false); + $this->indexOf = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false); + + // Generate galous field lookup table + $this->indexOf[0] = $this->blockSize; + $this->alphaTo[$this->blockSize] = 0; + + $sr = 1; + + for ($i = 0; $i < $this->blockSize; ++$i) { + $this->indexOf[$sr] = $i; + $this->alphaTo[$i] = $sr; + + $sr <<= 1; + + if ($sr & (1 << $symbolSize)) { + $sr ^= $gfPoly; + } + + $sr &= $this->blockSize; + } + + if (1 !== $sr) { + throw new RuntimeException('Field generator polynomial is not primitive'); + } + + // Form RS code generator polynomial from its roots + $this->generatorPoly = SplFixedArray::fromArray(array_fill(0, $numRoots + 1, 0), false); + $this->firstRoot = $firstRoot; + $this->primitive = $primitive; + $this->numRoots = $numRoots; + + // Find prim-th root of 1, used in decoding + for ($iPrimitive = 1; ($iPrimitive % $primitive) !== 0; $iPrimitive += $this->blockSize) { + } + + $this->iPrimitive = intdiv($iPrimitive, $primitive); + + $this->generatorPoly[0] = 1; + + for ($i = 0, $root = $firstRoot * $primitive; $i < $numRoots; ++$i, $root += $primitive) { + $this->generatorPoly[$i + 1] = 1; + + for ($j = $i; $j > 0; $j--) { + if ($this->generatorPoly[$j] !== 0) { + $this->generatorPoly[$j] = $this->generatorPoly[$j - 1] ^ $this->alphaTo[ + $this->modNn($this->indexOf[$this->generatorPoly[$j]] + $root) + ]; + } else { + $this->generatorPoly[$j] = $this->generatorPoly[$j - 1]; + } + } + + $this->generatorPoly[$j] = $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[0]] + $root)]; + } + + // Convert generator poly to index form for quicker encoding + for ($i = 0; $i <= $numRoots; ++$i) { + $this->generatorPoly[$i] = $this->indexOf[$this->generatorPoly[$i]]; + } + } + + /** + * Encodes data and writes result back into parity array. + */ + public function encode(SplFixedArray $data, SplFixedArray $parity) : void + { + for ($i = 0; $i < $this->numRoots; ++$i) { + $parity[$i] = 0; + } + + $iterations = $this->blockSize - $this->numRoots - $this->padding; + + for ($i = 0; $i < $iterations; ++$i) { + $feedback = $this->indexOf[$data[$i] ^ $parity[0]]; + + if ($feedback !== $this->blockSize) { + // Feedback term is non-zero + $feedback = $this->modNn($this->blockSize - $this->generatorPoly[$this->numRoots] + $feedback); + + for ($j = 1; $j < $this->numRoots; ++$j) { + $parity[$j] = $parity[$j] ^ $this->alphaTo[ + $this->modNn($feedback + $this->generatorPoly[$this->numRoots - $j]) + ]; + } + } + + for ($j = 0; $j < $this->numRoots - 1; ++$j) { + $parity[$j] = $parity[$j + 1]; + } + + if ($feedback !== $this->blockSize) { + $parity[$this->numRoots - 1] = $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[0])]; + } else { + $parity[$this->numRoots - 1] = 0; + } + } + } + + /** + * Decodes received data. + */ + public function decode(SplFixedArray $data, SplFixedArray $erasures = null) : ?int + { + // This speeds up the initialization a bit. + $numRootsPlusOne = SplFixedArray::fromArray(array_fill(0, $this->numRoots + 1, 0), false); + $numRoots = SplFixedArray::fromArray(array_fill(0, $this->numRoots, 0), false); + + $lambda = clone $numRootsPlusOne; + $b = clone $numRootsPlusOne; + $t = clone $numRootsPlusOne; + $omega = clone $numRootsPlusOne; + $root = clone $numRoots; + $loc = clone $numRoots; + + $numErasures = (null !== $erasures ? count($erasures) : 0); + + // Form the Syndromes; i.e., evaluate data(x) at roots of g(x) + $syndromes = SplFixedArray::fromArray(array_fill(0, $this->numRoots, $data[0]), false); + + for ($i = 1; $i < $this->blockSize - $this->padding; ++$i) { + for ($j = 0; $j < $this->numRoots; ++$j) { + if ($syndromes[$j] === 0) { + $syndromes[$j] = $data[$i]; + } else { + $syndromes[$j] = $data[$i] ^ $this->alphaTo[ + $this->modNn($this->indexOf[$syndromes[$j]] + ($this->firstRoot + $j) * $this->primitive) + ]; + } + } + } + + // Convert syndromes to index form, checking for nonzero conditions + $syndromeError = 0; + + for ($i = 0; $i < $this->numRoots; ++$i) { + $syndromeError |= $syndromes[$i]; + $syndromes[$i] = $this->indexOf[$syndromes[$i]]; + } + + if (! $syndromeError) { + // If syndrome is zero, data[] is a codeword and there are no errors to correct, so return data[] + // unmodified. + return 0; + } + + $lambda[0] = 1; + + if ($numErasures > 0) { + // Init lambda to be the erasure locator polynomial + $lambda[1] = $this->alphaTo[$this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[0]))]; + + for ($i = 1; $i < $numErasures; ++$i) { + $u = $this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[$i])); + + for ($j = $i + 1; $j > 0; --$j) { + $tmp = $this->indexOf[$lambda[$j - 1]]; + + if ($tmp !== $this->blockSize) { + $lambda[$j] = $lambda[$j] ^ $this->alphaTo[$this->modNn($u + $tmp)]; + } + } + } + } + + for ($i = 0; $i <= $this->numRoots; ++$i) { + $b[$i] = $this->indexOf[$lambda[$i]]; + } + + // Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial + $r = $numErasures; + $el = $numErasures; + + while (++$r <= $this->numRoots) { + // Compute discrepancy at the r-th step in poly form + $discrepancyR = 0; + + for ($i = 0; $i < $r; ++$i) { + if ($lambda[$i] !== 0 && $syndromes[$r - $i - 1] !== $this->blockSize) { + $discrepancyR ^= $this->alphaTo[ + $this->modNn($this->indexOf[$lambda[$i]] + $syndromes[$r - $i - 1]) + ]; + } + } + + $discrepancyR = $this->indexOf[$discrepancyR]; + + if ($discrepancyR === $this->blockSize) { + $tmp = $b->toArray(); + array_unshift($tmp, $this->blockSize); + array_pop($tmp); + $b = SplFixedArray::fromArray($tmp, false); + continue; + } + + $t[0] = $lambda[0]; + + for ($i = 0; $i < $this->numRoots; ++$i) { + if ($b[$i] !== $this->blockSize) { + $t[$i + 1] = $lambda[$i + 1] ^ $this->alphaTo[$this->modNn($discrepancyR + $b[$i])]; + } else { + $t[$i + 1] = $lambda[$i + 1]; + } + } + + if (2 * $el <= $r + $numErasures - 1) { + $el = $r + $numErasures - $el; + + for ($i = 0; $i <= $this->numRoots; ++$i) { + $b[$i] = ( + $lambda[$i] === 0 + ? $this->blockSize + : $this->modNn($this->indexOf[$lambda[$i]] - $discrepancyR + $this->blockSize) + ); + } + } else { + $tmp = $b->toArray(); + array_unshift($tmp, $this->blockSize); + array_pop($tmp); + $b = SplFixedArray::fromArray($tmp, false); + } + + $lambda = clone $t; + } + + // Convert lambda to index form and compute deg(lambda(x)) + $degLambda = 0; + + for ($i = 0; $i <= $this->numRoots; ++$i) { + $lambda[$i] = $this->indexOf[$lambda[$i]]; + + if ($lambda[$i] !== $this->blockSize) { + $degLambda = $i; + } + } + + // Find roots of the error+erasure locator polynomial by Chien search. + $reg = clone $lambda; + $reg[0] = 0; + $count = 0; + $i = 1; + + for ($k = $this->iPrimitive - 1; $i <= $this->blockSize; ++$i, $k = $this->modNn($k + $this->iPrimitive)) { + $q = 1; + + for ($j = $degLambda; $j > 0; $j--) { + if ($reg[$j] !== $this->blockSize) { + $reg[$j] = $this->modNn($reg[$j] + $j); + $q ^= $this->alphaTo[$reg[$j]]; + } + } + + if ($q !== 0) { + // Not a root + continue; + } + + // Store root (index-form) and error location number + $root[$count] = $i; + $loc[$count] = $k; + + if (++$count === $degLambda) { + break; + } + } + + if ($degLambda !== $count) { + // deg(lambda) unequal to number of roots: uncorrectable error detected + return null; + } + + // Compute err+eras evaluate poly omega(x) = s(x)*lambda(x) (modulo x**numRoots). In index form. Also find + // deg(omega). + $degOmega = $degLambda - 1; + + for ($i = 0; $i <= $degOmega; ++$i) { + $tmp = 0; + + for ($j = $i; $j >= 0; --$j) { + if ($syndromes[$i - $j] !== $this->blockSize && $lambda[$j] !== $this->blockSize) { + $tmp ^= $this->alphaTo[$this->modNn($syndromes[$i - $j] + $lambda[$j])]; + } + } + + $omega[$i] = $this->indexOf[$tmp]; + } + + // Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(firstRoot-1) and + // den = lambda_pr(inv(X(l))) all in poly form. + for ($j = $count - 1; $j >= 0; --$j) { + $num1 = 0; + + for ($i = $degOmega; $i >= 0; $i--) { + if ($omega[$i] !== $this->blockSize) { + $num1 ^= $this->alphaTo[$this->modNn($omega[$i] + $i * $root[$j])]; + } + } + + $num2 = $this->alphaTo[$this->modNn($root[$j] * ($this->firstRoot - 1) + $this->blockSize)]; + $den = 0; + + // lambda[i+1] for i even is the formal derivativelambda_pr of lambda[i] + for ($i = min($degLambda, $this->numRoots - 1) & ~1; $i >= 0; $i -= 2) { + if ($lambda[$i + 1] !== $this->blockSize) { + $den ^= $this->alphaTo[$this->modNn($lambda[$i + 1] + $i * $root[$j])]; + } + } + + // Apply error to data + if ($num1 !== 0 && $loc[$j] >= $this->padding) { + $data[$loc[$j] - $this->padding] = $data[$loc[$j] - $this->padding] ^ ( + $this->alphaTo[ + $this->modNn( + $this->indexOf[$num1] + $this->indexOf[$num2] + $this->blockSize - $this->indexOf[$den] + ) + ] + ); + } + } + + if (null !== $erasures) { + if (count($erasures) < $count) { + $erasures->setSize($count); + } + + for ($i = 0; $i < $count; $i++) { + $erasures[$i] = $loc[$i]; + } + } + + return $count; + } + + /** + * Computes $x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, without a slow divide. + */ + private function modNn(int $x) : int + { + while ($x >= $this->blockSize) { + $x -= $this->blockSize; + $x = ($x >> $this->symbolSize) + ($x & $this->blockSize); + } + + return $x; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/Version.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/Version.php new file mode 100644 index 0000000000..917d048dfc --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Common/Version.php @@ -0,0 +1,596 @@ +|null + */ + private static $versions; + + /** + * @param int[] $alignmentPatternCenters + */ + private function __construct( + int $versionNumber, + array $alignmentPatternCenters, + EcBlocks ...$ecBlocks + ) { + $this->versionNumber = $versionNumber; + $this->alignmentPatternCenters = $alignmentPatternCenters; + $this->ecBlocks = $ecBlocks; + + $totalCodewords = 0; + $ecCodewords = $ecBlocks[0]->getEcCodewordsPerBlock(); + + foreach ($ecBlocks[0]->getEcBlocks() as $ecBlock) { + $totalCodewords += $ecBlock->getCount() * ($ecBlock->getDataCodewords() + $ecCodewords); + } + + $this->totalCodewords = $totalCodewords; + } + + /** + * Returns the version number. + */ + public function getVersionNumber() : int + { + return $this->versionNumber; + } + + /** + * Returns the alignment pattern centers. + * + * @return int[] + */ + public function getAlignmentPatternCenters() : array + { + return $this->alignmentPatternCenters; + } + + /** + * Returns the total number of codewords. + */ + public function getTotalCodewords() : int + { + return $this->totalCodewords; + } + + /** + * Calculates the dimension for the current version. + */ + public function getDimensionForVersion() : int + { + return 17 + 4 * $this->versionNumber; + } + + /** + * Returns the number of EC blocks for a specific EC level. + */ + public function getEcBlocksForLevel(ErrorCorrectionLevel $ecLevel) : EcBlocks + { + return $this->ecBlocks[$ecLevel->ordinal()]; + } + + /** + * Gets a provisional version number for a specific dimension. + * + * @throws InvalidArgumentException if dimension is not 1 mod 4 + */ + public static function getProvisionalVersionForDimension(int $dimension) : self + { + if (1 !== $dimension % 4) { + throw new InvalidArgumentException('Dimension is not 1 mod 4'); + } + + return self::getVersionForNumber(intdiv($dimension - 17, 4)); + } + + /** + * Gets a version instance for a specific version number. + * + * @throws InvalidArgumentException if version number is out of range + */ + public static function getVersionForNumber(int $versionNumber) : self + { + if ($versionNumber < 1 || $versionNumber > 40) { + throw new InvalidArgumentException('Version number must be between 1 and 40'); + } + + return self::versions()[$versionNumber - 1]; + } + + /** + * Decodes version information from an integer and returns the version. + */ + public static function decodeVersionInformation(int $versionBits) : ?self + { + $bestDifference = PHP_INT_MAX; + $bestVersion = 0; + + foreach (self::VERSION_DECODE_INFO as $i => $targetVersion) { + if ($targetVersion === $versionBits) { + return self::getVersionForNumber($i + 7); + } + + $bitsDifference = FormatInformation::numBitsDiffering($versionBits, $targetVersion); + + if ($bitsDifference < $bestDifference) { + $bestVersion = $i + 7; + $bestDifference = $bitsDifference; + } + } + + if ($bestDifference <= 3) { + return self::getVersionForNumber($bestVersion); + } + + return null; + } + + /** + * Builds the function pattern for the current version. + */ + public function buildFunctionPattern() : BitMatrix + { + $dimension = $this->getDimensionForVersion(); + $bitMatrix = new BitMatrix($dimension); + + // Top left finder pattern + separator + format + $bitMatrix->setRegion(0, 0, 9, 9); + // Top right finder pattern + separator + format + $bitMatrix->setRegion($dimension - 8, 0, 8, 9); + // Bottom left finder pattern + separator + format + $bitMatrix->setRegion(0, $dimension - 8, 9, 8); + + // Alignment patterns + $max = count($this->alignmentPatternCenters); + + for ($x = 0; $x < $max; ++$x) { + $i = $this->alignmentPatternCenters[$x] - 2; + + for ($y = 0; $y < $max; ++$y) { + if (($x === 0 && ($y === 0 || $y === $max - 1)) || ($x === $max - 1 && $y === 0)) { + // No alignment patterns near the three finder paterns + continue; + } + + $bitMatrix->setRegion($this->alignmentPatternCenters[$y] - 2, $i, 5, 5); + } + } + + // Vertical timing pattern + $bitMatrix->setRegion(6, 9, 1, $dimension - 17); + // Horizontal timing pattern + $bitMatrix->setRegion(9, 6, $dimension - 17, 1); + + if ($this->versionNumber > 6) { + // Version info, top right + $bitMatrix->setRegion($dimension - 11, 0, 3, 6); + // Version info, bottom left + $bitMatrix->setRegion(0, $dimension - 11, 6, 3); + } + + return $bitMatrix; + } + + /** + * Returns a string representation for the version. + */ + public function __toString() : string + { + return (string) $this->versionNumber; + } + + /** + * Build and cache a specific version. + * + * See ISO 18004:2006 6.5.1 Table 9. + * + * @return array + */ + private static function versions() : array + { + if (null !== self::$versions) { + return self::$versions; + } + + return self::$versions = [ + new self( + 1, + [], + new EcBlocks(7, new EcBlock(1, 19)), + new EcBlocks(10, new EcBlock(1, 16)), + new EcBlocks(13, new EcBlock(1, 13)), + new EcBlocks(17, new EcBlock(1, 9)) + ), + new self( + 2, + [6, 18], + new EcBlocks(10, new EcBlock(1, 34)), + new EcBlocks(16, new EcBlock(1, 28)), + new EcBlocks(22, new EcBlock(1, 22)), + new EcBlocks(28, new EcBlock(1, 16)) + ), + new self( + 3, + [6, 22], + new EcBlocks(15, new EcBlock(1, 55)), + new EcBlocks(26, new EcBlock(1, 44)), + new EcBlocks(18, new EcBlock(2, 17)), + new EcBlocks(22, new EcBlock(2, 13)) + ), + new self( + 4, + [6, 26], + new EcBlocks(20, new EcBlock(1, 80)), + new EcBlocks(18, new EcBlock(2, 32)), + new EcBlocks(26, new EcBlock(3, 24)), + new EcBlocks(16, new EcBlock(4, 9)) + ), + new self( + 5, + [6, 30], + new EcBlocks(26, new EcBlock(1, 108)), + new EcBlocks(24, new EcBlock(2, 43)), + new EcBlocks(18, new EcBlock(2, 15), new EcBlock(2, 16)), + new EcBlocks(22, new EcBlock(2, 11), new EcBlock(2, 12)) + ), + new self( + 6, + [6, 34], + new EcBlocks(18, new EcBlock(2, 68)), + new EcBlocks(16, new EcBlock(4, 27)), + new EcBlocks(24, new EcBlock(4, 19)), + new EcBlocks(28, new EcBlock(4, 15)) + ), + new self( + 7, + [6, 22, 38], + new EcBlocks(20, new EcBlock(2, 78)), + new EcBlocks(18, new EcBlock(4, 31)), + new EcBlocks(18, new EcBlock(2, 14), new EcBlock(4, 15)), + new EcBlocks(26, new EcBlock(4, 13), new EcBlock(1, 14)) + ), + new self( + 8, + [6, 24, 42], + new EcBlocks(24, new EcBlock(2, 97)), + new EcBlocks(22, new EcBlock(2, 38), new EcBlock(2, 39)), + new EcBlocks(22, new EcBlock(4, 18), new EcBlock(2, 19)), + new EcBlocks(26, new EcBlock(4, 14), new EcBlock(2, 15)) + ), + new self( + 9, + [6, 26, 46], + new EcBlocks(30, new EcBlock(2, 116)), + new EcBlocks(22, new EcBlock(3, 36), new EcBlock(2, 37)), + new EcBlocks(20, new EcBlock(4, 16), new EcBlock(4, 17)), + new EcBlocks(24, new EcBlock(4, 12), new EcBlock(4, 13)) + ), + new self( + 10, + [6, 28, 50], + new EcBlocks(18, new EcBlock(2, 68), new EcBlock(2, 69)), + new EcBlocks(26, new EcBlock(4, 43), new EcBlock(1, 44)), + new EcBlocks(24, new EcBlock(6, 19), new EcBlock(2, 20)), + new EcBlocks(28, new EcBlock(6, 15), new EcBlock(2, 16)) + ), + new self( + 11, + [6, 30, 54], + new EcBlocks(20, new EcBlock(4, 81)), + new EcBlocks(30, new EcBlock(1, 50), new EcBlock(4, 51)), + new EcBlocks(28, new EcBlock(4, 22), new EcBlock(4, 23)), + new EcBlocks(24, new EcBlock(3, 12), new EcBlock(8, 13)) + ), + new self( + 12, + [6, 32, 58], + new EcBlocks(24, new EcBlock(2, 92), new EcBlock(2, 93)), + new EcBlocks(22, new EcBlock(6, 36), new EcBlock(2, 37)), + new EcBlocks(26, new EcBlock(4, 20), new EcBlock(6, 21)), + new EcBlocks(28, new EcBlock(7, 14), new EcBlock(4, 15)) + ), + new self( + 13, + [6, 34, 62], + new EcBlocks(26, new EcBlock(4, 107)), + new EcBlocks(22, new EcBlock(8, 37), new EcBlock(1, 38)), + new EcBlocks(24, new EcBlock(8, 20), new EcBlock(4, 21)), + new EcBlocks(22, new EcBlock(12, 11), new EcBlock(4, 12)) + ), + new self( + 14, + [6, 26, 46, 66], + new EcBlocks(30, new EcBlock(3, 115), new EcBlock(1, 116)), + new EcBlocks(24, new EcBlock(4, 40), new EcBlock(5, 41)), + new EcBlocks(20, new EcBlock(11, 16), new EcBlock(5, 17)), + new EcBlocks(24, new EcBlock(11, 12), new EcBlock(5, 13)) + ), + new self( + 15, + [6, 26, 48, 70], + new EcBlocks(22, new EcBlock(5, 87), new EcBlock(1, 88)), + new EcBlocks(24, new EcBlock(5, 41), new EcBlock(5, 42)), + new EcBlocks(30, new EcBlock(5, 24), new EcBlock(7, 25)), + new EcBlocks(24, new EcBlock(11, 12), new EcBlock(7, 13)) + ), + new self( + 16, + [6, 26, 50, 74], + new EcBlocks(24, new EcBlock(5, 98), new EcBlock(1, 99)), + new EcBlocks(28, new EcBlock(7, 45), new EcBlock(3, 46)), + new EcBlocks(24, new EcBlock(15, 19), new EcBlock(2, 20)), + new EcBlocks(30, new EcBlock(3, 15), new EcBlock(13, 16)) + ), + new self( + 17, + [6, 30, 54, 78], + new EcBlocks(28, new EcBlock(1, 107), new EcBlock(5, 108)), + new EcBlocks(28, new EcBlock(10, 46), new EcBlock(1, 47)), + new EcBlocks(28, new EcBlock(1, 22), new EcBlock(15, 23)), + new EcBlocks(28, new EcBlock(2, 14), new EcBlock(17, 15)) + ), + new self( + 18, + [6, 30, 56, 82], + new EcBlocks(30, new EcBlock(5, 120), new EcBlock(1, 121)), + new EcBlocks(26, new EcBlock(9, 43), new EcBlock(4, 44)), + new EcBlocks(28, new EcBlock(17, 22), new EcBlock(1, 23)), + new EcBlocks(28, new EcBlock(2, 14), new EcBlock(19, 15)) + ), + new self( + 19, + [6, 30, 58, 86], + new EcBlocks(28, new EcBlock(3, 113), new EcBlock(4, 114)), + new EcBlocks(26, new EcBlock(3, 44), new EcBlock(11, 45)), + new EcBlocks(26, new EcBlock(17, 21), new EcBlock(4, 22)), + new EcBlocks(26, new EcBlock(9, 13), new EcBlock(16, 14)) + ), + new self( + 20, + [6, 34, 62, 90], + new EcBlocks(28, new EcBlock(3, 107), new EcBlock(5, 108)), + new EcBlocks(26, new EcBlock(3, 41), new EcBlock(13, 42)), + new EcBlocks(30, new EcBlock(15, 24), new EcBlock(5, 25)), + new EcBlocks(28, new EcBlock(15, 15), new EcBlock(10, 16)) + ), + new self( + 21, + [6, 28, 50, 72, 94], + new EcBlocks(28, new EcBlock(4, 116), new EcBlock(4, 117)), + new EcBlocks(26, new EcBlock(17, 42)), + new EcBlocks(28, new EcBlock(17, 22), new EcBlock(6, 23)), + new EcBlocks(30, new EcBlock(19, 16), new EcBlock(6, 17)) + ), + new self( + 22, + [6, 26, 50, 74, 98], + new EcBlocks(28, new EcBlock(2, 111), new EcBlock(7, 112)), + new EcBlocks(28, new EcBlock(17, 46)), + new EcBlocks(30, new EcBlock(7, 24), new EcBlock(16, 25)), + new EcBlocks(24, new EcBlock(34, 13)) + ), + new self( + 23, + [6, 30, 54, 78, 102], + new EcBlocks(30, new EcBlock(4, 121), new EcBlock(5, 122)), + new EcBlocks(28, new EcBlock(4, 47), new EcBlock(14, 48)), + new EcBlocks(30, new EcBlock(11, 24), new EcBlock(14, 25)), + new EcBlocks(30, new EcBlock(16, 15), new EcBlock(14, 16)) + ), + new self( + 24, + [6, 28, 54, 80, 106], + new EcBlocks(30, new EcBlock(6, 117), new EcBlock(4, 118)), + new EcBlocks(28, new EcBlock(6, 45), new EcBlock(14, 46)), + new EcBlocks(30, new EcBlock(11, 24), new EcBlock(16, 25)), + new EcBlocks(30, new EcBlock(30, 16), new EcBlock(2, 17)) + ), + new self( + 25, + [6, 32, 58, 84, 110], + new EcBlocks(26, new EcBlock(8, 106), new EcBlock(4, 107)), + new EcBlocks(28, new EcBlock(8, 47), new EcBlock(13, 48)), + new EcBlocks(30, new EcBlock(7, 24), new EcBlock(22, 25)), + new EcBlocks(30, new EcBlock(22, 15), new EcBlock(13, 16)) + ), + new self( + 26, + [6, 30, 58, 86, 114], + new EcBlocks(28, new EcBlock(10, 114), new EcBlock(2, 115)), + new EcBlocks(28, new EcBlock(19, 46), new EcBlock(4, 47)), + new EcBlocks(28, new EcBlock(28, 22), new EcBlock(6, 23)), + new EcBlocks(30, new EcBlock(33, 16), new EcBlock(4, 17)) + ), + new self( + 27, + [6, 34, 62, 90, 118], + new EcBlocks(30, new EcBlock(8, 122), new EcBlock(4, 123)), + new EcBlocks(28, new EcBlock(22, 45), new EcBlock(3, 46)), + new EcBlocks(30, new EcBlock(8, 23), new EcBlock(26, 24)), + new EcBlocks(30, new EcBlock(12, 15), new EcBlock(28, 16)) + ), + new self( + 28, + [6, 26, 50, 74, 98, 122], + new EcBlocks(30, new EcBlock(3, 117), new EcBlock(10, 118)), + new EcBlocks(28, new EcBlock(3, 45), new EcBlock(23, 46)), + new EcBlocks(30, new EcBlock(4, 24), new EcBlock(31, 25)), + new EcBlocks(30, new EcBlock(11, 15), new EcBlock(31, 16)) + ), + new self( + 29, + [6, 30, 54, 78, 102, 126], + new EcBlocks(30, new EcBlock(7, 116), new EcBlock(7, 117)), + new EcBlocks(28, new EcBlock(21, 45), new EcBlock(7, 46)), + new EcBlocks(30, new EcBlock(1, 23), new EcBlock(37, 24)), + new EcBlocks(30, new EcBlock(19, 15), new EcBlock(26, 16)) + ), + new self( + 30, + [6, 26, 52, 78, 104, 130], + new EcBlocks(30, new EcBlock(5, 115), new EcBlock(10, 116)), + new EcBlocks(28, new EcBlock(19, 47), new EcBlock(10, 48)), + new EcBlocks(30, new EcBlock(15, 24), new EcBlock(25, 25)), + new EcBlocks(30, new EcBlock(23, 15), new EcBlock(25, 16)) + ), + new self( + 31, + [6, 30, 56, 82, 108, 134], + new EcBlocks(30, new EcBlock(13, 115), new EcBlock(3, 116)), + new EcBlocks(28, new EcBlock(2, 46), new EcBlock(29, 47)), + new EcBlocks(30, new EcBlock(42, 24), new EcBlock(1, 25)), + new EcBlocks(30, new EcBlock(23, 15), new EcBlock(28, 16)) + ), + new self( + 32, + [6, 34, 60, 86, 112, 138], + new EcBlocks(30, new EcBlock(17, 115)), + new EcBlocks(28, new EcBlock(10, 46), new EcBlock(23, 47)), + new EcBlocks(30, new EcBlock(10, 24), new EcBlock(35, 25)), + new EcBlocks(30, new EcBlock(19, 15), new EcBlock(35, 16)) + ), + new self( + 33, + [6, 30, 58, 86, 114, 142], + new EcBlocks(30, new EcBlock(17, 115), new EcBlock(1, 116)), + new EcBlocks(28, new EcBlock(14, 46), new EcBlock(21, 47)), + new EcBlocks(30, new EcBlock(29, 24), new EcBlock(19, 25)), + new EcBlocks(30, new EcBlock(11, 15), new EcBlock(46, 16)) + ), + new self( + 34, + [6, 34, 62, 90, 118, 146], + new EcBlocks(30, new EcBlock(13, 115), new EcBlock(6, 116)), + new EcBlocks(28, new EcBlock(14, 46), new EcBlock(23, 47)), + new EcBlocks(30, new EcBlock(44, 24), new EcBlock(7, 25)), + new EcBlocks(30, new EcBlock(59, 16), new EcBlock(1, 17)) + ), + new self( + 35, + [6, 30, 54, 78, 102, 126, 150], + new EcBlocks(30, new EcBlock(12, 121), new EcBlock(7, 122)), + new EcBlocks(28, new EcBlock(12, 47), new EcBlock(26, 48)), + new EcBlocks(30, new EcBlock(39, 24), new EcBlock(14, 25)), + new EcBlocks(30, new EcBlock(22, 15), new EcBlock(41, 16)) + ), + new self( + 36, + [6, 24, 50, 76, 102, 128, 154], + new EcBlocks(30, new EcBlock(6, 121), new EcBlock(14, 122)), + new EcBlocks(28, new EcBlock(6, 47), new EcBlock(34, 48)), + new EcBlocks(30, new EcBlock(46, 24), new EcBlock(10, 25)), + new EcBlocks(30, new EcBlock(2, 15), new EcBlock(64, 16)) + ), + new self( + 37, + [6, 28, 54, 80, 106, 132, 158], + new EcBlocks(30, new EcBlock(17, 122), new EcBlock(4, 123)), + new EcBlocks(28, new EcBlock(29, 46), new EcBlock(14, 47)), + new EcBlocks(30, new EcBlock(49, 24), new EcBlock(10, 25)), + new EcBlocks(30, new EcBlock(24, 15), new EcBlock(46, 16)) + ), + new self( + 38, + [6, 32, 58, 84, 110, 136, 162], + new EcBlocks(30, new EcBlock(4, 122), new EcBlock(18, 123)), + new EcBlocks(28, new EcBlock(13, 46), new EcBlock(32, 47)), + new EcBlocks(30, new EcBlock(48, 24), new EcBlock(14, 25)), + new EcBlocks(30, new EcBlock(42, 15), new EcBlock(32, 16)) + ), + new self( + 39, + [6, 26, 54, 82, 110, 138, 166], + new EcBlocks(30, new EcBlock(20, 117), new EcBlock(4, 118)), + new EcBlocks(28, new EcBlock(40, 47), new EcBlock(7, 48)), + new EcBlocks(30, new EcBlock(43, 24), new EcBlock(22, 25)), + new EcBlocks(30, new EcBlock(10, 15), new EcBlock(67, 16)) + ), + new self( + 40, + [6, 30, 58, 86, 114, 142, 170], + new EcBlocks(30, new EcBlock(19, 118), new EcBlock(6, 119)), + new EcBlocks(28, new EcBlock(18, 47), new EcBlock(31, 48)), + new EcBlocks(30, new EcBlock(34, 24), new EcBlock(34, 25)), + new EcBlocks(30, new EcBlock(20, 15), new EcBlock(61, 16)) + ), + ]; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/BlockPair.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/BlockPair.php new file mode 100644 index 0000000000..be54afaa9f --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/BlockPair.php @@ -0,0 +1,58 @@ + + */ + private $dataBytes; + + /** + * Error correction bytes in the block. + * + * @var SplFixedArray + */ + private $errorCorrectionBytes; + + /** + * Creates a new block pair. + * + * @param SplFixedArray $data + * @param SplFixedArray $errorCorrection + */ + public function __construct(SplFixedArray $data, SplFixedArray $errorCorrection) + { + $this->dataBytes = $data; + $this->errorCorrectionBytes = $errorCorrection; + } + + /** + * Gets the data bytes. + * + * @return SplFixedArray + */ + public function getDataBytes() : SplFixedArray + { + return $this->dataBytes; + } + + /** + * Gets the error correction bytes. + * + * @return SplFixedArray + */ + public function getErrorCorrectionBytes() : SplFixedArray + { + return $this->errorCorrectionBytes; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/ByteMatrix.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/ByteMatrix.php new file mode 100644 index 0000000000..b58cc0ab25 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/ByteMatrix.php @@ -0,0 +1,150 @@ +> + */ + private $bytes; + + /** + * Width of the matrix. + * + * @var int + */ + private $width; + + /** + * Height of the matrix. + * + * @var int + */ + private $height; + + public function __construct(int $width, int $height) + { + $this->height = $height; + $this->width = $width; + $this->bytes = new SplFixedArray($height); + + for ($y = 0; $y < $height; ++$y) { + $this->bytes[$y] = SplFixedArray::fromArray(array_fill(0, $width, 0)); + } + } + + /** + * Gets the width of the matrix. + */ + public function getWidth() : int + { + return $this->width; + } + + /** + * Gets the height of the matrix. + */ + public function getHeight() : int + { + return $this->height; + } + + /** + * Gets the internal representation of the matrix. + * + * @return SplFixedArray> + */ + public function getArray() : SplFixedArray + { + return $this->bytes; + } + + /** + * @return Traversable + */ + public function getBytes() : Traversable + { + foreach ($this->bytes as $row) { + foreach ($row as $byte) { + yield $byte; + } + } + } + + /** + * Gets the byte for a specific position. + */ + public function get(int $x, int $y) : int + { + return $this->bytes[$y][$x]; + } + + /** + * Sets the byte for a specific position. + */ + public function set(int $x, int $y, int $value) : void + { + $this->bytes[$y][$x] = $value; + } + + /** + * Clears the matrix with a specific value. + */ + public function clear(int $value) : void + { + for ($y = 0; $y < $this->height; ++$y) { + for ($x = 0; $x < $this->width; ++$x) { + $this->bytes[$y][$x] = $value; + } + } + } + + public function __clone() + { + $this->bytes = clone $this->bytes; + + foreach ($this->bytes as $index => $row) { + $this->bytes[$index] = clone $row; + } + } + + /** + * Returns a string representation of the matrix. + */ + public function __toString() : string + { + $result = ''; + + for ($y = 0; $y < $this->height; $y++) { + for ($x = 0; $x < $this->width; $x++) { + switch ($this->bytes[$y][$x]) { + case 0: + $result .= ' 0'; + break; + + case 1: + $result .= ' 1'; + break; + + default: + $result .= ' '; + break; + } + } + + $result .= "\n"; + } + + return $result; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/Encoder.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/Encoder.php new file mode 100644 index 0000000000..320846003d --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/Encoder.php @@ -0,0 +1,668 @@ + + */ + private static $codecs = []; + + /** + * Encodes "content" with the error correction level "ecLevel". + */ + public static function encode( + string $content, + ErrorCorrectionLevel $ecLevel, + string $encoding = self::DEFAULT_BYTE_MODE_ECODING, + ?Version $forcedVersion = null + ) : QrCode { + // Pick an encoding mode appropriate for the content. Note that this + // will not attempt to use multiple modes / segments even if that were + // more efficient. Would be nice. + $mode = self::chooseMode($content, $encoding); + + // This will store the header information, like mode and length, as well + // as "header" segments like an ECI segment. + $headerBits = new BitArray(); + + // Append ECI segment if applicable + if (Mode::BYTE() === $mode && self::DEFAULT_BYTE_MODE_ECODING !== $encoding) { + $eci = CharacterSetEci::getCharacterSetEciByName($encoding); + + if (null !== $eci) { + self::appendEci($eci, $headerBits); + } + } + + // (With ECI in place,) Write the mode marker + self::appendModeInfo($mode, $headerBits); + + // Collect data within the main segment, separately, to count its size + // if needed. Don't add it to main payload yet. + $dataBits = new BitArray(); + self::appendBytes($content, $mode, $dataBits, $encoding); + + // Hard part: need to know version to know how many bits length takes. + // But need to know how many bits it takes to know version. First we + // take a guess at version by assuming version will be the minimum, 1: + $provisionalBitsNeeded = $headerBits->getSize() + + $mode->getCharacterCountBits(Version::getVersionForNumber(1)) + + $dataBits->getSize(); + $provisionalVersion = self::chooseVersion($provisionalBitsNeeded, $ecLevel); + + // Use that guess to calculate the right version. I am still not sure + // this works in 100% of cases. + $bitsNeeded = $headerBits->getSize() + + $mode->getCharacterCountBits($provisionalVersion) + + $dataBits->getSize(); + $version = self::chooseVersion($bitsNeeded, $ecLevel); + + if (null !== $forcedVersion) { + // Forced version check + if ($version->getVersionNumber() <= $forcedVersion->getVersionNumber()) { + // Calculated minimum version is same or equal as forced version + $version = $forcedVersion; + } else { + throw new WriterException( + 'Invalid version! Calculated version: ' + . $version->getVersionNumber() + . ', requested version: ' + . $forcedVersion->getVersionNumber() + ); + } + } + + $headerAndDataBits = new BitArray(); + $headerAndDataBits->appendBitArray($headerBits); + + // Find "length" of main segment and write it. + $numLetters = (Mode::BYTE() === $mode ? $dataBits->getSizeInBytes() : strlen($content)); + self::appendLengthInfo($numLetters, $version, $mode, $headerAndDataBits); + + // Put data together into the overall payload. + $headerAndDataBits->appendBitArray($dataBits); + $ecBlocks = $version->getEcBlocksForLevel($ecLevel); + $numDataBytes = $version->getTotalCodewords() - $ecBlocks->getTotalEcCodewords(); + + // Terminate the bits properly. + self::terminateBits($numDataBytes, $headerAndDataBits); + + // Interleave data bits with error correction code. + $finalBits = self::interleaveWithEcBytes( + $headerAndDataBits, + $version->getTotalCodewords(), + $numDataBytes, + $ecBlocks->getNumBlocks() + ); + + // Choose the mask pattern. + $dimension = $version->getDimensionForVersion(); + $matrix = new ByteMatrix($dimension, $dimension); + $maskPattern = self::chooseMaskPattern($finalBits, $ecLevel, $version, $matrix); + + // Build the matrix. + MatrixUtil::buildMatrix($finalBits, $ecLevel, $version, $maskPattern, $matrix); + + return new QrCode($mode, $ecLevel, $version, $maskPattern, $matrix); + } + + /** + * Gets the alphanumeric code for a byte. + */ + private static function getAlphanumericCode(int $code) : int + { + if (isset(self::ALPHANUMERIC_TABLE[$code])) { + return self::ALPHANUMERIC_TABLE[$code]; + } + + return -1; + } + + /** + * Chooses the best mode for a given content. + */ + private static function chooseMode(string $content, string $encoding = null) : Mode + { + if (null !== $encoding && 0 === strcasecmp($encoding, 'SHIFT-JIS')) { + return self::isOnlyDoubleByteKanji($content) ? Mode::KANJI() : Mode::BYTE(); + } + + $hasNumeric = false; + $hasAlphanumeric = false; + $contentLength = strlen($content); + + for ($i = 0; $i < $contentLength; ++$i) { + $char = $content[$i]; + + if (ctype_digit($char)) { + $hasNumeric = true; + } elseif (-1 !== self::getAlphanumericCode(ord($char))) { + $hasAlphanumeric = true; + } else { + return Mode::BYTE(); + } + } + + if ($hasAlphanumeric) { + return Mode::ALPHANUMERIC(); + } elseif ($hasNumeric) { + return Mode::NUMERIC(); + } + + return Mode::BYTE(); + } + + /** + * Calculates the mask penalty for a matrix. + */ + private static function calculateMaskPenalty(ByteMatrix $matrix) : int + { + return ( + MaskUtil::applyMaskPenaltyRule1($matrix) + + MaskUtil::applyMaskPenaltyRule2($matrix) + + MaskUtil::applyMaskPenaltyRule3($matrix) + + MaskUtil::applyMaskPenaltyRule4($matrix) + ); + } + + /** + * Checks if content only consists of double-byte kanji characters. + */ + private static function isOnlyDoubleByteKanji(string $content) : bool + { + $bytes = @iconv('utf-8', 'SHIFT-JIS', $content); + + if (false === $bytes) { + return false; + } + + $length = strlen($bytes); + + if (0 !== $length % 2) { + return false; + } + + for ($i = 0; $i < $length; $i += 2) { + $byte = $bytes[$i] & 0xff; + + if (($byte < 0x81 || $byte > 0x9f) && $byte < 0xe0 || $byte > 0xeb) { + return false; + } + } + + return true; + } + + /** + * Chooses the best mask pattern for a matrix. + */ + private static function chooseMaskPattern( + BitArray $bits, + ErrorCorrectionLevel $ecLevel, + Version $version, + ByteMatrix $matrix + ) : int { + $minPenalty = PHP_INT_MAX; + $bestMaskPattern = -1; + + for ($maskPattern = 0; $maskPattern < QrCode::NUM_MASK_PATTERNS; ++$maskPattern) { + MatrixUtil::buildMatrix($bits, $ecLevel, $version, $maskPattern, $matrix); + $penalty = self::calculateMaskPenalty($matrix); + + if ($penalty < $minPenalty) { + $minPenalty = $penalty; + $bestMaskPattern = $maskPattern; + } + } + + return $bestMaskPattern; + } + + /** + * Chooses the best version for the input. + * + * @throws WriterException if data is too big + */ + private static function chooseVersion(int $numInputBits, ErrorCorrectionLevel $ecLevel) : Version + { + for ($versionNum = 1; $versionNum <= 40; ++$versionNum) { + $version = Version::getVersionForNumber($versionNum); + $numBytes = $version->getTotalCodewords(); + + $ecBlocks = $version->getEcBlocksForLevel($ecLevel); + $numEcBytes = $ecBlocks->getTotalEcCodewords(); + + $numDataBytes = $numBytes - $numEcBytes; + $totalInputBytes = intdiv($numInputBits + 8, 8); + + if ($numDataBytes >= $totalInputBytes) { + return $version; + } + } + + throw new WriterException('Data too big'); + } + + /** + * Terminates the bits in a bit array. + * + * @throws WriterException if data bits cannot fit in the QR code + * @throws WriterException if bits size does not equal the capacity + */ + private static function terminateBits(int $numDataBytes, BitArray $bits) : void + { + $capacity = $numDataBytes << 3; + + if ($bits->getSize() > $capacity) { + throw new WriterException('Data bits cannot fit in the QR code'); + } + + for ($i = 0; $i < 4 && $bits->getSize() < $capacity; ++$i) { + $bits->appendBit(false); + } + + $numBitsInLastByte = $bits->getSize() & 0x7; + + if ($numBitsInLastByte > 0) { + for ($i = $numBitsInLastByte; $i < 8; ++$i) { + $bits->appendBit(false); + } + } + + $numPaddingBytes = $numDataBytes - $bits->getSizeInBytes(); + + for ($i = 0; $i < $numPaddingBytes; ++$i) { + $bits->appendBits(0 === ($i & 0x1) ? 0xec : 0x11, 8); + } + + if ($bits->getSize() !== $capacity) { + throw new WriterException('Bits size does not equal capacity'); + } + } + + /** + * Gets number of data- and EC bytes for a block ID. + * + * @return int[] + * @throws WriterException if block ID is too large + * @throws WriterException if EC bytes mismatch + * @throws WriterException if RS blocks mismatch + * @throws WriterException if total bytes mismatch + */ + private static function getNumDataBytesAndNumEcBytesForBlockId( + int $numTotalBytes, + int $numDataBytes, + int $numRsBlocks, + int $blockId + ) : array { + if ($blockId >= $numRsBlocks) { + throw new WriterException('Block ID too large'); + } + + $numRsBlocksInGroup2 = $numTotalBytes % $numRsBlocks; + $numRsBlocksInGroup1 = $numRsBlocks - $numRsBlocksInGroup2; + $numTotalBytesInGroup1 = intdiv($numTotalBytes, $numRsBlocks); + $numTotalBytesInGroup2 = $numTotalBytesInGroup1 + 1; + $numDataBytesInGroup1 = intdiv($numDataBytes, $numRsBlocks); + $numDataBytesInGroup2 = $numDataBytesInGroup1 + 1; + $numEcBytesInGroup1 = $numTotalBytesInGroup1 - $numDataBytesInGroup1; + $numEcBytesInGroup2 = $numTotalBytesInGroup2 - $numDataBytesInGroup2; + + if ($numEcBytesInGroup1 !== $numEcBytesInGroup2) { + throw new WriterException('EC bytes mismatch'); + } + + if ($numRsBlocks !== $numRsBlocksInGroup1 + $numRsBlocksInGroup2) { + throw new WriterException('RS blocks mismatch'); + } + + if ($numTotalBytes !== + (($numDataBytesInGroup1 + $numEcBytesInGroup1) * $numRsBlocksInGroup1) + + (($numDataBytesInGroup2 + $numEcBytesInGroup2) * $numRsBlocksInGroup2) + ) { + throw new WriterException('Total bytes mismatch'); + } + + if ($blockId < $numRsBlocksInGroup1) { + return [$numDataBytesInGroup1, $numEcBytesInGroup1]; + } else { + return [$numDataBytesInGroup2, $numEcBytesInGroup2]; + } + } + + /** + * Interleaves data with EC bytes. + * + * @throws WriterException if number of bits and data bytes does not match + * @throws WriterException if data bytes does not match offset + * @throws WriterException if an interleaving error occurs + */ + private static function interleaveWithEcBytes( + BitArray $bits, + int $numTotalBytes, + int $numDataBytes, + int $numRsBlocks + ) : BitArray { + if ($bits->getSizeInBytes() !== $numDataBytes) { + throw new WriterException('Number of bits and data bytes does not match'); + } + + $dataBytesOffset = 0; + $maxNumDataBytes = 0; + $maxNumEcBytes = 0; + + $blocks = new SplFixedArray($numRsBlocks); + + for ($i = 0; $i < $numRsBlocks; ++$i) { + list($numDataBytesInBlock, $numEcBytesInBlock) = self::getNumDataBytesAndNumEcBytesForBlockId( + $numTotalBytes, + $numDataBytes, + $numRsBlocks, + $i + ); + + $size = $numDataBytesInBlock; + $dataBytes = $bits->toBytes(8 * $dataBytesOffset, $size); + $ecBytes = self::generateEcBytes($dataBytes, $numEcBytesInBlock); + $blocks[$i] = new BlockPair($dataBytes, $ecBytes); + + $maxNumDataBytes = max($maxNumDataBytes, $size); + $maxNumEcBytes = max($maxNumEcBytes, count($ecBytes)); + $dataBytesOffset += $numDataBytesInBlock; + } + + if ($numDataBytes !== $dataBytesOffset) { + throw new WriterException('Data bytes does not match offset'); + } + + $result = new BitArray(); + + for ($i = 0; $i < $maxNumDataBytes; ++$i) { + foreach ($blocks as $block) { + $dataBytes = $block->getDataBytes(); + + if ($i < count($dataBytes)) { + $result->appendBits($dataBytes[$i], 8); + } + } + } + + for ($i = 0; $i < $maxNumEcBytes; ++$i) { + foreach ($blocks as $block) { + $ecBytes = $block->getErrorCorrectionBytes(); + + if ($i < count($ecBytes)) { + $result->appendBits($ecBytes[$i], 8); + } + } + } + + if ($numTotalBytes !== $result->getSizeInBytes()) { + throw new WriterException( + 'Interleaving error: ' . $numTotalBytes . ' and ' . $result->getSizeInBytes() . ' differ' + ); + } + + return $result; + } + + /** + * Generates EC bytes for given data. + * + * @param SplFixedArray $dataBytes + * @return SplFixedArray + */ + private static function generateEcBytes(SplFixedArray $dataBytes, int $numEcBytesInBlock) : SplFixedArray + { + $numDataBytes = count($dataBytes); + $toEncode = new SplFixedArray($numDataBytes + $numEcBytesInBlock); + + for ($i = 0; $i < $numDataBytes; $i++) { + $toEncode[$i] = $dataBytes[$i] & 0xff; + } + + $ecBytes = new SplFixedArray($numEcBytesInBlock); + $codec = self::getCodec($numDataBytes, $numEcBytesInBlock); + $codec->encode($toEncode, $ecBytes); + + return $ecBytes; + } + + /** + * Gets an RS codec and caches it. + */ + private static function getCodec(int $numDataBytes, int $numEcBytesInBlock) : ReedSolomonCodec + { + $cacheId = $numDataBytes . '-' . $numEcBytesInBlock; + + if (isset(self::$codecs[$cacheId])) { + return self::$codecs[$cacheId]; + } + + return self::$codecs[$cacheId] = new ReedSolomonCodec( + 8, + 0x11d, + 0, + 1, + $numEcBytesInBlock, + 255 - $numDataBytes - $numEcBytesInBlock + ); + } + + /** + * Appends mode information to a bit array. + */ + private static function appendModeInfo(Mode $mode, BitArray $bits) : void + { + $bits->appendBits($mode->getBits(), 4); + } + + /** + * Appends length information to a bit array. + * + * @throws WriterException if num letters is bigger than expected + */ + private static function appendLengthInfo(int $numLetters, Version $version, Mode $mode, BitArray $bits) : void + { + $numBits = $mode->getCharacterCountBits($version); + + if ($numLetters >= (1 << $numBits)) { + throw new WriterException($numLetters . ' is bigger than ' . ((1 << $numBits) - 1)); + } + + $bits->appendBits($numLetters, $numBits); + } + + /** + * Appends bytes to a bit array in a specific mode. + * + * @throws WriterException if an invalid mode was supplied + */ + private static function appendBytes(string $content, Mode $mode, BitArray $bits, string $encoding) : void + { + switch ($mode) { + case Mode::NUMERIC(): + self::appendNumericBytes($content, $bits); + break; + + case Mode::ALPHANUMERIC(): + self::appendAlphanumericBytes($content, $bits); + break; + + case Mode::BYTE(): + self::append8BitBytes($content, $bits, $encoding); + break; + + case Mode::KANJI(): + self::appendKanjiBytes($content, $bits); + break; + + default: + throw new WriterException('Invalid mode: ' . $mode); + } + } + + /** + * Appends numeric bytes to a bit array. + */ + private static function appendNumericBytes(string $content, BitArray $bits) : void + { + $length = strlen($content); + $i = 0; + + while ($i < $length) { + $num1 = (int) $content[$i]; + + if ($i + 2 < $length) { + // Encode three numeric letters in ten bits. + $num2 = (int) $content[$i + 1]; + $num3 = (int) $content[$i + 2]; + $bits->appendBits($num1 * 100 + $num2 * 10 + $num3, 10); + $i += 3; + } elseif ($i + 1 < $length) { + // Encode two numeric letters in seven bits. + $num2 = (int) $content[$i + 1]; + $bits->appendBits($num1 * 10 + $num2, 7); + $i += 2; + } else { + // Encode one numeric letter in four bits. + $bits->appendBits($num1, 4); + ++$i; + } + } + } + + /** + * Appends alpha-numeric bytes to a bit array. + * + * @throws WriterException if an invalid alphanumeric code was found + */ + private static function appendAlphanumericBytes(string $content, BitArray $bits) : void + { + $length = strlen($content); + $i = 0; + + while ($i < $length) { + $code1 = self::getAlphanumericCode(ord($content[$i])); + + if (-1 === $code1) { + throw new WriterException('Invalid alphanumeric code'); + } + + if ($i + 1 < $length) { + $code2 = self::getAlphanumericCode(ord($content[$i + 1])); + + if (-1 === $code2) { + throw new WriterException('Invalid alphanumeric code'); + } + + // Encode two alphanumeric letters in 11 bits. + $bits->appendBits($code1 * 45 + $code2, 11); + $i += 2; + } else { + // Encode one alphanumeric letter in six bits. + $bits->appendBits($code1, 6); + ++$i; + } + } + } + + /** + * Appends regular 8-bit bytes to a bit array. + * + * @throws WriterException if content cannot be encoded to target encoding + */ + private static function append8BitBytes(string $content, BitArray $bits, string $encoding) : void + { + $bytes = @iconv('utf-8', $encoding, $content); + + if (false === $bytes) { + throw new WriterException('Could not encode content to ' . $encoding); + } + + $length = strlen($bytes); + + for ($i = 0; $i < $length; $i++) { + $bits->appendBits(ord($bytes[$i]), 8); + } + } + + /** + * Appends KANJI bytes to a bit array. + * + * @throws WriterException if content does not seem to be encoded in SHIFT-JIS + * @throws WriterException if an invalid byte sequence occurs + */ + private static function appendKanjiBytes(string $content, BitArray $bits) : void + { + if (strlen($content) % 2 > 0) { + // We just do a simple length check here. The for loop will check + // individual characters. + throw new WriterException('Content does not seem to be encoded in SHIFT-JIS'); + } + + $length = strlen($content); + + for ($i = 0; $i < $length; $i += 2) { + $byte1 = ord($content[$i]) & 0xff; + $byte2 = ord($content[$i + 1]) & 0xff; + $code = ($byte1 << 8) | $byte2; + + if ($code >= 0x8140 && $code <= 0x9ffc) { + $subtracted = $code - 0x8140; + } elseif ($code >= 0xe040 && $code <= 0xebbf) { + $subtracted = $code - 0xc140; + } else { + throw new WriterException('Invalid byte sequence'); + } + + $encoded = (($subtracted >> 8) * 0xc0) + ($subtracted & 0xff); + + $bits->appendBits($encoded, 13); + } + } + + /** + * Appends ECI information to a bit array. + */ + private static function appendEci(CharacterSetEci $eci, BitArray $bits) : void + { + $mode = Mode::ECI(); + $bits->appendBits($mode->getBits(), 4); + $bits->appendBits($eci->getValue(), 8); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/MaskUtil.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/MaskUtil.php new file mode 100644 index 0000000000..ba97dfb79e --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/MaskUtil.php @@ -0,0 +1,271 @@ +getArray(); + $width = $matrix->getWidth(); + $height = $matrix->getHeight(); + + for ($y = 0; $y < $height - 1; ++$y) { + for ($x = 0; $x < $width - 1; ++$x) { + $value = $array[$y][$x]; + + if ($value === $array[$y][$x + 1] + && $value === $array[$y + 1][$x] + && $value === $array[$y + 1][$x + 1] + ) { + ++$penalty; + } + } + } + + return self::N2 * $penalty; + } + + /** + * Applies mask penalty rule 3 and returns the penalty. + * + * Finds consecutive cells of 00001011101 or 10111010000, and gives penalty + * to them. If we find patterns like 000010111010000, we give penalties + * twice (i.e. 40 * 2). + */ + public static function applyMaskPenaltyRule3(ByteMatrix $matrix) : int + { + $penalty = 0; + $array = $matrix->getArray(); + $width = $matrix->getWidth(); + $height = $matrix->getHeight(); + + for ($y = 0; $y < $height; ++$y) { + for ($x = 0; $x < $width; ++$x) { + if ($x + 6 < $width + && 1 === $array[$y][$x] + && 0 === $array[$y][$x + 1] + && 1 === $array[$y][$x + 2] + && 1 === $array[$y][$x + 3] + && 1 === $array[$y][$x + 4] + && 0 === $array[$y][$x + 5] + && 1 === $array[$y][$x + 6] + && ( + ( + $x + 10 < $width + && 0 === $array[$y][$x + 7] + && 0 === $array[$y][$x + 8] + && 0 === $array[$y][$x + 9] + && 0 === $array[$y][$x + 10] + ) + || ( + $x - 4 >= 0 + && 0 === $array[$y][$x - 1] + && 0 === $array[$y][$x - 2] + && 0 === $array[$y][$x - 3] + && 0 === $array[$y][$x - 4] + ) + ) + ) { + $penalty += self::N3; + } + + if ($y + 6 < $height + && 1 === $array[$y][$x] + && 0 === $array[$y + 1][$x] + && 1 === $array[$y + 2][$x] + && 1 === $array[$y + 3][$x] + && 1 === $array[$y + 4][$x] + && 0 === $array[$y + 5][$x] + && 1 === $array[$y + 6][$x] + && ( + ( + $y + 10 < $height + && 0 === $array[$y + 7][$x] + && 0 === $array[$y + 8][$x] + && 0 === $array[$y + 9][$x] + && 0 === $array[$y + 10][$x] + ) + || ( + $y - 4 >= 0 + && 0 === $array[$y - 1][$x] + && 0 === $array[$y - 2][$x] + && 0 === $array[$y - 3][$x] + && 0 === $array[$y - 4][$x] + ) + ) + ) { + $penalty += self::N3; + } + } + } + + return $penalty; + } + + /** + * Applies mask penalty rule 4 and returns the penalty. + * + * Calculates the ratio of dark cells and gives penalty if the ratio is far + * from 50%. It gives 10 penalty for 5% distance. + */ + public static function applyMaskPenaltyRule4(ByteMatrix $matrix) : int + { + $numDarkCells = 0; + + $array = $matrix->getArray(); + $width = $matrix->getWidth(); + $height = $matrix->getHeight(); + + for ($y = 0; $y < $height; ++$y) { + $arrayY = $array[$y]; + + for ($x = 0; $x < $width; ++$x) { + if (1 === $arrayY[$x]) { + ++$numDarkCells; + } + } + } + + $numTotalCells = $height * $width; + $darkRatio = $numDarkCells / $numTotalCells; + $fixedPercentVariances = (int) (abs($darkRatio - 0.5) * 20); + + return $fixedPercentVariances * self::N4; + } + + /** + * Returns the mask bit for "getMaskPattern" at "x" and "y". + * + * See 8.8 of JISX0510:2004 for mask pattern conditions. + * + * @throws InvalidArgumentException if an invalid mask pattern was supplied + */ + public static function getDataMaskBit(int $maskPattern, int $x, int $y) : bool + { + switch ($maskPattern) { + case 0: + $intermediate = ($y + $x) & 0x1; + break; + + case 1: + $intermediate = $y & 0x1; + break; + + case 2: + $intermediate = $x % 3; + break; + + case 3: + $intermediate = ($y + $x) % 3; + break; + + case 4: + $intermediate = (BitUtils::unsignedRightShift($y, 1) + (int) ($x / 3)) & 0x1; + break; + + case 5: + $temp = $y * $x; + $intermediate = ($temp & 0x1) + ($temp % 3); + break; + + case 6: + $temp = $y * $x; + $intermediate = (($temp & 0x1) + ($temp % 3)) & 0x1; + break; + + case 7: + $temp = $y * $x; + $intermediate = (($temp % 3) + (($y + $x) & 0x1)) & 0x1; + break; + + default: + throw new InvalidArgumentException('Invalid mask pattern: ' . $maskPattern); + } + + return 0 == $intermediate; + } + + /** + * Helper function for applyMaskPenaltyRule1. + * + * We need this for doing this calculation in both vertical and horizontal + * orders respectively. + */ + private static function applyMaskPenaltyRule1Internal(ByteMatrix $matrix, bool $isHorizontal) : int + { + $penalty = 0; + $iLimit = $isHorizontal ? $matrix->getHeight() : $matrix->getWidth(); + $jLimit = $isHorizontal ? $matrix->getWidth() : $matrix->getHeight(); + $array = $matrix->getArray(); + + for ($i = 0; $i < $iLimit; ++$i) { + $numSameBitCells = 0; + $prevBit = -1; + + for ($j = 0; $j < $jLimit; $j++) { + $bit = $isHorizontal ? $array[$i][$j] : $array[$j][$i]; + + if ($bit === $prevBit) { + ++$numSameBitCells; + } else { + if ($numSameBitCells >= 5) { + $penalty += self::N1 + ($numSameBitCells - 5); + } + + $numSameBitCells = 1; + $prevBit = $bit; + } + } + + if ($numSameBitCells >= 5) { + $penalty += self::N1 + ($numSameBitCells - 5); + } + } + + return $penalty; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/MatrixUtil.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/MatrixUtil.php new file mode 100644 index 0000000000..0967e298ac --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/MatrixUtil.php @@ -0,0 +1,513 @@ +clear(-1); + } + + /** + * Builds a complete matrix. + */ + public static function buildMatrix( + BitArray $dataBits, + ErrorCorrectionLevel $level, + Version $version, + int $maskPattern, + ByteMatrix $matrix + ) : void { + self::clearMatrix($matrix); + self::embedBasicPatterns($version, $matrix); + self::embedTypeInfo($level, $maskPattern, $matrix); + self::maybeEmbedVersionInfo($version, $matrix); + self::embedDataBits($dataBits, $maskPattern, $matrix); + } + + /** + * Removes the position detection patterns from a matrix. + * + * This can be useful if you need to render those patterns separately. + */ + public static function removePositionDetectionPatterns(ByteMatrix $matrix) : void + { + $pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]); + + self::removePositionDetectionPattern(0, 0, $matrix); + self::removePositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix); + self::removePositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix); + } + + /** + * Embeds type information into a matrix. + */ + private static function embedTypeInfo(ErrorCorrectionLevel $level, int $maskPattern, ByteMatrix $matrix) : void + { + $typeInfoBits = new BitArray(); + self::makeTypeInfoBits($level, $maskPattern, $typeInfoBits); + + $typeInfoBitsSize = $typeInfoBits->getSize(); + + for ($i = 0; $i < $typeInfoBitsSize; ++$i) { + $bit = $typeInfoBits->get($typeInfoBitsSize - 1 - $i); + + $x1 = self::TYPE_INFO_COORDINATES[$i][0]; + $y1 = self::TYPE_INFO_COORDINATES[$i][1]; + + $matrix->set($x1, $y1, (int) $bit); + + if ($i < 8) { + $x2 = $matrix->getWidth() - $i - 1; + $y2 = 8; + } else { + $x2 = 8; + $y2 = $matrix->getHeight() - 7 + ($i - 8); + } + + $matrix->set($x2, $y2, (int) $bit); + } + } + + /** + * Generates type information bits and appends them to a bit array. + * + * @throws RuntimeException if bit array resulted in invalid size + */ + private static function makeTypeInfoBits(ErrorCorrectionLevel $level, int $maskPattern, BitArray $bits) : void + { + $typeInfo = ($level->getBits() << 3) | $maskPattern; + $bits->appendBits($typeInfo, 5); + + $bchCode = self::calculateBchCode($typeInfo, self::TYPE_INFO_POLY); + $bits->appendBits($bchCode, 10); + + $maskBits = new BitArray(); + $maskBits->appendBits(self::TYPE_INFO_MASK_PATTERN, 15); + $bits->xorBits($maskBits); + + if (15 !== $bits->getSize()) { + throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize()); + } + } + + /** + * Embeds version information if required. + */ + private static function maybeEmbedVersionInfo(Version $version, ByteMatrix $matrix) : void + { + if ($version->getVersionNumber() < 7) { + return; + } + + $versionInfoBits = new BitArray(); + self::makeVersionInfoBits($version, $versionInfoBits); + + $bitIndex = 6 * 3 - 1; + + for ($i = 0; $i < 6; ++$i) { + for ($j = 0; $j < 3; ++$j) { + $bit = $versionInfoBits->get($bitIndex); + --$bitIndex; + + $matrix->set($i, $matrix->getHeight() - 11 + $j, (int) $bit); + $matrix->set($matrix->getHeight() - 11 + $j, $i, (int) $bit); + } + } + } + + /** + * Generates version information bits and appends them to a bit array. + * + * @throws RuntimeException if bit array resulted in invalid size + */ + private static function makeVersionInfoBits(Version $version, BitArray $bits) : void + { + $bits->appendBits($version->getVersionNumber(), 6); + + $bchCode = self::calculateBchCode($version->getVersionNumber(), self::VERSION_INFO_POLY); + $bits->appendBits($bchCode, 12); + + if (18 !== $bits->getSize()) { + throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize()); + } + } + + /** + * Calculates the BCH code for a value and a polynomial. + */ + private static function calculateBchCode(int $value, int $poly) : int + { + $msbSetInPoly = self::findMsbSet($poly); + $value <<= $msbSetInPoly - 1; + + while (self::findMsbSet($value) >= $msbSetInPoly) { + $value ^= $poly << (self::findMsbSet($value) - $msbSetInPoly); + } + + return $value; + } + + /** + * Finds and MSB set. + */ + private static function findMsbSet(int $value) : int + { + $numDigits = 0; + + while (0 !== $value) { + $value >>= 1; + ++$numDigits; + } + + return $numDigits; + } + + /** + * Embeds basic patterns into a matrix. + */ + private static function embedBasicPatterns(Version $version, ByteMatrix $matrix) : void + { + self::embedPositionDetectionPatternsAndSeparators($matrix); + self::embedDarkDotAtLeftBottomCorner($matrix); + self::maybeEmbedPositionAdjustmentPatterns($version, $matrix); + self::embedTimingPatterns($matrix); + } + + /** + * Embeds position detection patterns and separators into a byte matrix. + */ + private static function embedPositionDetectionPatternsAndSeparators(ByteMatrix $matrix) : void + { + $pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]); + + self::embedPositionDetectionPattern(0, 0, $matrix); + self::embedPositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix); + self::embedPositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix); + + $hspWidth = 8; + + self::embedHorizontalSeparationPattern(0, $hspWidth - 1, $matrix); + self::embedHorizontalSeparationPattern($matrix->getWidth() - $hspWidth, $hspWidth - 1, $matrix); + self::embedHorizontalSeparationPattern(0, $matrix->getWidth() - $hspWidth, $matrix); + + $vspSize = 7; + + self::embedVerticalSeparationPattern($vspSize, 0, $matrix); + self::embedVerticalSeparationPattern($matrix->getHeight() - $vspSize - 1, 0, $matrix); + self::embedVerticalSeparationPattern($vspSize, $matrix->getHeight() - $vspSize, $matrix); + } + + /** + * Embeds a single position detection pattern into a byte matrix. + */ + private static function embedPositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void + { + for ($y = 0; $y < 7; ++$y) { + for ($x = 0; $x < 7; ++$x) { + $matrix->set($xStart + $x, $yStart + $y, self::POSITION_DETECTION_PATTERN[$y][$x]); + } + } + } + + private static function removePositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void + { + for ($y = 0; $y < 7; ++$y) { + for ($x = 0; $x < 7; ++$x) { + $matrix->set($xStart + $x, $yStart + $y, 0); + } + } + } + + /** + * Embeds a single horizontal separation pattern. + * + * @throws RuntimeException if a byte was already set + */ + private static function embedHorizontalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void + { + for ($x = 0; $x < 8; $x++) { + if (-1 !== $matrix->get($xStart + $x, $yStart)) { + throw new RuntimeException('Byte already set'); + } + + $matrix->set($xStart + $x, $yStart, 0); + } + } + + /** + * Embeds a single vertical separation pattern. + * + * @throws RuntimeException if a byte was already set + */ + private static function embedVerticalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void + { + for ($y = 0; $y < 7; $y++) { + if (-1 !== $matrix->get($xStart, $yStart + $y)) { + throw new RuntimeException('Byte already set'); + } + + $matrix->set($xStart, $yStart + $y, 0); + } + } + + /** + * Embeds a dot at the left bottom corner. + * + * @throws RuntimeException if a byte was already set to 0 + */ + private static function embedDarkDotAtLeftBottomCorner(ByteMatrix $matrix) : void + { + if (0 === $matrix->get(8, $matrix->getHeight() - 8)) { + throw new RuntimeException('Byte already set to 0'); + } + + $matrix->set(8, $matrix->getHeight() - 8, 1); + } + + /** + * Embeds position adjustment patterns if required. + */ + private static function maybeEmbedPositionAdjustmentPatterns(Version $version, ByteMatrix $matrix) : void + { + if ($version->getVersionNumber() < 2) { + return; + } + + $index = $version->getVersionNumber() - 1; + + $coordinates = self::POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[$index]; + $numCoordinates = count($coordinates); + + for ($i = 0; $i < $numCoordinates; ++$i) { + for ($j = 0; $j < $numCoordinates; ++$j) { + $y = $coordinates[$i]; + $x = $coordinates[$j]; + + if (null === $x || null === $y) { + continue; + } + + if (-1 === $matrix->get($x, $y)) { + self::embedPositionAdjustmentPattern($x - 2, $y - 2, $matrix); + } + } + } + } + + /** + * Embeds a single position adjustment pattern. + */ + private static function embedPositionAdjustmentPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void + { + for ($y = 0; $y < 5; $y++) { + for ($x = 0; $x < 5; $x++) { + $matrix->set($xStart + $x, $yStart + $y, self::POSITION_ADJUSTMENT_PATTERN[$y][$x]); + } + } + } + + /** + * Embeds timing patterns into a matrix. + */ + private static function embedTimingPatterns(ByteMatrix $matrix) : void + { + $matrixWidth = $matrix->getWidth(); + + for ($i = 8; $i < $matrixWidth - 8; ++$i) { + $bit = ($i + 1) % 2; + + if (-1 === $matrix->get($i, 6)) { + $matrix->set($i, 6, $bit); + } + + if (-1 === $matrix->get(6, $i)) { + $matrix->set(6, $i, $bit); + } + } + } + + /** + * Embeds "dataBits" using "getMaskPattern". + * + * For debugging purposes, it skips masking process if "getMaskPattern" is -1. See 8.7 of JISX0510:2004 (p.38) for + * how to embed data bits. + * + * @throws WriterException if not all bits could be consumed + */ + private static function embedDataBits(BitArray $dataBits, int $maskPattern, ByteMatrix $matrix) : void + { + $bitIndex = 0; + $direction = -1; + + // Start from the right bottom cell. + $x = $matrix->getWidth() - 1; + $y = $matrix->getHeight() - 1; + + while ($x > 0) { + // Skip vertical timing pattern. + if (6 === $x) { + --$x; + } + + while ($y >= 0 && $y < $matrix->getHeight()) { + for ($i = 0; $i < 2; $i++) { + $xx = $x - $i; + + // Skip the cell if it's not empty. + if (-1 !== $matrix->get($xx, $y)) { + continue; + } + + if ($bitIndex < $dataBits->getSize()) { + $bit = $dataBits->get($bitIndex); + ++$bitIndex; + } else { + // Padding bit. If there is no bit left, we'll fill the + // left cells with 0, as described in 8.4.9 of + // JISX0510:2004 (p. 24). + $bit = false; + } + + // Skip masking if maskPattern is -1. + if (-1 !== $maskPattern && MaskUtil::getDataMaskBit($maskPattern, $xx, $y)) { + $bit = ! $bit; + } + + $matrix->set($xx, $y, (int) $bit); + } + + $y += $direction; + } + + $direction = -$direction; + $y += $direction; + $x -= 2; + } + + // All bits should be consumed + if ($dataBits->getSize() !== $bitIndex) { + throw new WriterException('Not all bits consumed (' . $bitIndex . ' out of ' . $dataBits->getSize() .')'); + } + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/QrCode.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/QrCode.php new file mode 100644 index 0000000000..f568e88cf8 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Encoder/QrCode.php @@ -0,0 +1,141 @@ +mode = $mode; + $this->errorCorrectionLevel = $errorCorrectionLevel; + $this->version = $version; + $this->maskPattern = $maskPattern; + $this->matrix = $matrix; + } + + /** + * Gets the mode. + */ + public function getMode() : Mode + { + return $this->mode; + } + + /** + * Gets the EC level. + */ + public function getErrorCorrectionLevel() : ErrorCorrectionLevel + { + return $this->errorCorrectionLevel; + } + + /** + * Gets the version. + */ + public function getVersion() : Version + { + return $this->version; + } + + /** + * Gets the mask pattern. + */ + public function getMaskPattern() : int + { + return $this->maskPattern; + } + + /** + * Gets the matrix. + * + * @return ByteMatrix + */ + public function getMatrix() + { + return $this->matrix; + } + + /** + * Validates whether a mask pattern is valid. + */ + public static function isValidMaskPattern(int $maskPattern) : bool + { + return $maskPattern > 0 && $maskPattern < self::NUM_MASK_PATTERNS; + } + + /** + * Returns a string representation of the QR code. + */ + public function __toString() : string + { + $result = "<<\n" + . ' mode: ' . $this->mode . "\n" + . ' ecLevel: ' . $this->errorCorrectionLevel . "\n" + . ' version: ' . $this->version . "\n" + . ' maskPattern: ' . $this->maskPattern . "\n"; + + if ($this->matrix === null) { + $result .= " matrix: null\n"; + } else { + $result .= " matrix:\n"; + $result .= $this->matrix; + } + + $result .= ">>\n"; + + return $result; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Exception/ExceptionInterface.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..6f70c2055d --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Exception/ExceptionInterface.php @@ -0,0 +1,10 @@ + 100) { + throw new Exception\InvalidArgumentException('Alpha must be between 0 and 100'); + } + + $this->alpha = $alpha; + $this->baseColor = $baseColor; + } + + public function getAlpha() : int + { + return $this->alpha; + } + + public function getBaseColor() : ColorInterface + { + return $this->baseColor; + } + + public function toRgb() : Rgb + { + return $this->baseColor->toRgb(); + } + + public function toCmyk() : Cmyk + { + return $this->baseColor->toCmyk(); + } + + public function toGray() : Gray + { + return $this->baseColor->toGray(); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Color/Cmyk.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Color/Cmyk.php new file mode 100644 index 0000000000..d6de390ada --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Color/Cmyk.php @@ -0,0 +1,103 @@ + 100) { + throw new Exception\InvalidArgumentException('Cyan must be between 0 and 100'); + } + + if ($magenta < 0 || $magenta > 100) { + throw new Exception\InvalidArgumentException('Magenta must be between 0 and 100'); + } + + if ($yellow < 0 || $yellow > 100) { + throw new Exception\InvalidArgumentException('Yellow must be between 0 and 100'); + } + + if ($black < 0 || $black > 100) { + throw new Exception\InvalidArgumentException('Black must be between 0 and 100'); + } + + $this->cyan = $cyan; + $this->magenta = $magenta; + $this->yellow = $yellow; + $this->black = $black; + } + + public function getCyan() : int + { + return $this->cyan; + } + + public function getMagenta() : int + { + return $this->magenta; + } + + public function getYellow() : int + { + return $this->yellow; + } + + public function getBlack() : int + { + return $this->black; + } + + public function toRgb() : Rgb + { + $k = $this->black / 100; + $c = (-$k * $this->cyan + $k * 100 + $this->cyan) / 100; + $m = (-$k * $this->magenta + $k * 100 + $this->magenta) / 100; + $y = (-$k * $this->yellow + $k * 100 + $this->yellow) / 100; + + return new Rgb( + (int) (-$c * 255 + 255), + (int) (-$m * 255 + 255), + (int) (-$y * 255 + 255) + ); + } + + public function toCmyk() : Cmyk + { + return $this; + } + + public function toGray() : Gray + { + return $this->toRgb()->toGray(); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Color/ColorInterface.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Color/ColorInterface.php new file mode 100644 index 0000000000..b50d1cacee --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Color/ColorInterface.php @@ -0,0 +1,22 @@ + 100) { + throw new Exception\InvalidArgumentException('Gray must be between 0 and 100'); + } + + $this->gray = (int) $gray; + } + + public function getGray() : int + { + return $this->gray; + } + + public function toRgb() : Rgb + { + return new Rgb((int) ($this->gray * 2.55), (int) ($this->gray * 2.55), (int) ($this->gray * 2.55)); + } + + public function toCmyk() : Cmyk + { + return new Cmyk(0, 0, 0, 100 - $this->gray); + } + + public function toGray() : Gray + { + return $this; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Color/Rgb.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Color/Rgb.php new file mode 100644 index 0000000000..7935406216 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Color/Rgb.php @@ -0,0 +1,88 @@ + 255) { + throw new Exception\InvalidArgumentException('Red must be between 0 and 255'); + } + + if ($green < 0 || $green > 255) { + throw new Exception\InvalidArgumentException('Green must be between 0 and 255'); + } + + if ($blue < 0 || $blue > 255) { + throw new Exception\InvalidArgumentException('Blue must be between 0 and 255'); + } + + $this->red = $red; + $this->green = $green; + $this->blue = $blue; + } + + public function getRed() : int + { + return $this->red; + } + + public function getGreen() : int + { + return $this->green; + } + + public function getBlue() : int + { + return $this->blue; + } + + public function toRgb() : Rgb + { + return $this; + } + + public function toCmyk() : Cmyk + { + $c = 1 - ($this->red / 255); + $m = 1 - ($this->green / 255); + $y = 1 - ($this->blue / 255); + $k = min($c, $m, $y); + + return new Cmyk( + (int) (100 * ($c - $k) / (1 - $k)), + (int) (100 * ($m - $k) / (1 - $k)), + (int) (100 * ($y - $k) / (1 - $k)), + (int) (100 * $k) + ); + } + + public function toGray() : Gray + { + return new Gray((int) (($this->red * 0.21 + $this->green * 0.71 + $this->blue * 0.07) / 2.55)); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Eye/CompositeEye.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Eye/CompositeEye.php new file mode 100644 index 0000000000..0d03125903 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Eye/CompositeEye.php @@ -0,0 +1,38 @@ +externalEye = $externalEye; + $this->internalEye = $internalEye; + } + + public function getExternalPath() : Path + { + return $this->externalEye->getExternalPath(); + } + + public function getInternalPath() : Path + { + return $this->internalEye->getInternalPath(); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Eye/EyeInterface.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Eye/EyeInterface.php new file mode 100644 index 0000000000..ab68f3cd13 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Eye/EyeInterface.php @@ -0,0 +1,26 @@ +module = $module; + } + + public function getExternalPath() : Path + { + $matrix = new ByteMatrix(7, 7); + + for ($x = 0; $x < 7; ++$x) { + $matrix->set($x, 0, 1); + $matrix->set($x, 6, 1); + } + + for ($y = 1; $y < 6; ++$y) { + $matrix->set(0, $y, 1); + $matrix->set(6, $y, 1); + } + + return $this->module->createPath($matrix)->translate(-3.5, -3.5); + } + + public function getInternalPath() : Path + { + $matrix = new ByteMatrix(3, 3); + + for ($x = 0; $x < 3; ++$x) { + for ($y = 0; $y < 3; ++$y) { + $matrix->set($x, $y, 1); + } + } + + return $this->module->createPath($matrix)->translate(-1.5, -1.5); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Eye/SimpleCircleEye.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Eye/SimpleCircleEye.php new file mode 100644 index 0000000000..64d54ee28e --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Eye/SimpleCircleEye.php @@ -0,0 +1,54 @@ +move(-3.5, -3.5) + ->line(3.5, -3.5) + ->line(3.5, 3.5) + ->line(-3.5, 3.5) + ->close() + ->move(-2.5, -2.5) + ->line(-2.5, 2.5) + ->line(2.5, 2.5) + ->line(2.5, -2.5) + ->close() + ; + } + + public function getInternalPath() : Path + { + return (new Path()) + ->move(1.5, 0) + ->ellipticArc(1.5, 1.5, 0., false, true, 0., 1.5) + ->ellipticArc(1.5, 1.5, 0., false, true, -1.5, 0.) + ->ellipticArc(1.5, 1.5, 0., false, true, 0., -1.5) + ->ellipticArc(1.5, 1.5, 0., false, true, 1.5, 0.) + ->close() + ; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Eye/SquareEye.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Eye/SquareEye.php new file mode 100644 index 0000000000..a3892b4765 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Eye/SquareEye.php @@ -0,0 +1,53 @@ +move(-3.5, -3.5) + ->line(3.5, -3.5) + ->line(3.5, 3.5) + ->line(-3.5, 3.5) + ->close() + ->move(-2.5, -2.5) + ->line(-2.5, 2.5) + ->line(2.5, 2.5) + ->line(2.5, -2.5) + ->close() + ; + } + + public function getInternalPath() : Path + { + return (new Path()) + ->move(-1.5, -1.5) + ->line(1.5, -1.5) + ->line(1.5, 1.5) + ->line(-1.5, 1.5) + ->close() + ; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Image/EpsImageBackEnd.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Image/EpsImageBackEnd.php new file mode 100644 index 0000000000..b581b540d9 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Image/EpsImageBackEnd.php @@ -0,0 +1,376 @@ +eps = "%!PS-Adobe-3.0 EPSF-3.0\n" + . "%%Creator: BaconQrCode\n" + . sprintf("%%%%BoundingBox: 0 0 %d %d \n", $size, $size) + . "%%BeginProlog\n" + . "save\n" + . "50 dict begin\n" + . "/q { gsave } bind def\n" + . "/Q { grestore } bind def\n" + . "/s { scale } bind def\n" + . "/t { translate } bind def\n" + . "/r { rotate } bind def\n" + . "/n { newpath } bind def\n" + . "/m { moveto } bind def\n" + . "/l { lineto } bind def\n" + . "/c { curveto } bind def\n" + . "/z { closepath } bind def\n" + . "/f { eofill } bind def\n" + . "/rgb { setrgbcolor } bind def\n" + . "/cmyk { setcmykcolor } bind def\n" + . "/gray { setgray } bind def\n" + . "%%EndProlog\n" + . "1 -1 s\n" + . sprintf("0 -%d t\n", $size); + + if ($backgroundColor instanceof Alpha && 0 === $backgroundColor->getAlpha()) { + return; + } + + $this->eps .= wordwrap( + '0 0 m' + . sprintf(' %s 0 l', (string) $size) + . sprintf(' %s %s l', (string) $size, (string) $size) + . sprintf(' 0 %s l', (string) $size) + . ' z' + . ' ' .$this->getColorSetString($backgroundColor) . " f\n", + 75, + "\n " + ); + } + + public function scale(float $size) : void + { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $this->eps .= sprintf("%1\$s %1\$s s\n", round($size, self::PRECISION)); + } + + public function translate(float $x, float $y) : void + { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $this->eps .= sprintf("%s %s t\n", round($x, self::PRECISION), round($y, self::PRECISION)); + } + + public function rotate(int $degrees) : void + { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $this->eps .= sprintf("%d r\n", $degrees); + } + + public function push() : void + { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $this->eps .= "q\n"; + } + + public function pop() : void + { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $this->eps .= "Q\n"; + } + + public function drawPathWithColor(Path $path, ColorInterface $color) : void + { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $fromX = 0; + $fromY = 0; + $this->eps .= wordwrap( + 'n ' + . $this->drawPathOperations($path, $fromX, $fromY) + . ' ' . $this->getColorSetString($color) . " f\n", + 75, + "\n " + ); + } + + public function drawPathWithGradient( + Path $path, + Gradient $gradient, + float $x, + float $y, + float $width, + float $height + ) : void { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $fromX = 0; + $fromY = 0; + $this->eps .= wordwrap( + 'q n ' . $this->drawPathOperations($path, $fromX, $fromY) . "\n", + 75, + "\n " + ); + + $this->createGradientFill($gradient, $x, $y, $width, $height); + } + + public function done() : string + { + if (null === $this->eps) { + throw new RuntimeException('No image has been started'); + } + + $this->eps .= "%%TRAILER\nend restore\n%%EOF"; + $blob = $this->eps; + $this->eps = null; + + return $blob; + } + + private function drawPathOperations(Iterable $ops, &$fromX, &$fromY) : string + { + $pathData = []; + + foreach ($ops as $op) { + switch (true) { + case $op instanceof Move: + $fromX = $toX = round($op->getX(), self::PRECISION); + $fromY = $toY = round($op->getY(), self::PRECISION); + $pathData[] = sprintf('%s %s m', $toX, $toY); + break; + + case $op instanceof Line: + $fromX = $toX = round($op->getX(), self::PRECISION); + $fromY = $toY = round($op->getY(), self::PRECISION); + $pathData[] = sprintf('%s %s l', $toX, $toY); + break; + + case $op instanceof EllipticArc: + $pathData[] = $this->drawPathOperations($op->toCurves($fromX, $fromY), $fromX, $fromY); + break; + + case $op instanceof Curve: + $x1 = round($op->getX1(), self::PRECISION); + $y1 = round($op->getY1(), self::PRECISION); + $x2 = round($op->getX2(), self::PRECISION); + $y2 = round($op->getY2(), self::PRECISION); + $fromX = $x3 = round($op->getX3(), self::PRECISION); + $fromY = $y3 = round($op->getY3(), self::PRECISION); + $pathData[] = sprintf('%s %s %s %s %s %s c', $x1, $y1, $x2, $y2, $x3, $y3); + break; + + case $op instanceof Close: + $pathData[] = 'z'; + break; + + default: + throw new RuntimeException('Unexpected draw operation: ' . get_class($op)); + } + } + + return implode(' ', $pathData); + } + + private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : void + { + $startColor = $gradient->getStartColor(); + $endColor = $gradient->getEndColor(); + + if ($startColor instanceof Alpha) { + $startColor = $startColor->getBaseColor(); + } + + $startColorType = get_class($startColor); + + if (! in_array($startColorType, [Rgb::class, Cmyk::class, Gray::class])) { + $startColorType = Cmyk::class; + $startColor = $startColor->toCmyk(); + } + + if (get_class($endColor) !== $startColorType) { + switch ($startColorType) { + case Cmyk::class: + $endColor = $endColor->toCmyk(); + break; + + case Rgb::class: + $endColor = $endColor->toRgb(); + break; + + case Gray::class: + $endColor = $endColor->toGray(); + break; + } + } + + $this->eps .= "eoclip\n<<\n"; + + if ($gradient->getType() === GradientType::RADIAL()) { + $this->eps .= " /ShadingType 3\n"; + } else { + $this->eps .= " /ShadingType 2\n"; + } + + $this->eps .= " /Extend [ true true ]\n" + . " /AntiAlias true\n"; + + switch ($startColorType) { + case Cmyk::class: + $this->eps .= " /ColorSpace /DeviceCMYK\n"; + break; + + case Rgb::class: + $this->eps .= " /ColorSpace /DeviceRGB\n"; + break; + + case Gray::class: + $this->eps .= " /ColorSpace /DeviceGray\n"; + break; + } + + switch ($gradient->getType()) { + case GradientType::HORIZONTAL(): + $this->eps .= sprintf( + " /Coords [ %s %s %s %s ]\n", + round($x, self::PRECISION), + round($y, self::PRECISION), + round($x + $width, self::PRECISION), + round($y, self::PRECISION) + ); + break; + + case GradientType::VERTICAL(): + $this->eps .= sprintf( + " /Coords [ %s %s %s %s ]\n", + round($x, self::PRECISION), + round($y, self::PRECISION), + round($x, self::PRECISION), + round($y + $height, self::PRECISION) + ); + break; + + case GradientType::DIAGONAL(): + $this->eps .= sprintf( + " /Coords [ %s %s %s %s ]\n", + round($x, self::PRECISION), + round($y, self::PRECISION), + round($x + $width, self::PRECISION), + round($y + $height, self::PRECISION) + ); + break; + + case GradientType::INVERSE_DIAGONAL(): + $this->eps .= sprintf( + " /Coords [ %s %s %s %s ]\n", + round($x, self::PRECISION), + round($y + $height, self::PRECISION), + round($x + $width, self::PRECISION), + round($y, self::PRECISION) + ); + break; + + case GradientType::RADIAL(): + $centerX = ($x + $width) / 2; + $centerY = ($y + $height) / 2; + + $this->eps .= sprintf( + " /Coords [ %s %s 0 %s %s %s ]\n", + round($centerX, self::PRECISION), + round($centerY, self::PRECISION), + round($centerX, self::PRECISION), + round($centerY, self::PRECISION), + round(max($width, $height) / 2, self::PRECISION) + ); + break; + } + + $this->eps .= " /Function\n" + . " <<\n" + . " /FunctionType 2\n" + . " /Domain [ 0 1 ]\n" + . sprintf(" /C0 [ %s ]\n", $this->getColorString($startColor)) + . sprintf(" /C1 [ %s ]\n", $this->getColorString($endColor)) + . " /N 1\n" + . " >>\n>>\nshfill\nQ\n"; + } + + private function getColorSetString(ColorInterface $color) : string + { + if ($color instanceof Rgb) { + return $this->getColorString($color) . ' rgb'; + } + + if ($color instanceof Cmyk) { + return $this->getColorString($color) . ' cmyk'; + } + + if ($color instanceof Gray) { + return $this->getColorString($color) . ' gray'; + } + + return $this->getColorSetString($color->toCmyk()); + } + + private function getColorString(ColorInterface $color) : string + { + if ($color instanceof Rgb) { + return sprintf('%s %s %s', $color->getRed() / 255, $color->getGreen() / 255, $color->getBlue() / 255); + } + + if ($color instanceof Cmyk) { + return sprintf( + '%s %s %s %s', + $color->getCyan() / 100, + $color->getMagenta() / 100, + $color->getYellow() / 100, + $color->getBlack() / 100 + ); + } + + if ($color instanceof Gray) { + return sprintf('%s', $color->getGray() / 100); + } + + return $this->getColorString($color->toCmyk()); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Image/ImageBackEndInterface.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Image/ImageBackEndInterface.php new file mode 100644 index 0000000000..0935819a82 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Image/ImageBackEndInterface.php @@ -0,0 +1,87 @@ +imageFormat = $imageFormat; + $this->compressionQuality = $compressionQuality; + } + + public function new(int $size, ColorInterface $backgroundColor) : void + { + $this->image = new Imagick(); + $this->image->newImage($size, $size, $this->getColorPixel($backgroundColor)); + $this->image->setImageFormat($this->imageFormat); + $this->image->setCompressionQuality($this->compressionQuality); + $this->draw = new ImagickDraw(); + $this->gradientCount = 0; + $this->matrices = [new TransformationMatrix()]; + $this->matrixIndex = 0; + } + + public function scale(float $size) : void + { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->draw->scale($size, $size); + $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex] + ->multiply(TransformationMatrix::scale($size)); + } + + public function translate(float $x, float $y) : void + { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->draw->translate($x, $y); + $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex] + ->multiply(TransformationMatrix::translate($x, $y)); + } + + public function rotate(int $degrees) : void + { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->draw->rotate($degrees); + $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex] + ->multiply(TransformationMatrix::rotate($degrees)); + } + + public function push() : void + { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->draw->push(); + $this->matrices[++$this->matrixIndex] = $this->matrices[$this->matrixIndex - 1]; + } + + public function pop() : void + { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->draw->pop(); + unset($this->matrices[$this->matrixIndex--]); + } + + public function drawPathWithColor(Path $path, ColorInterface $color) : void + { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->draw->setFillColor($this->getColorPixel($color)); + $this->drawPath($path); + } + + public function drawPathWithGradient( + Path $path, + Gradient $gradient, + float $x, + float $y, + float $width, + float $height + ) : void { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->draw->setFillPatternURL('#' . $this->createGradientFill($gradient, $x, $y, $width, $height)); + $this->drawPath($path); + } + + public function done() : string + { + if (null === $this->draw) { + throw new RuntimeException('No image has been started'); + } + + $this->image->drawImage($this->draw); + $blob = $this->image->getImageBlob(); + $this->draw->clear(); + $this->image->clear(); + $this->draw = null; + $this->image = null; + $this->gradientCount = null; + + return $blob; + } + + private function drawPath(Path $path) : void + { + $this->draw->pathStart(); + + foreach ($path as $op) { + switch (true) { + case $op instanceof Move: + $this->draw->pathMoveToAbsolute($op->getX(), $op->getY()); + break; + + case $op instanceof Line: + $this->draw->pathLineToAbsolute($op->getX(), $op->getY()); + break; + + case $op instanceof EllipticArc: + $this->draw->pathEllipticArcAbsolute( + $op->getXRadius(), + $op->getYRadius(), + $op->getXAxisAngle(), + $op->isLargeArc(), + $op->isSweep(), + $op->getX(), + $op->getY() + ); + break; + + case $op instanceof Curve: + $this->draw->pathCurveToAbsolute( + $op->getX1(), + $op->getY1(), + $op->getX2(), + $op->getY2(), + $op->getX3(), + $op->getY3() + ); + break; + + case $op instanceof Close: + $this->draw->pathClose(); + break; + + default: + throw new RuntimeException('Unexpected draw operation: ' . get_class($op)); + } + } + + $this->draw->pathFinish(); + } + + private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string + { + list($width, $height) = $this->matrices[$this->matrixIndex]->apply($width, $height); + + $startColor = $this->getColorPixel($gradient->getStartColor())->getColorAsString(); + $endColor = $this->getColorPixel($gradient->getEndColor())->getColorAsString(); + $gradientImage = new Imagick(); + + switch ($gradient->getType()) { + case GradientType::HORIZONTAL(): + $gradientImage->newPseudoImage((int) $height, (int) $width, sprintf( + 'gradient:%s-%s', + $startColor, + $endColor + )); + $gradientImage->rotateImage('transparent', -90); + break; + + case GradientType::VERTICAL(): + $gradientImage->newPseudoImage((int) $width, (int) $height, sprintf( + 'gradient:%s-%s', + $startColor, + $endColor + )); + break; + + case GradientType::DIAGONAL(): + case GradientType::INVERSE_DIAGONAL(): + $gradientImage->newPseudoImage((int) ($width * sqrt(2)), (int) ($height * sqrt(2)), sprintf( + 'gradient:%s-%s', + $startColor, + $endColor + )); + + if (GradientType::DIAGONAL() === $gradient->getType()) { + $gradientImage->rotateImage('transparent', -45); + } else { + $gradientImage->rotateImage('transparent', -135); + } + + $rotatedWidth = $gradientImage->getImageWidth(); + $rotatedHeight = $gradientImage->getImageHeight(); + + $gradientImage->setImagePage($rotatedWidth, $rotatedHeight, 0, 0); + $gradientImage->cropImage( + intdiv($rotatedWidth, 2) - 2, + intdiv($rotatedHeight, 2) - 2, + intdiv($rotatedWidth, 4) + 1, + intdiv($rotatedWidth, 4) + 1 + ); + break; + + case GradientType::RADIAL(): + $gradientImage->newPseudoImage((int) $width, (int) $height, sprintf( + 'radial-gradient:%s-%s', + $startColor, + $endColor + )); + break; + } + + $id = sprintf('g%d', ++$this->gradientCount); + $this->draw->pushPattern($id, 0, 0, $width, $height); + $this->draw->composite(Imagick::COMPOSITE_COPY, 0, 0, $width, $height, $gradientImage); + $this->draw->popPattern(); + return $id; + } + + private function getColorPixel(ColorInterface $color) : ImagickPixel + { + $alpha = 100; + + if ($color instanceof Alpha) { + $alpha = $color->getAlpha(); + $color = $color->getBaseColor(); + } + + if ($color instanceof Rgb) { + return new ImagickPixel(sprintf( + 'rgba(%d, %d, %d, %F)', + $color->getRed(), + $color->getGreen(), + $color->getBlue(), + $alpha / 100 + )); + } + + if ($color instanceof Cmyk) { + return new ImagickPixel(sprintf( + 'cmyka(%d, %d, %d, %d, %F)', + $color->getCyan(), + $color->getMagenta(), + $color->getYellow(), + $color->getBlack(), + $alpha / 100 + )); + } + + if ($color instanceof Gray) { + return new ImagickPixel(sprintf( + 'graya(%d%%, %F)', + $color->getGray(), + $alpha / 100 + )); + } + + return $this->getColorPixel(new Alpha($alpha, $color->toRgb())); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Image/SvgImageBackEnd.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Image/SvgImageBackEnd.php new file mode 100644 index 0000000000..cb37a9ff8d --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Image/SvgImageBackEnd.php @@ -0,0 +1,369 @@ +xmlWriter = new XMLWriter(); + $this->xmlWriter->openMemory(); + + $this->xmlWriter->startDocument('1.0', 'UTF-8'); + $this->xmlWriter->startElement('svg'); + $this->xmlWriter->writeAttribute('xmlns', 'http://www.w3.org/2000/svg'); + $this->xmlWriter->writeAttribute('version', '1.1'); + $this->xmlWriter->writeAttribute('width', (string) $size); + $this->xmlWriter->writeAttribute('height', (string) $size); + $this->xmlWriter->writeAttribute('viewBox', '0 0 '. $size . ' ' . $size); + + $this->gradientCount = 0; + $this->currentStack = 0; + $this->stack[0] = 0; + + $alpha = 1; + + if ($backgroundColor instanceof Alpha) { + $alpha = $backgroundColor->getAlpha() / 100; + } + + if (0 === $alpha) { + return; + } + + $this->xmlWriter->startElement('rect'); + $this->xmlWriter->writeAttribute('x', '0'); + $this->xmlWriter->writeAttribute('y', '0'); + $this->xmlWriter->writeAttribute('width', (string) $size); + $this->xmlWriter->writeAttribute('height', (string) $size); + $this->xmlWriter->writeAttribute('fill', $this->getColorString($backgroundColor)); + + if ($alpha < 1) { + $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha); + } + + $this->xmlWriter->endElement(); + } + + public function scale(float $size) : void + { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + $this->xmlWriter->startElement('g'); + $this->xmlWriter->writeAttribute( + 'transform', + sprintf('scale(%s)', round($size, self::PRECISION)) + ); + ++$this->stack[$this->currentStack]; + } + + public function translate(float $x, float $y) : void + { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + $this->xmlWriter->startElement('g'); + $this->xmlWriter->writeAttribute( + 'transform', + sprintf('translate(%s,%s)', round($x, self::PRECISION), round($y, self::PRECISION)) + ); + ++$this->stack[$this->currentStack]; + } + + public function rotate(int $degrees) : void + { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + $this->xmlWriter->startElement('g'); + $this->xmlWriter->writeAttribute('transform', sprintf('rotate(%d)', $degrees)); + ++$this->stack[$this->currentStack]; + } + + public function push() : void + { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + $this->xmlWriter->startElement('g'); + $this->stack[] = 1; + ++$this->currentStack; + } + + public function pop() : void + { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + for ($i = 0; $i < $this->stack[$this->currentStack]; ++$i) { + $this->xmlWriter->endElement(); + } + + array_pop($this->stack); + --$this->currentStack; + } + + public function drawPathWithColor(Path $path, ColorInterface $color) : void + { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + $alpha = 1; + + if ($color instanceof Alpha) { + $alpha = $color->getAlpha() / 100; + } + + $this->startPathElement($path); + $this->xmlWriter->writeAttribute('fill', $this->getColorString($color)); + + if ($alpha < 1) { + $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha); + } + + $this->xmlWriter->endElement(); + } + + public function drawPathWithGradient( + Path $path, + Gradient $gradient, + float $x, + float $y, + float $width, + float $height + ) : void { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + $gradientId = $this->createGradientFill($gradient, $x, $y, $width, $height); + $this->startPathElement($path); + $this->xmlWriter->writeAttribute('fill', 'url(#' . $gradientId . ')'); + $this->xmlWriter->endElement(); + } + + public function done() : string + { + if (null === $this->xmlWriter) { + throw new RuntimeException('No image has been started'); + } + + foreach ($this->stack as $openElements) { + for ($i = $openElements; $i > 0; --$i) { + $this->xmlWriter->endElement(); + } + } + + $this->xmlWriter->endDocument(); + $blob = $this->xmlWriter->outputMemory(true); + $this->xmlWriter = null; + $this->stack = null; + $this->currentStack = null; + $this->gradientCount = null; + + return $blob; + } + + private function startPathElement(Path $path) : void + { + $pathData = []; + + foreach ($path as $op) { + switch (true) { + case $op instanceof Move: + $pathData[] = sprintf( + 'M%s %s', + round($op->getX(), self::PRECISION), + round($op->getY(), self::PRECISION) + ); + break; + + case $op instanceof Line: + $pathData[] = sprintf( + 'L%s %s', + round($op->getX(), self::PRECISION), + round($op->getY(), self::PRECISION) + ); + break; + + case $op instanceof EllipticArc: + $pathData[] = sprintf( + 'A%s %s %s %u %u %s %s', + round($op->getXRadius(), self::PRECISION), + round($op->getYRadius(), self::PRECISION), + round($op->getXAxisAngle(), self::PRECISION), + $op->isLargeArc(), + $op->isSweep(), + round($op->getX(), self::PRECISION), + round($op->getY(), self::PRECISION) + ); + break; + + case $op instanceof Curve: + $pathData[] = sprintf( + 'C%s %s %s %s %s %s', + round($op->getX1(), self::PRECISION), + round($op->getY1(), self::PRECISION), + round($op->getX2(), self::PRECISION), + round($op->getY2(), self::PRECISION), + round($op->getX3(), self::PRECISION), + round($op->getY3(), self::PRECISION) + ); + break; + + case $op instanceof Close: + $pathData[] = 'Z'; + break; + + default: + throw new RuntimeException('Unexpected draw operation: ' . get_class($op)); + } + } + + $this->xmlWriter->startElement('path'); + $this->xmlWriter->writeAttribute('fill-rule', 'evenodd'); + $this->xmlWriter->writeAttribute('d', implode('', $pathData)); + } + + private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string + { + $this->xmlWriter->startElement('defs'); + + $startColor = $gradient->getStartColor(); + $endColor = $gradient->getEndColor(); + + if ($gradient->getType() === GradientType::RADIAL()) { + $this->xmlWriter->startElement('radialGradient'); + } else { + $this->xmlWriter->startElement('linearGradient'); + } + + $this->xmlWriter->writeAttribute('gradientUnits', 'userSpaceOnUse'); + + switch ($gradient->getType()) { + case GradientType::HORIZONTAL(): + $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); + $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION)); + $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION)); + $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION)); + break; + + case GradientType::VERTICAL(): + $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); + $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION)); + $this->xmlWriter->writeAttribute('x2', (string) round($x, self::PRECISION)); + $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION)); + break; + + case GradientType::DIAGONAL(): + $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); + $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION)); + $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION)); + $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION)); + break; + + case GradientType::INVERSE_DIAGONAL(): + $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); + $this->xmlWriter->writeAttribute('y1', (string) round($y + $height, self::PRECISION)); + $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION)); + $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION)); + break; + + case GradientType::RADIAL(): + $this->xmlWriter->writeAttribute('cx', (string) round(($x + $width) / 2, self::PRECISION)); + $this->xmlWriter->writeAttribute('cy', (string) round(($y + $height) / 2, self::PRECISION)); + $this->xmlWriter->writeAttribute('r', (string) round(max($width, $height) / 2, self::PRECISION)); + break; + } + + $id = sprintf('g%d', ++$this->gradientCount); + $this->xmlWriter->writeAttribute('id', $id); + + $this->xmlWriter->startElement('stop'); + $this->xmlWriter->writeAttribute('offset', '0%'); + $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($startColor)); + + if ($startColor instanceof Alpha) { + $this->xmlWriter->writeAttribute('stop-opacity', (string) $startColor->getAlpha()); + } + + $this->xmlWriter->endElement(); + + $this->xmlWriter->startElement('stop'); + $this->xmlWriter->writeAttribute('offset', '100%'); + $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($endColor)); + + if ($endColor instanceof Alpha) { + $this->xmlWriter->writeAttribute('stop-opacity', (string) $endColor->getAlpha()); + } + + $this->xmlWriter->endElement(); + + $this->xmlWriter->endElement(); + $this->xmlWriter->endElement(); + + return $id; + } + + private function getColorString(ColorInterface $color) : string + { + $color = $color->toRgb(); + + return sprintf( + '#%02x%02x%02x', + $color->getRed(), + $color->getGreen(), + $color->getBlue() + ); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Image/TransformationMatrix.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Image/TransformationMatrix.php new file mode 100644 index 0000000000..7e88da6df3 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Image/TransformationMatrix.php @@ -0,0 +1,68 @@ +values = [1, 0, 0, 1, 0, 0]; + } + + public function multiply(self $other) : self + { + $matrix = new self(); + $matrix->values[0] = $this->values[0] * $other->values[0] + $this->values[2] * $other->values[1]; + $matrix->values[1] = $this->values[1] * $other->values[0] + $this->values[3] * $other->values[1]; + $matrix->values[2] = $this->values[0] * $other->values[2] + $this->values[2] * $other->values[3]; + $matrix->values[3] = $this->values[1] * $other->values[2] + $this->values[3] * $other->values[3]; + $matrix->values[4] = $this->values[0] * $other->values[4] + $this->values[2] * $other->values[5] + + $this->values[4]; + $matrix->values[5] = $this->values[1] * $other->values[4] + $this->values[3] * $other->values[5] + + $this->values[5]; + + return $matrix; + } + + public static function scale(float $size) : self + { + $matrix = new self(); + $matrix->values = [$size, 0, 0, $size, 0, 0]; + return $matrix; + } + + public static function translate(float $x, float $y) : self + { + $matrix = new self(); + $matrix->values = [1, 0, 0, 1, $x, $y]; + return $matrix; + } + + public static function rotate(int $degrees) : self + { + $matrix = new self(); + $rad = deg2rad($degrees); + $matrix->values = [cos($rad), sin($rad), -sin($rad), cos($rad), 0, 0]; + return $matrix; + } + + + /** + * Applies this matrix onto a point and returns the resulting viewport point. + * + * @return float[] + */ + public function apply(float $x, float $y) : array + { + return [ + $x * $this->values[0] + $y * $this->values[2] + $this->values[4], + $x * $this->values[1] + $y * $this->values[3] + $this->values[5], + ]; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/ImageRenderer.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/ImageRenderer.php new file mode 100644 index 0000000000..ab16276c91 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/ImageRenderer.php @@ -0,0 +1,152 @@ +rendererStyle = $rendererStyle; + $this->imageBackEnd = $imageBackEnd; + } + + /** + * @throws InvalidArgumentException if matrix width doesn't match height + */ + public function render(QrCode $qrCode) : string + { + $size = $this->rendererStyle->getSize(); + $margin = $this->rendererStyle->getMargin(); + $matrix = $qrCode->getMatrix(); + $matrixSize = $matrix->getWidth(); + + if ($matrixSize !== $matrix->getHeight()) { + throw new InvalidArgumentException('Matrix must have the same width and height'); + } + + $totalSize = $matrixSize + ($margin * 2); + $moduleSize = $size / $totalSize; + $fill = $this->rendererStyle->getFill(); + + $this->imageBackEnd->new($size, $fill->getBackgroundColor()); + $this->imageBackEnd->scale((float) $moduleSize); + $this->imageBackEnd->translate((float) $margin, (float) $margin); + + $module = $this->rendererStyle->getModule(); + $moduleMatrix = clone $matrix; + MatrixUtil::removePositionDetectionPatterns($moduleMatrix); + $modulePath = $this->drawEyes($matrixSize, $module->createPath($moduleMatrix)); + + if ($fill->hasGradientFill()) { + $this->imageBackEnd->drawPathWithGradient( + $modulePath, + $fill->getForegroundGradient(), + 0, + 0, + $matrixSize, + $matrixSize + ); + } else { + $this->imageBackEnd->drawPathWithColor($modulePath, $fill->getForegroundColor()); + } + + return $this->imageBackEnd->done(); + } + + private function drawEyes(int $matrixSize, Path $modulePath) : Path + { + $fill = $this->rendererStyle->getFill(); + + $eye = $this->rendererStyle->getEye(); + $externalPath = $eye->getExternalPath(); + $internalPath = $eye->getInternalPath(); + + $modulePath = $this->drawEye( + $externalPath, + $internalPath, + $fill->getTopLeftEyeFill(), + 3.5, + 3.5, + 0, + $modulePath + ); + $modulePath = $this->drawEye( + $externalPath, + $internalPath, + $fill->getTopRightEyeFill(), + $matrixSize - 3.5, + 3.5, + 90, + $modulePath + ); + $modulePath = $this->drawEye( + $externalPath, + $internalPath, + $fill->getBottomLeftEyeFill(), + 3.5, + $matrixSize - 3.5, + -90, + $modulePath + ); + + return $modulePath; + } + + private function drawEye( + Path $externalPath, + Path $internalPath, + EyeFill $fill, + float $xTranslation, + float $yTranslation, + int $rotation, + Path $modulePath + ) : Path { + if ($fill->inheritsBothColors()) { + return $modulePath + ->append($externalPath->translate($xTranslation, $yTranslation)) + ->append($internalPath->translate($xTranslation, $yTranslation)); + } + + $this->imageBackEnd->push(); + $this->imageBackEnd->translate($xTranslation, $yTranslation); + + if (0 !== $rotation) { + $this->imageBackEnd->rotate($rotation); + } + + if ($fill->inheritsExternalColor()) { + $modulePath = $modulePath->append($externalPath->translate($xTranslation, $yTranslation)); + } else { + $this->imageBackEnd->drawPathWithColor($externalPath, $fill->getExternalColor()); + } + + if ($fill->inheritsInternalColor()) { + $modulePath = $modulePath->append($internalPath->translate($xTranslation, $yTranslation)); + } else { + $this->imageBackEnd->drawPathWithColor($internalPath, $fill->getInternalColor()); + } + + $this->imageBackEnd->pop(); + + return $modulePath; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/DotsModule.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/DotsModule.php new file mode 100644 index 0000000000..f536e5ab36 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/DotsModule.php @@ -0,0 +1,63 @@ + 1) { + throw new InvalidArgumentException('Size must between 0 (exclusive) and 1 (inclusive)'); + } + + $this->size = $size; + } + + public function createPath(ByteMatrix $matrix) : Path + { + $width = $matrix->getWidth(); + $height = $matrix->getHeight(); + $path = new Path(); + $halfSize = $this->size / 2; + $margin = (1 - $this->size) / 2; + + for ($y = 0; $y < $height; ++$y) { + for ($x = 0; $x < $width; ++$x) { + if (! $matrix->get($x, $y)) { + continue; + } + + $pathX = $x + $margin; + $pathY = $y + $margin; + + $path = $path + ->move($pathX + $this->size, $pathY + $halfSize) + ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $halfSize, $pathY + $this->size) + ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX, $pathY + $halfSize) + ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $halfSize, $pathY) + ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $this->size, $pathY + $halfSize) + ->close() + ; + } + } + + return $path; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/Edge.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/Edge.php new file mode 100644 index 0000000000..90482f21d4 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/Edge.php @@ -0,0 +1,100 @@ + + */ + private $points = []; + + /** + * @var array|null + */ + private $simplifiedPoints; + + /** + * @var int + */ + private $minX = PHP_INT_MAX; + + /** + * @var int + */ + private $minY = PHP_INT_MAX; + + /** + * @var int + */ + private $maxX = -1; + + /** + * @var int + */ + private $maxY = -1; + + public function __construct(bool $positive) + { + $this->positive = $positive; + } + + public function addPoint(int $x, int $y) : void + { + $this->points[] = [$x, $y]; + $this->minX = min($this->minX, $x); + $this->minY = min($this->minY, $y); + $this->maxX = max($this->maxX, $x); + $this->maxY = max($this->maxY, $y); + } + + public function isPositive() : bool + { + return $this->positive; + } + + /** + * @return array + */ + public function getPoints() : array + { + return $this->points; + } + + public function getMaxX() : int + { + return $this->maxX; + } + + public function getSimplifiedPoints() : array + { + if (null !== $this->simplifiedPoints) { + return $this->simplifiedPoints; + } + + $points = []; + $length = count($this->points); + + for ($i = 0; $i < $length; ++$i) { + $previousPoint = $this->points[(0 === $i ? $length : $i) - 1]; + $nextPoint = $this->points[($length - 1 === $i ? -1 : $i) + 1]; + $currentPoint = $this->points[$i]; + + if (($previousPoint[0] === $currentPoint[0] && $currentPoint[0] === $nextPoint[0]) + || ($previousPoint[1] === $currentPoint[1] && $currentPoint[1] === $nextPoint[1]) + ) { + continue; + } + + $points[] = $currentPoint; + } + + return $this->simplifiedPoints = $points; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/EdgeIterator.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/EdgeIterator.php new file mode 100644 index 0000000000..eb29dc60aa --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/EdgeIterator.php @@ -0,0 +1,169 @@ +bytes = iterator_to_array($matrix->getBytes()); + $this->size = count($this->bytes); + $this->width = $matrix->getWidth(); + $this->height = $matrix->getHeight(); + } + + /** + * @return Traversable + */ + public function getIterator() : Traversable + { + $originalBytes = $this->bytes; + $point = $this->findNext(0, 0); + + while (null !== $point) { + $edge = $this->findEdge($point[0], $point[1]); + $this->xorEdge($edge); + + yield $edge; + + $point = $this->findNext($point[0], $point[1]); + } + + $this->bytes = $originalBytes; + } + + /** + * @return int[]|null + */ + private function findNext(int $x, int $y) : ?array + { + $i = $this->width * $y + $x; + + while ($i < $this->size && 1 !== $this->bytes[$i]) { + ++$i; + } + + if ($i < $this->size) { + return $this->pointOf($i); + } + + return null; + } + + private function findEdge(int $x, int $y) : Edge + { + $edge = new Edge($this->isSet($x, $y)); + $startX = $x; + $startY = $y; + $dirX = 0; + $dirY = 1; + + while (true) { + $edge->addPoint($x, $y); + $x += $dirX; + $y += $dirY; + + if ($x === $startX && $y === $startY) { + break; + } + + $left = $this->isSet($x + ($dirX + $dirY - 1 ) / 2, $y + ($dirY - $dirX - 1) / 2); + $right = $this->isSet($x + ($dirX - $dirY - 1) / 2, $y + ($dirY + $dirX - 1) / 2); + + if ($right && ! $left) { + $tmp = $dirX; + $dirX = -$dirY; + $dirY = $tmp; + } elseif ($right) { + $tmp = $dirX; + $dirX = -$dirY; + $dirY = $tmp; + } elseif (! $left) { + $tmp = $dirX; + $dirX = $dirY; + $dirY = -$tmp; + } + } + + return $edge; + } + + private function xorEdge(Edge $path) : void + { + $points = $path->getPoints(); + $y1 = $points[0][1]; + $length = count($points); + $maxX = $path->getMaxX(); + + for ($i = 1; $i < $length; ++$i) { + $y = $points[$i][1]; + + if ($y === $y1) { + continue; + } + + $x = $points[$i][0]; + $minY = min($y1, $y); + + for ($j = $x; $j < $maxX; ++$j) { + $this->flip($j, $minY); + } + + $y1 = $y; + } + } + + private function isSet(int $x, int $y) : bool + { + return ( + $x >= 0 + && $x < $this->width + && $y >= 0 + && $y < $this->height + ) && 1 === $this->bytes[$this->width * $y + $x]; + } + + /** + * @return int[] + */ + private function pointOf(int $i) : array + { + $y = intdiv($i, $this->width); + return [$i - $y * $this->width, $y]; + } + + private function flip(int $x, int $y) : void + { + $this->bytes[$this->width * $y + $x] = ( + $this->isSet($x, $y) ? 0 : 1 + ); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/ModuleInterface.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/ModuleInterface.php new file mode 100644 index 0000000000..0ccb0e0fe3 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/ModuleInterface.php @@ -0,0 +1,18 @@ + 1) { + throw new InvalidArgumentException('Intensity must between 0 (exclusive) and 1 (inclusive)'); + } + + $this->intensity = $intensity / 2; + } + + public function createPath(ByteMatrix $matrix) : Path + { + $path = new Path(); + + foreach (new EdgeIterator($matrix) as $edge) { + $points = $edge->getSimplifiedPoints(); + $length = count($points); + + $currentPoint = $points[0]; + $nextPoint = $points[1]; + $horizontal = ($currentPoint[1] === $nextPoint[1]); + + if ($horizontal) { + $right = $nextPoint[0] > $currentPoint[0]; + $path = $path->move( + $currentPoint[0] + ($right ? $this->intensity : -$this->intensity), + $currentPoint[1] + ); + } else { + $up = $nextPoint[0] < $currentPoint[0]; + $path = $path->move( + $currentPoint[0], + $currentPoint[1] + ($up ? -$this->intensity : $this->intensity) + ); + } + + for ($i = 1; $i <= $length; ++$i) { + if ($i === $length) { + $previousPoint = $points[$length - 1]; + $currentPoint = $points[0]; + $nextPoint = $points[1]; + } else { + $previousPoint = $points[(0 === $i ? $length : $i) - 1]; + $currentPoint = $points[$i]; + $nextPoint = $points[($length - 1 === $i ? -1 : $i) + 1]; + } + + $horizontal = ($previousPoint[1] === $currentPoint[1]); + + if ($horizontal) { + $right = $previousPoint[0] < $currentPoint[0]; + $up = $nextPoint[1] < $currentPoint[1]; + $sweep = ($up xor $right); + + if ($this->intensity < 0.5 + || ($right && $previousPoint[0] !== $currentPoint[0] - 1) + || (! $right && $previousPoint[0] - 1 !== $currentPoint[0]) + ) { + $path = $path->line( + $currentPoint[0] + ($right ? -$this->intensity : $this->intensity), + $currentPoint[1] + ); + } + + $path = $path->ellipticArc( + $this->intensity, + $this->intensity, + 0, + false, + $sweep, + $currentPoint[0], + $currentPoint[1] + ($up ? -$this->intensity : $this->intensity) + ); + } else { + $up = $previousPoint[1] > $currentPoint[1]; + $right = $nextPoint[0] > $currentPoint[0]; + $sweep = ! ($up xor $right); + + if ($this->intensity < 0.5 + || ($up && $previousPoint[1] !== $currentPoint[1] + 1) + || (! $up && $previousPoint[0] + 1 !== $currentPoint[0]) + ) { + $path = $path->line( + $currentPoint[0], + $currentPoint[1] + ($up ? $this->intensity : -$this->intensity) + ); + } + + $path = $path->ellipticArc( + $this->intensity, + $this->intensity, + 0, + false, + $sweep, + $currentPoint[0] + ($right ? $this->intensity : -$this->intensity), + $currentPoint[1] + ); + } + } + + $path = $path->close(); + } + + return $path; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/SquareModule.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/SquareModule.php new file mode 100644 index 0000000000..9ab4607667 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Module/SquareModule.php @@ -0,0 +1,47 @@ +getSimplifiedPoints(); + $length = count($points); + $path = $path->move($points[0][0], $points[0][1]); + + for ($i = 1; $i < $length; ++$i) { + $path = $path->line($points[$i][0], $points[$i][1]); + } + + $path = $path->close(); + } + + return $path; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/Close.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/Close.php new file mode 100644 index 0000000000..b07feb02d6 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/Close.php @@ -0,0 +1,29 @@ +x1 = $x1; + $this->y1 = $y1; + $this->x2 = $x2; + $this->y2 = $y2; + $this->x3 = $x3; + $this->y3 = $y3; + } + + public function getX1() : float + { + return $this->x1; + } + + public function getY1() : float + { + return $this->y1; + } + + public function getX2() : float + { + return $this->x2; + } + + public function getY2() : float + { + return $this->y2; + } + + public function getX3() : float + { + return $this->x3; + } + + public function getY3() : float + { + return $this->y3; + } + + /** + * @return self + */ + public function translate(float $x, float $y) : OperationInterface + { + return new self( + $this->x1 + $x, + $this->y1 + $y, + $this->x2 + $x, + $this->y2 + $y, + $this->x3 + $x, + $this->y3 + $y + ); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/EllipticArc.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/EllipticArc.php new file mode 100644 index 0000000000..9f2385abbb --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/EllipticArc.php @@ -0,0 +1,278 @@ +xRadius = abs($xRadius); + $this->yRadius = abs($yRadius); + $this->xAxisAngle = $xAxisAngle % 360; + $this->largeArc = $largeArc; + $this->sweep = $sweep; + $this->x = $x; + $this->y = $y; + } + + public function getXRadius() : float + { + return $this->xRadius; + } + + public function getYRadius() : float + { + return $this->yRadius; + } + + public function getXAxisAngle() : float + { + return $this->xAxisAngle; + } + + public function isLargeArc() : bool + { + return $this->largeArc; + } + + public function isSweep() : bool + { + return $this->sweep; + } + + public function getX() : float + { + return $this->x; + } + + public function getY() : float + { + return $this->y; + } + + /** + * @return self + */ + public function translate(float $x, float $y) : OperationInterface + { + return new self( + $this->xRadius, + $this->yRadius, + $this->xAxisAngle, + $this->largeArc, + $this->sweep, + $this->x + $x, + $this->y + $y + ); + } + + /** + * Converts the elliptic arc to multiple curves. + * + * Since not all image back ends support elliptic arcs, this method allows to convert the arc into multiple curves + * resembling the same result. + * + * @see https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/ + * @return array + */ + public function toCurves(float $fromX, float $fromY) : array + { + if (sqrt(($fromX - $this->x) ** 2 + ($fromY - $this->y) ** 2) < self::ZERO_TOLERANCE) { + return []; + } + + if ($this->xRadius < self::ZERO_TOLERANCE || $this->yRadius < self::ZERO_TOLERANCE) { + return [new Line($this->x, $this->y)]; + } + + return $this->createCurves($fromX, $fromY); + } + + /** + * @return Curve[] + */ + private function createCurves(float $fromX, float $fromY) : array + { + $xAngle = deg2rad($this->xAxisAngle); + list($centerX, $centerY, $radiusX, $radiusY, $startAngle, $deltaAngle) = + $this->calculateCenterPointParameters($fromX, $fromY, $xAngle); + + $s = $startAngle; + $e = $s + $deltaAngle; + $sign = ($e < $s) ? -1 : 1; + $remain = abs($e - $s); + $p1 = self::point($centerX, $centerY, $radiusX, $radiusY, $xAngle, $s); + $curves = []; + + while ($remain > self::ZERO_TOLERANCE) { + $step = min($remain, pi() / 2); + $signStep = $step * $sign; + $p2 = self::point($centerX, $centerY, $radiusX, $radiusY, $xAngle, $s + $signStep); + + $alphaT = tan($signStep / 2); + $alpha = sin($signStep) * (sqrt(4 + 3 * $alphaT ** 2) - 1) / 3; + $d1 = self::derivative($radiusX, $radiusY, $xAngle, $s); + $d2 = self::derivative($radiusX, $radiusY, $xAngle, $s + $signStep); + + $curves[] = new Curve( + $p1[0] + $alpha * $d1[0], + $p1[1] + $alpha * $d1[1], + $p2[0] - $alpha * $d2[0], + $p2[1] - $alpha * $d2[1], + $p2[0], + $p2[1] + ); + + $s += $signStep; + $remain -= $step; + $p1 = $p2; + } + + return $curves; + } + + /** + * @return float[] + */ + private function calculateCenterPointParameters(float $fromX, float $fromY, float $xAngle) + { + $rX = $this->xRadius; + $rY = $this->yRadius; + + // F.6.5.1 + $dx2 = ($fromX - $this->x) / 2; + $dy2 = ($fromY - $this->y) / 2; + $x1p = cos($xAngle) * $dx2 + sin($xAngle) * $dy2; + $y1p = -sin($xAngle) * $dx2 + cos($xAngle) * $dy2; + + // F.6.5.2 + $rxs = $rX ** 2; + $rys = $rY ** 2; + $x1ps = $x1p ** 2; + $y1ps = $y1p ** 2; + $cr = $x1ps / $rxs + $y1ps / $rys; + + if ($cr > 1) { + $s = sqrt($cr); + $rX *= $s; + $rY *= $s; + $rxs = $rX ** 2; + $rys = $rY ** 2; + } + + $dq = ($rxs * $y1ps + $rys * $x1ps); + $pq = ($rxs * $rys - $dq) / $dq; + $q = sqrt(max(0, $pq)); + + if ($this->largeArc === $this->sweep) { + $q = -$q; + } + + $cxp = $q * $rX * $y1p / $rY; + $cyp = -$q * $rY * $x1p / $rX; + + // F.6.5.3 + $cx = cos($xAngle) * $cxp - sin($xAngle) * $cyp + ($fromX + $this->x) / 2; + $cy = sin($xAngle) * $cxp + cos($xAngle) * $cyp + ($fromY + $this->y) / 2; + + // F.6.5.5 + $theta = self::angle(1, 0, ($x1p - $cxp) / $rX, ($y1p - $cyp) / $rY); + + // F.6.5.6 + $delta = self::angle(($x1p - $cxp) / $rX, ($y1p - $cyp) / $rY, (-$x1p - $cxp) / $rX, (-$y1p - $cyp) / $rY); + $delta = fmod($delta, pi() * 2); + + if (! $this->sweep) { + $delta -= 2 * pi(); + } + + return [$cx, $cy, $rX, $rY, $theta, $delta]; + } + + private static function angle(float $ux, float $uy, float $vx, float $vy) : float + { + // F.6.5.4 + $dot = $ux * $vx + $uy * $vy; + $length = sqrt($ux ** 2 + $uy ** 2) * sqrt($vx ** 2 + $vy ** 2); + $angle = acos(min(1, max(-1, $dot / $length))); + + if (($ux * $vy - $uy * $vx) < 0) { + return -$angle; + } + + return $angle; + } + + /** + * @return float[] + */ + private static function point( + float $centerX, + float $centerY, + float $radiusX, + float $radiusY, + float $xAngle, + float $angle + ) : array { + return [ + $centerX + $radiusX * cos($xAngle) * cos($angle) - $radiusY * sin($xAngle) * sin($angle), + $centerY + $radiusX * sin($xAngle) * cos($angle) + $radiusY * cos($xAngle) * sin($angle), + ]; + } + + /** + * @return float[] + */ + private static function derivative(float $radiusX, float $radiusY, float $xAngle, float $angle) : array + { + return [ + -$radiusX * cos($xAngle) * sin($angle) - $radiusY * sin($xAngle) * cos($angle), + -$radiusX * sin($xAngle) * sin($angle) + $radiusY * cos($xAngle) * cos($angle), + ]; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/Line.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/Line.php new file mode 100644 index 0000000000..3149a39bcf --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/Line.php @@ -0,0 +1,41 @@ +x = $x; + $this->y = $y; + } + + public function getX() : float + { + return $this->x; + } + + public function getY() : float + { + return $this->y; + } + + /** + * @return self + */ + public function translate(float $x, float $y) : OperationInterface + { + return new self($this->x + $x, $this->y + $y); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/Move.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/Move.php new file mode 100644 index 0000000000..481d0dd153 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/Move.php @@ -0,0 +1,41 @@ +x = $x; + $this->y = $y; + } + + public function getX() : float + { + return $this->x; + } + + public function getY() : float + { + return $this->y; + } + + /** + * @return self + */ + public function translate(float $x, float $y) : OperationInterface + { + return new self($this->x + $x, $this->y + $y); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/OperationInterface.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/OperationInterface.php new file mode 100644 index 0000000000..a5fa0edc4c --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/Path/OperationInterface.php @@ -0,0 +1,12 @@ +operations[] = new Move($x, $y); + return $path; + } + + /** + * Draws a line from the current position to another position. + */ + public function line(float $x, float $y) : self + { + $path = clone $this; + $path->operations[] = new Line($x, $y); + return $path; + } + + /** + * Draws an elliptic arc from the current position to another position. + */ + public function ellipticArc( + float $xRadius, + float $yRadius, + float $xAxisRotation, + bool $largeArc, + bool $sweep, + float $x, + float $y + ) : self { + $path = clone $this; + $path->operations[] = new EllipticArc($xRadius, $yRadius, $xAxisRotation, $largeArc, $sweep, $x, $y); + return $path; + } + + /** + * Draws a curve from the current position to another position. + */ + public function curve(float $x1, float $y1, float $x2, float $y2, float $x3, float $y3) : self + { + $path = clone $this; + $path->operations[] = new Curve($x1, $y1, $x2, $y2, $x3, $y3); + return $path; + } + + /** + * Closes a sub-path. + */ + public function close() : self + { + $path = clone $this; + $path->operations[] = Close::instance(); + return $path; + } + + /** + * Appends another path to this one. + */ + public function append(self $other) : self + { + $path = clone $this; + $path->operations = array_merge($this->operations, $other->operations); + return $path; + } + + public function translate(float $x, float $y) : self + { + $path = new self(); + + foreach ($this->operations as $operation) { + $path->operations[] = $operation->translate($x, $y); + } + + return $path; + } + + /** + * @return OperationInterface[]|Traversable + */ + public function getIterator() : Traversable + { + foreach ($this->operations as $operation) { + yield $operation; + } + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/PlainTextRenderer.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/PlainTextRenderer.php new file mode 100644 index 0000000000..8aa76528ba --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/PlainTextRenderer.php @@ -0,0 +1,86 @@ +margin = $margin; + } + + /** + * @throws InvalidArgumentException if matrix width doesn't match height + */ + public function render(QrCode $qrCode) : string + { + $matrix = $qrCode->getMatrix(); + $matrixSize = $matrix->getWidth(); + + if ($matrixSize !== $matrix->getHeight()) { + throw new InvalidArgumentException('Matrix must have the same width and height'); + } + + $rows = $matrix->getArray()->toArray(); + + if (0 !== $matrixSize % 2) { + $rows[] = array_fill(0, $matrixSize, 0); + } + + $horizontalMargin = str_repeat(self::EMPTY_BLOCK, $this->margin); + $result = str_repeat("\n", (int) ceil($this->margin / 2)); + + for ($i = 0; $i < $matrixSize; $i += 2) { + $result .= $horizontalMargin; + + $upperRow = $rows[$i]; + $lowerRow = $rows[$i + 1]; + + for ($j = 0; $j < $matrixSize; ++$j) { + $upperBit = $upperRow[$j]; + $lowerBit = $lowerRow[$j]; + + if ($upperBit) { + $result .= $lowerBit ? self::FULL_BLOCK : self::UPPER_HALF_BLOCK; + } else { + $result .= $lowerBit ? self::LOWER_HALF_BLOCK : self::EMPTY_BLOCK; + } + } + + $result .= $horizontalMargin . "\n"; + } + + $result .= str_repeat("\n", (int) ceil($this->margin / 2)); + + return $result; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/RendererInterface.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/RendererInterface.php new file mode 100644 index 0000000000..b0aae390d3 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/RendererInterface.php @@ -0,0 +1,11 @@ +externalColor = $externalColor; + $this->internalColor = $internalColor; + } + + public static function uniform(ColorInterface $color) : self + { + return new self($color, $color); + } + + public static function inherit() : self + { + return self::$inherit ?: self::$inherit = new self(null, null); + } + + public function inheritsBothColors() : bool + { + return null === $this->externalColor && null === $this->internalColor; + } + + public function inheritsExternalColor() : bool + { + return null === $this->externalColor; + } + + public function inheritsInternalColor() : bool + { + return null === $this->internalColor; + } + + public function getExternalColor() : ColorInterface + { + if (null === $this->externalColor) { + throw new RuntimeException('External eye color inherits foreground color'); + } + + return $this->externalColor; + } + + public function getInternalColor() : ColorInterface + { + if (null === $this->internalColor) { + throw new RuntimeException('Internal eye color inherits foreground color'); + } + + return $this->internalColor; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/RendererStyle/Fill.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/RendererStyle/Fill.php new file mode 100644 index 0000000000..d54268e634 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/RendererStyle/Fill.php @@ -0,0 +1,168 @@ +backgroundColor = $backgroundColor; + $this->foregroundColor = $foregroundColor; + $this->foregroundGradient = $foregroundGradient; + $this->topLeftEyeFill = $topLeftEyeFill; + $this->topRightEyeFill = $topRightEyeFill; + $this->bottomLeftEyeFill = $bottomLeftEyeFill; + } + + public static function default() : self + { + return self::$default ?: self::$default = self::uniformColor(new Gray(100), new Gray(0)); + } + + public static function withForegroundColor( + ColorInterface $backgroundColor, + ColorInterface $foregroundColor, + EyeFill $topLeftEyeFill, + EyeFill $topRightEyeFill, + EyeFill $bottomLeftEyeFill + ) : self { + return new self( + $backgroundColor, + $foregroundColor, + null, + $topLeftEyeFill, + $topRightEyeFill, + $bottomLeftEyeFill + ); + } + + public static function withForegroundGradient( + ColorInterface $backgroundColor, + Gradient $foregroundGradient, + EyeFill $topLeftEyeFill, + EyeFill $topRightEyeFill, + EyeFill $bottomLeftEyeFill + ) : self { + return new self( + $backgroundColor, + null, + $foregroundGradient, + $topLeftEyeFill, + $topRightEyeFill, + $bottomLeftEyeFill + ); + } + + public static function uniformColor(ColorInterface $backgroundColor, ColorInterface $foregroundColor) : self + { + return new self( + $backgroundColor, + $foregroundColor, + null, + EyeFill::inherit(), + EyeFill::inherit(), + EyeFill::inherit() + ); + } + + public static function uniformGradient(ColorInterface $backgroundColor, Gradient $foregroundGradient) : self + { + return new self( + $backgroundColor, + null, + $foregroundGradient, + EyeFill::inherit(), + EyeFill::inherit(), + EyeFill::inherit() + ); + } + + public function hasGradientFill() : bool + { + return null !== $this->foregroundGradient; + } + + public function getBackgroundColor() : ColorInterface + { + return $this->backgroundColor; + } + + public function getForegroundColor() : ColorInterface + { + if (null === $this->foregroundColor) { + throw new RuntimeException('Fill uses a gradient, thus no foreground color is available'); + } + + return $this->foregroundColor; + } + + public function getForegroundGradient() : Gradient + { + if (null === $this->foregroundGradient) { + throw new RuntimeException('Fill uses a single color, thus no foreground gradient is available'); + } + + return $this->foregroundGradient; + } + + public function getTopLeftEyeFill() : EyeFill + { + return $this->topLeftEyeFill; + } + + public function getTopRightEyeFill() : EyeFill + { + return $this->topRightEyeFill; + } + + public function getBottomLeftEyeFill() : EyeFill + { + return $this->bottomLeftEyeFill; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/RendererStyle/Gradient.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/RendererStyle/Gradient.php new file mode 100644 index 0000000000..3813dfd171 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/RendererStyle/Gradient.php @@ -0,0 +1,46 @@ +startColor = $startColor; + $this->endColor = $endColor; + $this->type = $type; + } + + public function getStartColor() : ColorInterface + { + return $this->startColor; + } + + public function getEndColor() : ColorInterface + { + return $this->endColor; + } + + public function getType() : GradientType + { + return $this->type; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/RendererStyle/GradientType.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/RendererStyle/GradientType.php new file mode 100644 index 0000000000..c1ca754717 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Renderer/RendererStyle/GradientType.php @@ -0,0 +1,22 @@ +margin = $margin; + $this->size = $size; + $this->module = $module ?: SquareModule::instance(); + $this->eye = $eye ?: new ModuleEye($this->module); + $this->fill = $fill ?: Fill::default(); + } + + public function withSize(int $size) : self + { + $style = clone $this; + $style->size = $size; + return $style; + } + + public function withMargin(int $margin) : self + { + $style = clone $this; + $style->margin = $margin; + return $style; + } + + public function getSize() : int + { + return $this->size; + } + + public function getMargin() : int + { + return $this->margin; + } + + public function getModule() : ModuleInterface + { + return $this->module; + } + + public function getEye() : EyeInterface + { + return $this->eye; + } + + public function getFill() : Fill + { + return $this->fill; + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Writer.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Writer.php new file mode 100644 index 0000000000..d5bdc5c352 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/src/Writer.php @@ -0,0 +1,71 @@ +renderer = $renderer; + } + + /** + * Writes QR code and returns it as string. + * + * Content is a string which *should* be encoded in UTF-8, in case there are + * non ASCII-characters present. + * + * @throws InvalidArgumentException if the content is empty + */ + public function writeString( + string $content, + string $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING, + ?ErrorCorrectionLevel $ecLevel = null, + ?Version $forcedVersion = null + ) : string { + if (strlen($content) === 0) { + throw new InvalidArgumentException('Found empty contents'); + } + + if (null === $ecLevel) { + $ecLevel = ErrorCorrectionLevel::L(); + } + + return $this->renderer->render(Encoder::encode($content, $ecLevel, $encoding, $forcedVersion)); + } + + /** + * Writes QR code to a file. + * + * @see Writer::writeString() + */ + public function writeFile( + string $content, + string $filename, + string $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING, + ?ErrorCorrectionLevel $ecLevel = null, + ?Version $forcedVersion = null + ) : void { + file_put_contents($filename, $this->writeString($content, $encoding, $ecLevel, $forcedVersion)); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/BitArrayTest.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/BitArrayTest.php new file mode 100644 index 0000000000..add798b1b3 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/BitArrayTest.php @@ -0,0 +1,222 @@ +assertFalse($array->get($i)); + $array->set($i); + $this->assertTrue($array->get($i)); + } + } + + public function testGetNextSet1() : void + { + $array = new BitArray(32); + + for ($i = 0; $i < $array->getSize(); ++$i) { + if ($this->getPhpUnitMajorVersion() === 7) { + $this->assertEquals($i, 32, '', $array->getNextSet($i)); + } else { + $this->assertEqualsWithDelta($i, 32, $array->getNextSet($i)); + } + } + + $array = new BitArray(33); + + for ($i = 0; $i < $array->getSize(); ++$i) { + if ($this->getPhpUnitMajorVersion() === 7) { + $this->assertEquals($i, 33, '', $array->getNextSet($i)); + } else { + $this->assertEqualsWithDelta($i, 33, $array->getNextSet($i)); + } + } + } + + public function testGetNextSet2() : void + { + $array = new BitArray(33); + + for ($i = 0; $i < $array->getSize(); ++$i) { + if ($this->getPhpUnitMajorVersion() === 7) { + $this->assertEquals($i, $i <= 31 ? 31 : 33, '', $array->getNextSet($i)); + } else { + $this->assertEqualsWithDelta($i, $i <= 31 ? 31 : 33, $array->getNextSet($i)); + } + } + + $array = new BitArray(33); + + for ($i = 0; $i < $array->getSize(); ++$i) { + if ($this->getPhpUnitMajorVersion() === 7) { + $this->assertEquals($i, 32, '', $array->getNextSet($i)); + } else { + $this->assertEqualsWithDelta($i, 32, $array->getNextSet($i)); + } + } + } + + public function testGetNextSet3() : void + { + $array = new BitArray(63); + $array->set(31); + $array->set(32); + + for ($i = 0; $i < $array->getSize(); ++$i) { + if ($i <= 31) { + $expected = 31; + } elseif ($i <= 32) { + $expected = 32; + } else { + $expected = 63; + } + + if ($this->getPhpUnitMajorVersion() === 7) { + $this->assertEquals($i, $expected, '', $array->getNextSet($i)); + } else { + $this->assertEqualsWithDelta($i, $expected, $array->getNextSet($i)); + } + } + } + + public function testGetNextSet4() : void + { + $array = new BitArray(63); + $array->set(33); + $array->set(40); + + for ($i = 0; $i < $array->getSize(); ++$i) { + if ($i <= 33) { + $expected = 33; + } elseif ($i <= 40) { + $expected = 40; + } else { + $expected = 63; + } + + if ($this->getPhpUnitMajorVersion() === 7) { + $this->assertEquals($i, $expected, '', $array->getNextSet($i)); + } else { + $this->assertEqualsWithDelta($i, $expected, $array->getNextSet($i)); + } + } + } + + public function testGetNextSet5() : void + { + mt_srand(0xdeadbeef, MT_RAND_PHP); + + for ($i = 0; $i < 10; ++$i) { + $array = new BitArray(mt_rand(1, 100)); + $numSet = mt_rand(0, 19); + + for ($j = 0; $j < $numSet; ++$j) { + $array->set(mt_rand(0, $array->getSize() - 1)); + } + + $numQueries = mt_rand(0, 19); + + for ($j = 0; $j < $numQueries; ++$j) { + $query = mt_rand(0, $array->getSize() - 1); + $expected = $query; + + while ($expected < $array->getSize() && ! $array->get($expected)) { + ++$expected; + } + + $actual = $array->getNextSet($query); + + if ($actual !== $expected) { + $array->getNextSet($query); + } + + $this->assertEquals($expected, $actual); + } + } + } + + public function testSetBulk() : void + { + $array = new BitArray(64); + $array->setBulk(32, 0xFFFF0000); + + for ($i = 0; $i < 48; ++$i) { + $this->assertFalse($array->get($i)); + } + + for ($i = 48; $i < 64; ++$i) { + $this->assertTrue($array->get($i)); + } + } + + public function testClear() : void + { + $array = new BitArray(32); + + for ($i = 0; $i < 32; ++$i) { + $array->set($i); + } + + $array->clear(); + + for ($i = 0; $i < 32; ++$i) { + $this->assertFalse($array->get($i)); + } + } + + public function testGetArray() : void + { + $array = new BitArray(64); + $array->set(0); + $array->set(63); + + $ints = $array->getBitArray(); + + $this->assertSame(1, $ints[0]); + $this->assertSame(0x80000000, $ints[1]); + } + + public function testIsRange() : void + { + $array = new BitArray(64); + $this->assertTrue($array->isRange(0, 64, false)); + $this->assertFalse($array->isRange(0, 64, true)); + + $array->set(32); + $this->assertTrue($array->isRange(32, 33, true)); + + $array->set(31); + $this->assertTrue($array->isRange(31, 33, true)); + + $array->set(34); + $this->assertFalse($array->isRange(31, 35, true)); + + for ($i = 0; $i < 31; ++$i) { + $array->set($i); + } + + $this->assertTrue($array->isRange(0, 33, true)); + + for ($i = 33; $i < 64; ++$i) { + $array->set($i); + } + + $this->assertTrue($array->isRange(0, 64, true)); + $this->assertFalse($array->isRange(0, 64, false)); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/BitMatrixTest.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/BitMatrixTest.php new file mode 100644 index 0000000000..8ad86d4c22 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/BitMatrixTest.php @@ -0,0 +1,115 @@ +assertEquals(33, $matrix->getHeight()); + + for ($y = 0; $y < 33; ++$y) { + for ($x = 0; $x < 33; ++$x) { + if ($y * $x % 3 === 0) { + $matrix->set($x, $y); + } + } + } + + for ($y = 0; $y < 33; $y++) { + for ($x = 0; $x < 33; ++$x) { + $this->assertSame(0 === $x * $y % 3, $matrix->get($x, $y)); + } + } + } + + public function testSetRegion() : void + { + $matrix = new BitMatrix(5); + $matrix->setRegion(1, 1, 3, 3); + + for ($y = 0; $y < 5; ++$y) { + for ($x = 0; $x < 5; ++$x) { + $this->assertSame($y >= 1 && $y <= 3 && $x >= 1 && $x <= 3, $matrix->get($x, $y)); + } + } + } + + public function testRectangularMatrix() : void + { + $matrix = new BitMatrix(75, 20); + $this->assertSame(75, $matrix->getWidth()); + $this->assertSame(20, $matrix->getHeight()); + + $matrix->set(10, 0); + $matrix->set(11, 1); + $matrix->set(50, 2); + $matrix->set(51, 3); + $matrix->flip(74, 4); + $matrix->flip(0, 5); + + $this->assertTrue($matrix->get(10, 0)); + $this->assertTrue($matrix->get(11, 1)); + $this->assertTrue($matrix->get(50, 2)); + $this->assertTrue($matrix->get(51, 3)); + $this->assertTrue($matrix->get(74, 4)); + $this->assertTrue($matrix->get(0, 5)); + + $matrix->flip(50, 2); + $matrix->flip(51, 3); + + $this->assertFalse($matrix->get(50, 2)); + $this->assertFalse($matrix->get(51, 3)); + } + + public function testRectangularSetRegion() : void + { + $matrix = new BitMatrix(320, 240); + $this->assertSame(320, $matrix->getWidth()); + $this->assertSame(240, $matrix->getHeight()); + + $matrix->setRegion(105, 22, 80, 12); + + for ($y = 0; $y < 240; ++$y) { + for ($x = 0; $x < 320; ++$x) { + $this->assertEquals($y >= 22 && $y < 34 && $x >= 105 && $x < 185, $matrix->get($x, $y)); + } + } + } + + public function testGetRow() : void + { + $matrix = new BitMatrix(102, 5); + + for ($x = 0; $x < 102; ++$x) { + if (0 === ($x & 3)) { + $matrix->set($x, 2); + } + } + + $array1 = $matrix->getRow(2, null); + $this->assertSame(102, $array1->getSize()); + + $array2 = new BitArray(60); + $array2 = $matrix->getRow(2, $array2); + $this->assertSame(102, $array2->getSize()); + + $array3 = new BitArray(200); + $array3 = $matrix->getRow(2, $array3); + $this->assertSame(200, $array3->getSize()); + + for ($x = 0; $x < 102; ++$x) { + $on = (0 === ($x & 3)); + + $this->assertSame($on, $array1->get($x)); + $this->assertSame($on, $array2->get($x)); + $this->assertSame($on, $array3->get($x)); + } + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/BitUtilsTest.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/BitUtilsTest.php new file mode 100644 index 0000000000..2904d312fa --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/BitUtilsTest.php @@ -0,0 +1,25 @@ +assertSame(1, BitUtils::unsignedRightShift(1, 0)); + $this->assertSame(1, BitUtils::unsignedRightShift(10, 3)); + $this->assertSame(536870910, BitUtils::unsignedRightShift(-10, 3)); + } + + public function testNumberOfTrailingZeros() : void + { + $this->assertSame(32, BitUtils::numberOfTrailingZeros(0)); + $this->assertSame(1, BitUtils::numberOfTrailingZeros(10)); + $this->assertSame(0, BitUtils::numberOfTrailingZeros(15)); + $this->assertSame(2, BitUtils::numberOfTrailingZeros(20)); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/ErrorCorrectionLevelTest.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/ErrorCorrectionLevelTest.php new file mode 100644 index 0000000000..369b5d917f --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/ErrorCorrectionLevelTest.php @@ -0,0 +1,25 @@ +assertSame(0x0, ErrorCorrectionLevel::M()->getBits()); + $this->assertSame(0x1, ErrorCorrectionLevel::L()->getBits()); + $this->assertSame(0x2, ErrorCorrectionLevel::H()->getBits()); + $this->assertSame(0x3, ErrorCorrectionLevel::Q()->getBits()); + } + + public function testInvalidErrorCorrectionLevelThrowsException() : void + { + $this->expectException(OutOfBoundsException::class); + ErrorCorrectionLevel::forBits(4); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/FormatInformationTest.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/FormatInformationTest.php new file mode 100644 index 0000000000..39534a24af --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/FormatInformationTest.php @@ -0,0 +1,94 @@ +assertSame(0, FormatInformation::numBitsDiffering(1, 1)); + $this->assertSame(1, FormatInformation::numBitsDiffering(0, 2)); + $this->assertSame(2, FormatInformation::numBitsDiffering(1, 2)); + $this->assertEquals(32, FormatInformation::numBitsDiffering(-1, 0)); + } + + public function testDecode() : void + { + $expected = FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO, + self::MASKED_TEST_FORMAT_INFO + ); + + $this->assertNotNull($expected); + $this->assertSame(7, $expected->getDataMask()); + $this->assertSame(ErrorCorrectionLevel::Q(), $expected->getErrorCorrectionLevel()); + + $this->assertEquals( + $expected, + FormatInformation::decodeFormatInformation( + self::UNMAKSED_TEST_FORMAT_INFO, + self::MASKED_TEST_FORMAT_INFO + ) + ); + } + + public function testDecodeWithBitDifference() : void + { + $expected = FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO, + self::MASKED_TEST_FORMAT_INFO + ); + + $this->assertEquals( + $expected, + FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO ^ 0x1, + self::MASKED_TEST_FORMAT_INFO ^ 0x1 + ) + ); + $this->assertEquals( + $expected, + FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO ^ 0x3, + self::MASKED_TEST_FORMAT_INFO ^ 0x3 + ) + ); + $this->assertEquals( + $expected, + FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO ^ 0x7, + self::MASKED_TEST_FORMAT_INFO ^ 0x7 + ) + ); + $this->assertNull( + FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO ^ 0xf, + self::MASKED_TEST_FORMAT_INFO ^ 0xf + ) + ); + } + + public function testDecodeWithMisRead() : void + { + $expected = FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO, + self::MASKED_TEST_FORMAT_INFO + ); + + $this->assertEquals( + $expected, + FormatInformation::decodeFormatInformation( + self::MASKED_TEST_FORMAT_INFO ^ 0x3, + self::MASKED_TEST_FORMAT_INFO ^ 0xf + ) + ); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/ModeTest.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/ModeTest.php new file mode 100644 index 0000000000..51fcb3eb40 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/ModeTest.php @@ -0,0 +1,19 @@ +assertSame(0x0, Mode::TERMINATOR()->getBits()); + $this->assertSame(0x1, Mode::NUMERIC()->getBits()); + $this->assertSame(0x2, Mode::ALPHANUMERIC()->getBits()); + $this->assertSame(0x4, Mode::BYTE()->getBits()); + $this->assertSame(0x8, Mode::KANJI()->getBits()); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/ReedSolomonCodecTest.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/ReedSolomonCodecTest.php new file mode 100644 index 0000000000..47975b523f --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/ReedSolomonCodecTest.php @@ -0,0 +1,96 @@ +encode($block, $parity); + + // Copy parity into test blocks + for ($i = 0; $i < $numRoots; ++$i) { + $block[$i + $dataSize] = $parity[$i]; + $tBlock[$i + $dataSize] = $parity[$i]; + } + + // Seed with errors + for ($i = 0; $i < $errors; ++$i) { + $errorValue = mt_rand(1, $blockSize); + + do { + $errorLocation = mt_rand(0, $blockSize); + } while (0 !== $errorLocations[$errorLocation]); + + $errorLocations[$errorLocation] = 1; + + if (mt_rand(0, 1)) { + $erasures[] = $errorLocation; + } + + $tBlock[$errorLocation] ^= $errorValue; + } + + $erasures = SplFixedArray::fromArray($erasures, false); + + // Decode the errored block + $foundErrors = $codec->decode($tBlock, $erasures); + + if ($errors > 0 && null === $foundErrors) { + $this->assertSame($block, $tBlock, 'Decoder failed to correct errors'); + } + + $this->assertSame($errors, $foundErrors, 'Found errors do not equal expected errors'); + + for ($i = 0; $i < $foundErrors; ++$i) { + if (0 === $errorLocations[$erasures[$i]]) { + $this->fail(sprintf('Decoder indicates error in location %d without error', $erasures[$i])); + } + } + + $this->assertEquals($block, $tBlock, 'Decoder did not correct errors'); + } + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/VersionTest.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/VersionTest.php new file mode 100644 index 0000000000..f6f038ba01 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Common/VersionTest.php @@ -0,0 +1,78 @@ +assertNotNull($version); + $this->assertEquals($versionNumber, $version->getVersionNumber()); + $this->assertNotNull($version->getAlignmentPatternCenters()); + + if ($versionNumber > 1) { + $this->assertTrue(count($version->getAlignmentPatternCenters()) > 0); + } + + $this->assertEquals($dimension, $version->getDimensionForVersion()); + $this->assertNotNull($version->getEcBlocksForLevel(ErrorCorrectionLevel::H())); + $this->assertNotNull($version->getEcBlocksForLevel(ErrorCorrectionLevel::L())); + $this->assertNotNull($version->getEcBlocksForLevel(ErrorCorrectionLevel::M())); + $this->assertNotNull($version->getEcBlocksForLevel(ErrorCorrectionLevel::Q())); + $this->assertNotNull($version->buildFunctionPattern()); + } + + /** + * @dataProvider versions + */ + public function testGetProvisionalVersionForDimension(int $versionNumber, int $dimension) : void + { + $this->assertSame( + $versionNumber, + Version::getProvisionalVersionForDimension($dimension)->getVersionNumber() + ); + } + + /** + * @dataProvider decodeInformation + */ + public function testDecodeVersionInformation(int $expectedVersion, int $mask) : void + { + $version = Version::decodeVersionInformation($mask); + $this->assertNotNull($version); + $this->assertSame($expectedVersion, $version->getVersionNumber()); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Encoder/EncoderTest.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Encoder/EncoderTest.php new file mode 100644 index 0000000000..9baa66b161 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Encoder/EncoderTest.php @@ -0,0 +1,487 @@ +getMethods(ReflectionMethod::IS_STATIC) as $method) { + $method->setAccessible(true); + $this->methods[$method->getName()] = $method; + } + } + + public function testGetAlphanumericCode() : void + { + // The first ten code points are numbers. + for ($i = 0; $i < 10; ++$i) { + $this->assertSame($i, $this->methods['getAlphanumericCode']->invoke(null, ord('0') + $i)); + } + + // The next 26 code points are capital alphabet letters. + for ($i = 10; $i < 36; ++$i) { + // The first ten code points are numbers + $this->assertSame($i, $this->methods['getAlphanumericCode']->invoke(null, ord('A') + $i - 10)); + } + + // Others are symbol letters. + $this->assertSame(36, $this->methods['getAlphanumericCode']->invoke(null, ord(' '))); + $this->assertSame(37, $this->methods['getAlphanumericCode']->invoke(null, ord('$'))); + $this->assertSame(38, $this->methods['getAlphanumericCode']->invoke(null, ord('%'))); + $this->assertSame(39, $this->methods['getAlphanumericCode']->invoke(null, ord('*'))); + $this->assertSame(40, $this->methods['getAlphanumericCode']->invoke(null, ord('+'))); + $this->assertSame(41, $this->methods['getAlphanumericCode']->invoke(null, ord('-'))); + $this->assertSame(42, $this->methods['getAlphanumericCode']->invoke(null, ord('.'))); + $this->assertSame(43, $this->methods['getAlphanumericCode']->invoke(null, ord('/'))); + $this->assertSame(44, $this->methods['getAlphanumericCode']->invoke(null, ord(':'))); + + // Should return -1 for other letters. + $this->assertSame(-1, $this->methods['getAlphanumericCode']->invoke(null, ord('a'))); + $this->assertSame(-1, $this->methods['getAlphanumericCode']->invoke(null, ord('#'))); + $this->assertSame(-1, $this->methods['getAlphanumericCode']->invoke(null, ord("\0"))); + } + + public function testChooseMode() : void + { + // Numeric mode + $this->assertSame(Mode::NUMERIC(), $this->methods['chooseMode']->invoke(null, '0')); + $this->assertSame(Mode::NUMERIC(), $this->methods['chooseMode']->invoke(null, '0123456789')); + + // Alphanumeric mode + $this->assertSame(Mode::ALPHANUMERIC(), $this->methods['chooseMode']->invoke(null, 'A')); + $this->assertSame( + Mode::ALPHANUMERIC(), + $this->methods['chooseMode']->invoke(null, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:') + ); + + // 8-bit byte mode + $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, 'a')); + $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, '#')); + $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, '')); + + // AIUE in Hiragana in SHIFT-JIS + $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, "\x8\xa\x8\xa\x8\xa\x8\xa6")); + + // Nihon in Kanji in SHIFT-JIS + $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, "\x9\xf\x9\x7b")); + + // Sou-Utso-Byou in Kanji in SHIFT-JIS + $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, "\xe\x4\x9\x5\x9\x61")); + } + + public function testEncode() : void + { + $qrCode = Encoder::encode('ABCDEF', ErrorCorrectionLevel::H()); + $expected = "<<\n" + . " mode: ALPHANUMERIC\n" + . " ecLevel: H\n" + . " version: 1\n" + . " maskPattern: 0\n" + . " matrix:\n" + . " 1 1 1 1 1 1 1 0 1 1 1 1 0 0 1 1 1 1 1 1 1\n" + . " 1 0 0 0 0 0 1 0 0 1 1 1 0 0 1 0 0 0 0 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 0 1 1 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 1 1 1 0 1 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 1 1 0 0 1 0 1 1 1 0 1\n" + . " 1 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 1\n" + . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" + . " 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0\n" + . " 0 0 1 0 1 1 1 0 1 1 0 0 1 1 0 0 0 1 0 0 1\n" + . " 1 0 1 1 1 0 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0\n" + . " 0 0 1 1 0 0 1 0 1 0 0 0 1 0 1 0 1 0 1 1 0\n" + . " 1 1 0 1 0 1 0 1 1 1 0 1 0 1 0 0 0 0 0 1 0\n" + . " 0 0 1 1 0 1 1 1 1 0 0 0 1 0 1 0 1 1 1 1 0\n" + . " 0 0 0 0 0 0 0 0 1 0 0 1 1 1 0 1 0 1 0 0 0\n" + . " 1 1 1 1 1 1 1 0 0 0 1 0 1 0 1 1 0 0 0 0 1\n" + . " 1 0 0 0 0 0 1 0 1 1 1 1 0 1 0 1 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 1 0 1 1 0 1 0 1 0 0 0 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 1 0 1 1 1 1 0 1 0 1 0\n" + . " 1 0 1 1 1 0 1 0 1 0 0 0 1 0 1 0 1 1 1 0 1\n" + . " 1 0 0 0 0 0 1 0 0 1 1 0 1 1 0 1 0 0 0 1 1\n" + . " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 0 1 0 1\n" + . ">>\n"; + + $this->assertSame($expected, (string) $qrCode); + } + + public function testSimpleUtf8Eci() : void + { + $qrCode = Encoder::encode('hello', ErrorCorrectionLevel::H(), 'utf-8'); + $expected = "<<\n" + . " mode: BYTE\n" + . " ecLevel: H\n" + . " version: 1\n" + . " maskPattern: 3\n" + . " matrix:\n" + . " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1\n" + . " 1 0 0 0 0 0 1 0 0 0 1 0 1 0 1 0 0 0 0 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 0 1 0 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 1 0 1 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 0 1\n" + . " 1 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1\n" + . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" + . " 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0\n" + . " 0 0 1 1 0 0 1 1 1 1 0 0 0 1 1 0 1 0 0 0 0\n" + . " 0 0 1 1 1 0 0 0 0 0 1 1 0 0 0 1 0 1 1 1 0\n" + . " 0 1 0 1 0 1 1 1 0 1 0 1 0 0 0 0 0 1 1 1 1\n" + . " 1 1 0 0 1 0 0 1 1 0 0 1 1 1 1 0 1 0 1 1 0\n" + . " 0 0 0 0 1 0 1 1 1 1 0 0 0 0 0 1 0 0 1 0 0\n" + . " 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1 1 1 0 0 0 1\n" + . " 1 1 1 1 1 1 1 0 1 1 1 0 1 0 1 1 0 0 1 0 0\n" + . " 1 0 0 0 0 0 1 0 0 0 1 0 0 1 1 1 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 0 0 0 0 1 1 0 0 0 0 0\n" + . " 1 0 1 1 1 0 1 0 1 1 1 0 1 0 0 0 1 1 0 0 0\n" + . " 1 0 1 1 1 0 1 0 1 1 0 0 0 1 0 0 1 0 0 0 0\n" + . " 1 0 0 0 0 0 1 0 0 0 0 1 1 0 1 0 1 0 1 1 0\n" + . " 1 1 1 1 1 1 1 0 0 1 0 1 1 1 0 1 1 0 0 0 0\n" + . ">>\n"; + + $this->assertSame($expected, (string) $qrCode); + } + + public function testAppendModeInfo() : void + { + $bits = new BitArray(); + $this->methods['appendModeInfo']->invoke(null, Mode::NUMERIC(), $bits); + $this->assertSame(' ...X', (string) $bits); + } + + public function testAppendLengthInfo() : void + { + // 1 letter (1/1), 10 bits. + $bits = new BitArray(); + $this->methods['appendLengthInfo']->invoke( + null, + 1, + Version::getVersionForNumber(1), + Mode::NUMERIC(), + $bits + ); + $this->assertSame(' ........ .X', (string) $bits); + + // 2 letters (2/1), 11 bits. + $bits = new BitArray(); + $this->methods['appendLengthInfo']->invoke( + null, + 2, + Version::getVersionForNumber(10), + Mode::ALPHANUMERIC(), + $bits + ); + $this->assertSame(' ........ .X.', (string) $bits); + + // 255 letters (255/1), 16 bits. + $bits = new BitArray(); + $this->methods['appendLengthInfo']->invoke( + null, + 255, + Version::getVersionForNumber(27), + Mode::BYTE(), + $bits + ); + $this->assertSame(' ........ XXXXXXXX', (string) $bits); + + // 512 letters (1024/2), 12 bits. + $bits = new BitArray(); + $this->methods['appendLengthInfo']->invoke( + null, + 512, + Version::getVersionForNumber(40), + Mode::KANJI(), + $bits + ); + $this->assertSame(' ..X..... ....', (string) $bits); + } + + public function testAppendBytes() : void + { + // Should use appendNumericBytes. + // 1 = 01 = 0001 in 4 bits. + $bits = new BitArray(); + $this->methods['appendBytes']->invoke( + null, + '1', + Mode::NUMERIC(), + $bits, + Encoder::DEFAULT_BYTE_MODE_ECODING + ); + $this->assertSame(' ...X', (string) $bits); + + // Should use appendAlphaNumericBytes. + // A = 10 = 0xa = 001010 in 6 bits. + $bits = new BitArray(); + $this->methods['appendBytes']->invoke( + null, + 'A', + Mode::ALPHANUMERIC(), + $bits, + Encoder::DEFAULT_BYTE_MODE_ECODING + ); + $this->assertSame(' ..X.X.', (string) $bits); + + // Should use append8BitBytes. + // 0x61, 0x62, 0x63 + $bits = new BitArray(); + $this->methods['appendBytes']->invoke( + null, + 'abc', + Mode::BYTE(), + $bits, + Encoder::DEFAULT_BYTE_MODE_ECODING + ); + $this->assertSame(' .XX....X .XX...X. .XX...XX', (string) $bits); + + // Should use appendKanjiBytes. + // 0x93, 0x5f + $bits = new BitArray(); + $this->methods['appendBytes']->invoke( + null, + "\x93\x5f", + Mode::KANJI(), + $bits, + Encoder::DEFAULT_BYTE_MODE_ECODING + ); + $this->assertSame(' .XX.XX.. XXXXX', (string) $bits); + + // Lower letters such as 'a' cannot be encoded in alphanumeric mode. + $this->expectException(WriterException::class); + $this->methods['appendBytes']->invoke( + null, + 'a', + Mode::ALPHANUMERIC(), + $bits, + Encoder::DEFAULT_BYTE_MODE_ECODING + ); + } + + public function testTerminateBits() : void + { + $bits = new BitArray(); + $this->methods['terminateBits']->invoke(null, 0, $bits); + $this->assertSame('', (string) $bits); + + $bits = new BitArray(); + $this->methods['terminateBits']->invoke(null, 1, $bits); + $this->assertSame(' ........', (string) $bits); + + $bits = new BitArray(); + $bits->appendBits(0, 3); + $this->methods['terminateBits']->invoke(null, 1, $bits); + $this->assertSame(' ........', (string) $bits); + + $bits = new BitArray(); + $bits->appendBits(0, 5); + $this->methods['terminateBits']->invoke(null, 1, $bits); + $this->assertSame(' ........', (string) $bits); + + $bits = new BitArray(); + $bits->appendBits(0, 8); + $this->methods['terminateBits']->invoke(null, 1, $bits); + $this->assertSame(' ........', (string) $bits); + + $bits = new BitArray(); + $this->methods['terminateBits']->invoke(null, 2, $bits); + $this->assertSame(' ........ XXX.XX..', (string) $bits); + + $bits = new BitArray(); + $bits->appendBits(0, 1); + $this->methods['terminateBits']->invoke(null, 3, $bits); + $this->assertSame(' ........ XXX.XX.. ...X...X', (string) $bits); + } + + public function testGetNumDataBytesAndNumEcBytesForBlockId() : void + { + // Version 1-H. + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 26, 9, 1, 0); + $this->assertSame(9, $numDataBytes); + $this->assertSame(17, $numEcBytes); + + // Version 3-H. 2 blocks. + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 70, 26, 2, 0); + $this->assertSame(13, $numDataBytes); + $this->assertSame(22, $numEcBytes); + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 70, 26, 2, 1); + $this->assertSame(13, $numDataBytes); + $this->assertSame(22, $numEcBytes); + + // Version 7-H. (4 + 1) blocks. + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 196, 66, 5, 0); + $this->assertSame(13, $numDataBytes); + $this->assertSame(26, $numEcBytes); + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 196, 66, 5, 4); + $this->assertSame(14, $numDataBytes); + $this->assertSame(26, $numEcBytes); + + // Version 40-H. (20 + 61) blocks. + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 3706, 1276, 81, 0); + $this->assertSame(15, $numDataBytes); + $this->assertSame(30, $numEcBytes); + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 3706, 1276, 81, 20); + $this->assertSame(16, $numDataBytes); + $this->assertSame(30, $numEcBytes); + list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] + ->invoke(null, 3706, 1276, 81, 80); + $this->assertSame(16, $numDataBytes); + $this->assertSame(30, $numEcBytes); + } + + public function testInterleaveWithEcBytes() : void + { + $dataBytes = SplFixedArray::fromArray([32, 65, 205, 69, 41, 220, 46, 128, 236], false); + $in = new BitArray(); + + foreach ($dataBytes as $dataByte) { + $in->appendBits($dataByte, 8); + } + + $outBits = $this->methods['interleaveWithEcBytes']->invoke(null, $in, 26, 9, 1); + $expected = SplFixedArray::fromArray([ + // Data bytes. + 32, 65, 205, 69, 41, 220, 46, 128, 236, + // Error correction bytes. + 42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219, 61, + ], false); + + $out = $outBits->toBytes(0, count($expected)); + + $this->assertEquals($expected, $out); + } + + public function testAppendNumericBytes() : void + { + // 1 = 01 = 0001 in 4 bits. + $bits = new BitArray(); + $this->methods['appendNumericBytes']->invoke(null, '1', $bits); + $this->assertSame(' ...X', (string) $bits); + + // 12 = 0xc = 0001100 in 7 bits. + $bits = new BitArray(); + $this->methods['appendNumericBytes']->invoke(null, '12', $bits); + $this->assertSame(' ...XX..', (string) $bits); + + // 123 = 0x7b = 0001111011 in 10 bits. + $bits = new BitArray(); + $this->methods['appendNumericBytes']->invoke(null, '123', $bits); + $this->assertSame(' ...XXXX. XX', (string) $bits); + + // 1234 = "123" + "4" = 0001111011 + 0100 in 14 bits. + $bits = new BitArray(); + $this->methods['appendNumericBytes']->invoke(null, '1234', $bits); + $this->assertSame(' ...XXXX. XX.X..', (string) $bits); + + // Empty + $bits = new BitArray(); + $this->methods['appendNumericBytes']->invoke(null, '', $bits); + $this->assertSame('', (string) $bits); + } + + public function testAppendAlphanumericBytes() : void + { + $bits = new BitArray(); + $this->methods['appendAlphanumericBytes']->invoke(null, 'A', $bits); + $this->assertSame(' ..X.X.', (string) $bits); + + $bits = new BitArray(); + $this->methods['appendAlphanumericBytes']->invoke(null, 'AB', $bits); + $this->assertSame(' ..XXX..X X.X', (string) $bits); + + $bits = new BitArray(); + $this->methods['appendAlphanumericBytes']->invoke(null, 'ABC', $bits); + $this->assertSame(' ..XXX..X X.X..XX. .', (string) $bits); + + // Empty + $bits = new BitArray(); + $this->methods['appendAlphanumericBytes']->invoke(null, '', $bits); + $this->assertSame('', (string) $bits); + + // Invalid data + $this->expectException(WriterException::class); + $bits = new BitArray(); + $this->methods['appendAlphanumericBytes']->invoke(null, 'abc', $bits); + } + + public function testAppend8BitBytes() : void + { + // 0x61, 0x62, 0x63 + $bits = new BitArray(); + $this->methods['append8BitBytes']->invoke(null, 'abc', $bits, Encoder::DEFAULT_BYTE_MODE_ECODING); + $this->assertSame(' .XX....X .XX...X. .XX...XX', (string) $bits); + + // Empty + $bits = new BitArray(); + $this->methods['append8BitBytes']->invoke(null, '', $bits, Encoder::DEFAULT_BYTE_MODE_ECODING); + $this->assertSame('', (string) $bits); + } + + public function testAppendKanjiBytes() : void + { + // Numbers are from page 21 of JISX0510:2004 + $bits = new BitArray(); + $this->methods['appendKanjiBytes']->invoke(null, "\x93\x5f", $bits); + $this->assertSame(' .XX.XX.. XXXXX', (string) $bits); + + $this->methods['appendKanjiBytes']->invoke(null, "\xe4\xaa", $bits); + $this->assertSame(' .XX.XX.. XXXXXXX. X.X.X.X. X.', (string) $bits); + } + + public function testGenerateEcBytes() : void + { + // Numbers are from http://www.swetake.com/qr/qr3.html and + // http://www.swetake.com/qr/qr9.html + $dataBytes = SplFixedArray::fromArray([32, 65, 205, 69, 41, 220, 46, 128, 236], false); + $ecBytes = $this->methods['generateEcBytes']->invoke(null, $dataBytes, 17); + $expected = SplFixedArray::fromArray( + [42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219, 61], + false + ); + $this->assertEquals($expected, $ecBytes); + + $dataBytes = SplFixedArray::fromArray( + [67, 70, 22, 38, 54, 70, 86, 102, 118, 134, 150, 166, 182, 198, 214], + false + ); + $ecBytes = $this->methods['generateEcBytes']->invoke(null, $dataBytes, 18); + $expected = SplFixedArray::fromArray( + [175, 80, 155, 64, 178, 45, 214, 233, 65, 209, 12, 155, 117, 31, 140, 214, 27, 187], + false + ); + $this->assertEquals($expected, $ecBytes); + + // High-order zero coefficient case. + $dataBytes = SplFixedArray::fromArray([32, 49, 205, 69, 42, 20, 0, 236, 17], false); + $ecBytes = $this->methods['generateEcBytes']->invoke(null, $dataBytes, 17); + $expected = SplFixedArray::fromArray( + [0, 3, 130, 179, 194, 0, 55, 211, 110, 79, 98, 72, 170, 96, 211, 137, 213], + false + ); + $this->assertEquals($expected, $ecBytes); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Encoder/MaskUtilTest.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Encoder/MaskUtilTest.php new file mode 100644 index 0000000000..46670fce7b --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Encoder/MaskUtilTest.php @@ -0,0 +1,251 @@ +assertSame( + 1 === $expected[$y][$x], + MaskUtil::getDataMaskBit($maskPattern, $x, $y) + ); + } + } + } + + public function testApplyMaskPenaltyRule1() : void + { + $matrix = new ByteMatrix(4, 1); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 0); + $matrix->set(2, 0, 0); + $matrix->set(3, 0, 0); + + $this->assertSame(0, MaskUtil::applyMaskPenaltyRule1($matrix)); + + // Horizontal + $matrix = new ByteMatrix(6, 1); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 0); + $matrix->set(2, 0, 0); + $matrix->set(3, 0, 0); + $matrix->set(4, 0, 0); + $matrix->set(5, 0, 1); + $this->assertSame(3, MaskUtil::applyMaskPenaltyRule1($matrix)); + $matrix->set(5, 0, 0); + $this->assertSame(4, MaskUtil::applyMaskPenaltyRule1($matrix)); + + // Vertical + $matrix = new ByteMatrix(1, 6); + $matrix->set(0, 0, 0); + $matrix->set(0, 1, 0); + $matrix->set(0, 2, 0); + $matrix->set(0, 3, 0); + $matrix->set(0, 4, 0); + $matrix->set(0, 5, 1); + $this->assertSame(3, MaskUtil::applyMaskPenaltyRule1($matrix)); + $matrix->set(0, 5, 0); + $this->assertSame(4, MaskUtil::applyMaskPenaltyRule1($matrix)); + } + + public function testApplyMaskPenaltyRule2() : void + { + $matrix = new ByteMatrix(1, 1); + $matrix->set(0, 0, 0); + $this->assertSame(0, MaskUtil::applyMaskPenaltyRule2($matrix)); + + $matrix = new ByteMatrix(2, 2); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 0); + $matrix->set(0, 1, 0); + $matrix->set(1, 1, 1); + $this->assertSame(0, MaskUtil::applyMaskPenaltyRule2($matrix)); + + $matrix = new ByteMatrix(2, 2); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 0); + $matrix->set(0, 1, 0); + $matrix->set(1, 1, 0); + $this->assertSame(3, MaskUtil::applyMaskPenaltyRule2($matrix)); + + $matrix = new ByteMatrix(3, 3); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 0); + $matrix->set(2, 0, 0); + $matrix->set(0, 1, 0); + $matrix->set(1, 1, 0); + $matrix->set(2, 1, 0); + $matrix->set(0, 2, 0); + $matrix->set(1, 2, 0); + $matrix->set(2, 2, 0); + $this->assertSame(3 * 4, MaskUtil::applyMaskPenaltyRule2($matrix)); + } + + public function testApplyMaskPenalty3() : void + { + // Horizontal 00001011101 + $matrix = new ByteMatrix(11, 1); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 0); + $matrix->set(2, 0, 0); + $matrix->set(3, 0, 0); + $matrix->set(4, 0, 1); + $matrix->set(5, 0, 0); + $matrix->set(6, 0, 1); + $matrix->set(7, 0, 1); + $matrix->set(8, 0, 1); + $matrix->set(9, 0, 0); + $matrix->set(10, 0, 1); + $this->assertSame(40, MaskUtil::applyMaskPenaltyRule3($matrix)); + + // Horizontal 10111010000 + $matrix = new ByteMatrix(11, 1); + $matrix->set(0, 0, 1); + $matrix->set(1, 0, 0); + $matrix->set(2, 0, 1); + $matrix->set(3, 0, 1); + $matrix->set(4, 0, 1); + $matrix->set(5, 0, 0); + $matrix->set(6, 0, 1); + $matrix->set(7, 0, 0); + $matrix->set(8, 0, 0); + $matrix->set(9, 0, 0); + $matrix->set(10, 0, 0); + $this->assertSame(40, MaskUtil::applyMaskPenaltyRule3($matrix)); + + // Vertical 00001011101 + $matrix = new ByteMatrix(1, 11); + $matrix->set(0, 0, 0); + $matrix->set(0, 1, 0); + $matrix->set(0, 2, 0); + $matrix->set(0, 3, 0); + $matrix->set(0, 4, 1); + $matrix->set(0, 5, 0); + $matrix->set(0, 6, 1); + $matrix->set(0, 7, 1); + $matrix->set(0, 8, 1); + $matrix->set(0, 9, 0); + $matrix->set(0, 10, 1); + $this->assertSame(40, MaskUtil::applyMaskPenaltyRule3($matrix)); + + // Vertical 10111010000 + $matrix = new ByteMatrix(1, 11); + $matrix->set(0, 0, 1); + $matrix->set(0, 1, 0); + $matrix->set(0, 2, 1); + $matrix->set(0, 3, 1); + $matrix->set(0, 4, 1); + $matrix->set(0, 5, 0); + $matrix->set(0, 6, 1); + $matrix->set(0, 7, 0); + $matrix->set(0, 8, 0); + $matrix->set(0, 9, 0); + $matrix->set(0, 10, 0); + $this->assertSame(40, MaskUtil::applyMaskPenaltyRule3($matrix)); + } + + public function testApplyMaskPenaltyRule4() : void + { + // Dark cell ratio = 0% + $matrix = new ByteMatrix(1, 1); + $matrix->set(0, 0, 0); + $this->assertSame(100, MaskUtil::applyMaskPenaltyRule4($matrix)); + + // Dark cell ratio = 5% + $matrix = new ByteMatrix(2, 1); + $matrix->set(0, 0, 0); + $matrix->set(0, 0, 1); + $this->assertSame(0, MaskUtil::applyMaskPenaltyRule4($matrix)); + + // Dark cell ratio = 66.67% + $matrix = new ByteMatrix(6, 1); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 1); + $matrix->set(2, 0, 1); + $matrix->set(3, 0, 1); + $matrix->set(4, 0, 1); + $matrix->set(5, 0, 0); + $this->assertSame(30, MaskUtil::applyMaskPenaltyRule4($matrix)); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Encoder/MatrixUtilTest.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Encoder/MatrixUtilTest.php new file mode 100644 index 0000000000..106ceaaf56 --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Encoder/MatrixUtilTest.php @@ -0,0 +1,335 @@ +getMethods(ReflectionMethod::IS_STATIC) as $method) { + $method->setAccessible(true); + $this->methods[$method->getName()] = $method; + } + } + + public function testToString() : void + { + $matrix = new ByteMatrix(3, 3); + $matrix->set(0, 0, 0); + $matrix->set(1, 0, 1); + $matrix->set(2, 0, 0); + $matrix->set(0, 1, 1); + $matrix->set(1, 1, 0); + $matrix->set(2, 1, 1); + $matrix->set(0, 2, -1); + $matrix->set(1, 2, -1); + $matrix->set(2, 2, -1); + + $expected = " 0 1 0\n 1 0 1\n \n"; + $this->assertSame($expected, (string) $matrix); + } + + public function testClearMatrix() : void + { + $matrix = new ByteMatrix(2, 2); + MatrixUtil::clearMatrix($matrix); + + $this->assertSame(-1, $matrix->get(0, 0)); + $this->assertSame(-1, $matrix->get(1, 0)); + $this->assertSame(-1, $matrix->get(0, 1)); + $this->assertSame(-1, $matrix->get(1, 1)); + } + + public function testEmbedBasicPatterns1() : void + { + $matrix = new ByteMatrix(21, 21); + MatrixUtil::clearMatrix($matrix); + $this->methods['embedBasicPatterns']->invoke( + null, + Version::getVersionForNumber(1), + $matrix + ); + $expected = " 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1\n" + . " 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1\n" + . " 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1\n" + . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" + . " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 1 \n" + . " 0 \n" + . " 1 \n" + . " 0 \n" + . " 1 \n" + . " 0 0 0 0 0 0 0 0 1 \n" + . " 1 1 1 1 1 1 1 0 \n" + . " 1 0 0 0 0 0 1 0 \n" + . " 1 0 1 1 1 0 1 0 \n" + . " 1 0 1 1 1 0 1 0 \n" + . " 1 0 1 1 1 0 1 0 \n" + . " 1 0 0 0 0 0 1 0 \n" + . " 1 1 1 1 1 1 1 0 \n"; + + $this->assertSame($expected, (string) $matrix); + } + + public function testEmbedBasicPatterns2() : void + { + $matrix = new ByteMatrix(25, 25); + MatrixUtil::clearMatrix($matrix); + $this->methods['embedBasicPatterns']->invoke( + null, + Version::getVersionForNumber(2), + $matrix + ); + $expected = " 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1\n" + . " 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1\n" + . " 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1\n" + . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" + . " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 1 \n" + . " 0 \n" + . " 1 \n" + . " 0 \n" + . " 1 \n" + . " 0 \n" + . " 1 \n" + . " 0 \n" + . " 1 1 1 1 1 1 \n" + . " 0 0 0 0 0 0 0 0 1 1 0 0 0 1 \n" + . " 1 1 1 1 1 1 1 0 1 0 1 0 1 \n" + . " 1 0 0 0 0 0 1 0 1 0 0 0 1 \n" + . " 1 0 1 1 1 0 1 0 1 1 1 1 1 \n" + . " 1 0 1 1 1 0 1 0 \n" + . " 1 0 1 1 1 0 1 0 \n" + . " 1 0 0 0 0 0 1 0 \n" + . " 1 1 1 1 1 1 1 0 \n"; + + $this->assertSame($expected, (string) $matrix); + } + + public function testEmbedTypeInfo() : void + { + $matrix = new ByteMatrix(21, 21); + MatrixUtil::clearMatrix($matrix); + $this->methods['embedTypeInfo']->invoke( + null, + ErrorCorrectionLevel::M(), + 5, + $matrix + ); + $expected = " 0 \n" + . " 1 \n" + . " 1 \n" + . " 1 \n" + . " 0 \n" + . " 0 \n" + . " \n" + . " 1 \n" + . " 1 0 0 0 0 0 0 1 1 1 0 0 1 1 1 0\n" + . " \n" + . " \n" + . " \n" + . " \n" + . " \n" + . " 0 \n" + . " 0 \n" + . " 0 \n" + . " 0 \n" + . " 0 \n" + . " 0 \n" + . " 1 \n"; + + $this->assertSame($expected, (string) $matrix); + } + + public function testEmbedVersionInfo() : void + { + $matrix = new ByteMatrix(21, 21); + MatrixUtil::clearMatrix($matrix); + $this->methods['maybeEmbedVersionInfo']->invoke( + null, + Version::getVersionForNumber(7), + $matrix + ); + $expected = " 0 0 1 \n" + . " 0 1 0 \n" + . " 0 1 0 \n" + . " 0 1 1 \n" + . " 1 1 1 \n" + . " 0 0 0 \n" + . " \n" + . " \n" + . " \n" + . " \n" + . " 0 0 0 0 1 0 \n" + . " 0 1 1 1 1 0 \n" + . " 1 0 0 1 1 0 \n" + . " \n" + . " \n" + . " \n" + . " \n" + . " \n" + . " \n" + . " \n" + . " \n"; + + $this->assertSame($expected, (string) $matrix); + } + + public function testEmbedDataBits() : void + { + $matrix = new ByteMatrix(21, 21); + MatrixUtil::clearMatrix($matrix); + $this->methods['embedBasicPatterns']->invoke( + null, + Version::getVersionForNumber(1), + $matrix + ); + + $bits = new BitArray(); + $this->methods['embedDataBits']->invoke( + null, + $bits, + -1, + $matrix + ); + + $expected = " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1\n" + . " 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1\n" + . " 1 0 1 1 1 0 1 0 0 0 0 0 0 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 0 0 0 0 0 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 0 0 0 0 0 0 1 0 1 1 1 0 1\n" + . " 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1\n" + . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" + . " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 1 0 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 1 0 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 1 0 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" + . " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n"; + + $this->assertSame($expected, (string) $matrix); + } + + public function testBuildMatrix() : void + { + $bytes = [ + 32, 65, 205, 69, 41, 220, 46, 128, 236, 42, 159, 74, 221, 244, 169, + 239, 150, 138, 70, 237, 85, 224, 96, 74, 219 , 61 + ]; + $bits = new BitArray(); + + foreach ($bytes as $byte) { + $bits->appendBits($byte, 8); + } + + $matrix = new ByteMatrix(21, 21); + MatrixUtil::buildMatrix( + $bits, + ErrorCorrectionLevel::H(), + Version::getVersionForNumber(1), + 3, + $matrix + ); + + $expected = " 1 1 1 1 1 1 1 0 0 1 1 0 0 0 1 1 1 1 1 1 1\n" + . " 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1\n" + . " 1 0 1 1 1 0 1 0 0 0 0 1 0 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 0 1 1 0 0 0 1 0 1 1 1 0 1\n" + . " 1 0 1 1 1 0 1 0 1 1 0 0 1 0 1 0 1 1 1 0 1\n" + . " 1 0 0 0 0 0 1 0 0 0 1 1 1 0 1 0 0 0 0 0 1\n" + . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" + . " 0 0 0 0 0 0 0 0 1 1 0 1 1 0 0 0 0 0 0 0 0\n" + . " 0 0 1 1 0 0 1 1 1 0 0 1 1 1 1 0 1 0 0 0 0\n" + . " 1 0 1 0 1 0 0 0 0 0 1 1 1 0 0 1 0 1 1 1 0\n" + . " 1 1 1 1 0 1 1 0 1 0 1 1 1 0 0 1 1 1 0 1 0\n" + . " 1 0 1 0 1 1 0 1 1 1 0 0 1 1 1 0 0 1 0 1 0\n" + . " 0 0 1 0 0 1 1 1 0 0 0 0 0 0 1 0 1 1 1 1 1\n" + . " 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 0 0 1 0 1 1\n" + . " 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 0 1 0 1 1 0\n" + . " 1 0 0 0 0 0 1 0 0 0 0 1 0 1 1 1 0 0 0 0 0\n" + . " 1 0 1 1 1 0 1 0 0 1 0 0 1 1 0 0 1 0 0 1 1\n" + . " 1 0 1 1 1 0 1 0 1 1 0 1 0 0 0 0 0 1 1 1 0\n" + . " 1 0 1 1 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 0 0\n" + . " 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 0\n" + . " 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 0 1 0 0 1 0\n"; + + $this->assertSame($expected, (string) $matrix); + } + + public function testFindMsbSet() : void + { + $this->assertSame(0, $this->methods['findMsbSet']->invoke(null, 0)); + $this->assertSame(1, $this->methods['findMsbSet']->invoke(null, 1)); + $this->assertSame(8, $this->methods['findMsbSet']->invoke(null, 0x80)); + $this->assertSame(32, $this->methods['findMsbSet']->invoke(null, 0x80000000)); + } + + public function testCalculateBchCode() : void + { + // Encoding of type information. + // From Appendix C in JISX0510:2004 (p 65) + $this->assertSame(0xdc, $this->methods['calculateBchCode']->invoke(null, 5, 0x537)); + // From http://www.swetake.com/qr/qr6.html + $this->assertSame(0x1c2, $this->methods['calculateBchCode']->invoke(null, 0x13, 0x537)); + // From http://www.swetake.com/qr/qr11.html + $this->assertSame(0x214, $this->methods['calculateBchCode']->invoke(null, 0x1b, 0x537)); + + // Encoding of version information. + // From Appendix D in JISX0510:2004 (p 68) + $this->assertSame(0xc94, $this->methods['calculateBchCode']->invoke(null, 7, 0x1f25)); + $this->assertSame(0x5bc, $this->methods['calculateBchCode']->invoke(null, 8, 0x1f25)); + $this->assertSame(0xa99, $this->methods['calculateBchCode']->invoke(null, 9, 0x1f25)); + $this->assertSame(0x4d3, $this->methods['calculateBchCode']->invoke(null, 10, 0x1f25)); + $this->assertSame(0x9a6, $this->methods['calculateBchCode']->invoke(null, 20, 0x1f25)); + $this->assertSame(0xd75, $this->methods['calculateBchCode']->invoke(null, 30, 0x1f25)); + $this->assertSame(0xc69, $this->methods['calculateBchCode']->invoke(null, 40, 0x1f25)); + } + + public function testMakeVersionInfoBits() : void + { + // From Appendix D in JISX0510:2004 (p 68) + $bits = new BitArray(); + $this->methods['makeVersionInfoBits']->invoke(null, Version::getVersionForNumber(7), $bits); + $this->assertSame(' ...XXXXX ..X..X.X ..', (string) $bits); + } + + public function testMakeTypeInfoBits() : void + { + // From Appendix D in JISX0510:2004 (p 68) + $bits = new BitArray(); + $this->methods['makeTypeInfoBits']->invoke(null, ErrorCorrectionLevel::M(), 5, $bits); + $this->assertSame(' X......X X..XXX.', (string) $bits); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Integration/ImagickRenderingTest.php b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Integration/ImagickRenderingTest.php new file mode 100644 index 0000000000..3df8687eca --- /dev/null +++ b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Integration/ImagickRenderingTest.php @@ -0,0 +1,72 @@ +writeFile('Hello World!', $tempName); + + $this->assertMatchesFileSnapshot($tempName); + unlink($tempName); + } + + /** + * @requires extension imagick + */ + public function testIssue79() : void + { + $eye = SquareEye::instance(); + $squareModule = SquareModule::instance(); + + $eyeFill = new EyeFill(new Rgb(100, 100, 55), new Rgb(100, 100, 255)); + $gradient = new Gradient(new Rgb(100, 100, 55), new Rgb(100, 100, 255), GradientType::HORIZONTAL()); + + $renderer = new ImageRenderer( + new RendererStyle( + 400, + 2, + $squareModule, + $eye, + Fill::withForegroundGradient(new Rgb(255, 255, 255), $gradient, $eyeFill, $eyeFill, $eyeFill) + ), + new ImagickImageBackEnd() + ); + $writer = new Writer($renderer); + $tempName = tempnam(sys_get_temp_dir(), 'test') . '.png'; + $writer->writeFile('https://apiroad.net/very-long-url', $tempName); + + $this->assertMatchesFileSnapshot($tempName); + unlink($tempName); + } +} diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Integration/__snapshots__/files/ImagickRenderingTest__testGenericQrCode__1.png b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Integration/__snapshots__/files/ImagickRenderingTest__testGenericQrCode__1.png new file mode 100644 index 0000000000..9a429edc8c Binary files /dev/null and b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Integration/__snapshots__/files/ImagickRenderingTest__testGenericQrCode__1.png differ diff --git a/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Integration/__snapshots__/files/ImagickRenderingTest__testIssue79__1.png b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Integration/__snapshots__/files/ImagickRenderingTest__testIssue79__1.png new file mode 100644 index 0000000000..47e3b1a290 Binary files /dev/null and b/data/web/inc/lib/vendor/bacon/bacon-qr-code/test/Integration/__snapshots__/files/ImagickRenderingTest__testIssue79__1.png differ diff --git a/data/web/inc/lib/vendor/bin/carbon b/data/web/inc/lib/vendor/bin/carbon new file mode 100755 index 0000000000..86fbfdfd88 --- /dev/null +++ b/data/web/inc/lib/vendor/bin/carbon @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/nesbot/carbon/bin/carbon'); + } +} + +return include __DIR__ . '/..'.'/nesbot/carbon/bin/carbon'; diff --git a/data/web/inc/lib/vendor/bin/minifycss b/data/web/inc/lib/vendor/bin/minifycss new file mode 100755 index 0000000000..0604366b17 --- /dev/null +++ b/data/web/inc/lib/vendor/bin/minifycss @@ -0,0 +1,97 @@ +#!/usr/bin/env php +handle = fopen($opened_path, $mode); + $this->position = 0; + + // remove all traces of this stream wrapper once it has been used + stream_wrapper_unregister('composer-bin-proxy'); + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return fstat($this->handle); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + } + } + + if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { + include("composer-bin-proxy://" . __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifycss'); + exit(0); + } +} + +include __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifycss'; diff --git a/data/web/inc/lib/vendor/bin/minifyjs b/data/web/inc/lib/vendor/bin/minifyjs new file mode 100755 index 0000000000..f8ee9123a6 --- /dev/null +++ b/data/web/inc/lib/vendor/bin/minifyjs @@ -0,0 +1,97 @@ +#!/usr/bin/env php +handle = fopen($opened_path, $mode); + $this->position = 0; + + // remove all traces of this stream wrapper once it has been used + stream_wrapper_unregister('composer-bin-proxy'); + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return fstat($this->handle); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + } + } + + if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { + include("composer-bin-proxy://" . __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifyjs'); + exit(0); + } +} + +include __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifyjs'; diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/CHANGELOG.md b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/CHANGELOG.md new file mode 100644 index 0000000000..1b87f3da37 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/CHANGELOG.md @@ -0,0 +1,200 @@ +CHANGELOG for 1.x +================= + +This changelog references the relevant changes (bug and security fixes) done +in 1.x minor versions. + +To see the files changed for a given bug, go to https://github.com/bshaffer/oauth2-server-php/issues/### where ### is the bug number +To get the diff between two versions, go to https://github.com/bshaffer/oauth2-server-php/compare/v1.0...v1.1 +To get the diff for a specific change, go to https://github.com/bshaffer/oauth2-server-php/commit/XXX where XXX is the change hash + +* 1.10.0 (2017-11-15) + + PR: https://github.com/bshaffer/oauth2-server-php/pull/889 + + * #795 - [feature] added protected createPayload method to allow easier customization of JWT payload + * #807 - [refactor] simplifies UserInfoController constructor + * #814 - [docs] Adds https to README link + * #827 - [testing] Explicitly pulls in phpunit 4 + * #828 - [docs] PHPDoc improvements and type hinting of variables. + * #829 - [bug] Fix CORS issue for revoking and requesting an access token + * #869 - [testing] Remove php 5.3 from travis and use vendored phpunit + * #834 - [feature] use random_bytes if available + * #851 - [docs] Fix PHPDoc + * #872 - [bug] Fix count() error on PHP 7.2 + * #873 - [testing] adds php 7.2 to travis + * #794 - [docs] Fix typo in composer.json + * #885 - [testing] Use PHPUnit\Framework\TestCase instead of PHPUnit_Framework_TestCase + +* 1.9.0 (2017-01-06) + + PR: https://github.com/bshaffer/oauth2-server-php/pull/788 + + * bug #645 - Allow null for client_secret + * bug #651 - Fix bug in isPublicClient of Cassandra Storage + * bug #670 - Bug in client's scope restriction + * bug #672 - Implemented method to override the password hashing algorithm + * bug #698 - Fix Token Response's Content-Type to application/json + * bug #729 - Ensures unsetAccessToken and unsetRefreshToken return a bool + * bug #749 - Fix UserClaims for CodeIdToken + * bug #784 - RFC6750 compatibility + * bug #776 - Fix "redirect_uri_mismatch" for URIs with encoded characters + * bug #759 - no access token supplied to resource controller results in empty request body + * bug #773 - Use OpenSSL random method before attempting Mcrypt's. + * bug #790 - Add mongo db + +* 1.8.0 (2015-09-18) + + PR: https://github.com/bshaffer/oauth2-server-php/pull/643 + + * bug #594 - adds jti + * bug #598 - fixes lifetime configurations for JWTs + * bug #634 - fixes travis builds, upgrade to containers + * bug #586 - support for revoking tokens + * bug #636 - Adds FirebaseJWT bridge + * bug #639 - Mongo HHVM compatibility + +* 1.7.0 (2015-04-23) + + PR: https://github.com/bshaffer/oauth2-server-php/pull/572 + + * bug #500 - PDO fetch mode changed from FETCH_BOTH to FETCH_ASSOC + * bug #508 - Case insensitive for Bearer token header name ba716d4 + * bug #512 - validateRedirectUri is now public + * bug #530 - Add PublicKeyInterface, UserClaimsInterface to Cassandra Storage + * bug #505 - DynamoDB storage fixes + * bug #556 - adds "code id_token" return type to openid connect + * bug #563 - Include "issuer" config key for JwtAccessToken + * bug #564 - Fixes JWT vulnerability + * bug #571 - Added unset_refresh_token_after_use option + +* 1.6 (2015-01-16) + + PR: https://github.com/bshaffer/oauth2-server-php/pull/496 + + * bug 437 - renames CryptoToken to JwtAccessToken / use_crypto_tokens to use_jwt_access_tokens + * bug 447 - Adds a Couchbase storage implementation + * bug 460 - Rename JWT claims to match spec + * bug 470 - order does not matter for multi-valued response types + * bug 471 - Make validateAuthorizeRequest available for POST in addition to GET + * bug 475 - Adds JTI table definitiion + * bug 481 - better randomness for generating access tokens + * bug 480 - Use hash_equals() for signature verification (prevents remote timing attacks) + * bugs 489, 491, 498 - misc other fixes + +* 1.5 (2014-08-27) + + PR: https://github.com/bshaffer/oauth2-server-php/pull/446 + + * bug #399 - Add DynamoDB Support + * bug #404 - renamed error name for malformed/expired tokens + * bug #412 - Openid connect: fixes for claims with more than one scope / Add support for the prompt parameter ('consent' and 'none') + * bug #411 - fixes xml output + * bug #413 - fixes invalid format error + * bug #401 - fixes code standards / whitespace + * bug #354 - bundles PDO SQL with the library + * [BC] bug #397 - refresh tokens should not be encrypted + * bug #423 - makes "scope" optional for refresh token storage + +* 1.4 (2014-06-12) + + PR: https://github.com/bshaffer/oauth2-server-php/pull/392 + + * bug #189 Storage\PDO - allows DSN string in constructor + * bug #233 Bearer Tokens - allows token in request body for PUT requests + * bug #346 Fixes open_basedir warning + * bug #351 Adds OpenID Connect support + * bug #355 Adds php 5.6 and HHVM to travis.ci testing + * [BC] bug #358 Adds `getQueryStringIdentifier()` to the GrantType interface + * bug #363 Encryption\JWT - Allows for subclassing JWT Headers + * bug #349 Bearer Tokens - adds requestHasToken method for when access tokens are optional + * bug #301 Encryption\JWT - fixes urlSafeB64Encode(): ensures newlines are replaced as expected + * bug #323 ResourceController - client_id is no longer required to be returned when calling getAccessToken + * bug #367 Storage\PDO - adds Postgres support + * bug #368 Access Tokens - use mcrypt_create_iv or openssl_random_pseudo_bytes to create token string + * bug #376 Request - allows case insensitive headers + * bug #384 Storage\PDO - can pass in PDO options in constructor of PDO storage + * misc fixes #361, #292, #373, #374, #379, #396 +* 1.3 (2014-02-27) + + PR: https://github.com/bshaffer/oauth2-server-php/pull/325 + + * bug #311 adds cassandra storage + * bug #298 fixes response code for user credentials grant type + * bug #318 adds 'use_crypto_tokens' config to Server class for better DX + * [BC] bug #320 pass client_id to getDefaultScope + * bug #324 better feedback when running tests + * bug #335 adds support for non-expiring refresh tokens + * bug #333 fixes Pdo storage for getClientKey + * bug #336 fixes Redis storage for expireAuthorizationCode + +* 1.3 (2014-02-27) + + PR: https://github.com/bshaffer/oauth2-server-php/pull/325 + + * bug #311 adds cassandra storage + * bug #298 fixes response code for user credentials grant type + * bug #318 adds 'use_crypto_tokens' config to Server class for better DX + * bug #320 pass client_id to getDefaultScope + * bug #324 better feedback when running tests + * bug #335 adds support for non-expiring refresh tokens + * bug #333 fixes Pdo storage for getClientKey + * bug #336 fixes Redis storage for expireAuthorizationCode + +* 1.2 (2014-01-03) + + PR: https://github.com/bshaffer/oauth2-server-php/pull/288 + + * bug #285 changed response header from 200 to 401 when empty token received + * bug #286 adds documentation and links to spec for not including error messages when no token is supplied + * bug #280 ensures PHP warnings do not get thrown as a result of an invalid argument to $jwt->decode() + * bug #279 predis wrong number of arguments + * bug #277 Securing JS WebApp client secret w/ password grant type + +* 1.1 (2013-12-17) + + PR: https://github.com/bshaffer/oauth2-server-php/pull/276 + + * bug #278 adds refresh token configuration to Server class + * bug #274 Supplying a null client_id and client_secret grants API access + * bug #244 [MongoStorage] More detailed implementation info + * bug #268 Implement jti for JWT Bearer tokens to prevent replay attacks. + * bug #266 Removing unused argument to getAccessTokenData + * bug #247 Make Bearer token type consistent + * bug #253 Fixing CryptoToken refresh token lifetime + * bug #246 refactors public key logic to be more intuitive + * bug #245 adds support for JSON crypto tokens + * bug #230 Remove unused columns in oauth_clients + * bug #215 makes Redis Scope Storage obey the same paradigm as PDO + * bug #228 removes scope group + * bug #227 squelches open basedir restriction error + * bug #223 Updated docblocks for RefreshTokenInterface.php + * bug #224 Adds protected properties + * bug #217 Implement ScopeInterface for PDO, Redis + +* 1.0 (2013-08-12) + + * bug #203 Add redirect\_status_code config param for AuthorizeController + * bug #205 ensures unnecessary ? is not set when ** bug + * bug #204 Fixed call to LogicException + * bug #202 Add explode to checkRestrictedGrant in PDO Storage + * bug #197 adds support for 'false' default scope ** bug + * bug #192 reference errors and adds tests + * bug #194 makes some appropriate properties ** bug + * bug #191 passes config to HttpBasic + * bug #190 validates client credentials before ** bug + * bug #171 Fix wrong redirect following authorization step + * bug #187 client_id is now passed to getDefaultScope(). + * bug #176 Require refresh_token in getRefreshToken response + * bug #174 make user\_id not required for refresh_token grant + * bug #173 Duplication in JwtBearer Grant + * bug #168 user\_id not required for authorization_code grant + * bug #133 hardens default security for user object + * bug #163 allows redirect\_uri on authorization_code to be NULL in docs example + * bug #162 adds getToken on ResourceController for convenience + * bug #161 fixes fatal error + * bug #163 Invalid redirect_uri handling + * bug #156 user\_id in OAuth2\_Storage_AuthorizationCodeInterface::getAuthorizationCode() response + * bug #157 Fix for extending access and refresh tokens + * bug #154 ResponseInterface: getParameter method is used in the library but not defined in the interface + * bug #148 Add more detail to examples in Readme.md diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/LICENSE b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/LICENSE new file mode 100644 index 0000000000..d7ece84679 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2014 Brent Shaffer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/README.md b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/README.md new file mode 100644 index 0000000000..117743d4fd --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/README.md @@ -0,0 +1,8 @@ +oauth2-server-php +================= + +[![Build Status](https://travis-ci.org/bshaffer/oauth2-server-php.svg?branch=master)](https://travis-ci.org/bshaffer/oauth2-server-php) + +[![Total Downloads](https://poser.pugx.org/bshaffer/oauth2-server-php/downloads.png)](https://packagist.org/packages/bshaffer/oauth2-server-php) + +View the [complete documentation](https://bshaffer.github.io/oauth2-server-php-docs/) diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/composer.json b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/composer.json new file mode 100644 index 0000000000..272d200279 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/composer.json @@ -0,0 +1,36 @@ +{ + "name": "bshaffer/oauth2-server-php", + "description":"OAuth2 Server for PHP", + "keywords":["oauth","oauth2","auth"], + "type":"library", + "license":"MIT", + "authors":[ + { + "name":"Brent Shaffer", + "email": "bshafs@gmail.com", + "homepage":"http://brentertainment.com" + } + ], + "homepage": "http://github.com/bshaffer/oauth2-server-php", + "autoload": { + "psr-0": { "OAuth2": "src/" } + }, + "require":{ + "php":">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "aws/aws-sdk-php": "~2.8", + "firebase/php-jwt": "~2.2", + "predis/predis": "dev-master", + "thobbs/phpcassa": "dev-master", + "mongodb/mongodb": "^1.1" + }, + "suggest": { + "predis/predis": "Required to use Redis storage", + "thobbs/phpcassa": "Required to use Cassandra storage", + "aws/aws-sdk-php": "~2.8 is required to use DynamoDB storage", + "firebase/php-jwt": "~2.2 is required to use JWT features", + "mongodb/mongodb": "^1.1 is required to use MongoDB storage" + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Autoloader.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Autoloader.php new file mode 100644 index 0000000000..4ec08cbdd3 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Autoloader.php @@ -0,0 +1,54 @@ + + * @license MIT License + */ +class Autoloader +{ + /** + * @var string + */ + private $dir; + + /** + * @param string $dir + */ + public function __construct($dir = null) + { + if (is_null($dir)) { + $dir = dirname(__FILE__).'/..'; + } + $this->dir = $dir; + } + + /** + * Registers OAuth2\Autoloader as an SPL autoloader. + */ + public static function register($dir = null) + { + ini_set('unserialize_callback_func', 'spl_autoload_call'); + spl_autoload_register(array(new self($dir), 'autoload')); + } + + /** + * Handles autoloading of classes. + * + * @param string $class - A class name. + * @return boolean - Returns true if the class has been loaded + */ + public function autoload($class) + { + if (0 !== strpos($class, 'OAuth2')) { + return; + } + + if (file_exists($file = $this->dir.'/'.str_replace('\\', '/', $class).'.php')) { + require $file; + } + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ClientAssertionType/ClientAssertionTypeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ClientAssertionType/ClientAssertionTypeInterface.php new file mode 100644 index 0000000000..6a167da904 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ClientAssertionType/ClientAssertionTypeInterface.php @@ -0,0 +1,28 @@ + + */ +class HttpBasic implements ClientAssertionTypeInterface +{ + private $clientData; + + protected $storage; + protected $config; + + /** + * Config array $config should look as follows: + * @code + * $config = array( + * 'allow_credentials_in_request_body' => true, // whether to look for credentials in the POST body in addition to the Authorize HTTP Header + * 'allow_public_clients' => true // if true, "public clients" (clients without a secret) may be authenticated + * ); + * @endcode + * + * @param ClientCredentialsInterface $storage Storage + * @param array $config Configuration options for the server + */ + public function __construct(ClientCredentialsInterface $storage, array $config = array()) + { + $this->storage = $storage; + $this->config = array_merge(array( + 'allow_credentials_in_request_body' => true, + 'allow_public_clients' => true, + ), $config); + } + + /** + * Validate the OAuth request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool|mixed + * @throws LogicException + */ + public function validateRequest(RequestInterface $request, ResponseInterface $response) + { + if (!$clientData = $this->getClientCredentials($request, $response)) { + return false; + } + + if (!isset($clientData['client_id'])) { + throw new LogicException('the clientData array must have "client_id" set'); + } + + if (!isset($clientData['client_secret']) || $clientData['client_secret'] == '') { + if (!$this->config['allow_public_clients']) { + $response->setError(400, 'invalid_client', 'client credentials are required'); + + return false; + } + + if (!$this->storage->isPublicClient($clientData['client_id'])) { + $response->setError(400, 'invalid_client', 'This client is invalid or must authenticate using a client secret'); + + return false; + } + } elseif ($this->storage->checkClientCredentials($clientData['client_id'], $clientData['client_secret']) === false) { + $response->setError(400, 'invalid_client', 'The client credentials are invalid'); + + return false; + } + + $this->clientData = $clientData; + + return true; + } + + /** + * Get the client id + * + * @return mixed + */ + public function getClientId() + { + return $this->clientData['client_id']; + } + + /** + * Internal function used to get the client credentials from HTTP basic + * auth or POST data. + * + * According to the spec (draft 20), the client_id can be provided in + * the Basic Authorization header (recommended) or via GET/POST. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array|null A list containing the client identifier and password, for example: + * @code + * return array( + * "client_id" => CLIENT_ID, // REQUIRED the client id + * "client_secret" => CLIENT_SECRET, // OPTIONAL the client secret (may be omitted for public clients) + * ); + * @endcode + * + * @see http://tools.ietf.org/html/rfc6749#section-2.3.1 + * + * @ingroup oauth2_section_2 + */ + public function getClientCredentials(RequestInterface $request, ResponseInterface $response = null) + { + if (!is_null($request->headers('PHP_AUTH_USER')) && !is_null($request->headers('PHP_AUTH_PW'))) { + return array('client_id' => $request->headers('PHP_AUTH_USER'), 'client_secret' => $request->headers('PHP_AUTH_PW')); + } + + if ($this->config['allow_credentials_in_request_body']) { + // Using POST for HttpBasic authorization is not recommended, but is supported by specification + if (!is_null($request->request('client_id'))) { + /** + * client_secret can be null if the client's password is an empty string + * @see http://tools.ietf.org/html/rfc6749#section-2.3.1 + */ + return array('client_id' => $request->request('client_id'), 'client_secret' => $request->request('client_secret')); + } + } + + if ($response) { + $message = $this->config['allow_credentials_in_request_body'] ? ' or body' : ''; + $response->setError(400, 'invalid_client', 'Client credentials were not found in the headers'.$message); + } + + return null; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeController.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeController.php new file mode 100644 index 0000000000..4bafb1d24d --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeController.php @@ -0,0 +1,480 @@ + false, // if the controller should allow the "implicit" grant type + * 'enforce_state' => true // if the controller should require the "state" parameter + * 'require_exact_redirect_uri' => true, // if the controller should require an exact match on the "redirect_uri" parameter + * 'redirect_status_code' => 302, // HTTP status code to use for redirect responses + * ); + * @endcode + */ + public function __construct(ClientInterface $clientStorage, array $responseTypes = array(), array $config = array(), ScopeInterface $scopeUtil = null) + { + $this->clientStorage = $clientStorage; + $this->responseTypes = $responseTypes; + $this->config = array_merge(array( + 'allow_implicit' => false, + 'enforce_state' => true, + 'require_exact_redirect_uri' => true, + 'redirect_status_code' => 302, + ), $config); + + if (is_null($scopeUtil)) { + $scopeUtil = new Scope(); + } + $this->scopeUtil = $scopeUtil; + } + + /** + * Handle the authorization request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param boolean $is_authorized + * @param mixed $user_id + * @return mixed|void + * @throws InvalidArgumentException + */ + public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null) + { + if (!is_bool($is_authorized)) { + throw new InvalidArgumentException('Argument "is_authorized" must be a boolean. This method must know if the user has granted access to the client.'); + } + + // We repeat this, because we need to re-validate. The request could be POSTed + // by a 3rd-party (because we are not internally enforcing NONCEs, etc) + if (!$this->validateAuthorizeRequest($request, $response)) { + return; + } + + // If no redirect_uri is passed in the request, use client's registered one + if (empty($this->redirect_uri)) { + $clientData = $this->clientStorage->getClientDetails($this->client_id); + $registered_redirect_uri = $clientData['redirect_uri']; + } + + // the user declined access to the client's application + if ($is_authorized === false) { + $redirect_uri = $this->redirect_uri ?: $registered_redirect_uri; + $this->setNotAuthorizedResponse($request, $response, $redirect_uri, $user_id); + + return; + } + + // build the parameters to set in the redirect URI + if (!$params = $this->buildAuthorizeParameters($request, $response, $user_id)) { + return; + } + + $authResult = $this->responseTypes[$this->response_type]->getAuthorizeResponse($params, $user_id); + + list($redirect_uri, $uri_params) = $authResult; + + if (empty($redirect_uri) && !empty($registered_redirect_uri)) { + $redirect_uri = $registered_redirect_uri; + } + + $uri = $this->buildUri($redirect_uri, $uri_params); + + // return redirect response + $response->setRedirect($this->config['redirect_status_code'], $uri); + } + + /** + * Set not authorized response + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param string $redirect_uri + * @param mixed $user_id + */ + protected function setNotAuthorizedResponse(RequestInterface $request, ResponseInterface $response, $redirect_uri, $user_id = null) + { + $error = 'access_denied'; + $error_message = 'The user denied access to your application'; + $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->state, $error, $error_message); + } + + /** + * We have made this protected so this class can be extended to add/modify + * these parameters + * + * @TODO: add dependency injection for the parameters in this method + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param mixed $user_id + * @return array + */ + protected function buildAuthorizeParameters($request, $response, $user_id) + { + // @TODO: we should be explicit with this in the future + $params = array( + 'scope' => $this->scope, + 'state' => $this->state, + 'client_id' => $this->client_id, + 'redirect_uri' => $this->redirect_uri, + 'response_type' => $this->response_type, + ); + + return $params; + } + + /** + * Validate the OAuth request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response) + { + // Make sure a valid client id was supplied (we can not redirect because we were unable to verify the URI) + if (!$client_id = $request->query('client_id', $request->request('client_id'))) { + // We don't have a good URI to use + $response->setError(400, 'invalid_client', "No client id supplied"); + + return false; + } + + // Get client details + if (!$clientData = $this->clientStorage->getClientDetails($client_id)) { + $response->setError(400, 'invalid_client', 'The client id supplied is invalid'); + + return false; + } + + $registered_redirect_uri = isset($clientData['redirect_uri']) ? $clientData['redirect_uri'] : ''; + + // Make sure a valid redirect_uri was supplied. If specified, it must match the clientData URI. + // @see http://tools.ietf.org/html/rfc6749#section-3.1.2 + // @see http://tools.ietf.org/html/rfc6749#section-4.1.2.1 + // @see http://tools.ietf.org/html/rfc6749#section-4.2.2.1 + if ($supplied_redirect_uri = $request->query('redirect_uri', $request->request('redirect_uri'))) { + // validate there is no fragment supplied + $parts = parse_url($supplied_redirect_uri); + if (isset($parts['fragment']) && $parts['fragment']) { + $response->setError(400, 'invalid_uri', 'The redirect URI must not contain a fragment'); + + return false; + } + + // validate against the registered redirect uri(s) if available + if ($registered_redirect_uri && !$this->validateRedirectUri($supplied_redirect_uri, $registered_redirect_uri)) { + $response->setError(400, 'redirect_uri_mismatch', 'The redirect URI provided is missing or does not match', '#section-3.1.2'); + + return false; + } + $redirect_uri = $supplied_redirect_uri; + } else { + // use the registered redirect_uri if none has been supplied, if possible + if (!$registered_redirect_uri) { + $response->setError(400, 'invalid_uri', 'No redirect URI was supplied or stored'); + + return false; + } + + if (count(explode(' ', $registered_redirect_uri)) > 1) { + $response->setError(400, 'invalid_uri', 'A redirect URI must be supplied when multiple redirect URIs are registered', '#section-3.1.2.3'); + + return false; + } + $redirect_uri = $registered_redirect_uri; + } + + // Select the response type + $response_type = $request->query('response_type', $request->request('response_type')); + + // for multiple-valued response types - make them alphabetical + if (false !== strpos($response_type, ' ')) { + $types = explode(' ', $response_type); + sort($types); + $response_type = ltrim(implode(' ', $types)); + } + + $state = $request->query('state', $request->request('state')); + + // type and client_id are required + if (!$response_type || !in_array($response_type, $this->getValidResponseTypes())) { + $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_request', 'Invalid or missing response type', null); + + return false; + } + + if ($response_type == self::RESPONSE_TYPE_AUTHORIZATION_CODE) { + if (!isset($this->responseTypes['code'])) { + $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unsupported_response_type', 'authorization code grant type not supported', null); + + return false; + } + if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'authorization_code')) { + $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unauthorized_client', 'The grant type is unauthorized for this client_id', null); + + return false; + } + if ($this->responseTypes['code']->enforceRedirect() && !$redirect_uri) { + $response->setError(400, 'redirect_uri_mismatch', 'The redirect URI is mandatory and was not supplied'); + + return false; + } + } else { + if (!$this->config['allow_implicit']) { + $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unsupported_response_type', 'implicit grant type not supported', null); + + return false; + } + if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'implicit')) { + $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unauthorized_client', 'The grant type is unauthorized for this client_id', null); + + return false; + } + } + + // validate requested scope if it exists + $requestedScope = $this->scopeUtil->getScopeFromRequest($request); + + if ($requestedScope) { + // restrict scope by client specific scope if applicable, + // otherwise verify the scope exists + $clientScope = $this->clientStorage->getClientScope($client_id); + if ((empty($clientScope) && !$this->scopeUtil->scopeExists($requestedScope)) + || (!empty($clientScope) && !$this->scopeUtil->checkScope($requestedScope, $clientScope))) { + $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_scope', 'An unsupported scope was requested', null); + + return false; + } + } else { + // use a globally-defined default scope + $defaultScope = $this->scopeUtil->getDefaultScope($client_id); + + if (false === $defaultScope) { + $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_client', 'This application requires you specify a scope parameter', null); + + return false; + } + + $requestedScope = $defaultScope; + } + + // Validate state parameter exists (if configured to enforce this) + if ($this->config['enforce_state'] && !$state) { + $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, null, 'invalid_request', 'The state parameter is required'); + + return false; + } + + // save the input data and return true + $this->scope = $requestedScope; + $this->state = $state; + $this->client_id = $client_id; + // Only save the SUPPLIED redirect URI (@see http://tools.ietf.org/html/rfc6749#section-4.1.3) + $this->redirect_uri = $supplied_redirect_uri; + $this->response_type = $response_type; + + return true; + } + + /** + * Build the absolute URI based on supplied URI and parameters. + * + * @param string $uri An absolute URI. + * @param array $params Parameters to be append as GET. + * + * @return string + * An absolute URI with supplied parameters. + * + * @ingroup oauth2_section_4 + */ + private function buildUri($uri, $params) + { + $parse_url = parse_url($uri); + + // Add our params to the parsed uri + foreach ($params as $k => $v) { + if (isset($parse_url[$k])) { + $parse_url[$k] .= "&" . http_build_query($v, '', '&'); + } else { + $parse_url[$k] = http_build_query($v, '', '&'); + } + } + + // Put the uri back together + return + ((isset($parse_url["scheme"])) ? $parse_url["scheme"] . "://" : "") + . ((isset($parse_url["user"])) ? $parse_url["user"] + . ((isset($parse_url["pass"])) ? ":" . $parse_url["pass"] : "") . "@" : "") + . ((isset($parse_url["host"])) ? $parse_url["host"] : "") + . ((isset($parse_url["port"])) ? ":" . $parse_url["port"] : "") + . ((isset($parse_url["path"])) ? $parse_url["path"] : "") + . ((isset($parse_url["query"]) && !empty($parse_url['query'])) ? "?" . $parse_url["query"] : "") + . ((isset($parse_url["fragment"])) ? "#" . $parse_url["fragment"] : "") + ; + } + + protected function getValidResponseTypes() + { + return array( + self::RESPONSE_TYPE_ACCESS_TOKEN, + self::RESPONSE_TYPE_AUTHORIZATION_CODE, + ); + } + + /** + * Internal method for validating redirect URI supplied + * + * @param string $inputUri The submitted URI to be validated + * @param string $registeredUriString The allowed URI(s) to validate against. Can be a space-delimited string of URIs to + * allow for multiple URIs + * @return bool + * @see http://tools.ietf.org/html/rfc6749#section-3.1.2 + */ + protected function validateRedirectUri($inputUri, $registeredUriString) + { + if (!$inputUri || !$registeredUriString) { + return false; // if either one is missing, assume INVALID + } + + $registered_uris = preg_split('/\s+/', $registeredUriString); + foreach ($registered_uris as $registered_uri) { + if ($this->config['require_exact_redirect_uri']) { + // the input uri is validated against the registered uri using exact match + if (strcmp($inputUri, $registered_uri) === 0) { + return true; + } + } else { + $registered_uri_length = strlen($registered_uri); + if ($registered_uri_length === 0) { + return false; + } + + // the input uri is validated against the registered uri using case-insensitive match of the initial string + // i.e. additional query parameters may be applied + if (strcasecmp(substr($inputUri, 0, $registered_uri_length), $registered_uri) === 0) { + return true; + } + } + } + + return false; + } + + /** + * Convenience method to access the scope + * + * @return string + */ + public function getScope() + { + return $this->scope; + } + + /** + * Convenience method to access the state + * + * @return int + */ + public function getState() + { + return $this->state; + } + + /** + * Convenience method to access the client id + * + * @return mixed + */ + public function getClientId() + { + return $this->client_id; + } + + /** + * Convenience method to access the redirect url + * + * @return string + */ + public function getRedirectUri() + { + return $this->redirect_uri; + } + + /** + * Convenience method to access the response type + * + * @return string + */ + public function getResponseType() + { + return $this->response_type; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeControllerInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeControllerInterface.php new file mode 100644 index 0000000000..f758f976ac --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeControllerInterface.php @@ -0,0 +1,58 @@ +somehowDetermineUserId(); + * $is_authorized = $this->somehowDetermineUserAuthorization(); + * $response = new OAuth2\Response(); + * $authorizeController->handleAuthorizeRequest( + * OAuth2\Request::createFromGlobals(), + * $response, + * $is_authorized, + * $user_id + * ); + * $response->send(); + * @endcode + */ +interface AuthorizeControllerInterface +{ + /** + * List of possible authentication response types. + * The "authorization_code" mechanism exclusively supports 'code' + * and the "implicit" mechanism exclusively supports 'token'. + * + * @var string + * @see http://tools.ietf.org/html/rfc6749#section-4.1.1 + * @see http://tools.ietf.org/html/rfc6749#section-4.2.1 + */ + const RESPONSE_TYPE_AUTHORIZATION_CODE = 'code'; + const RESPONSE_TYPE_ACCESS_TOKEN = 'token'; + + /** + * Handle the OAuth request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param $is_authorized + * @param null $user_id + * @return mixed + */ + public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null); + + /** + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceController.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceController.php new file mode 100644 index 0000000000..926f90fda7 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceController.php @@ -0,0 +1,156 @@ +tokenType = $tokenType; + $this->tokenStorage = $tokenStorage; + + $this->config = array_merge(array( + 'www_realm' => 'Service', + ), $config); + + if (is_null($scopeUtil)) { + $scopeUtil = new Scope(); + } + $this->scopeUtil = $scopeUtil; + } + + /** + * Verify the resource request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param null $scope + * @return bool + */ + public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null) + { + $token = $this->getAccessTokenData($request, $response); + + // Check if we have token data + if (is_null($token)) { + return false; + } + + /** + * Check scope, if provided + * If token doesn't have a scope, it's null/empty, or it's insufficient, then throw 403 + * @see http://tools.ietf.org/html/rfc6750#section-3.1 + */ + if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->scopeUtil->checkScope($scope, $token["scope"]))) { + $response->setError(403, 'insufficient_scope', 'The request requires higher privileges than provided by the access token'); + $response->addHttpHeaders(array( + 'WWW-Authenticate' => sprintf('%s realm="%s", scope="%s", error="%s", error_description="%s"', + $this->tokenType->getTokenType(), + $this->config['www_realm'], + $scope, + $response->getParameter('error'), + $response->getParameter('error_description') + ) + )); + + return false; + } + + // allow retrieval of the token + $this->token = $token; + + return (bool) $token; + } + + /** + * Get access token data. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array|null + */ + public function getAccessTokenData(RequestInterface $request, ResponseInterface $response) + { + // Get the token parameter + if ($token_param = $this->tokenType->getAccessTokenParameter($request, $response)) { + // Get the stored token data (from the implementing subclass) + // Check we have a well formed token + // Check token expiration (expires is a mandatory paramter) + if (!$token = $this->tokenStorage->getAccessToken($token_param)) { + $response->setError(401, 'invalid_token', 'The access token provided is invalid'); + } elseif (!isset($token["expires"]) || !isset($token["client_id"])) { + $response->setError(401, 'malformed_token', 'Malformed token (missing "expires")'); + } elseif (time() > $token["expires"]) { + $response->setError(401, 'invalid_token', 'The access token provided has expired'); + } else { + return $token; + } + } + + $authHeader = sprintf('%s realm="%s"', $this->tokenType->getTokenType(), $this->config['www_realm']); + + if ($error = $response->getParameter('error')) { + $authHeader = sprintf('%s, error="%s"', $authHeader, $error); + if ($error_description = $response->getParameter('error_description')) { + $authHeader = sprintf('%s, error_description="%s"', $authHeader, $error_description); + } + } + + $response->addHttpHeaders(array('WWW-Authenticate' => $authHeader)); + + return null; + } + + /** + * convenience method to allow retrieval of the token. + * + * @return array + */ + public function getToken() + { + return $this->token; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceControllerInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceControllerInterface.php new file mode 100644 index 0000000000..9d005143d9 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceControllerInterface.php @@ -0,0 +1,41 @@ +verifyResourceRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response())) { + * $response->send(); // authorization failed + * exit(); + * } + * return json_encode($resource); // valid token! Send the stuff! + * @endcode + */ +interface ResourceControllerInterface +{ + /** + * Verify the resource request + * + * @param RequestInterface $request - Request object + * @param ResponseInterface $response - Response object + * @param string $scope + * @return mixed + */ + public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null); + + /** + * Get access token data. + * + * @param RequestInterface $request - Request object + * @param ResponseInterface $response - Response object + * @return mixed + */ + public function getAccessTokenData(RequestInterface $request, ResponseInterface $response); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenController.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenController.php new file mode 100644 index 0000000000..7fdaf85a68 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenController.php @@ -0,0 +1,333 @@ + + */ + protected $grantTypes; + + /** + * @var ClientAssertionTypeInterface + */ + protected $clientAssertionType; + + /** + * @var ScopeInterface + */ + protected $scopeUtil; + + /** + * @var ClientInterface + */ + protected $clientStorage; + + /** + * Constructor + * + * @param AccessTokenInterface $accessToken + * @param ClientInterface $clientStorage + * @param array $grantTypes + * @param ClientAssertionTypeInterface $clientAssertionType + * @param ScopeInterface $scopeUtil + * @throws InvalidArgumentException + */ + public function __construct(AccessTokenInterface $accessToken, ClientInterface $clientStorage, array $grantTypes = array(), ClientAssertionTypeInterface $clientAssertionType = null, ScopeInterface $scopeUtil = null) + { + if (is_null($clientAssertionType)) { + foreach ($grantTypes as $grantType) { + if (!$grantType instanceof ClientAssertionTypeInterface) { + throw new InvalidArgumentException('You must supply an instance of OAuth2\ClientAssertionType\ClientAssertionTypeInterface or only use grant types which implement OAuth2\ClientAssertionType\ClientAssertionTypeInterface'); + } + } + } + $this->clientAssertionType = $clientAssertionType; + $this->accessToken = $accessToken; + $this->clientStorage = $clientStorage; + foreach ($grantTypes as $grantType) { + $this->addGrantType($grantType); + } + + if (is_null($scopeUtil)) { + $scopeUtil = new Scope(); + } + $this->scopeUtil = $scopeUtil; + } + + /** + * Handle the token request. + * + * @param RequestInterface $request - Request object to grant access token + * @param ResponseInterface $response - Response object + */ + public function handleTokenRequest(RequestInterface $request, ResponseInterface $response) + { + if ($token = $this->grantAccessToken($request, $response)) { + // @see http://tools.ietf.org/html/rfc6749#section-5.1 + // server MUST disable caching in headers when tokens are involved + $response->setStatusCode(200); + $response->addParameters($token); + $response->addHttpHeaders(array( + 'Cache-Control' => 'no-store', + 'Pragma' => 'no-cache', + 'Content-Type' => 'application/json' + )); + } + } + + /** + * Grant or deny a requested access token. + * This would be called from the "/token" endpoint as defined in the spec. + * You can call your endpoint whatever you want. + * + * @param RequestInterface $request - Request object to grant access token + * @param ResponseInterface $response - Response object + * + * @return bool|null|array + * + * @throws \InvalidArgumentException + * @throws \LogicException + * + * @see http://tools.ietf.org/html/rfc6749#section-4 + * @see http://tools.ietf.org/html/rfc6749#section-10.6 + * @see http://tools.ietf.org/html/rfc6749#section-4.1.3 + * + * @ingroup oauth2_section_4 + */ + public function grantAccessToken(RequestInterface $request, ResponseInterface $response) + { + if (strtolower($request->server('REQUEST_METHOD')) === 'options') { + $response->addHttpHeaders(array('Allow' => 'POST, OPTIONS')); + + return null; + } + + if (strtolower($request->server('REQUEST_METHOD')) !== 'post') { + $response->setError(405, 'invalid_request', 'The request method must be POST when requesting an access token', '#section-3.2'); + $response->addHttpHeaders(array('Allow' => 'POST, OPTIONS')); + + return null; + } + + /** + * Determine grant type from request + * and validate the request for that grant type + */ + if (!$grantTypeIdentifier = $request->request('grant_type')) { + $response->setError(400, 'invalid_request', 'The grant type was not specified in the request'); + + return null; + } + + if (!isset($this->grantTypes[$grantTypeIdentifier])) { + /* TODO: If this is an OAuth2 supported grant type that we have chosen not to implement, throw a 501 Not Implemented instead */ + $response->setError(400, 'unsupported_grant_type', sprintf('Grant type "%s" not supported', $grantTypeIdentifier)); + + return null; + } + + /** @var GrantTypeInterface $grantType */ + $grantType = $this->grantTypes[$grantTypeIdentifier]; + + /** + * Retrieve the client information from the request + * ClientAssertionTypes allow for grant types which also assert the client data + * in which case ClientAssertion is handled in the validateRequest method + * + * @see \OAuth2\GrantType\JWTBearer + * @see \OAuth2\GrantType\ClientCredentials + */ + if (!$grantType instanceof ClientAssertionTypeInterface) { + if (!$this->clientAssertionType->validateRequest($request, $response)) { + return null; + } + $clientId = $this->clientAssertionType->getClientId(); + } + + /** + * Retrieve the grant type information from the request + * The GrantTypeInterface object handles all validation + * If the object is an instance of ClientAssertionTypeInterface, + * That logic is handled here as well + */ + if (!$grantType->validateRequest($request, $response)) { + return null; + } + + if ($grantType instanceof ClientAssertionTypeInterface) { + $clientId = $grantType->getClientId(); + } else { + // validate the Client ID (if applicable) + if (!is_null($storedClientId = $grantType->getClientId()) && $storedClientId != $clientId) { + $response->setError(400, 'invalid_grant', sprintf('%s doesn\'t exist or is invalid for the client', $grantTypeIdentifier)); + + return null; + } + } + + /** + * Validate the client can use the requested grant type + */ + if (!$this->clientStorage->checkRestrictedGrantType($clientId, $grantTypeIdentifier)) { + $response->setError(400, 'unauthorized_client', 'The grant type is unauthorized for this client_id'); + + return false; + } + + /** + * Validate the scope of the token + * + * requestedScope - the scope specified in the token request + * availableScope - the scope associated with the grant type + * ex: in the case of the "Authorization Code" grant type, + * the scope is specified in the authorize request + * + * @see http://tools.ietf.org/html/rfc6749#section-3.3 + */ + $requestedScope = $this->scopeUtil->getScopeFromRequest($request); + $availableScope = $grantType->getScope(); + + if ($requestedScope) { + // validate the requested scope + if ($availableScope) { + if (!$this->scopeUtil->checkScope($requestedScope, $availableScope)) { + $response->setError(400, 'invalid_scope', 'The scope requested is invalid for this request'); + + return null; + } + } else { + // validate the client has access to this scope + if ($clientScope = $this->clientStorage->getClientScope($clientId)) { + if (!$this->scopeUtil->checkScope($requestedScope, $clientScope)) { + $response->setError(400, 'invalid_scope', 'The scope requested is invalid for this client'); + + return false; + } + } elseif (!$this->scopeUtil->scopeExists($requestedScope)) { + $response->setError(400, 'invalid_scope', 'An unsupported scope was requested'); + + return null; + } + } + } elseif ($availableScope) { + // use the scope associated with this grant type + $requestedScope = $availableScope; + } else { + // use a globally-defined default scope + $defaultScope = $this->scopeUtil->getDefaultScope($clientId); + + // "false" means default scopes are not allowed + if (false === $defaultScope) { + $response->setError(400, 'invalid_scope', 'This application requires you specify a scope parameter'); + + return null; + } + + $requestedScope = $defaultScope; + } + + return $grantType->createAccessToken($this->accessToken, $clientId, $grantType->getUserId(), $requestedScope); + } + + /** + * Add grant type + * + * @param GrantTypeInterface $grantType - the grant type to add for the specified identifier + * @param string|null $identifier - a string passed in as "grant_type" in the response that will call this grantType + */ + public function addGrantType(GrantTypeInterface $grantType, $identifier = null) + { + if (is_null($identifier) || is_numeric($identifier)) { + $identifier = $grantType->getQueryStringIdentifier(); + } + + $this->grantTypes[$identifier] = $grantType; + } + + /** + * @param RequestInterface $request + * @param ResponseInterface $response + */ + public function handleRevokeRequest(RequestInterface $request, ResponseInterface $response) + { + if ($this->revokeToken($request, $response)) { + $response->setStatusCode(200); + $response->addParameters(array('revoked' => true)); + } + } + + /** + * Revoke a refresh or access token. Returns true on success and when tokens are invalid + * + * Note: invalid tokens do not cause an error response since the client + * cannot handle such an error in a reasonable way. Moreover, the + * purpose of the revocation request, invalidating the particular token, + * is already achieved. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @throws RuntimeException + * @return bool|null + */ + public function revokeToken(RequestInterface $request, ResponseInterface $response) + { + if (strtolower($request->server('REQUEST_METHOD')) === 'options') { + $response->addHttpHeaders(array('Allow' => 'POST, OPTIONS')); + + return null; + } + + if (strtolower($request->server('REQUEST_METHOD')) !== 'post') { + $response->setError(405, 'invalid_request', 'The request method must be POST when revoking an access token', '#section-3.2'); + $response->addHttpHeaders(array('Allow' => 'POST, OPTIONS')); + + return null; + } + + $token_type_hint = $request->request('token_type_hint'); + if (!in_array($token_type_hint, array(null, 'access_token', 'refresh_token'), true)) { + $response->setError(400, 'invalid_request', 'Token type hint must be either \'access_token\' or \'refresh_token\''); + + return null; + } + + $token = $request->request('token'); + if ($token === null) { + $response->setError(400, 'invalid_request', 'Missing token parameter to revoke'); + + return null; + } + + // @todo remove this check for v2.0 + if (!method_exists($this->accessToken, 'revokeToken')) { + $class = get_class($this->accessToken); + throw new RuntimeException("AccessToken {$class} does not implement required revokeToken method"); + } + + $this->accessToken->revokeToken($token, $token_type_hint); + + return true; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenControllerInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenControllerInterface.php new file mode 100644 index 0000000000..2f83ce4bd6 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenControllerInterface.php @@ -0,0 +1,39 @@ +handleTokenRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response()); + * $response->send(); + * @endcode + */ +interface TokenControllerInterface +{ + /** + * Handle the token request + * + * @param RequestInterface $request - The current http request + * @param ResponseInterface $response - An instance of OAuth2\ResponseInterface to contain the response data + */ + public function handleTokenRequest(RequestInterface $request, ResponseInterface $response); + + /** + * Grant or deny a requested access token. + * This would be called from the "/token" endpoint as defined in the spec. + * You can call your endpoint whatever you want. + * + * @param RequestInterface $request - Request object to grant access token + * @param ResponseInterface $response - Response object + * + * @return mixed + */ + public function grantAccessToken(RequestInterface $request, ResponseInterface $response); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/EncryptionInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/EncryptionInterface.php new file mode 100644 index 0000000000..8dc720a435 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/EncryptionInterface.php @@ -0,0 +1,34 @@ + + */ +class FirebaseJwt implements EncryptionInterface +{ + public function __construct() + { + if (!class_exists('\JWT')) { + throw new \ErrorException('firebase/php-jwt must be installed to use this feature. You can do this by running "composer require firebase/php-jwt"'); + } + } + + public function encode($payload, $key, $alg = 'HS256', $keyId = null) + { + return \JWT::encode($payload, $key, $alg, $keyId); + } + + public function decode($jwt, $key = null, $allowedAlgorithms = null) + { + try { + + //Maintain BC: Do not verify if no algorithms are passed in. + if (!$allowedAlgorithms) { + $key = null; + } + + return (array)\JWT::decode($jwt, $key, $allowedAlgorithms); + } catch (\Exception $e) { + return false; + } + } + + public function urlSafeB64Encode($data) + { + return \JWT::urlsafeB64Encode($data); + } + + public function urlSafeB64Decode($b64) + { + return \JWT::urlsafeB64Decode($b64); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/Jwt.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/Jwt.php new file mode 100644 index 0000000000..c258b8fc3b --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/Jwt.php @@ -0,0 +1,223 @@ +generateJwtHeader($payload, $algo); + + $segments = array( + $this->urlSafeB64Encode(json_encode($header)), + $this->urlSafeB64Encode(json_encode($payload)) + ); + + $signing_input = implode('.', $segments); + + $signature = $this->sign($signing_input, $key, $algo); + $segments[] = $this->urlsafeB64Encode($signature); + + return implode('.', $segments); + } + + /** + * @param string $jwt + * @param null $key + * @param array|bool $allowedAlgorithms + * @return bool|mixed + */ + public function decode($jwt, $key = null, $allowedAlgorithms = true) + { + if (!strpos($jwt, '.')) { + return false; + } + + $tks = explode('.', $jwt); + + if (count($tks) != 3) { + return false; + } + + list($headb64, $payloadb64, $cryptob64) = $tks; + + if (null === ($header = json_decode($this->urlSafeB64Decode($headb64), true))) { + return false; + } + + if (null === $payload = json_decode($this->urlSafeB64Decode($payloadb64), true)) { + return false; + } + + $sig = $this->urlSafeB64Decode($cryptob64); + + if ((bool) $allowedAlgorithms) { + if (!isset($header['alg'])) { + return false; + } + + // check if bool arg supplied here to maintain BC + if (is_array($allowedAlgorithms) && !in_array($header['alg'], $allowedAlgorithms)) { + return false; + } + + if (!$this->verifySignature($sig, "$headb64.$payloadb64", $key, $header['alg'])) { + return false; + } + } + + return $payload; + } + + /** + * @param $signature + * @param $input + * @param $key + * @param string $algo + * @return bool + * @throws InvalidArgumentException + */ + private function verifySignature($signature, $input, $key, $algo = 'HS256') + { + // use constants when possible, for HipHop support + switch ($algo) { + case'HS256': + case'HS384': + case'HS512': + return $this->hash_equals( + $this->sign($input, $key, $algo), + $signature + ); + + case 'RS256': + return openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256') === 1; + + case 'RS384': + return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384') === 1; + + case 'RS512': + return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512') === 1; + + default: + throw new InvalidArgumentException("Unsupported or invalid signing algorithm."); + } + } + + /** + * @param $input + * @param $key + * @param string $algo + * @return string + * @throws Exception + */ + private function sign($input, $key, $algo = 'HS256') + { + switch ($algo) { + case 'HS256': + return hash_hmac('sha256', $input, $key, true); + + case 'HS384': + return hash_hmac('sha384', $input, $key, true); + + case 'HS512': + return hash_hmac('sha512', $input, $key, true); + + case 'RS256': + return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256'); + + case 'RS384': + return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384'); + + case 'RS512': + return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512'); + + default: + throw new Exception("Unsupported or invalid signing algorithm."); + } + } + + /** + * @param $input + * @param $key + * @param string $algo + * @return mixed + * @throws Exception + */ + private function generateRSASignature($input, $key, $algo) + { + if (!openssl_sign($input, $signature, $key, $algo)) { + throw new Exception("Unable to sign data."); + } + + return $signature; + } + + /** + * @param string $data + * @return string + */ + public function urlSafeB64Encode($data) + { + $b64 = base64_encode($data); + $b64 = str_replace(array('+', '/', "\r", "\n", '='), + array('-', '_'), + $b64); + + return $b64; + } + + /** + * @param string $b64 + * @return mixed|string + */ + public function urlSafeB64Decode($b64) + { + $b64 = str_replace(array('-', '_'), + array('+', '/'), + $b64); + + return base64_decode($b64); + } + + /** + * Override to create a custom header + */ + protected function generateJwtHeader($payload, $algorithm) + { + return array( + 'typ' => 'JWT', + 'alg' => $algorithm, + ); + } + + /** + * @param string $a + * @param string $b + * @return bool + */ + protected function hash_equals($a, $b) + { + if (function_exists('hash_equals')) { + return hash_equals($a, $b); + } + $diff = strlen($a) ^ strlen($b); + for ($i = 0; $i < strlen($a) && $i < strlen($b); $i++) { + $diff |= ord($a[$i]) ^ ord($b[$i]); + } + + return $diff === 0; + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/AuthorizationCode.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/AuthorizationCode.php new file mode 100644 index 0000000000..784f6b3a32 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/AuthorizationCode.php @@ -0,0 +1,142 @@ + + */ +class AuthorizationCode implements GrantTypeInterface +{ + /** + * @var AuthorizationCodeInterface + */ + protected $storage; + + /** + * @var array + */ + protected $authCode; + + /** + * @param AuthorizationCodeInterface $storage - REQUIRED Storage class for retrieving authorization code information + */ + public function __construct(AuthorizationCodeInterface $storage) + { + $this->storage = $storage; + } + + /** + * @return string + */ + public function getQueryStringIdentifier() + { + return 'authorization_code'; + } + + /** + * Validate the OAuth request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + * @throws Exception + */ + public function validateRequest(RequestInterface $request, ResponseInterface $response) + { + if (!$request->request('code')) { + $response->setError(400, 'invalid_request', 'Missing parameter: "code" is required'); + + return false; + } + + $code = $request->request('code'); + if (!$authCode = $this->storage->getAuthorizationCode($code)) { + $response->setError(400, 'invalid_grant', 'Authorization code doesn\'t exist or is invalid for the client'); + + return false; + } + + /* + * 4.1.3 - ensure that the "redirect_uri" parameter is present if the "redirect_uri" parameter was included in the initial authorization request + * @uri - http://tools.ietf.org/html/rfc6749#section-4.1.3 + */ + if (isset($authCode['redirect_uri']) && $authCode['redirect_uri']) { + if (!$request->request('redirect_uri') || urldecode($request->request('redirect_uri')) != urldecode($authCode['redirect_uri'])) { + $response->setError(400, 'redirect_uri_mismatch', "The redirect URI is missing or do not match", "#section-4.1.3"); + + return false; + } + } + + if (!isset($authCode['expires'])) { + throw new \Exception('Storage must return authcode with a value for "expires"'); + } + + if ($authCode["expires"] < time()) { + $response->setError(400, 'invalid_grant', "The authorization code has expired"); + + return false; + } + + if (!isset($authCode['code'])) { + $authCode['code'] = $code; // used to expire the code after the access token is granted + } + + $this->authCode = $authCode; + + return true; + } + + /** + * Get the client id + * + * @return mixed + */ + public function getClientId() + { + return $this->authCode['client_id']; + } + + /** + * Get the scope + * + * @return string + */ + public function getScope() + { + return isset($this->authCode['scope']) ? $this->authCode['scope'] : null; + } + + /** + * Get the user id + * + * @return mixed + */ + public function getUserId() + { + return isset($this->authCode['user_id']) ? $this->authCode['user_id'] : null; + } + + /** + * Create access token + * + * @param AccessTokenInterface $accessToken + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user id associated with the access token + * @param string $scope - scopes to be stored in space-separated string. + * @return array + */ + public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope) + { + $token = $accessToken->createAccessToken($client_id, $user_id, $scope); + $this->storage->expireAuthorizationCode($this->authCode['code']); + + return $token; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/ClientCredentials.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/ClientCredentials.php new file mode 100644 index 0000000000..e135c2dd2e --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/ClientCredentials.php @@ -0,0 +1,98 @@ + + * + * @see HttpBasic + */ +class ClientCredentials extends HttpBasic implements GrantTypeInterface +{ + /** + * @var array + */ + private $clientData; + + /** + * @param ClientCredentialsInterface $storage + * @param array $config + */ + public function __construct(ClientCredentialsInterface $storage, array $config = array()) + { + /** + * The client credentials grant type MUST only be used by confidential clients + * + * @see http://tools.ietf.org/html/rfc6749#section-4.4 + */ + $config['allow_public_clients'] = false; + + parent::__construct($storage, $config); + } + + /** + * Get query string identifier + * + * @return string + */ + public function getQueryStringIdentifier() + { + return 'client_credentials'; + } + + /** + * Get scope + * + * @return string|null + */ + public function getScope() + { + $this->loadClientData(); + + return isset($this->clientData['scope']) ? $this->clientData['scope'] : null; + } + + /** + * Get user id + * + * @return mixed + */ + public function getUserId() + { + $this->loadClientData(); + + return isset($this->clientData['user_id']) ? $this->clientData['user_id'] : null; + } + + /** + * Create access token + * + * @param AccessTokenInterface $accessToken + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user id associated with the access token + * @param string $scope - scopes to be stored in space-separated string. + * @return array + */ + public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope) + { + /** + * Client Credentials Grant does NOT include a refresh token + * + * @see http://tools.ietf.org/html/rfc6749#section-4.4.3 + */ + $includeRefreshToken = false; + + return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken); + } + + private function loadClientData() + { + if (!$this->clientData) { + $this->clientData = $this->storage->getClientDetails($this->getClientId()); + } + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/GrantTypeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/GrantTypeInterface.php new file mode 100644 index 0000000000..f45786ff59 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/GrantTypeInterface.php @@ -0,0 +1,59 @@ + + */ +class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface +{ + private $jwt; + + protected $storage; + protected $audience; + protected $jwtUtil; + protected $allowedAlgorithms; + + /** + * Creates an instance of the JWT bearer grant type. + * + * @param JwtBearerInterface $storage - A valid storage interface that implements storage hooks for the JWT + * bearer grant type. + * @param string $audience - The audience to validate the token against. This is usually the full + * URI of the OAuth token requests endpoint. + * @param EncryptionInterface|JWT $jwtUtil - OPTONAL The class used to decode, encode and verify JWTs. + * @param array $config + */ + public function __construct(JwtBearerInterface $storage, $audience, EncryptionInterface $jwtUtil = null, array $config = array()) + { + $this->storage = $storage; + $this->audience = $audience; + + if (is_null($jwtUtil)) { + $jwtUtil = new Jwt(); + } + + $this->config = array_merge(array( + 'allowed_algorithms' => array('RS256', 'RS384', 'RS512') + ), $config); + + $this->jwtUtil = $jwtUtil; + + $this->allowedAlgorithms = $this->config['allowed_algorithms']; + } + + /** + * Returns the grant_type get parameter to identify the grant type request as JWT bearer authorization grant. + * + * @return string - The string identifier for grant_type. + * + * @see GrantTypeInterface::getQueryStringIdentifier() + */ + public function getQueryStringIdentifier() + { + return 'urn:ietf:params:oauth:grant-type:jwt-bearer'; + } + + /** + * Validates the data from the decoded JWT. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool|mixed|null TRUE if the JWT request is valid and can be decoded. Otherwise, FALSE is returned.@see GrantTypeInterface::getTokenData() + */ + public function validateRequest(RequestInterface $request, ResponseInterface $response) + { + if (!$request->request("assertion")) { + $response->setError(400, 'invalid_request', 'Missing parameters: "assertion" required'); + + return null; + } + + // Store the undecoded JWT for later use + $undecodedJWT = $request->request('assertion'); + + // Decode the JWT + $jwt = $this->jwtUtil->decode($request->request('assertion'), null, false); + + if (!$jwt) { + $response->setError(400, 'invalid_request', "JWT is malformed"); + + return null; + } + + // ensure these properties contain a value + // @todo: throw malformed error for missing properties + $jwt = array_merge(array( + 'scope' => null, + 'iss' => null, + 'sub' => null, + 'aud' => null, + 'exp' => null, + 'nbf' => null, + 'iat' => null, + 'jti' => null, + 'typ' => null, + ), $jwt); + + if (!isset($jwt['iss'])) { + $response->setError(400, 'invalid_grant', "Invalid issuer (iss) provided"); + + return null; + } + + if (!isset($jwt['sub'])) { + $response->setError(400, 'invalid_grant', "Invalid subject (sub) provided"); + + return null; + } + + if (!isset($jwt['exp'])) { + $response->setError(400, 'invalid_grant', "Expiration (exp) time must be present"); + + return null; + } + + // Check expiration + if (ctype_digit($jwt['exp'])) { + if ($jwt['exp'] <= time()) { + $response->setError(400, 'invalid_grant', "JWT has expired"); + + return null; + } + } else { + $response->setError(400, 'invalid_grant', "Expiration (exp) time must be a unix time stamp"); + + return null; + } + + // Check the not before time + if ($notBefore = $jwt['nbf']) { + if (ctype_digit($notBefore)) { + if ($notBefore > time()) { + $response->setError(400, 'invalid_grant', "JWT cannot be used before the Not Before (nbf) time"); + + return null; + } + } else { + $response->setError(400, 'invalid_grant', "Not Before (nbf) time must be a unix time stamp"); + + return null; + } + } + + // Check the audience if required to match + if (!isset($jwt['aud']) || ($jwt['aud'] != $this->audience)) { + $response->setError(400, 'invalid_grant', "Invalid audience (aud)"); + + return null; + } + + // Check the jti (nonce) + // @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-13#section-4.1.7 + if (isset($jwt['jti'])) { + $jti = $this->storage->getJti($jwt['iss'], $jwt['sub'], $jwt['aud'], $jwt['exp'], $jwt['jti']); + + //Reject if jti is used and jwt is still valid (exp parameter has not expired). + if ($jti && $jti['expires'] > time()) { + $response->setError(400, 'invalid_grant', "JSON Token Identifier (jti) has already been used"); + + return null; + } else { + $this->storage->setJti($jwt['iss'], $jwt['sub'], $jwt['aud'], $jwt['exp'], $jwt['jti']); + } + } + + // Get the iss's public key + // @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06#section-4.1.1 + if (!$key = $this->storage->getClientKey($jwt['iss'], $jwt['sub'])) { + $response->setError(400, 'invalid_grant', "Invalid issuer (iss) or subject (sub) provided"); + + return null; + } + + // Verify the JWT + if (!$this->jwtUtil->decode($undecodedJWT, $key, $this->allowedAlgorithms)) { + $response->setError(400, 'invalid_grant', "JWT failed signature verification"); + + return null; + } + + $this->jwt = $jwt; + + return true; + } + + /** + * Get client id + * + * @return mixed + */ + public function getClientId() + { + return $this->jwt['iss']; + } + + /** + * Get user id + * + * @return mixed + */ + public function getUserId() + { + return $this->jwt['sub']; + } + + /** + * Get scope + * + * @return null + */ + public function getScope() + { + return null; + } + + /** + * Creates an access token that is NOT associated with a refresh token. + * If a subject (sub) the name of the user/account we are accessing data on behalf of. + * + * @see GrantTypeInterface::createAccessToken() + * + * @param AccessTokenInterface $accessToken + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user id associated with the access token + * @param string $scope - scopes to be stored in space-separated string. + * @return array + */ + public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope) + { + $includeRefreshToken = false; + + return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/RefreshToken.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/RefreshToken.php new file mode 100644 index 0000000000..75c611f1de --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/RefreshToken.php @@ -0,0 +1,154 @@ + + */ +class RefreshToken implements GrantTypeInterface +{ + /** + * @var array + */ + private $refreshToken; + + /** + * @var RefreshTokenInterface + */ + protected $storage; + + /** + * @var array + */ + protected $config; + + /** + * @param RefreshTokenInterface $storage - REQUIRED Storage class for retrieving refresh token information + * @param array $config - OPTIONAL Configuration options for the server + * @code + * $config = array( + * 'always_issue_new_refresh_token' => true, // whether to issue a new refresh token upon successful token request + * 'unset_refresh_token_after_use' => true // whether to unset the refresh token after after using + * ); + * @endcode + */ + public function __construct(RefreshTokenInterface $storage, $config = array()) + { + $this->config = array_merge(array( + 'always_issue_new_refresh_token' => false, + 'unset_refresh_token_after_use' => true + ), $config); + + // to preserve B.C. with v1.6 + // @see https://github.com/bshaffer/oauth2-server-php/pull/580 + // @todo - remove in v2.0 + if (isset($config['always_issue_new_refresh_token']) && !isset($config['unset_refresh_token_after_use'])) { + $this->config['unset_refresh_token_after_use'] = $config['always_issue_new_refresh_token']; + } + + $this->storage = $storage; + } + + /** + * @return string + */ + public function getQueryStringIdentifier() + { + return 'refresh_token'; + } + + /** + * Validate the OAuth request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool|mixed|null + */ + public function validateRequest(RequestInterface $request, ResponseInterface $response) + { + if (!$request->request("refresh_token")) { + $response->setError(400, 'invalid_request', 'Missing parameter: "refresh_token" is required'); + + return null; + } + + if (!$refreshToken = $this->storage->getRefreshToken($request->request("refresh_token"))) { + $response->setError(400, 'invalid_grant', 'Invalid refresh token'); + + return null; + } + + if ($refreshToken['expires'] > 0 && $refreshToken["expires"] < time()) { + $response->setError(400, 'invalid_grant', 'Refresh token has expired'); + + return null; + } + + // store the refresh token locally so we can delete it when a new refresh token is generated + $this->refreshToken = $refreshToken; + + return true; + } + + /** + * Get client id + * + * @return mixed + */ + public function getClientId() + { + return $this->refreshToken['client_id']; + } + + /** + * Get user id + * + * @return mixed|null + */ + public function getUserId() + { + return isset($this->refreshToken['user_id']) ? $this->refreshToken['user_id'] : null; + } + + /** + * Get scope + * + * @return null|string + */ + public function getScope() + { + return isset($this->refreshToken['scope']) ? $this->refreshToken['scope'] : null; + } + + /** + * Create access token + * + * @param AccessTokenInterface $accessToken + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user id associated with the access token + * @param string $scope - scopes to be stored in space-separated string. + * @return array + */ + public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope) + { + /* + * It is optional to force a new refresh token when a refresh token is used. + * However, if a new refresh token is issued, the old one MUST be expired + * @see http://tools.ietf.org/html/rfc6749#section-6 + */ + $issueNewRefreshToken = $this->config['always_issue_new_refresh_token']; + $unsetRefreshToken = $this->config['unset_refresh_token_after_use']; + $token = $accessToken->createAccessToken($client_id, $user_id, $scope, $issueNewRefreshToken); + + if ($unsetRefreshToken) { + $this->storage->unsetRefreshToken($this->refreshToken['refresh_token']); + } + + return $token; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/UserCredentials.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/UserCredentials.php new file mode 100644 index 0000000000..b10c2dd095 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/UserCredentials.php @@ -0,0 +1,123 @@ + + */ +class UserCredentials implements GrantTypeInterface +{ + /** + * @var array + */ + private $userInfo; + + /** + * @var UserCredentialsInterface + */ + protected $storage; + + /** + * @param UserCredentialsInterface $storage - REQUIRED Storage class for retrieving user credentials information + */ + public function __construct(UserCredentialsInterface $storage) + { + $this->storage = $storage; + } + + /** + * @return string + */ + public function getQueryStringIdentifier() + { + return 'password'; + } + + /** + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool|mixed|null + * + * @throws LogicException + */ + public function validateRequest(RequestInterface $request, ResponseInterface $response) + { + if (!$request->request("password") || !$request->request("username")) { + $response->setError(400, 'invalid_request', 'Missing parameters: "username" and "password" required'); + + return null; + } + + if (!$this->storage->checkUserCredentials($request->request("username"), $request->request("password"))) { + $response->setError(401, 'invalid_grant', 'Invalid username and password combination'); + + return null; + } + + $userInfo = $this->storage->getUserDetails($request->request("username")); + + if (empty($userInfo)) { + $response->setError(400, 'invalid_grant', 'Unable to retrieve user information'); + + return null; + } + + if (!isset($userInfo['user_id'])) { + throw new \LogicException("you must set the user_id on the array returned by getUserDetails"); + } + + $this->userInfo = $userInfo; + + return true; + } + + /** + * Get client id + * + * @return mixed|null + */ + public function getClientId() + { + return null; + } + + /** + * Get user id + * + * @return mixed + */ + public function getUserId() + { + return $this->userInfo['user_id']; + } + + /** + * Get scope + * + * @return null|string + */ + public function getScope() + { + return isset($this->userInfo['scope']) ? $this->userInfo['scope'] : null; + } + + /** + * Create access token + * + * @param AccessTokenInterface $accessToken + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user id associated with the access token + * @param string $scope - scopes to be stored in space-separated string. + * @return array + */ + public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope) + { + return $accessToken->createAccessToken($client_id, $user_id, $scope); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeController.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeController.php new file mode 100644 index 0000000000..54c5f9a630 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeController.php @@ -0,0 +1,135 @@ +query('prompt', 'consent'); + if ($prompt == 'none') { + if (is_null($user_id)) { + $error = 'login_required'; + $error_message = 'The user must log in'; + } else { + $error = 'interaction_required'; + $error_message = 'The user must grant access to your application'; + } + } else { + $error = 'consent_required'; + $error_message = 'The user denied access to your application'; + } + + $response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->getState(), $error, $error_message); + } + + /** + * @TODO: add dependency injection for the parameters in this method + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param mixed $user_id + * @return array + */ + protected function buildAuthorizeParameters($request, $response, $user_id) + { + if (!$params = parent::buildAuthorizeParameters($request, $response, $user_id)) { + return; + } + + // Generate an id token if needed. + if ($this->needsIdToken($this->getScope()) && $this->getResponseType() == self::RESPONSE_TYPE_AUTHORIZATION_CODE) { + $params['id_token'] = $this->responseTypes['id_token']->createIdToken($this->getClientId(), $user_id, $this->nonce); + } + + // add the nonce to return with the redirect URI + $params['nonce'] = $this->nonce; + + return $params; + } + + /** + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response) + { + if (!parent::validateAuthorizeRequest($request, $response)) { + return false; + } + + $nonce = $request->query('nonce'); + + // Validate required nonce for "id_token" and "id_token token" + if (!$nonce && in_array($this->getResponseType(), array(self::RESPONSE_TYPE_ID_TOKEN, self::RESPONSE_TYPE_ID_TOKEN_TOKEN))) { + $response->setError(400, 'invalid_nonce', 'This application requires you specify a nonce parameter'); + + return false; + } + + $this->nonce = $nonce; + + return true; + } + + /** + * Array of valid response types + * + * @return array + */ + protected function getValidResponseTypes() + { + return array( + self::RESPONSE_TYPE_ACCESS_TOKEN, + self::RESPONSE_TYPE_AUTHORIZATION_CODE, + self::RESPONSE_TYPE_ID_TOKEN, + self::RESPONSE_TYPE_ID_TOKEN_TOKEN, + self::RESPONSE_TYPE_CODE_ID_TOKEN, + ); + } + + /** + * Returns whether the current request needs to generate an id token. + * + * ID Tokens are a part of the OpenID Connect specification, so this + * method checks whether OpenID Connect is enabled in the server settings + * and whether the openid scope was requested. + * + * @param string $request_scope - A space-separated string of scopes. + * @return boolean - TRUE if an id token is needed, FALSE otherwise. + */ + public function needsIdToken($request_scope) + { + // see if the "openid" scope exists in the requested scope + return $this->scopeUtil->checkScope('openid', $request_scope); + } + + /** + * @return mixed + */ + public function getNonce() + { + return $this->nonce; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php new file mode 100644 index 0000000000..b4967c317c --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php @@ -0,0 +1,12 @@ +userClaimsStorage = $userClaimsStorage; + } + + /** + * Handle the user info request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response) + { + if (!$this->verifyResourceRequest($request, $response, 'openid')) { + return; + } + + $token = $this->getToken(); + $claims = $this->userClaimsStorage->getUserClaims($token['user_id'], $token['scope']); + // The sub Claim MUST always be returned in the UserInfo Response. + // http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse + $claims += array( + 'sub' => $token['user_id'], + ); + $response->addParameters($claims); + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/UserInfoControllerInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/UserInfoControllerInterface.php new file mode 100644 index 0000000000..88e9228d00 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/UserInfoControllerInterface.php @@ -0,0 +1,30 @@ +handleUserInfoRequest( + * OAuth2\Request::createFromGlobals(), + * $response + * ); + * $response->send(); + * @endcode + */ +interface UserInfoControllerInterface +{ + /** + * Handle user info request + * + * @param RequestInterface $request + * @param ResponseInterface $response + */ + public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/GrantType/AuthorizationCode.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/GrantType/AuthorizationCode.php new file mode 100644 index 0000000000..ee113a0e57 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/GrantType/AuthorizationCode.php @@ -0,0 +1,41 @@ + + */ +class AuthorizationCode extends BaseAuthorizationCode +{ + /** + * Create access token + * + * @param AccessTokenInterface $accessToken + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user id associated with the access token + * @param string $scope - scopes to be stored in space-separated string. + * @return array + */ + public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope) + { + $includeRefreshToken = true; + if (isset($this->authCode['id_token'])) { + // OpenID Connect requests include the refresh token only if the + // offline_access scope has been requested and granted. + $scopes = explode(' ', trim($scope)); + $includeRefreshToken = in_array('offline_access', $scopes); + } + + $token = $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken); + if (isset($this->authCode['id_token'])) { + $token['id_token'] = $this->authCode['id_token']; + } + + $this->storage->expireAuthorizationCode($this->authCode['code']); + + return $token; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php new file mode 100644 index 0000000000..b8ad41ffb1 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php @@ -0,0 +1,66 @@ + + */ +class AuthorizationCode extends BaseAuthorizationCode implements AuthorizationCodeInterface +{ + /** + * Constructor + * + * @param AuthorizationCodeStorageInterface $storage + * @param array $config + */ + public function __construct(AuthorizationCodeStorageInterface $storage, array $config = array()) + { + parent::__construct($storage, $config); + } + + /** + * @param $params + * @param null $user_id + * @return array + */ + public function getAuthorizeResponse($params, $user_id = null) + { + // build the URL to redirect to + $result = array('query' => array()); + + $params += array('scope' => null, 'state' => null, 'id_token' => null); + + $result['query']['code'] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope'], $params['id_token']); + + if (isset($params['state'])) { + $result['query']['state'] = $params['state']; + } + + return array($params['redirect_uri'], $result); + } + + /** + * Handle the creation of the authorization code. + * + * @param mixed $client_id - Client identifier related to the authorization code + * @param mixed $user_id - User ID associated with the authorization code + * @param string $redirect_uri - An absolute URI to which the authorization server will redirect the + * user-agent to when the end-user authorization step is completed. + * @param string $scope - OPTIONAL Scopes to be stored in space-separated string. + * @param string $id_token - OPTIONAL The OpenID Connect id_token. + * + * @return string + * @see http://tools.ietf.org/html/rfc6749#section-4 + * @ingroup oauth2_section_4 + */ + public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $id_token = null) + { + $code = $this->generateAuthorizationCode(); + $this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope, $id_token); + + return $code; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php new file mode 100644 index 0000000000..eb94ef0776 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php @@ -0,0 +1,27 @@ + + */ +interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface +{ + /** + * Handle the creation of the authorization code. + * + * @param mixed $client_id - Client identifier related to the authorization code + * @param mixed $user_id - User ID associated with the authorization code + * @param string $redirect_uri - An absolute URI to which the authorization server will redirect the + * user-agent to when the end-user authorization step is completed. + * @param string $scope - OPTIONAL Scopes to be stored in space-separated string. + * @param string $id_token - OPTIONAL The OpenID Connect id_token. + * @return string + * + * @see http://tools.ietf.org/html/rfc6749#section-4 + * @ingroup oauth2_section_4 + */ + public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $id_token = null); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/CodeIdToken.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/CodeIdToken.php new file mode 100644 index 0000000000..2696ada379 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/CodeIdToken.php @@ -0,0 +1,40 @@ +authCode = $authCode; + $this->idToken = $idToken; + } + + /** + * @param array $params + * @param mixed $user_id + * @return mixed + */ + public function getAuthorizeResponse($params, $user_id = null) + { + $result = $this->authCode->getAuthorizeResponse($params, $user_id); + $resultIdToken = $this->idToken->getAuthorizeResponse($params, $user_id); + $result[1]['query']['id_token'] = $resultIdToken[1]['fragment']['id_token']; + + return $result; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/CodeIdTokenInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/CodeIdTokenInterface.php new file mode 100644 index 0000000000..629adcca88 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/CodeIdTokenInterface.php @@ -0,0 +1,9 @@ +userClaimsStorage = $userClaimsStorage; + $this->publicKeyStorage = $publicKeyStorage; + if (is_null($encryptionUtil)) { + $encryptionUtil = new Jwt(); + } + $this->encryptionUtil = $encryptionUtil; + + if (!isset($config['issuer'])) { + throw new LogicException('config parameter "issuer" must be set'); + } + $this->config = array_merge(array( + 'id_lifetime' => 3600, + ), $config); + } + + /** + * @param array $params + * @param null $userInfo + * @return array|mixed + */ + public function getAuthorizeResponse($params, $userInfo = null) + { + // build the URL to redirect to + $result = array('query' => array()); + $params += array('scope' => null, 'state' => null, 'nonce' => null); + + // create the id token. + list($user_id, $auth_time) = $this->getUserIdAndAuthTime($userInfo); + $userClaims = $this->userClaimsStorage->getUserClaims($user_id, $params['scope']); + + $id_token = $this->createIdToken($params['client_id'], $userInfo, $params['nonce'], $userClaims, null); + $result["fragment"] = array('id_token' => $id_token); + if (isset($params['state'])) { + $result["fragment"]["state"] = $params['state']; + } + + return array($params['redirect_uri'], $result); + } + + /** + * Create id token + * + * @param string $client_id + * @param mixed $userInfo + * @param mixed $nonce + * @param mixed $userClaims + * @param mixed $access_token + * @return mixed|string + */ + public function createIdToken($client_id, $userInfo, $nonce = null, $userClaims = null, $access_token = null) + { + // pull auth_time from user info if supplied + list($user_id, $auth_time) = $this->getUserIdAndAuthTime($userInfo); + + $token = array( + 'iss' => $this->config['issuer'], + 'sub' => $user_id, + 'aud' => $client_id, + 'iat' => time(), + 'exp' => time() + $this->config['id_lifetime'], + 'auth_time' => $auth_time, + ); + + if ($nonce) { + $token['nonce'] = $nonce; + } + + if ($userClaims) { + $token += $userClaims; + } + + if ($access_token) { + $token['at_hash'] = $this->createAtHash($access_token, $client_id); + } + + return $this->encodeToken($token, $client_id); + } + + /** + * @param $access_token + * @param null $client_id + * @return mixed|string + */ + protected function createAtHash($access_token, $client_id = null) + { + // maps HS256 and RS256 to sha256, etc. + $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id); + $hash_algorithm = 'sha' . substr($algorithm, 2); + $hash = hash($hash_algorithm, $access_token, true); + $at_hash = substr($hash, 0, strlen($hash) / 2); + + return $this->encryptionUtil->urlSafeB64Encode($at_hash); + } + + /** + * @param array $token + * @param null $client_id + * @return mixed|string + */ + protected function encodeToken(array $token, $client_id = null) + { + $private_key = $this->publicKeyStorage->getPrivateKey($client_id); + $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id); + + return $this->encryptionUtil->encode($token, $private_key, $algorithm); + } + + /** + * @param $userInfo + * @return array + * @throws LogicException + */ + private function getUserIdAndAuthTime($userInfo) + { + $auth_time = null; + + // support an array for user_id / auth_time + if (is_array($userInfo)) { + if (!isset($userInfo['user_id'])) { + throw new LogicException('if $user_id argument is an array, user_id index must be set'); + } + + $auth_time = isset($userInfo['auth_time']) ? $userInfo['auth_time'] : null; + $user_id = $userInfo['user_id']; + } else { + $user_id = $userInfo; + } + + if (is_null($auth_time)) { + $auth_time = time(); + } + + // userInfo is a scalar, and so this is the $user_id. Auth Time is null + return array($user_id, $auth_time); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php new file mode 100644 index 0000000000..226a3bcbbc --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php @@ -0,0 +1,30 @@ +accessToken = $accessToken; + $this->idToken = $idToken; + } + + /** + * @param array $params + * @param mixed $user_id + * @return mixed + */ + public function getAuthorizeResponse($params, $user_id = null) + { + $result = $this->accessToken->getAuthorizeResponse($params, $user_id); + $access_token = $result[1]['fragment']['access_token']; + $id_token = $this->idToken->createIdToken($params['client_id'], $user_id, $params['nonce'], null, $access_token); + $result[1]['fragment']['id_token'] = $id_token; + + return $result; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenTokenInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenTokenInterface.php new file mode 100644 index 0000000000..ac13e2032a --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenTokenInterface.php @@ -0,0 +1,9 @@ + + */ +interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface +{ + /** + * Take the provided authorization code values and store them somewhere. + * + * This function should be the storage counterpart to getAuthCode(). + * + * If storage fails for some reason, we're not currently checking for + * any sort of success/failure, so you should bail out of the script + * and provide a descriptive fail message. + * + * Required for OAuth2::GRANT_TYPE_AUTH_CODE. + * + * @param string $code - authorization code to be stored. + * @param mixed $client_id - client identifier to be stored. + * @param mixed $user_id - user identifier to be stored. + * @param string $redirect_uri - redirect URI(s) to be stored in a space-separated string. + * @param int $expires - expiration to be stored as a Unix timestamp. + * @param string $scope - OPTIONAL scopes to be stored in space-separated string. + * @param string $id_token - OPTIONAL the OpenID Connect id_token. + * + * @ingroup oauth2_section_4 + */ + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Storage/UserClaimsInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Storage/UserClaimsInterface.php new file mode 100644 index 0000000000..9c5e7c8c47 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Storage/UserClaimsInterface.php @@ -0,0 +1,35 @@ + value format. + * + * @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims + */ + public function getUserClaims($user_id, $scope); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Request.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Request.php new file mode 100644 index 0000000000..f547bf6e8f --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Request.php @@ -0,0 +1,252 @@ +initialize($query, $request, $attributes, $cookies, $files, $server, $content, $headers); + } + + /** + * Sets the parameters for this request. + * + * This method also re-initializes all properties. + * + * @param array $query - The GET parameters + * @param array $request - The POST parameters + * @param array $attributes - The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies - The COOKIE parameters + * @param array $files - The FILES parameters + * @param array $server - The SERVER parameters + * @param string $content - The raw body data + * @param array $headers - The headers + * + * @api + */ + public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null, array $headers = null) + { + $this->request = $request; + $this->query = $query; + $this->attributes = $attributes; + $this->cookies = $cookies; + $this->files = $files; + $this->server = $server; + $this->content = $content; + + if ($headers === null) { + $headers = array(); + } + + $this->headers = $headers + $this->getHeadersFromServer($this->server); + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function query($name, $default = null) + { + return isset($this->query[$name]) ? $this->query[$name] : $default; + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function request($name, $default = null) + { + return isset($this->request[$name]) ? $this->request[$name] : $default; + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function server($name, $default = null) + { + return isset($this->server[$name]) ? $this->server[$name] : $default; + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function headers($name, $default = null) + { + $headers = array_change_key_case($this->headers); + $name = strtolower($name); + + return isset($headers[$name]) ? $headers[$name] : $default; + } + + /** + * @return array + */ + public function getAllQueryParameters() + { + return $this->query; + } + + /** + * Returns the request body content. + * + * @param boolean $asResource - If true, a resource will be returned + * @return string|resource - The request body content or a resource to read the body stream. + * + * @throws LogicException + */ + public function getContent($asResource = false) + { + if (false === $this->content || (true === $asResource && null !== $this->content)) { + throw new LogicException('getContent() can only be called once when using the resource return type.'); + } + + if (true === $asResource) { + $this->content = false; + + return fopen('php://input', 'rb'); + } + + if (null === $this->content) { + $this->content = file_get_contents('php://input'); + } + + return $this->content; + } + + /** + * @param array $server + * @return array + */ + private function getHeadersFromServer($server) + { + $headers = array(); + foreach ($server as $key => $value) { + if (0 === strpos($key, 'HTTP_')) { + $headers[substr($key, 5)] = $value; + } + // CONTENT_* are not prefixed with HTTP_ + elseif (in_array($key, array('CONTENT_LENGTH', 'CONTENT_MD5', 'CONTENT_TYPE'))) { + $headers[$key] = $value; + } + } + + if (isset($server['PHP_AUTH_USER'])) { + $headers['PHP_AUTH_USER'] = $server['PHP_AUTH_USER']; + $headers['PHP_AUTH_PW'] = isset($server['PHP_AUTH_PW']) ? $server['PHP_AUTH_PW'] : ''; + } else { + /* + * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default + * For this workaround to work, add this line to your .htaccess file: + * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * + * A sample .htaccess file: + * RewriteEngine On + * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * RewriteCond %{REQUEST_FILENAME} !-f + * RewriteRule ^(.*)$ app.php [QSA,L] + */ + + $authorizationHeader = null; + if (isset($server['HTTP_AUTHORIZATION'])) { + $authorizationHeader = $server['HTTP_AUTHORIZATION']; + } elseif (isset($server['REDIRECT_HTTP_AUTHORIZATION'])) { + $authorizationHeader = $server['REDIRECT_HTTP_AUTHORIZATION']; + } elseif (function_exists('apache_request_headers')) { + $requestHeaders = (array) apache_request_headers(); + + // Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization) + $requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders)); + + if (isset($requestHeaders['Authorization'])) { + $authorizationHeader = trim($requestHeaders['Authorization']); + } + } + + if (null !== $authorizationHeader) { + $headers['AUTHORIZATION'] = $authorizationHeader; + // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic + if (0 === stripos($authorizationHeader, 'basic')) { + $exploded = explode(':', base64_decode(substr($authorizationHeader, 6))); + if (count($exploded) == 2) { + list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded; + } + } + } + } + + // PHP_AUTH_USER/PHP_AUTH_PW + if (isset($headers['PHP_AUTH_USER'])) { + $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']); + } + + return $headers; + } + + /** + * Creates a new request with values from PHP's super globals. + * + * @return Request - A new request + * + * @api + */ + public static function createFromGlobals() + { + $class = get_called_class(); + + /** @var Request $request */ + $request = new $class($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER); + + $contentType = $request->server('CONTENT_TYPE', ''); + $requestMethod = $request->server('REQUEST_METHOD', 'GET'); + if (0 === strpos($contentType, 'application/x-www-form-urlencoded') + && in_array(strtoupper($requestMethod), array('PUT', 'DELETE')) + ) { + parse_str($request->getContent(), $data); + $request->request = $data; + } elseif (0 === strpos($contentType, 'application/json') + && in_array(strtoupper($requestMethod), array('POST', 'PUT', 'DELETE')) + ) { + $data = json_decode($request->getContent(), true); + $request->request = $data; + } + + return $request; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/RequestInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/RequestInterface.php new file mode 100644 index 0000000000..1d036b7350 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/RequestInterface.php @@ -0,0 +1,39 @@ + 'Continue', + 101 => 'Switching Protocols', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + ); + + /** + * @param array $parameters + * @param int $statusCode + * @param array $headers + */ + public function __construct($parameters = array(), $statusCode = 200, $headers = array()) + { + $this->setParameters($parameters); + $this->setStatusCode($statusCode); + $this->setHttpHeaders($headers); + $this->version = '1.1'; + } + + /** + * Converts the response object to string containing all headers and the response content. + * + * @return string The response with headers and content + */ + public function __toString() + { + $headers = array(); + foreach ($this->httpHeaders as $name => $value) { + $headers[$name] = (array) $value; + } + + return + sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". + $this->getHttpHeadersAsString($headers)."\r\n". + $this->getResponseBody(); + } + + /** + * Returns the build header line. + * + * @param string $name The header name + * @param string $value The header value + * + * @return string The built header line + */ + protected function buildHeader($name, $value) + { + return sprintf("%s: %s\n", $name, $value); + } + + /** + * @return int + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * @param int $statusCode + * @param string $text + * @throws InvalidArgumentException + */ + public function setStatusCode($statusCode, $text = null) + { + $this->statusCode = (int) $statusCode; + if ($this->isInvalid()) { + throw new InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $statusCode)); + } + + $this->statusText = false === $text ? '' : (null === $text ? self::$statusTexts[$this->statusCode] : $text); + } + + /** + * @return string + */ + public function getStatusText() + { + return $this->statusText; + } + + /** + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * @param array $parameters + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + } + + /** + * @param array $parameters + */ + public function addParameters(array $parameters) + { + $this->parameters = array_merge($this->parameters, $parameters); + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function getParameter($name, $default = null) + { + return isset($this->parameters[$name]) ? $this->parameters[$name] : $default; + } + + /** + * @param string $name + * @param mixed $value + */ + public function setParameter($name, $value) + { + $this->parameters[$name] = $value; + } + + /** + * @param array $httpHeaders + */ + public function setHttpHeaders(array $httpHeaders) + { + $this->httpHeaders = $httpHeaders; + } + + /** + * @param string $name + * @param mixed $value + */ + public function setHttpHeader($name, $value) + { + $this->httpHeaders[$name] = $value; + } + + /** + * @param array $httpHeaders + */ + public function addHttpHeaders(array $httpHeaders) + { + $this->httpHeaders = array_merge($this->httpHeaders, $httpHeaders); + } + + /** + * @return array + */ + public function getHttpHeaders() + { + return $this->httpHeaders; + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function getHttpHeader($name, $default = null) + { + return isset($this->httpHeaders[$name]) ? $this->httpHeaders[$name] : $default; + } + + /** + * @param string $format + * @return mixed + * @throws InvalidArgumentException + */ + public function getResponseBody($format = 'json') + { + switch ($format) { + case 'json': + return $this->parameters ? json_encode($this->parameters) : ''; + case 'xml': + // this only works for single-level arrays + $xml = new \SimpleXMLElement(''); + foreach ($this->parameters as $key => $param) { + $xml->addChild($key, $param); + } + + return $xml->asXML(); + } + + throw new InvalidArgumentException(sprintf('The format %s is not supported', $format)); + + } + + /** + * @param string $format + */ + public function send($format = 'json') + { + // headers have already been sent by the developer + if (headers_sent()) { + return; + } + + switch ($format) { + case 'json': + $this->setHttpHeader('Content-Type', 'application/json'); + break; + case 'xml': + $this->setHttpHeader('Content-Type', 'text/xml'); + break; + } + // status + header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)); + + foreach ($this->getHttpHeaders() as $name => $header) { + header(sprintf('%s: %s', $name, $header)); + } + echo $this->getResponseBody($format); + } + + /** + * @param int $statusCode + * @param string $error + * @param string $errorDescription + * @param string $errorUri + * @return mixed + * @throws InvalidArgumentException + */ + public function setError($statusCode, $error, $errorDescription = null, $errorUri = null) + { + $parameters = array( + 'error' => $error, + 'error_description' => $errorDescription, + ); + + if (!is_null($errorUri)) { + if (strlen($errorUri) > 0 && $errorUri[0] == '#') { + // we are referencing an oauth bookmark (for brevity) + $errorUri = 'http://tools.ietf.org/html/rfc6749' . $errorUri; + } + $parameters['error_uri'] = $errorUri; + } + + $httpHeaders = array( + 'Cache-Control' => 'no-store' + ); + + $this->setStatusCode($statusCode); + $this->addParameters($parameters); + $this->addHttpHeaders($httpHeaders); + + if (!$this->isClientError() && !$this->isServerError()) { + throw new InvalidArgumentException(sprintf('The HTTP status code is not an error ("%s" given).', $statusCode)); + } + } + + /** + * @param int $statusCode + * @param string $url + * @param string $state + * @param string $error + * @param string $errorDescription + * @param string $errorUri + * @return mixed + * @throws InvalidArgumentException + */ + public function setRedirect($statusCode, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null) + { + if (empty($url)) { + throw new InvalidArgumentException('Cannot redirect to an empty URL.'); + } + + $parameters = array(); + + if (!is_null($state)) { + $parameters['state'] = $state; + } + + if (!is_null($error)) { + $this->setError(400, $error, $errorDescription, $errorUri); + } + $this->setStatusCode($statusCode); + $this->addParameters($parameters); + + if (count($this->parameters) > 0) { + // add parameters to URL redirection + $parts = parse_url($url); + $sep = isset($parts['query']) && !empty($parts['query']) ? '&' : '?'; + $url .= $sep . http_build_query($this->parameters); + } + + $this->addHttpHeaders(array('Location' => $url)); + + if (!$this->isRedirection()) { + throw new InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $statusCode)); + } + } + + /** + * @return Boolean + * + * @api + * + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + */ + public function isInvalid() + { + return $this->statusCode < 100 || $this->statusCode >= 600; + } + + /** + * @return Boolean + * + * @api + */ + public function isInformational() + { + return $this->statusCode >= 100 && $this->statusCode < 200; + } + + /** + * @return Boolean + * + * @api + */ + public function isSuccessful() + { + return $this->statusCode >= 200 && $this->statusCode < 300; + } + + /** + * @return Boolean + * + * @api + */ + public function isRedirection() + { + return $this->statusCode >= 300 && $this->statusCode < 400; + } + + /** + * @return Boolean + * + * @api + */ + public function isClientError() + { + return $this->statusCode >= 400 && $this->statusCode < 500; + } + + /** + * @return Boolean + * + * @api + */ + public function isServerError() + { + return $this->statusCode >= 500 && $this->statusCode < 600; + } + + /** + * Function from Symfony2 HttpFoundation - output pretty header + * + * @param array $headers + * @return string + */ + private function getHttpHeadersAsString($headers) + { + if (count($headers) == 0) { + return ''; + } + + $max = max(array_map('strlen', array_keys($headers))) + 1; + $content = ''; + ksort($headers); + foreach ($headers as $name => $values) { + foreach ($values as $value) { + $content .= sprintf("%-{$max}s %s\r\n", $this->beautifyHeaderName($name).':', $value); + } + } + + return $content; + } + + /** + * Function from Symfony2 HttpFoundation - output pretty header + * + * @param string $name + * @return mixed + */ + private function beautifyHeaderName($name) + { + return preg_replace_callback('/\-(.)/', array($this, 'beautifyCallback'), ucfirst($name)); + } + + /** + * Function from Symfony2 HttpFoundation - output pretty header + * + * @param array $match + * @return string + */ + private function beautifyCallback($match) + { + return '-'.strtoupper($match[1]); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseInterface.php new file mode 100644 index 0000000000..fe920864a9 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseInterface.php @@ -0,0 +1,53 @@ + + */ +class AccessToken implements AccessTokenInterface +{ + /** + * @var AccessTokenInterface + */ + protected $tokenStorage; + + /** + * @var RefreshTokenInterface + */ + protected $refreshStorage; + + /** + * @var array + */ + protected $config; + + /** + * @param AccessTokenStorageInterface $tokenStorage - REQUIRED Storage class for saving access token information + * @param RefreshTokenInterface $refreshStorage - OPTIONAL Storage class for saving refresh token information + * @param array $config - OPTIONAL Configuration options for the server + * @code + * $config = array( + * 'token_type' => 'bearer', // token type identifier + * 'access_lifetime' => 3600, // time before access token expires + * 'refresh_token_lifetime' => 1209600, // time before refresh token expires + * ); + * @endcode + */ + public function __construct(AccessTokenStorageInterface $tokenStorage, RefreshTokenInterface $refreshStorage = null, array $config = array()) + { + $this->tokenStorage = $tokenStorage; + $this->refreshStorage = $refreshStorage; + + $this->config = array_merge(array( + 'token_type' => 'bearer', + 'access_lifetime' => 3600, + 'refresh_token_lifetime' => 1209600, + ), $config); + } + + /** + * Get authorize response + * + * @param array $params + * @param mixed $user_id + * @return array + */ + public function getAuthorizeResponse($params, $user_id = null) + { + // build the URL to redirect to + $result = array('query' => array()); + + $params += array('scope' => null, 'state' => null); + + /* + * a refresh token MUST NOT be included in the fragment + * + * @see http://tools.ietf.org/html/rfc6749#section-4.2.2 + */ + $includeRefreshToken = false; + $result["fragment"] = $this->createAccessToken($params['client_id'], $user_id, $params['scope'], $includeRefreshToken); + + if (isset($params['state'])) { + $result["fragment"]["state"] = $params['state']; + } + + return array($params['redirect_uri'], $result); + } + + /** + * Handle the creation of access token, also issue refresh token if supported / desirable. + * + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user ID associated with the access token + * @param string $scope - OPTIONAL scopes to be stored in space-separated string. + * @param bool $includeRefreshToken - if true, a new refresh_token will be added to the response + * @return array + * + * @see http://tools.ietf.org/html/rfc6749#section-5 + * @ingroup oauth2_section_5 + */ + public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true) + { + $token = array( + "access_token" => $this->generateAccessToken(), + "expires_in" => $this->config['access_lifetime'], + "token_type" => $this->config['token_type'], + "scope" => $scope + ); + + $this->tokenStorage->setAccessToken($token["access_token"], $client_id, $user_id, $this->config['access_lifetime'] ? time() + $this->config['access_lifetime'] : null, $scope); + + /* + * Issue a refresh token also, if we support them + * + * Refresh Tokens are considered supported if an instance of OAuth2\Storage\RefreshTokenInterface + * is supplied in the constructor + */ + if ($includeRefreshToken && $this->refreshStorage) { + $token["refresh_token"] = $this->generateRefreshToken(); + $expires = 0; + if ($this->config['refresh_token_lifetime'] > 0) { + $expires = time() + $this->config['refresh_token_lifetime']; + } + $this->refreshStorage->setRefreshToken($token['refresh_token'], $client_id, $user_id, $expires, $scope); + } + + return $token; + } + + /** + * Generates an unique access token. + * + * Implementing classes may want to override this function to implement + * other access token generation schemes. + * + * @return string - A unique access token. + * + * @ingroup oauth2_section_4 + */ + protected function generateAccessToken() + { + if (function_exists('random_bytes')) { + $randomData = random_bytes(20); + if ($randomData !== false && strlen($randomData) === 20) { + return bin2hex($randomData); + } + } + if (function_exists('openssl_random_pseudo_bytes')) { + $randomData = openssl_random_pseudo_bytes(20); + if ($randomData !== false && strlen($randomData) === 20) { + return bin2hex($randomData); + } + } + if (function_exists('mcrypt_create_iv')) { + $randomData = mcrypt_create_iv(20, MCRYPT_DEV_URANDOM); + if ($randomData !== false && strlen($randomData) === 20) { + return bin2hex($randomData); + } + } + if (@file_exists('/dev/urandom')) { // Get 100 bytes of random data + $randomData = file_get_contents('/dev/urandom', false, null, 0, 20); + if ($randomData !== false && strlen($randomData) === 20) { + return bin2hex($randomData); + } + } + // Last resort which you probably should just get rid of: + $randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true); + + return substr(hash('sha512', $randomData), 0, 40); + } + + /** + * Generates an unique refresh token + * + * Implementing classes may want to override this function to implement + * other refresh token generation schemes. + * + * @return string - A unique refresh token. + * + * @ingroup oauth2_section_4 + * @see OAuth2::generateAccessToken() + */ + protected function generateRefreshToken() + { + return $this->generateAccessToken(); // let's reuse the same scheme for token generation + } + + /** + * Handle the revoking of refresh tokens, and access tokens if supported / desirable + * RFC7009 specifies that "If the server is unable to locate the token using + * the given hint, it MUST extend its search across all of its supported token types" + * + * @param $token + * @param null $tokenTypeHint + * @throws RuntimeException + * @return boolean + */ + public function revokeToken($token, $tokenTypeHint = null) + { + if ($tokenTypeHint == 'refresh_token') { + if ($this->refreshStorage && $revoked = $this->refreshStorage->unsetRefreshToken($token)) { + return true; + } + } + + /** @TODO remove in v2 */ + if (!method_exists($this->tokenStorage, 'unsetAccessToken')) { + throw new RuntimeException( + sprintf('Token storage %s must implement unsetAccessToken method', get_class($this->tokenStorage) + )); + } + + $revoked = $this->tokenStorage->unsetAccessToken($token); + + // if a typehint is supplied and fails, try other storages + // @see https://tools.ietf.org/html/rfc7009#section-2.1 + if (!$revoked && $tokenTypeHint != 'refresh_token') { + if ($this->refreshStorage) { + $revoked = $this->refreshStorage->unsetRefreshToken($token); + } + } + + return $revoked; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AccessTokenInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AccessTokenInterface.php new file mode 100644 index 0000000000..0e576df52f --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AccessTokenInterface.php @@ -0,0 +1,33 @@ + + */ +interface AccessTokenInterface extends ResponseTypeInterface +{ + /** + * Handle the creation of access token, also issue refresh token if supported / desirable. + * + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user ID associated with the access token + * @param string $scope - OPTONAL scopes to be stored in space-separated string. + * @param bool $includeRefreshToken - if true, a new refresh_token will be added to the response + * + * @see http://tools.ietf.org/html/rfc6749#section-5 + * @ingroup oauth2_section_5 + */ + public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true); + + /** + * Handle the revoking of refresh tokens, and access tokens if supported / desirable + * + * @param $token + * @param $tokenTypeHint + * @return mixed + * + * @todo v2.0 include this method in interface. Omitted to maintain BC in v1.x + */ + //public function revokeToken($token, $tokenTypeHint); +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCode.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCode.php new file mode 100644 index 0000000000..b92c73cda2 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCode.php @@ -0,0 +1,101 @@ + + */ +class AuthorizationCode implements AuthorizationCodeInterface +{ + protected $storage; + protected $config; + + public function __construct(AuthorizationCodeStorageInterface $storage, array $config = array()) + { + $this->storage = $storage; + $this->config = array_merge(array( + 'enforce_redirect' => false, + 'auth_code_lifetime' => 30, + ), $config); + } + + public function getAuthorizeResponse($params, $user_id = null) + { + // build the URL to redirect to + $result = array('query' => array()); + + $params += array('scope' => null, 'state' => null); + + $result['query']['code'] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope']); + + if (isset($params['state'])) { + $result['query']['state'] = $params['state']; + } + + return array($params['redirect_uri'], $result); + } + + /** + * Handle the creation of the authorization code. + * + * @param $client_id + * Client identifier related to the authorization code + * @param $user_id + * User ID associated with the authorization code + * @param $redirect_uri + * An absolute URI to which the authorization server will redirect the + * user-agent to when the end-user authorization step is completed. + * @param $scope + * (optional) Scopes to be stored in space-separated string. + * + * @see http://tools.ietf.org/html/rfc6749#section-4 + * @ingroup oauth2_section_4 + */ + public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null) + { + $code = $this->generateAuthorizationCode(); + $this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope); + + return $code; + } + + /** + * @return + * TRUE if the grant type requires a redirect_uri, FALSE if not + */ + public function enforceRedirect() + { + return $this->config['enforce_redirect']; + } + + /** + * Generates an unique auth code. + * + * Implementing classes may want to override this function to implement + * other auth code generation schemes. + * + * @return + * An unique auth code. + * + * @ingroup oauth2_section_4 + */ + protected function generateAuthorizationCode() + { + $tokenLen = 40; + if (function_exists('random_bytes')) { + $randomData = random_bytes(100); + } elseif (function_exists('openssl_random_pseudo_bytes')) { + $randomData = openssl_random_pseudo_bytes(100); + } elseif (function_exists('mcrypt_create_iv')) { + $randomData = mcrypt_create_iv(100, MCRYPT_DEV_URANDOM); + } elseif (@file_exists('/dev/urandom')) { // Get 100 bytes of random data + $randomData = file_get_contents('/dev/urandom', false, null, 0, 100) . uniqid(mt_rand(), true); + } else { + $randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true); + } + + return substr(hash('sha512', $randomData), 0, $tokenLen); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCodeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCodeInterface.php new file mode 100644 index 0000000000..4f0a29df43 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCodeInterface.php @@ -0,0 +1,30 @@ + + */ +interface AuthorizationCodeInterface extends ResponseTypeInterface +{ + /** + * @return + * TRUE if the grant type requires a redirect_uri, FALSE if not + */ + public function enforceRedirect(); + + /** + * Handle the creation of the authorization code. + * + * @param mixed $client_id - Client identifier related to the authorization code + * @param mixed $user_id - User ID associated with the authorization code + * @param string $redirect_uri - An absolute URI to which the authorization server will redirect the + * user-agent to when the end-user authorization step is completed. + * @param string $scope - OPTIONAL Scopes to be stored in space-separated string. + * @return string + * + * @see http://tools.ietf.org/html/rfc6749#section-4 + * @ingroup oauth2_section_4 + */ + public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/JwtAccessToken.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/JwtAccessToken.php new file mode 100644 index 0000000000..0ee3708aaa --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/JwtAccessToken.php @@ -0,0 +1,159 @@ + + */ +class JwtAccessToken extends AccessToken +{ + protected $publicKeyStorage; + protected $encryptionUtil; + + /** + * @param PublicKeyInterface $publicKeyStorage - + * @param AccessTokenStorageInterface $tokenStorage - + * @param RefreshTokenInterface $refreshStorage - + * @param array $config - array with key store_encrypted_token_string (bool true) + * whether the entire encrypted string is stored, + * or just the token ID is stored + * @param EncryptionInterface $encryptionUtil - + */ + public function __construct(PublicKeyInterface $publicKeyStorage = null, AccessTokenStorageInterface $tokenStorage = null, RefreshTokenInterface $refreshStorage = null, array $config = array(), EncryptionInterface $encryptionUtil = null) + { + $this->publicKeyStorage = $publicKeyStorage; + $config = array_merge(array( + 'store_encrypted_token_string' => true, + 'issuer' => '' + ), $config); + if (is_null($tokenStorage)) { + // a pass-thru, so we can call the parent constructor + $tokenStorage = new Memory(); + } + if (is_null($encryptionUtil)) { + $encryptionUtil = new Jwt(); + } + $this->encryptionUtil = $encryptionUtil; + parent::__construct($tokenStorage, $refreshStorage, $config); + } + + /** + * Handle the creation of access token, also issue refresh token if supported / desirable. + * + * @param mixed $client_id - Client identifier related to the access token. + * @param mixed $user_id - User ID associated with the access token + * @param string $scope - (optional) Scopes to be stored in space-separated string. + * @param bool $includeRefreshToken - If true, a new refresh_token will be added to the response + * @return array - The access token + * + * @see http://tools.ietf.org/html/rfc6749#section-5 + * @ingroup oauth2_section_5 + */ + public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true) + { + // payload to encrypt + $payload = $this->createPayload($client_id, $user_id, $scope); + + /* + * Encode the payload data into a single JWT access_token string + */ + $access_token = $this->encodeToken($payload, $client_id); + + /* + * Save the token to a secondary storage. This is implemented on the + * OAuth2\Storage\JwtAccessToken side, and will not actually store anything, + * if no secondary storage has been supplied + */ + $token_to_store = $this->config['store_encrypted_token_string'] ? $access_token : $payload['id']; + $this->tokenStorage->setAccessToken($token_to_store, $client_id, $user_id, $this->config['access_lifetime'] ? time() + $this->config['access_lifetime'] : null, $scope); + + // token to return to the client + $token = array( + 'access_token' => $access_token, + 'expires_in' => $this->config['access_lifetime'], + 'token_type' => $this->config['token_type'], + 'scope' => $scope + ); + + /* + * Issue a refresh token also, if we support them + * + * Refresh Tokens are considered supported if an instance of OAuth2\Storage\RefreshTokenInterface + * is supplied in the constructor + */ + if ($includeRefreshToken && $this->refreshStorage) { + $refresh_token = $this->generateRefreshToken(); + $expires = 0; + if ($this->config['refresh_token_lifetime'] > 0) { + $expires = time() + $this->config['refresh_token_lifetime']; + } + $this->refreshStorage->setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope); + $token['refresh_token'] = $refresh_token; + } + + return $token; + } + + /** + * @param array $token + * @param mixed $client_id + * @return mixed + */ + protected function encodeToken(array $token, $client_id = null) + { + $private_key = $this->publicKeyStorage->getPrivateKey($client_id); + $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id); + + return $this->encryptionUtil->encode($token, $private_key, $algorithm); + } + + /** + * This function can be used to create custom JWT payloads + * + * @param mixed $client_id - Client identifier related to the access token. + * @param mixed $user_id - User ID associated with the access token + * @param string $scope - (optional) Scopes to be stored in space-separated string. + * @return array - The access token + */ + protected function createPayload($client_id, $user_id, $scope = null) + { + // token to encrypt + $expires = time() + $this->config['access_lifetime']; + $id = $this->generateAccessToken(); + + $payload = array( + 'id' => $id, // for BC (see #591) + 'jti' => $id, + 'iss' => $this->config['issuer'], + 'aud' => $client_id, + 'sub' => $user_id, + 'exp' => $expires, + 'iat' => time(), + 'token_type' => $this->config['token_type'], + 'scope' => $scope + ); + + if (isset($this->config['jwt_extra_payload_callable'])) { + if (!is_callable($this->config['jwt_extra_payload_callable'])) { + throw new \InvalidArgumentException('jwt_extra_payload_callable is not callable'); + } + + $extra = call_user_func($this->config['jwt_extra_payload_callable'], $client_id, $user_id, $scope); + + if (!is_array($extra)) { + throw new \InvalidArgumentException('jwt_extra_payload_callable must return array'); + } + + $payload = array_merge($extra, $payload); + } + + return $payload; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/ResponseTypeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/ResponseTypeInterface.php new file mode 100644 index 0000000000..a271565808 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/ResponseTypeInterface.php @@ -0,0 +1,13 @@ +storage = $storage; + } + + /** + * Check if everything in required scope is contained in available scope. + * + * @param string $required_scope - A space-separated string of scopes. + * @param string $available_scope - A space-separated string of scopes. + * @return bool - TRUE if everything in required scope is contained in available scope and FALSE + * if it isn't. + * + * @see http://tools.ietf.org/html/rfc6749#section-7 + * + * @ingroup oauth2_section_7 + */ + public function checkScope($required_scope, $available_scope) + { + $required_scope = explode(' ', trim($required_scope)); + $available_scope = explode(' ', trim($available_scope)); + + return (count(array_diff($required_scope, $available_scope)) == 0); + } + + /** + * Check if the provided scope exists in storage. + * + * @param string $scope - A space-separated string of scopes. + * @return bool - TRUE if it exists, FALSE otherwise. + */ + public function scopeExists($scope) + { + // Check reserved scopes first. + $scope = explode(' ', trim($scope)); + $reservedScope = $this->getReservedScopes(); + $nonReservedScopes = array_diff($scope, $reservedScope); + if (count($nonReservedScopes) == 0) { + return true; + } else { + // Check the storage for non-reserved scopes. + $nonReservedScopes = implode(' ', $nonReservedScopes); + + return $this->storage->scopeExists($nonReservedScopes); + } + } + + /** + * @param RequestInterface $request + * @return string + */ + public function getScopeFromRequest(RequestInterface $request) + { + // "scope" is valid if passed in either POST or QUERY + return $request->request('scope', $request->query('scope')); + } + + /** + * @param null $client_id + * @return mixed + */ + public function getDefaultScope($client_id = null) + { + return $this->storage->getDefaultScope($client_id); + } + + /** + * Get reserved scopes needed by the server. + * + * In case OpenID Connect is used, these scopes must include: + * 'openid', offline_access'. + * + * @return array - An array of reserved scopes. + */ + public function getReservedScopes() + { + return array('openid', 'offline_access'); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ScopeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ScopeInterface.php new file mode 100644 index 0000000000..f65cfa7bd8 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ScopeInterface.php @@ -0,0 +1,35 @@ + 'OAuth2\Storage\AccessTokenInterface', + 'authorization_code' => 'OAuth2\Storage\AuthorizationCodeInterface', + 'client_credentials' => 'OAuth2\Storage\ClientCredentialsInterface', + 'client' => 'OAuth2\Storage\ClientInterface', + 'refresh_token' => 'OAuth2\Storage\RefreshTokenInterface', + 'user_credentials' => 'OAuth2\Storage\UserCredentialsInterface', + 'user_claims' => 'OAuth2\OpenID\Storage\UserClaimsInterface', + 'public_key' => 'OAuth2\Storage\PublicKeyInterface', + 'jwt_bearer' => 'OAuth2\Storage\JWTBearerInterface', + 'scope' => 'OAuth2\Storage\ScopeInterface', + ); + + /** + * @var array + */ + protected $responseTypeMap = array( + 'token' => 'OAuth2\ResponseType\AccessTokenInterface', + 'code' => 'OAuth2\ResponseType\AuthorizationCodeInterface', + 'id_token' => 'OAuth2\OpenID\ResponseType\IdTokenInterface', + 'id_token token' => 'OAuth2\OpenID\ResponseType\IdTokenTokenInterface', + 'code id_token' => 'OAuth2\OpenID\ResponseType\CodeIdTokenInterface', + ); + + /** + * @param mixed $storage (array or OAuth2\Storage) - single object or array of objects implementing the + * required storage types (ClientCredentialsInterface and AccessTokenInterface as a minimum) + * @param array $config specify a different token lifetime, token header name, etc + * @param array $grantTypes An array of OAuth2\GrantType\GrantTypeInterface to use for granting access tokens + * @param array $responseTypes Response types to use. array keys should be "code" and "token" for + * Access Token and Authorization Code response types + * @param TokenTypeInterface $tokenType The token type object to use. Valid token types are "bearer" and "mac" + * @param ScopeInterface $scopeUtil The scope utility class to use to validate scope + * @param ClientAssertionTypeInterface $clientAssertionType The method in which to verify the client identity. Default is HttpBasic + * + * @ingroup oauth2_section_7 + */ + public function __construct($storage = array(), array $config = array(), array $grantTypes = array(), array $responseTypes = array(), TokenTypeInterface $tokenType = null, ScopeInterface $scopeUtil = null, ClientAssertionTypeInterface $clientAssertionType = null) + { + $storage = is_array($storage) ? $storage : array($storage); + $this->storages = array(); + foreach ($storage as $key => $service) { + $this->addStorage($service, $key); + } + + // merge all config values. These get passed to our controller objects + $this->config = array_merge(array( + 'use_jwt_access_tokens' => false, + 'jwt_extra_payload_callable' => null, + 'store_encrypted_token_string' => true, + 'use_openid_connect' => false, + 'id_lifetime' => 3600, + 'access_lifetime' => 3600, + 'www_realm' => 'Service', + 'token_param_name' => 'access_token', + 'token_bearer_header_name' => 'Bearer', + 'enforce_state' => true, + 'require_exact_redirect_uri' => true, + 'allow_implicit' => false, + 'allow_credentials_in_request_body' => true, + 'allow_public_clients' => true, + 'always_issue_new_refresh_token' => false, + 'unset_refresh_token_after_use' => true, + ), $config); + + foreach ($grantTypes as $key => $grantType) { + $this->addGrantType($grantType, $key); + } + + foreach ($responseTypes as $key => $responseType) { + $this->addResponseType($responseType, $key); + } + + $this->tokenType = $tokenType; + $this->scopeUtil = $scopeUtil; + $this->clientAssertionType = $clientAssertionType; + + if ($this->config['use_openid_connect']) { + $this->validateOpenIdConnect(); + } + } + + /** + * @return AuthorizeControllerInterface + */ + public function getAuthorizeController() + { + if (is_null($this->authorizeController)) { + $this->authorizeController = $this->createDefaultAuthorizeController(); + } + + return $this->authorizeController; + } + + /** + * @return TokenController + */ + public function getTokenController() + { + if (is_null($this->tokenController)) { + $this->tokenController = $this->createDefaultTokenController(); + } + + return $this->tokenController; + } + + /** + * @return ResourceControllerInterface + */ + public function getResourceController() + { + if (is_null($this->resourceController)) { + $this->resourceController = $this->createDefaultResourceController(); + } + + return $this->resourceController; + } + + /** + * @return UserInfoControllerInterface + */ + public function getUserInfoController() + { + if (is_null($this->userInfoController)) { + $this->userInfoController = $this->createDefaultUserInfoController(); + } + + return $this->userInfoController; + } + + /** + * @param AuthorizeControllerInterface $authorizeController + */ + public function setAuthorizeController(AuthorizeControllerInterface $authorizeController) + { + $this->authorizeController = $authorizeController; + } + + /** + * @param TokenControllerInterface $tokenController + */ + public function setTokenController(TokenControllerInterface $tokenController) + { + $this->tokenController = $tokenController; + } + + /** + * @param ResourceControllerInterface $resourceController + */ + public function setResourceController(ResourceControllerInterface $resourceController) + { + $this->resourceController = $resourceController; + } + + /** + * @param UserInfoControllerInterface $userInfoController + */ + public function setUserInfoController(UserInfoControllerInterface $userInfoController) + { + $this->userInfoController = $userInfoController; + } + + /** + * Return claims about the authenticated end-user. + * This would be called from the "/UserInfo" endpoint as defined in the spec. + * + * @param RequestInterface $request - Request object to grant access token + * @param ResponseInterface $response - Response object containing error messages (failure) or user claims (success) + * @return ResponseInterface + * + * @throws \InvalidArgumentException + * @throws \LogicException + * + * @see http://openid.net/specs/openid-connect-core-1_0.html#UserInfo + */ + public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response = null) + { + $this->response = is_null($response) ? new Response() : $response; + $this->getUserInfoController()->handleUserInfoRequest($request, $this->response); + + return $this->response; + } + + /** + * Grant or deny a requested access token. + * This would be called from the "/token" endpoint as defined in the spec. + * Obviously, you can call your endpoint whatever you want. + * + * @param RequestInterface $request - Request object to grant access token + * @param ResponseInterface $response - Response object containing error messages (failure) or access token (success) + * @return ResponseInterface + * + * @throws \InvalidArgumentException + * @throws \LogicException + * + * @see http://tools.ietf.org/html/rfc6749#section-4 + * @see http://tools.ietf.org/html/rfc6749#section-10.6 + * @see http://tools.ietf.org/html/rfc6749#section-4.1.3 + * + * @ingroup oauth2_section_4 + */ + public function handleTokenRequest(RequestInterface $request, ResponseInterface $response = null) + { + $this->response = is_null($response) ? new Response() : $response; + $this->getTokenController()->handleTokenRequest($request, $this->response); + + return $this->response; + } + + /** + * @param RequestInterface $request - Request object to grant access token + * @param ResponseInterface $response - Response object + * @return mixed + */ + public function grantAccessToken(RequestInterface $request, ResponseInterface $response = null) + { + $this->response = is_null($response) ? new Response() : $response; + $value = $this->getTokenController()->grantAccessToken($request, $this->response); + + return $value; + } + + /** + * Handle a revoke token request + * This would be called from the "/revoke" endpoint as defined in the draft Token Revocation spec + * + * @see https://tools.ietf.org/html/rfc7009#section-2 + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return Response|ResponseInterface + */ + public function handleRevokeRequest(RequestInterface $request, ResponseInterface $response = null) + { + $this->response = is_null($response) ? new Response() : $response; + $this->getTokenController()->handleRevokeRequest($request, $this->response); + + return $this->response; + } + + /** + * Redirect the user appropriately after approval. + * + * After the user has approved or denied the resource request the + * authorization server should call this function to redirect the user + * appropriately. + * + * @param RequestInterface $request - The request should have the follow parameters set in the querystring: + * - response_type: The requested response: an access token, an authorization code, or both. + * - client_id: The client identifier as described in Section 2. + * - redirect_uri: An absolute URI to which the authorization server will redirect the user-agent to when the + * end-user authorization step is completed. + * - scope: (optional) The scope of the resource request expressed as a list of space-delimited strings. + * - state: (optional) An opaque value used by the client to maintain state between the request and callback. + * + * @param ResponseInterface $response - Response object + * @param bool $is_authorized - TRUE or FALSE depending on whether the user authorized the access. + * @param mixed $user_id - Identifier of user who authorized the client + * @return ResponseInterface + * + * @see http://tools.ietf.org/html/rfc6749#section-4 + * + * @ingroup oauth2_section_4 + */ + public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null) + { + $this->response = $response; + $this->getAuthorizeController()->handleAuthorizeRequest($request, $this->response, $is_authorized, $user_id); + + return $this->response; + } + + /** + * Pull the authorization request data out of the HTTP request. + * - The redirect_uri is OPTIONAL as per draft 20. But your implementation can enforce it + * by setting $config['enforce_redirect'] to true. + * - The state is OPTIONAL but recommended to enforce CSRF. Draft 21 states, however, that + * CSRF protection is MANDATORY. You can enforce this by setting the $config['enforce_state'] to true. + * + * The draft specifies that the parameters should be retrieved from GET, override the Response + * object to change this + * + * @param RequestInterface $request - Request object + * @param ResponseInterface $response - Response object + * @return bool + * + * The authorization parameters so the authorization server can prompt + * the user for approval if valid. + * + * @see http://tools.ietf.org/html/rfc6749#section-4.1.1 + * @see http://tools.ietf.org/html/rfc6749#section-10.12 + * + * @ingroup oauth2_section_3 + */ + public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response = null) + { + $this->response = is_null($response) ? new Response() : $response; + $value = $this->getAuthorizeController()->validateAuthorizeRequest($request, $this->response); + + return $value; + } + + /** + * @param RequestInterface $request - Request object + * @param ResponseInterface $response - Response object + * @param string $scope - Scope + * @return mixed + */ + public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response = null, $scope = null) + { + $this->response = is_null($response) ? new Response() : $response; + $value = $this->getResourceController()->verifyResourceRequest($request, $this->response, $scope); + + return $value; + } + + /** + * @param RequestInterface $request - Request object + * @param ResponseInterface $response - Response object + * @return mixed + */ + public function getAccessTokenData(RequestInterface $request, ResponseInterface $response = null) + { + $this->response = is_null($response) ? new Response() : $response; + $value = $this->getResourceController()->getAccessTokenData($request, $this->response); + + return $value; + } + + /** + * @param GrantTypeInterface $grantType + * @param mixed $identifier + */ + public function addGrantType(GrantTypeInterface $grantType, $identifier = null) + { + if (!is_string($identifier)) { + $identifier = $grantType->getQueryStringIdentifier(); + } + + $this->grantTypes[$identifier] = $grantType; + + // persist added grant type down to TokenController + if (!is_null($this->tokenController)) { + $this->getTokenController()->addGrantType($grantType, $identifier); + } + } + + /** + * Set a storage object for the server + * + * @param object $storage - An object implementing one of the Storage interfaces + * @param mixed $key - If null, the storage is set to the key of each storage interface it implements + * + * @throws InvalidArgumentException + * @see storageMap + */ + public function addStorage($storage, $key = null) + { + // if explicitly set to a valid key, do not "magically" set below + if (isset($this->storageMap[$key])) { + if (!is_null($storage) && !$storage instanceof $this->storageMap[$key]) { + throw new \InvalidArgumentException(sprintf('storage of type "%s" must implement interface "%s"', $key, $this->storageMap[$key])); + } + $this->storages[$key] = $storage; + + // special logic to handle "client" and "client_credentials" strangeness + if ($key === 'client' && !isset($this->storages['client_credentials'])) { + if ($storage instanceof ClientCredentialsInterface) { + $this->storages['client_credentials'] = $storage; + } + } elseif ($key === 'client_credentials' && !isset($this->storages['client'])) { + if ($storage instanceof ClientInterface) { + $this->storages['client'] = $storage; + } + } + } elseif (!is_null($key) && !is_numeric($key)) { + throw new \InvalidArgumentException(sprintf('unknown storage key "%s", must be one of [%s]', $key, implode(', ', array_keys($this->storageMap)))); + } else { + $set = false; + foreach ($this->storageMap as $type => $interface) { + if ($storage instanceof $interface) { + $this->storages[$type] = $storage; + $set = true; + } + } + + if (!$set) { + throw new \InvalidArgumentException(sprintf('storage of class "%s" must implement one of [%s]', get_class($storage), implode(', ', $this->storageMap))); + } + } + } + + /** + * @param ResponseTypeInterface $responseType + * @param mixed $key + * + * @throws InvalidArgumentException + */ + public function addResponseType(ResponseTypeInterface $responseType, $key = null) + { + $key = $this->normalizeResponseType($key); + + if (isset($this->responseTypeMap[$key])) { + if (!$responseType instanceof $this->responseTypeMap[$key]) { + throw new \InvalidArgumentException(sprintf('responseType of type "%s" must implement interface "%s"', $key, $this->responseTypeMap[$key])); + } + $this->responseTypes[$key] = $responseType; + } elseif (!is_null($key) && !is_numeric($key)) { + throw new \InvalidArgumentException(sprintf('unknown responseType key "%s", must be one of [%s]', $key, implode(', ', array_keys($this->responseTypeMap)))); + } else { + $set = false; + foreach ($this->responseTypeMap as $type => $interface) { + if ($responseType instanceof $interface) { + $this->responseTypes[$type] = $responseType; + $set = true; + } + } + + if (!$set) { + throw new \InvalidArgumentException(sprintf('Unknown response type %s. Please implement one of [%s]', get_class($responseType), implode(', ', $this->responseTypeMap))); + } + } + } + + /** + * @return ScopeInterface + */ + public function getScopeUtil() + { + if (!$this->scopeUtil) { + $storage = isset($this->storages['scope']) ? $this->storages['scope'] : null; + $this->scopeUtil = new Scope($storage); + } + + return $this->scopeUtil; + } + + /** + * @param ScopeInterface $scopeUtil + */ + public function setScopeUtil($scopeUtil) + { + $this->scopeUtil = $scopeUtil; + } + + /** + * @return AuthorizeControllerInterface + * @throws LogicException + */ + protected function createDefaultAuthorizeController() + { + if (!isset($this->storages['client'])) { + throw new \LogicException('You must supply a storage object implementing \OAuth2\Storage\ClientInterface to use the authorize server'); + } + if (0 == count($this->responseTypes)) { + $this->responseTypes = $this->getDefaultResponseTypes(); + } + if ($this->config['use_openid_connect'] && !isset($this->responseTypes['id_token'])) { + $this->responseTypes['id_token'] = $this->createDefaultIdTokenResponseType(); + if ($this->config['allow_implicit']) { + $this->responseTypes['id_token token'] = $this->createDefaultIdTokenTokenResponseType(); + } + } + + $config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_implicit enforce_state require_exact_redirect_uri'))); + + if ($this->config['use_openid_connect']) { + return new OpenIDAuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->getScopeUtil()); + } + + return new AuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->getScopeUtil()); + } + + /** + * @return TokenControllerInterface + * @throws LogicException + */ + protected function createDefaultTokenController() + { + if (0 == count($this->grantTypes)) { + $this->grantTypes = $this->getDefaultGrantTypes(); + } + + if (is_null($this->clientAssertionType)) { + // see if HttpBasic assertion type is requred. If so, then create it from storage classes. + foreach ($this->grantTypes as $grantType) { + if (!$grantType instanceof ClientAssertionTypeInterface) { + if (!isset($this->storages['client_credentials'])) { + throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\ClientCredentialsInterface to use the token server'); + } + $config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_credentials_in_request_body allow_public_clients'))); + $this->clientAssertionType = new HttpBasic($this->storages['client_credentials'], $config); + break; + } + } + } + + if (!isset($this->storages['client'])) { + throw new LogicException("You must supply a storage object implementing OAuth2\Storage\ClientInterface to use the token server"); + } + + $accessTokenResponseType = $this->getAccessTokenResponseType(); + + return new TokenController($accessTokenResponseType, $this->storages['client'], $this->grantTypes, $this->clientAssertionType, $this->getScopeUtil()); + } + + /** + * @return ResourceControllerInterface + * @throws LogicException + */ + protected function createDefaultResourceController() + { + if ($this->config['use_jwt_access_tokens']) { + // overwrites access token storage with crypto token storage if "use_jwt_access_tokens" is set + if (!isset($this->storages['access_token']) || !$this->storages['access_token'] instanceof JwtAccessTokenInterface) { + $this->storages['access_token'] = $this->createDefaultJwtAccessTokenStorage(); + } + } elseif (!isset($this->storages['access_token'])) { + throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\AccessTokenInterface or use JwtAccessTokens to use the resource server'); + } + + if (!$this->tokenType) { + $this->tokenType = $this->getDefaultTokenType(); + } + + $config = array_intersect_key($this->config, array('www_realm' => '')); + + return new ResourceController($this->tokenType, $this->storages['access_token'], $config, $this->getScopeUtil()); + } + + /** + * @return UserInfoControllerInterface + * @throws LogicException + */ + protected function createDefaultUserInfoController() + { + if ($this->config['use_jwt_access_tokens']) { + // overwrites access token storage with crypto token storage if "use_jwt_access_tokens" is set + if (!isset($this->storages['access_token']) || !$this->storages['access_token'] instanceof JwtAccessTokenInterface) { + $this->storages['access_token'] = $this->createDefaultJwtAccessTokenStorage(); + } + } elseif (!isset($this->storages['access_token'])) { + throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\AccessTokenInterface or use JwtAccessTokens to use the UserInfo server'); + } + + if (!isset($this->storages['user_claims'])) { + throw new \LogicException('You must supply a storage object implementing OAuth2\OpenID\Storage\UserClaimsInterface to use the UserInfo server'); + } + + if (!$this->tokenType) { + $this->tokenType = $this->getDefaultTokenType(); + } + + $config = array_intersect_key($this->config, array('www_realm' => '')); + + return new UserInfoController($this->tokenType, $this->storages['access_token'], $this->storages['user_claims'], $config, $this->getScopeUtil()); + } + + /** + * @return Bearer + */ + protected function getDefaultTokenType() + { + $config = array_intersect_key($this->config, array_flip(explode(' ', 'token_param_name token_bearer_header_name'))); + + return new Bearer($config); + } + + /** + * @return array + * @throws LogicException + */ + protected function getDefaultResponseTypes() + { + $responseTypes = array(); + + if ($this->config['allow_implicit']) { + $responseTypes['token'] = $this->getAccessTokenResponseType(); + } + + if ($this->config['use_openid_connect']) { + $responseTypes['id_token'] = $this->getIdTokenResponseType(); + if ($this->config['allow_implicit']) { + $responseTypes['id_token token'] = $this->getIdTokenTokenResponseType(); + } + } + + if (isset($this->storages['authorization_code'])) { + $config = array_intersect_key($this->config, array_flip(explode(' ', 'enforce_redirect auth_code_lifetime'))); + if ($this->config['use_openid_connect']) { + if (!$this->storages['authorization_code'] instanceof OpenIDAuthorizationCodeInterface) { + throw new \LogicException('Your authorization_code storage must implement OAuth2\OpenID\Storage\AuthorizationCodeInterface to work when "use_openid_connect" is true'); + } + $responseTypes['code'] = new OpenIDAuthorizationCodeResponseType($this->storages['authorization_code'], $config); + $responseTypes['code id_token'] = new CodeIdToken($responseTypes['code'], $responseTypes['id_token']); + } else { + $responseTypes['code'] = new AuthorizationCodeResponseType($this->storages['authorization_code'], $config); + } + } + + if (count($responseTypes) == 0) { + throw new \LogicException('You must supply an array of response_types in the constructor or implement a OAuth2\Storage\AuthorizationCodeInterface storage object or set "allow_implicit" to true and implement a OAuth2\Storage\AccessTokenInterface storage object'); + } + + return $responseTypes; + } + + /** + * @return array + * @throws LogicException + */ + protected function getDefaultGrantTypes() + { + $grantTypes = array(); + + if (isset($this->storages['user_credentials'])) { + $grantTypes['password'] = new UserCredentials($this->storages['user_credentials']); + } + + if (isset($this->storages['client_credentials'])) { + $config = array_intersect_key($this->config, array('allow_credentials_in_request_body' => '')); + $grantTypes['client_credentials'] = new ClientCredentials($this->storages['client_credentials'], $config); + } + + if (isset($this->storages['refresh_token'])) { + $config = array_intersect_key($this->config, array_flip(explode(' ', 'always_issue_new_refresh_token unset_refresh_token_after_use'))); + $grantTypes['refresh_token'] = new RefreshToken($this->storages['refresh_token'], $config); + } + + if (isset($this->storages['authorization_code'])) { + if ($this->config['use_openid_connect']) { + if (!$this->storages['authorization_code'] instanceof OpenIDAuthorizationCodeInterface) { + throw new \LogicException('Your authorization_code storage must implement OAuth2\OpenID\Storage\AuthorizationCodeInterface to work when "use_openid_connect" is true'); + } + $grantTypes['authorization_code'] = new OpenIDAuthorizationCodeGrantType($this->storages['authorization_code']); + } else { + $grantTypes['authorization_code'] = new AuthorizationCode($this->storages['authorization_code']); + } + } + + if (count($grantTypes) == 0) { + throw new \LogicException('Unable to build default grant types - You must supply an array of grant_types in the constructor'); + } + + return $grantTypes; + } + + /** + * @return AccessToken + */ + protected function getAccessTokenResponseType() + { + if (isset($this->responseTypes['token'])) { + return $this->responseTypes['token']; + } + + if ($this->config['use_jwt_access_tokens']) { + return $this->createDefaultJwtAccessTokenResponseType(); + } + + return $this->createDefaultAccessTokenResponseType(); + } + + /** + * @return IdToken + */ + protected function getIdTokenResponseType() + { + if (isset($this->responseTypes['id_token'])) { + return $this->responseTypes['id_token']; + } + + return $this->createDefaultIdTokenResponseType(); + } + + /** + * @return IdTokenToken + */ + protected function getIdTokenTokenResponseType() + { + if (isset($this->responseTypes['id_token token'])) { + return $this->responseTypes['id_token token']; + } + + return $this->createDefaultIdTokenTokenResponseType(); + } + + /** + * For Resource Controller + * + * @return JwtAccessTokenStorage + * @throws LogicException + */ + protected function createDefaultJwtAccessTokenStorage() + { + if (!isset($this->storages['public_key'])) { + throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use crypto tokens'); + } + $tokenStorage = null; + if (!empty($this->config['store_encrypted_token_string']) && isset($this->storages['access_token'])) { + $tokenStorage = $this->storages['access_token']; + } + // wrap the access token storage as required. + return new JwtAccessTokenStorage($this->storages['public_key'], $tokenStorage); + } + + /** + * For Authorize and Token Controllers + * + * @return JwtAccessToken + * @throws LogicException + */ + protected function createDefaultJwtAccessTokenResponseType() + { + if (!isset($this->storages['public_key'])) { + throw new \LogicException('You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use crypto tokens'); + } + + $tokenStorage = null; + if (isset($this->storages['access_token'])) { + $tokenStorage = $this->storages['access_token']; + } + + $refreshStorage = null; + if (isset($this->storages['refresh_token'])) { + $refreshStorage = $this->storages['refresh_token']; + } + + $config = array_intersect_key($this->config, array_flip(explode(' ', 'store_encrypted_token_string issuer access_lifetime refresh_token_lifetime jwt_extra_payload_callable'))); + + return new JwtAccessToken($this->storages['public_key'], $tokenStorage, $refreshStorage, $config); + } + + /** + * @return AccessToken + * @throws LogicException + */ + protected function createDefaultAccessTokenResponseType() + { + if (!isset($this->storages['access_token'])) { + throw new LogicException("You must supply a response type implementing OAuth2\ResponseType\AccessTokenInterface, or a storage object implementing OAuth2\Storage\AccessTokenInterface to use the token server"); + } + + $refreshStorage = null; + if (isset($this->storages['refresh_token'])) { + $refreshStorage = $this->storages['refresh_token']; + } + + $config = array_intersect_key($this->config, array_flip(explode(' ', 'access_lifetime refresh_token_lifetime'))); + $config['token_type'] = $this->tokenType ? $this->tokenType->getTokenType() : $this->getDefaultTokenType()->getTokenType(); + + return new AccessToken($this->storages['access_token'], $refreshStorage, $config); + } + + /** + * @return IdToken + * @throws LogicException + */ + protected function createDefaultIdTokenResponseType() + { + if (!isset($this->storages['user_claims'])) { + throw new LogicException("You must supply a storage object implementing OAuth2\OpenID\Storage\UserClaimsInterface to use openid connect"); + } + if (!isset($this->storages['public_key'])) { + throw new LogicException("You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use openid connect"); + } + + $config = array_intersect_key($this->config, array_flip(explode(' ', 'issuer id_lifetime'))); + + return new IdToken($this->storages['user_claims'], $this->storages['public_key'], $config); + } + + /** + * @return IdTokenToken + */ + protected function createDefaultIdTokenTokenResponseType() + { + return new IdTokenToken($this->getAccessTokenResponseType(), $this->getIdTokenResponseType()); + } + + /** + * @throws InvalidArgumentException + */ + protected function validateOpenIdConnect() + { + $authCodeGrant = $this->getGrantType('authorization_code'); + if (!empty($authCodeGrant) && !$authCodeGrant instanceof OpenIDAuthorizationCodeGrantType) { + throw new \InvalidArgumentException('You have enabled OpenID Connect, but supplied a grant type that does not support it.'); + } + } + + /** + * @param string $name + * @return string + */ + protected function normalizeResponseType($name) + { + // for multiple-valued response types - make them alphabetical + if (!empty($name) && false !== strpos($name, ' ')) { + $types = explode(' ', $name); + sort($types); + $name = implode(' ', $types); + } + + return $name; + } + + /** + * @return mixed + */ + public function getResponse() + { + return $this->response; + } + + /** + * @return array + */ + public function getStorages() + { + return $this->storages; + } + + /** + * @param string $name + * @return object|null + */ + public function getStorage($name) + { + return isset($this->storages[$name]) ? $this->storages[$name] : null; + } + + /** + * @return array + */ + public function getGrantTypes() + { + return $this->grantTypes; + } + + /** + * @param string $name + * @return object|null + */ + public function getGrantType($name) + { + return isset($this->grantTypes[$name]) ? $this->grantTypes[$name] : null; + } + + /** + * @return array + */ + public function getResponseTypes() + { + return $this->responseTypes; + } + + /** + * @param string $name + * @return object|null + */ + public function getResponseType($name) + { + // for multiple-valued response types - make them alphabetical + $name = $this->normalizeResponseType($name); + + return isset($this->responseTypes[$name]) ? $this->responseTypes[$name] : null; + } + + /** + * @return TokenTypeInterface + */ + public function getTokenType() + { + return $this->tokenType; + } + + /** + * @return ClientAssertionTypeInterface + */ + public function getClientAssertionType() + { + return $this->clientAssertionType; + } + + /** + * @param string $name + * @param mixed $value + */ + public function setConfig($name, $value) + { + $this->config[$name] = $value; + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function getConfig($name, $default = null) + { + return isset($this->config[$name]) ? $this->config[$name] : $default; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/AccessTokenInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/AccessTokenInterface.php new file mode 100644 index 0000000000..22428f2c8a --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/AccessTokenInterface.php @@ -0,0 +1,65 @@ + + */ +interface AccessTokenInterface +{ + /** + * Look up the supplied oauth_token from storage. + * + * We need to retrieve access token data as we create and verify tokens. + * + * @param string $oauth_token - oauth_token to be check with. + * + * @return array|null - An associative array as below, and return NULL if the supplied oauth_token is invalid: + * @code + * array( + * 'expires' => $expires, // Stored expiration in unix timestamp. + * 'client_id' => $client_id, // (optional) Stored client identifier. + * 'user_id' => $user_id, // (optional) Stored user identifier. + * 'scope' => $scope, // (optional) Stored scope values in space-separated string. + * 'id_token' => $id_token // (optional) Stored id_token (if "use_openid_connect" is true). + * ); + * @endcode + * + * @ingroup oauth2_section_7 + */ + public function getAccessToken($oauth_token); + + /** + * Store the supplied access token values to storage. + * + * We need to store access token data as we create and verify tokens. + * + * @param string $oauth_token - oauth_token to be stored. + * @param mixed $client_id - client identifier to be stored. + * @param mixed $user_id - user identifier to be stored. + * @param int $expires - expiration to be stored as a Unix timestamp. + * @param string $scope - OPTIONAL Scopes to be stored in space-separated string. + * + * @ingroup oauth2_section_4 + */ + public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = null); + + /** + * Expire an access token. + * + * This is not explicitly required in the spec, but if defined in a draft RFC for token + * revoking (RFC 7009) https://tools.ietf.org/html/rfc7009 + * + * @param $access_token + * Access token to be expired. + * + * @return BOOL true if an access token was unset, false if not + * @ingroup oauth2_section_6 + * + * @todo v2.0 include this method in interface. Omitted to maintain BC in v1.x + */ + //public function unsetAccessToken($access_token); +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/AuthorizationCodeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/AuthorizationCodeInterface.php new file mode 100644 index 0000000000..2dbc138ac9 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/AuthorizationCodeInterface.php @@ -0,0 +1,86 @@ + + */ +interface AuthorizationCodeInterface +{ + /** + * The Authorization Code grant type supports a response type of "code". + * + * @var string + * @see http://tools.ietf.org/html/rfc6749#section-1.4.1 + * @see http://tools.ietf.org/html/rfc6749#section-4.2 + */ + const RESPONSE_TYPE_CODE = "code"; + + /** + * Fetch authorization code data (probably the most common grant type). + * + * Retrieve the stored data for the given authorization code. + * + * Required for OAuth2::GRANT_TYPE_AUTH_CODE. + * + * @param $code + * Authorization code to be check with. + * + * @return + * An associative array as below, and NULL if the code is invalid + * @code + * return array( + * "client_id" => CLIENT_ID, // REQUIRED Stored client identifier + * "user_id" => USER_ID, // REQUIRED Stored user identifier + * "expires" => EXPIRES, // REQUIRED Stored expiration in unix timestamp + * "redirect_uri" => REDIRECT_URI, // REQUIRED Stored redirect URI + * "scope" => SCOPE, // OPTIONAL Stored scope values in space-separated string + * ); + * @endcode + * + * @see http://tools.ietf.org/html/rfc6749#section-4.1 + * + * @ingroup oauth2_section_4 + */ + public function getAuthorizationCode($code); + + /** + * Take the provided authorization code values and store them somewhere. + * + * This function should be the storage counterpart to getAuthCode(). + * + * If storage fails for some reason, we're not currently checking for + * any sort of success/failure, so you should bail out of the script + * and provide a descriptive fail message. + * + * Required for OAuth2::GRANT_TYPE_AUTH_CODE. + * + * @param string $code - Authorization code to be stored. + * @param mixed $client_id - Client identifier to be stored. + * @param mixed $user_id - User identifier to be stored. + * @param string $redirect_uri - Redirect URI(s) to be stored in a space-separated string. + * @param int $expires - Expiration to be stored as a Unix timestamp. + * @param string $scope - OPTIONAL Scopes to be stored in space-separated string. + * + * @ingroup oauth2_section_4 + */ + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null); + + /** + * once an Authorization Code is used, it must be expired + * + * @see http://tools.ietf.org/html/rfc6749#section-4.1.2 + * + * The client MUST NOT use the authorization code + * more than once. If an authorization code is used more than + * once, the authorization server MUST deny the request and SHOULD + * revoke (when possible) all tokens previously issued based on + * that authorization code + * + */ + public function expireAuthorizationCode($code); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Cassandra.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Cassandra.php new file mode 100644 index 0000000000..e60e9d3ad8 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Cassandra.php @@ -0,0 +1,660 @@ + + * composer require thobbs/phpcassa:dev-master + * + * + * Once this is done, instantiate the connection: + * + * $cassandra = new \phpcassa\Connection\ConnectionPool('oauth2_server', array('127.0.0.1:9160')); + * + * + * Then, register the storage client: + * + * $storage = new OAuth2\Storage\Cassandra($cassandra); + * $storage->setClientDetails($client_id, $client_secret, $redirect_uri); + * + * + * @see test/lib/OAuth2/Storage/Bootstrap::getCassandraStorage + */ +class Cassandra implements AuthorizationCodeInterface, + AccessTokenInterface, + ClientCredentialsInterface, + UserCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + ScopeInterface, + PublicKeyInterface, + UserClaimsInterface, + OpenIDAuthorizationCodeInterface +{ + + private $cache; + + /** + * @var ConnectionPool + */ + protected $cassandra; + + /** + * @var array + */ + protected $config; + + /** + * Cassandra Storage! uses phpCassa + * + * @param ConnectionPool|array $connection + * @param array $config + * + * @throws InvalidArgumentException + */ + public function __construct($connection = array(), array $config = array()) + { + if ($connection instanceof ConnectionPool) { + $this->cassandra = $connection; + } else { + if (!is_array($connection)) { + throw new InvalidArgumentException('First argument to OAuth2\Storage\Cassandra must be an instance of phpcassa\Connection\ConnectionPool or a configuration array'); + } + $connection = array_merge(array( + 'keyspace' => 'oauth2', + 'servers' => null, + ), $connection); + + $this->cassandra = new ConnectionPool($connection['keyspace'], $connection['servers']); + } + + $this->config = array_merge(array( + // cassandra config + 'column_family' => 'auth', + + // key names + 'client_key' => 'oauth_clients:', + 'access_token_key' => 'oauth_access_tokens:', + 'refresh_token_key' => 'oauth_refresh_tokens:', + 'code_key' => 'oauth_authorization_codes:', + 'user_key' => 'oauth_users:', + 'jwt_key' => 'oauth_jwt:', + 'scope_key' => 'oauth_scopes:', + 'public_key_key' => 'oauth_public_keys:', + ), $config); + } + + /** + * @param $key + * @return bool|mixed + */ + protected function getValue($key) + { + if (isset($this->cache[$key])) { + return $this->cache[$key]; + } + $cf = new ColumnFamily($this->cassandra, $this->config['column_family']); + + try { + $value = $cf->get($key, new ColumnSlice("", "")); + $value = array_shift($value); + } catch (\cassandra\NotFoundException $e) { + return false; + } + + return json_decode($value, true); + } + + /** + * @param $key + * @param $value + * @param int $expire + * @return bool + */ + protected function setValue($key, $value, $expire = 0) + { + $this->cache[$key] = $value; + + $cf = new ColumnFamily($this->cassandra, $this->config['column_family']); + + $str = json_encode($value); + if ($expire > 0) { + try { + $seconds = $expire - time(); + // __data key set as C* requires a field, note: max TTL can only be 630720000 seconds + $cf->insert($key, array('__data' => $str), null, $seconds); + } catch (\Exception $e) { + return false; + } + } else { + try { + // __data key set as C* requires a field + $cf->insert($key, array('__data' => $str)); + } catch (\Exception $e) { + return false; + } + } + + return true; + } + + /** + * @param $key + * @return bool + */ + protected function expireValue($key) + { + unset($this->cache[$key]); + + $cf = new ColumnFamily($this->cassandra, $this->config['column_family']); + + if ($cf->get_count($key) > 0) { + try { + // __data key set as C* requires a field + $cf->remove($key, array('__data')); + } catch (\Exception $e) { + return false; + } + + return true; + } + + return false; + } + + /** + * @param string $code + * @return bool|mixed + */ + public function getAuthorizationCode($code) + { + return $this->getValue($this->config['code_key'] . $code); + } + + /** + * @param string $authorization_code + * @param mixed $client_id + * @param mixed $user_id + * @param string $redirect_uri + * @param int $expires + * @param string $scope + * @param string $id_token + * @return bool + */ + public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + return $this->setValue( + $this->config['code_key'] . $authorization_code, + compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'), + $expires + ); + } + + /** + * @param string $code + * @return bool + */ + public function expireAuthorizationCode($code) + { + $key = $this->config['code_key'] . $code; + unset($this->cache[$key]); + + return $this->expireValue($key); + } + + /** + * @param string $username + * @param string $password + * @return bool + */ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + + return false; + } + + /** + * plaintext passwords are bad! Override this for your application + * + * @param array $user + * @param string $password + * @return bool + */ + protected function checkPassword($user, $password) + { + return $user['password'] == $this->hashPassword($password); + } + + // use a secure hashing algorithm when storing passwords. Override this for your application + protected function hashPassword($password) + { + return sha1($password); + } + + /** + * @param string $username + * @return array|bool|false + */ + public function getUserDetails($username) + { + return $this->getUser($username); + } + + /** + * @param string $username + * @return array|bool + */ + public function getUser($username) + { + if (!$userInfo = $this->getValue($this->config['user_key'] . $username)) { + return false; + } + + // the default behavior is to use "username" as the user_id + return array_merge(array( + 'user_id' => $username, + ), $userInfo); + } + + /** + * @param string $username + * @param string $password + * @param string $first_name + * @param string $last_name + * @return bool + */ + public function setUser($username, $password, $first_name = null, $last_name = null) + { + $password = $this->hashPassword($password); + + return $this->setValue( + $this->config['user_key'] . $username, + compact('username', 'password', 'first_name', 'last_name') + ); + } + + /** + * @param mixed $client_id + * @param string $client_secret + * @return bool + */ + public function checkClientCredentials($client_id, $client_secret = null) + { + if (!$client = $this->getClientDetails($client_id)) { + return false; + } + + return isset($client['client_secret']) + && $client['client_secret'] == $client_secret; + } + + /** + * @param $client_id + * @return bool + */ + public function isPublicClient($client_id) + { + if (!$client = $this->getClientDetails($client_id)) { + return false; + } + + return empty($client['client_secret']); + } + + /** + * @param $client_id + * @return array|bool|mixed + */ + public function getClientDetails($client_id) + { + return $this->getValue($this->config['client_key'] . $client_id); + } + + /** + * @param $client_id + * @param null $client_secret + * @param null $redirect_uri + * @param null $grant_types + * @param null $scope + * @param null $user_id + * @return bool + */ + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + return $this->setValue( + $this->config['client_key'] . $client_id, + compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id') + ); + } + + /** + * @param $client_id + * @param $grant_type + * @return bool + */ + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + + return in_array($grant_type, (array) $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + /** + * @param $refresh_token + * @return bool|mixed + */ + public function getRefreshToken($refresh_token) + { + return $this->getValue($this->config['refresh_token_key'] . $refresh_token); + } + + /** + * @param $refresh_token + * @param $client_id + * @param $user_id + * @param $expires + * @param null $scope + * @return bool + */ + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + return $this->setValue( + $this->config['refresh_token_key'] . $refresh_token, + compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope'), + $expires + ); + } + + /** + * @param $refresh_token + * @return bool + */ + public function unsetRefreshToken($refresh_token) + { + return $this->expireValue($this->config['refresh_token_key'] . $refresh_token); + } + + /** + * @param string $access_token + * @return array|bool|mixed|null + */ + public function getAccessToken($access_token) + { + return $this->getValue($this->config['access_token_key'].$access_token); + } + + /** + * @param string $access_token + * @param mixed $client_id + * @param mixed $user_id + * @param int $expires + * @param null $scope + * @return bool + */ + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + return $this->setValue( + $this->config['access_token_key'].$access_token, + compact('access_token', 'client_id', 'user_id', 'expires', 'scope'), + $expires + ); + } + + /** + * @param $access_token + * @return bool + */ + public function unsetAccessToken($access_token) + { + return $this->expireValue($this->config['access_token_key'] . $access_token); + } + + /** + * @param $scope + * @return bool + */ + public function scopeExists($scope) + { + $scope = explode(' ', $scope); + + $result = $this->getValue($this->config['scope_key'].'supported:global'); + + $supportedScope = explode(' ', (string) $result); + + return (count(array_diff($scope, $supportedScope)) == 0); + } + + /** + * @param null $client_id + * @return bool|mixed + */ + public function getDefaultScope($client_id = null) + { + if (is_null($client_id) || !$result = $this->getValue($this->config['scope_key'].'default:'.$client_id)) { + $result = $this->getValue($this->config['scope_key'].'default:global'); + } + + return $result; + } + + /** + * @param $scope + * @param null $client_id + * @param string $type + * @return bool + * @throws \InvalidArgumentException + */ + public function setScope($scope, $client_id = null, $type = 'supported') + { + if (!in_array($type, array('default', 'supported'))) { + throw new \InvalidArgumentException('"$type" must be one of "default", "supported"'); + } + + if (is_null($client_id)) { + $key = $this->config['scope_key'].$type.':global'; + } else { + $key = $this->config['scope_key'].$type.':'.$client_id; + } + + return $this->setValue($key, $scope); + } + + /** + * @param $client_id + * @param $subject + * @return bool|null + */ + public function getClientKey($client_id, $subject) + { + if (!$jwt = $this->getValue($this->config['jwt_key'] . $client_id)) { + return false; + } + + if (isset($jwt['subject']) && $jwt['subject'] == $subject ) { + return $jwt['key']; + } + + return null; + } + + /** + * @param $client_id + * @param $key + * @param null $subject + * @return bool + */ + public function setClientKey($client_id, $key, $subject = null) + { + return $this->setValue($this->config['jwt_key'] . $client_id, array( + 'key' => $key, + 'subject' => $subject + )); + } + + /** + * @param $client_id + * @return bool|null + */ + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + /** + * @param $client_id + * @param $subject + * @param $audience + * @param $expiration + * @param $jti + * @throws \Exception + */ + public function getJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs cassandra implementation. + throw new \Exception('getJti() for the Cassandra driver is currently unimplemented.'); + } + + /** + * @param $client_id + * @param $subject + * @param $audience + * @param $expiration + * @param $jti + * @throws \Exception + */ + public function setJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs cassandra implementation. + throw new \Exception('setJti() for the Cassandra driver is currently unimplemented.'); + } + + /** + * @param string $client_id + * @return mixed + */ + public function getPublicKey($client_id = '') + { + $public_key = $this->getValue($this->config['public_key_key'] . $client_id); + if (is_array($public_key)) { + return $public_key['public_key']; + } + $public_key = $this->getValue($this->config['public_key_key']); + if (is_array($public_key)) { + return $public_key['public_key']; + } + } + + /** + * @param string $client_id + * @return mixed + */ + public function getPrivateKey($client_id = '') + { + $public_key = $this->getValue($this->config['public_key_key'] . $client_id); + if (is_array($public_key)) { + return $public_key['private_key']; + } + $public_key = $this->getValue($this->config['public_key_key']); + if (is_array($public_key)) { + return $public_key['private_key']; + } + } + + /** + * @param null $client_id + * @return mixed|string + */ + public function getEncryptionAlgorithm($client_id = null) + { + $public_key = $this->getValue($this->config['public_key_key'] . $client_id); + if (is_array($public_key)) { + return $public_key['encryption_algorithm']; + } + $public_key = $this->getValue($this->config['public_key_key']); + if (is_array($public_key)) { + return $public_key['encryption_algorithm']; + } + + return 'RS256'; + } + + /** + * @param mixed $user_id + * @param string $claims + * @return array|bool + */ + public function getUserClaims($user_id, $claims) + { + $userDetails = $this->getUserDetails($user_id); + if (!is_array($userDetails)) { + return false; + } + + $claims = explode(' ', trim($claims)); + $userClaims = array(); + + // for each requested claim, if the user has the claim, set it in the response + $validClaims = explode(' ', self::VALID_CLAIMS); + foreach ($validClaims as $validClaim) { + if (in_array($validClaim, $claims)) { + if ($validClaim == 'address') { + // address is an object with subfields + $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails); + } else { + $userClaims = array_merge($userClaims, $this->getUserClaim($validClaim, $userDetails)); + } + } + } + + return $userClaims; + } + + /** + * @param $claim + * @param $userDetails + * @return array + */ + protected function getUserClaim($claim, $userDetails) + { + $userClaims = array(); + $claimValuesString = constant(sprintf('self::%s_CLAIM_VALUES', strtoupper($claim))); + $claimValues = explode(' ', $claimValuesString); + + foreach ($claimValues as $value) { + if ($value == 'email_verified') { + $userClaims[$value] = $userDetails[$value]=='true' ? true : false; + } else { + $userClaims[$value] = isset($userDetails[$value]) ? $userDetails[$value] : null; + } + } + + return $userClaims; + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ClientCredentialsInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ClientCredentialsInterface.php new file mode 100644 index 0000000000..3318c69667 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ClientCredentialsInterface.php @@ -0,0 +1,49 @@ + + */ +interface ClientCredentialsInterface extends ClientInterface +{ + + /** + * Make sure that the client credentials is valid. + * + * @param $client_id + * Client identifier to be check with. + * @param $client_secret + * (optional) If a secret is required, check that they've given the right one. + * + * @return + * TRUE if the client credentials are valid, and MUST return FALSE if it isn't. + * @endcode + * + * @see http://tools.ietf.org/html/rfc6749#section-3.1 + * + * @ingroup oauth2_section_3 + */ + public function checkClientCredentials($client_id, $client_secret = null); + + /** + * Determine if the client is a "public" client, and therefore + * does not require passing credentials for certain grant types + * + * @param $client_id + * Client identifier to be check with. + * + * @return + * TRUE if the client is public, and FALSE if it isn't. + * @endcode + * + * @see http://tools.ietf.org/html/rfc6749#section-2.3 + * @see https://github.com/bshaffer/oauth2-server-php/issues/257 + * + * @ingroup oauth2_section_2 + */ + public function isPublicClient($client_id); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ClientInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ClientInterface.php new file mode 100644 index 0000000000..09a5bffc10 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ClientInterface.php @@ -0,0 +1,66 @@ + + */ +interface ClientInterface +{ + /** + * Get client details corresponding client_id. + * + * OAuth says we should store request URIs for each registered client. + * Implement this function to grab the stored URI for a given client id. + * + * @param $client_id + * Client identifier to be check with. + * + * @return array + * Client details. The only mandatory key in the array is "redirect_uri". + * This function MUST return FALSE if the given client does not exist or is + * invalid. "redirect_uri" can be space-delimited to allow for multiple valid uris. + * + * return array( + * "redirect_uri" => REDIRECT_URI, // REQUIRED redirect_uri registered for the client + * "client_id" => CLIENT_ID, // OPTIONAL the client id + * "grant_types" => GRANT_TYPES, // OPTIONAL an array of restricted grant types + * "user_id" => USER_ID, // OPTIONAL the user identifier associated with this client + * "scope" => SCOPE, // OPTIONAL the scopes allowed for this client + * ); + * + * + * @ingroup oauth2_section_4 + */ + public function getClientDetails($client_id); + + /** + * Get the scope associated with this client + * + * @return + * STRING the space-delineated scope list for the specified client_id + */ + public function getClientScope($client_id); + + /** + * Check restricted grant types of corresponding client identifier. + * + * If you want to restrict clients to certain grant types, override this + * function. + * + * @param $client_id + * Client identifier to be check with. + * @param $grant_type + * Grant type to be check with + * + * @return + * TRUE if the grant type is supported by this client identifier, and + * FALSE if it isn't. + * + * @ingroup oauth2_section_4 + */ + public function checkRestrictedGrantType($client_id, $grant_type); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/CouchbaseDB.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/CouchbaseDB.php new file mode 100755 index 0000000000..9e8148b6b2 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/CouchbaseDB.php @@ -0,0 +1,331 @@ + + */ +class CouchbaseDB implements AuthorizationCodeInterface, + AccessTokenInterface, + ClientCredentialsInterface, + UserCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + OpenIDAuthorizationCodeInterface +{ + protected $db; + protected $config; + + public function __construct($connection, $config = array()) + { + if ($connection instanceof \Couchbase) { + $this->db = $connection; + } else { + if (!is_array($connection) || !is_array($connection['servers'])) { + throw new \InvalidArgumentException('First argument to OAuth2\Storage\CouchbaseDB must be an instance of Couchbase or a configuration array containing a server array'); + } + + $this->db = new \Couchbase($connection['servers'], (!isset($connection['username'])) ? '' : $connection['username'], (!isset($connection['password'])) ? '' : $connection['password'], $connection['bucket'], false); + } + + $this->config = array_merge(array( + 'client_table' => 'oauth_clients', + 'access_token_table' => 'oauth_access_tokens', + 'refresh_token_table' => 'oauth_refresh_tokens', + 'code_table' => 'oauth_authorization_codes', + 'user_table' => 'oauth_users', + 'jwt_table' => 'oauth_jwt', + ), $config); + } + + // Helper function to access couchbase item by type: + protected function getObjectByType($name,$id) + { + return json_decode($this->db->get($this->config[$name].'-'.$id),true); + } + + // Helper function to set couchbase item by type: + protected function setObjectByType($name,$id,$array) + { + $array['type'] = $name; + + return $this->db->set($this->config[$name].'-'.$id,json_encode($array)); + } + + // Helper function to delete couchbase item by type, wait for persist to at least 1 node + protected function deleteObjectByType($name,$id) + { + $this->db->delete($this->config[$name].'-'.$id,"",1); + } + + /* ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + if ($result = $this->getObjectByType('client_table',$client_id)) { + return $result['client_secret'] == $client_secret; + } + + return false; + } + + public function isPublicClient($client_id) + { + if (!$result = $this->getObjectByType('client_table',$client_id)) { + return false; + } + + return empty($result['client_secret']); + } + + /* ClientInterface */ + public function getClientDetails($client_id) + { + $result = $this->getObjectByType('client_table',$client_id); + + return is_null($result) ? false : $result; + } + + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + if ($this->getClientDetails($client_id)) { + + $this->setObjectByType('client_table',$client_id, array( + 'client_id' => $client_id, + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + )); + } else { + $this->setObjectByType('client_table',$client_id, array( + 'client_id' => $client_id, + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + )); + } + + return true; + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + + return in_array($grant_type, $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + /* AccessTokenInterface */ + public function getAccessToken($access_token) + { + $token = $this->getObjectByType('access_token_table',$access_token); + + return is_null($token) ? false : $token; + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + // if it exists, update it. + if ($this->getAccessToken($access_token)) { + $this->setObjectByType('access_token_table',$access_token, array( + 'access_token' => $access_token, + 'client_id' => $client_id, + 'expires' => $expires, + 'user_id' => $user_id, + 'scope' => $scope + )); + } else { + $this->setObjectByType('access_token_table',$access_token, array( + 'access_token' => $access_token, + 'client_id' => $client_id, + 'expires' => $expires, + 'user_id' => $user_id, + 'scope' => $scope + )); + } + + return true; + } + + /* AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + $code = $this->getObjectByType('code_table',$code); + + return is_null($code) ? false : $code; + } + + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + // if it exists, update it. + if ($this->getAuthorizationCode($code)) { + $this->setObjectByType('code_table',$code, array( + 'authorization_code' => $code, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'redirect_uri' => $redirect_uri, + 'expires' => $expires, + 'scope' => $scope, + 'id_token' => $id_token, + )); + } else { + $this->setObjectByType('code_table',$code,array( + 'authorization_code' => $code, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'redirect_uri' => $redirect_uri, + 'expires' => $expires, + 'scope' => $scope, + 'id_token' => $id_token, + )); + } + + return true; + } + + public function expireAuthorizationCode($code) + { + $this->deleteObjectByType('code_table',$code); + + return true; + } + + /* UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + + return false; + } + + public function getUserDetails($username) + { + if ($user = $this->getUser($username)) { + $user['user_id'] = $user['username']; + } + + return $user; + } + + /* RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + $token = $this->getObjectByType('refresh_token_table',$refresh_token); + + return is_null($token) ? false : $token; + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + $this->setObjectByType('refresh_token_table',$refresh_token, array( + 'refresh_token' => $refresh_token, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'expires' => $expires, + 'scope' => $scope + )); + + return true; + } + + public function unsetRefreshToken($refresh_token) + { + $this->deleteObjectByType('refresh_token_table',$refresh_token); + + return true; + } + + // plaintext passwords are bad! Override this for your application + protected function checkPassword($user, $password) + { + return $user['password'] == $password; + } + + public function getUser($username) + { + $result = $this->getObjectByType('user_table',$username); + + return is_null($result) ? false : $result; + } + + public function setUser($username, $password, $firstName = null, $lastName = null) + { + if ($this->getUser($username)) { + $this->setObjectByType('user_table',$username, array( + 'username' => $username, + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName + )); + + } else { + $this->setObjectByType('user_table',$username, array( + 'username' => $username, + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName + )); + + } + + return true; + } + + public function getClientKey($client_id, $subject) + { + if (!$jwt = $this->getObjectByType('jwt_table',$client_id)) { + return false; + } + + if (isset($jwt['subject']) && $jwt['subject'] == $subject) { + return $jwt['key']; + } + + return false; + } + + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + public function getJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs couchbase implementation. + throw new \Exception('getJti() for the Couchbase driver is currently unimplemented.'); + } + + public function setJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs couchbase implementation. + throw new \Exception('setJti() for the Couchbase driver is currently unimplemented.'); + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/DynamoDB.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/DynamoDB.php new file mode 100644 index 0000000000..a54cb3712f --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/DynamoDB.php @@ -0,0 +1,540 @@ + + * composer require aws/aws-sdk-php:dev-master + * + * + * Once this is done, instantiate the DynamoDB client + * + * $storage = new OAuth2\Storage\Dynamodb(array("key" => "YOURKEY", "secret" => "YOURSECRET", "region" => "YOURREGION")); + * + * + * Table : + * - oauth_access_tokens (primary hash key : access_token) + * - oauth_authorization_codes (primary hash key : authorization_code) + * - oauth_clients (primary hash key : client_id) + * - oauth_jwt (primary hash key : client_id, primary range key : subject) + * - oauth_public_keys (primary hash key : client_id) + * - oauth_refresh_tokens (primary hash key : refresh_token) + * - oauth_scopes (primary hash key : scope, secondary index : is_default-index hash key is_default) + * - oauth_users (primary hash key : username) + * + * @author Frederic AUGUSTE + */ +class DynamoDB implements + AuthorizationCodeInterface, + AccessTokenInterface, + ClientCredentialsInterface, + UserCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + ScopeInterface, + PublicKeyInterface, + UserClaimsInterface, + OpenIDAuthorizationCodeInterface +{ + protected $client; + protected $config; + + public function __construct($connection, $config = array()) + { + if (!($connection instanceof DynamoDbClient)) { + if (!is_array($connection)) { + throw new \InvalidArgumentException('First argument to OAuth2\Storage\Dynamodb must be an instance a configuration array containt key, secret, region'); + } + if (!array_key_exists("key",$connection) || !array_key_exists("secret",$connection) || !array_key_exists("region",$connection) ) { + throw new \InvalidArgumentException('First argument to OAuth2\Storage\Dynamodb must be an instance a configuration array containt key, secret, region'); + } + $this->client = DynamoDbClient::factory(array( + 'key' => $connection["key"], + 'secret' => $connection["secret"], + 'region' =>$connection["region"] + )); + } else { + $this->client = $connection; + } + + $this->config = array_merge(array( + 'client_table' => 'oauth_clients', + 'access_token_table' => 'oauth_access_tokens', + 'refresh_token_table' => 'oauth_refresh_tokens', + 'code_table' => 'oauth_authorization_codes', + 'user_table' => 'oauth_users', + 'jwt_table' => 'oauth_jwt', + 'scope_table' => 'oauth_scopes', + 'public_key_table' => 'oauth_public_keys', + ), $config); + } + + /* OAuth2\Storage\ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['client_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + + return $result->count()==1 && $result["Item"]["client_secret"]["S"] == $client_secret; + } + + public function isPublicClient($client_id) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['client_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + + if ($result->count()==0) { + return false ; + } + + return empty($result["Item"]["client_secret"]); + } + + /* OAuth2\Storage\ClientInterface */ + public function getClientDetails($client_id) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['client_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + if ($result->count()==0) { + return false ; + } + $result = $this->dynamo2array($result); + foreach (array('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id') as $key => $val) { + if (!array_key_exists ($val, $result)) { + $result[$val] = null; + } + } + + return $result; + } + + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + $clientData = compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id'); + $clientData = array_filter($clientData, 'self::isNotEmpty'); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['client_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + + return in_array($grant_type, (array) $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + /* OAuth2\Storage\AccessTokenInterface */ + public function getAccessToken($access_token) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['access_token_table'], + "Key" => array('access_token' => array('S' => $access_token)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + if (array_key_exists ('expires', $token)) { + $token['expires'] = strtotime($token['expires']); + } + + return $token; + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + $clientData = compact('access_token', 'client_id', 'user_id', 'expires', 'scope'); + $clientData = array_filter($clientData, 'self::isNotEmpty'); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['access_token_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + + } + + public function unsetAccessToken($access_token) + { + $result = $this->client->deleteItem(array( + 'TableName' => $this->config['access_token_table'], + 'Key' => $this->client->formatAttributes(array("access_token" => $access_token)), + 'ReturnValues' => 'ALL_OLD', + )); + + return null !== $result->get('Attributes'); + } + + /* OAuth2\Storage\AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['code_table'], + "Key" => array('authorization_code' => array('S' => $code)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + if (!array_key_exists("id_token", $token )) { + $token['id_token'] = null; + } + $token['expires'] = strtotime($token['expires']); + + return $token; + + } + + public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + $clientData = compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'id_token', 'scope'); + $clientData = array_filter($clientData, 'self::isNotEmpty'); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['code_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + } + + public function expireAuthorizationCode($code) + { + + $result = $this->client->deleteItem(array( + 'TableName' => $this->config['code_table'], + 'Key' => $this->client->formatAttributes(array("authorization_code" => $code)) + )); + + return true; + } + + /* OAuth2\Storage\UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + + return false; + } + + public function getUserDetails($username) + { + return $this->getUser($username); + } + + /* UserClaimsInterface */ + public function getUserClaims($user_id, $claims) + { + if (!$userDetails = $this->getUserDetails($user_id)) { + return false; + } + + $claims = explode(' ', trim($claims)); + $userClaims = array(); + + // for each requested claim, if the user has the claim, set it in the response + $validClaims = explode(' ', self::VALID_CLAIMS); + foreach ($validClaims as $validClaim) { + if (in_array($validClaim, $claims)) { + if ($validClaim == 'address') { + // address is an object with subfields + $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails); + } else { + $userClaims = array_merge($userClaims, $this->getUserClaim($validClaim, $userDetails)); + } + } + } + + return $userClaims; + } + + protected function getUserClaim($claim, $userDetails) + { + $userClaims = array(); + $claimValuesString = constant(sprintf('self::%s_CLAIM_VALUES', strtoupper($claim))); + $claimValues = explode(' ', $claimValuesString); + + foreach ($claimValues as $value) { + if ($value == 'email_verified') { + $userClaims[$value] = $userDetails[$value]=='true' ? true : false; + } else { + $userClaims[$value] = isset($userDetails[$value]) ? $userDetails[$value] : null; + } + } + + return $userClaims; + } + + /* OAuth2\Storage\RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['refresh_token_table'], + "Key" => array('refresh_token' => array('S' => $refresh_token)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + $token['expires'] = strtotime($token['expires']); + + return $token; + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + $clientData = compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope'); + $clientData = array_filter($clientData, 'self::isNotEmpty'); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['refresh_token_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + } + + public function unsetRefreshToken($refresh_token) + { + $result = $this->client->deleteItem(array( + 'TableName' => $this->config['refresh_token_table'], + 'Key' => $this->client->formatAttributes(array("refresh_token" => $refresh_token)) + )); + + return true; + } + + // plaintext passwords are bad! Override this for your application + protected function checkPassword($user, $password) + { + return $user['password'] == $this->hashPassword($password); + } + + // use a secure hashing algorithm when storing passwords. Override this for your application + protected function hashPassword($password) + { + return sha1($password); + } + + public function getUser($username) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['user_table'], + "Key" => array('username' => array('S' => $username)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + $token['user_id'] = $username; + + return $token; + } + + public function setUser($username, $password, $first_name = null, $last_name = null) + { + // do not store in plaintext + $password = $this->hashPassword($password); + + $clientData = compact('username', 'password', 'first_name', 'last_name'); + $clientData = array_filter($clientData, 'self::isNotEmpty'); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['user_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + + } + + /* ScopeInterface */ + public function scopeExists($scope) + { + $scope = explode(' ', $scope); + $scope_query = array(); + $count = 0; + foreach ($scope as $key => $val) { + $result = $this->client->query(array( + 'TableName' => $this->config['scope_table'], + 'Select' => 'COUNT', + 'KeyConditions' => array( + 'scope' => array( + 'AttributeValueList' => array(array('S' => $val)), + 'ComparisonOperator' => 'EQ' + ) + ) + )); + $count += $result['Count']; + } + + return $count == count($scope); + } + + public function getDefaultScope($client_id = null) + { + + $result = $this->client->query(array( + 'TableName' => $this->config['scope_table'], + 'IndexName' => 'is_default-index', + 'Select' => 'ALL_ATTRIBUTES', + 'KeyConditions' => array( + 'is_default' => array( + 'AttributeValueList' => array(array('S' => 'true')), + 'ComparisonOperator' => 'EQ', + ), + ) + )); + $defaultScope = array(); + if ($result->count() > 0) { + $array = $result->toArray(); + foreach ($array["Items"] as $item) { + $defaultScope[] = $item['scope']['S']; + } + + return empty($defaultScope) ? null : implode(' ', $defaultScope); + } + + return null; + } + + /* JWTBearerInterface */ + public function getClientKey($client_id, $subject) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['jwt_table'], + "Key" => array('client_id' => array('S' => $client_id), 'subject' => array('S' => $subject)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + + return $token['public_key']; + } + + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + public function getJti($client_id, $subject, $audience, $expires, $jti) + { + //TODO not use. + } + + public function setJti($client_id, $subject, $audience, $expires, $jti) + { + //TODO not use. + } + + /* PublicKeyInterface */ + public function getPublicKey($client_id = '0') + { + + $result = $this->client->getItem(array( + "TableName"=> $this->config['public_key_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + + return $token['public_key']; + + } + + public function getPrivateKey($client_id = '0') + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['public_key_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + + return $token['private_key']; + } + + public function getEncryptionAlgorithm($client_id = null) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['public_key_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + if ($result->count()==0) { + return 'RS256' ; + } + $token = $this->dynamo2array($result); + + return $token['encryption_algorithm']; + } + + /** + * Transform dynamodb resultset to an array. + * @param $dynamodbResult + * @return $array + */ + private function dynamo2array($dynamodbResult) + { + $result = array(); + foreach ($dynamodbResult["Item"] as $key => $val) { + $result[$key] = $val["S"]; + $result[] = $val["S"]; + } + + return $result; + } + + private static function isNotEmpty($value) + { + return null !== $value && '' !== $value; + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessToken.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessToken.php new file mode 100644 index 0000000000..6ccacd6d9c --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessToken.php @@ -0,0 +1,87 @@ + + */ +class JwtAccessToken implements JwtAccessTokenInterface +{ + protected $publicKeyStorage; + protected $tokenStorage; + protected $encryptionUtil; + + /** + * @param OAuth2\Encryption\PublicKeyInterface $publicKeyStorage the public key encryption to use + * @param OAuth2\Storage\AccessTokenInterface $tokenStorage OPTIONAL persist the access token to another storage. This is useful if + * you want to retain access token grant information somewhere, but + * is not necessary when using this grant type. + * @param OAuth2\Encryption\EncryptionInterface $encryptionUtil OPTIONAL class to use for "encode" and "decode" functions. + */ + public function __construct(PublicKeyInterface $publicKeyStorage, AccessTokenInterface $tokenStorage = null, EncryptionInterface $encryptionUtil = null) + { + $this->publicKeyStorage = $publicKeyStorage; + $this->tokenStorage = $tokenStorage; + if (is_null($encryptionUtil)) { + $encryptionUtil = new Jwt; + } + $this->encryptionUtil = $encryptionUtil; + } + + public function getAccessToken($oauth_token) + { + // just decode the token, don't verify + if (!$tokenData = $this->encryptionUtil->decode($oauth_token, null, false)) { + return false; + } + + $client_id = isset($tokenData['aud']) ? $tokenData['aud'] : null; + $public_key = $this->publicKeyStorage->getPublicKey($client_id); + $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id); + + // now that we have the client_id, verify the token + if (false === $this->encryptionUtil->decode($oauth_token, $public_key, array($algorithm))) { + return false; + } + + // normalize the JWT claims to the format expected by other components in this library + return $this->convertJwtToOAuth2($tokenData); + } + + public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = null) + { + if ($this->tokenStorage) { + return $this->tokenStorage->setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope); + } + } + + public function unsetAccessToken($access_token) + { + if ($this->tokenStorage) { + return $this->tokenStorage->unsetAccessToken($access_token); + } + } + + + // converts a JWT access token into an OAuth2-friendly format + protected function convertJwtToOAuth2($tokenData) + { + $keyMapping = array( + 'aud' => 'client_id', + 'exp' => 'expires', + 'sub' => 'user_id' + ); + + foreach ($keyMapping as $jwtKey => $oauth2Key) { + if (isset($tokenData[$jwtKey])) { + $tokenData[$oauth2Key] = $tokenData[$jwtKey]; + unset($tokenData[$jwtKey]); + } + } + + return $tokenData; + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessTokenInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessTokenInterface.php new file mode 100644 index 0000000000..3abb2aa2d5 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessTokenInterface.php @@ -0,0 +1,14 @@ + + */ +interface JwtAccessTokenInterface extends AccessTokenInterface +{ + +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtBearerInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtBearerInterface.php new file mode 100644 index 0000000000..c83aa72ea2 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtBearerInterface.php @@ -0,0 +1,74 @@ + + */ +interface JwtBearerInterface +{ + /** + * Get the public key associated with a client_id + * + * @param $client_id + * Client identifier to be checked with. + * + * @return + * STRING Return the public key for the client_id if it exists, and MUST return FALSE if it doesn't. + */ + public function getClientKey($client_id, $subject); + + /** + * Get a jti (JSON token identifier) by matching against the client_id, subject, audience and expiration. + * + * @param $client_id + * Client identifier to match. + * + * @param $subject + * The subject to match. + * + * @param $audience + * The audience to match. + * + * @param $expiration + * The expiration of the jti. + * + * @param $jti + * The jti to match. + * + * @return + * An associative array as below, and return NULL if the jti does not exist. + * - issuer: Stored client identifier. + * - subject: Stored subject. + * - audience: Stored audience. + * - expires: Stored expiration in unix timestamp. + * - jti: The stored jti. + */ + public function getJti($client_id, $subject, $audience, $expiration, $jti); + + /** + * Store a used jti so that we can check against it to prevent replay attacks. + * @param $client_id + * Client identifier to insert. + * + * @param $subject + * The subject to insert. + * + * @param $audience + * The audience to insert. + * + * @param $expiration + * The expiration of the jti. + * + * @param $jti + * The jti to insert. + */ + public function setJti($client_id, $subject, $audience, $expiration, $jti); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Memory.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Memory.php new file mode 100644 index 0000000000..2c60b71cea --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Memory.php @@ -0,0 +1,381 @@ + + */ +class Memory implements AuthorizationCodeInterface, + UserCredentialsInterface, + UserClaimsInterface, + AccessTokenInterface, + ClientCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + ScopeInterface, + PublicKeyInterface, + OpenIDAuthorizationCodeInterface +{ + public $authorizationCodes; + public $userCredentials; + public $clientCredentials; + public $refreshTokens; + public $accessTokens; + public $jwt; + public $jti; + public $supportedScopes; + public $defaultScope; + public $keys; + + public function __construct($params = array()) + { + $params = array_merge(array( + 'authorization_codes' => array(), + 'user_credentials' => array(), + 'client_credentials' => array(), + 'refresh_tokens' => array(), + 'access_tokens' => array(), + 'jwt' => array(), + 'jti' => array(), + 'default_scope' => null, + 'supported_scopes' => array(), + 'keys' => array(), + ), $params); + + $this->authorizationCodes = $params['authorization_codes']; + $this->userCredentials = $params['user_credentials']; + $this->clientCredentials = $params['client_credentials']; + $this->refreshTokens = $params['refresh_tokens']; + $this->accessTokens = $params['access_tokens']; + $this->jwt = $params['jwt']; + $this->jti = $params['jti']; + $this->supportedScopes = $params['supported_scopes']; + $this->defaultScope = $params['default_scope']; + $this->keys = $params['keys']; + } + + /* AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + if (!isset($this->authorizationCodes[$code])) { + return false; + } + + return array_merge(array( + 'authorization_code' => $code, + ), $this->authorizationCodes[$code]); + } + + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + $this->authorizationCodes[$code] = compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'); + + return true; + } + + public function setAuthorizationCodes($authorization_codes) + { + $this->authorizationCodes = $authorization_codes; + } + + public function expireAuthorizationCode($code) + { + unset($this->authorizationCodes[$code]); + } + + /* UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + $userDetails = $this->getUserDetails($username); + + return $userDetails && $userDetails['password'] && $userDetails['password'] === $password; + } + + public function setUser($username, $password, $firstName = null, $lastName = null) + { + $this->userCredentials[$username] = array( + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName, + ); + + return true; + } + + public function getUserDetails($username) + { + if (!isset($this->userCredentials[$username])) { + return false; + } + + return array_merge(array( + 'user_id' => $username, + 'password' => null, + 'first_name' => null, + 'last_name' => null, + ), $this->userCredentials[$username]); + } + + /* UserClaimsInterface */ + public function getUserClaims($user_id, $claims) + { + if (!$userDetails = $this->getUserDetails($user_id)) { + return false; + } + + $claims = explode(' ', trim($claims)); + $userClaims = array(); + + // for each requested claim, if the user has the claim, set it in the response + $validClaims = explode(' ', self::VALID_CLAIMS); + foreach ($validClaims as $validClaim) { + if (in_array($validClaim, $claims)) { + if ($validClaim == 'address') { + // address is an object with subfields + $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails); + } else { + $userClaims = array_merge($userClaims, $this->getUserClaim($validClaim, $userDetails)); + } + } + } + + return $userClaims; + } + + protected function getUserClaim($claim, $userDetails) + { + $userClaims = array(); + $claimValuesString = constant(sprintf('self::%s_CLAIM_VALUES', strtoupper($claim))); + $claimValues = explode(' ', $claimValuesString); + + foreach ($claimValues as $value) { + $userClaims[$value] = isset($userDetails[$value]) ? $userDetails[$value] : null; + } + + return $userClaims; + } + + /* ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + return isset($this->clientCredentials[$client_id]['client_secret']) && $this->clientCredentials[$client_id]['client_secret'] === $client_secret; + } + + public function isPublicClient($client_id) + { + if (!isset($this->clientCredentials[$client_id])) { + return false; + } + + return empty($this->clientCredentials[$client_id]['client_secret']); + } + + /* ClientInterface */ + public function getClientDetails($client_id) + { + if (!isset($this->clientCredentials[$client_id])) { + return false; + } + + $clientDetails = array_merge(array( + 'client_id' => $client_id, + 'client_secret' => null, + 'redirect_uri' => null, + 'scope' => null, + ), $this->clientCredentials[$client_id]); + + return $clientDetails; + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + if (isset($this->clientCredentials[$client_id]['grant_types'])) { + $grant_types = explode(' ', $this->clientCredentials[$client_id]['grant_types']); + + return in_array($grant_type, $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + $this->clientCredentials[$client_id] = array( + 'client_id' => $client_id, + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + ); + + return true; + } + + /* RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + return isset($this->refreshTokens[$refresh_token]) ? $this->refreshTokens[$refresh_token] : false; + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + $this->refreshTokens[$refresh_token] = compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope'); + + return true; + } + + public function unsetRefreshToken($refresh_token) + { + if (isset($this->refreshTokens[$refresh_token])) { + unset($this->refreshTokens[$refresh_token]); + + return true; + } + + return false; + } + + public function setRefreshTokens($refresh_tokens) + { + $this->refreshTokens = $refresh_tokens; + } + + /* AccessTokenInterface */ + public function getAccessToken($access_token) + { + return isset($this->accessTokens[$access_token]) ? $this->accessTokens[$access_token] : false; + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null, $id_token = null) + { + $this->accessTokens[$access_token] = compact('access_token', 'client_id', 'user_id', 'expires', 'scope', 'id_token'); + + return true; + } + + public function unsetAccessToken($access_token) + { + if (isset($this->accessTokens[$access_token])) { + unset($this->accessTokens[$access_token]); + + return true; + } + + return false; + } + + public function scopeExists($scope) + { + $scope = explode(' ', trim($scope)); + + return (count(array_diff($scope, $this->supportedScopes)) == 0); + } + + public function getDefaultScope($client_id = null) + { + return $this->defaultScope; + } + + /*JWTBearerInterface */ + public function getClientKey($client_id, $subject) + { + if (isset($this->jwt[$client_id])) { + $jwt = $this->jwt[$client_id]; + if ($jwt) { + if ($jwt["subject"] == $subject) { + return $jwt["key"]; + } + } + } + + return false; + } + + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + public function getJti($client_id, $subject, $audience, $expires, $jti) + { + foreach ($this->jti as $storedJti) { + if ($storedJti['issuer'] == $client_id && $storedJti['subject'] == $subject && $storedJti['audience'] == $audience && $storedJti['expires'] == $expires && $storedJti['jti'] == $jti) { + return array( + 'issuer' => $storedJti['issuer'], + 'subject' => $storedJti['subject'], + 'audience' => $storedJti['audience'], + 'expires' => $storedJti['expires'], + 'jti' => $storedJti['jti'] + ); + } + } + + return null; + } + + public function setJti($client_id, $subject, $audience, $expires, $jti) + { + $this->jti[] = array('issuer' => $client_id, 'subject' => $subject, 'audience' => $audience, 'expires' => $expires, 'jti' => $jti); + } + + /*PublicKeyInterface */ + public function getPublicKey($client_id = null) + { + if (isset($this->keys[$client_id])) { + return $this->keys[$client_id]['public_key']; + } + + // use a global encryption pair + if (isset($this->keys['public_key'])) { + return $this->keys['public_key']; + } + + return false; + } + + public function getPrivateKey($client_id = null) + { + if (isset($this->keys[$client_id])) { + return $this->keys[$client_id]['private_key']; + } + + // use a global encryption pair + if (isset($this->keys['private_key'])) { + return $this->keys['private_key']; + } + + return false; + } + + public function getEncryptionAlgorithm($client_id = null) + { + if (isset($this->keys[$client_id]['encryption_algorithm'])) { + return $this->keys[$client_id]['encryption_algorithm']; + } + + // use a global encryption algorithm + if (isset($this->keys['encryption_algorithm'])) { + return $this->keys['encryption_algorithm']; + } + + return 'RS256'; + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Mongo.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Mongo.php new file mode 100644 index 0000000000..eea06e315b --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Mongo.php @@ -0,0 +1,392 @@ + + */ +class Mongo implements AuthorizationCodeInterface, + AccessTokenInterface, + ClientCredentialsInterface, + UserCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + PublicKeyInterface, + OpenIDAuthorizationCodeInterface +{ + protected $db; + protected $config; + + public function __construct($connection, $config = array()) + { + if ($connection instanceof \MongoDB) { + $this->db = $connection; + } else { + if (!is_array($connection)) { + throw new \InvalidArgumentException('First argument to OAuth2\Storage\Mongo must be an instance of MongoDB or a configuration array'); + } + $server = sprintf('mongodb://%s:%d', $connection['host'], $connection['port']); + $m = new \MongoClient($server); + $this->db = $m->{$connection['database']}; + } + + $this->config = array_merge(array( + 'client_table' => 'oauth_clients', + 'access_token_table' => 'oauth_access_tokens', + 'refresh_token_table' => 'oauth_refresh_tokens', + 'code_table' => 'oauth_authorization_codes', + 'user_table' => 'oauth_users', + 'key_table' => 'oauth_keys', + 'jwt_table' => 'oauth_jwt', + ), $config); + } + + // Helper function to access a MongoDB collection by `type`: + protected function collection($name) + { + return $this->db->{$this->config[$name]}; + } + + /* ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + if ($result = $this->collection('client_table')->findOne(array('client_id' => $client_id))) { + return $result['client_secret'] == $client_secret; + } + + return false; + } + + public function isPublicClient($client_id) + { + if (!$result = $this->collection('client_table')->findOne(array('client_id' => $client_id))) { + return false; + } + + return empty($result['client_secret']); + } + + /* ClientInterface */ + public function getClientDetails($client_id) + { + $result = $this->collection('client_table')->findOne(array('client_id' => $client_id)); + + return is_null($result) ? false : $result; + } + + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + if ($this->getClientDetails($client_id)) { + $this->collection('client_table')->update( + array('client_id' => $client_id), + array('$set' => array( + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + )) + ); + } else { + $client = array( + 'client_id' => $client_id, + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + ); + $this->collection('client_table')->insert($client); + } + + return true; + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + + return in_array($grant_type, $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + /* AccessTokenInterface */ + public function getAccessToken($access_token) + { + $token = $this->collection('access_token_table')->findOne(array('access_token' => $access_token)); + + return is_null($token) ? false : $token; + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + // if it exists, update it. + if ($this->getAccessToken($access_token)) { + $this->collection('access_token_table')->update( + array('access_token' => $access_token), + array('$set' => array( + 'client_id' => $client_id, + 'expires' => $expires, + 'user_id' => $user_id, + 'scope' => $scope + )) + ); + } else { + $token = array( + 'access_token' => $access_token, + 'client_id' => $client_id, + 'expires' => $expires, + 'user_id' => $user_id, + 'scope' => $scope + ); + $this->collection('access_token_table')->insert($token); + } + + return true; + } + + public function unsetAccessToken($access_token) + { + $result = $this->collection('access_token_table')->remove(array( + 'access_token' => $access_token + ), array('w' => 1)); + + return $result['n'] > 0; + } + + + /* AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + $code = $this->collection('code_table')->findOne(array('authorization_code' => $code)); + + return is_null($code) ? false : $code; + } + + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + // if it exists, update it. + if ($this->getAuthorizationCode($code)) { + $this->collection('code_table')->update( + array('authorization_code' => $code), + array('$set' => array( + 'client_id' => $client_id, + 'user_id' => $user_id, + 'redirect_uri' => $redirect_uri, + 'expires' => $expires, + 'scope' => $scope, + 'id_token' => $id_token, + )) + ); + } else { + $token = array( + 'authorization_code' => $code, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'redirect_uri' => $redirect_uri, + 'expires' => $expires, + 'scope' => $scope, + 'id_token' => $id_token, + ); + $this->collection('code_table')->insert($token); + } + + return true; + } + + public function expireAuthorizationCode($code) + { + $this->collection('code_table')->remove(array('authorization_code' => $code)); + + return true; + } + + /* UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + + return false; + } + + public function getUserDetails($username) + { + if ($user = $this->getUser($username)) { + $user['user_id'] = $user['username']; + } + + return $user; + } + + /* RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + $token = $this->collection('refresh_token_table')->findOne(array('refresh_token' => $refresh_token)); + + return is_null($token) ? false : $token; + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + $token = array( + 'refresh_token' => $refresh_token, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'expires' => $expires, + 'scope' => $scope + ); + $this->collection('refresh_token_table')->insert($token); + + return true; + } + + public function unsetRefreshToken($refresh_token) + { + $result = $this->collection('refresh_token_table')->remove(array( + 'refresh_token' => $refresh_token + ), array('w' => 1)); + + return $result['n'] > 0; + } + + // plaintext passwords are bad! Override this for your application + protected function checkPassword($user, $password) + { + return $user['password'] == $password; + } + + public function getUser($username) + { + $result = $this->collection('user_table')->findOne(array('username' => $username)); + + return is_null($result) ? false : $result; + } + + public function setUser($username, $password, $firstName = null, $lastName = null) + { + if ($this->getUser($username)) { + $this->collection('user_table')->update( + array('username' => $username), + array('$set' => array( + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName + )) + ); + } else { + $user = array( + 'username' => $username, + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName + ); + $this->collection('user_table')->insert($user); + } + + return true; + } + + public function getClientKey($client_id, $subject) + { + $result = $this->collection('jwt_table')->findOne(array( + 'client_id' => $client_id, + 'subject' => $subject + )); + + return is_null($result) ? false : $result['key']; + } + + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + public function getJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs mongodb implementation. + throw new \Exception('getJti() for the MongoDB driver is currently unimplemented.'); + } + + public function setJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs mongodb implementation. + throw new \Exception('setJti() for the MongoDB driver is currently unimplemented.'); + } + + public function getPublicKey($client_id = null) + { + if ($client_id) { + $result = $this->collection('key_table')->findOne(array( + 'client_id' => $client_id + )); + if ($result) { + return $result['public_key']; + } + } + + $result = $this->collection('key_table')->findOne(array( + 'client_id' => null + )); + return is_null($result) ? false : $result['public_key']; + } + + public function getPrivateKey($client_id = null) + { + if ($client_id) { + $result = $this->collection('key_table')->findOne(array( + 'client_id' => $client_id + )); + if ($result) { + return $result['private_key']; + } + } + + $result = $this->collection('key_table')->findOne(array( + 'client_id' => null + )); + return is_null($result) ? false : $result['private_key']; + } + + public function getEncryptionAlgorithm($client_id = null) + { + if ($client_id) { + $result = $this->collection('key_table')->findOne(array( + 'client_id' => $client_id + )); + if ($result) { + return $result['encryption_algorithm']; + } + } + + $result = $this->collection('key_table')->findOne(array( + 'client_id' => null + )); + return is_null($result) ? 'RS256' : $result['encryption_algorithm']; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/MongoDB.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/MongoDB.php new file mode 100644 index 0000000000..64f740fc16 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/MongoDB.php @@ -0,0 +1,380 @@ + + */ +class MongoDB implements AuthorizationCodeInterface, + UserCredentialsInterface, + AccessTokenInterface, + ClientCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + PublicKeyInterface, + OpenIDAuthorizationCodeInterface +{ + protected $db; + protected $config; + + public function __construct($connection, $config = array()) + { + if ($connection instanceof Database) { + $this->db = $connection; + } else { + if (!is_array($connection)) { + throw new \InvalidArgumentException('First argument to OAuth2\Storage\Mongo must be an instance of MongoDB\Database or a configuration array'); + } + $server = sprintf('mongodb://%s:%d', $connection['host'], $connection['port']); + $m = new Client($server); + $this->db = $m->selectDatabase($connection['database']); + } + $this->config = array_merge(array( + 'client_table' => 'oauth_clients', + 'access_token_table' => 'oauth_access_tokens', + 'refresh_token_table' => 'oauth_refresh_tokens', + 'code_table' => 'oauth_authorization_codes', + 'user_table' => 'oauth_users', + 'jwt_table' => 'oauth_jwt', + 'jti_table' => 'oauth_jti', + 'scope_table' => 'oauth_scopes', + 'key_table' => 'oauth_keys', + ), $config); + } + + /* ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + if ($result = $this->collection('client_table')->findOne(array('client_id' => $client_id))) { + return $result['client_secret'] == $client_secret; + } + return false; + } + + public function isPublicClient($client_id) + { + if (!$result = $this->collection('client_table')->findOne(array('client_id' => $client_id))) { + return false; + } + return empty($result['client_secret']); + } + + /* ClientInterface */ + public function getClientDetails($client_id) + { + $result = $this->collection('client_table')->findOne(array('client_id' => $client_id)); + return is_null($result) ? false : $result; + } + + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + if ($this->getClientDetails($client_id)) { + $result = $this->collection('client_table')->updateOne( + array('client_id' => $client_id), + array('$set' => array( + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + )) + ); + return $result->getMatchedCount() > 0; + } + $client = array( + 'client_id' => $client_id, + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + ); + $result = $this->collection('client_table')->insertOne($client); + return $result->getInsertedCount() > 0; + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + return in_array($grant_type, $grant_types); + } + // if grant_types are not defined, then none are restricted + return true; + } + + /* AccessTokenInterface */ + public function getAccessToken($access_token) + { + $token = $this->collection('access_token_table')->findOne(array('access_token' => $access_token)); + return is_null($token) ? false : $token; + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + // if it exists, update it. + if ($this->getAccessToken($access_token)) { + $result = $this->collection('access_token_table')->updateOne( + array('access_token' => $access_token), + array('$set' => array( + 'client_id' => $client_id, + 'expires' => $expires, + 'user_id' => $user_id, + 'scope' => $scope + )) + ); + return $result->getMatchedCount() > 0; + } + $token = array( + 'access_token' => $access_token, + 'client_id' => $client_id, + 'expires' => $expires, + 'user_id' => $user_id, + 'scope' => $scope + ); + $result = $this->collection('access_token_table')->insertOne($token); + return $result->getInsertedCount() > 0; + } + + public function unsetAccessToken($access_token) + { + $result = $this->collection('access_token_table')->deleteOne(array( + 'access_token' => $access_token + )); + return $result->getDeletedCount() > 0; + } + + /* AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + $code = $this->collection('code_table')->findOne(array( + 'authorization_code' => $code + )); + return is_null($code) ? false : $code; + } + + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + // if it exists, update it. + if ($this->getAuthorizationCode($code)) { + $result = $this->collection('code_table')->updateOne( + array('authorization_code' => $code), + array('$set' => array( + 'client_id' => $client_id, + 'user_id' => $user_id, + 'redirect_uri' => $redirect_uri, + 'expires' => $expires, + 'scope' => $scope, + 'id_token' => $id_token, + )) + ); + return $result->getMatchedCount() > 0; + } + $token = array( + 'authorization_code' => $code, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'redirect_uri' => $redirect_uri, + 'expires' => $expires, + 'scope' => $scope, + 'id_token' => $id_token, + ); + $result = $this->collection('code_table')->insertOne($token); + return $result->getInsertedCount() > 0; + } + + public function expireAuthorizationCode($code) + { + $result = $this->collection('code_table')->deleteOne(array( + 'authorization_code' => $code + )); + return $result->getDeletedCount() > 0; + } + + /* UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + return false; + } + + public function getUserDetails($username) + { + if ($user = $this->getUser($username)) { + $user['user_id'] = $user['username']; + } + return $user; + } + + /* RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + $token = $this->collection('refresh_token_table')->findOne(array( + 'refresh_token' => $refresh_token + )); + return is_null($token) ? false : $token; + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + $token = array( + 'refresh_token' => $refresh_token, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'expires' => $expires, + 'scope' => $scope + ); + $result = $this->collection('refresh_token_table')->insertOne($token); + return $result->getInsertedCount() > 0; + } + + public function unsetRefreshToken($refresh_token) + { + $result = $this->collection('refresh_token_table')->deleteOne(array( + 'refresh_token' => $refresh_token + )); + return $result->getDeletedCount() > 0; + } + + // plaintext passwords are bad! Override this for your application + protected function checkPassword($user, $password) + { + return $user['password'] == $password; + } + + public function getUser($username) + { + $result = $this->collection('user_table')->findOne(array('username' => $username)); + return is_null($result) ? false : $result; + } + + public function setUser($username, $password, $firstName = null, $lastName = null) + { + if ($this->getUser($username)) { + $result = $this->collection('user_table')->updateOne( + array('username' => $username), + array('$set' => array( + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName + )) + ); + + return $result->getMatchedCount() > 0; + } + + $user = array( + 'username' => $username, + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName + ); + $result = $this->collection('user_table')->insertOne($user); + return $result->getInsertedCount() > 0; + } + + public function getClientKey($client_id, $subject) + { + $result = $this->collection('jwt_table')->findOne(array( + 'client_id' => $client_id, + 'subject' => $subject + )); + return is_null($result) ? false : $result['key']; + } + + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + return null; + } + + public function getJti($client_id, $subject, $audience, $expires, $jti) + { + //TODO: Needs mongodb implementation. + throw new \Exception('getJti() for the MongoDB driver is currently unimplemented.'); + } + + public function setJti($client_id, $subject, $audience, $expires, $jti) + { + //TODO: Needs mongodb implementation. + throw new \Exception('setJti() for the MongoDB driver is currently unimplemented.'); + } + + public function getPublicKey($client_id = null) + { + if ($client_id) { + $result = $this->collection('key_table')->findOne(array( + 'client_id' => $client_id + )); + if ($result) { + return $result['public_key']; + } + } + + $result = $this->collection('key_table')->findOne(array( + 'client_id' => null + )); + return is_null($result) ? false : $result['public_key']; + } + + public function getPrivateKey($client_id = null) + { + if ($client_id) { + $result = $this->collection('key_table')->findOne(array( + 'client_id' => $client_id + )); + if ($result) { + return $result['private_key']; + } + } + + $result = $this->collection('key_table')->findOne(array( + 'client_id' => null + )); + return is_null($result) ? false : $result['private_key']; + } + + public function getEncryptionAlgorithm($client_id = null) + { + if ($client_id) { + $result = $this->collection('key_table')->findOne(array( + 'client_id' => $client_id + )); + if ($result) { + return $result['encryption_algorithm']; + } + } + + $result = $this->collection('key_table')->findOne(array( + 'client_id' => null + )); + return is_null($result) ? 'RS256' : $result['encryption_algorithm']; + } + + // Helper function to access a MongoDB collection by `type`: + protected function collection($name) + { + return $this->db->{$this->config[$name]}; + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Pdo.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Pdo.php new file mode 100644 index 0000000000..074cee4478 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Pdo.php @@ -0,0 +1,731 @@ + + */ +class Pdo implements + AuthorizationCodeInterface, + AccessTokenInterface, + ClientCredentialsInterface, + UserCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + ScopeInterface, + PublicKeyInterface, + UserClaimsInterface, + OpenIDAuthorizationCodeInterface +{ + /** + * @var \PDO + */ + protected $db; + + /** + * @var array + */ + protected $config; + + /** + * @param mixed $connection + * @param array $config + * + * @throws InvalidArgumentException + */ + public function __construct($connection, $config = array()) + { + if (!$connection instanceof \PDO) { + if (is_string($connection)) { + $connection = array('dsn' => $connection); + } + if (!is_array($connection)) { + throw new \InvalidArgumentException('First argument to OAuth2\Storage\Pdo must be an instance of PDO, a DSN string, or a configuration array'); + } + if (!isset($connection['dsn'])) { + throw new \InvalidArgumentException('configuration array must contain "dsn"'); + } + // merge optional parameters + $connection = array_merge(array( + 'username' => null, + 'password' => null, + 'options' => array(), + ), $connection); + $connection = new \PDO($connection['dsn'], $connection['username'], $connection['password'], $connection['options']); + } + $this->db = $connection; + + // debugging + $connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + $this->config = array_merge(array( + 'client_table' => 'oauth_clients', + 'access_token_table' => 'oauth_access_tokens', + 'refresh_token_table' => 'oauth_refresh_tokens', + 'code_table' => 'oauth_authorization_codes', + 'user_table' => 'oauth_users', + 'jwt_table' => 'oauth_jwt', + 'jti_table' => 'oauth_jti', + 'scope_table' => 'oauth_scopes', + 'public_key_table' => 'oauth_public_keys', + ), $config); + } + + /** + * @param string $client_id + * @param null|string $client_secret + * @return bool + */ + public function checkClientCredentials($client_id, $client_secret = null) + { + $stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table'])); + $stmt->execute(compact('client_id')); + $result = $stmt->fetch(\PDO::FETCH_ASSOC); + + // make this extensible + return $result && $result['client_secret'] == $client_secret; + } + + /** + * @param string $client_id + * @return bool + */ + public function isPublicClient($client_id) + { + $stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table'])); + $stmt->execute(compact('client_id')); + + if (!$result = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return false; + } + + return empty($result['client_secret']); + } + + /** + * @param string $client_id + * @return array|mixed + */ + public function getClientDetails($client_id) + { + $stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table'])); + $stmt->execute(compact('client_id')); + + return $stmt->fetch(\PDO::FETCH_ASSOC); + } + + /** + * @param string $client_id + * @param null|string $client_secret + * @param null|string $redirect_uri + * @param null|array $grant_types + * @param null|string $scope + * @param null|string $user_id + * @return bool + */ + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + // if it exists, update it. + if ($this->getClientDetails($client_id)) { + $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_secret=:client_secret, redirect_uri=:redirect_uri, grant_types=:grant_types, scope=:scope, user_id=:user_id where client_id=:client_id', $this->config['client_table'])); + } else { + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (client_id, client_secret, redirect_uri, grant_types, scope, user_id) VALUES (:client_id, :client_secret, :redirect_uri, :grant_types, :scope, :user_id)', $this->config['client_table'])); + } + + return $stmt->execute(compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id')); + } + + /** + * @param $client_id + * @param $grant_type + * @return bool + */ + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + + return in_array($grant_type, (array) $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + /** + * @param string $access_token + * @return array|bool|mixed|null + */ + public function getAccessToken($access_token) + { + $stmt = $this->db->prepare(sprintf('SELECT * from %s where access_token = :access_token', $this->config['access_token_table'])); + + $token = $stmt->execute(compact('access_token')); + if ($token = $stmt->fetch(\PDO::FETCH_ASSOC)) { + // convert date string back to timestamp + $token['expires'] = strtotime($token['expires']); + } + + return $token; + } + + /** + * @param string $access_token + * @param mixed $client_id + * @param mixed $user_id + * @param int $expires + * @param string $scope + * @return bool + */ + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + // if it exists, update it. + if ($this->getAccessToken($access_token)) { + $stmt = $this->db->prepare(sprintf('UPDATE %s SET client_id=:client_id, expires=:expires, user_id=:user_id, scope=:scope where access_token=:access_token', $this->config['access_token_table'])); + } else { + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (access_token, client_id, expires, user_id, scope) VALUES (:access_token, :client_id, :expires, :user_id, :scope)', $this->config['access_token_table'])); + } + + return $stmt->execute(compact('access_token', 'client_id', 'user_id', 'expires', 'scope')); + } + + /** + * @param $access_token + * @return bool + */ + public function unsetAccessToken($access_token) + { + $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE access_token = :access_token', $this->config['access_token_table'])); + + $stmt->execute(compact('access_token')); + + return $stmt->rowCount() > 0; + } + + /* OAuth2\Storage\AuthorizationCodeInterface */ + /** + * @param string $code + * @return mixed + */ + public function getAuthorizationCode($code) + { + $stmt = $this->db->prepare(sprintf('SELECT * from %s where authorization_code = :code', $this->config['code_table'])); + $stmt->execute(compact('code')); + + if ($code = $stmt->fetch(\PDO::FETCH_ASSOC)) { + // convert date string back to timestamp + $code['expires'] = strtotime($code['expires']); + } + + return $code; + } + + /** + * @param string $code + * @param mixed $client_id + * @param mixed $user_id + * @param string $redirect_uri + * @param int $expires + * @param string $scope + * @param string $id_token + * @return bool|mixed + */ + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + if (func_num_args() > 6) { + // we are calling with an id token + return call_user_func_array(array($this, 'setAuthorizationCodeWithIdToken'), func_get_args()); + } + + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + // if it exists, update it. + if ($this->getAuthorizationCode($code)) { + $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_id=:client_id, user_id=:user_id, redirect_uri=:redirect_uri, expires=:expires, scope=:scope where authorization_code=:code', $this->config['code_table'])); + } else { + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope)', $this->config['code_table'])); + } + + return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope')); + } + + /** + * @param string $code + * @param mixed $client_id + * @param mixed $user_id + * @param string $redirect_uri + * @param string $expires + * @param string $scope + * @param string $id_token + * @return bool + */ + private function setAuthorizationCodeWithIdToken($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + // if it exists, update it. + if ($this->getAuthorizationCode($code)) { + $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_id=:client_id, user_id=:user_id, redirect_uri=:redirect_uri, expires=:expires, scope=:scope, id_token =:id_token where authorization_code=:code', $this->config['code_table'])); + } else { + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope, id_token) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope, :id_token)', $this->config['code_table'])); + } + + return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token')); + } + + /** + * @param string $code + * @return bool + */ + public function expireAuthorizationCode($code) + { + $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE authorization_code = :code', $this->config['code_table'])); + + return $stmt->execute(compact('code')); + } + + /** + * @param string $username + * @param string $password + * @return bool + */ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + + return false; + } + + /** + * @param string $username + * @return array|bool + */ + public function getUserDetails($username) + { + return $this->getUser($username); + } + + /** + * @param mixed $user_id + * @param string $claims + * @return array|bool + */ + public function getUserClaims($user_id, $claims) + { + if (!$userDetails = $this->getUserDetails($user_id)) { + return false; + } + + $claims = explode(' ', trim($claims)); + $userClaims = array(); + + // for each requested claim, if the user has the claim, set it in the response + $validClaims = explode(' ', self::VALID_CLAIMS); + foreach ($validClaims as $validClaim) { + if (in_array($validClaim, $claims)) { + if ($validClaim == 'address') { + // address is an object with subfields + $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails); + } else { + $userClaims = array_merge($userClaims, $this->getUserClaim($validClaim, $userDetails)); + } + } + } + + return $userClaims; + } + + /** + * @param string $claim + * @param array $userDetails + * @return array + */ + protected function getUserClaim($claim, $userDetails) + { + $userClaims = array(); + $claimValuesString = constant(sprintf('self::%s_CLAIM_VALUES', strtoupper($claim))); + $claimValues = explode(' ', $claimValuesString); + + foreach ($claimValues as $value) { + $userClaims[$value] = isset($userDetails[$value]) ? $userDetails[$value] : null; + } + + return $userClaims; + } + + /** + * @param string $refresh_token + * @return bool|mixed + */ + public function getRefreshToken($refresh_token) + { + $stmt = $this->db->prepare(sprintf('SELECT * FROM %s WHERE refresh_token = :refresh_token', $this->config['refresh_token_table'])); + + $token = $stmt->execute(compact('refresh_token')); + if ($token = $stmt->fetch(\PDO::FETCH_ASSOC)) { + // convert expires to epoch time + $token['expires'] = strtotime($token['expires']); + } + + return $token; + } + + /** + * @param string $refresh_token + * @param mixed $client_id + * @param mixed $user_id + * @param string $expires + * @param string $scope + * @return bool + */ + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (refresh_token, client_id, user_id, expires, scope) VALUES (:refresh_token, :client_id, :user_id, :expires, :scope)', $this->config['refresh_token_table'])); + + return $stmt->execute(compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope')); + } + + /** + * @param string $refresh_token + * @return bool + */ + public function unsetRefreshToken($refresh_token) + { + $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE refresh_token = :refresh_token', $this->config['refresh_token_table'])); + + $stmt->execute(compact('refresh_token')); + + return $stmt->rowCount() > 0; + } + + /** + * plaintext passwords are bad! Override this for your application + * + * @param array $user + * @param string $password + * @return bool + */ + protected function checkPassword($user, $password) + { + return $user['password'] == $this->hashPassword($password); + } + + // use a secure hashing algorithm when storing passwords. Override this for your application + protected function hashPassword($password) + { + return sha1($password); + } + + /** + * @param string $username + * @return array|bool + */ + public function getUser($username) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT * from %s where username=:username', $this->config['user_table'])); + $stmt->execute(array('username' => $username)); + + if (!$userInfo = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return false; + } + + // the default behavior is to use "username" as the user_id + return array_merge(array( + 'user_id' => $username + ), $userInfo); + } + + /** + * plaintext passwords are bad! Override this for your application + * + * @param string $username + * @param string $password + * @param string $firstName + * @param string $lastName + * @return bool + */ + public function setUser($username, $password, $firstName = null, $lastName = null) + { + // do not store in plaintext + $password = $this->hashPassword($password); + + // if it exists, update it. + if ($this->getUser($username)) { + $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET password=:password, first_name=:firstName, last_name=:lastName where username=:username', $this->config['user_table'])); + } else { + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (username, password, first_name, last_name) VALUES (:username, :password, :firstName, :lastName)', $this->config['user_table'])); + } + + return $stmt->execute(compact('username', 'password', 'firstName', 'lastName')); + } + + /** + * @param string $scope + * @return bool + */ + public function scopeExists($scope) + { + $scope = explode(' ', $scope); + $whereIn = implode(',', array_fill(0, count($scope), '?')); + $stmt = $this->db->prepare(sprintf('SELECT count(scope) as count FROM %s WHERE scope IN (%s)', $this->config['scope_table'], $whereIn)); + $stmt->execute($scope); + + if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return $result['count'] == count($scope); + } + + return false; + } + + /** + * @param mixed $client_id + * @return null|string + */ + public function getDefaultScope($client_id = null) + { + $stmt = $this->db->prepare(sprintf('SELECT scope FROM %s WHERE is_default=:is_default', $this->config['scope_table'])); + $stmt->execute(array('is_default' => true)); + + if ($result = $stmt->fetchAll(\PDO::FETCH_ASSOC)) { + $defaultScope = array_map(function ($row) { + return $row['scope']; + }, $result); + + return implode(' ', $defaultScope); + } + + return null; + } + + /** + * @param mixed $client_id + * @param $subject + * @return string + */ + public function getClientKey($client_id, $subject) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT public_key from %s where client_id=:client_id AND subject=:subject', $this->config['jwt_table'])); + + $stmt->execute(array('client_id' => $client_id, 'subject' => $subject)); + + return $stmt->fetchColumn(); + } + + /** + * @param mixed $client_id + * @return bool|null + */ + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + /** + * @param mixed $client_id + * @param $subject + * @param $audience + * @param $expires + * @param $jti + * @return array|null + */ + public function getJti($client_id, $subject, $audience, $expires, $jti) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT * FROM %s WHERE issuer=:client_id AND subject=:subject AND audience=:audience AND expires=:expires AND jti=:jti', $this->config['jti_table'])); + + $stmt->execute(compact('client_id', 'subject', 'audience', 'expires', 'jti')); + + if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return array( + 'issuer' => $result['issuer'], + 'subject' => $result['subject'], + 'audience' => $result['audience'], + 'expires' => $result['expires'], + 'jti' => $result['jti'], + ); + } + + return null; + } + + /** + * @param mixed $client_id + * @param $subject + * @param $audience + * @param $expires + * @param $jti + * @return bool + */ + public function setJti($client_id, $subject, $audience, $expires, $jti) + { + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (issuer, subject, audience, expires, jti) VALUES (:client_id, :subject, :audience, :expires, :jti)', $this->config['jti_table'])); + + return $stmt->execute(compact('client_id', 'subject', 'audience', 'expires', 'jti')); + } + + /** + * @param mixed $client_id + * @return mixed + */ + public function getPublicKey($client_id = null) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT public_key FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table'])); + + $stmt->execute(compact('client_id')); + if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return $result['public_key']; + } + } + + /** + * @param mixed $client_id + * @return mixed + */ + public function getPrivateKey($client_id = null) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT private_key FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table'])); + + $stmt->execute(compact('client_id')); + if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return $result['private_key']; + } + } + + /** + * @param mixed $client_id + * @return string + */ + public function getEncryptionAlgorithm($client_id = null) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT encryption_algorithm FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table'])); + + $stmt->execute(compact('client_id')); + if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return $result['encryption_algorithm']; + } + + return 'RS256'; + } + + /** + * DDL to create OAuth2 database and tables for PDO storage + * + * @see https://github.com/dsquier/oauth2-server-php-mysql + * + * @param string $dbName + * @return string + */ + public function getBuildSql($dbName = 'oauth2_server_php') + { + $sql = " + CREATE TABLE {$this->config['client_table']} ( + client_id VARCHAR(80) NOT NULL, + client_secret VARCHAR(80), + redirect_uri VARCHAR(2000), + grant_types VARCHAR(80), + scope VARCHAR(4000), + user_id VARCHAR(80), + PRIMARY KEY (client_id) + ); + + CREATE TABLE {$this->config['access_token_table']} ( + access_token VARCHAR(40) NOT NULL, + client_id VARCHAR(80) NOT NULL, + user_id VARCHAR(80), + expires TIMESTAMP NOT NULL, + scope VARCHAR(4000), + PRIMARY KEY (access_token) + ); + + CREATE TABLE {$this->config['code_table']} ( + authorization_code VARCHAR(40) NOT NULL, + client_id VARCHAR(80) NOT NULL, + user_id VARCHAR(80), + redirect_uri VARCHAR(2000), + expires TIMESTAMP NOT NULL, + scope VARCHAR(4000), + id_token VARCHAR(1000), + PRIMARY KEY (authorization_code) + ); + + CREATE TABLE {$this->config['refresh_token_table']} ( + refresh_token VARCHAR(40) NOT NULL, + client_id VARCHAR(80) NOT NULL, + user_id VARCHAR(80), + expires TIMESTAMP NOT NULL, + scope VARCHAR(4000), + PRIMARY KEY (refresh_token) + ); + + CREATE TABLE {$this->config['user_table']} ( + username VARCHAR(80), + password VARCHAR(80), + first_name VARCHAR(80), + last_name VARCHAR(80), + email VARCHAR(80), + email_verified BOOLEAN, + scope VARCHAR(4000) + ); + + CREATE TABLE {$this->config['scope_table']} ( + scope VARCHAR(80) NOT NULL, + is_default BOOLEAN, + PRIMARY KEY (scope) + ); + + CREATE TABLE {$this->config['jwt_table']} ( + client_id VARCHAR(80) NOT NULL, + subject VARCHAR(80), + public_key VARCHAR(2000) NOT NULL + ); + + CREATE TABLE {$this->config['jti_table']} ( + issuer VARCHAR(80) NOT NULL, + subject VARCHAR(80), + audiance VARCHAR(80), + expires TIMESTAMP NOT NULL, + jti VARCHAR(2000) NOT NULL + ); + + CREATE TABLE {$this->config['public_key_table']} ( + client_id VARCHAR(80), + public_key VARCHAR(2000), + private_key VARCHAR(2000), + encryption_algorithm VARCHAR(100) DEFAULT 'RS256' + ) + "; + + return $sql; + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/PublicKeyInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/PublicKeyInterface.php new file mode 100644 index 0000000000..a6dc49fb54 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/PublicKeyInterface.php @@ -0,0 +1,30 @@ + + */ +interface PublicKeyInterface +{ + /** + * @param mixed $client_id + * @return mixed + */ + public function getPublicKey($client_id = null); + + /** + * @param mixed $client_id + * @return mixed + */ + public function getPrivateKey($client_id = null); + + /** + * @param mixed $client_id + * @return mixed + */ + public function getEncryptionAlgorithm($client_id = null); +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Redis.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Redis.php new file mode 100644 index 0000000000..e6294e22d9 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Redis.php @@ -0,0 +1,321 @@ + + * $storage = new OAuth2\Storage\Redis($redis); + * $storage->setClientDetails($client_id, $client_secret, $redirect_uri); + * + */ +class Redis implements AuthorizationCodeInterface, + AccessTokenInterface, + ClientCredentialsInterface, + UserCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + ScopeInterface, + OpenIDAuthorizationCodeInterface +{ + + private $cache; + + /* The redis client */ + protected $redis; + + /* Configuration array */ + protected $config; + + /** + * Redis Storage! + * + * @param \Predis\Client $redis + * @param array $config + */ + public function __construct($redis, $config=array()) + { + $this->redis = $redis; + $this->config = array_merge(array( + 'client_key' => 'oauth_clients:', + 'access_token_key' => 'oauth_access_tokens:', + 'refresh_token_key' => 'oauth_refresh_tokens:', + 'code_key' => 'oauth_authorization_codes:', + 'user_key' => 'oauth_users:', + 'jwt_key' => 'oauth_jwt:', + 'scope_key' => 'oauth_scopes:', + ), $config); + } + + protected function getValue($key) + { + if ( isset($this->cache[$key]) ) { + return $this->cache[$key]; + } + $value = $this->redis->get($key); + if ( isset($value) ) { + return json_decode($value, true); + } else { + return false; + } + } + + protected function setValue($key, $value, $expire=0) + { + $this->cache[$key] = $value; + $str = json_encode($value); + if ($expire > 0) { + $seconds = $expire - time(); + $ret = $this->redis->setex($key, $seconds, $str); + } else { + $ret = $this->redis->set($key, $str); + } + + // check that the key was set properly + // if this fails, an exception will usually thrown, so this step isn't strictly necessary + return is_bool($ret) ? $ret : $ret->getPayload() == 'OK'; + } + + protected function expireValue($key) + { + unset($this->cache[$key]); + + return $this->redis->del($key); + } + + /* AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + return $this->getValue($this->config['code_key'] . $code); + } + + public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + return $this->setValue( + $this->config['code_key'] . $authorization_code, + compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'), + $expires + ); + } + + public function expireAuthorizationCode($code) + { + $key = $this->config['code_key'] . $code; + unset($this->cache[$key]); + + return $this->expireValue($key); + } + + /* UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + $user = $this->getUserDetails($username); + + return $user && $user['password'] === $password; + } + + public function getUserDetails($username) + { + return $this->getUser($username); + } + + public function getUser($username) + { + if (!$userInfo = $this->getValue($this->config['user_key'] . $username)) { + return false; + } + + // the default behavior is to use "username" as the user_id + return array_merge(array( + 'user_id' => $username, + ), $userInfo); + } + + public function setUser($username, $password, $first_name = null, $last_name = null) + { + return $this->setValue( + $this->config['user_key'] . $username, + compact('username', 'password', 'first_name', 'last_name') + ); + } + + /* ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + if (!$client = $this->getClientDetails($client_id)) { + return false; + } + + return isset($client['client_secret']) + && $client['client_secret'] == $client_secret; + } + + public function isPublicClient($client_id) + { + if (!$client = $this->getClientDetails($client_id)) { + return false; + } + + return empty($client['client_secret']); + } + + /* ClientInterface */ + public function getClientDetails($client_id) + { + return $this->getValue($this->config['client_key'] . $client_id); + } + + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + return $this->setValue( + $this->config['client_key'] . $client_id, + compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id') + ); + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + + return in_array($grant_type, (array) $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + /* RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + return $this->getValue($this->config['refresh_token_key'] . $refresh_token); + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + return $this->setValue( + $this->config['refresh_token_key'] . $refresh_token, + compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope'), + $expires + ); + } + + public function unsetRefreshToken($refresh_token) + { + $result = $this->expireValue($this->config['refresh_token_key'] . $refresh_token); + + return $result > 0; + } + + /* AccessTokenInterface */ + public function getAccessToken($access_token) + { + return $this->getValue($this->config['access_token_key'].$access_token); + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + return $this->setValue( + $this->config['access_token_key'].$access_token, + compact('access_token', 'client_id', 'user_id', 'expires', 'scope'), + $expires + ); + } + + public function unsetAccessToken($access_token) + { + $result = $this->expireValue($this->config['access_token_key'] . $access_token); + + return $result > 0; + } + + /* ScopeInterface */ + public function scopeExists($scope) + { + $scope = explode(' ', $scope); + + $result = $this->getValue($this->config['scope_key'].'supported:global'); + + $supportedScope = explode(' ', (string) $result); + + return (count(array_diff($scope, $supportedScope)) == 0); + } + + public function getDefaultScope($client_id = null) + { + if (is_null($client_id) || !$result = $this->getValue($this->config['scope_key'].'default:'.$client_id)) { + $result = $this->getValue($this->config['scope_key'].'default:global'); + } + + return $result; + } + + public function setScope($scope, $client_id = null, $type = 'supported') + { + if (!in_array($type, array('default', 'supported'))) { + throw new \InvalidArgumentException('"$type" must be one of "default", "supported"'); + } + + if (is_null($client_id)) { + $key = $this->config['scope_key'].$type.':global'; + } else { + $key = $this->config['scope_key'].$type.':'.$client_id; + } + + return $this->setValue($key, $scope); + } + + /*JWTBearerInterface */ + public function getClientKey($client_id, $subject) + { + if (!$jwt = $this->getValue($this->config['jwt_key'] . $client_id)) { + return false; + } + + if (isset($jwt['subject']) && $jwt['subject'] == $subject) { + return $jwt['key']; + } + + return null; + } + + public function setClientKey($client_id, $key, $subject = null) + { + return $this->setValue($this->config['jwt_key'] . $client_id, array( + 'key' => $key, + 'subject' => $subject + )); + } + + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + public function getJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs redis implementation. + throw new \Exception('getJti() for the Redis driver is currently unimplemented.'); + } + + public function setJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs redis implementation. + throw new \Exception('setJti() for the Redis driver is currently unimplemented.'); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/RefreshTokenInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/RefreshTokenInterface.php new file mode 100644 index 0000000000..e6407e440d --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/RefreshTokenInterface.php @@ -0,0 +1,82 @@ + + */ +interface RefreshTokenInterface +{ + /** + * Grant refresh access tokens. + * + * Retrieve the stored data for the given refresh token. + * + * Required for OAuth2::GRANT_TYPE_REFRESH_TOKEN. + * + * @param $refresh_token + * Refresh token to be check with. + * + * @return + * An associative array as below, and NULL if the refresh_token is + * invalid: + * - refresh_token: Refresh token identifier. + * - client_id: Client identifier. + * - user_id: User identifier. + * - expires: Expiration unix timestamp, or 0 if the token doesn't expire. + * - scope: (optional) Scope values in space-separated string. + * + * @see http://tools.ietf.org/html/rfc6749#section-6 + * + * @ingroup oauth2_section_6 + */ + public function getRefreshToken($refresh_token); + + /** + * Take the provided refresh token values and store them somewhere. + * + * This function should be the storage counterpart to getRefreshToken(). + * + * If storage fails for some reason, we're not currently checking for + * any sort of success/failure, so you should bail out of the script + * and provide a descriptive fail message. + * + * Required for OAuth2::GRANT_TYPE_REFRESH_TOKEN. + * + * @param $refresh_token + * Refresh token to be stored. + * @param $client_id + * Client identifier to be stored. + * @param $user_id + * User identifier to be stored. + * @param $expires + * Expiration timestamp to be stored. 0 if the token doesn't expire. + * @param $scope + * (optional) Scopes to be stored in space-separated string. + * + * @ingroup oauth2_section_6 + */ + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null); + + /** + * Expire a used refresh token. + * + * This is not explicitly required in the spec, but is almost implied. + * After granting a new refresh token, the old one is no longer useful and + * so should be forcibly expired in the data store so it can't be used again. + * + * If storage fails for some reason, we're not currently checking for + * any sort of success/failure, so you should bail out of the script + * and provide a descriptive fail message. + * + * @param $refresh_token + * Refresh token to be expired. + * + * @ingroup oauth2_section_6 + */ + public function unsetRefreshToken($refresh_token); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ScopeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ScopeInterface.php new file mode 100644 index 0000000000..a8292269b3 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ScopeInterface.php @@ -0,0 +1,46 @@ + + */ +interface ScopeInterface +{ + /** + * Check if the provided scope exists. + * + * @param $scope + * A space-separated string of scopes. + * + * @return + * TRUE if it exists, FALSE otherwise. + */ + public function scopeExists($scope); + + /** + * The default scope to use in the event the client + * does not request one. By returning "false", a + * request_error is returned by the server to force a + * scope request by the client. By returning "null", + * opt out of requiring scopes + * + * @param $client_id + * An optional client id that can be used to return customized default scopes. + * + * @return + * string representation of default scope, null if + * scopes are not defined, or false to force scope + * request by the client + * + * ex: + * 'default' + * ex: + * null + */ + public function getDefaultScope($client_id = null); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/UserCredentialsInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/UserCredentialsInterface.php new file mode 100644 index 0000000000..f550579e4f --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/UserCredentialsInterface.php @@ -0,0 +1,52 @@ + + */ +interface UserCredentialsInterface +{ + /** + * Grant access tokens for basic user credentials. + * + * Check the supplied username and password for validity. + * + * You can also use the $client_id param to do any checks required based + * on a client, if you need that. + * + * Required for OAuth2::GRANT_TYPE_USER_CREDENTIALS. + * + * @param $username + * Username to be check with. + * @param $password + * Password to be check with. + * + * @return + * TRUE if the username and password are valid, and FALSE if it isn't. + * Moreover, if the username and password are valid, and you want to + * + * @see http://tools.ietf.org/html/rfc6749#section-4.3 + * + * @ingroup oauth2_section_4 + */ + public function checkUserCredentials($username, $password); + + /** + * @param string $username - username to get details for + * @return array|false - the associated "user_id" and optional "scope" values + * This function MUST return FALSE if the requested user does not exist or is + * invalid. "scope" is a space-separated list of restricted scopes. + * @code + * return array( + * "user_id" => USER_ID, // REQUIRED user_id to be stored with the authorization code or access token + * "scope" => SCOPE // OPTIONAL space-separated list of restricted scopes + * ); + * @endcode + */ + public function getUserDetails($username); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php new file mode 100644 index 0000000000..8ac8596ac4 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php @@ -0,0 +1,130 @@ +config = array_merge(array( + 'token_param_name' => 'access_token', + 'token_bearer_header_name' => 'Bearer', + ), $config); + } + + public function getTokenType() + { + return 'Bearer'; + } + + /** + * Check if the request has supplied token + * + * @see https://github.com/bshaffer/oauth2-server-php/issues/349#issuecomment-37993588 + */ + public function requestHasToken(RequestInterface $request) + { + $headers = $request->headers('AUTHORIZATION'); + + // check the header, then the querystring, then the request body + return !empty($headers) || (bool) ($request->request($this->config['token_param_name'])) || (bool) ($request->query($this->config['token_param_name'])); + } + + /** + * This is a convenience function that can be used to get the token, which can then + * be passed to getAccessTokenData(). The constraints specified by the draft are + * attempted to be adheared to in this method. + * + * As per the Bearer spec (draft 8, section 2) - there are three ways for a client + * to specify the bearer token, in order of preference: Authorization Header, + * POST and GET. + * + * NB: Resource servers MUST accept tokens via the Authorization scheme + * (http://tools.ietf.org/html/rfc6750#section-2). + * + * @todo Should we enforce TLS/SSL in this function? + * + * @see http://tools.ietf.org/html/rfc6750#section-2.1 + * @see http://tools.ietf.org/html/rfc6750#section-2.2 + * @see http://tools.ietf.org/html/rfc6750#section-2.3 + * + * Old Android version bug (at least with version 2.2) + * @see http://code.google.com/p/android/issues/detail?id=6684 + * + */ + public function getAccessTokenParameter(RequestInterface $request, ResponseInterface $response) + { + $headers = $request->headers('AUTHORIZATION'); + + /** + * Ensure more than one method is not used for including an + * access token + * + * @see http://tools.ietf.org/html/rfc6750#section-3.1 + */ + $methodsUsed = !empty($headers) + (bool) ($request->query($this->config['token_param_name'])) + (bool) ($request->request($this->config['token_param_name'])); + if ($methodsUsed > 1) { + $response->setError(400, 'invalid_request', 'Only one method may be used to authenticate at a time (Auth header, GET or POST)'); + + return null; + } + + /** + * If no authentication is provided, set the status code + * to 401 and return no other error information + * + * @see http://tools.ietf.org/html/rfc6750#section-3.1 + */ + if ($methodsUsed == 0) { + $response->setStatusCode(401); + + return null; + } + + // HEADER: Get the access token from the header + if (!empty($headers)) { + if (!preg_match('/' . $this->config['token_bearer_header_name'] . '\s(\S+)/i', $headers, $matches)) { + $response->setError(400, 'invalid_request', 'Malformed auth header'); + + return null; + } + + return $matches[1]; + } + + if ($request->request($this->config['token_param_name'])) { + // // POST: Get the token from POST data + if (!in_array(strtolower($request->server('REQUEST_METHOD')), array('post', 'put'))) { + $response->setError(400, 'invalid_request', 'When putting the token in the body, the method must be POST or PUT', '#section-2.2'); + + return null; + } + + $contentType = $request->server('CONTENT_TYPE'); + if (false !== $pos = strpos($contentType, ';')) { + $contentType = substr($contentType, 0, $pos); + } + + if ($contentType !== null && $contentType != 'application/x-www-form-urlencoded') { + // IETF specifies content-type. NB: Not all webservers populate this _SERVER variable + // @see http://tools.ietf.org/html/rfc6750#section-2.2 + $response->setError(400, 'invalid_request', 'The content type for POST requests must be "application/x-www-form-urlencoded"'); + + return null; + } + + return $request->request($this->config['token_param_name']); + } + + // GET method + return $request->query($this->config['token_param_name']); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Mac.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Mac.php new file mode 100644 index 0000000000..fe6a86aa6b --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Mac.php @@ -0,0 +1,22 @@ +assertTrue(class_exists('OAuth2\Server')); + $this->assertTrue(class_exists('OAuth2\Request')); + $this->assertTrue(class_exists('OAuth2\Response')); + $this->assertTrue(class_exists('OAuth2\GrantType\UserCredentials')); + $this->assertTrue(interface_exists('OAuth2\Storage\AccessTokenInterface')); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/AuthorizeControllerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/AuthorizeControllerTest.php new file mode 100644 index 0000000000..fe3553b232 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/AuthorizeControllerTest.php @@ -0,0 +1,493 @@ +getTestServer(); + $request = new Request(); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'No client id supplied'); + } + + public function testInvalidClientIdResponse() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Fake Client ID', // invalid client id + )); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'The client id supplied is invalid'); + } + + public function testNoRedirectUriSuppliedOrStoredResponse() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + )); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_uri'); + $this->assertEquals($response->getParameter('error_description'), 'No redirect URI was supplied or stored'); + } + + public function testNoResponseTypeResponse() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + )); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $this->assertEquals($query['error'], 'invalid_request'); + $this->assertEquals($query['error_description'], 'Invalid or missing response type'); + } + + public function testInvalidResponseTypeResponse() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'invalid', // invalid response type + )); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $this->assertEquals($query['error'], 'invalid_request'); + $this->assertEquals($query['error_description'], 'Invalid or missing response type'); + } + + public function testRedirectUriFragmentResponse() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com#fragment', // valid redirect URI + 'response_type' => 'code', // invalid response type + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_uri'); + $this->assertEquals($response->getParameter('error_description'), 'The redirect URI must not contain a fragment'); + } + + public function testEnforceState() + { + $server = $this->getTestServer(array('enforce_state' => true)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'code', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $this->assertEquals($query['error'], 'invalid_request'); + $this->assertEquals($query['error_description'], 'The state parameter is required'); + } + + public function testDoNotEnforceState() + { + $server = $this->getTestServer(array('enforce_state' => false)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'code', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $this->assertNotContains('error', $response->getHttpHeader('Location')); + } + + public function testEnforceScope() + { + $server = $this->getTestServer(); + $scopeStorage = new Memory(array('default_scope' => false, 'supported_scopes' => array('testscope'))); + $server->setScopeUtil(new Scope($scopeStorage)); + + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'code', + 'state' => 'xyz', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['query'], $query); + + $this->assertEquals($query['error'], 'invalid_client'); + $this->assertEquals($query['error_description'], 'This application requires you specify a scope parameter'); + + $request->query['scope'] = 'testscope'; + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $this->assertNotContains('error', $response->getHttpHeader('Location')); + } + + public function testInvalidRedirectUri() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID with Redirect Uri', // valid client id + 'redirect_uri' => 'http://adobe.com', // invalid redirect URI + 'response_type' => 'code', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'redirect_uri_mismatch'); + $this->assertEquals($response->getParameter('error_description'), 'The redirect URI provided is missing or does not match'); + } + + public function testInvalidRedirectUriApprovedByBuggyRegisteredUri() + { + $server = $this->getTestServer(); + $server->setConfig('require_exact_redirect_uri', false); + $request = new Request(array( + 'client_id' => 'Test Client ID with Buggy Redirect Uri', // valid client id + 'redirect_uri' => 'http://adobe.com', // invalid redirect URI + 'response_type' => 'code', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'redirect_uri_mismatch'); + $this->assertEquals($response->getParameter('error_description'), 'The redirect URI provided is missing or does not match'); + } + + public function testNoRedirectUriWithMultipleRedirectUris() + { + $server = $this->getTestServer(); + + // create a request with no "redirect_uri" in querystring + $request = new Request(array( + 'client_id' => 'Test Client ID with Multiple Redirect Uris', // valid client id + 'response_type' => 'code', + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_uri'); + $this->assertEquals($response->getParameter('error_description'), 'A redirect URI must be supplied when multiple redirect URIs are registered'); + } + + public function testRedirectUriWithValidRedirectUri() + { + $server = $this->getTestServer(); + + // create a request with no "redirect_uri" in querystring + $request = new Request(array( + 'client_id' => 'Test Client ID with Redirect Uri Parts', // valid client id + 'response_type' => 'code', + 'redirect_uri' => 'http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth&test=true', + 'state' => 'xyz', + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $this->assertContains('code', $response->getHttpHeader('Location')); + } + + public function testRedirectUriWithDifferentQueryAndExactMatchRequired() + { + $server = $this->getTestServer(array('require_exact_redirect_uri' => true)); + + // create a request with no "redirect_uri" in querystring + $request = new Request(array( + 'client_id' => 'Test Client ID with Redirect Uri Parts', // valid client id + 'response_type' => 'code', + 'redirect_uri' => 'http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth&test=true&hereisa=querystring', + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'redirect_uri_mismatch'); + $this->assertEquals($response->getParameter('error_description'), 'The redirect URI provided is missing or does not match'); + } + + public function testRedirectUriWithDifferentQueryAndExactMatchNotRequired() + { + $server = $this->getTestServer(array('require_exact_redirect_uri' => false)); + + // create a request with no "redirect_uri" in querystring + $request = new Request(array( + 'client_id' => 'Test Client ID with Redirect Uri Parts', // valid client id + 'response_type' => 'code', + 'redirect_uri' => 'http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth&test=true&hereisa=querystring', + 'state' => 'xyz', + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $this->assertContains('code', $response->getHttpHeader('Location')); + } + + public function testMultipleRedirectUris() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID with Multiple Redirect Uris', // valid client id + 'redirect_uri' => 'http://brentertainment.com', // valid redirect URI + 'response_type' => 'code', + 'state' => 'xyz' + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + $this->assertEquals($response->getStatusCode(), 302); + $this->assertContains('code', $response->getHttpHeader('Location')); + + // call again with different (but still valid) redirect URI + $request->query['redirect_uri'] = 'http://morehazards.com'; + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + $this->assertEquals($response->getStatusCode(), 302); + $this->assertContains('code', $response->getHttpHeader('Location')); + } + + /** + * @see http://tools.ietf.org/html/rfc6749#section-4.1.3 + * @see https://github.com/bshaffer/oauth2-server-php/issues/163 + */ + public function testNoRedirectUriSuppliedDoesNotRequireTokenRedirectUri() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID with Redirect Uri', // valid client id + 'response_type' => 'code', + 'state' => 'xyz', + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + $this->assertEquals($response->getStatusCode(), 302); + $this->assertContains('state', $response->getHttpHeader('Location')); + $this->assertStringStartsWith('http://brentertainment.com?code=', $response->getHttpHeader('Location')); + + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['query'], $query); + + // call token endpoint with no redirect_uri supplied + $request = TestRequest::createPost(array( + 'client_id' => 'Test Client ID with Redirect Uri', // valid client id + 'client_secret' => 'TestSecret2', + 'grant_type' => 'authorization_code', + 'code' => $query['code'], + )); + + $server->handleTokenRequest($request, $response = new Response(), true); + $this->assertEquals($response->getStatusCode(), 200); + $this->assertNotNull($response->getParameter('access_token')); + } + + public function testUserDeniesAccessResponse() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'code', + 'state' => 'xyz', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $this->assertEquals($query['error'], 'access_denied'); + $this->assertEquals($query['error_description'], 'The user denied access to your application'); + } + + public function testCodeQueryParamIsSet() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'code', + 'state' => 'xyz', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + + $this->assertEquals('http', $parts['scheme']); // same as passed in to redirect_uri + $this->assertEquals('adobe.com', $parts['host']); // same as passed in to redirect_uri + $this->assertArrayHasKey('query', $parts); + $this->assertFalse(isset($parts['fragment'])); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['query'], $query); + $this->assertNotNull($query); + $this->assertArrayHasKey('code', $query); + + // ensure no id_token was saved, since the openid scope wasn't requested + $storage = $server->getStorage('authorization_code'); + $code = $storage->getAuthorizationCode($query['code']); + $this->assertTrue(empty($code['id_token'])); + + // ensure no error was returned + $this->assertFalse(isset($query['error'])); + $this->assertFalse(isset($query['error_description'])); + } + + public function testSuccessfulRequestReturnsStateParameter() + { + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'code', + 'state' => 'test', // valid state string (just needs to be passed back to us) + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + $this->assertArrayHasKey('query', $parts); + parse_str($parts['query'], $query); + + $this->assertArrayHasKey('state', $query); + $this->assertEquals($query['state'], 'test'); + + // ensure no error was returned + $this->assertFalse(isset($query['error'])); + $this->assertFalse(isset($query['error_description'])); + } + + public function testSuccessfulRequestStripsExtraParameters() + { + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'code', + 'state' => 'test', // valid state string (just needs to be passed back to us) + 'fake' => 'something', // extra query param + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $this->assertNotContains('error', $location); + + $parts = parse_url($location); + $this->assertFalse(isset($parts['fake'])); + $this->assertArrayHasKey('query', $parts); + parse_str($parts['query'], $query); + + $this->assertFalse(isset($parmas['fake'])); + $this->assertArrayHasKey('state', $query); + $this->assertEquals($query['state'], 'test'); + } + + public function testSuccessfulOpenidConnectRequest() + { + $server = $this->getTestServer(array( + 'use_openid_connect' => true, + 'issuer' => 'bojanz', + )); + + $request = new Request(array( + 'client_id' => 'Test Client ID', + 'redirect_uri' => 'http://adobe.com', + 'response_type' => 'code', + 'state' => 'xyz', + 'scope' => 'openid', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + $this->assertArrayHasKey('query', $parts); + $this->assertFalse(isset($parts['fragment'])); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['query'], $query); + $this->assertNotNull($query); + $this->assertArrayHasKey('code', $query); + + // ensure no error was returned + $this->assertFalse(isset($query['error'])); + $this->assertFalse(isset($query['error_description'])); + + // confirm that the id_token has been created. + $storage = $server->getStorage('authorization_code'); + $code = $storage->getAuthorizationCode($query['code']); + $this->assertTrue(!empty($code['id_token'])); + } + + public function testCreateController() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $controller = new AuthorizeController($storage); + } + + private function getTestServer($config = array()) + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, $config); + + // Add the two types supported for authorization grant + $server->addGrantType(new AuthorizationCode($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/ResourceControllerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/ResourceControllerTest.php new file mode 100644 index 0000000000..cd54d239ab --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/ResourceControllerTest.php @@ -0,0 +1,177 @@ +getTestServer(); + $request = Request::createFromGlobals(); + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 401); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + $this->assertEquals('', $response->getResponseBody()); + } + + public function testMalformedHeader() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'tH1s i5 B0gU5'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Malformed auth header'); + } + + public function testMultipleTokensSubmitted() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->request['access_token'] = 'TEST'; + $request->query['access_token'] = 'TEST'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Only one method may be used to authenticate at a time (Auth header, GET or POST)'); + } + + public function testInvalidRequestMethod() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->server['REQUEST_METHOD'] = 'GET'; + $request->request['access_token'] = 'TEST'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'When putting the token in the body, the method must be POST or PUT'); + } + + public function testInvalidContentType() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->server['REQUEST_METHOD'] = 'POST'; + $request->server['CONTENT_TYPE'] = 'application/json'; + $request->request['access_token'] = 'TEST'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'The content type for POST requests must be "application/x-www-form-urlencoded"'); + } + + public function testInvalidToken() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'Bearer TESTTOKEN'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 401); + $this->assertEquals($response->getParameter('error'), 'invalid_token'); + $this->assertEquals($response->getParameter('error_description'), 'The access token provided is invalid'); + } + + public function testExpiredToken() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-expired'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 401); + $this->assertEquals($response->getParameter('error'), 'invalid_token'); + $this->assertEquals($response->getParameter('error_description'), 'The access token provided has expired'); + } + + public function testOutOfScopeToken() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-scope'; + $scope = 'outofscope'; + $allow = $server->verifyResourceRequest($request, $response = new Response(), $scope); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 403); + $this->assertEquals($response->getParameter('error'), 'insufficient_scope'); + $this->assertEquals($response->getParameter('error_description'), 'The request requires higher privileges than provided by the access token'); + + // verify the "scope" has been set in the "WWW-Authenticate" header + preg_match('/scope="(.*?)"/', $response->getHttpHeader('WWW-Authenticate'), $matches); + $this->assertEquals(2, count($matches)); + $this->assertEquals($matches[1], 'outofscope'); + } + + public function testMalformedToken() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-malformed'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 401); + $this->assertEquals($response->getParameter('error'), 'malformed_token'); + $this->assertEquals($response->getParameter('error_description'), 'Malformed token (missing "expires")'); + } + + public function testValidToken() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-scope'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertTrue($allow); + } + + public function testValidTokenWithScopeParam() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-scope'; + $request->query['scope'] = 'testscope'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertTrue($allow); + } + + public function testCreateController() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $tokenType = new \OAuth2\TokenType\Bearer(); + $controller = new ResourceController($tokenType, $storage); + } + + private function getTestServer($config = array()) + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, $config); + + // Add the two types supported for authorization grant + $server->addGrantType(new AuthorizationCode($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/TokenControllerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/TokenControllerTest.php new file mode 100644 index 0000000000..d18eaa6d72 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/TokenControllerTest.php @@ -0,0 +1,332 @@ +getTestServer(); + $server->handleTokenRequest(TestRequest::createPost(), $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'The grant type was not specified in the request'); + } + + public function testInvalidGrantType() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'invalid_grant_type', // invalid grant type + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'unsupported_grant_type'); + $this->assertEquals($response->getParameter('error_description'), 'Grant type "invalid_grant_type" not supported'); + } + + public function testNoClientId() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'Client credentials were not found in the headers or body'); + } + + public function testNoClientSecretWithConfidentialClient() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', + 'client_id' => 'Test Client ID', // valid client id + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'This client is invalid or must authenticate using a client secret'); + } + + public function testNoClientSecretWithEmptySecret() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode-empty-secret', + 'client_id' => 'Test Client ID Empty Secret', // valid client id + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 200); + } + + public function testInvalidClientId() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', + 'client_id' => 'Fake Client ID', // invalid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'The client credentials are invalid'); + } + + public function testInvalidClientSecret() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'Fake Client Secret', // invalid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'The client credentials are invalid'); + } + + public function testValidTokenResponse() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode', // valid authorization code + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertTrue($response instanceof Response); + $this->assertEquals($response->getStatusCode(), 200); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + $this->assertNotNull($response->getParameter('access_token')); + $this->assertNotNull($response->getParameter('expires_in')); + $this->assertNotNull($response->getParameter('token_type')); + } + + public function testValidClientIdScope() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'scope' => 'clientscope1 clientscope2' + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 200); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + $this->assertEquals('clientscope1 clientscope2', $response->getParameter('scope')); + } + + public function testInvalidClientIdScope() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode-with-scope', + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'scope' => 'clientscope3' + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'The scope requested is invalid for this request'); + } + + public function testEnforceScope() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage); + $server->addGrantType(new ClientCredentials($storage)); + + $scope = new Scope(array( + 'default_scope' => false, + 'supported_scopes' => array('testscope') + )); + $server->setScopeUtil($scope); + + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $response = $server->handleTokenRequest($request); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'This application requires you specify a scope parameter'); + } + + public function testCanReceiveAccessTokenUsingPasswordGrantTypeWithoutClientSecret() + { + // add the test parameters in memory + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage); + $server->addGrantType(new UserCredentials($storage)); + + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID For Password Grant', // valid client id + 'username' => 'johndoe', // valid username + 'password' => 'password', // valid password for username + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertTrue($response instanceof Response); + $this->assertEquals(200, $response->getStatusCode(), var_export($response, 1)); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + $this->assertNotNull($response->getParameter('access_token')); + $this->assertNotNull($response->getParameter('expires_in')); + $this->assertNotNull($response->getParameter('token_type')); + } + + public function testInvalidTokenTypeHintForRevoke() + { + $server = $this->getTestServer(); + + $request = TestRequest::createPost(array( + 'token_type_hint' => 'foo', + 'token' => 'sometoken' + )); + + $server->handleRevokeRequest($request, $response = new Response()); + + $this->assertTrue($response instanceof Response); + $this->assertEquals(400, $response->getStatusCode(), var_export($response, 1)); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Token type hint must be either \'access_token\' or \'refresh_token\''); + } + + public function testMissingTokenForRevoke() + { + $server = $this->getTestServer(); + + $request = TestRequest::createPost(array( + 'token_type_hint' => 'access_token' + )); + + $server->handleRevokeRequest($request, $response = new Response()); + $this->assertTrue($response instanceof Response); + $this->assertEquals(400, $response->getStatusCode(), var_export($response, 1)); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Missing token parameter to revoke'); + } + + public function testInvalidRequestMethodForRevoke() + { + $server = $this->getTestServer(); + + $request = new TestRequest(); + $request->setQuery(array( + 'token_type_hint' => 'access_token' + )); + + $server->handleRevokeRequest($request, $response = new Response()); + $this->assertTrue($response instanceof Response); + $this->assertEquals(405, $response->getStatusCode(), var_export($response, 1)); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'The request method must be POST when revoking an access token'); + } + + public function testCanUseCrossOriginRequestForRevoke() + { + $server = $this->getTestServer(); + + $request = new TestRequest(); + $request->setMethod('OPTIONS'); + + $server->handleRevokeRequest($request, $response = new Response()); + $this->assertTrue($response instanceof Response); + $this->assertEquals(200, $response->getStatusCode(), var_export($response, 1)); + $this->assertEquals($response->getHttpHeader('Allow'), 'POST, OPTIONS'); + } + + public function testInvalidRequestMethodForAccessToken() + { + $server = $this->getTestServer(); + + $request = new TestRequest(); + $request->setQuery(array( + 'token_type_hint' => 'access_token' + )); + + $server->handleTokenRequest($request, $response = new Response()); + $this->assertTrue($response instanceof Response); + $this->assertEquals(405, $response->getStatusCode(), var_export($response, 1)); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'The request method must be POST when requesting an access token'); + } + + public function testCanUseCrossOriginRequestForAccessToken() + { + $server = $this->getTestServer(); + + $request = new TestRequest(); + $request->setMethod('OPTIONS'); + + $server->handleTokenRequest($request, $response = new Response()); + $this->assertTrue($response instanceof Response); + $this->assertEquals(200, $response->getStatusCode(), var_export($response, 1)); + $this->assertEquals($response->getHttpHeader('Allow'), 'POST, OPTIONS'); + } + + public function testCreateController() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $accessToken = new \OAuth2\ResponseType\AccessToken($storage); + $controller = new TokenController($accessToken, $storage); + } + + private function getTestServer() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage); + $server->addGrantType(new AuthorizationCode($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/FirebaseJwtTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/FirebaseJwtTest.php new file mode 100644 index 0000000000..c7e92c0535 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/FirebaseJwtTest.php @@ -0,0 +1,103 @@ +privateKey = << $client_id, + 'exp' => time() + 1000, + 'iat' => time(), + 'sub' => 'testuser@ourdomain.com', + 'aud' => 'http://myapp.com/oauth/auth', + 'scope' => null, + ); + + $encoded = $jwtUtil->encode($params, $this->privateKey, 'RS256'); + + // test BC behaviour of trusting the algorithm in the header + $payload = $jwtUtil->decode($encoded, $client_key, array('RS256')); + $this->assertEquals($params, $payload); + + // test BC behaviour of not verifying by passing false + $payload = $jwtUtil->decode($encoded, $client_key, false); + $this->assertEquals($params, $payload); + + // test the new restricted algorithms header + $payload = $jwtUtil->decode($encoded, $client_key, array('RS256')); + $this->assertEquals($params, $payload); + } + + public function testInvalidJwt() + { + $jwtUtil = new FirebaseJwt(); + + $this->assertFalse($jwtUtil->decode('goob')); + $this->assertFalse($jwtUtil->decode('go.o.b')); + } + + /** @dataProvider provideClientCredentials */ + public function testInvalidJwtHeader($client_id, $client_key) + { + $jwtUtil = new FirebaseJwt(); + + $params = array( + 'iss' => $client_id, + 'exp' => time() + 1000, + 'iat' => time(), + 'sub' => 'testuser@ourdomain.com', + 'aud' => 'http://myapp.com/oauth/auth', + 'scope' => null, + ); + + // testing for algorithm tampering when only RSA256 signing is allowed + // @see https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/ + $tampered = $jwtUtil->encode($params, $client_key, 'HS256'); + + $payload = $jwtUtil->decode($tampered, $client_key, array('RS256')); + + $this->assertFalse($payload); + } + + public function provideClientCredentials() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $client_id = 'Test Client ID'; + $client_key = $storage->getClientKey($client_id, "testuser@ourdomain.com"); + + return array( + array($client_id, $client_key), + ); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/JwtTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/JwtTest.php new file mode 100644 index 0000000000..d73b4c9233 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/JwtTest.php @@ -0,0 +1,103 @@ +privateKey = << $client_id, + 'exp' => time() + 1000, + 'iat' => time(), + 'sub' => 'testuser@ourdomain.com', + 'aud' => 'http://myapp.com/oauth/auth', + 'scope' => null, + ); + + $encoded = $jwtUtil->encode($params, $this->privateKey, 'RS256'); + + // test BC behaviour of trusting the algorithm in the header + $payload = $jwtUtil->decode($encoded, $client_key); + $this->assertEquals($params, $payload); + + // test BC behaviour of not verifying by passing false + $payload = $jwtUtil->decode($encoded, $client_key, false); + $this->assertEquals($params, $payload); + + // test the new restricted algorithms header + $payload = $jwtUtil->decode($encoded, $client_key, array('RS256')); + $this->assertEquals($params, $payload); + } + + public function testInvalidJwt() + { + $jwtUtil = new Jwt(); + + $this->assertFalse($jwtUtil->decode('goob')); + $this->assertFalse($jwtUtil->decode('go.o.b')); + } + + /** @dataProvider provideClientCredentials */ + public function testInvalidJwtHeader($client_id, $client_key) + { + $jwtUtil = new Jwt(); + + $params = array( + 'iss' => $client_id, + 'exp' => time() + 1000, + 'iat' => time(), + 'sub' => 'testuser@ourdomain.com', + 'aud' => 'http://myapp.com/oauth/auth', + 'scope' => null, + ); + + // testing for algorithm tampering when only RSA256 signing is allowed + // @see https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/ + $tampered = $jwtUtil->encode($params, $client_key, 'HS256'); + + $payload = $jwtUtil->decode($tampered, $client_key, array('RS256')); + + $this->assertFalse($payload); + } + + public function provideClientCredentials() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $client_id = 'Test Client ID'; + $client_key = $storage->getClientKey($client_id, "testuser@ourdomain.com"); + + return array( + array($client_id, $client_key), + ); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/AuthorizationCodeTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/AuthorizationCodeTest.php new file mode 100644 index 0000000000..b2314ffc66 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/AuthorizationCodeTest.php @@ -0,0 +1,224 @@ +getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Missing parameter: "code" is required'); + } + + public function testInvalidCode() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'InvalidCode', // invalid authorization code + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Authorization code doesn\'t exist or is invalid for the client'); + } + + public function testCodeCannotBeUsedTwice() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode', // valid code + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 200); + $this->assertNotNull($response->getParameter('access_token')); + + // try to use the same code again + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Authorization code doesn\'t exist or is invalid for the client'); + } + + public function testExpiredCode() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-expired', // expired authorization code + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'The authorization code has expired'); + } + + public function testValidCode() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode', // valid code + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testValidRedirectUri() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://brentertainment.com/voil%C3%A0', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-redirect-uri', // valid code + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testValidCodeNoScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1 scope2'); + } + + public function testValidCodeSameScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + 'scope' => 'scope2 scope1', + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope2 scope1'); + } + + public function testValidCodeLessScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + 'scope' => 'scope1', + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidCodeDifferentScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + 'scope' => 'scope3', + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'The scope requested is invalid for this request'); + } + + public function testValidCodeInvalidScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + 'scope' => 'invalid-scope', + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'The scope requested is invalid for this request'); + } + + public function testValidClientDifferentCode() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Some Other Client', // valid client id + 'client_secret' => 'TestSecret3', // valid client secret + 'code' => 'testcode', // valid code + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'authorization_code doesn\'t exist or is invalid for the client'); + } + + private function getTestServer() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage); + $server->addGrantType(new AuthorizationCode($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/ClientCredentialsTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/ClientCredentialsTest.php new file mode 100644 index 0000000000..2a7d0eb3d2 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/ClientCredentialsTest.php @@ -0,0 +1,160 @@ +getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'FakeSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'The client credentials are invalid'); + } + + public function testValidCredentials() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('scope', $token); + $this->assertNull($token['scope']); + } + + public function testValidCredentialsWithScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'scope' => 'scope1', + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidCredentialsInvalidScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'scope' => 'invalid-scope', + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested'); + } + + public function testValidCredentialsInHeader() + { + // create with HTTP_AUTHORIZATION in header + $server = $this->getTestServer(); + $headers = array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('Test Client ID:TestSecret'), 'REQUEST_METHOD' => 'POST'); + $params = array('grant_type' => 'client_credentials'); + $request = new Request(array(), $params, array(), array(), array(), $headers); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertNotNull($token['access_token']); + + // create using PHP Authorization Globals + $headers = array('PHP_AUTH_USER' => 'Test Client ID', 'PHP_AUTH_PW' => 'TestSecret', 'REQUEST_METHOD' => 'POST'); + $params = array('grant_type' => 'client_credentials'); + $request = new Request(array(), $params, array(), array(), array(), $headers); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertNotNull($token['access_token']); + } + + public function testValidCredentialsInRequest() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertNotNull($token['access_token']); + } + + public function testValidCredentialsInQuerystring() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertNotNull($token['access_token']); + } + + public function testClientUserIdIsSetInAccessToken() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Client ID With User ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + + // verify the user_id was associated with the token + $storage = $server->getStorage('client'); + $token = $storage->getAccessToken($token['access_token']); + $this->assertNotNull($token); + $this->assertArrayHasKey('user_id', $token); + $this->assertEquals($token['user_id'], 'brent@brentertainment.com'); + } + + private function getTestServer() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage); + $server->addGrantType(new ClientCredentials($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/ImplicitTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/ImplicitTest.php new file mode 100644 index 0000000000..14bf980f12 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/ImplicitTest.php @@ -0,0 +1,144 @@ +getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'token', // invalid response type + )); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $this->assertEquals($query['error'], 'unsupported_response_type'); + $this->assertEquals($query['error_description'], 'implicit grant type not supported'); + } + + public function testUserDeniesAccessResponse() + { + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'token', // valid response type + 'state' => 'xyz', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $this->assertEquals($query['error'], 'access_denied'); + $this->assertEquals($query['error_description'], 'The user denied access to your application'); + } + + public function testSuccessfulRequestFragmentParameter() + { + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'token', // valid response type + 'state' => 'xyz', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + + $this->assertEquals('http', $parts['scheme']); // same as passed in to redirect_uri + $this->assertEquals('adobe.com', $parts['host']); // same as passed in to redirect_uri + $this->assertArrayHasKey('fragment', $parts); + $this->assertFalse(isset($parts['query'])); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['fragment'], $params); + $this->assertNotNull($params); + $this->assertArrayHasKey('access_token', $params); + $this->assertArrayHasKey('expires_in', $params); + $this->assertArrayHasKey('token_type', $params); + } + + public function testSuccessfulRequestReturnsStateParameter() + { + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'token', // valid response type + 'state' => 'test', // valid state string (just needs to be passed back to us) + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + $this->assertArrayHasKey('fragment', $parts); + parse_str($parts['fragment'], $params); + + $this->assertArrayHasKey('state', $params); + $this->assertEquals($params['state'], 'test'); + } + + public function testSuccessfulRequestStripsExtraParameters() + { + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com?fake=something', // valid redirect URI + 'response_type' => 'token', // valid response type + 'state' => 'test', // valid state string (just needs to be passed back to us) + 'fake' => 'something', // add extra param to querystring + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + $this->assertFalse(isset($parts['fake'])); + $this->assertArrayHasKey('fragment', $parts); + parse_str($parts['fragment'], $params); + + $this->assertFalse(isset($params['fake'])); + $this->assertArrayHasKey('state', $params); + $this->assertEquals($params['state'], 'test'); + } + + private function getTestServer($config = array()) + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, $config); + + // Add the two types supported for authorization grant + $server->addGrantType(new AuthorizationCode($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/JwtBearerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/JwtBearerTest.php new file mode 100644 index 0000000000..e60023b3ca --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/JwtBearerTest.php @@ -0,0 +1,361 @@ +privateKey = <<getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + )); + + //Get the jwt and break it + $jwt = $this->getJWT(); + $jwt = substr_replace($jwt, 'broken', 3, 6); + + $request->request['assertion'] = $jwt; + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'JWT is malformed'); + } + + public function testBrokenSignature() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + )); + + //Get the jwt and break signature + $jwt = $this->getJWT() . 'notSupposeToBeHere'; + $request->request['assertion'] = $jwt; + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'JWT failed signature verification'); + } + + public function testExpiredJWT() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + )); + + //Get an expired JWT + $jwt = $this->getJWT(1234); + $request->request['assertion'] = $jwt; + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'JWT has expired'); + } + + public function testBadExp() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + )); + + //Get an expired JWT + $jwt = $this->getJWT('badtimestamp'); + $request->request['assertion'] = $jwt; + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Expiration (exp) time must be a unix time stamp'); + } + + public function testNoAssert() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + )); + + //Do not pass the assert (JWT) + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Missing parameters: "assertion" required'); + } + + public function testNotBefore() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + )); + + //Get a future NBF + $jwt = $this->getJWT(null, time() + 10000); + $request->request['assertion'] = $jwt; + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'JWT cannot be used before the Not Before (nbf) time'); + } + + public function testBadNotBefore() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + )); + + //Get a non timestamp nbf + $jwt = $this->getJWT(null, 'notatimestamp'); + $request->request['assertion'] = $jwt; + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Not Before (nbf) time must be a unix time stamp'); + } + + public function testNonMatchingAudience() + { + $server = $this->getTestServer('http://google.com/oauth/o/auth'); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(), + )); + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Invalid audience (aud)'); + } + + public function testBadClientID() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, null, 'bad_client_id'), + )); + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Invalid issuer (iss) or subject (sub) provided'); + } + + public function testBadSubject() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, 'anotheruser@ourdomain,com'), + )); + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Invalid issuer (iss) or subject (sub) provided'); + } + + public function testMissingKey() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, null, 'Missing Key Cli,nt'), + )); + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Invalid issuer (iss) or subject (sub) provided'); + } + + public function testValidJwt() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(), // valid assertion + )); + + $token = $server->grantAccessToken($request, new Response()); + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testValidJwtWithScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, null, 'Test Client ID'), // valid assertion + 'scope' => 'scope1', // valid scope + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidJwtInvalidScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, null, 'Test Client ID'), // valid assertion + 'scope' => 'invalid-scope', // invalid scope + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested'); + } + + public function testValidJti() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, 'testuser@ourdomain.com', 'Test Client ID', 'unused_jti'), // valid assertion with invalid scope + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testInvalidJti() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(99999999900, null, 'testuser@ourdomain.com', 'Test Client ID', 'used_jti'), // valid assertion with invalid scope + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'JSON Token Identifier (jti) has already been used'); + } + + public function testJtiReplayAttack() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(99999999900, null, 'testuser@ourdomain.com', 'Test Client ID', 'totally_new_jti'), // valid assertion with invalid scope + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + + //Replay the same request + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'JSON Token Identifier (jti) has already been used'); + } + + /** + * Generates a JWT + * @param $exp The expiration date. If the current time is greater than the exp, the JWT is invalid. + * @param $nbf The "not before" time. If the current time is less than the nbf, the JWT is invalid. + * @param $sub The subject we are acting on behalf of. This could be the email address of the user in the system. + * @param $iss The issuer, usually the client_id. + * @return string + */ + private function getJWT($exp = null, $nbf = null, $sub = null, $iss = 'Test Client ID', $jti = null) + { + if (!$exp) { + $exp = time() + 1000; + } + + if (!$sub) { + $sub = "testuser@ourdomain.com"; + } + + $params = array( + 'iss' => $iss, + 'exp' => $exp, + 'iat' => time(), + 'sub' => $sub, + 'aud' => 'http://myapp.com/oauth/auth', + ); + + if ($nbf) { + $params['nbf'] = $nbf; + } + + if ($jti) { + $params['jti'] = $jti; + } + + $jwtUtil = new Jwt(); + + return $jwtUtil->encode($params, $this->privateKey, 'RS256'); + } + + private function getTestServer($audience = 'http://myapp.com/oauth/auth') + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage); + $server->addGrantType(new JwtBearer($storage, $audience, new Jwt())); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/RefreshTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/RefreshTokenTest.php new file mode 100644 index 0000000000..c964c6bbdb --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/RefreshTokenTest.php @@ -0,0 +1,205 @@ +getTestServer(); + $server->addGrantType(new RefreshToken($this->storage)); + + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Missing parameter: "refresh_token" is required'); + } + + public function testInvalidRefreshToken() + { + $server = $this->getTestServer(); + $server->addGrantType(new RefreshToken($this->storage)); + + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'fake-token', // invalid refresh token + )); + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Invalid refresh token'); + } + + public function testValidRefreshTokenWithNewRefreshTokenInResponse() + { + $server = $this->getTestServer(); + $server->addGrantType(new RefreshToken($this->storage, array('always_issue_new_refresh_token' => true))); + + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken', // valid refresh token + )); + $token = $server->grantAccessToken($request, new Response()); + $this->assertTrue(isset($token['refresh_token']), 'refresh token should always refresh'); + + $refresh_token = $this->storage->getRefreshToken($token['refresh_token']); + $this->assertNotNull($refresh_token); + $this->assertEquals($refresh_token['refresh_token'], $token['refresh_token']); + $this->assertEquals($refresh_token['client_id'], $request->request('client_id')); + $this->assertTrue($token['refresh_token'] != 'test-refreshtoken', 'the refresh token returned is not the one used'); + $used_token = $this->storage->getRefreshToken('test-refreshtoken'); + $this->assertFalse($used_token, 'the refresh token used is no longer valid'); + } + + public function testValidRefreshTokenDoesNotUnsetToken() + { + $server = $this->getTestServer(); + $server->addGrantType(new RefreshToken($this->storage, array( + 'always_issue_new_refresh_token' => true, + 'unset_refresh_token_after_use' => false, + ))); + + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken', // valid refresh token + )); + $token = $server->grantAccessToken($request, new Response()); + $this->assertTrue(isset($token['refresh_token']), 'refresh token should always refresh'); + + $used_token = $this->storage->getRefreshToken('test-refreshtoken'); + $this->assertNotNull($used_token, 'the refresh token used is still valid'); + } + + public function testValidRefreshTokenWithNoRefreshTokenInResponse() + { + $server = $this->getTestServer(); + $server->addGrantType(new RefreshToken($this->storage, array('always_issue_new_refresh_token' => false))); + + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken', // valid refresh token + )); + $token = $server->grantAccessToken($request, new Response()); + $this->assertFalse(isset($token['refresh_token']), 'refresh token should not be returned'); + + $used_token = $this->storage->getRefreshToken('test-refreshtoken'); + $this->assertNotNull($used_token, 'the refresh token used is still valid'); + } + + public function testValidRefreshTokenSameScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken-with-scope', // valid refresh token (with scope) + 'scope' => 'scope2 scope1', + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope2 scope1'); + } + + public function testValidRefreshTokenLessScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken-with-scope', // valid refresh token (with scope) + 'scope' => 'scope1', + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidRefreshTokenDifferentScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken-with-scope', // valid refresh token (with scope) + 'scope' => 'scope3', + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'The scope requested is invalid for this request'); + } + + public function testValidRefreshTokenInvalidScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken-with-scope', // valid refresh token (with scope) + 'scope' => 'invalid-scope', + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'The scope requested is invalid for this request'); + } + + public function testValidClientDifferentRefreshToken() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Some Other Client', // valid client id + 'client_secret' => 'TestSecret3', // valid client secret + 'refresh_token' => 'test-refreshtoken', // valid refresh token + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'refresh_token doesn\'t exist or is invalid for the client'); + } + + private function getTestServer() + { + $this->storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($this->storage); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/UserCredentialsTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/UserCredentialsTest.php new file mode 100644 index 0000000000..8b725e13a2 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/UserCredentialsTest.php @@ -0,0 +1,173 @@ +getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'password' => 'testpass', // valid password + )); + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Missing parameters: "username" and "password" required'); + } + + public function testNoPassword() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + )); + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Missing parameters: "username" and "password" required'); + } + + public function testInvalidUsername() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'fake-username', // valid username + 'password' => 'testpass', // valid password + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 401); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Invalid username and password combination'); + } + + public function testInvalidPassword() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + 'password' => 'fakepass', // invalid password + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 401); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Invalid username and password combination'); + } + + public function testValidCredentials() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testValidCredentialsWithScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + 'scope' => 'scope1', // valid scope + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidCredentialsInvalidScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + 'scope' => 'invalid-scope', + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested'); + } + + public function testNoSecretWithPublicClient() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID Empty Secret', // valid public client + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + )); + + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testNoSecretWithConfidentialClient() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid public client + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + )); + + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'This client is invalid or must authenticate using a client secret'); + } + + private function getTestServer() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage); + $server->addGrantType(new UserCredentials($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Controller/AuthorizeControllerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Controller/AuthorizeControllerTest.php new file mode 100644 index 0000000000..fdc16daba6 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Controller/AuthorizeControllerTest.php @@ -0,0 +1,183 @@ +getTestServer(); + + $response = new Response(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'id_token', + 'state' => 'af0ifjsldkj', + 'nonce' => 'n-0S6_WzA2Mj', + )); + + // Test valid id_token request + $server->handleAuthorizeRequest($request, $response, true); + + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['fragment'], $query); + + $this->assertEquals('n-0S6_WzA2Mj', $server->getAuthorizeController()->getNonce()); + $this->assertEquals($query['state'], 'af0ifjsldkj'); + + $this->assertArrayHasKey('id_token', $query); + $this->assertArrayHasKey('state', $query); + $this->assertArrayNotHasKey('access_token', $query); + $this->assertArrayNotHasKey('expires_in', $query); + $this->assertArrayNotHasKey('token_type', $query); + + // Test valid token id_token request + $request->query['response_type'] = 'id_token token'; + $server->handleAuthorizeRequest($request, $response, true); + + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['fragment'], $query); + + $this->assertEquals('n-0S6_WzA2Mj', $server->getAuthorizeController()->getNonce()); + $this->assertEquals($query['state'], 'af0ifjsldkj'); + + $this->assertArrayHasKey('access_token', $query); + $this->assertArrayHasKey('expires_in', $query); + $this->assertArrayHasKey('token_type', $query); + $this->assertArrayHasKey('state', $query); + $this->assertArrayHasKey('id_token', $query); + + // assert that with multiple-valued response types, order does not matter + $request->query['response_type'] = 'token id_token'; + $server->handleAuthorizeRequest($request, $response, true); + + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['fragment'], $query); + + $this->assertEquals('n-0S6_WzA2Mj', $server->getAuthorizeController()->getNonce()); + $this->assertEquals($query['state'], 'af0ifjsldkj'); + + $this->assertArrayHasKey('access_token', $query); + $this->assertArrayHasKey('expires_in', $query); + $this->assertArrayHasKey('token_type', $query); + $this->assertArrayHasKey('state', $query); + $this->assertArrayHasKey('id_token', $query); + + // assert that with multiple-valued response types with extra spaces do not matter + $request->query['response_type'] = ' token id_token '; + $server->handleAuthorizeRequest($request, $response, true); + + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['fragment'], $query); + + $this->assertEquals('n-0S6_WzA2Mj', $server->getAuthorizeController()->getNonce()); + $this->assertEquals($query['state'], 'af0ifjsldkj'); + + $this->assertArrayHasKey('access_token', $query); + $this->assertArrayHasKey('expires_in', $query); + $this->assertArrayHasKey('token_type', $query); + $this->assertArrayHasKey('state', $query); + $this->assertArrayHasKey('id_token', $query); + } + + public function testMissingNonce() + { + $server = $this->getTestServer(); + $authorize = $server->getAuthorizeController(); + + $response = new Response(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'id_token', + 'state' => 'xyz', + )); + + // Test missing nonce for 'id_token' response type + $server->handleAuthorizeRequest($request, $response, true); + $params = $response->getParameters(); + + $this->assertEquals($params['error'], 'invalid_nonce'); + $this->assertEquals($params['error_description'], 'This application requires you specify a nonce parameter'); + + // Test missing nonce for 'id_token token' response type + $request->query['response_type'] = 'id_token token'; + $server->handleAuthorizeRequest($request, $response, true); + $params = $response->getParameters(); + + $this->assertEquals($params['error'], 'invalid_nonce'); + $this->assertEquals($params['error_description'], 'This application requires you specify a nonce parameter'); + } + + public function testNotGrantedApplication() + { + $server = $this->getTestServer(); + + $response = new Response(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'id_token', + 'state' => 'af0ifjsldkj', + 'nonce' => 'n-0S6_WzA2Mj', + )); + + // Test not approved application + $server->handleAuthorizeRequest($request, $response, false); + + $params = $response->getParameters(); + + $this->assertEquals($params['error'], 'consent_required'); + $this->assertEquals($params['error_description'], 'The user denied access to your application'); + + // Test not approved application with prompt parameter + $request->query['prompt'] = 'none'; + $server->handleAuthorizeRequest($request, $response, false); + + $params = $response->getParameters(); + + $this->assertEquals($params['error'], 'login_required'); + $this->assertEquals($params['error_description'], 'The user must log in'); + + // Test not approved application with user_id set + $request->query['prompt'] = 'none'; + $server->handleAuthorizeRequest($request, $response, false, 'some-user-id'); + + $params = $response->getParameters(); + + $this->assertEquals($params['error'], 'interaction_required'); + $this->assertEquals($params['error_description'], 'The user must grant access to your application'); + } + + public function testNeedsIdToken() + { + $server = $this->getTestServer(); + $authorize = $server->getAuthorizeController(); + + $this->assertTrue($authorize->needsIdToken('openid')); + $this->assertTrue($authorize->needsIdToken('openid profile')); + $this->assertFalse($authorize->needsIdToken('')); + $this->assertFalse($authorize->needsIdToken('some-scope')); + } + + private function getTestServer($config = array()) + { + $config += array( + 'use_openid_connect' => true, + 'issuer' => 'phpunit', + 'allow_implicit' => true + ); + + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, $config); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Controller/UserInfoControllerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Controller/UserInfoControllerTest.php new file mode 100644 index 0000000000..62e84df57d --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Controller/UserInfoControllerTest.php @@ -0,0 +1,45 @@ +handleUserInfoRequest(new Request(), $response); + $this->assertEquals(401, $response->getStatusCode()); + } + + public function testValidToken() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-openid-connect'; + $response = new Response(); + + $server->handleUserInfoRequest($request, $response); + $parameters = $response->getParameters(); + $this->assertEquals($parameters['sub'], 'testuser'); + $this->assertEquals($parameters['email'], 'testuser@test.com'); + $this->assertEquals($parameters['email_verified'], true); + } + + private function getTestServer($config = array()) + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, $config); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/GrantType/AuthorizationCodeTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/GrantType/AuthorizationCodeTest.php new file mode 100644 index 0000000000..c89a1ae978 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/GrantType/AuthorizationCodeTest.php @@ -0,0 +1,58 @@ +getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-openid', // valid code + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('id_token', $token); + $this->assertEquals('test_id_token', $token['id_token']); + + // this is only true if "offline_access" was requested + $this->assertFalse(isset($token['refresh_token'])); + } + + public function testOfflineAccess() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-openid', // valid code + 'scope' => 'offline_access', // valid code + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('id_token', $token); + $this->assertEquals('test_id_token', $token['id_token']); + $this->assertTrue(isset($token['refresh_token'])); + } + + private function getTestServer() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, array('use_openid_connect' => true)); + $server->addGrantType(new AuthorizationCode($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/CodeIdTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/CodeIdTokenTest.php new file mode 100644 index 0000000000..7b892c9465 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/CodeIdTokenTest.php @@ -0,0 +1,183 @@ +getTestServer(); + + $request = new Request(array( + 'response_type' => 'code id_token', + 'redirect_uri' => 'http://adobe.com', + 'client_id' => 'Test Client ID', + 'scope' => 'openid', + 'state' => 'test', + 'nonce' => 'test', + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $this->assertNotContains('error', $location); + + $parts = parse_url($location); + $this->assertArrayHasKey('query', $parts); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['query'], $params); + $this->assertNotNull($params); + $this->assertArrayHasKey('id_token', $params); + $this->assertArrayHasKey('code', $params); + + // validate ID Token + $parts = explode('.', $params['id_token']); + foreach ($parts as &$part) { + // Each part is a base64url encoded json string. + $part = str_replace(array('-', '_'), array('+', '/'), $part); + $part = base64_decode($part); + $part = json_decode($part, true); + } + list($header, $claims, $signature) = $parts; + + $this->assertArrayHasKey('iss', $claims); + $this->assertArrayHasKey('sub', $claims); + $this->assertArrayHasKey('aud', $claims); + $this->assertArrayHasKey('iat', $claims); + $this->assertArrayHasKey('exp', $claims); + $this->assertArrayHasKey('auth_time', $claims); + $this->assertArrayHasKey('nonce', $claims); + + // only exists if an access token was granted along with the id_token + $this->assertArrayNotHasKey('at_hash', $claims); + + $this->assertEquals($claims['iss'], 'test'); + $this->assertEquals($claims['aud'], 'Test Client ID'); + $this->assertEquals($claims['nonce'], 'test'); + $duration = $claims['exp'] - $claims['iat']; + $this->assertEquals($duration, 3600); + } + + public function testUserClaimsWithUserId() + { + // add the test parameters in memory + $server = $this->getTestServer(); + + $request = new Request(array( + 'response_type' => 'code id_token', + 'redirect_uri' => 'http://adobe.com', + 'client_id' => 'Test Client ID', + 'scope' => 'openid email', + 'state' => 'test', + 'nonce' => 'test', + )); + + $userId = 'testuser'; + $server->handleAuthorizeRequest($request, $response = new Response(), true, $userId); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $this->assertNotContains('error', $location); + + $parts = parse_url($location); + $this->assertArrayHasKey('query', $parts); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['query'], $params); + $this->assertNotNull($params); + $this->assertArrayHasKey('id_token', $params); + $this->assertArrayHasKey('code', $params); + + // validate ID Token + $parts = explode('.', $params['id_token']); + foreach ($parts as &$part) { + // Each part is a base64url encoded json string. + $part = str_replace(array('-', '_'), array('+', '/'), $part); + $part = base64_decode($part); + $part = json_decode($part, true); + } + list($header, $claims, $signature) = $parts; + + $this->assertArrayHasKey('email', $claims); + $this->assertArrayHasKey('email_verified', $claims); + $this->assertNotNull($claims['email']); + $this->assertNotNull($claims['email_verified']); + } + + public function testUserClaimsWithoutUserId() + { + // add the test parameters in memory + $server = $this->getTestServer(); + + $request = new Request(array( + 'response_type' => 'code id_token', + 'redirect_uri' => 'http://adobe.com', + 'client_id' => 'Test Client ID', + 'scope' => 'openid email', + 'state' => 'test', + 'nonce' => 'test', + )); + + $userId = null; + $server->handleAuthorizeRequest($request, $response = new Response(), true, $userId); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $this->assertNotContains('error', $location); + + $parts = parse_url($location); + $this->assertArrayHasKey('query', $parts); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['query'], $params); + $this->assertNotNull($params); + $this->assertArrayHasKey('id_token', $params); + $this->assertArrayHasKey('code', $params); + + // validate ID Token + $parts = explode('.', $params['id_token']); + foreach ($parts as &$part) { + // Each part is a base64url encoded json string. + $part = str_replace(array('-', '_'), array('+', '/'), $part); + $part = base64_decode($part); + $part = json_decode($part, true); + } + list($header, $claims, $signature) = $parts; + + $this->assertArrayNotHasKey('email', $claims); + $this->assertArrayNotHasKey('email_verified', $claims); + } + + private function getTestServer($config = array()) + { + $config += array( + 'use_openid_connect' => true, + 'issuer' => 'test', + 'id_lifetime' => 3600, + 'allow_implicit' => true, + ); + + $memoryStorage = Bootstrap::getInstance()->getMemoryStorage(); + $memoryStorage->supportedScopes[] = 'email'; + $responseTypes = array( + 'code' => $code = new AuthorizationCode($memoryStorage), + 'id_token' => $idToken = new IdToken($memoryStorage, $memoryStorage, $config), + 'code id_token' => new CodeIdToken($code, $idToken), + ); + + $server = new Server($memoryStorage, $config, array(), $responseTypes); + $server->addGrantType(new ClientCredentials($memoryStorage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTest.php new file mode 100644 index 0000000000..a0df3a936e --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTest.php @@ -0,0 +1,185 @@ + 'id_token', + 'redirect_uri' => 'http://adobe.com', + 'client_id' => 'Test Client ID', + 'scope' => 'openid', + 'state' => 'test', + ); + + // attempt to do the request without a nonce. + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request($query); + $valid = $server->validateAuthorizeRequest($request, $response = new Response()); + + // Add a nonce and retry. + $query['nonce'] = 'test'; + $request = new Request($query); + $valid = $server->validateAuthorizeRequest($request, $response = new Response()); + $this->assertTrue($valid); + } + + public function testHandleAuthorizeRequest() + { + // add the test parameters in memory + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'response_type' => 'id_token', + 'redirect_uri' => 'http://adobe.com', + 'client_id' => 'Test Client ID', + 'scope' => 'openid email', + 'state' => 'test', + 'nonce' => 'test', + )); + + $user_id = 'testuser'; + $server->handleAuthorizeRequest($request, $response = new Response(), true, $user_id); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $this->assertNotContains('error', $location); + + $parts = parse_url($location); + $this->assertArrayHasKey('fragment', $parts); + $this->assertFalse(isset($parts['query'])); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['fragment'], $params); + $this->assertNotNull($params); + $this->assertArrayHasKey('id_token', $params); + $this->assertArrayNotHasKey('access_token', $params); + $this->validateIdToken($params['id_token']); + } + + public function testPassInAuthTime() + { + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'response_type' => 'id_token', + 'redirect_uri' => 'http://adobe.com', + 'client_id' => 'Test Client ID', + 'scope' => 'openid email', + 'state' => 'test', + 'nonce' => 'test', + )); + + // test with a scalar user id + $user_id = 'testuser123'; + $server->handleAuthorizeRequest($request, $response = new Response(), true, $user_id); + + list($header, $payload, $signature) = $this->extractTokenDataFromResponse($response); + + $this->assertTrue(is_array($payload)); + $this->assertArrayHasKey('sub', $payload); + $this->assertEquals($user_id, $payload['sub']); + $this->assertArrayHasKey('auth_time', $payload); + + // test with an array of user info + $userInfo = array( + 'user_id' => 'testuser1234', + 'auth_time' => date('Y-m-d H:i:s', strtotime('20 minutes ago') + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true, $userInfo); + + list($header, $payload, $signature) = $this->extractTokenDataFromResponse($response); + + $this->assertTrue(is_array($payload)); + $this->assertArrayHasKey('sub', $payload); + $this->assertEquals($userInfo['user_id'], $payload['sub']); + $this->assertArrayHasKey('auth_time', $payload); + $this->assertEquals($userInfo['auth_time'], $payload['auth_time']); + } + + private function extractTokenDataFromResponse(Response $response) + { + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $this->assertNotContains('error', $location); + + $parts = parse_url($location); + $this->assertArrayHasKey('fragment', $parts); + $this->assertFalse(isset($parts['query'])); + + parse_str($parts['fragment'], $params); + $this->assertNotNull($params); + $this->assertArrayHasKey('id_token', $params); + $this->assertArrayNotHasKey('access_token', $params); + + list($headb64, $payloadb64, $signature) = explode('.', $params['id_token']); + + $jwt = new Jwt(); + $header = json_decode($jwt->urlSafeB64Decode($headb64), true); + $payload = json_decode($jwt->urlSafeB64Decode($payloadb64), true); + + return array($header, $payload, $signature); + } + + private function validateIdToken($id_token) + { + $parts = explode('.', $id_token); + foreach ($parts as &$part) { + // Each part is a base64url encoded json string. + $part = str_replace(array('-', '_'), array('+', '/'), $part); + $part = base64_decode($part); + $part = json_decode($part, true); + } + list($header, $claims, $signature) = $parts; + + $this->assertArrayHasKey('iss', $claims); + $this->assertArrayHasKey('sub', $claims); + $this->assertArrayHasKey('aud', $claims); + $this->assertArrayHasKey('iat', $claims); + $this->assertArrayHasKey('exp', $claims); + $this->assertArrayHasKey('auth_time', $claims); + $this->assertArrayHasKey('nonce', $claims); + $this->assertArrayHasKey('email', $claims); + $this->assertArrayHasKey('email_verified', $claims); + + $this->assertEquals($claims['iss'], 'test'); + $this->assertEquals($claims['aud'], 'Test Client ID'); + $this->assertEquals($claims['nonce'], 'test'); + $this->assertEquals($claims['email'], 'testuser@test.com'); + $duration = $claims['exp'] - $claims['iat']; + $this->assertEquals($duration, 3600); + } + + private function getTestServer($config = array()) + { + $config += array( + 'use_openid_connect' => true, + 'issuer' => 'test', + 'id_lifetime' => 3600, + ); + + $memoryStorage = Bootstrap::getInstance()->getMemoryStorage(); + $memoryStorage->supportedScopes[] = 'email'; + $storage = array( + 'client' => $memoryStorage, + 'scope' => $memoryStorage, + ); + $responseTypes = array( + 'id_token' => new IdToken($memoryStorage, $memoryStorage, $config), + ); + + $server = new Server($storage, $config, array(), $responseTypes); + $server->addGrantType(new ClientCredentials($memoryStorage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTokenTest.php new file mode 100644 index 0000000000..0573a98661 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTokenTest.php @@ -0,0 +1,92 @@ +getTestServer(array('allow_implicit' => true)); + + $request = new Request(array( + 'response_type' => 'id_token token', + 'redirect_uri' => 'http://adobe.com', + 'client_id' => 'Test Client ID', + 'scope' => 'openid', + 'state' => 'test', + 'nonce' => 'test', + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $this->assertNotContains('error', $location); + + $parts = parse_url($location); + $this->assertArrayHasKey('fragment', $parts); + $this->assertFalse(isset($parts['query'])); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['fragment'], $params); + $this->assertNotNull($params); + $this->assertArrayHasKey('id_token', $params); + $this->assertArrayHasKey('access_token', $params); + + // validate ID Token + $parts = explode('.', $params['id_token']); + foreach ($parts as &$part) { + // Each part is a base64url encoded json string. + $part = str_replace(array('-', '_'), array('+', '/'), $part); + $part = base64_decode($part); + $part = json_decode($part, true); + } + list($header, $claims, $signature) = $parts; + + $this->assertArrayHasKey('iss', $claims); + $this->assertArrayHasKey('sub', $claims); + $this->assertArrayHasKey('aud', $claims); + $this->assertArrayHasKey('iat', $claims); + $this->assertArrayHasKey('exp', $claims); + $this->assertArrayHasKey('auth_time', $claims); + $this->assertArrayHasKey('nonce', $claims); + $this->assertArrayHasKey('at_hash', $claims); + + $this->assertEquals($claims['iss'], 'test'); + $this->assertEquals($claims['aud'], 'Test Client ID'); + $this->assertEquals($claims['nonce'], 'test'); + $duration = $claims['exp'] - $claims['iat']; + $this->assertEquals($duration, 3600); + } + + private function getTestServer($config = array()) + { + $config += array( + 'use_openid_connect' => true, + 'issuer' => 'test', + 'id_lifetime' => 3600, + ); + + $memoryStorage = Bootstrap::getInstance()->getMemoryStorage(); + $responseTypes = array( + 'token' => $token = new AccessToken($memoryStorage, $memoryStorage), + 'id_token' => $idToken = new IdToken($memoryStorage, $memoryStorage, $config), + 'id_token token' => new IdTokenToken($token, $idToken), + ); + + $server = new Server($memoryStorage, $config, array(), $responseTypes); + $server->addGrantType(new ClientCredentials($memoryStorage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Storage/AuthorizationCodeTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Storage/AuthorizationCodeTest.php new file mode 100644 index 0000000000..bdfb085e3c --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Storage/AuthorizationCodeTest.php @@ -0,0 +1,95 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + if (!$storage instanceof AuthorizationCodeInterface) { + return; + } + + // assert code we are about to add does not exist + $code = $storage->getAuthorizationCode('new-openid-code'); + $this->assertFalse($code); + + // add new code + $expires = time() + 20; + $scope = null; + $id_token = 'fake_id_token'; + $success = $storage->setAuthorizationCode('new-openid-code', 'client ID', 'SOMEUSERID', 'http://example.com', $expires, $scope, $id_token); + $this->assertTrue($success); + + $code = $storage->getAuthorizationCode('new-openid-code'); + $this->assertNotNull($code); + $this->assertArrayHasKey('authorization_code', $code); + $this->assertArrayHasKey('client_id', $code); + $this->assertArrayHasKey('user_id', $code); + $this->assertArrayHasKey('redirect_uri', $code); + $this->assertArrayHasKey('expires', $code); + $this->assertEquals($code['authorization_code'], 'new-openid-code'); + $this->assertEquals($code['client_id'], 'client ID'); + $this->assertEquals($code['user_id'], 'SOMEUSERID'); + $this->assertEquals($code['redirect_uri'], 'http://example.com'); + $this->assertEquals($code['expires'], $expires); + $this->assertEquals($code['id_token'], $id_token); + + // change existing code + $expires = time() + 42; + $new_id_token = 'fake_id_token-2'; + $success = $storage->setAuthorizationCode('new-openid-code', 'client ID2', 'SOMEOTHERID', 'http://example.org', $expires, $scope, $new_id_token); + $this->assertTrue($success); + + $code = $storage->getAuthorizationCode('new-openid-code'); + $this->assertNotNull($code); + $this->assertArrayHasKey('authorization_code', $code); + $this->assertArrayHasKey('client_id', $code); + $this->assertArrayHasKey('user_id', $code); + $this->assertArrayHasKey('redirect_uri', $code); + $this->assertArrayHasKey('expires', $code); + $this->assertEquals($code['authorization_code'], 'new-openid-code'); + $this->assertEquals($code['client_id'], 'client ID2'); + $this->assertEquals($code['user_id'], 'SOMEOTHERID'); + $this->assertEquals($code['redirect_uri'], 'http://example.org'); + $this->assertEquals($code['expires'], $expires); + $this->assertEquals($code['id_token'], $new_id_token); + } + + /** @dataProvider provideStorage */ + public function testRemoveIdTokenFromAuthorizationCode($storage) + { + // add new code + $expires = time() + 20; + $scope = null; + $id_token = 'fake_id_token_to_remove'; + $authcode = 'new-openid-code-'.rand(); + $success = $storage->setAuthorizationCode($authcode, 'client ID', 'SOMEUSERID', 'http://example.com', $expires, $scope, $id_token); + $this->assertTrue($success); + + // verify params were set + $code = $storage->getAuthorizationCode($authcode); + $this->assertNotNull($code); + $this->assertArrayHasKey('id_token', $code); + $this->assertEquals($code['id_token'], $id_token); + + // remove the id_token + $success = $storage->setAuthorizationCode($authcode, 'client ID', 'SOMEUSERID', 'http://example.com', $expires, $scope, null); + + // verify the "id_token" is now null + $code = $storage->getAuthorizationCode($authcode); + $this->assertNotNull($code); + $this->assertArrayHasKey('id_token', $code); + $this->assertEquals($code['id_token'], null); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Storage/UserClaimsTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Storage/UserClaimsTest.php new file mode 100644 index 0000000000..840f6c566f --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Storage/UserClaimsTest.php @@ -0,0 +1,41 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + if (!$storage instanceof UserClaimsInterface) { + // incompatible storage + return; + } + + // invalid user + $claims = $storage->getUserClaims('fake-user', ''); + $this->assertFalse($claims); + + // valid user (no scope) + $claims = $storage->getUserClaims('testuser', ''); + + /* assert the decoded token is the same */ + $this->assertFalse(isset($claims['email'])); + + // valid user + $claims = $storage->getUserClaims('testuser', 'email'); + + /* assert the decoded token is the same */ + $this->assertEquals($claims['email'], "testuser@test.com"); + $this->assertEquals($claims['email_verified'], true); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/RequestTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/RequestTest.php new file mode 100644 index 0000000000..770cd89943 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/RequestTest.php @@ -0,0 +1,117 @@ +getTestServer(); + + // Smoke test for override request class + // $server->handleTokenRequest($request, $response = new Response()); + // $this->assertInstanceOf('Response', $response); + // $server->handleAuthorizeRequest($request, $response = new Response(), true); + // $this->assertInstanceOf('Response', $response); + // $response = $server->verifyResourceRequest($request, $response = new Response()); + // $this->assertTrue(is_bool($response)); + + /*** make some valid requests ***/ + + // Valid Token Request + $request->setPost(array( + 'grant_type' => 'authorization_code', + 'client_id' => 'Test Client ID', + 'client_secret' => 'TestSecret', + 'code' => 'testcode', + )); + $server->handleTokenRequest($request, $response = new Response()); + $this->assertEquals($response->getStatusCode(), 200); + $this->assertNull($response->getParameter('error')); + $this->assertNotNUll($response->getParameter('access_token')); + } + + public function testHeadersReturnsValueByKey() + { + $request = new Request( + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array('AUTHORIZATION' => 'Basic secret') + ); + + $this->assertEquals('Basic secret', $request->headers('AUTHORIZATION')); + } + + public function testHeadersReturnsDefaultIfHeaderNotPresent() + { + $request = new Request(); + + $this->assertEquals('Bearer', $request->headers('AUTHORIZATION', 'Bearer')); + } + + public function testHeadersIsCaseInsensitive() + { + $request = new Request( + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array('AUTHORIZATION' => 'Basic secret') + ); + + $this->assertEquals('Basic secret', $request->headers('Authorization')); + } + + public function testRequestReturnsPostParamIfNoQueryParamAvailable() + { + $request = new Request( + array(), + array('client_id' => 'correct') + ); + + $this->assertEquals('correct', $request->query('client_id', $request->request('client_id'))); + } + + public function testRequestHasHeadersAndServerHeaders() + { + $request = new Request( + array(), + array(), + array(), + array(), + array(), + array('CONTENT_TYPE' => 'text/xml', 'PHP_AUTH_USER' => 'client_id', 'PHP_AUTH_PW' => 'client_pass'), + null, + array('CONTENT_TYPE' => 'application/json') + ); + + $this->assertSame('client_id', $request->headers('PHP_AUTH_USER')); + $this->assertSame('client_pass', $request->headers('PHP_AUTH_PW')); + $this->assertSame('application/json', $request->headers('CONTENT_TYPE')); + } + + private function getTestServer($config = array()) + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, $config); + + // Add the two types supported for authorization grant + $server->addGrantType(new AuthorizationCode($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseTest.php new file mode 100644 index 0000000000..172bc88fd4 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseTest.php @@ -0,0 +1,38 @@ + 'bar', + 'halland' => 'oates', + )); + + $string = $response->getResponseBody('xml'); + $this->assertContains('baroates', $string); + } + + public function testSetRedirect() + { + $response = new Response(); + $url = 'https://foo/bar'; + $state = 'stateparam'; + $response->setRedirect(301, $url, $state); + $this->assertEquals( + sprintf('%s?state=%s', $url, $state), + $response->getHttpHeader('Location') + ); + + $query = 'query=foo'; + $response->setRedirect(301, $url . '?' . $query, $state); + $this->assertEquals( + sprintf('%s?%s&state=%s', $url, $query, $state), + $response->getHttpHeader('Location') + ); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/AccessTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/AccessTokenTest.php new file mode 100644 index 0000000000..43b02254f2 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/AccessTokenTest.php @@ -0,0 +1,108 @@ + array( + 'revoke' => array('mytoken'), + ), + )); + + $this->assertEquals(array('mytoken'), $tokenStorage->getAccessToken('revoke')); + $accessToken = new AccessToken($tokenStorage); + $accessToken->revokeToken('revoke', 'access_token'); + $this->assertFalse($tokenStorage->getAccessToken('revoke')); + } + + public function testRevokeAccessTokenWithoutTypeHint() + { + $tokenStorage = new Memory(array( + 'access_tokens' => array( + 'revoke' => array('mytoken'), + ), + )); + + $this->assertEquals(array('mytoken'), $tokenStorage->getAccessToken('revoke')); + $accessToken = new AccessToken($tokenStorage); + $accessToken->revokeToken('revoke'); + $this->assertFalse($tokenStorage->getAccessToken('revoke')); + } + + public function testRevokeRefreshTokenWithTypeHint() + { + $tokenStorage = new Memory(array( + 'refresh_tokens' => array( + 'revoke' => array('mytoken'), + ), + )); + + $this->assertEquals(array('mytoken'), $tokenStorage->getRefreshToken('revoke')); + $accessToken = new AccessToken(new Memory, $tokenStorage); + $accessToken->revokeToken('revoke', 'refresh_token'); + $this->assertFalse($tokenStorage->getRefreshToken('revoke')); + } + + public function testRevokeRefreshTokenWithoutTypeHint() + { + $tokenStorage = new Memory(array( + 'refresh_tokens' => array( + 'revoke' => array('mytoken'), + ), + )); + + $this->assertEquals(array('mytoken'), $tokenStorage->getRefreshToken('revoke')); + $accessToken = new AccessToken(new Memory, $tokenStorage); + $accessToken->revokeToken('revoke'); + $this->assertFalse($tokenStorage->getRefreshToken('revoke')); + } + + public function testRevokeAccessTokenWithRefreshTokenTypeHint() + { + $tokenStorage = new Memory(array( + 'access_tokens' => array( + 'revoke' => array('mytoken'), + ), + )); + + $this->assertEquals(array('mytoken'), $tokenStorage->getAccessToken('revoke')); + $accessToken = new AccessToken($tokenStorage); + $accessToken->revokeToken('revoke', 'refresh_token'); + $this->assertFalse($tokenStorage->getAccessToken('revoke')); + } + + public function testRevokeAccessTokenWithBogusTypeHint() + { + $tokenStorage = new Memory(array( + 'access_tokens' => array( + 'revoke' => array('mytoken'), + ), + )); + + $this->assertEquals(array('mytoken'), $tokenStorage->getAccessToken('revoke')); + $accessToken = new AccessToken($tokenStorage); + $accessToken->revokeToken('revoke', 'foo'); + $this->assertFalse($tokenStorage->getAccessToken('revoke')); + } + + public function testRevokeRefreshTokenWithBogusTypeHint() + { + $tokenStorage = new Memory(array( + 'refresh_tokens' => array( + 'revoke' => array('mytoken'), + ), + )); + + $this->assertEquals(array('mytoken'), $tokenStorage->getRefreshToken('revoke')); + $accessToken = new AccessToken(new Memory, $tokenStorage); + $accessToken->revokeToken('revoke', 'foo'); + $this->assertFalse($tokenStorage->getRefreshToken('revoke')); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/JwtAccessTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/JwtAccessTokenTest.php new file mode 100644 index 0000000000..6195d557a2 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/JwtAccessTokenTest.php @@ -0,0 +1,178 @@ +getTestServer(); + $jwtResponseType = $server->getResponseType('token'); + + $accessToken = $jwtResponseType->createAccessToken('Test Client ID', 123, 'test', false); + $jwt = new Jwt; + $decodedAccessToken = $jwt->decode($accessToken['access_token'], null, false); + + $this->assertArrayHasKey('id', $decodedAccessToken); + $this->assertArrayHasKey('jti', $decodedAccessToken); + $this->assertArrayHasKey('iss', $decodedAccessToken); + $this->assertArrayHasKey('aud', $decodedAccessToken); + $this->assertArrayHasKey('exp', $decodedAccessToken); + $this->assertArrayHasKey('iat', $decodedAccessToken); + $this->assertArrayHasKey('token_type', $decodedAccessToken); + $this->assertArrayHasKey('scope', $decodedAccessToken); + + $this->assertEquals('https://api.example.com', $decodedAccessToken['iss']); + $this->assertEquals('Test Client ID', $decodedAccessToken['aud']); + $this->assertEquals(123, $decodedAccessToken['sub']); + $delta = $decodedAccessToken['exp'] - $decodedAccessToken['iat']; + $this->assertEquals(3600, $delta); + $this->assertEquals($decodedAccessToken['id'], $decodedAccessToken['jti']); + } + + public function testExtraPayloadCallback() + { + $jwtconfig = array('jwt_extra_payload_callable' => function() { + return array('custom_param' => 'custom_value'); + }); + + $server = $this->getTestServer($jwtconfig); + $jwtResponseType = $server->getResponseType('token'); + + $accessToken = $jwtResponseType->createAccessToken('Test Client ID', 123, 'test', false); + $jwt = new Jwt; + $decodedAccessToken = $jwt->decode($accessToken['access_token'], null, false); + + $this->assertArrayHasKey('custom_param', $decodedAccessToken); + $this->assertEquals('custom_value', $decodedAccessToken['custom_param']); + } + + public function testGrantJwtAccessToken() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertNotNull($response->getParameter('access_token')); + $this->assertEquals(2, substr_count($response->getParameter('access_token'), '.')); + } + + public function testAccessResourceWithJwtAccessToken() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + $this->assertNotNull($JwtAccessToken = $response->getParameter('access_token')); + + // make a call to the resource server using the crypto token + $request = TestRequest::createPost(array( + 'access_token' => $JwtAccessToken, + )); + + $this->assertTrue($server->verifyResourceRequest($request)); + } + + public function testAccessResourceWithJwtAccessTokenUsingSecondaryStorage() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + $this->assertNotNull($JwtAccessToken = $response->getParameter('access_token')); + + // make a call to the resource server using the crypto token + $request = TestRequest::createPost(array( + 'access_token' => $JwtAccessToken, + )); + + // create a resource server with the "memory" storage from the grant server + $resourceServer = new Server($server->getStorage('client_credentials')); + + $this->assertTrue($resourceServer->verifyResourceRequest($request)); + } + + public function testJwtAccessTokenWithRefreshToken() + { + $server = $this->getTestServer(); + + // add "UserCredentials" grant type and "JwtAccessToken" response type + // and ensure "JwtAccessToken" response type has "RefreshToken" storage + $memoryStorage = Bootstrap::getInstance()->getMemoryStorage(); + $server->addGrantType(new UserCredentials($memoryStorage)); + $server->addGrantType(new RefreshToken($memoryStorage)); + $server->addResponseType(new JwtAccessToken($memoryStorage, $memoryStorage, $memoryStorage), 'token'); + + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + )); + + // make the call to grant a crypto token + $server->handleTokenRequest($request, $response = new Response()); + $this->assertNotNull($JwtAccessToken = $response->getParameter('access_token')); + $this->assertNotNull($refreshToken = $response->getParameter('refresh_token')); + + // decode token and make sure refresh_token isn't set + list($header, $payload, $signature) = explode('.', $JwtAccessToken); + $decodedToken = json_decode(base64_decode($payload), true); + $this->assertFalse(array_key_exists('refresh_token', $decodedToken)); + + // use the refresh token to get another access token + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => $refreshToken, + )); + + $server->handleTokenRequest($request, $response = new Response()); + $this->assertNotNull($response->getParameter('access_token')); + } + + private function getTestServer($jwtconfig = array()) + { + $memoryStorage = Bootstrap::getInstance()->getMemoryStorage(); + + $storage = array( + 'access_token' => new JwtAccessTokenStorage($memoryStorage), + 'client' => $memoryStorage, + 'client_credentials' => $memoryStorage, + ); + $server = new Server($storage); + $server->addGrantType(new ClientCredentials($memoryStorage)); + + // make the "token" response type a JwtAccessToken + $config = array_merge(array('issuer' => 'https://api.example.com'), $jwtconfig); + $server->addResponseType(new JwtAccessToken($memoryStorage, $memoryStorage, null, $config)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ScopeTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ScopeTest.php new file mode 100644 index 0000000000..5b51be1d11 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ScopeTest.php @@ -0,0 +1,43 @@ +assertFalse($scopeUtil->checkScope('invalid', 'list of scopes')); + $this->assertTrue($scopeUtil->checkScope('valid', 'valid and-some other-scopes')); + $this->assertTrue($scopeUtil->checkScope('valid another-valid', 'valid another-valid and-some other-scopes')); + // all scopes must match + $this->assertFalse($scopeUtil->checkScope('valid invalid', 'valid and-some other-scopes')); + $this->assertFalse($scopeUtil->checkScope('valid valid2 invalid', 'valid valid2 and-some other-scopes')); + } + + public function testScopeStorage() + { + $scopeUtil = new Scope(); + $this->assertEquals($scopeUtil->getDefaultScope(), null); + + $scopeUtil = new Scope(array( + 'default_scope' => 'default', + 'supported_scopes' => array('this', 'that', 'another'), + )); + $this->assertEquals($scopeUtil->getDefaultScope(), 'default'); + $this->assertTrue($scopeUtil->scopeExists('this that another', 'client_id')); + + $memoryStorage = new Memory(array( + 'default_scope' => 'base', + 'supported_scopes' => array('only-this-one'), + )); + $scopeUtil = new Scope($memoryStorage); + + $this->assertEquals($scopeUtil->getDefaultScope(), 'base'); + $this->assertTrue($scopeUtil->scopeExists('only-this-one', 'client_id')); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ServerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ServerTest.php new file mode 100644 index 0000000000..3106961e26 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ServerTest.php @@ -0,0 +1,685 @@ +getAuthorizeController(); + } + + /** + * @expectedException LogicException OAuth2\Storage\AccessTokenInterface + **/ + public function testGetAuthorizeControllerWithNoAccessTokenStorageThrowsException() + { + // must set AccessToken or AuthorizationCode + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); + $server->getAuthorizeController(); + } + + public function testGetAuthorizeControllerWithClientStorageAndAccessTokenResponseType() + { + // must set AccessToken or AuthorizationCode + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); + $server->addResponseType($this->getMock('OAuth2\ResponseType\AccessTokenInterface')); + + $this->assertNotNull($server->getAuthorizeController()); + } + + public function testGetAuthorizeControllerWithClientStorageAndAuthorizationCodeResponseType() + { + // must set AccessToken or AuthorizationCode + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); + $server->addResponseType($this->getMock('OAuth2\ResponseType\AuthorizationCodeInterface')); + + $this->assertNotNull($server->getAuthorizeController()); + } + + /** + * @expectedException LogicException allow_implicit + **/ + public function testGetAuthorizeControllerWithClientStorageAndAccessTokenStorageThrowsException() + { + // must set AuthorizationCode or AccessToken / implicit + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); + $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); + + $this->assertNotNull($server->getAuthorizeController()); + } + + public function testGetAuthorizeControllerWithClientStorageAndAccessTokenStorage() + { + // must set AuthorizationCode or AccessToken / implicit + $server = new Server(array(), array('allow_implicit' => true)); + $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); + $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); + + $this->assertNotNull($server->getAuthorizeController()); + } + + public function testGetAuthorizeControllerWithClientStorageAndAuthorizationCodeStorage() + { + // must set AccessToken or AuthorizationCode + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); + $server->addStorage($this->getMock('OAuth2\Storage\AuthorizationCodeInterface')); + + $this->assertNotNull($server->getAuthorizeController()); + } + + /** + * @expectedException LogicException grant_types + **/ + public function testGetTokenControllerWithGrantTypeStorageThrowsException() + { + $server = new Server(); + $server->getTokenController(); + } + + /** + * @expectedException LogicException OAuth2\Storage\ClientCredentialsInterface + **/ + public function testGetTokenControllerWithNoClientCredentialsStorageThrowsException() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\UserCredentialsInterface')); + $server->getTokenController(); + } + + /** + * @expectedException LogicException OAuth2\Storage\AccessTokenInterface + **/ + public function testGetTokenControllerWithNoAccessTokenStorageThrowsException() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); + $server->getTokenController(); + } + + public function testGetTokenControllerWithAccessTokenAndClientCredentialsStorage() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); + $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); + $server->getTokenController(); + } + + public function testGetTokenControllerAccessTokenStorageAndClientCredentialsStorageAndGrantTypes() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); + $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); + $server->addGrantType($this->getMockBuilder('OAuth2\GrantType\AuthorizationCode')->disableOriginalConstructor()->getMock()); + $server->getTokenController(); + } + + /** + * @expectedException LogicException OAuth2\Storage\AccessTokenInterface + **/ + public function testGetResourceControllerWithNoAccessTokenStorageThrowsException() + { + $server = new Server(); + $server->getResourceController(); + } + + public function testGetResourceControllerWithAccessTokenStorage() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); + $server->getResourceController(); + } + + /** + * @expectedException InvalidArgumentException OAuth2\Storage\AccessTokenInterface + **/ + public function testAddingStorageWithInvalidClass() + { + $server = new Server(); + $server->addStorage(new \StdClass()); + } + + /** + * @expectedException InvalidArgumentException access_token + **/ + public function testAddingStorageWithInvalidKey() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface'), 'nonexistant_storage'); + } + + /** + * @expectedException InvalidArgumentException OAuth2\Storage\AuthorizationCodeInterface + **/ + public function testAddingStorageWithInvalidKeyStorageCombination() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface'), 'authorization_code'); + } + + public function testAddingStorageWithValidKeyOnlySetsThatKey() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\Memory'), 'access_token'); + + $reflection = new \ReflectionClass($server); + $prop = $reflection->getProperty('storages'); + $prop->setAccessible(true); + + $storages = $prop->getValue($server); // get the private "storages" property + + $this->assertEquals(1, count($storages)); + $this->assertTrue(isset($storages['access_token'])); + $this->assertFalse(isset($storages['authorization_code'])); + } + + public function testAddingClientStorageSetsClientCredentialsStorageByDefault() + { + $server = new Server(); + $memory = $this->getMock('OAuth2\Storage\Memory'); + $server->addStorage($memory, 'client'); + + $client_credentials = $server->getStorage('client_credentials'); + + $this->assertNotNull($client_credentials); + $this->assertEquals($client_credentials, $memory); + } + + public function testAddStorageWithNullValue() + { + $memory = $this->getMock('OAuth2\Storage\Memory'); + $server = new Server($memory); + $server->addStorage(null, 'refresh_token'); + + $client_credentials = $server->getStorage('client_credentials'); + + $this->assertNotNull($client_credentials); + $this->assertEquals($client_credentials, $memory); + + $refresh_token = $server->getStorage('refresh_token'); + + $this->assertNull($refresh_token); + } + + public function testNewServerWithNullStorageValue() + { + $memory = $this->getMock('OAuth2\Storage\Memory'); + $server = new Server(array( + 'client_credentials' => $memory, + 'refresh_token' => null, + )); + + $client_credentials = $server->getStorage('client_credentials'); + + $this->assertNotNull($client_credentials); + $this->assertEquals($client_credentials, $memory); + + $refresh_token = $server->getStorage('refresh_token'); + + $this->assertNull($refresh_token); + } + + public function testAddingClientCredentialsStorageSetsClientStorageByDefault() + { + $server = new Server(); + $memory = $this->getMock('OAuth2\Storage\Memory'); + $server->addStorage($memory, 'client_credentials'); + + $client = $server->getStorage('client'); + + $this->assertNotNull($client); + $this->assertEquals($client, $memory); + } + + public function testSettingClientStorageByDefaultDoesNotOverrideSetStorage() + { + $server = new Server(); + $pdo = $this->getMockBuilder('OAuth2\Storage\Pdo') + ->disableOriginalConstructor()->getMock(); + + $memory = $this->getMock('OAuth2\Storage\Memory'); + + $server->addStorage($pdo, 'client'); + $server->addStorage($memory, 'client_credentials'); + + $client = $server->getStorage('client'); + $client_credentials = $server->getStorage('client_credentials'); + + $this->assertEquals($client, $pdo); + $this->assertEquals($client_credentials, $memory); + } + + public function testAddingResponseType() + { + $storage = $this->getMock('OAuth2\Storage\Memory'); + $storage + ->expects($this->any()) + ->method('getClientDetails') + ->will($this->returnValue(array('client_id' => 'some_client'))); + $storage + ->expects($this->any()) + ->method('checkRestrictedGrantType') + ->will($this->returnValue(true)); + + // add with the "code" key explicitly set + $codeType = new AuthorizationCode($storage); + $server = new Server(); + $server->addStorage($storage); + $server->addResponseType($codeType); + $request = new Request(array( + 'response_type' => 'code', + 'client_id' => 'some_client', + 'redirect_uri' => 'http://example.com', + 'state' => 'xyx', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + // the response is successful + $this->assertEquals($response->getStatusCode(), 302); + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['query'], $query); + $this->assertTrue(isset($query['code'])); + $this->assertFalse(isset($query['error'])); + + // add with the "code" key not set + $codeType = new AuthorizationCode($storage); + $server = new Server(array($storage), array(), array(), array($codeType)); + $request = new Request(array( + 'response_type' => 'code', + 'client_id' => 'some_client', + 'redirect_uri' => 'http://example.com', + 'state' => 'xyx', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + // the response is successful + $this->assertEquals($response->getStatusCode(), 302); + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['query'], $query); + $this->assertTrue(isset($query['code'])); + $this->assertFalse(isset($query['error'])); + } + + public function testCustomClientAssertionType() + { + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', + 'client_id' =>'Test Client ID', + 'code' => 'testcode', + )); + // verify the mock clientAssertionType was called as expected + $clientAssertionType = $this->getMock('OAuth2\ClientAssertionType\ClientAssertionTypeInterface', array('validateRequest', 'getClientId')); + $clientAssertionType + ->expects($this->once()) + ->method('validateRequest') + ->will($this->returnValue(true)); + $clientAssertionType + ->expects($this->once()) + ->method('getClientId') + ->will($this->returnValue('Test Client ID')); + + // create mock storage + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server(array($storage), array(), array(), array(), null, null, $clientAssertionType); + $server->handleTokenRequest($request, $response = new Response()); + } + + public function testHttpBasicConfig() + { + // create mock storage + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server(array($storage), array( + 'allow_credentials_in_request_body' => false, + 'allow_public_clients' => false + )); + $server->getTokenController(); + $httpBasic = $server->getClientAssertionType(); + + $reflection = new \ReflectionClass($httpBasic); + $prop = $reflection->getProperty('config'); + $prop->setAccessible(true); + + $config = $prop->getValue($httpBasic); // get the private "config" property + + $this->assertEquals($config['allow_credentials_in_request_body'], false); + $this->assertEquals($config['allow_public_clients'], false); + } + + public function testRefreshTokenConfig() + { + // create mock storage + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server1 = new Server(array($storage)); + $server2 = new Server(array($storage), array('always_issue_new_refresh_token' => true, 'unset_refresh_token_after_use' => false)); + + $server1->getTokenController(); + $refreshToken1 = $server1->getGrantType('refresh_token'); + + $server2->getTokenController(); + $refreshToken2 = $server2->getGrantType('refresh_token'); + + $reflection1 = new \ReflectionClass($refreshToken1); + $prop1 = $reflection1->getProperty('config'); + $prop1->setAccessible(true); + + $reflection2 = new \ReflectionClass($refreshToken2); + $prop2 = $reflection2->getProperty('config'); + $prop2->setAccessible(true); + + // get the private "config" property + $config1 = $prop1->getValue($refreshToken1); + $config2 = $prop2->getValue($refreshToken2); + + $this->assertEquals($config1['always_issue_new_refresh_token'], false); + $this->assertEquals($config2['always_issue_new_refresh_token'], true); + + $this->assertEquals($config1['unset_refresh_token_after_use'], true); + $this->assertEquals($config2['unset_refresh_token_after_use'], false); + } + + /** + * Test setting "always_issue_new_refresh_token" on a server level + * + * @see test/OAuth2/GrantType/RefreshTokenTest::testValidRefreshTokenWithNewRefreshTokenInResponse + **/ + public function testValidRefreshTokenWithNewRefreshTokenInResponse() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, array('always_issue_new_refresh_token' => true)); + + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken', // valid refresh token + )); + $token = $server->grantAccessToken($request, new Response()); + $this->assertTrue(isset($token['refresh_token']), 'refresh token should always refresh'); + + $refresh_token = $storage->getRefreshToken($token['refresh_token']); + $this->assertNotNull($refresh_token); + $this->assertEquals($refresh_token['refresh_token'], $token['refresh_token']); + $this->assertEquals($refresh_token['client_id'], $request->request('client_id')); + $this->assertTrue($token['refresh_token'] != 'test-refreshtoken', 'the refresh token returned is not the one used'); + $used_token = $storage->getRefreshToken('test-refreshtoken'); + $this->assertFalse($used_token, 'the refresh token used is no longer valid'); + } + + /** + * @expectedException InvalidArgumentException OAuth2\ResponseType\AuthorizationCodeInterface + **/ + public function testAddingUnknownResponseTypeThrowsException() + { + $server = new Server(); + $server->addResponseType($this->getMock('OAuth2\ResponseType\ResponseTypeInterface')); + } + + /** + * @expectedException LogicException OAuth2\Storage\PublicKeyInterface + **/ + public function testUsingJwtAccessTokensWithoutPublicKeyStorageThrowsException() + { + $server = new Server(array(), array('use_jwt_access_tokens' => true)); + $server->addGrantType($this->getMock('OAuth2\GrantType\GrantTypeInterface')); + $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); + $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); + + $server->getTokenController(); + } + + public function testUsingJustJwtAccessTokenStorageWithResourceControllerIsOkay() + { + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $server = new Server(array($pubkey), array('use_jwt_access_tokens' => true)); + + $this->assertNotNull($server->getResourceController()); + $this->assertInstanceOf('OAuth2\Storage\PublicKeyInterface', $server->getStorage('public_key')); + } + + /** + * @expectedException LogicException OAuth2\Storage\ClientInterface + **/ + public function testUsingJustJwtAccessTokenStorageWithAuthorizeControllerThrowsException() + { + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $server = new Server(array($pubkey), array('use_jwt_access_tokens' => true)); + $this->assertNotNull($server->getAuthorizeController()); + } + + /** + * @expectedException LogicException grant_types + **/ + public function testUsingJustJwtAccessTokenStorageWithTokenControllerThrowsException() + { + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $server = new Server(array($pubkey), array('use_jwt_access_tokens' => true)); + $server->getTokenController(); + } + + public function testUsingJwtAccessTokenAndClientStorageWithAuthorizeControllerIsOkay() + { + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $server = new Server(array($pubkey, $client), array('use_jwt_access_tokens' => true, 'allow_implicit' => true)); + $this->assertNotNull($server->getAuthorizeController()); + + $this->assertInstanceOf('OAuth2\ResponseType\JwtAccessToken', $server->getResponseType('token')); + } + + /** + * @expectedException LogicException UserClaims + **/ + public function testUsingOpenIDConnectWithoutUserClaimsThrowsException() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $server = new Server($client, array('use_openid_connect' => true)); + + $server->getAuthorizeController(); + } + + /** + * @expectedException LogicException PublicKeyInterface + **/ + public function testUsingOpenIDConnectWithoutPublicKeyThrowsException() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->getMock('OAuth2\OPenID\Storage\UserClaimsInterface'); + $server = new Server(array($client, $userclaims), array('use_openid_connect' => true)); + + $server->getAuthorizeController(); + } + + /** + * @expectedException LogicException issuer + **/ + public function testUsingOpenIDConnectWithoutIssuerThrowsException() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $server = new Server(array($client, $userclaims, $pubkey), array('use_openid_connect' => true)); + + $server->getAuthorizeController(); + } + + public function testUsingOpenIDConnectWithIssuerPublicKeyAndUserClaimsIsOkay() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $server = new Server(array($client, $userclaims, $pubkey), array( + 'use_openid_connect' => true, + 'issuer' => 'someguy', + )); + + $server->getAuthorizeController(); + + $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenInterface', $server->getResponseType('id_token')); + $this->assertNull($server->getResponseType('id_token token')); + } + + /** + * @expectedException LogicException OAuth2\ResponseType\AccessTokenInterface + **/ + public function testUsingOpenIDConnectWithAllowImplicitWithoutTokenStorageThrowsException() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $server = new Server(array($client, $userclaims, $pubkey), array( + 'use_openid_connect' => true, + 'issuer' => 'someguy', + 'allow_implicit' => true, + )); + + $server->getAuthorizeController(); + } + + public function testUsingOpenIDConnectWithAllowImplicitAndUseJwtAccessTokensIsOkay() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $server = new Server(array($client, $userclaims, $pubkey), array( + 'use_openid_connect' => true, + 'issuer' => 'someguy', + 'allow_implicit' => true, + 'use_jwt_access_tokens' => true, + )); + + $server->getAuthorizeController(); + + $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenInterface', $server->getResponseType('id_token')); + $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenTokenInterface', $server->getResponseType('id_token token')); + } + + public function testUsingOpenIDConnectWithAllowImplicitAndAccessTokenStorageIsOkay() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $token = $this->getMock('OAuth2\Storage\AccessTokenInterface'); + $server = new Server(array($client, $userclaims, $pubkey, $token), array( + 'use_openid_connect' => true, + 'issuer' => 'someguy', + 'allow_implicit' => true, + )); + + $server->getAuthorizeController(); + + $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenInterface', $server->getResponseType('id_token')); + $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenTokenInterface', $server->getResponseType('id_token token')); + } + + public function testUsingOpenIDConnectWithAllowImplicitAndAccessTokenResponseTypeIsOkay() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + // $token = $this->getMock('OAuth2\Storage\AccessTokenInterface'); + $server = new Server(array($client, $userclaims, $pubkey), array( + 'use_openid_connect' => true, + 'issuer' => 'someguy', + 'allow_implicit' => true, + )); + + $token = $this->getMock('OAuth2\ResponseType\AccessTokenInterface'); + $server->addResponseType($token, 'token'); + + $server->getAuthorizeController(); + + $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenInterface', $server->getResponseType('id_token')); + $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenTokenInterface', $server->getResponseType('id_token token')); + } + + /** + * @expectedException LogicException OAuth2\OpenID\Storage\AuthorizationCodeInterface + **/ + public function testUsingOpenIDConnectWithAuthorizationCodeStorageThrowsException() + { + $client = $this->getMock('OAuth2\Storage\ClientCredentialsInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $token = $this->getMock('OAuth2\Storage\AccessTokenInterface'); + $authcode = $this->getMock('OAuth2\Storage\AuthorizationCodeInterface'); + + $server = new Server(array($client, $userclaims, $pubkey, $token, $authcode), array( + 'use_openid_connect' => true, + 'issuer' => 'someguy' + )); + + $server->getTokenController(); + + $this->assertInstanceOf('OAuth2\OpenID\GrantType\AuthorizationCode', $server->getGrantType('authorization_code')); + } + + public function testUsingOpenIDConnectWithOpenIDAuthorizationCodeStorageCreatesOpenIDAuthorizationCodeGrantType() + { + $client = $this->getMock('OAuth2\Storage\ClientCredentialsInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $token = $this->getMock('OAuth2\Storage\AccessTokenInterface'); + $authcode = $this->getMock('OAuth2\OpenID\Storage\AuthorizationCodeInterface'); + + $server = new Server(array($client, $userclaims, $pubkey, $token, $authcode), array( + 'use_openid_connect' => true, + 'issuer' => 'someguy' + )); + + $server->getTokenController(); + + $this->assertInstanceOf('OAuth2\OpenID\GrantType\AuthorizationCode', $server->getGrantType('authorization_code')); + } + + public function testMultipleValuedResponseTypeOrderDoesntMatter() + { + $responseType = $this->getMock('OAuth2\OpenID\ResponseType\IdTokenTokenInterface'); + $server = new Server(array(), array(), array(), array( + 'token id_token' => $responseType, + )); + + $this->assertEquals($responseType, $server->getResponseType('id_token token')); + } + + public function testAddGrantTypeWithoutKey() + { + $server = new Server(); + $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($this->getMock('OAuth2\Storage\AuthorizationCodeInterface'))); + + $grantTypes = $server->getGrantTypes(); + $this->assertEquals('authorization_code', key($grantTypes)); + } + + public function testAddGrantTypeWithKey() + { + $server = new Server(); + $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($this->getMock('OAuth2\Storage\AuthorizationCodeInterface')), 'ac'); + + $grantTypes = $server->getGrantTypes(); + $this->assertEquals('ac', key($grantTypes)); + } + + public function testAddGrantTypeWithKeyNotString() + { + $server = new Server(); + $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($this->getMock('OAuth2\Storage\AuthorizationCodeInterface')), 42); + + $grantTypes = $server->getGrantTypes(); + $this->assertEquals('authorization_code', key($grantTypes)); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/AccessTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/AccessTokenTest.php new file mode 100644 index 0000000000..b34e0bfc01 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/AccessTokenTest.php @@ -0,0 +1,102 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // assert token we are about to add does not exist + $token = $storage->getAccessToken('newtoken'); + $this->assertFalse($token); + + // add new token + $expires = time() + 20; + $success = $storage->setAccessToken('newtoken', 'client ID', 'SOMEUSERID', $expires); + $this->assertTrue($success); + + $token = $storage->getAccessToken('newtoken'); + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('client_id', $token); + $this->assertArrayHasKey('user_id', $token); + $this->assertArrayHasKey('expires', $token); + $this->assertEquals($token['access_token'], 'newtoken'); + $this->assertEquals($token['client_id'], 'client ID'); + $this->assertEquals($token['user_id'], 'SOMEUSERID'); + $this->assertEquals($token['expires'], $expires); + + // change existing token + $expires = time() + 42; + $success = $storage->setAccessToken('newtoken', 'client ID2', 'SOMEOTHERID', $expires); + $this->assertTrue($success); + + $token = $storage->getAccessToken('newtoken'); + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('client_id', $token); + $this->assertArrayHasKey('user_id', $token); + $this->assertArrayHasKey('expires', $token); + $this->assertEquals($token['access_token'], 'newtoken'); + $this->assertEquals($token['client_id'], 'client ID2'); + $this->assertEquals($token['user_id'], 'SOMEOTHERID'); + $this->assertEquals($token['expires'], $expires); + + // add token with scope having an empty string value + $expires = time() + 42; + $success = $storage->setAccessToken('newtoken', 'client ID', 'SOMEOTHERID', $expires, ''); + $this->assertTrue($success); + } + + /** @dataProvider provideStorage */ + public function testUnsetAccessToken(AccessTokenInterface $storage) + { + if ($storage instanceof NullStorage || !method_exists($storage, 'unsetAccessToken')) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // assert token we are about to unset does not exist + $token = $storage->getAccessToken('revokabletoken'); + $this->assertFalse($token); + + // add new token + $expires = time() + 20; + $success = $storage->setAccessToken('revokabletoken', 'client ID', 'SOMEUSERID', $expires); + $this->assertTrue($success); + + // assert unsetAccessToken returns true + $result = $storage->unsetAccessToken('revokabletoken'); + $this->assertTrue($result); + + // assert token we unset does not exist + $token = $storage->getAccessToken('revokabletoken'); + $this->assertFalse($token); + } + + /** @dataProvider provideStorage */ + public function testUnsetAccessTokenReturnsFalse(AccessTokenInterface $storage) + { + if ($storage instanceof NullStorage || !method_exists($storage, 'unsetAccessToken')) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // assert token we are about to unset does not exist + $token = $storage->getAccessToken('nonexistanttoken'); + $this->assertFalse($token); + + // assert unsetAccessToken returns false + $result = $storage->unsetAccessToken('nonexistanttoken'); + $this->assertFalse($result); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/AuthorizationCodeTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/AuthorizationCodeTest.php new file mode 100644 index 0000000000..2d901b5019 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/AuthorizationCodeTest.php @@ -0,0 +1,106 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // nonexistant client_id + $details = $storage->getAuthorizationCode('faketoken'); + $this->assertFalse($details); + + // valid client_id + $details = $storage->getAuthorizationCode('testtoken'); + $this->assertNotNull($details); + } + + /** @dataProvider provideStorage */ + public function testSetAuthorizationCode(AuthorizationCodeInterface $storage) + { + if ($storage instanceof NullStorage) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // assert code we are about to add does not exist + $code = $storage->getAuthorizationCode('newcode'); + $this->assertFalse($code); + + // add new code + $expires = time() + 20; + $success = $storage->setAuthorizationCode('newcode', 'client ID', 'SOMEUSERID', 'http://example.com', $expires); + $this->assertTrue($success); + + $code = $storage->getAuthorizationCode('newcode'); + $this->assertNotNull($code); + $this->assertArrayHasKey('authorization_code', $code); + $this->assertArrayHasKey('client_id', $code); + $this->assertArrayHasKey('user_id', $code); + $this->assertArrayHasKey('redirect_uri', $code); + $this->assertArrayHasKey('expires', $code); + $this->assertEquals($code['authorization_code'], 'newcode'); + $this->assertEquals($code['client_id'], 'client ID'); + $this->assertEquals($code['user_id'], 'SOMEUSERID'); + $this->assertEquals($code['redirect_uri'], 'http://example.com'); + $this->assertEquals($code['expires'], $expires); + + // change existing code + $expires = time() + 42; + $success = $storage->setAuthorizationCode('newcode', 'client ID2', 'SOMEOTHERID', 'http://example.org', $expires); + $this->assertTrue($success); + + $code = $storage->getAuthorizationCode('newcode'); + $this->assertNotNull($code); + $this->assertArrayHasKey('authorization_code', $code); + $this->assertArrayHasKey('client_id', $code); + $this->assertArrayHasKey('user_id', $code); + $this->assertArrayHasKey('redirect_uri', $code); + $this->assertArrayHasKey('expires', $code); + $this->assertEquals($code['authorization_code'], 'newcode'); + $this->assertEquals($code['client_id'], 'client ID2'); + $this->assertEquals($code['user_id'], 'SOMEOTHERID'); + $this->assertEquals($code['redirect_uri'], 'http://example.org'); + $this->assertEquals($code['expires'], $expires); + + // add new code with scope having an empty string value + $expires = time() + 20; + $success = $storage->setAuthorizationCode('newcode', 'client ID', 'SOMEUSERID', 'http://example.com', $expires, ''); + $this->assertTrue($success); + } + + /** @dataProvider provideStorage */ + public function testExpireAccessToken(AccessTokenInterface $storage) + { + if ($storage instanceof NullStorage) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // create a valid code + $expires = time() + 20; + $success = $storage->setAuthorizationCode('code-to-expire', 'client ID', 'SOMEUSERID', 'http://example.com', time() + 20); + $this->assertTrue($success); + + // verify the new code exists + $code = $storage->getAuthorizationCode('code-to-expire'); + $this->assertNotNull($code); + + $this->assertArrayHasKey('authorization_code', $code); + $this->assertEquals($code['authorization_code'], 'code-to-expire'); + + // now expire the code and ensure it's no longer available + $storage->expireAuthorizationCode('code-to-expire'); + $code = $storage->getAuthorizationCode('code-to-expire'); + $this->assertFalse($code); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ClientCredentialsTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ClientCredentialsTest.php new file mode 100644 index 0000000000..15289af308 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ClientCredentialsTest.php @@ -0,0 +1,28 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // nonexistant client_id + $pass = $storage->checkClientCredentials('fakeclient', 'testpass'); + $this->assertFalse($pass); + + // invalid password + $pass = $storage->checkClientCredentials('oauth_test_client', 'invalidcredentials'); + $this->assertFalse($pass); + + // valid credentials + $pass = $storage->checkClientCredentials('oauth_test_client', 'testpass'); + $this->assertTrue($pass); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ClientTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ClientTest.php new file mode 100644 index 0000000000..6a5cc0b492 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ClientTest.php @@ -0,0 +1,110 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // nonexistant client_id + $details = $storage->getClientDetails('fakeclient'); + $this->assertFalse($details); + + // valid client_id + $details = $storage->getClientDetails('oauth_test_client'); + $this->assertNotNull($details); + $this->assertArrayHasKey('client_id', $details); + $this->assertArrayHasKey('client_secret', $details); + $this->assertArrayHasKey('redirect_uri', $details); + } + + /** @dataProvider provideStorage */ + public function testCheckRestrictedGrantType(ClientInterface $storage) + { + if ($storage instanceof NullStorage) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // Check invalid + $pass = $storage->checkRestrictedGrantType('oauth_test_client', 'authorization_code'); + $this->assertFalse($pass); + + // Check valid + $pass = $storage->checkRestrictedGrantType('oauth_test_client', 'implicit'); + $this->assertTrue($pass); + } + + /** @dataProvider provideStorage */ + public function testGetAccessToken(ClientInterface $storage) + { + if ($storage instanceof NullStorage) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // nonexistant client_id + $details = $storage->getAccessToken('faketoken'); + $this->assertFalse($details); + + // valid client_id + $details = $storage->getAccessToken('testtoken'); + $this->assertNotNull($details); + } + + /** @dataProvider provideStorage */ + public function testIsPublicClient(ClientInterface $storage) + { + if ($storage instanceof NullStorage) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + $publicClientId = 'public-client-'.rand(); + $confidentialClientId = 'confidential-client-'.rand(); + + // create a new client + $success1 = $storage->setClientDetails($publicClientId, ''); + $success2 = $storage->setClientDetails($confidentialClientId, 'some-secret'); + $this->assertTrue($success1); + $this->assertTrue($success2); + + // assert isPublicClient for both + $this->assertTrue($storage->isPublicClient($publicClientId)); + $this->assertFalse($storage->isPublicClient($confidentialClientId)); + } + + /** @dataProvider provideStorage */ + public function testSaveClient(ClientInterface $storage) + { + if ($storage instanceof NullStorage) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + $clientId = 'some-client-'.rand(); + + // create a new client + $success = $storage->setClientDetails($clientId, 'somesecret', 'http://test.com', 'client_credentials', 'clientscope1', 'brent@brentertainment.com'); + $this->assertTrue($success); + + // valid client_id + $details = $storage->getClientDetails($clientId); + $this->assertEquals($details['client_secret'], 'somesecret'); + $this->assertEquals($details['redirect_uri'], 'http://test.com'); + $this->assertEquals($details['grant_types'], 'client_credentials'); + $this->assertEquals($details['scope'], 'clientscope1'); + $this->assertEquals($details['user_id'], 'brent@brentertainment.com'); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/DynamoDBTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/DynamoDBTest.php new file mode 100644 index 0000000000..2147f0914d --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/DynamoDBTest.php @@ -0,0 +1,40 @@ +getMockBuilder('\Aws\DynamoDb\DynamoDbClient') + ->disableOriginalConstructor() + ->setMethods(array('query')) + ->getMock(); + + $return = $this->getMockBuilder('\Guzzle\Service\Resource\Model') + ->setMethods(array('count', 'toArray')) + ->getMock(); + + $data = array( + 'Items' => array(), + 'Count' => 0, + 'ScannedCount'=> 0 + ); + + $return->expects($this->once()) + ->method('count') + ->will($this->returnValue(count($data))); + + $return->expects($this->once()) + ->method('toArray') + ->will($this->returnValue($data)); + + // should return null default scope if none is set in database + $client->expects($this->once()) + ->method('query') + ->will($this->returnValue($return)); + + $storage = new DynamoDB($client); + $this->assertNull($storage->getDefaultScope()); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/JwtAccessTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/JwtAccessTokenTest.php new file mode 100644 index 0000000000..a6acbea1f8 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/JwtAccessTokenTest.php @@ -0,0 +1,41 @@ +getMemoryStorage(); + $encryptionUtil = new Jwt(); + + $jwtAccessToken = array( + 'access_token' => rand(), + 'expires' => time() + 100, + 'scope' => 'foo', + ); + + $token = $encryptionUtil->encode($jwtAccessToken, $storage->getPrivateKey(), $storage->getEncryptionAlgorithm()); + + $this->assertNotNull($token); + + $tokenData = $crypto->getAccessToken($token); + + $this->assertTrue(is_array($tokenData)); + + /* assert the decoded token is the same */ + $this->assertEquals($tokenData['access_token'], $jwtAccessToken['access_token']); + $this->assertEquals($tokenData['expires'], $jwtAccessToken['expires']); + $this->assertEquals($tokenData['scope'], $jwtAccessToken['scope']); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/JwtBearerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/JwtBearerTest.php new file mode 100644 index 0000000000..d0ab9b8995 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/JwtBearerTest.php @@ -0,0 +1,25 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // nonexistant client_id + $key = $storage->getClientKey('this-is-not-real', 'nor-is-this'); + $this->assertFalse($key); + + // valid client_id and subject + $key = $storage->getClientKey('oauth_test_client', 'test_subject'); + $this->assertNotNull($key); + $this->assertEquals($key, Bootstrap::getInstance()->getTestPublicKey()); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PdoTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PdoTest.php new file mode 100644 index 0000000000..4599f69bf3 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PdoTest.php @@ -0,0 +1,41 @@ +getSqliteDir()); + $pdo = new \PDO($dsn); + $storage = new Pdo($pdo); + + $this->assertNotNull($storage->getClientDetails('oauth_test_client')); + } + + public function testCreatePdoStorageUsingDSN() + { + $dsn = sprintf('sqlite:%s', Bootstrap::getInstance()->getSqliteDir()); + $storage = new Pdo($dsn); + + $this->assertNotNull($storage->getClientDetails('oauth_test_client')); + } + + public function testCreatePdoStorageUsingConfig() + { + $dsn = sprintf('sqlite:%s', Bootstrap::getInstance()->getSqliteDir()); + $config = array('dsn' => $dsn); + $storage = new Pdo($config); + + $this->assertNotNull($storage->getClientDetails('oauth_test_client')); + } + + /** + * @expectedException InvalidArgumentException dsn + */ + public function testCreatePdoStorageWithoutDSNThrowsException() + { + $config = array('username' => 'brent', 'password' => 'brentisaballer'); + $storage = new Pdo($config); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PublicKeyTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PublicKeyTest.php new file mode 100644 index 0000000000..f85195870b --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PublicKeyTest.php @@ -0,0 +1,29 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + if (!$storage instanceof PublicKeyInterface) { + // incompatible storage + return; + } + + $configDir = Bootstrap::getInstance()->getConfigDir(); + $globalPublicKey = file_get_contents($configDir.'/keys/id_rsa.pub'); + $globalPrivateKey = file_get_contents($configDir.'/keys/id_rsa'); + + /* assert values from storage */ + $this->assertEquals($storage->getPublicKey(), $globalPublicKey); + $this->assertEquals($storage->getPrivateKey(), $globalPrivateKey); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/RefreshTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/RefreshTokenTest.php new file mode 100644 index 0000000000..314c93195b --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/RefreshTokenTest.php @@ -0,0 +1,41 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // assert token we are about to add does not exist + $token = $storage->getRefreshToken('refreshtoken'); + $this->assertFalse($token); + + // add new token + $expires = time() + 20; + $success = $storage->setRefreshToken('refreshtoken', 'client ID', 'SOMEUSERID', $expires); + $this->assertTrue($success); + + $token = $storage->getRefreshToken('refreshtoken'); + $this->assertNotNull($token); + $this->assertArrayHasKey('refresh_token', $token); + $this->assertArrayHasKey('client_id', $token); + $this->assertArrayHasKey('user_id', $token); + $this->assertArrayHasKey('expires', $token); + $this->assertEquals($token['refresh_token'], 'refreshtoken'); + $this->assertEquals($token['client_id'], 'client ID'); + $this->assertEquals($token['user_id'], 'SOMEUSERID'); + $this->assertEquals($token['expires'], $expires); + + // add token with scope having an empty string value + $expires = time() + 20; + $success = $storage->setRefreshToken('refreshtoken2', 'client ID', 'SOMEUSERID', $expires, ''); + $this->assertTrue($success); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ScopeTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ScopeTest.php new file mode 100644 index 0000000000..fd1edeb93f --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ScopeTest.php @@ -0,0 +1,53 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + if (!$storage instanceof ScopeInterface) { + // incompatible storage + return; + } + + //Test getting scopes + $scopeUtil = new Scope($storage); + $this->assertTrue($scopeUtil->scopeExists('supportedscope1')); + $this->assertTrue($scopeUtil->scopeExists('supportedscope1 supportedscope2 supportedscope3')); + $this->assertFalse($scopeUtil->scopeExists('fakescope')); + $this->assertFalse($scopeUtil->scopeExists('supportedscope1 supportedscope2 supportedscope3 fakescope')); + } + + /** @dataProvider provideStorage */ + public function testGetDefaultScope($storage) + { + if ($storage instanceof NullStorage) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + if (!$storage instanceof ScopeInterface) { + // incompatible storage + return; + } + + // test getting default scope + $scopeUtil = new Scope($storage); + $expected = explode(' ', $scopeUtil->getDefaultScope()); + $actual = explode(' ', 'defaultscope1 defaultscope2'); + sort($expected); + sort($actual); + $this->assertEquals($expected, $actual); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/UserCredentialsTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/UserCredentialsTest.php new file mode 100644 index 0000000000..65655a6b27 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/UserCredentialsTest.php @@ -0,0 +1,40 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // create a new user for testing + $success = $storage->setUser('testusername', 'testpass', 'Test', 'User'); + $this->assertTrue($success); + + // correct credentials + $this->assertTrue($storage->checkUserCredentials('testusername', 'testpass')); + // invalid password + $this->assertFalse($storage->checkUserCredentials('testusername', 'fakepass')); + // invalid username + $this->assertFalse($storage->checkUserCredentials('fakeusername', 'testpass')); + + // invalid username + $this->assertFalse($storage->getUserDetails('fakeusername')); + + // ensure all properties are set + $user = $storage->getUserDetails('testusername'); + $this->assertTrue($user !== false); + $this->assertArrayHasKey('user_id', $user); + $this->assertArrayHasKey('first_name', $user); + $this->assertArrayHasKey('last_name', $user); + $this->assertEquals($user['user_id'], 'testusername'); + $this->assertEquals($user['first_name'], 'Test'); + $this->assertEquals($user['last_name'], 'User'); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/TokenType/BearerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/TokenType/BearerTest.php new file mode 100644 index 0000000000..71cca3bd91 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/TokenType/BearerTest.php @@ -0,0 +1,59 @@ + 'ThisIsMyAccessToken' + )); + $request->server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=UTF-8'; + + $param = $bearer->getAccessTokenParameter($request, $response = new Response()); + $this->assertEquals($param, 'ThisIsMyAccessToken'); + } + + public function testInvalidContentType() + { + $bearer = new Bearer(); + $request = TestRequest::createPost(array( + 'access_token' => 'ThisIsMyAccessToken' + )); + $request->server['CONTENT_TYPE'] = 'application/json; charset=UTF-8'; + + $param = $bearer->getAccessTokenParameter($request, $response = new Response()); + $this->assertNull($param); + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'The content type for POST requests must be "application/x-www-form-urlencoded"'); + } + + public function testValidRequestUsingAuthorizationHeader() + { + $bearer = new Bearer(); + $request = new TestRequest(); + $request->headers['AUTHORIZATION'] = 'Bearer MyToken'; + $request->server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=UTF-8'; + + $param = $bearer->getAccessTokenParameter($request, $response = new Response()); + $this->assertEquals('MyToken', $param); + } + + public function testValidRequestUsingAuthorizationHeaderCaseInsensitive() + { + $bearer = new Bearer(); + $request = new TestRequest(); + $request->server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=UTF-8'; + $request->headers['Authorization'] = 'Bearer MyToken'; + + $param = $bearer->getAccessTokenParameter($request, $response = new Response()); + $this->assertEquals('MyToken', $param); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/bootstrap.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/bootstrap.php new file mode 100644 index 0000000000..0a4af0716d --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/bootstrap.php @@ -0,0 +1,12 @@ +cleanupTravisDynamoDb(); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/keys/id_rsa b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/keys/id_rsa new file mode 100644 index 0000000000..e8b9eff2d3 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/keys/id_rsa @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLsNjP+uAt2eO0cc5J9H5XV +8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdwizIum8j0KzpsGYH5qReN +QDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJuBe+FQpZTs8DewwIDAQAB +AoGARfNxNknmtx/n1bskZ/01iZRzAge6BLEE0LV6Q4gS7mkRZu/Oyiv39Sl5vUlA ++WdGxLjkBwKNjxGN8Vxw9/ASd8rSsqeAUYIwAeifXrHhj5DBPQT/pDPkeFnp9B1w +C6jo+3AbBQ4/b0ONSIEnCL2xGGglSIAxO17T1ViXp7lzXPECQQDe63nkRdWM0OCb +oaHQPT3E26224maIstrGFUdt9yw3yJf4bOF7TtiPLlLuHsTTge3z+fG6ntC0xG56 +1cl37C3ZAkEA2HdVcRGugNp/qmVz4LJTpD+WZKi73PLAO47wDOrYh9Pn2I6fcEH0 +CPnggt1ko4ujvGzFTvRH64HXa6aPCv1j+wJBAMQMah3VQPNf/DlDVFEUmw9XeBZg +VHaifX851aEjgXLp6qVj9IYCmLiLsAmVa9rr6P7p8asD418nZlaHUHE0eDkCQQCr +uxis6GMx1Ka971jcJX2X696LoxXPd0KsvXySMupv79yagKPa8mgBiwPjrnK+EPVo +cj6iochA/bSCshP/mwFrAkBHEKPi6V6gb94JinCT7x3weahbdp6bJ6/nzBH/p9VA +HoT1JtwNFhGv9BCjmDydshQHfSWpY9NxlccBKL7ITm8R +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/keys/id_rsa.pub b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/keys/id_rsa.pub new file mode 100644 index 0000000000..1ac15f5ebf --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/keys/id_rsa.pub @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICiDCCAfGgAwIBAgIBADANBgkqhkiG9w0BAQQFADA9MQswCQYDVQQGEwJVUzEL +MAkGA1UECBMCVVQxITAfBgNVBAoTGFZpZ25ldHRlIENvcnBvcmF0aW9uIFNCWDAe +Fw0xMTEwMTUwMzE4MjdaFw0zMTEwMTAwMzE4MjdaMD0xCzAJBgNVBAYTAlVTMQsw +CQYDVQQIEwJVVDEhMB8GA1UEChMYVmlnbmV0dGUgQ29ycG9yYXRpb24gU0JYMIGf +MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLs +NjP+uAt2eO0cc5J9H5XV8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdw +izIum8j0KzpsGYH5qReNQDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJu +Be+FQpZTs8DewwIDAQABo4GXMIGUMB0GA1UdDgQWBBRe8hrEXm+Yim4YlD5Nx+1K +vCYs9DBlBgNVHSMEXjBcgBRe8hrEXm+Yim4YlD5Nx+1KvCYs9KFBpD8wPTELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAlVUMSEwHwYDVQQKExhWaWduZXR0ZSBDb3Jwb3Jh +dGlvbiBTQliCAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQBjhyRD +lM7vnLn6drgQVftW5V9nDFAyPAuiGvMIKFSbiAf1PxXCRn5sfJquwWKsJUi4ZGNl +aViXdFmN6/F13PSM+yg63tpKy0fYqMbTM+Oe5WuSHkSW1VuYNHV+24adgNk/FRDL +FRrlM1f6s9VTLWvwGItjfrof0Ba8Uq7ZDSb9Xg== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/storage.json b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/storage.json new file mode 100644 index 0000000000..52d3f23996 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/storage.json @@ -0,0 +1,188 @@ +{ + "authorization_codes": { + "testcode": { + "client_id": "Test Client ID", + "user_id": "", + "redirect_uri": "", + "expires": "9999999999", + "id_token": "IDTOKEN" + }, + "testcode-with-scope": { + "client_id": "Test Client ID", + "user_id": "", + "redirect_uri": "", + "expires": "9999999999", + "scope": "scope1 scope2" + }, + "testcode-expired": { + "client_id": "Test Client ID", + "user_id": "", + "redirect_uri": "", + "expires": "1356998400" + }, + "testcode-empty-secret": { + "client_id": "Test Client ID Empty Secret", + "user_id": "", + "redirect_uri": "", + "expires": "9999999999" + }, + "testcode-openid": { + "client_id": "Test Client ID", + "user_id": "", + "redirect_uri": "", + "expires": "9999999999", + "id_token": "test_id_token" + }, + "testcode-redirect-uri": { + "client_id": "Test Client ID", + "user_id": "", + "redirect_uri": "http://brentertainment.com/voil%C3%A0", + "expires": "9999999999", + "id_token": "IDTOKEN" + } + }, + "client_credentials" : { + "Test Client ID": { + "client_secret": "TestSecret" + }, + "Test Client ID with Redirect Uri": { + "client_secret": "TestSecret2", + "redirect_uri": "http://brentertainment.com" + }, + "Test Client ID with Buggy Redirect Uri": { + "client_secret": "TestSecret2", + "redirect_uri": " http://brentertainment.com" + }, + "Test Client ID with Multiple Redirect Uris": { + "client_secret": "TestSecret3", + "redirect_uri": "http://brentertainment.com http://morehazards.com" + }, + "Test Client ID with Redirect Uri Parts": { + "client_secret": "TestSecret4", + "redirect_uri": "http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth&test=true" + }, + "Test Some Other Client": { + "client_secret": "TestSecret3" + }, + "Test Client ID Empty Secret": { + "client_secret": "" + }, + "Test Client ID For Password Grant": { + "grant_types": "password", + "client_secret": "" + }, + "Client ID With User ID": { + "client_secret": "TestSecret", + "user_id": "brent@brentertainment.com" + }, + "oauth_test_client": { + "client_secret": "testpass", + "grant_types": "implicit password" + } + }, + "user_credentials" : { + "test-username": { + "password": "testpass" + }, + "testusername": { + "password": "testpass" + }, + "testuser": { + "password": "password", + "email": "testuser@test.com", + "email_verified": true + }, + "johndoe": { + "password": "password" + } + }, + "refresh_tokens" : { + "test-refreshtoken": { + "refresh_token": "test-refreshtoken", + "client_id": "Test Client ID", + "user_id": "test-username", + "expires": 0, + "scope": null + }, + "test-refreshtoken-with-scope": { + "refresh_token": "test-refreshtoken", + "client_id": "Test Client ID", + "user_id": "test-username", + "expires": 0, + "scope": "scope1 scope2" + } + }, + "access_tokens" : { + "accesstoken-expired": { + "access_token": "accesstoken-expired", + "client_id": "Test Client ID", + "expires": 1234567, + "scope": null + }, + "accesstoken-scope": { + "access_token": "accesstoken-scope", + "client_id": "Test Client ID", + "expires": 99999999900, + "scope": "testscope" + }, + "accesstoken-openid-connect": { + "access_token": "accesstoken-openid-connect", + "client_id": "Test Client ID", + "user_id": "testuser", + "expires": 99999999900, + "scope": "openid email" + }, + "accesstoken-malformed": { + "access_token": "accesstoken-mallformed", + "expires": 99999999900, + "scope": "testscope" + } + }, + "jwt": { + "Test Client ID": { + "key": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5/SxVlE8gnpFqCxgl2wjhzY7u\ncEi00s0kUg3xp7lVEvgLgYcAnHiWp+gtSjOFfH2zsvpiWm6Lz5f743j/FEzHIO1o\nwR0p4d9pOaJK07d01+RzoQLOIQAgXrr4T1CCWUesncwwPBVCyy2Mw3Nmhmr9MrF8\nUlvdRKBxriRnlP3qJQIDAQAB\n-----END PUBLIC KEY-----", + "subject": "testuser@ourdomain.com" + }, + "Test Client ID PHP-5.2": { + "key": "mysecretkey", + "subject": "testuser@ourdomain.com" + }, + "Missing Key Client": { + "key": null, + "subject": "testuser@ourdomain.com" + }, + "Missing Key Client PHP-5.2": { + "key": null, + "subject": "testuser@ourdomain.com" + }, + "oauth_test_client": { + "key": "-----BEGIN CERTIFICATE-----\nMIICiDCCAfGgAwIBAgIBADANBgkqhkiG9w0BAQQFADA9MQswCQYDVQQGEwJVUzEL\nMAkGA1UECBMCVVQxITAfBgNVBAoTGFZpZ25ldHRlIENvcnBvcmF0aW9uIFNCWDAe\nFw0xMTEwMTUwMzE4MjdaFw0zMTEwMTAwMzE4MjdaMD0xCzAJBgNVBAYTAlVTMQsw\nCQYDVQQIEwJVVDEhMB8GA1UEChMYVmlnbmV0dGUgQ29ycG9yYXRpb24gU0JYMIGf\nMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLs\nNjP+uAt2eO0cc5J9H5XV8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdw\nizIum8j0KzpsGYH5qReNQDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJu\nBe+FQpZTs8DewwIDAQABo4GXMIGUMB0GA1UdDgQWBBRe8hrEXm+Yim4YlD5Nx+1K\nvCYs9DBlBgNVHSMEXjBcgBRe8hrEXm+Yim4YlD5Nx+1KvCYs9KFBpD8wPTELMAkG\nA1UEBhMCVVMxCzAJBgNVBAgTAlVUMSEwHwYDVQQKExhWaWduZXR0ZSBDb3Jwb3Jh\ndGlvbiBTQliCAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQBjhyRD\nlM7vnLn6drgQVftW5V9nDFAyPAuiGvMIKFSbiAf1PxXCRn5sfJquwWKsJUi4ZGNl\naViXdFmN6/F13PSM+yg63tpKy0fYqMbTM+Oe5WuSHkSW1VuYNHV+24adgNk/FRDL\nFRrlM1f6s9VTLWvwGItjfrof0Ba8Uq7ZDSb9Xg==\n-----END CERTIFICATE-----", + "subject": "test_subject" + } + }, + "jti": [ + { + "issuer": "Test Client ID", + "subject": "testuser@ourdomain.com", + "audience": "http://myapp.com/oauth/auth", + "expires": 99999999900, + "jti": "used_jti" + } + ], + "supported_scopes" : [ + "scope1", + "scope2", + "scope3", + "clientscope1", + "clientscope2", + "clientscope3", + "supportedscope1", + "supportedscope2", + "supportedscope3", + "supportedscope4" + ], + "keys": { + "public_key": "-----BEGIN CERTIFICATE-----\nMIICiDCCAfGgAwIBAgIBADANBgkqhkiG9w0BAQQFADA9MQswCQYDVQQGEwJVUzEL\nMAkGA1UECBMCVVQxITAfBgNVBAoTGFZpZ25ldHRlIENvcnBvcmF0aW9uIFNCWDAe\nFw0xMTEwMTUwMzE4MjdaFw0zMTEwMTAwMzE4MjdaMD0xCzAJBgNVBAYTAlVTMQsw\nCQYDVQQIEwJVVDEhMB8GA1UEChMYVmlnbmV0dGUgQ29ycG9yYXRpb24gU0JYMIGf\nMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLs\nNjP+uAt2eO0cc5J9H5XV8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdw\nizIum8j0KzpsGYH5qReNQDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJu\nBe+FQpZTs8DewwIDAQABo4GXMIGUMB0GA1UdDgQWBBRe8hrEXm+Yim4YlD5Nx+1K\nvCYs9DBlBgNVHSMEXjBcgBRe8hrEXm+Yim4YlD5Nx+1KvCYs9KFBpD8wPTELMAkG\nA1UEBhMCVVMxCzAJBgNVBAgTAlVUMSEwHwYDVQQKExhWaWduZXR0ZSBDb3Jwb3Jh\ndGlvbiBTQliCAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQBjhyRD\nlM7vnLn6drgQVftW5V9nDFAyPAuiGvMIKFSbiAf1PxXCRn5sfJquwWKsJUi4ZGNl\naViXdFmN6/F13PSM+yg63tpKy0fYqMbTM+Oe5WuSHkSW1VuYNHV+24adgNk/FRDL\nFRrlM1f6s9VTLWvwGItjfrof0Ba8Uq7ZDSb9Xg==\n-----END CERTIFICATE-----", + "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLsNjP+uAt2eO0cc5J9H5XV\n8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdwizIum8j0KzpsGYH5qReN\nQDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJuBe+FQpZTs8DewwIDAQAB\nAoGARfNxNknmtx/n1bskZ/01iZRzAge6BLEE0LV6Q4gS7mkRZu/Oyiv39Sl5vUlA\n+WdGxLjkBwKNjxGN8Vxw9/ASd8rSsqeAUYIwAeifXrHhj5DBPQT/pDPkeFnp9B1w\nC6jo+3AbBQ4/b0ONSIEnCL2xGGglSIAxO17T1ViXp7lzXPECQQDe63nkRdWM0OCb\noaHQPT3E26224maIstrGFUdt9yw3yJf4bOF7TtiPLlLuHsTTge3z+fG6ntC0xG56\n1cl37C3ZAkEA2HdVcRGugNp/qmVz4LJTpD+WZKi73PLAO47wDOrYh9Pn2I6fcEH0\nCPnggt1ko4ujvGzFTvRH64HXa6aPCv1j+wJBAMQMah3VQPNf/DlDVFEUmw9XeBZg\nVHaifX851aEjgXLp6qVj9IYCmLiLsAmVa9rr6P7p8asD418nZlaHUHE0eDkCQQCr\nuxis6GMx1Ka971jcJX2X696LoxXPd0KsvXySMupv79yagKPa8mgBiwPjrnK+EPVo\ncj6iochA/bSCshP/mwFrAkBHEKPi6V6gb94JinCT7x3weahbdp6bJ6/nzBH/p9VA\nHoT1JtwNFhGv9BCjmDydshQHfSWpY9NxlccBKL7ITm8R\n-----END RSA PRIVATE KEY-----" + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Request/TestRequest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Request/TestRequest.php new file mode 100644 index 0000000000..a916ff2a36 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Request/TestRequest.php @@ -0,0 +1,66 @@ +query = $_GET; + $this->request = $_POST; + $this->server = $_SERVER; + $this->headers = array(); + } + + public function query($name, $default = null) + { + return isset($this->query[$name]) ? $this->query[$name] : $default; + } + + public function request($name, $default = null) + { + return isset($this->request[$name]) ? $this->request[$name] : $default; + } + + public function server($name, $default = null) + { + return isset($this->server[$name]) ? $this->server[$name] : $default; + } + + public function getAllQueryParameters() + { + return $this->query; + } + + public function setQuery(array $query) + { + $this->query = $query; + } + + public function setMethod($method) + { + $this->server['REQUEST_METHOD'] = $method; + } + + public function setPost(array $params) + { + $this->setMethod('POST'); + $this->request = $params; + } + + public static function createPost(array $params = array()) + { + $request = new self(); + $request->setPost($params); + + return $request; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/BaseTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/BaseTest.php new file mode 100755 index 0000000000..e841d3ad2f --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/BaseTest.php @@ -0,0 +1,38 @@ +getMemoryStorage(); + $sqlite = Bootstrap::getInstance()->getSqlitePdo(); + $mysql = Bootstrap::getInstance()->getMysqlPdo(); + $postgres = Bootstrap::getInstance()->getPostgresPdo(); + $mongo = Bootstrap::getInstance()->getMongo(); + $mongoDb = Bootstrap::getInstance()->getMongoDB(); + $redis = Bootstrap::getInstance()->getRedisStorage(); + $cassandra = Bootstrap::getInstance()->getCassandraStorage(); + $dynamodb = Bootstrap::getInstance()->getDynamoDbStorage(); + $couchbase = Bootstrap::getInstance()->getCouchbase(); + + /* hack until we can fix "default_scope" dependencies in other tests */ + $memory->defaultScope = 'defaultscope1 defaultscope2'; + + return array( + array($memory), + array($sqlite), + array($mysql), + array($postgres), + array($mongo), + array($mongoDb), + array($redis), + array($cassandra), + array($dynamodb), + array($couchbase), + ); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/Bootstrap.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/Bootstrap.php new file mode 100755 index 0000000000..8e428f9b53 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/Bootstrap.php @@ -0,0 +1,967 @@ +configDir = __DIR__.'/../../../config'; + } + + public static function getInstance() + { + if (!self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + public function getSqlitePdo() + { + if (!$this->sqlite) { + $this->removeSqliteDb(); + $pdo = new \PDO(sprintf('sqlite:%s', $this->getSqliteDir())); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->createSqliteDb($pdo); + + $this->sqlite = new Pdo($pdo); + } + + return $this->sqlite; + } + + public function getPostgresPdo() + { + if (!$this->postgres) { + if (in_array('pgsql', \PDO::getAvailableDrivers())) { + $this->removePostgresDb(); + $this->createPostgresDb(); + if ($pdo = $this->getPostgresDriver()) { + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->populatePostgresDb($pdo); + $this->postgres = new Pdo($pdo); + } + } else { + $this->postgres = new NullStorage('Postgres', 'Missing postgres PDO extension.'); + } + } + + return $this->postgres; + } + + public function getPostgresDriver() + { + try { + $pdo = new \PDO('pgsql:host=localhost;dbname=oauth2_server_php', 'postgres'); + + return $pdo; + } catch (\PDOException $e) { + $this->postgres = new NullStorage('Postgres', $e->getMessage()); + } + } + + public function getMemoryStorage() + { + return new Memory(json_decode(file_get_contents($this->configDir. '/storage.json'), true)); + } + + public function getRedisStorage() + { + if (!$this->redis) { + if (class_exists('Predis\Client')) { + $redis = new \Predis\Client(); + if ($this->testRedisConnection($redis)) { + $redis->flushdb(); + $this->redis = new Redis($redis); + $this->createRedisDb($this->redis); + } else { + $this->redis = new NullStorage('Redis', 'Unable to connect to redis server on port 6379'); + } + } else { + $this->redis = new NullStorage('Redis', 'Missing redis library. Please run "composer.phar require predis/predis:dev-master"'); + } + } + + return $this->redis; + } + + private function testRedisConnection(\Predis\Client $redis) + { + try { + $redis->connect(); + } catch (\Predis\CommunicationException $exception) { + // we were unable to connect to the redis server + return false; + } + + return true; + } + + public function getMysqlPdo() + { + if (!$this->mysql) { + $pdo = null; + try { + $pdo = new \PDO('mysql:host=localhost;', 'root'); + } catch (\PDOException $e) { + $this->mysql = new NullStorage('MySQL', 'Unable to connect to MySQL on root@localhost'); + } + + if ($pdo) { + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->removeMysqlDb($pdo); + $this->createMysqlDb($pdo); + + $this->mysql = new Pdo($pdo); + } + } + + return $this->mysql; + } + + public function getMongo() + { + if (!$this->mongo) { + if (class_exists('MongoClient')) { + $mongo = new \MongoClient('mongodb://localhost:27017', array('connect' => false)); + if ($this->testMongoConnection($mongo)) { + $db = $mongo->oauth2_server_php_legacy; + $this->removeMongo($db); + $this->createMongo($db); + + $this->mongo = new Mongo($db); + } else { + $this->mongo = new NullStorage('Mongo', 'Unable to connect to mongo server on "localhost:27017"'); + } + } else { + $this->mongo = new NullStorage('Mongo', 'Missing mongo php extension. Please install mongo.so'); + } + } + + return $this->mongo; + } + + public function getMongoDb() + { + if (!$this->mongoDb) { + if (class_exists('MongoDB\Client')) { + $mongoDb = new \MongoDB\Client('mongodb://localhost:27017'); + if ($this->testMongoDBConnection($mongoDb)) { + $db = $mongoDb->oauth2_server_php; + $this->removeMongoDb($db); + $this->createMongoDb($db); + + $this->mongoDb = new MongoDB($db); + } else { + $this->mongoDb = new NullStorage('MongoDB', 'Unable to connect to mongo server on "localhost:27017"'); + } + } else { + $this->mongoDb = new NullStorage('MongoDB', 'Missing MongoDB php extension. Please install mongodb.so'); + } + } + + return $this->mongoDb; + } + + private function testMongoConnection(\MongoClient $mongo) + { + try { + $mongo->connect(); + } catch (\MongoConnectionException $e) { + return false; + } + + return true; + } + + private function testMongoDBConnection(\MongoDB\Client $mongo) + { + return true; + } + + public function getCouchbase() + { + if (!$this->couchbase) { + if ($this->getEnvVar('SKIP_COUCHBASE_TESTS')) { + $this->couchbase = new NullStorage('Couchbase', 'Skipping Couchbase tests'); + } elseif (!class_exists('Couchbase')) { + $this->couchbase = new NullStorage('Couchbase', 'Missing Couchbase php extension. Please install couchbase.so'); + } else { + // round-about way to make sure couchbase is working + // this is required because it throws a "floating point exception" otherwise + $code = "new \Couchbase(array('localhost:8091'), '', '', 'auth', false);"; + $exec = sprintf('php -r "%s"', $code); + $ret = exec($exec, $test, $var); + if ($ret != 0) { + $couchbase = new \Couchbase(array('localhost:8091'), '', '', 'auth', false); + if ($this->testCouchbaseConnection($couchbase)) { + $this->clearCouchbase($couchbase); + $this->createCouchbaseDB($couchbase); + + $this->couchbase = new CouchbaseDB($couchbase); + } else { + $this->couchbase = new NullStorage('Couchbase', 'Unable to connect to Couchbase server on "localhost:8091"'); + } + } else { + $this->couchbase = new NullStorage('Couchbase', 'Error while trying to connect to Couchbase'); + } + } + } + + return $this->couchbase; + } + + private function testCouchbaseConnection(\Couchbase $couchbase) + { + try { + if (count($couchbase->getServers()) > 0) { + return true; + } + } catch (\CouchbaseException $e) { + return false; + } + + return true; + } + + public function getCassandraStorage() + { + if (!$this->cassandra) { + if (class_exists('phpcassa\ColumnFamily')) { + $cassandra = new \phpcassa\Connection\ConnectionPool('oauth2_test', array('127.0.0.1:9160')); + if ($this->testCassandraConnection($cassandra)) { + $this->removeCassandraDb(); + $this->cassandra = new Cassandra($cassandra); + $this->createCassandraDb($this->cassandra); + } else { + $this->cassandra = new NullStorage('Cassandra', 'Unable to connect to cassandra server on "127.0.0.1:9160"'); + } + } else { + $this->cassandra = new NullStorage('Cassandra', 'Missing cassandra library. Please run "composer.phar require thobbs/phpcassa:dev-master"'); + } + } + + return $this->cassandra; + } + + private function testCassandraConnection(\phpcassa\Connection\ConnectionPool $cassandra) + { + try { + new \phpcassa\SystemManager('localhost:9160'); + } catch (\Exception $e) { + return false; + } + + return true; + } + + private function removeCassandraDb() + { + $sys = new \phpcassa\SystemManager('localhost:9160'); + + try { + $sys->drop_keyspace('oauth2_test'); + } catch (\cassandra\InvalidRequestException $e) { + + } + } + + private function createCassandraDb(Cassandra $storage) + { + // create the cassandra keyspace and column family + $sys = new \phpcassa\SystemManager('localhost:9160'); + + $sys->create_keyspace('oauth2_test', array( + "strategy_class" => \phpcassa\Schema\StrategyClass::SIMPLE_STRATEGY, + "strategy_options" => array('replication_factor' => '1') + )); + + $sys->create_column_family('oauth2_test', 'auth'); + $cassandra = new \phpcassa\Connection\ConnectionPool('oauth2_test', array('127.0.0.1:9160')); + $cf = new \phpcassa\ColumnFamily($cassandra, 'auth'); + + // populate the data + $storage->setClientDetails("oauth_test_client", "testpass", "http://example.com", 'implicit password'); + $storage->setAccessToken("testtoken", "Some Client", '', time() + 1000); + $storage->setAuthorizationCode("testcode", "Some Client", '', '', time() + 1000); + + $storage->setScope('supportedscope1 supportedscope2 supportedscope3 supportedscope4'); + $storage->setScope('defaultscope1 defaultscope2', null, 'default'); + + $storage->setScope('clientscope1 clientscope2', 'Test Client ID'); + $storage->setScope('clientscope1 clientscope2', 'Test Client ID', 'default'); + + $storage->setScope('clientscope1 clientscope2 clientscope3', 'Test Client ID 2'); + $storage->setScope('clientscope1 clientscope2', 'Test Client ID 2', 'default'); + + $storage->setScope('clientscope1 clientscope2', 'Test Default Scope Client ID'); + $storage->setScope('clientscope1 clientscope2', 'Test Default Scope Client ID', 'default'); + + $storage->setScope('clientscope1 clientscope2 clientscope3', 'Test Default Scope Client ID 2'); + $storage->setScope('clientscope3', 'Test Default Scope Client ID 2', 'default'); + + $storage->setClientKey('oauth_test_client', $this->getTestPublicKey(), 'test_subject'); + + $cf->insert("oauth_public_keys:ClientID_One", array('__data' => json_encode(array("public_key" => "client_1_public", "private_key" => "client_1_private", "encryption_algorithm" => "RS256")))); + $cf->insert("oauth_public_keys:ClientID_Two", array('__data' => json_encode(array("public_key" => "client_2_public", "private_key" => "client_2_private", "encryption_algorithm" => "RS256")))); + $cf->insert("oauth_public_keys:", array('__data' => json_encode(array("public_key" => $this->getTestPublicKey(), "private_key" => $this->getTestPrivateKey(), "encryption_algorithm" => "RS256")))); + + $cf->insert("oauth_users:testuser", array('__data' =>json_encode(array("password" => "password", "email" => "testuser@test.com", "email_verified" => true)))); + + } + + private function createSqliteDb(\PDO $pdo) + { + $this->runPdoSql($pdo); + } + + private function removeSqliteDb() + { + if (file_exists($this->getSqliteDir())) { + unlink($this->getSqliteDir()); + } + } + + private function createMysqlDb(\PDO $pdo) + { + $pdo->exec('CREATE DATABASE oauth2_server_php'); + $pdo->exec('USE oauth2_server_php'); + $this->runPdoSql($pdo); + } + + private function removeMysqlDb(\PDO $pdo) + { + $pdo->exec('DROP DATABASE IF EXISTS oauth2_server_php'); + } + + private function createPostgresDb() + { + if (!`psql postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='postgres'"`) { + `createuser -s -r postgres`; + } + + `createdb -O postgres oauth2_server_php`; + } + + private function populatePostgresDb(\PDO $pdo) + { + $this->runPdoSql($pdo); + } + + private function removePostgresDb() + { + if (trim(`psql -l | grep oauth2_server_php | wc -l`)) { + `dropdb oauth2_server_php`; + } + } + + public function runPdoSql(\PDO $pdo) + { + $storage = new Pdo($pdo); + foreach (explode(';', $storage->getBuildSql()) as $statement) { + $result = $pdo->exec($statement); + } + + // set up scopes + $sql = 'INSERT INTO oauth_scopes (scope) VALUES (?)'; + foreach (explode(' ', 'supportedscope1 supportedscope2 supportedscope3 supportedscope4 clientscope1 clientscope2 clientscope3') as $supportedScope) { + $pdo->prepare($sql)->execute(array($supportedScope)); + } + + $sql = 'INSERT INTO oauth_scopes (scope, is_default) VALUES (?, ?)'; + foreach (array('defaultscope1', 'defaultscope2') as $defaultScope) { + $pdo->prepare($sql)->execute(array($defaultScope, true)); + } + + // set up clients + $sql = 'INSERT INTO oauth_clients (client_id, client_secret, scope, grant_types) VALUES (?, ?, ?, ?)'; + $pdo->prepare($sql)->execute(array('Test Client ID', 'TestSecret', 'clientscope1 clientscope2', null)); + $pdo->prepare($sql)->execute(array('Test Client ID 2', 'TestSecret', 'clientscope1 clientscope2 clientscope3', null)); + $pdo->prepare($sql)->execute(array('Test Default Scope Client ID', 'TestSecret', 'clientscope1 clientscope2', null)); + $pdo->prepare($sql)->execute(array('oauth_test_client', 'testpass', null, 'implicit password')); + + // set up misc + $sql = 'INSERT INTO oauth_access_tokens (access_token, client_id, expires, user_id) VALUES (?, ?, ?, ?)'; + $pdo->prepare($sql)->execute(array('testtoken', 'Some Client', date('Y-m-d H:i:s', strtotime('+1 hour')), null)); + $pdo->prepare($sql)->execute(array('accesstoken-openid-connect', 'Some Client', date('Y-m-d H:i:s', strtotime('+1 hour')), 'testuser')); + + $sql = 'INSERT INTO oauth_authorization_codes (authorization_code, client_id, expires) VALUES (?, ?, ?)'; + $pdo->prepare($sql)->execute(array('testcode', 'Some Client', date('Y-m-d H:i:s', strtotime('+1 hour')))); + + $sql = 'INSERT INTO oauth_users (username, password, email, email_verified) VALUES (?, ?, ?, ?)'; + $pdo->prepare($sql)->execute(array('testuser', 'password', 'testuser@test.com', true)); + + $sql = 'INSERT INTO oauth_public_keys (client_id, public_key, private_key, encryption_algorithm) VALUES (?, ?, ?, ?)'; + $pdo->prepare($sql)->execute(array('ClientID_One', 'client_1_public', 'client_1_private', 'RS256')); + $pdo->prepare($sql)->execute(array('ClientID_Two', 'client_2_public', 'client_2_private', 'RS256')); + + $sql = 'INSERT INTO oauth_public_keys (client_id, public_key, private_key, encryption_algorithm) VALUES (?, ?, ?, ?)'; + $pdo->prepare($sql)->execute(array(null, $this->getTestPublicKey(), $this->getTestPrivateKey(), 'RS256')); + + $sql = 'INSERT INTO oauth_jwt (client_id, subject, public_key) VALUES (?, ?, ?)'; + $pdo->prepare($sql)->execute(array('oauth_test_client', 'test_subject', $this->getTestPublicKey())); + } + + public function getSqliteDir() + { + return $this->configDir. '/test.sqlite'; + } + + public function getConfigDir() + { + return $this->configDir; + } + + private function createCouchbaseDB(\Couchbase $db) + { + $db->set('oauth_clients-oauth_test_client',json_encode(array( + 'client_id' => "oauth_test_client", + 'client_secret' => "testpass", + 'redirect_uri' => "http://example.com", + 'grant_types' => 'implicit password' + ))); + + $db->set('oauth_access_tokens-testtoken',json_encode(array( + 'access_token' => "testtoken", + 'client_id' => "Some Client" + ))); + + $db->set('oauth_authorization_codes-testcode',json_encode(array( + 'access_token' => "testcode", + 'client_id' => "Some Client" + ))); + + $db->set('oauth_users-testuser',json_encode(array( + 'username' => 'testuser', + 'password' => 'password', + 'email' => 'testuser@test.com', + 'email_verified' => true, + ))); + + $db->set('oauth_jwt-oauth_test_client',json_encode(array( + 'client_id' => 'oauth_test_client', + 'key' => $this->getTestPublicKey(), + 'subject' => 'test_subject', + ))); + } + + private function clearCouchbase(\Couchbase $cb) + { + $cb->delete('oauth_authorization_codes-new-openid-code'); + $cb->delete('oauth_access_tokens-newtoken'); + $cb->delete('oauth_authorization_codes-newcode'); + $cb->delete('oauth_refresh_tokens-refreshtoken'); + } + + private function createMongo(\MongoDB $db) + { + $db->oauth_clients->insert(array( + 'client_id' => "oauth_test_client", + 'client_secret' => "testpass", + 'redirect_uri' => "http://example.com", + 'grant_types' => 'implicit password' + )); + + $db->oauth_access_tokens->insert(array( + 'access_token' => "testtoken", + 'client_id' => "Some Client" + )); + + $db->oauth_authorization_codes->insert(array( + 'authorization_code' => "testcode", + 'client_id' => "Some Client" + )); + + $db->oauth_users->insert(array( + 'username' => 'testuser', + 'password' => 'password', + 'email' => 'testuser@test.com', + 'email_verified' => true, + )); + + $db->oauth_keys->insert(array( + 'client_id' => null, + 'public_key' => $this->getTestPublicKey(), + 'private_key' => $this->getTestPrivateKey(), + 'encryption_algorithm' => 'RS256' + )); + + $db->oauth_jwt->insert(array( + 'client_id' => 'oauth_test_client', + 'key' => $this->getTestPublicKey(), + 'subject' => 'test_subject', + )); + } + + public function removeMongo(\MongoDB $db) + { + $db->drop(); + } + + private function createMongoDB(\MongoDB\Database $db) + { + $db->oauth_clients->insertOne(array( + 'client_id' => "oauth_test_client", + 'client_secret' => "testpass", + 'redirect_uri' => "http://example.com", + 'grant_types' => 'implicit password' + )); + + $db->oauth_access_tokens->insertOne(array( + 'access_token' => "testtoken", + 'client_id' => "Some Client" + )); + + $db->oauth_authorization_codes->insertOne(array( + 'authorization_code' => "testcode", + 'client_id' => "Some Client" + )); + + $db->oauth_users->insertOne(array( + 'username' => 'testuser', + 'password' => 'password', + 'email' => 'testuser@test.com', + 'email_verified' => true, + )); + + $db->oauth_keys->insertOne(array( + 'client_id' => null, + 'public_key' => $this->getTestPublicKey(), + 'private_key' => $this->getTestPrivateKey(), + 'encryption_algorithm' => 'RS256' + )); + + $db->oauth_jwt->insertOne(array( + 'client_id' => 'oauth_test_client', + 'key' => $this->getTestPublicKey(), + 'subject' => 'test_subject', + )); + } + + public function removeMongoDB(\MongoDB\Database $db) + { + $db->drop(); + } + + private function createRedisDb(Redis $storage) + { + $storage->setClientDetails("oauth_test_client", "testpass", "http://example.com", 'implicit password'); + $storage->setAccessToken("testtoken", "Some Client", '', time() + 1000); + $storage->setAuthorizationCode("testcode", "Some Client", '', '', time() + 1000); + $storage->setUser("testuser", "password"); + + $storage->setScope('supportedscope1 supportedscope2 supportedscope3 supportedscope4'); + $storage->setScope('defaultscope1 defaultscope2', null, 'default'); + + $storage->setScope('clientscope1 clientscope2', 'Test Client ID'); + $storage->setScope('clientscope1 clientscope2', 'Test Client ID', 'default'); + + $storage->setScope('clientscope1 clientscope2 clientscope3', 'Test Client ID 2'); + $storage->setScope('clientscope1 clientscope2', 'Test Client ID 2', 'default'); + + $storage->setScope('clientscope1 clientscope2', 'Test Default Scope Client ID'); + $storage->setScope('clientscope1 clientscope2', 'Test Default Scope Client ID', 'default'); + + $storage->setScope('clientscope1 clientscope2 clientscope3', 'Test Default Scope Client ID 2'); + $storage->setScope('clientscope3', 'Test Default Scope Client ID 2', 'default'); + + $storage->setClientKey('oauth_test_client', $this->getTestPublicKey(), 'test_subject'); + } + + public function getTestPublicKey() + { + return file_get_contents(__DIR__.'/../../../config/keys/id_rsa.pub'); + } + + private function getTestPrivateKey() + { + return file_get_contents(__DIR__.'/../../../config/keys/id_rsa'); + } + + public function getDynamoDbStorage() + { + if (!$this->dynamodb) { + // only run once per travis build + if (true == $this->getEnvVar('TRAVIS')) { + if (self::DYNAMODB_PHP_VERSION != $this->getEnvVar('TRAVIS_PHP_VERSION')) { + $this->dynamodb = new NullStorage('DynamoDb', 'Skipping for travis.ci - only run once per build'); + + return; + } + } + if (class_exists('\Aws\DynamoDb\DynamoDbClient')) { + if ($client = $this->getDynamoDbClient()) { + // travis runs a unique set of tables per build, to avoid conflict + $prefix = ''; + if ($build_id = $this->getEnvVar('TRAVIS_JOB_NUMBER')) { + $prefix = sprintf('build_%s_', $build_id); + } else { + if (!$this->deleteDynamoDb($client, $prefix, true)) { + return $this->dynamodb = new NullStorage('DynamoDb', 'Timed out while waiting for DynamoDB deletion (30 seconds)'); + } + } + $this->createDynamoDb($client, $prefix); + $this->populateDynamoDb($client, $prefix); + $config = array( + 'client_table' => $prefix.'oauth_clients', + 'access_token_table' => $prefix.'oauth_access_tokens', + 'refresh_token_table' => $prefix.'oauth_refresh_tokens', + 'code_table' => $prefix.'oauth_authorization_codes', + 'user_table' => $prefix.'oauth_users', + 'jwt_table' => $prefix.'oauth_jwt', + 'scope_table' => $prefix.'oauth_scopes', + 'public_key_table' => $prefix.'oauth_public_keys', + ); + $this->dynamodb = new DynamoDB($client, $config); + } elseif (!$this->dynamodb) { + $this->dynamodb = new NullStorage('DynamoDb', 'unable to connect to DynamoDB'); + } + } else { + $this->dynamodb = new NullStorage('DynamoDb', 'Missing DynamoDB library. Please run "composer.phar require aws/aws-sdk-php:dev-master'); + } + } + + return $this->dynamodb; + } + + private function getDynamoDbClient() + { + $config = array(); + // check for environment variables + if (($key = $this->getEnvVar('AWS_ACCESS_KEY_ID')) && ($secret = $this->getEnvVar('AWS_SECRET_KEY'))) { + $config['key'] = $key; + $config['secret'] = $secret; + } else { + // fall back on ~/.aws/credentials file + // @see http://docs.aws.amazon.com/aws-sdk-php/guide/latest/credentials.html#credential-profiles + if (!file_exists($this->getEnvVar('HOME') . '/.aws/credentials')) { + $this->dynamodb = new NullStorage('DynamoDb', 'No aws credentials file found, and no AWS_ACCESS_KEY_ID or AWS_SECRET_KEY environment variable set'); + + return; + } + + // set profile in AWS_PROFILE environment variable, defaults to "default" + $config['profile'] = $this->getEnvVar('AWS_PROFILE', 'default'); + } + + // set region in AWS_REGION environment variable, defaults to "us-east-1" + $config['region'] = $this->getEnvVar('AWS_REGION', \Aws\Common\Enum\Region::US_EAST_1); + + return \Aws\DynamoDb\DynamoDbClient::factory($config); + } + + private function deleteDynamoDb(\Aws\DynamoDb\DynamoDbClient $client, $prefix = null, $waitForDeletion = false) + { + $tablesList = explode(' ', 'oauth_access_tokens oauth_authorization_codes oauth_clients oauth_jwt oauth_public_keys oauth_refresh_tokens oauth_scopes oauth_users'); + $nbTables = count($tablesList); + + // Delete all table. + foreach ($tablesList as $key => $table) { + try { + $client->deleteTable(array('TableName' => $prefix.$table)); + } catch (\Aws\DynamoDb\Exception\DynamoDbException $e) { + // Table does not exist : nothing to do + } + } + + // Wait for deleting + if ($waitForDeletion) { + $retries = 5; + $nbTableDeleted = 0; + while ($nbTableDeleted != $nbTables) { + $nbTableDeleted = 0; + foreach ($tablesList as $key => $table) { + try { + $result = $client->describeTable(array('TableName' => $prefix.$table)); + } catch (\Aws\DynamoDb\Exception\DynamoDbException $e) { + // Table does not exist : nothing to do + $nbTableDeleted++; + } + } + if ($nbTableDeleted != $nbTables) { + if ($retries < 0) { + // we are tired of waiting + return false; + } + sleep(5); + echo "Sleeping 5 seconds for DynamoDB ($retries more retries)...\n"; + $retries--; + } + } + } + + return true; + } + + private function createDynamoDb(\Aws\DynamoDb\DynamoDbClient $client, $prefix = null) + { + $tablesList = explode(' ', 'oauth_access_tokens oauth_authorization_codes oauth_clients oauth_jwt oauth_public_keys oauth_refresh_tokens oauth_scopes oauth_users'); + $nbTables = count($tablesList); + $client->createTable(array( + 'TableName' => $prefix.'oauth_access_tokens', + 'AttributeDefinitions' => array( + array('AttributeName' => 'access_token','AttributeType' => 'S') + ), + 'KeySchema' => array(array('AttributeName' => 'access_token','KeyType' => 'HASH')), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + $client->createTable(array( + 'TableName' => $prefix.'oauth_authorization_codes', + 'AttributeDefinitions' => array( + array('AttributeName' => 'authorization_code','AttributeType' => 'S') + ), + 'KeySchema' => array(array('AttributeName' => 'authorization_code','KeyType' => 'HASH')), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + $client->createTable(array( + 'TableName' => $prefix.'oauth_clients', + 'AttributeDefinitions' => array( + array('AttributeName' => 'client_id','AttributeType' => 'S') + ), + 'KeySchema' => array(array('AttributeName' => 'client_id','KeyType' => 'HASH')), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + $client->createTable(array( + 'TableName' => $prefix.'oauth_jwt', + 'AttributeDefinitions' => array( + array('AttributeName' => 'client_id','AttributeType' => 'S'), + array('AttributeName' => 'subject','AttributeType' => 'S') + ), + 'KeySchema' => array( + array('AttributeName' => 'client_id','KeyType' => 'HASH'), + array('AttributeName' => 'subject','KeyType' => 'RANGE') + ), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + $client->createTable(array( + 'TableName' => $prefix.'oauth_public_keys', + 'AttributeDefinitions' => array( + array('AttributeName' => 'client_id','AttributeType' => 'S') + ), + 'KeySchema' => array(array('AttributeName' => 'client_id','KeyType' => 'HASH')), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + $client->createTable(array( + 'TableName' => $prefix.'oauth_refresh_tokens', + 'AttributeDefinitions' => array( + array('AttributeName' => 'refresh_token','AttributeType' => 'S') + ), + 'KeySchema' => array(array('AttributeName' => 'refresh_token','KeyType' => 'HASH')), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + $client->createTable(array( + 'TableName' => $prefix.'oauth_scopes', + 'AttributeDefinitions' => array( + array('AttributeName' => 'scope','AttributeType' => 'S'), + array('AttributeName' => 'is_default','AttributeType' => 'S') + ), + 'KeySchema' => array(array('AttributeName' => 'scope','KeyType' => 'HASH')), + 'GlobalSecondaryIndexes' => array( + array( + 'IndexName' => 'is_default-index', + 'KeySchema' => array(array('AttributeName' => 'is_default', 'KeyType' => 'HASH')), + 'Projection' => array('ProjectionType' => 'ALL'), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + ), + ), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + $client->createTable(array( + 'TableName' => $prefix.'oauth_users', + 'AttributeDefinitions' => array(array('AttributeName' => 'username','AttributeType' => 'S')), + 'KeySchema' => array(array('AttributeName' => 'username','KeyType' => 'HASH')), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + // Wait for creation + $nbTableCreated = 0; + while ($nbTableCreated != $nbTables) { + $nbTableCreated = 0; + foreach ($tablesList as $key => $table) { + try { + $result = $client->describeTable(array('TableName' => $prefix.$table)); + if ($result['Table']['TableStatus'] == 'ACTIVE') { + $nbTableCreated++; + } + } catch (\Aws\DynamoDb\Exception\DynamoDbException $e) { + // Table does not exist : nothing to do + $nbTableCreated++; + } + } + if ($nbTableCreated != $nbTables) { + sleep(1); + } + } + } + + private function populateDynamoDb($client, $prefix = null) + { + // set up scopes + foreach (explode(' ', 'supportedscope1 supportedscope2 supportedscope3 supportedscope4 clientscope1 clientscope2 clientscope3') as $supportedScope) { + $client->putItem(array( + 'TableName' => $prefix.'oauth_scopes', + 'Item' => array('scope' => array('S' => $supportedScope)) + )); + } + + foreach (array('defaultscope1', 'defaultscope2') as $defaultScope) { + $client->putItem(array( + 'TableName' => $prefix.'oauth_scopes', + 'Item' => array('scope' => array('S' => $defaultScope), 'is_default' => array('S' => "true")) + )); + } + + $client->putItem(array( + 'TableName' => $prefix.'oauth_clients', + 'Item' => array( + 'client_id' => array('S' => 'Test Client ID'), + 'client_secret' => array('S' => 'TestSecret'), + 'scope' => array('S' => 'clientscope1 clientscope2') + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_clients', + 'Item' => array( + 'client_id' => array('S' => 'Test Client ID 2'), + 'client_secret' => array('S' => 'TestSecret'), + 'scope' => array('S' => 'clientscope1 clientscope2 clientscope3') + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_clients', + 'Item' => array( + 'client_id' => array('S' => 'Test Default Scope Client ID'), + 'client_secret' => array('S' => 'TestSecret'), + 'scope' => array('S' => 'clientscope1 clientscope2') + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_clients', + 'Item' => array( + 'client_id' => array('S' => 'oauth_test_client'), + 'client_secret' => array('S' => 'testpass'), + 'grant_types' => array('S' => 'implicit password') + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_access_tokens', + 'Item' => array( + 'access_token' => array('S' => 'testtoken'), + 'client_id' => array('S' => 'Some Client'), + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_access_tokens', + 'Item' => array( + 'access_token' => array('S' => 'accesstoken-openid-connect'), + 'client_id' => array('S' => 'Some Client'), + 'user_id' => array('S' => 'testuser'), + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_authorization_codes', + 'Item' => array( + 'authorization_code' => array('S' => 'testcode'), + 'client_id' => array('S' => 'Some Client'), + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_users', + 'Item' => array( + 'username' => array('S' => 'testuser'), + 'password' => array('S' => 'password'), + 'email' => array('S' => 'testuser@test.com'), + 'email_verified' => array('S' => 'true'), + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_public_keys', + 'Item' => array( + 'client_id' => array('S' => 'ClientID_One'), + 'public_key' => array('S' => 'client_1_public'), + 'private_key' => array('S' => 'client_1_private'), + 'encryption_algorithm' => array('S' => 'RS256'), + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_public_keys', + 'Item' => array( + 'client_id' => array('S' => 'ClientID_Two'), + 'public_key' => array('S' => 'client_2_public'), + 'private_key' => array('S' => 'client_2_private'), + 'encryption_algorithm' => array('S' => 'RS256'), + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_public_keys', + 'Item' => array( + 'client_id' => array('S' => '0'), + 'public_key' => array('S' => $this->getTestPublicKey()), + 'private_key' => array('S' => $this->getTestPrivateKey()), + 'encryption_algorithm' => array('S' => 'RS256'), + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_jwt', + 'Item' => array( + 'client_id' => array('S' => 'oauth_test_client'), + 'subject' => array('S' => 'test_subject'), + 'public_key' => array('S' => $this->getTestPublicKey()), + ) + )); + } + + public function cleanupTravisDynamoDb($prefix = null) + { + if (is_null($prefix)) { + // skip this when not applicable + if (!$this->getEnvVar('TRAVIS') || self::DYNAMODB_PHP_VERSION != $this->getEnvVar('TRAVIS_PHP_VERSION')) { + return; + } + + $prefix = sprintf('build_%s_', $this->getEnvVar('TRAVIS_JOB_NUMBER')); + } + + $client = $this->getDynamoDbClient(); + $this->deleteDynamoDb($client, $prefix); + } + + private function getEnvVar($var, $default = null) + { + return isset($_SERVER[$var]) ? $_SERVER[$var] : (getenv($var) ?: $default); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/NullStorage.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/NullStorage.php new file mode 100644 index 0000000000..6caa620688 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/NullStorage.php @@ -0,0 +1,32 @@ +name = $name; + $this->description = $description; + } + + public function __toString() + { + return $this->name; + } + + public function getMessage() + { + if ($this->description) { + return $this->description; + } + + return $this->name; + } +} diff --git a/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/LICENSE b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/LICENSE new file mode 100644 index 0000000000..2ee1671db1 --- /dev/null +++ b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Carbon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/README.md b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/README.md new file mode 100644 index 0000000000..5a18121b6c --- /dev/null +++ b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/README.md @@ -0,0 +1,14 @@ +# carbonphp/carbon-doctrine-types + +Types to use Carbon in Doctrine + +## Documentation + +[Check how to use in the official Carbon documentation](https://carbon.nesbot.com/symfony/) + +This package is an externalization of [src/Carbon/Doctrine](https://github.com/briannesbitt/Carbon/tree/2.71.0/src/Carbon/Doctrine) +from `nestbot/carbon` package. + +Externalization allows to better deal with different versions of dbal. With +version 4.0 of dbal, it no longer sustainable to be compatible with all version +using a single code. diff --git a/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/composer.json b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/composer.json new file mode 100644 index 0000000000..abf45c5fb5 --- /dev/null +++ b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/composer.json @@ -0,0 +1,36 @@ +{ + "name": "carbonphp/carbon-doctrine-types", + "description": "Types to use Carbon in Doctrine", + "type": "library", + "keywords": [ + "date", + "time", + "DateTime", + "Carbon", + "Doctrine" + ], + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" + }, + "license": "MIT", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "minimum-stability": "dev" +} diff --git a/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/CarbonDoctrineType.php b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/CarbonDoctrineType.php new file mode 100644 index 0000000000..a63a9b8d27 --- /dev/null +++ b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/CarbonDoctrineType.php @@ -0,0 +1,16 @@ + + */ + protected function getCarbonClassName(): string + { + return Carbon::class; + } + + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string + { + $precision = min( + $fieldDeclaration['precision'] ?? DateTimeDefaultPrecision::get(), + $this->getMaximumPrecision($platform), + ); + + $type = parent::getSQLDeclaration($fieldDeclaration, $platform); + + if (!$precision) { + return $type; + } + + if (str_contains($type, '(')) { + return preg_replace('/\(\d+\)/', "($precision)", $type); + } + + [$before, $after] = explode(' ', "$type "); + + return trim("$before($precision) $after"); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string + { + if ($value === null) { + return $value; + } + + if ($value instanceof DateTimeInterface) { + return $value->format('Y-m-d H:i:s.u'); + } + + throw InvalidType::new( + $value, + static::class, + ['null', 'DateTime', 'Carbon'] + ); + } + + private function doConvertToPHPValue(mixed $value) + { + $class = $this->getCarbonClassName(); + + if ($value === null || is_a($value, $class)) { + return $value; + } + + if ($value instanceof DateTimeInterface) { + return $class::instance($value); + } + + $date = null; + $error = null; + + try { + $date = $class::parse($value); + } catch (Exception $exception) { + $error = $exception; + } + + if (!$date) { + throw ValueNotConvertible::new( + $value, + static::class, + 'Y-m-d H:i:s.u or any format supported by '.$class.'::parse()', + $error + ); + } + + return $date; + } + + private function getMaximumPrecision(AbstractPlatform $platform): int + { + if ($platform instanceof DB2Platform) { + return 12; + } + + if ($platform instanceof OraclePlatform) { + return 9; + } + + if ($platform instanceof SQLServerPlatform || $platform instanceof SQLitePlatform) { + return 3; + } + + return 6; + } +} diff --git a/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/DateTimeDefaultPrecision.php b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/DateTimeDefaultPrecision.php new file mode 100644 index 0000000000..cd9896f9ca --- /dev/null +++ b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/DateTimeDefaultPrecision.php @@ -0,0 +1,30 @@ + */ + use CarbonTypeConverter; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?CarbonImmutable + { + return $this->doConvertToPHPValue($value); + } + + /** + * @return class-string + */ + protected function getCarbonClassName(): string + { + return CarbonImmutable::class; + } +} diff --git a/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/DateTimeType.php b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/DateTimeType.php new file mode 100644 index 0000000000..89e4b7903a --- /dev/null +++ b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/DateTimeType.php @@ -0,0 +1,24 @@ + */ + use CarbonTypeConverter; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?Carbon + { + return $this->doConvertToPHPValue($value); + } +} diff --git a/data/web/inc/lib/vendor/composer/ClassLoader.php b/data/web/inc/lib/vendor/composer/ClassLoader.php index dc02dfb114..7824d8f7ea 100644 --- a/data/web/inc/lib/vendor/composer/ClassLoader.php +++ b/data/web/inc/lib/vendor/composer/ClassLoader.php @@ -37,57 +37,126 @@ * * @author Fabien Potencier * @author Jordi Boggiano - * @see http://www.php-fig.org/psr/psr-0/ - * @see http://www.php-fig.org/psr/psr-4/ + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + // PSR-4 + /** + * @var array> + */ private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ private $prefixDirsPsr4 = array(); + /** + * @var list + */ private $fallbackDirsPsr4 = array(); // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ private $prefixesPsr0 = array(); + /** + * @var list + */ private $fallbackDirsPsr0 = array(); + /** @var bool */ private $useIncludePath = false; + + /** + * @var array + */ private $classMap = array(); + + /** @var bool */ private $classMapAuthoritative = false; + + /** + * @var array + */ private $missingClasses = array(); + + /** @var string|null */ private $apcuPrefix; + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { - return call_user_func_array('array_merge', $this->prefixesPsr0); + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); } + /** + * @return array> + */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } + /** + * @return list + */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } + /** + * @return list + */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } + /** + * @return array Array of classname => path + */ public function getClassMap() { return $this->classMap; } /** - * @param array $classMap Class to filename map + * @param array $classMap Class to filename map + * + * @return void */ public function addClassMap(array $classMap) { @@ -102,22 +171,25 @@ public function addClassMap(array $classMap) * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * - * @param string $prefix The prefix - * @param array|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void */ public function add($prefix, $paths, $prepend = false) { + $paths = (array) $paths; if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr0 = array_merge( - (array) $paths, + $paths, $this->fallbackDirsPsr0 ); } else { $this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0, - (array) $paths + $paths ); } @@ -126,19 +198,19 @@ public function add($prefix, $paths, $prepend = false) $first = $prefix[0]; if (!isset($this->prefixesPsr0[$first][$prefix])) { - $this->prefixesPsr0[$first][$prefix] = (array) $paths; + $this->prefixesPsr0[$first][$prefix] = $paths; return; } if ($prepend) { $this->prefixesPsr0[$first][$prefix] = array_merge( - (array) $paths, + $paths, $this->prefixesPsr0[$first][$prefix] ); } else { $this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix], - (array) $paths + $paths ); } } @@ -147,25 +219,28 @@ public function add($prefix, $paths, $prepend = false) * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param array|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException + * + * @return void */ public function addPsr4($prefix, $paths, $prepend = false) { + $paths = (array) $paths; if (!$prefix) { // Register directories for the root namespace. if ($prepend) { $this->fallbackDirsPsr4 = array_merge( - (array) $paths, + $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, - (array) $paths + $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { @@ -175,18 +250,18 @@ public function addPsr4($prefix, $paths, $prepend = false) throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; - $this->prefixDirsPsr4[$prefix] = (array) $paths; + $this->prefixDirsPsr4[$prefix] = $paths; } elseif ($prepend) { // Prepend directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( - (array) $paths, + $paths, $this->prefixDirsPsr4[$prefix] ); } else { // Append directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], - (array) $paths + $paths ); } } @@ -195,8 +270,10 @@ public function addPsr4($prefix, $paths, $prepend = false) * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * - * @param string $prefix The prefix - * @param array|string $paths The PSR-0 base directories + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void */ public function set($prefix, $paths) { @@ -211,10 +288,12 @@ public function set($prefix, $paths) * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param array|string $paths The PSR-4 base directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException + * + * @return void */ public function setPsr4($prefix, $paths) { @@ -234,6 +313,8 @@ public function setPsr4($prefix, $paths) * Turns on searching the include path for class files. * * @param bool $useIncludePath + * + * @return void */ public function setUseIncludePath($useIncludePath) { @@ -256,6 +337,8 @@ public function getUseIncludePath() * that have not been registered with the class map. * * @param bool $classMapAuthoritative + * + * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { @@ -276,10 +359,12 @@ public function isClassMapAuthoritative() * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix + * + * @return void */ public function setApcuPrefix($apcuPrefix) { - $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; } /** @@ -296,33 +381,55 @@ public function getApcuPrefix() * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void */ public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } } /** * Unregisters this instance as an autoloader. + * + * @return void */ public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } } /** * Loads the given class or interface. * * @param string $class The name of the class - * @return bool|null True if loaded, null otherwise + * @return true|null True if loaded, null otherwise */ public function loadClass($class) { if ($file = $this->findFile($class)) { - includeFile($file); + $includeFile = self::$includeFile; + $includeFile($file); return true; } + + return null; } /** @@ -367,6 +474,21 @@ public function findFile($class) return $file; } + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup @@ -377,7 +499,7 @@ private function findFileWithExtension($class, $ext) $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); - $search = $subPath.'\\'; + $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { @@ -432,14 +554,26 @@ private function findFileWithExtension($class, $ext) return false; } -} -/** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - */ -function includeFile($file) -{ - include $file; + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } } diff --git a/data/web/inc/lib/vendor/composer/InstalledVersions.php b/data/web/inc/lib/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000000..6d29bff66a --- /dev/null +++ b/data/web/inc/lib/vendor/composer/InstalledVersions.php @@ -0,0 +1,378 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool + */ + private static $installedIsLocalDir; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + $copiedLocalDir = false; + + if (self::$canGetVendors) { + $selfDir = strtr(__DIR__, '\\', '/'); + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + self::$installed = $required; + self::$installedIsLocalDir = true; + } + } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array() && !$copiedLocalDir) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/data/web/inc/lib/vendor/composer/autoload_classmap.php b/data/web/inc/lib/vendor/composer/autoload_classmap.php index bf29c8913a..3885b22a68 100644 --- a/data/web/inc/lib/vendor/composer/autoload_classmap.php +++ b/data/web/inc/lib/vendor/composer/autoload_classmap.php @@ -2,21 +2,16 @@ // autoload_classmap.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( - 'EasyPeasyICS' => $vendorDir . '/phpmailer/phpmailer/extras/EasyPeasyICS.php', - 'PHPMailer' => $vendorDir . '/phpmailer/phpmailer/class.phpmailer.php', - 'PHPMailerOAuth' => $vendorDir . '/phpmailer/phpmailer/class.phpmaileroauth.php', - 'PHPMailerOAuthGoogle' => $vendorDir . '/phpmailer/phpmailer/class.phpmaileroauthgoogle.php', - 'POP3' => $vendorDir . '/phpmailer/phpmailer/class.pop3.php', - 'SMTP' => $vendorDir . '/phpmailer/phpmailer/class.smtp.php', - 'ntlm_sasl_client_class' => $vendorDir . '/phpmailer/phpmailer/extras/ntlm_sasl_client.php', - 'phpmailerException' => $vendorDir . '/phpmailer/phpmailer/class.phpmailer.php', - 'u2flib_server\\Error' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php', - 'u2flib_server\\RegisterRequest' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php', - 'u2flib_server\\Registration' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php', - 'u2flib_server\\SignRequest' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php', - 'u2flib_server\\U2F' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php', + 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CURLStringFile' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', ); diff --git a/data/web/inc/lib/vendor/composer/autoload_files.php b/data/web/inc/lib/vendor/composer/autoload_files.php new file mode 100644 index 0000000000..e2cabb90dc --- /dev/null +++ b/data/web/inc/lib/vendor/composer/autoload_files.php @@ -0,0 +1,24 @@ + $vendorDir . '/symfony/deprecation-contracts/function.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', + 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + 'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php', + '60799491728b879e74601d83e38b2cad' => $vendorDir . '/illuminate/collections/helpers.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + '23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php', + '04c6c5c2f7095ccf6c481d3e53e1776f' => $vendorDir . '/mustangostang/spyc/Spyc.php', + '89efb1254ef2d1c5d80096acd12c4098' => $vendorDir . '/twig/twig/src/Resources/core.php', + 'ffecb95d45175fd40f75be8a23b34f90' => $vendorDir . '/twig/twig/src/Resources/debug.php', + 'c7baa00073ee9c61edf148c51917cfb4' => $vendorDir . '/twig/twig/src/Resources/escaper.php', + 'f844ccf1d25df8663951193c3fc307c8' => $vendorDir . '/twig/twig/src/Resources/string_loader.php', +); diff --git a/data/web/inc/lib/vendor/composer/autoload_namespaces.php b/data/web/inc/lib/vendor/composer/autoload_namespaces.php index b7fc0125db..207e705ff7 100644 --- a/data/web/inc/lib/vendor/composer/autoload_namespaces.php +++ b/data/web/inc/lib/vendor/composer/autoload_namespaces.php @@ -2,8 +2,9 @@ // autoload_namespaces.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( + 'OAuth2' => array($vendorDir . '/bshaffer/oauth2-server-php/src'), ); diff --git a/data/web/inc/lib/vendor/composer/autoload_psr4.php b/data/web/inc/lib/vendor/composer/autoload_psr4.php index cfa01d564c..236034cf0e 100644 --- a/data/web/inc/lib/vendor/composer/autoload_psr4.php +++ b/data/web/inc/lib/vendor/composer/autoload_psr4.php @@ -2,11 +2,41 @@ // autoload_psr4.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( + 'Twig\\' => array($vendorDir . '/twig/twig/src'), + 'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'), + 'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'), + 'Stevenmaguire\\OAuth2\\Client\\' => array($vendorDir . '/stevenmaguire/oauth2-keycloak/src'), 'RobThree\\Auth\\' => array($vendorDir . '/robthree/twofactorauth/lib'), + 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), + 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'), 'PhpMimeMailParser\\' => array($vendorDir . '/php-mime-mail-parser/php-mime-mail-parser/src'), + 'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'), + 'MatthiasMullie\\PathConverter\\' => array($vendorDir . '/matthiasmullie/path-converter/src'), + 'MatthiasMullie\\Minify\\' => array($vendorDir . '/matthiasmullie/minify/src'), + 'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src'), + 'LdapRecord\\' => array($vendorDir . '/directorytree/ldaprecord/src'), + 'Illuminate\\Support\\' => array($vendorDir . '/illuminate/collections', $vendorDir . '/illuminate/macroable', $vendorDir . '/illuminate/conditionable'), + 'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'), 'Html2Text\\' => array($vendorDir . '/soundasleep/html2text/src'), + 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), + 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), + 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), + 'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'), + 'Ddeboer\\Imap\\' => array($vendorDir . '/ddeboer/imap/src'), + 'DASPRiD\\Enum\\' => array($vendorDir . '/dasprid/enum/src'), + 'Carbon\\Doctrine\\' => array($vendorDir . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine'), + 'Carbon\\' => array($vendorDir . '/nesbot/carbon/src/Carbon'), + 'BaconQrCode\\' => array($vendorDir . '/bacon/bacon-qr-code/src'), ); diff --git a/data/web/inc/lib/vendor/composer/autoload_real.php b/data/web/inc/lib/vendor/composer/autoload_real.php index caf455f3d8..5e20825340 100644 --- a/data/web/inc/lib/vendor/composer/autoload_real.php +++ b/data/web/inc/lib/vendor/composer/autoload_real.php @@ -13,40 +13,38 @@ public static function loadClassLoader($class) } } + /** + * @return \Composer\Autoload\ClassLoader + */ public static function getLoader() { if (null !== self::$loader) { return self::$loader; } + require __DIR__ . '/platform_check.php'; + spl_autoload_register(array('ComposerAutoloaderInit873464e4bd965a3168f133248b1b218b', 'loadClassLoader'), true, true); - self::$loader = $loader = new \Composer\Autoload\ClassLoader(); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); spl_autoload_unregister(array('ComposerAutoloaderInit873464e4bd965a3168f133248b1b218b', 'loadClassLoader')); - $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); - if ($useStaticLoader) { - require_once __DIR__ . '/autoload_static.php'; + require __DIR__ . '/autoload_static.php'; + call_user_func(\Composer\Autoload\ComposerStaticInit873464e4bd965a3168f133248b1b218b::getInitializer($loader)); - call_user_func(\Composer\Autoload\ComposerStaticInit873464e4bd965a3168f133248b1b218b::getInitializer($loader)); - } else { - $map = require __DIR__ . '/autoload_namespaces.php'; - foreach ($map as $namespace => $path) { - $loader->set($namespace, $path); - } + $loader->register(true); - $map = require __DIR__ . '/autoload_psr4.php'; - foreach ($map as $namespace => $path) { - $loader->setPsr4($namespace, $path); - } + $filesToLoad = \Composer\Autoload\ComposerStaticInit873464e4bd965a3168f133248b1b218b::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; - $classMap = require __DIR__ . '/autoload_classmap.php'; - if ($classMap) { - $loader->addClassMap($classMap); + require $file; } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); } - $loader->register(true); - return $loader; } } diff --git a/data/web/inc/lib/vendor/composer/autoload_static.php b/data/web/inc/lib/vendor/composer/autoload_static.php index 9d9f1a85ca..f66ad0c718 100644 --- a/data/web/inc/lib/vendor/composer/autoload_static.php +++ b/data/web/inc/lib/vendor/composer/autoload_static.php @@ -6,50 +6,256 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b { + public static $files = array ( + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', + 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + 'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php', + '60799491728b879e74601d83e38b2cad' => __DIR__ . '/..' . '/illuminate/collections/helpers.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + '23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php', + '04c6c5c2f7095ccf6c481d3e53e1776f' => __DIR__ . '/..' . '/mustangostang/spyc/Spyc.php', + '89efb1254ef2d1c5d80096acd12c4098' => __DIR__ . '/..' . '/twig/twig/src/Resources/core.php', + 'ffecb95d45175fd40f75be8a23b34f90' => __DIR__ . '/..' . '/twig/twig/src/Resources/debug.php', + 'c7baa00073ee9c61edf148c51917cfb4' => __DIR__ . '/..' . '/twig/twig/src/Resources/escaper.php', + 'f844ccf1d25df8663951193c3fc307c8' => __DIR__ . '/..' . '/twig/twig/src/Resources/string_loader.php', + ); + public static $prefixLengthsPsr4 = array ( + 'T' => + array ( + 'Twig\\' => 5, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Php81\\' => 23, + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Contracts\\Translation\\' => 30, + 'Symfony\\Component\\Translation\\' => 30, + 'Stevenmaguire\\OAuth2\\Client\\' => 28, + ), 'R' => array ( 'RobThree\\Auth\\' => 14, ), 'P' => array ( + 'Psr\\SimpleCache\\' => 16, + 'Psr\\Log\\' => 8, + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Http\\Client\\' => 16, + 'Psr\\Container\\' => 14, + 'Psr\\Clock\\' => 10, 'PhpMimeMailParser\\' => 18, + 'PHPMailer\\PHPMailer\\' => 20, + ), + 'M' => + array ( + 'MatthiasMullie\\PathConverter\\' => 29, + 'MatthiasMullie\\Minify\\' => 22, + ), + 'L' => + array ( + 'League\\OAuth2\\Client\\' => 21, + 'LdapRecord\\' => 11, + ), + 'I' => + array ( + 'Illuminate\\Support\\' => 19, + 'Illuminate\\Contracts\\' => 21, ), 'H' => array ( 'Html2Text\\' => 10, ), + 'G' => + array ( + 'GuzzleHttp\\Psr7\\' => 16, + 'GuzzleHttp\\Promise\\' => 19, + 'GuzzleHttp\\' => 11, + ), + 'F' => + array ( + 'Firebase\\JWT\\' => 13, + ), + 'D' => + array ( + 'Ddeboer\\Imap\\' => 13, + 'DASPRiD\\Enum\\' => 13, + ), + 'C' => + array ( + 'Carbon\\Doctrine\\' => 16, + 'Carbon\\' => 7, + ), + 'B' => + array ( + 'BaconQrCode\\' => 12, + ), ); public static $prefixDirsPsr4 = array ( + 'Twig\\' => + array ( + 0 => __DIR__ . '/..' . '/twig/twig/src', + ), + 'Symfony\\Polyfill\\Php81\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php81', + ), + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Contracts\\Translation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/translation-contracts', + ), + 'Symfony\\Component\\Translation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/translation', + ), + 'Stevenmaguire\\OAuth2\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/stevenmaguire/oauth2-keycloak/src', + ), 'RobThree\\Auth\\' => array ( 0 => __DIR__ . '/..' . '/robthree/twofactorauth/lib', ), + 'Psr\\SimpleCache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/simple-cache/src', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/src', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-factory/src', + 1 => __DIR__ . '/..' . '/psr/http-message/src', + ), + 'Psr\\Http\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-client/src', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'Psr\\Clock\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/clock/src', + ), 'PhpMimeMailParser\\' => array ( 0 => __DIR__ . '/..' . '/php-mime-mail-parser/php-mime-mail-parser/src', ), + 'PHPMailer\\PHPMailer\\' => + array ( + 0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src', + ), + 'MatthiasMullie\\PathConverter\\' => + array ( + 0 => __DIR__ . '/..' . '/matthiasmullie/path-converter/src', + ), + 'MatthiasMullie\\Minify\\' => + array ( + 0 => __DIR__ . '/..' . '/matthiasmullie/minify/src', + ), + 'League\\OAuth2\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/league/oauth2-client/src', + ), + 'LdapRecord\\' => + array ( + 0 => __DIR__ . '/..' . '/directorytree/ldaprecord/src', + ), + 'Illuminate\\Support\\' => + array ( + 0 => __DIR__ . '/..' . '/illuminate/collections', + 1 => __DIR__ . '/..' . '/illuminate/macroable', + 2 => __DIR__ . '/..' . '/illuminate/conditionable', + ), + 'Illuminate\\Contracts\\' => + array ( + 0 => __DIR__ . '/..' . '/illuminate/contracts', + ), 'Html2Text\\' => array ( 0 => __DIR__ . '/..' . '/soundasleep/html2text/src', ), + 'GuzzleHttp\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', + ), + 'GuzzleHttp\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', + ), + 'GuzzleHttp\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', + ), + 'Firebase\\JWT\\' => + array ( + 0 => __DIR__ . '/..' . '/firebase/php-jwt/src', + ), + 'Ddeboer\\Imap\\' => + array ( + 0 => __DIR__ . '/..' . '/ddeboer/imap/src', + ), + 'DASPRiD\\Enum\\' => + array ( + 0 => __DIR__ . '/..' . '/dasprid/enum/src', + ), + 'Carbon\\Doctrine\\' => + array ( + 0 => __DIR__ . '/..' . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine', + ), + 'Carbon\\' => + array ( + 0 => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon', + ), + 'BaconQrCode\\' => + array ( + 0 => __DIR__ . '/..' . '/bacon/bacon-qr-code/src', + ), + ); + + public static $prefixesPsr0 = array ( + 'O' => + array ( + 'OAuth2' => + array ( + 0 => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src', + ), + ), ); public static $classMap = array ( - 'EasyPeasyICS' => __DIR__ . '/..' . '/phpmailer/phpmailer/extras/EasyPeasyICS.php', - 'PHPMailer' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.phpmailer.php', - 'PHPMailerOAuth' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.phpmaileroauth.php', - 'PHPMailerOAuthGoogle' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.phpmaileroauthgoogle.php', - 'POP3' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.pop3.php', - 'SMTP' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.smtp.php', - 'ntlm_sasl_client_class' => __DIR__ . '/..' . '/phpmailer/phpmailer/extras/ntlm_sasl_client.php', - 'phpmailerException' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.phpmailer.php', - 'u2flib_server\\Error' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php', - 'u2flib_server\\RegisterRequest' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php', - 'u2flib_server\\Registration' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php', - 'u2flib_server\\SignRequest' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php', - 'u2flib_server\\U2F' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php', + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CURLStringFile' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', ); public static function getInitializer(ClassLoader $loader) @@ -57,6 +263,7 @@ public static function getInitializer(ClassLoader $loader) return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInit873464e4bd965a3168f133248b1b218b::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit873464e4bd965a3168f133248b1b218b::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit873464e4bd965a3168f133248b1b218b::$prefixesPsr0; $loader->classMap = ComposerStaticInit873464e4bd965a3168f133248b1b218b::$classMap; }, null, ClassLoader::class); diff --git a/data/web/inc/lib/vendor/composer/installed.json b/data/web/inc/lib/vendor/composer/installed.json index bd1deb8582..e2d7113bc2 100644 --- a/data/web/inc/lib/vendor/composer/installed.json +++ b/data/web/inc/lib/vendor/composer/installed.json @@ -1,305 +1,2894 @@ -[ - { - "name": "php-mime-mail-parser/php-mime-mail-parser", - "version": "2.11.1", - "version_normalized": "2.11.1.0", - "source": { - "type": "git", - "url": "https://github.com/php-mime-mail-parser/php-mime-mail-parser.git", - "reference": "4769e942ed0dbbdd7882fc390b119d625463c8af" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/4769e942ed0dbbdd7882fc390b119d625463c8af", - "reference": "4769e942ed0dbbdd7882fc390b119d625463c8af", - "shasum": "" - }, - "require": { - "ext-mailparse": "*", - "php": "^5.4.0 || ^7.0" - }, - "replace": { - "exorus/php-mime-mail-parser": "*", - "messaged/php-mime-mail-parser": "*" - }, - "require-dev": { - "phpunit/php-token-stream": "^1.3.0", - "phpunit/phpunit": "^4.0 || ^5.0", - "satooshi/php-coveralls": "0.*", - "squizlabs/php_codesniffer": "2.*" - }, - "time": "2018-04-30T05:55:59+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "PhpMimeMailParser\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "bucabay", - "email": "gabe@fijiwebdesign.com", - "homepage": "http://www.fijiwebdesign.com", - "role": "Developer" - }, - { - "name": "eXorus", - "email": "exorus.spam@gmail.com", - "homepage": "https://github.com/eXorus/", - "role": "Developer" - }, - { - "name": "M.Valinskis", - "email": "M.Valins@gmail.com", - "homepage": "https://code.google.com/p/php-mime-mail-parser", - "role": "Developer" - }, - { - "name": "eugene.emmett.wood", - "email": "gene_w@cementhorizon.com", - "homepage": "https://code.google.com/p/php-mime-mail-parser", - "role": "Developer" - }, - { - "name": "alknetso", - "email": "alkne@gmail.com", - "homepage": "https://code.google.com/p/php-mime-mail-parser", - "role": "Developer" - } - ], - "description": "Fully Tested Mailparse Extension Wrapper for PHP 5.4+", - "homepage": "https://github.com/php-mime-mail-parser/php-mime-mail-parser", - "keywords": [ - "MimeMailParser", - "mail", - "mailparse", - "mime" - ] - }, - { - "name": "phpmailer/phpmailer", - "version": "v5.2.26", - "version_normalized": "5.2.26.0", - "source": { - "type": "git", - "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "70362997bda4376378be7d92d81e2200550923f7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/70362997bda4376378be7d92d81e2200550923f7", - "reference": "70362997bda4376378be7d92d81e2200550923f7", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "php": ">=5.0.0" - }, - "require-dev": { - "doctrine/annotations": "1.2.*", - "jms/serializer": "0.16.*", - "phpdocumentor/phpdocumentor": "2.*", - "phpunit/phpunit": "4.8.*", - "symfony/debug": "2.8.*", - "symfony/filesystem": "2.8.*", - "symfony/translation": "2.8.*", - "symfony/yaml": "2.8.*", - "zendframework/zend-cache": "2.5.1", - "zendframework/zend-config": "2.5.1", - "zendframework/zend-eventmanager": "2.5.1", - "zendframework/zend-filter": "2.5.1", - "zendframework/zend-i18n": "2.5.1", - "zendframework/zend-json": "2.5.1", - "zendframework/zend-math": "2.5.1", - "zendframework/zend-serializer": "2.5.*", - "zendframework/zend-servicemanager": "2.5.*", - "zendframework/zend-stdlib": "2.5.1" - }, - "suggest": { - "league/oauth2-google": "Needed for Google XOAUTH2 authentication" - }, - "time": "2017-11-04T09:26:05+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "classmap": [ - "class.phpmailer.php", - "class.phpmaileroauth.php", - "class.phpmaileroauthgoogle.php", - "class.smtp.php", - "class.pop3.php", - "extras/EasyPeasyICS.php", - "extras/ntlm_sasl_client.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1" - ], - "authors": [ - { - "name": "Jim Jagielski", - "email": "jimjag@gmail.com" - }, - { - "name": "Marcus Bointon", - "email": "phpmailer@synchromedia.co.uk" - }, - { - "name": "Andy Prevost", - "email": "codeworxtech@users.sourceforge.net" - }, - { - "name": "Brent R. Matzelle" - } - ], - "description": "PHPMailer is a full-featured email creation and transfer class for PHP" - }, - { - "name": "robthree/twofactorauth", - "version": "1.6.5", - "version_normalized": "1.6.5.0", - "source": { - "type": "git", - "url": "https://github.com/RobThree/TwoFactorAuth.git", - "reference": "f5f58a4c62d0336a0e6175856894a51f3565dad2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/f5f58a4c62d0336a0e6175856894a51f3565dad2", - "reference": "f5f58a4c62d0336a0e6175856894a51f3565dad2", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "@stable" - }, - "time": "2018-06-09T10:09:59+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "RobThree\\Auth\\": "lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Rob Janssen", - "homepage": "http://robiii.me", - "role": "Developer" - } - ], - "description": "Two Factor Authentication", - "homepage": "https://github.com/RobThree/TwoFactorAuth", - "keywords": [ - "Authentication", - "MFA", - "Multi Factor Authentication", - "Two Factor Authentication", - "authenticator", - "authy", - "php", - "tfa" - ] - }, - { - "name": "soundasleep/html2text", - "version": "0.5.0", - "version_normalized": "0.5.0.0", - "source": { - "type": "git", - "url": "https://github.com/soundasleep/html2text.git", - "reference": "cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/soundasleep/html2text/zipball/cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad", - "reference": "cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": ">=4.0", - "soundasleep/component-tests": "dev-master" - }, - "time": "2017-04-19T22:01:50+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "Html2Text\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "EPL-1.0" - ], - "authors": [ - { - "name": "Jevon Wright", - "homepage": "https://jevon.org", - "role": "Developer" - } - ], - "description": "A PHP script to convert HTML into a plain text format", - "homepage": "https://github.com/soundasleep/html2text", - "keywords": [ - "email", - "html", - "php", - "text" - ] - }, - { - "name": "yubico/u2flib-server", - "version": "1.0.1", - "version_normalized": "1.0.1.0", - "source": { - "type": "git", - "url": "https://github.com/Yubico/php-u2flib-server.git", - "reference": "dc318c80b59e62921c210f31b014def26ceebbab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Yubico/php-u2flib-server/zipball/dc318c80b59e62921c210f31b014def26ceebbab", - "reference": "dc318c80b59e62921c210f31b014def26ceebbab", - "shasum": "" - }, - "require": { - "ext-openssl": "*", - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "~5.7" - }, - "time": "2017-05-09T07:33:58+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "description": "Library for U2F implementation", - "homepage": "https://developers.yubico.com/php-u2flib-server" - } -] +{ + "packages": [ + { + "name": "bacon/bacon-qr-code", + "version": "2.0.8", + "version_normalized": "2.0.8.0", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22", + "shasum": "" + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phly/keep-a-changelog": "^2.1", + "phpunit/phpunit": "^7 | ^8 | ^9", + "spatie/phpunit-snapshot-assertions": "^4.2.9", + "squizlabs/php_codesniffer": "^3.4" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "time": "2022-12-07T17:46:57+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8" + }, + "install-path": "../bacon/bacon-qr-code" + }, + { + "name": "bshaffer/oauth2-server-php", + "version": "v1.11.1", + "version_normalized": "1.11.1.0", + "source": { + "type": "git", + "url": "https://github.com/bshaffer/oauth2-server-php.git", + "reference": "5a0c8000d4763b276919e2106f54eddda6bc50fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bshaffer/oauth2-server-php/zipball/5a0c8000d4763b276919e2106f54eddda6bc50fa", + "reference": "5a0c8000d4763b276919e2106f54eddda6bc50fa", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "aws/aws-sdk-php": "~2.8", + "firebase/php-jwt": "~2.2", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^4.0", + "predis/predis": "dev-master", + "thobbs/phpcassa": "dev-master" + }, + "suggest": { + "aws/aws-sdk-php": "~2.8 is required to use DynamoDB storage", + "firebase/php-jwt": "~2.2 is required to use JWT features", + "mongodb/mongodb": "^1.1 is required to use MongoDB storage", + "predis/predis": "Required to use Redis storage", + "thobbs/phpcassa": "Required to use Cassandra storage" + }, + "time": "2018-12-04T00:29:32+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "OAuth2": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Shaffer", + "email": "bshafs@gmail.com", + "homepage": "http://brentertainment.com" + } + ], + "description": "OAuth2 Server for PHP", + "homepage": "http://github.com/bshaffer/oauth2-server-php", + "keywords": [ + "auth", + "oauth", + "oauth2" + ], + "install-path": "../bshaffer/oauth2-server-php" + }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "3.2.0", + "version_normalized": "3.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "time": "2024-02-09T16:56:22+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "install-path": "../carbonphp/carbon-doctrine-types" + }, + { + "name": "dasprid/enum", + "version": "1.0.7", + "version_normalized": "1.0.7.0", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "shasum": "" + }, + "require": { + "php": ">=7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "*" + }, + "time": "2025-09-16T12:23:56+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.7" + }, + "install-path": "../dasprid/enum" + }, + { + "name": "ddeboer/imap", + "version": "1.13.1", + "version_normalized": "1.13.1.0", + "source": { + "type": "git", + "url": "https://github.com/ddeboer/imap.git", + "reference": "8b772d04b1deadb5df13782fb78c4b648f77496e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ddeboer/imap/zipball/8b772d04b1deadb5df13782fb78c4b648f77496e", + "reference": "8b772d04b1deadb5df13782fb78c4b648f77496e", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-imap": "*", + "ext-mbstring": "*", + "php": "^8.0.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v3.4.0", + "laminas/laminas-mail": "^2.15.1", + "malukenho/mcbumpface": "^1.1.5", + "phpstan/phpstan": "^1.3.3", + "phpstan/phpstan-phpunit": "^1.0.0", + "phpstan/phpstan-strict-rules": "^1.1.0", + "phpunit/phpunit": "^9.5.11" + }, + "time": "2022-01-10T10:53:05+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Ddeboer\\Imap\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com" + }, + { + "name": "Community contributors", + "homepage": "https://github.com/ddeboer/imap/graphs/contributors" + } + ], + "description": "Object-oriented IMAP for PHP", + "keywords": [ + "email", + "imap", + "mail" + ], + "support": { + "issues": "https://github.com/ddeboer/imap/issues", + "source": "https://github.com/ddeboer/imap/tree/1.13.1" + }, + "funding": [ + { + "url": "https://github.com/Slamdunk", + "type": "github" + }, + { + "url": "https://github.com/ddeboer", + "type": "github" + } + ], + "install-path": "../ddeboer/imap" + }, + { + "name": "directorytree/ldaprecord", + "version": "v3.8.5", + "version_normalized": "3.8.5.0", + "source": { + "type": "git", + "url": "https://github.com/DirectoryTree/LdapRecord.git", + "reference": "00e5f088f8c4028d5f398783cccc2e8119a27a65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DirectoryTree/LdapRecord/zipball/00e5f088f8c4028d5f398783cccc2e8119a27a65", + "reference": "00e5f088f8c4028d5f398783cccc2e8119a27a65", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-json": "*", + "ext-ldap": "*", + "illuminate/collections": "^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0", + "nesbot/carbon": "*", + "php": ">=8.1", + "psr/log": "*", + "psr/simple-cache": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "fakerphp/faker": "^1.21", + "laravel/pint": "^1.6", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^9.0", + "spatie/ray": "^1.24" + }, + "time": "2025-10-06T02:22:34+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "LdapRecord\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Steve Bauman", + "email": "steven_bauman@outlook.com", + "role": "Developer" + } + ], + "description": "A fully-featured LDAP ORM.", + "homepage": "https://www.ldaprecord.com", + "keywords": [ + "active directory", + "ad", + "adLDAP", + "adldap2", + "directory", + "ldap", + "ldaprecord", + "orm", + "windows" + ], + "support": { + "docs": "https://ldaprecord.com", + "email": "steven_bauman@outlook.com", + "issues": "https://github.com/DirectoryTree/LdapRecord/issues", + "source": "https://github.com/DirectoryTree/LdapRecord" + }, + "funding": [ + { + "url": "https://github.com/stevebauman", + "type": "github" + } + ], + "install-path": "../directorytree/ldaprecord" + }, + { + "name": "firebase/php-jwt", + "version": "v6.5.0", + "version_normalized": "6.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/e94e7353302b0c11ec3cfff7180cd0b1743975d2", + "reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2", + "shasum": "" + }, + "require": { + "php": "^7.4||^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.5||^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^1.0||^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "time": "2023-05-12T15:47:07+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.5.0" + }, + "install-path": "../firebase/php-jwt" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.5.0", + "version_normalized": "7.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.9 || ^2.4", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "time": "2022-08-28T15:39:27+00:00", + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "7.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/guzzle" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.2", + "version_normalized": "1.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "b94b2807d85443f9719887892882d0329d1e2598" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", + "reference": "b94b2807d85443f9719887892882d0329d1e2598", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "time": "2022-08-28T14:55:35+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/promises" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.4.5", + "version_normalized": "2.4.5.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "0454e12ef0cd597ccd2adb036f7bda4e7fface66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/0454e12ef0cd597ccd2adb036f7bda4e7fface66", + "reference": "0454e12ef0cd597ccd2adb036f7bda4e7fface66", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "time": "2023-04-17T16:00:45+00:00", + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.4.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/psr7" + }, + { + "name": "illuminate/collections", + "version": "v10.49.0", + "version_normalized": "10.49.0.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "6ae9c74fa92d4e1824d1b346cd435e8eacdc3232" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/6ae9c74fa92d4e1824d1b346cd435e8eacdc3232", + "reference": "6ae9c74fa92d4e1824d1b346cd435e8eacdc3232", + "shasum": "" + }, + "require": { + "illuminate/conditionable": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/macroable": "^10.0", + "php": "^8.1" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^6.2)." + }, + "time": "2025-09-08T19:05:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/collections" + }, + { + "name": "illuminate/conditionable", + "version": "v10.49.0", + "version_normalized": "10.49.0.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/conditionable.git", + "reference": "47c700320b7a419f0d188d111f3bbed978fcbd3f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/conditionable/zipball/47c700320b7a419f0d188d111f3bbed978fcbd3f", + "reference": "47c700320b7a419f0d188d111f3bbed978fcbd3f", + "shasum": "" + }, + "require": { + "php": "^8.0.2" + }, + "time": "2025-03-24T11:47:24+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Conditionable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/conditionable" + }, + { + "name": "illuminate/contracts", + "version": "v10.44.0", + "version_normalized": "10.44.0.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac", + "reference": "8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac", + "shasum": "" + }, + "require": { + "php": "^8.1", + "psr/container": "^1.1.1|^2.0.1", + "psr/simple-cache": "^1.0|^2.0|^3.0" + }, + "time": "2024-01-15T18:52:32+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/contracts" + }, + { + "name": "illuminate/macroable", + "version": "v10.49.0", + "version_normalized": "10.49.0.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/dff667a46ac37b634dcf68909d9d41e94dc97c27", + "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "time": "2023-06-05T12:46:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "install-path": "../illuminate/macroable" + }, + { + "name": "league/oauth2-client", + "version": "2.7.0", + "version_normalized": "2.7.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "160d6274b03562ebeb55ed18399281d8118b76c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/160d6274b03562ebeb55ed18399281d8118b76c8", + "reference": "160d6274b03562ebeb55ed18399281d8118b76c8", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "paragonie/random_compat": "^1 || ^2 || ^9.99", + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.5", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", + "squizlabs/php_codesniffer": "^2.3 || ^3.0" + }, + "time": "2023-04-16T18:19:15+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-client/issues", + "source": "https://github.com/thephpleague/oauth2-client/tree/2.7.0" + }, + "install-path": "../league/oauth2-client" + }, + { + "name": "matthiasmullie/minify", + "version": "1.3.66", + "version_normalized": "1.3.66.0", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/minify.git", + "reference": "45fd3b0f1dfa2c965857c6d4a470bea52adc31a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/45fd3b0f1dfa2c965857c6d4a470bea52adc31a6", + "reference": "45fd3b0f1dfa2c965857c6d4a470bea52adc31a6", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "matthiasmullie/path-converter": "~1.1", + "php": ">=5.3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.0", + "matthiasmullie/scrapbook": "dev-master", + "phpunit/phpunit": ">=4.8" + }, + "suggest": { + "psr/cache-implementation": "Cache implementation to use with Minify::cache" + }, + "time": "2021-01-06T15:18:10+00:00", + "bin": [ + "bin/minifycss", + "bin/minifyjs" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "MatthiasMullie\\Minify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "minify@mullie.eu", + "homepage": "http://www.mullie.eu", + "role": "Developer" + } + ], + "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", + "homepage": "http://www.minifier.org", + "keywords": [ + "JS", + "css", + "javascript", + "minifier", + "minify" + ], + "support": { + "issues": "https://github.com/matthiasmullie/minify/issues", + "source": "https://github.com/matthiasmullie/minify/tree/1.3.66" + }, + "funding": [ + { + "url": "https://github.com/[user1", + "type": "github" + }, + { + "url": "https://github.com/matthiasmullie] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g.", + "type": "github" + }, + { + "url": "https://github.com/user2", + "type": "github" + } + ], + "install-path": "../matthiasmullie/minify" + }, + { + "name": "matthiasmullie/path-converter", + "version": "1.1.3", + "version_normalized": "1.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/path-converter.git", + "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/e7d13b2c7e2f2268e1424aaed02085518afa02d9", + "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "time": "2019-02-05T23:41:09+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "MatthiasMullie\\PathConverter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "pathconverter@mullie.eu", + "homepage": "http://www.mullie.eu", + "role": "Developer" + } + ], + "description": "Relative path converter", + "homepage": "http://github.com/matthiasmullie/path-converter", + "keywords": [ + "converter", + "path", + "paths", + "relative" + ], + "install-path": "../matthiasmullie/path-converter" + }, + { + "name": "mustangostang/spyc", + "version": "0.6.3", + "version_normalized": "0.6.3.0", + "source": { + "type": "git", + "url": "git@github.com:mustangostang/spyc.git", + "reference": "4627c838b16550b666d15aeae1e5289dd5b77da0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mustangostang/spyc/zipball/4627c838b16550b666d15aeae1e5289dd5b77da0", + "reference": "4627c838b16550b666d15aeae1e5289dd5b77da0", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "4.3.*@dev" + }, + "time": "2019-09-10T13:16:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "Spyc.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + } + ], + "description": "A simple YAML loader/dumper class for PHP", + "homepage": "https://github.com/mustangostang/spyc/", + "keywords": [ + "spyc", + "yaml", + "yml" + ], + "install-path": "../mustangostang/spyc" + }, + { + "name": "nesbot/carbon", + "version": "2.72.3", + "version_normalized": "2.72.3.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "0c6fd108360c562f6e4fd1dedb8233b423e91c83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/0c6fd108360c562f6e4fd1dedb8233b423e91c83", + "reference": "0c6fd108360c562f6e4fd1dedb8233b423e91c83", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "*", + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "psr/clock": "^1.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0", + "doctrine/orm": "^2.7 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "*", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", + "squizlabs/php_codesniffer": "^3.4" + }, + "time": "2024-01-25T10:35:09+00:00", + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.x-dev", + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "install-path": "../nesbot/carbon" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "version_normalized": "9.99.100.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "time": "2020-10-15T08:29:30+00:00", + "type": "library", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "install-path": "../paragonie/random_compat" + }, + { + "name": "php-mime-mail-parser/php-mime-mail-parser", + "version": "7.0.0", + "version_normalized": "7.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-mime-mail-parser/php-mime-mail-parser.git", + "reference": "9d09a017f3f103fec8456211a4a538b80e0eca0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/9d09a017f3f103fec8456211a4a538b80e0eca0d", + "reference": "9d09a017f3f103fec8456211a4a538b80e0eca0d", + "shasum": "" + }, + "require": { + "ext-mailparse": "*", + "php": "^7.2|^8.0" + }, + "replace": { + "exorus/php-mime-mail-parser": "*", + "messaged/php-mime-mail-parser": "*" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^8.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "time": "2021-02-25T17:21:57+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpMimeMailParser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "eXorus", + "email": "exorus.spam@gmail.com", + "homepage": "https://github.com/eXorus/", + "role": "Developer" + }, + { + "name": "M.Valinskis", + "email": "M.Valins@gmail.com", + "homepage": "https://code.google.com/p/php-mime-mail-parser", + "role": "Developer" + }, + { + "name": "eugene.emmett.wood", + "email": "gene_w@cementhorizon.com", + "homepage": "https://code.google.com/p/php-mime-mail-parser", + "role": "Developer" + }, + { + "name": "alknetso", + "email": "alkne@gmail.com", + "homepage": "https://code.google.com/p/php-mime-mail-parser", + "role": "Developer" + }, + { + "name": "bucabay", + "email": "gabe@fijiwebdesign.com", + "homepage": "http://www.fijiwebdesign.com", + "role": "Developer" + } + ], + "description": "A fully tested email parser for PHP 7.2+ (mailparse extension wrapper).", + "homepage": "https://github.com/php-mime-mail-parser/php-mime-mail-parser", + "keywords": [ + "MimeMailParser", + "mail", + "mailparse", + "mime", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/php-mime-mail-parser/php-mime-mail-parser/issues", + "source": "https://github.com/php-mime-mail-parser/php-mime-mail-parser/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/eXorus", + "type": "github" + } + ], + "install-path": "../php-mime-mail-parser/php-mime-mail-parser" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.6.0", + "version_normalized": "6.6.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "e43bac82edc26ca04b36143a48bde1c051cfd5b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/e43bac82edc26ca04b36143a48bde1c051cfd5b1", + "reference": "e43bac82edc26ca04b36143a48bde1c051cfd5b1", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.2", + "php-parallel-lint/php-console-highlighter": "^0.5.0", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.6.2", + "yoast/phpunit-polyfills": "^1.0.0" + }, + "suggest": { + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + }, + "time": "2022-02-28T15:31:21+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.6.0" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "install-path": "../phpmailer/phpmailer" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "time": "2022-11-25T14:36:26+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "install-path": "../psr/clock" + }, + { + "name": "psr/container", + "version": "2.0.2", + "version_normalized": "2.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "time": "2021-11-05T16:47:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "install-path": "../psr/container" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "time": "2020-06-29T06:28:15+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "install-path": "../psr/http-client" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "time": "2019-04-30T12:38:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "install-path": "../psr/http-factory" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T14:39:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "install-path": "../psr/http-message" + }, + { + "name": "psr/log", + "version": "3.0.0", + "version_normalized": "3.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2021-07-14T16:46:02+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "install-path": "../psr/log" + }, + { + "name": "psr/simple-cache", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/8707bf3cea6f710bf6ef05491234e3ab06f6432a", + "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2021-10-29T13:22:09+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/2.0.0" + }, + "install-path": "../psr/simple-cache" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "version_normalized": "3.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "time": "2019-03-08T08:55:37+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "install-path": "../ralouphie/getallheaders" + }, + { + "name": "robthree/twofactorauth", + "version": "1.8.1", + "version_normalized": "1.8.1.0", + "source": { + "type": "git", + "url": "https://github.com/RobThree/TwoFactorAuth.git", + "reference": "5afcb45282f1c75562a48d479ecd1732c9bdb11b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/5afcb45282f1c75562a48d479ecd1732c9bdb11b", + "reference": "5afcb45282f1c75562a48d479ecd1732c9bdb11b", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpunit/phpunit": "@stable" + }, + "suggest": { + "bacon/bacon-qr-code": "Needed for BaconQrCodeProvider provider", + "endroid/qr-code": "Needed for EndroidQrCodeProvider" + }, + "time": "2021-10-20T12:19:55+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "RobThree\\Auth\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Janssen", + "homepage": "http://robiii.me", + "role": "Developer" + } + ], + "description": "Two Factor Authentication", + "homepage": "https://github.com/RobThree/TwoFactorAuth", + "keywords": [ + "Authentication", + "MFA", + "Multi Factor Authentication", + "Two Factor Authentication", + "authenticator", + "authy", + "php", + "tfa" + ], + "support": { + "issues": "https://github.com/RobThree/TwoFactorAuth/issues", + "source": "https://github.com/RobThree/TwoFactorAuth" + }, + "funding": [ + { + "url": "https://paypal.me/robiii", + "type": "custom" + }, + { + "url": "https://github.com/RobThree", + "type": "github" + } + ], + "install-path": "../robthree/twofactorauth" + }, + { + "name": "soundasleep/html2text", + "version": "0.5.0", + "version_normalized": "0.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/soundasleep/html2text.git", + "reference": "cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/soundasleep/html2text/zipball/cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad", + "reference": "cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": ">=4.0", + "soundasleep/component-tests": "dev-master" + }, + "time": "2017-04-19T22:01:50+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Html2Text\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "EPL-1.0" + ], + "authors": [ + { + "name": "Jevon Wright", + "homepage": "https://jevon.org", + "role": "Developer" + } + ], + "description": "A PHP script to convert HTML into a plain text format", + "homepage": "https://github.com/soundasleep/html2text", + "keywords": [ + "email", + "html", + "php", + "text" + ], + "install-path": "../soundasleep/html2text" + }, + { + "name": "stevenmaguire/oauth2-keycloak", + "version": "4.0.0", + "version_normalized": "4.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/stevenmaguire/oauth2-keycloak.git", + "reference": "05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d", + "reference": "05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "^4.0 || ^5.0 || ^6.0", + "league/oauth2-client": "^2.0", + "php": "~7.2 || ~8.0" + }, + "require-dev": { + "mockery/mockery": "~1.5.0", + "phpunit/phpunit": "~9.6.4", + "squizlabs/php_codesniffer": "~3.7.0" + }, + "time": "2023-03-14T09:43:47+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Stevenmaguire\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Steven Maguire", + "email": "stevenmaguire@gmail.com", + "homepage": "https://github.com/stevenmaguire" + } + ], + "description": "Keycloak OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "authorisation", + "authorization", + "client", + "keycloak", + "oauth", + "oauth2" + ], + "support": { + "issues": "https://github.com/stevenmaguire/oauth2-keycloak/issues", + "source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/4.0.0" + }, + "install-path": "../stevenmaguire/oauth2-keycloak" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", + "version_normalized": "3.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2024-04-18T09:32:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.24.0", + "version_normalized": "1.24.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "30885182c981ab175d4d034db0f6f469898070ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2021-10-20T20:35:02+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-ctype" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2024-01-29T20:11:03+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-mbstring" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2024-01-29T20:11:03+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.31.0", + "version_normalized": "1.31.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php81" + }, + { + "name": "symfony/translation", + "version": "v6.4.3", + "version_normalized": "6.4.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "637c51191b6b184184bbf98937702bcf554f7d04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/637c51191b6b184184bbf98937702bcf554f7d04", + "reference": "637c51191b6b184184bbf98937702bcf554f7d04", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "conflict": { + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.18|^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "time": "2024-01-29T13:11:52+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v6.4.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/translation" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.4.1", + "version_normalized": "3.4.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "06450585bf65e978026bda220cdebca3f867fde7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/06450585bf65e978026bda220cdebca3f867fde7", + "reference": "06450585bf65e978026bda220cdebca3f867fde7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2023-12-26T14:02:43+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.4.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/translation-contracts" + }, + { + "name": "twig/twig", + "version": "v3.14.0", + "version_normalized": "3.14.0.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72", + "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php81": "^1.29" + }, + "require-dev": { + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "time": "2024-09-09T17:55:12+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.14.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "install-path": "../twig/twig" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/data/web/inc/lib/vendor/composer/installed.php b/data/web/inc/lib/vendor/composer/installed.php new file mode 100644 index 0000000000..15a1aa89f3 --- /dev/null +++ b/data/web/inc/lib/vendor/composer/installed.php @@ -0,0 +1,434 @@ + array( + 'name' => '__root__', + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => null, + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => null, + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'bacon/bacon-qr-code' => array( + 'pretty_version' => '2.0.8', + 'version' => '2.0.8.0', + 'reference' => '8674e51bb65af933a5ffaf1c308a660387c35c22', + 'type' => 'library', + 'install_path' => __DIR__ . '/../bacon/bacon-qr-code', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'bshaffer/oauth2-server-php' => array( + 'pretty_version' => 'v1.11.1', + 'version' => '1.11.1.0', + 'reference' => '5a0c8000d4763b276919e2106f54eddda6bc50fa', + 'type' => 'library', + 'install_path' => __DIR__ . '/../bshaffer/oauth2-server-php', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'carbonphp/carbon-doctrine-types' => array( + 'pretty_version' => '3.2.0', + 'version' => '3.2.0.0', + 'reference' => '18ba5ddfec8976260ead6e866180bd5d2f71aa1d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../carbonphp/carbon-doctrine-types', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'dasprid/enum' => array( + 'pretty_version' => '1.0.7', + 'version' => '1.0.7.0', + 'reference' => 'b5874fa9ed0043116c72162ec7f4fb50e02e7cce', + 'type' => 'library', + 'install_path' => __DIR__ . '/../dasprid/enum', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'ddeboer/imap' => array( + 'pretty_version' => '1.13.1', + 'version' => '1.13.1.0', + 'reference' => '8b772d04b1deadb5df13782fb78c4b648f77496e', + 'type' => 'library', + 'install_path' => __DIR__ . '/../ddeboer/imap', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'directorytree/ldaprecord' => array( + 'pretty_version' => 'v3.8.5', + 'version' => '3.8.5.0', + 'reference' => '00e5f088f8c4028d5f398783cccc2e8119a27a65', + 'type' => 'library', + 'install_path' => __DIR__ . '/../directorytree/ldaprecord', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'exorus/php-mime-mail-parser' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '*', + ), + ), + 'firebase/php-jwt' => array( + 'pretty_version' => 'v6.5.0', + 'version' => '6.5.0.0', + 'reference' => 'e94e7353302b0c11ec3cfff7180cd0b1743975d2', + 'type' => 'library', + 'install_path' => __DIR__ . '/../firebase/php-jwt', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzlehttp/guzzle' => array( + 'pretty_version' => '7.5.0', + 'version' => '7.5.0.0', + 'reference' => 'b50a2a1251152e43f6a37f0fa053e730a67d25ba', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/guzzle', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzlehttp/promises' => array( + 'pretty_version' => '1.5.2', + 'version' => '1.5.2.0', + 'reference' => 'b94b2807d85443f9719887892882d0329d1e2598', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/promises', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzlehttp/psr7' => array( + 'pretty_version' => '2.4.5', + 'version' => '2.4.5.0', + 'reference' => '0454e12ef0cd597ccd2adb036f7bda4e7fface66', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/psr7', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/collections' => array( + 'pretty_version' => 'v10.49.0', + 'version' => '10.49.0.0', + 'reference' => '6ae9c74fa92d4e1824d1b346cd435e8eacdc3232', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/collections', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/conditionable' => array( + 'pretty_version' => 'v10.49.0', + 'version' => '10.49.0.0', + 'reference' => '47c700320b7a419f0d188d111f3bbed978fcbd3f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/conditionable', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/contracts' => array( + 'pretty_version' => 'v10.44.0', + 'version' => '10.44.0.0', + 'reference' => '8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'illuminate/macroable' => array( + 'pretty_version' => 'v10.49.0', + 'version' => '10.49.0.0', + 'reference' => 'dff667a46ac37b634dcf68909d9d41e94dc97c27', + 'type' => 'library', + 'install_path' => __DIR__ . '/../illuminate/macroable', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'league/oauth2-client' => array( + 'pretty_version' => '2.7.0', + 'version' => '2.7.0.0', + 'reference' => '160d6274b03562ebeb55ed18399281d8118b76c8', + 'type' => 'library', + 'install_path' => __DIR__ . '/../league/oauth2-client', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'matthiasmullie/minify' => array( + 'pretty_version' => '1.3.66', + 'version' => '1.3.66.0', + 'reference' => '45fd3b0f1dfa2c965857c6d4a470bea52adc31a6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../matthiasmullie/minify', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'matthiasmullie/path-converter' => array( + 'pretty_version' => '1.1.3', + 'version' => '1.1.3.0', + 'reference' => 'e7d13b2c7e2f2268e1424aaed02085518afa02d9', + 'type' => 'library', + 'install_path' => __DIR__ . '/../matthiasmullie/path-converter', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'messaged/php-mime-mail-parser' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '*', + ), + ), + 'mustangostang/spyc' => array( + 'pretty_version' => '0.6.3', + 'version' => '0.6.3.0', + 'reference' => '4627c838b16550b666d15aeae1e5289dd5b77da0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../mustangostang/spyc', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'nesbot/carbon' => array( + 'pretty_version' => '2.72.3', + 'version' => '2.72.3.0', + 'reference' => '0c6fd108360c562f6e4fd1dedb8233b423e91c83', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nesbot/carbon', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'paragonie/random_compat' => array( + 'pretty_version' => 'v9.99.100', + 'version' => '9.99.100.0', + 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../paragonie/random_compat', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'php-mime-mail-parser/php-mime-mail-parser' => array( + 'pretty_version' => '7.0.0', + 'version' => '7.0.0.0', + 'reference' => '9d09a017f3f103fec8456211a4a538b80e0eca0d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../php-mime-mail-parser/php-mime-mail-parser', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'phpmailer/phpmailer' => array( + 'pretty_version' => 'v6.6.0', + 'version' => '6.6.0.0', + 'reference' => 'e43bac82edc26ca04b36143a48bde1c051cfd5b1', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpmailer/phpmailer', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/clock' => array( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'reference' => 'e41a24703d4560fd0acb709162f73b8adfc3aa0d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/clock', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/clock-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/container' => array( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/container', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-client' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-client', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-factory' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-factory', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-factory-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-message' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-message', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-message-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/log' => array( + 'pretty_version' => '3.0.0', + 'version' => '3.0.0.0', + 'reference' => 'fe5ea303b0887d5caefd3d431c3e61ad47037001', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/log', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/simple-cache' => array( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'reference' => '8707bf3cea6f710bf6ef05491234e3ab06f6432a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/simple-cache', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'ralouphie/getallheaders' => array( + 'pretty_version' => '3.0.3', + 'version' => '3.0.3.0', + 'reference' => '120b605dfeb996808c31b6477290a714d356e822', + 'type' => 'library', + 'install_path' => __DIR__ . '/../ralouphie/getallheaders', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'robthree/twofactorauth' => array( + 'pretty_version' => '1.8.1', + 'version' => '1.8.1.0', + 'reference' => '5afcb45282f1c75562a48d479ecd1732c9bdb11b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../robthree/twofactorauth', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'soundasleep/html2text' => array( + 'pretty_version' => '0.5.0', + 'version' => '0.5.0.0', + 'reference' => 'cdb89f6ffa2c4cc78f8ed9ea6ee0594a9133ccad', + 'type' => 'library', + 'install_path' => __DIR__ . '/../soundasleep/html2text', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'stevenmaguire/oauth2-keycloak' => array( + 'pretty_version' => '4.0.0', + 'version' => '4.0.0.0', + 'reference' => '05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../stevenmaguire/oauth2-keycloak', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v3.5.0', + 'version' => '3.5.0.0', + 'reference' => '0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-ctype' => array( + 'pretty_version' => 'v1.24.0', + 'version' => '1.24.0.0', + 'reference' => '30885182c981ab175d4d034db0f6f469898070ab', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-mbstring' => array( + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => '9773676c8a1bb1f8d4340a62efe641cf76eda7ec', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => '87b68208d5c1188808dd7839ee1e6c8ec3b02f1b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php81' => array( + 'pretty_version' => 'v1.31.0', + 'version' => '1.31.0.0', + 'reference' => '4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php81', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/translation' => array( + 'pretty_version' => 'v6.4.3', + 'version' => '6.4.3.0', + 'reference' => '637c51191b6b184184bbf98937702bcf554f7d04', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/translation', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/translation-contracts' => array( + 'pretty_version' => 'v3.4.1', + 'version' => '3.4.1.0', + 'reference' => '06450585bf65e978026bda220cdebca3f867fde7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/translation-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/translation-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '2.3|3.0', + ), + ), + 'twig/twig' => array( + 'pretty_version' => 'v3.14.0', + 'version' => '3.14.0.0', + 'reference' => '126b2c97818dbff0cdf3fbfc881aedb3d40aae72', + 'type' => 'library', + 'install_path' => __DIR__ . '/../twig/twig', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/data/web/inc/lib/vendor/composer/platform_check.php b/data/web/inc/lib/vendor/composer/platform_check.php new file mode 100644 index 0000000000..4c3a5d68f1 --- /dev/null +++ b/data/web/inc/lib/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 80100)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/data/web/inc/lib/vendor/dasprid/enum/LICENSE b/data/web/inc/lib/vendor/dasprid/enum/LICENSE new file mode 100644 index 0000000000..d45a35647a --- /dev/null +++ b/data/web/inc/lib/vendor/dasprid/enum/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2017, Ben Scholzen 'DASPRiD' +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/data/web/inc/lib/vendor/dasprid/enum/README.md b/data/web/inc/lib/vendor/dasprid/enum/README.md new file mode 100644 index 0000000000..da37045e39 --- /dev/null +++ b/data/web/inc/lib/vendor/dasprid/enum/README.md @@ -0,0 +1,164 @@ +# PHP 7.1 enums + +[![Build Status](https://github.com/DASPRiD/Enum/actions/workflows/tests.yml/badge.svg)](https://github.com/DASPRiD/Enum/actions?query=workflow%3Atests) +[![Coverage Status](https://coveralls.io/repos/github/DASPRiD/Enum/badge.svg?branch=master)](https://coveralls.io/github/DASPRiD/Enum?branch=master) +[![Latest Stable Version](https://poser.pugx.org/dasprid/enum/v/stable)](https://packagist.org/packages/dasprid/enum) +[![Total Downloads](https://poser.pugx.org/dasprid/enum/downloads)](https://packagist.org/packages/dasprid/enum) +[![License](https://poser.pugx.org/dasprid/enum/license)](https://packagist.org/packages/dasprid/enum) + +It is a well known fact that PHP is missing a basic enum type, ignoring the rather incomplete `SplEnum` implementation +which is only available as a PECL extension. There are also quite a few other userland enum implementations around, +but all of them have one or another compromise. This library tries to close that gap as far as PHP allows it to. + +## Usage + +### Basics + +At its core, there is the `DASPRiD\Enum\AbstractEnum` class, which by default will work with constants like any other +enum implementation you might know. The first clear difference is that you should define all the constants as protected +(so nobody outside your class can read them but the `AbstractEnum` can still do so). The other even mightier difference +is that, for simple enums, the value of the constant doesn't matter at all. Let's have a look at a simple example: + +```php +use DASPRiD\Enum\AbstractEnum; + +/** + * @method static self MONDAY() + * @method static self TUESDAY() + * @method static self WEDNESDAY() + * @method static self THURSDAY() + * @method static self FRIDAY() + * @method static self SATURDAY() + * @method static self SUNDAY() + */ +final class WeekDay extends AbstractEnum +{ + protected const MONDAY = null; + protected const TUESDAY = null; + protected const WEDNESDAY = null; + protected const THURSDAY = null; + protected const FRIDAY = null; + protected const SATURDAY = null; + protected const SUNDAY = null; +} +``` + +If you need to provide constants for either internal use or public use, you can mark them as either private or public, +in which case they will be ignored by the enum, which only considers protected constants as valid values. As you can +see, we specifically defined the generated magic methods in a class level doc block, so anyone using this class will +automatically have proper auto-completion in their IDE. Now since you have defined the enum, you can simply use it like +that: + +```php +function tellItLikeItIs(WeekDay $weekDay) +{ + switch ($weekDay) { + case WeekDay::MONDAY(): + echo 'Mondays are bad.'; + break; + + case WeekDay::FRIDAY(): + echo 'Fridays are better.'; + break; + + case WeekDay::SATURDAY(): + case WeekDay::SUNDAY(): + echo 'Weekends are best.'; + break; + + default: + echo 'Midweek days are so-so.'; + } +} + +tellItLikeItIs(WeekDay::MONDAY()); +tellItLikeItIs(WeekDay::WEDNESDAY()); +tellItLikeItIs(WeekDay::FRIDAY()); +tellItLikeItIs(WeekDay::SATURDAY()); +tellItLikeItIs(WeekDay::SUNDAY()); +``` + +### More complex example + +Of course, all enums are singletons, which are not cloneable or serializable. Thus you can be sure that there is always +just one instance of the same type. Of course, the values of constants are not completely useless, let's have a look at +a more complex example: + +```php +use DASPRiD\Enum\AbstractEnum; + +/** + * @method static self MERCURY() + * @method static self VENUS() + * @method static self EARTH() + * @method static self MARS() + * @method static self JUPITER() + * @method static self SATURN() + * @method static self URANUS() + * @method static self NEPTUNE() + */ +final class Planet extends AbstractEnum +{ + protected const MERCURY = [3.303e+23, 2.4397e6]; + protected const VENUS = [4.869e+24, 6.0518e6]; + protected const EARTH = [5.976e+24, 6.37814e6]; + protected const MARS = [6.421e+23, 3.3972e6]; + protected const JUPITER = [1.9e+27, 7.1492e7]; + protected const SATURN = [5.688e+26, 6.0268e7]; + protected const URANUS = [8.686e+25, 2.5559e7]; + protected const NEPTUNE = [1.024e+26, 2.4746e7]; + + /** + * Universal gravitational constant. + * + * @var float + */ + private const G = 6.67300E-11; + + /** + * Mass in kilograms. + * + * @var float + */ + private $mass; + + /** + * Radius in meters. + * + * @var float + */ + private $radius; + + protected function __construct(float $mass, float $radius) + { + $this->mass = $mass; + $this->radius = $radius; + } + + public function mass() : float + { + return $this->mass; + } + + public function radius() : float + { + return $this->radius; + } + + public function surfaceGravity() : float + { + return self::G * $this->mass / ($this->radius * $this->radius); + } + + public function surfaceWeight(float $otherMass) : float + { + return $otherMass * $this->surfaceGravity(); + } +} + +$myMass = 80; + +foreach (Planet::values() as $planet) { + printf("Your weight on %s is %f\n", $planet, $planet->surfaceWeight($myMass)); +} +``` diff --git a/data/web/inc/lib/vendor/dasprid/enum/composer.json b/data/web/inc/lib/vendor/dasprid/enum/composer.json new file mode 100644 index 0000000000..a099aba54a --- /dev/null +++ b/data/web/inc/lib/vendor/dasprid/enum/composer.json @@ -0,0 +1,34 @@ +{ + "name": "dasprid/enum", + "description": "PHP 7.1 enum implementation", + "license": "BSD-2-Clause", + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "keywords": [ + "enum", + "map" + ], + "require": { + "php": ">=7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "*" + }, + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "DASPRiD\\EnumTest\\": "test/" + } + } +} diff --git a/data/web/inc/lib/vendor/dasprid/enum/src/AbstractEnum.php b/data/web/inc/lib/vendor/dasprid/enum/src/AbstractEnum.php new file mode 100644 index 0000000000..43006dce6a --- /dev/null +++ b/data/web/inc/lib/vendor/dasprid/enum/src/AbstractEnum.php @@ -0,0 +1,261 @@ +> + */ + private static $values = []; + + /** + * @var array + */ + private static $allValuesLoaded = []; + + /** + * @var array + */ + private static $constants = []; + + /** + * The constructor is private by default to avoid arbitrary enum creation. + * + * When creating your own constructor for a parameterized enum, make sure to declare it as protected, so that + * the static methods are able to construct it. Avoid making it public, as that would allow creation of + * non-singleton enum instances. + */ + private function __construct() + { + } + + /** + * Magic getter which forwards all calls to {@see self::valueOf()}. + * + * @return static + */ + final public static function __callStatic(string $name, array $arguments) : self + { + return static::valueOf($name); + } + + /** + * Returns an enum with the specified name. + * + * The name must match exactly an identifier used to declare an enum in this type (extraneous whitespace characters + * are not permitted). + * + * @return static + * @throws IllegalArgumentException if the enum has no constant with the specified name + */ + final public static function valueOf(string $name) : self + { + if (isset(self::$values[static::class][$name])) { + return self::$values[static::class][$name]; + } + + $constants = self::constants(); + + if (array_key_exists($name, $constants)) { + return self::createValue($name, $constants[$name][0], $constants[$name][1]); + } + + throw new IllegalArgumentException(sprintf('No enum constant %s::%s', static::class, $name)); + } + + /** + * @return static + */ + private static function createValue(string $name, int $ordinal, array $arguments) : self + { + $instance = new static(...$arguments); + $instance->name = $name; + $instance->ordinal = $ordinal; + self::$values[static::class][$name] = $instance; + return $instance; + } + + /** + * Obtains all possible types defined by this enum. + * + * @return static[] + */ + final public static function values() : array + { + if (isset(self::$allValuesLoaded[static::class])) { + return self::$values[static::class]; + } + + if (! isset(self::$values[static::class])) { + self::$values[static::class] = []; + } + + foreach (self::constants() as $name => $constant) { + if (array_key_exists($name, self::$values[static::class])) { + continue; + } + + static::createValue($name, $constant[0], $constant[1]); + } + + uasort(self::$values[static::class], function (self $a, self $b) { + return $a->ordinal() <=> $b->ordinal(); + }); + + self::$allValuesLoaded[static::class] = true; + return self::$values[static::class]; + } + + private static function constants() : array + { + if (isset(self::$constants[static::class])) { + return self::$constants[static::class]; + } + + self::$constants[static::class] = []; + $reflectionClass = new ReflectionClass(static::class); + $ordinal = -1; + + foreach ($reflectionClass->getReflectionConstants() as $reflectionConstant) { + if (! $reflectionConstant->isProtected()) { + continue; + } + + $value = $reflectionConstant->getValue(); + + self::$constants[static::class][$reflectionConstant->name] = [ + ++$ordinal, + is_array($value) ? $value : [] + ]; + } + + return self::$constants[static::class]; + } + + /** + * Returns the name of this enum constant, exactly as declared in its enum declaration. + * + * Most programmers should use the {@see self::__toString()} method in preference to this one, as the toString + * method may return a more user-friendly name. This method is designed primarily for use in specialized situations + * where correctness depends on getting the exact name, which will not vary from release to release. + */ + final public function name() : string + { + return $this->name; + } + + /** + * Returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial + * constant is assigned an ordinal of zero). + * + * Most programmers will have no use for this method. It is designed for use by sophisticated enum-based data + * structures. + */ + final public function ordinal() : int + { + return $this->ordinal; + } + + /** + * Compares this enum with the specified object for order. + * + * Returns negative integer, zero or positive integer as this object is less than, equal to or greater than the + * specified object. + * + * Enums are only comparable to other enums of the same type. The natural order implemented by this method is the + * order in which the constants are declared. + * + * @throws MismatchException if the passed enum is not of the same type + */ + final public function compareTo(self $other) : int + { + if (! $other instanceof static) { + throw new MismatchException(sprintf( + 'The passed enum %s is not of the same type as %s', + get_class($other), + static::class + )); + } + + return $this->ordinal - $other->ordinal; + } + + /** + * Forbid cloning enums. + * + * @throws CloneNotSupportedException + */ + final public function __clone() + { + throw new CloneNotSupportedException(); + } + + /** + * Forbid serializing enums. + * + * @throws SerializeNotSupportedException + */ + final public function __sleep() : array + { + throw new SerializeNotSupportedException(); + } + + /** + * Forbid serializing enums. + * + * @throws SerializeNotSupportedException + */ + final public function __serialize() : array + { + throw new SerializeNotSupportedException(); + } + + /** + * Forbid unserializing enums. + * + * @throws UnserializeNotSupportedException + */ + final public function __wakeup() : void + { + throw new UnserializeNotSupportedException(); + } + + /** + * Forbid unserializing enums. + * + * @throws UnserializeNotSupportedException + */ + final public function __unserialize($arg) : void + { + throw new UnserializeNotSupportedException(); + } + + /** + * Turns the enum into a string representation. + * + * You may override this method to give a more user-friendly version. + */ + public function __toString() : string + { + return $this->name; + } +} diff --git a/data/web/inc/lib/vendor/dasprid/enum/src/EnumMap.php b/data/web/inc/lib/vendor/dasprid/enum/src/EnumMap.php new file mode 100644 index 0000000000..95b88568d5 --- /dev/null +++ b/data/web/inc/lib/vendor/dasprid/enum/src/EnumMap.php @@ -0,0 +1,385 @@ + + */ + private $keyUniverse; + + /** + * Array representation of this map. The ith element is the value to which universe[i] is currently mapped, or null + * if it isn't mapped to anything, or NullValue if it's mapped to null. + * + * @var array + */ + private $values; + + /** + * @var int + */ + private $size = 0; + + /** + * Creates a new enum map. + * + * @param string $keyType the type of the keys, must extend AbstractEnum + * @param string $valueType the type of the values + * @param bool $allowNullValues whether to allow null values + * @throws IllegalArgumentException when key type does not extend AbstractEnum + */ + public function __construct(string $keyType, string $valueType, bool $allowNullValues) + { + if (! is_subclass_of($keyType, AbstractEnum::class)) { + throw new IllegalArgumentException(sprintf( + 'Class %s does not extend %s', + $keyType, + AbstractEnum::class + )); + } + + $this->keyType = $keyType; + $this->valueType = $valueType; + $this->allowNullValues = $allowNullValues; + $this->keyUniverse = $keyType::values(); + $this->values = array_fill(0, count($this->keyUniverse), null); + } + + public function __serialize(): array + { + $values = []; + + foreach ($this->values as $ordinal => $value) { + if (null === $value) { + continue; + } + + $values[$ordinal] = $this->unmaskNull($value); + } + + return [ + 'keyType' => $this->keyType, + 'valueType' => $this->valueType, + 'allowNullValues' => $this->allowNullValues, + 'values' => $values, + ]; + } + + public function __unserialize(array $data): void + { + $this->unserialize(serialize($data)); + } + + /** + * Checks whether the map types match the supplied ones. + * + * You should call this method when an EnumMap is passed to you and you want to ensure that it's made up of the + * correct types. + * + * @throws ExpectationException when supplied key type mismatches local key type + * @throws ExpectationException when supplied value type mismatches local value type + * @throws ExpectationException when the supplied map allows null values, abut should not + */ + public function expect(string $keyType, string $valueType, bool $allowNullValues) : void + { + if ($keyType !== $this->keyType) { + throw new ExpectationException(sprintf( + 'Callee expected an EnumMap with key type %s, but got %s', + $keyType, + $this->keyType + )); + } + + if ($valueType !== $this->valueType) { + throw new ExpectationException(sprintf( + 'Callee expected an EnumMap with value type %s, but got %s', + $keyType, + $this->keyType + )); + } + + if ($allowNullValues !== $this->allowNullValues) { + throw new ExpectationException(sprintf( + 'Callee expected an EnumMap with nullable flag %s, but got %s', + ($allowNullValues ? 'true' : 'false'), + ($this->allowNullValues ? 'true' : 'false') + )); + } + } + + /** + * Returns the number of key-value mappings in this map. + */ + public function size() : int + { + return $this->size; + } + + /** + * Returns true if this map maps one or more keys to the specified value. + */ + public function containsValue($value) : bool + { + return in_array($this->maskNull($value), $this->values, true); + } + + /** + * Returns true if this map contains a mapping for the specified key. + */ + public function containsKey(AbstractEnum $key) : bool + { + $this->checkKeyType($key); + return null !== $this->values[$key->ordinal()]; + } + + /** + * Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key. + * + * More formally, if this map contains a mapping from a key to a value, then this method returns the value; + * otherwise it returns null (there can be at most one such mapping). + * + * A return value of null does not necessarily indicate that the map contains no mapping for the key; it's also + * possible that hte map explicitly maps the key to null. The {@see self::containsKey()} operation may be used to + * distinguish these two cases. + * + * @return mixed + */ + public function get(AbstractEnum $key) + { + $this->checkKeyType($key); + return $this->unmaskNull($this->values[$key->ordinal()]); + } + + /** + * Associates the specified value with the specified key in this map. + * + * If the map previously contained a mapping for this key, the old value is replaced. + * + * @return mixed the previous value associated with the specified key, or null if there was no mapping for the key. + * (a null return can also indicate that the map previously associated null with the specified key.) + * @throws IllegalArgumentException when the passed values does not match the internal value type + */ + public function put(AbstractEnum $key, $value) + { + $this->checkKeyType($key); + + if (! $this->isValidValue($value)) { + throw new IllegalArgumentException(sprintf('Value is not of type %s', $this->valueType)); + } + + $index = $key->ordinal(); + $oldValue = $this->values[$index]; + $this->values[$index] = $this->maskNull($value); + + if (null === $oldValue) { + ++$this->size; + } + + return $this->unmaskNull($oldValue); + } + + /** + * Removes the mapping for this key frm this map if present. + * + * @return mixed the previous value associated with the specified key, or null if there was no mapping for the key. + * (a null return can also indicate that the map previously associated null with the specified key.) + */ + public function remove(AbstractEnum $key) + { + $this->checkKeyType($key); + + $index = $key->ordinal(); + $oldValue = $this->values[$index]; + $this->values[$index] = null; + + if (null !== $oldValue) { + --$this->size; + } + + return $this->unmaskNull($oldValue); + } + + /** + * Removes all mappings from this map. + */ + public function clear() : void + { + $this->values = array_fill(0, count($this->keyUniverse), null); + $this->size = 0; + } + + /** + * Compares the specified map with this map for quality. + * + * Returns true if the two maps represent the same mappings. + */ + public function equals(self $other) : bool + { + if ($this === $other) { + return true; + } + + if ($this->size !== $other->size) { + return false; + } + + return $this->values === $other->values; + } + + /** + * Returns the values contained in this map. + * + * The array will contain the values in the order their corresponding keys appear in the map, which is their natural + * order (the order in which the num constants are declared). + */ + public function values() : array + { + return array_values(array_map(function ($value) { + return $this->unmaskNull($value); + }, array_filter($this->values, function ($value) : bool { + return null !== $value; + }))); + } + + public function serialize() : string + { + return serialize($this->__serialize()); + } + + public function unserialize($serialized) : void + { + $data = unserialize($serialized); + $this->__construct($data['keyType'], $data['valueType'], $data['allowNullValues']); + + foreach ($this->keyUniverse as $key) { + if (array_key_exists($key->ordinal(), $data['values'])) { + $this->put($key, $data['values'][$key->ordinal()]); + } + } + } + + public function getIterator() : Traversable + { + foreach ($this->keyUniverse as $key) { + if (null === $this->values[$key->ordinal()]) { + continue; + } + + yield $key => $this->unmaskNull($this->values[$key->ordinal()]); + } + } + + private function maskNull($value) + { + if (null === $value) { + return NullValue::instance(); + } + + return $value; + } + + private function unmaskNull($value) + { + if ($value instanceof NullValue) { + return null; + } + + return $value; + } + + /** + * @throws IllegalArgumentException when the passed key does not match the internal key type + */ + private function checkKeyType(AbstractEnum $key) : void + { + if (get_class($key) !== $this->keyType) { + throw new IllegalArgumentException(sprintf( + 'Object of type %s is not the same type as %s', + get_class($key), + $this->keyType + )); + } + } + + private function isValidValue($value) : bool + { + if (null === $value) { + if ($this->allowNullValues) { + return true; + } + + return false; + } + + switch ($this->valueType) { + case 'mixed': + return true; + + case 'bool': + case 'boolean': + return is_bool($value); + + case 'int': + case 'integer': + return is_int($value); + + case 'float': + case 'double': + return is_float($value); + + case 'string': + return is_string($value); + + case 'object': + return is_object($value); + + case 'array': + return is_array($value); + } + + return $value instanceof $this->valueType; + } +} diff --git a/data/web/inc/lib/vendor/dasprid/enum/src/Exception/CloneNotSupportedException.php b/data/web/inc/lib/vendor/dasprid/enum/src/Exception/CloneNotSupportedException.php new file mode 100644 index 0000000000..4b37dbebee --- /dev/null +++ b/data/web/inc/lib/vendor/dasprid/enum/src/Exception/CloneNotSupportedException.php @@ -0,0 +1,10 @@ +setRiskyAllowed(true) + ->setRules([ + '@DoctrineAnnotation' => true, + '@Symfony' => true, + '@Symfony:risky' => true, + '@PHPUnit75Migration:risky' => true, + '@PHP71Migration' => true, + '@PHP70Migration:risky' => true, // @TODO with next major version + 'align_multiline_comment' => ['comment_type' => 'all_multiline'], + 'array_indentation' => true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => ['default' => 'align_single_space'], + 'blank_line_before_statement' => true, + 'class_definition' => ['single_item_single_line' => true], + 'compact_nullable_typehint' => true, + 'concat_space' => ['spacing' => 'one'], + 'echo_tag_syntax' => ['format' => 'long'], + 'error_suppression' => false, + 'escape_implicit_backslashes' => true, + 'explicit_indirect_variable' => true, + 'explicit_string_variable' => true, + 'fully_qualified_strict_types' => true, + 'heredoc_to_nowdoc' => true, + 'list_syntax' => ['syntax' => 'long'], + 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], + 'method_chaining_indentation' => true, + 'multiline_comment_opening_closing' => true, + 'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'], + 'native_constant_invocation' => true, + 'native_function_invocation' => ['include' => ['@internal']], + 'no_alternative_syntax' => true, + 'no_break_comment' => true, + 'no_extra_blank_lines' => ['tokens' => ['break', 'continue', 'extra', 'return', 'throw', 'use', 'parenthesis_brace_block', 'square_brace_block', 'curly_brace_block']], + 'no_null_property_initialization' => true, + 'no_php4_constructor' => true, + 'no_superfluous_elseif' => true, + 'no_unneeded_curly_braces' => true, + 'no_unneeded_final_method' => true, + 'no_unreachable_default_argument_value' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'ordered_imports' => true, + 'php_unit_method_casing' => true, + 'php_unit_set_up_tear_down_visibility' => true, + 'php_unit_strict' => true, + 'php_unit_test_annotation' => true, + 'php_unit_test_case_static_method_calls' => true, + 'php_unit_test_class_requires_covers' => false, + 'phpdoc_add_missing_param_annotation' => true, + 'phpdoc_order' => true, + 'phpdoc_order_by_value' => true, + 'phpdoc_types_order' => true, + 'random_api_migration' => true, + 'semicolon_after_instruction' => true, + 'simplified_null_return' => true, + 'single_line_comment_style' => true, + 'single_line_throw' => false, + 'space_after_semicolon' => true, + 'static_lambda' => true, + 'strict_comparison' => true, + 'string_line_ending' => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__ . '/src') + ->in(__DIR__ . '/tests') + ) +; diff --git a/data/web/inc/lib/vendor/ddeboer/imap/LICENSE b/data/web/inc/lib/vendor/ddeboer/imap/LICENSE new file mode 100644 index 0000000000..2c679e3027 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2013 David de Boer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/data/web/inc/lib/vendor/ddeboer/imap/README.md b/data/web/inc/lib/vendor/ddeboer/imap/README.md new file mode 100644 index 0000000000..c57ab01fcf --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/README.md @@ -0,0 +1,307 @@ +# PHP IMAP library + +[![Latest Stable Version](https://img.shields.io/packagist/v/ddeboer/imap.svg)](https://packagist.org/packages/ddeboer/imap) +[![Downloads](https://img.shields.io/packagist/dt/ddeboer/imap.svg)](https://packagist.org/packages/ddeboer/imap) +[![Integrate](https://github.com/ddeboer/imap/workflows/Integrate/badge.svg?branch=master)](https://github.com/ddeboer/imap/actions) +[![Code Coverage](https://codecov.io/gh/ddeboer/imap/coverage.svg?branch=master)](https://codecov.io/gh/ddeboer/imap?branch=master) + +A PHP IMAP library to read and process e-mails over IMAP protocol, built with robust Object-Oriented architecture. + +This library requires PHP >= 7.4 with [IMAP](https://www.php.net/manual/en/book.imap.php), +[iconv](https://www.php.net/manual/en/book.iconv.php) and +[Multibyte String](https://www.php.net/manual/en/book.mbstring.php) extensions installed. + +## Installation + +The recommended way to install the IMAP library is through [Composer](https://getcomposer.org): + +```bash +$ composer require ddeboer/imap +``` + +This command requires you to have Composer installed globally, as explained +in the [installation chapter](https://getcomposer.org/doc/00-intro.md) +of the Composer documentation. + +## Usage + +### Connect and Authenticate + +```php +use Ddeboer\Imap\Server; + +$server = new Server('imap.gmail.com'); + +// $connection is instance of \Ddeboer\Imap\Connection +$connection = $server->authenticate('my_username', 'my_password'); +``` + +You can specify port, [flags and parameters](https://secure.php.net/manual/en/function.imap-open.php) +to the server: + +```php +$server = new Server( + $hostname, // required + $port, // defaults to '993' + $flags, // defaults to '/imap/ssl/validate-cert' + $parameters +); +``` + +### Mailboxes + +Retrieve mailboxes (also known as mail folders) from the mail server and iterate +over them: + +```php +$mailboxes = $connection->getMailboxes(); + +foreach ($mailboxes as $mailbox) { + // Skip container-only mailboxes + // @see https://secure.php.net/manual/en/function.imap-getmailboxes.php + if ($mailbox->getAttributes() & \LATT_NOSELECT) { + continue; + } + + // $mailbox is instance of \Ddeboer\Imap\Mailbox + printf('Mailbox "%s" has %s messages', $mailbox->getName(), $mailbox->count()); +} +``` + +Or retrieve a specific mailbox: + +```php +$mailbox = $connection->getMailbox('INBOX'); +``` + +Delete a mailbox: + +```php +$connection->deleteMailbox($mailbox); +``` + +You can bulk set, or clear, any [flag](https://secure.php.net/manual/en/function.imap-setflag-full.php) of mailbox messages (by UIDs): + +```php +$mailbox->setFlag('\\Seen \\Flagged', ['1:5', '7', '9']); +$mailbox->setFlag('\\Seen', '1,3,5,6:8'); + +$mailbox->clearFlag('\\Flagged', '1,3'); +``` + +**WARNING** You must retrieve new Message instances in case of bulk modify flags to refresh the single Messages flags. + +### Messages + +Retrieve messages (e-mails) from a mailbox and iterate over them: + +```php +$messages = $mailbox->getMessages(); + +foreach ($messages as $message) { + // $message is instance of \Ddeboer\Imap\Message +} +``` + +To insert a new message (that just has been sent) into the Sent mailbox and flag it as seen: + +```php +$mailbox = $connection->getMailbox('Sent'); +$mailbox->addMessage($messageMIME, '\\Seen'); +``` + +Note that the message should be a string at MIME format (as described in the [RFC2045](https://tools.ietf.org/html/rfc2045)). + +#### Searching for Messages + +```php +use Ddeboer\Imap\SearchExpression; +use Ddeboer\Imap\Search\Email\To; +use Ddeboer\Imap\Search\Text\Body; + +$search = new SearchExpression(); +$search->addCondition(new To('me@here.com')); +$search->addCondition(new Body('contents')); + +$messages = $mailbox->getMessages($search); +``` + +**WARNING** We are currently unable to have both spaces _and_ double-quotes +escaped together. Only spaces are currently escaped correctly. +You can use `Ddeboer\Imap\Search\RawExpression` to write the complete search +condition by yourself. + +Messages can also be retrieved sorted as per [imap_sort](https://secure.php.net/manual/en/function.imap-sort.php) +function: + +```php +$today = new DateTimeImmutable(); +$thirtyDaysAgo = $today->sub(new DateInterval('P30D')); + +$messages = $mailbox->getMessages( + new Ddeboer\Imap\Search\Date\Since($thirtyDaysAgo), + \SORTDATE, // Sort criteria + true // Descending order +); +``` + +#### Unknown search criterion: OR + +Note that PHP imap library relies on the `c-client` library available at https://www.washington.edu/imap/ +which doesn't fully support some IMAP4 search criteria like `OR`. If you want those unsupported criteria, +you need to manually patch the latest version (`imap-2007f` of 23-Jul-2011 at the time of this commit) +and recompile PHP onto your patched `c-client` library. + +By the way most of the common search criteria are available and functioning, browse them in `./src/Search`. + +References: + +1. https://stackoverflow.com/questions/36356715/imap-search-unknown-search-criterion-or +1. imap-2007f.tar.gz: `./src/c-client/mail.c` and `./docs/internal.txt` + +#### Message Properties and Operations + +Get message number and unique [message id](https://en.wikipedia.org/wiki/Message-ID) +in the form <...>: + +```php +$message->getNumber(); +$message->getId(); +``` + +Get other message properties: + +```php +$message->getSubject(); +$message->getFrom(); // Message\EmailAddress +$message->getTo(); // array of Message\EmailAddress +$message->getDate(); // DateTimeImmutable +$message->isAnswered(); +$message->isDeleted(); +$message->isDraft(); +$message->isSeen(); +``` + +Get message headers as a [\Ddeboer\Imap\Message\Headers](/src/Ddeboer/Imap/Message/Headers.php) object: + +```php +$message->getHeaders(); +``` + +Get message body as HTML or plain text: + +```php +$message->getBodyHtml(); // Content of text/html part, if present +$message->getBodyText(); // Content of text/plain part, if present +``` + +Reading the message body keeps the message as unseen. +If you want to mark the message as seen: + +```php +$message->markAsSeen(); +``` + +Or you can set, or clear, any [flag](https://secure.php.net/manual/en/function.imap-setflag-full.php): + +```php +$message->setFlag('\\Seen \\Flagged'); +$message->clearFlag('\\Flagged'); +``` + +Move a message to another mailbox: + +```php +$mailbox = $connection->getMailbox('another-mailbox'); +$message->move($mailbox); +``` + +Deleting messages: + +```php +$mailbox->getMessage(1)->delete(); +$mailbox->getMessage(2)->delete(); +$connection->expunge(); +``` + +### Message Attachments + +Get message attachments (both inline and attached) and iterate over them: + +```php +$attachments = $message->getAttachments(); + +foreach ($attachments as $attachment) { + // $attachment is instance of \Ddeboer\Imap\Message\Attachment +} +``` + +Download a message attachment to a local file: + +```php +// getDecodedContent() decodes the attachmentโ€™s contents automatically: +file_put_contents( + '/my/local/dir/' . $attachment->getFilename(), + $attachment->getDecodedContent() +); +``` + +### Embedded Messages + +Check if attachment is embedded message and get it: + +```php +$attachments = $message->getAttachments(); + +foreach ($attachments as $attachment) { + if ($attachment->isEmbeddedMessage()) { + $embeddedMessage = $attachment->getEmbeddedMessage(); + // $embeddedMessage is instance of \Ddeboer\Imap\Message\EmbeddedMessage + } +} +``` + +An EmbeddedMessage has the same API as a normal Message, apart from flags +and operations like copy, move or delete. + +### Timeouts + +The IMAP extension provides the [imap_timeout](https://secure.php.net/manual/en/function.imap-timeout.php) +function to adjust the timeout seconds for various operations. + +However the extension's implementation doesn't link the functionality to a +specific context or connection, instead they are global. So in order to not +affect functionalities outside this library, we had to choose whether wrap +every `imap_*` call around an optional user-provided timeout or leave this +task to the user. + +Because of the heterogeneous world of IMAP servers and the high complexity +burden cost for such a little gain of the former, we chose the latter. + +## Mock the library + +Mockability is granted by interfaces present for each API. +Dig into [MockabilityTest](tests/MockabilityTest.php) for an example of a +mocked workflow. + +## Running the Tests + +This library is functionally tested on [Travis CI](https://travis-ci.org/ddeboer/imap) +against a local Dovecot server. + +If you have your own IMAP (test) account, you can run the tests locally by +providing your IMAP credentials: + +```bash +$ composer install +$ IMAP_SERVER_NAME="my.imap.server.com" IMAP_SERVER_PORT="60993" IMAP_USERNAME="johndoe" IMAP_PASSWORD="p4ssword" vendor/bin/phpunit +``` + +You can also copy `phpunit.xml.dist` file to a custom `phpunit.xml` and put +these environment variables in it. + +**WARNING** Tests create new mailboxes without removing them. + +``` +$ docker-compose run tests +``` diff --git a/data/web/inc/lib/vendor/ddeboer/imap/composer.json b/data/web/inc/lib/vendor/ddeboer/imap/composer.json new file mode 100644 index 0000000000..0bcf72af42 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/composer.json @@ -0,0 +1,54 @@ +{ + "name": "ddeboer/imap", + "description": "Object-oriented IMAP for PHP", + "license": "MIT", + "keywords": [ + "email", + "mail", + "imap" + ], + "authors": [ + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com" + }, + { + "name": "Community contributors", + "homepage": "https://github.com/ddeboer/imap/graphs/contributors" + } + ], + "require": { + "php": "^8.0.1", + "ext-iconv": "*", + "ext-imap": "*", + "ext-mbstring": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v3.4.0", + "laminas/laminas-mail": "^2.15.1", + "malukenho/mcbumpface": "^1.1.5", + "phpstan/phpstan": "^1.3.3", + "phpstan/phpstan-phpunit": "^1.0.0", + "phpstan/phpstan-strict-rules": "^1.1.0", + "phpunit/phpunit": "^9.5.11" + }, + "autoload": { + "psr-4": { + "Ddeboer\\Imap\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Ddeboer\\Imap\\Tests\\": "tests/" + } + }, + "config": { + "allow-plugins": { + "malukenho/mcbumpface": true + } + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php new file mode 100644 index 0000000000..9f5ef90ff9 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php @@ -0,0 +1,178 @@ + + */ + private ?array $mailboxNames = null; + + /** + * Constructor. + * + * @throws \InvalidArgumentException + */ + public function __construct(ImapResourceInterface $resource, string $server) + { + $this->resource = $resource; + $this->server = $server; + } + + public function getResource(): ImapResourceInterface + { + return $this->resource; + } + + public function expunge(): bool + { + return \imap_expunge($this->resource->getStream()); + } + + public function close(int $flag = 0): bool + { + $this->resource->clearLastMailboxUsedCache(); + + return \imap_close($this->resource->getStream(), $flag); + } + + public function getQuota(string $root = 'INBOX'): array + { + $errorMessage = null; + $errorNumber = 0; + \set_error_handler(static function ($nr, $message) use (&$errorMessage, &$errorNumber): bool { + $errorMessage = $message; + $errorNumber = $nr; + + return true; + }); + + $return = \imap_get_quotaroot($this->resource->getStream(), $root); + + \restore_error_handler(); + + if (false === $return || null !== $errorMessage) { + throw new ImapQuotaException( + \sprintf( + 'IMAP Quota request failed for "%s"%s', + $root, + null !== $errorMessage ? ': ' . $errorMessage : '' + ), + $errorNumber + ); + } + + return $return; + } + + public function getMailboxes(): array + { + $this->initMailboxNames(); + \assert(null !== $this->mailboxNames); + + if (null === $this->mailboxes) { + $this->mailboxes = []; + foreach ($this->mailboxNames as $mailboxName => $mailboxInfo) { + $this->mailboxes[(string) $mailboxName] = $this->getMailbox((string) $mailboxName); + } + } + + return $this->mailboxes; + } + + public function hasMailbox(string $name): bool + { + $this->initMailboxNames(); + \assert(null !== $this->mailboxNames); + + return isset($this->mailboxNames[$name]); + } + + public function getMailbox(string $name): MailboxInterface + { + if (false === $this->hasMailbox($name)) { + throw new MailboxDoesNotExistException(\sprintf('Mailbox name "%s" does not exist', $name)); + } + \assert(isset($this->mailboxNames[$name])); + + return new Mailbox($this->resource, $name, $this->mailboxNames[$name]); + } + + #[\ReturnTypeWillChange] + public function count() + { + $return = \imap_num_msg($this->resource->getStream()); + + if (false === $return) { + throw new ImapNumMsgException('imap_num_msg failed'); + } + + return $return; + } + + public function ping(): bool + { + return \imap_ping($this->resource->getStream()); + } + + public function createMailbox(string $name): MailboxInterface + { + if (false === \imap_createmailbox($this->resource->getStream(), $this->server . \mb_convert_encoding($name, 'UTF7-IMAP', 'UTF-8'))) { + throw new CreateMailboxException(\sprintf('Can not create "%s" mailbox at "%s"', $name, $this->server)); + } + + $this->mailboxNames = $this->mailboxes = null; + $this->resource->clearLastMailboxUsedCache(); + + return $this->getMailbox($name); + } + + public function deleteMailbox(MailboxInterface $mailbox): void + { + if (false === \imap_deletemailbox($this->resource->getStream(), $mailbox->getFullEncodedName())) { + throw new DeleteMailboxException(\sprintf('Mailbox "%s" could not be deleted', $mailbox->getName())); + } + + $this->mailboxes = $this->mailboxNames = null; + $this->resource->clearLastMailboxUsedCache(); + } + + private function initMailboxNames(): void + { + if (null !== $this->mailboxNames) { + return; + } + + $this->mailboxNames = []; + $mailboxesInfo = \imap_getmailboxes($this->resource->getStream(), $this->server, '*'); + if (!\is_array($mailboxesInfo)) { + throw new ImapGetmailboxesException('imap_getmailboxes failed'); + } + + foreach ($mailboxesInfo as $mailboxInfo) { + $name = \mb_convert_encoding(\str_replace($this->server, '', $mailboxInfo->name), 'UTF-8', 'UTF7-IMAP'); + \assert(\is_string($name)); + + $this->mailboxNames[$name] = $mailboxInfo; + } + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/ConnectionInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/ConnectionInterface.php new file mode 100644 index 0000000000..6289339a7a --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/ConnectionInterface.php @@ -0,0 +1,82 @@ + + */ + public function getQuota(string $root = 'INBOX'): array; + + /** + * Get a list of mailboxes (also known as folders). + * + * @return MailboxInterface[] + */ + public function getMailboxes(): array; + + /** + * Check that a mailbox with the given name exists. + * + * @param string $name Mailbox name + */ + public function hasMailbox(string $name): bool; + + /** + * Get a mailbox by its name. + * + * @param string $name Mailbox name + * + * @throws MailboxDoesNotExistException If mailbox does not exist + */ + public function getMailbox(string $name): MailboxInterface; + + /** + * Create mailbox. + * + * @throws CreateMailboxException + */ + public function createMailbox(string $name): MailboxInterface; + + /** + * Delete mailbox. + * + * @throws DeleteMailboxException + */ + public function deleteMailbox(MailboxInterface $mailbox): void; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AbstractException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AbstractException.php new file mode 100644 index 0000000000..6537a05740 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AbstractException.php @@ -0,0 +1,49 @@ + 'E_ERROR', + \E_WARNING => 'E_WARNING', + \E_PARSE => 'E_PARSE', + \E_NOTICE => 'E_NOTICE', + \E_CORE_ERROR => 'E_CORE_ERROR', + \E_CORE_WARNING => 'E_CORE_WARNING', + \E_COMPILE_ERROR => 'E_COMPILE_ERROR', + \E_COMPILE_WARNING => 'E_COMPILE_WARNING', + \E_USER_ERROR => 'E_USER_ERROR', + \E_USER_WARNING => 'E_USER_WARNING', + \E_USER_NOTICE => 'E_USER_NOTICE', + \E_STRICT => 'E_STRICT', + \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + \E_DEPRECATED => 'E_DEPRECATED', + \E_USER_DEPRECATED => 'E_USER_DEPRECATED', + ]; + + final public function __construct(string $message, int $code = 0, \Throwable $previous = null) + { + $errorType = ''; + if (isset(self::ERROR_LABELS[$code])) { + $errorType = \sprintf('[%s] ', self::ERROR_LABELS[$code]); + } + + $joinString = "\n- "; + $alerts = \imap_alerts(); + $errors = \imap_errors(); + $completeMessage = \sprintf( + "%s%s\nimap_alerts (%s):%s\nimap_errors (%s):%s", + $errorType, + $message, + false !== $alerts ? \count($alerts) : 0, + false !== $alerts ? $joinString . \implode($joinString, $alerts) : '', + false !== $errors ? \count($errors) : 0, + false !== $errors ? $joinString . \implode($joinString, $errors) : '' + ); + + parent::__construct($completeMessage, $code, $previous); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AuthenticationFailedException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AuthenticationFailedException.php new file mode 100644 index 0000000000..c0e93d002a --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AuthenticationFailedException.php @@ -0,0 +1,9 @@ +resource = $resource; + $this->mailbox = $mailbox; + } + + public function getStream() + { + if ( + !$this->resource instanceof Connection + && (false === \is_resource($this->resource) || 'imap' !== \get_resource_type($this->resource)) + ) { + throw new InvalidResourceException('Supplied resource is not a valid imap resource'); + } + + $this->initMailbox(); + + return $this->resource; + } + + public function clearLastMailboxUsedCache(): void + { + self::$lastMailboxUsedCache = null; + } + + /** + * If connection is not currently in this mailbox, switch it to this mailbox. + */ + private function initMailbox(): void + { + if (null === $this->mailbox || self::isMailboxOpen($this->mailbox, $this->resource)) { + return; + } + + \set_error_handler(static function (): bool { + return true; + }); + + \imap_reopen($this->resource, $this->mailbox->getFullEncodedName()); + + \restore_error_handler(); + + if (self::isMailboxOpen($this->mailbox, $this->resource)) { + return; + } + + throw new ReopenMailboxException(\sprintf('Cannot reopen mailbox "%s"', $this->mailbox->getName())); + } + + /** + * Check whether the current mailbox is open. + * + * @param resource $resource + */ + private static function isMailboxOpen(MailboxInterface $mailbox, $resource): bool + { + $currentMailboxName = $mailbox->getFullEncodedName(); + if ($currentMailboxName === self::$lastMailboxUsedCache) { + return true; + } + + self::$lastMailboxUsedCache = null; + $check = \imap_check($resource); + $return = false !== $check && $check->Mailbox === $currentMailboxName; + + if (true === $return) { + self::$lastMailboxUsedCache = $currentMailboxName; + } + + return $return; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResourceInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResourceInterface.php new file mode 100644 index 0000000000..cf49f6b7c1 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResourceInterface.php @@ -0,0 +1,24 @@ +resource = new ImapResource($resource->getStream(), $this); + $this->name = $name; + $this->info = $info; + } + + public function getName(): string + { + return $this->name; + } + + public function getEncodedName(): string + { + /** @var string $name */ + $name = $this->info->name; + + return (string) \preg_replace('/^{.+}/', '', $name); + } + + public function getFullEncodedName(): string + { + return $this->info->name; + } + + public function getAttributes(): int + { + return $this->info->attributes; + } + + public function getDelimiter(): string + { + return $this->info->delimiter; + } + + #[\ReturnTypeWillChange] + public function count() + { + $return = \imap_num_msg($this->resource->getStream()); + + if (false === $return) { + throw new ImapNumMsgException('imap_num_msg failed'); + } + + return $return; + } + + public function getStatus(int $flags = null): \stdClass + { + $return = \imap_status($this->resource->getStream(), $this->getFullEncodedName(), $flags ?? \SA_ALL); + + if (false === $return) { + throw new ImapStatusException('imap_status failed'); + } + + return $return; + } + + public function setFlag(string $flag, $numbers): bool + { + return \imap_setflag_full($this->resource->getStream(), $this->prepareMessageIds($numbers), $flag, \ST_UID); + } + + public function clearFlag(string $flag, $numbers): bool + { + return \imap_clearflag_full($this->resource->getStream(), $this->prepareMessageIds($numbers), $flag, \ST_UID); + } + + public function getMessages(ConditionInterface $search = null, int $sortCriteria = null, bool $descending = false, string $charset = null): MessageIteratorInterface + { + if (null === $search) { + $search = new All(); + } + $query = $search->toString(); + + if (\PHP_VERSION_ID < 80000) { + $descending = (int) $descending; + } + + // We need to clear the stack to know whether imap_last_error() + // is related to this imap_search + \imap_errors(); + + if (null !== $sortCriteria) { + $params = [ + $this->resource->getStream(), + $sortCriteria, + $descending, + \SE_UID, + $query, + ]; + if (null !== $charset) { + $params[] = $charset; + } + $messageNumbers = \imap_sort(...$params); + } else { + $params = [ + $this->resource->getStream(), + $query, + \SE_UID, + ]; + if (null !== $charset) { + $params[] = $charset; + } + $messageNumbers = \imap_search(...$params); + } + if (false !== \imap_last_error()) { + // this way all errors occurred during search will be reported + throw new InvalidSearchCriteriaException( + \sprintf('Invalid search criteria [%s]', $query) + ); + } + if (false === $messageNumbers) { + // imap_search can also return false + $messageNumbers = []; + } + + return new MessageIterator($this->resource, $messageNumbers); + } + + public function getMessageSequence(string $sequence): MessageIteratorInterface + { + \imap_errors(); + + $overview = \imap_fetch_overview($this->resource->getStream(), $sequence, \FT_UID); + if (false !== \imap_last_error()) { + throw new InvalidSearchCriteriaException( + \sprintf('Invalid sequence [%s]', $sequence) + ); + } + if (\is_array($overview) && [] !== $overview) { + $messageNumbers = \array_column($overview, 'uid'); + } else { + $messageNumbers = []; + } + + return new MessageIterator($this->resource, $messageNumbers); + } + + public function getMessage(int $number): MessageInterface + { + return new Message($this->resource, $number); + } + + public function getIterator(): MessageIteratorInterface + { + return $this->getMessages(); + } + + public function addMessage(string $message, string $options = null, DateTimeInterface $internalDate = null): bool + { + $arguments = [ + $this->resource->getStream(), + $this->getFullEncodedName(), + $message, + $options ?? '', + ]; + if (null !== $internalDate) { + $arguments[] = $internalDate->format('d-M-Y H:i:s O'); + } + + return \imap_append(...$arguments); + } + + public function getThread(): array + { + \set_error_handler(static function (): bool { + return true; + }); + + /** @var array|false $tree */ + $tree = \imap_thread($this->resource->getStream(), \SE_UID); + + \restore_error_handler(); + + return false !== $tree ? $tree : []; + } + + public function move($numbers, MailboxInterface $mailbox): void + { + if (!\imap_mail_copy($this->resource->getStream(), $this->prepareMessageIds($numbers), $mailbox->getEncodedName(), \CP_UID | \CP_MOVE)) { + throw new MessageMoveException(\sprintf('Messages cannot be moved to "%s"', $mailbox->getName())); + } + } + + public function copy($numbers, MailboxInterface $mailbox): void + { + if (!\imap_mail_copy($this->resource->getStream(), $this->prepareMessageIds($numbers), $mailbox->getEncodedName(), \CP_UID)) { + throw new MessageCopyException(\sprintf('Messages cannot be copied to "%s"', $mailbox->getName())); + } + } + + /** + * Prepare message ids for the use with bulk functions. + * + * @param array|MessageIterator|string $messageIds Message numbers + */ + private function prepareMessageIds($messageIds): string + { + if ($messageIds instanceof MessageIterator) { + $messageIds = $messageIds->getArrayCopy(); + } + + if (\is_array($messageIds)) { + $messageIds = \implode(',', $messageIds); + } + + return $messageIds; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php new file mode 100644 index 0000000000..067bdc2888 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php @@ -0,0 +1,123 @@ + + */ +interface MailboxInterface extends \Countable, \IteratorAggregate +{ + /** + * Get mailbox decoded name. + */ + public function getName(): string; + + /** + * Get mailbox encoded path. + */ + public function getEncodedName(): string; + + /** + * Get mailbox encoded full name. + */ + public function getFullEncodedName(): string; + + /** + * Get mailbox attributes. + */ + public function getAttributes(): int; + + /** + * Get mailbox delimiter. + */ + public function getDelimiter(): string; + + /** + * Get Mailbox status. + */ + public function getStatus(int $flags = null): \stdClass; + + /** + * Bulk Set Flag for Messages. + * + * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft + * @param array|MessageIterator|string $numbers Message numbers + */ + public function setFlag(string $flag, $numbers): bool; + + /** + * Bulk Clear Flag for Messages. + * + * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft + * @param array|MessageIterator|string $numbers Message numbers + */ + public function clearFlag(string $flag, $numbers): bool; + + /** + * Get message ids. + * + * @param ConditionInterface $search Search expression (optional) + */ + public function getMessages(ConditionInterface $search = null, int $sortCriteria = null, bool $descending = false, string $charset = null): MessageIteratorInterface; + + /** + * Get message iterator for a sequence. + * + * @param string $sequence Message numbers + */ + public function getMessageSequence(string $sequence): MessageIteratorInterface; + + /** + * Get a message by message number. + * + * @param int $number Message number + * + * @return MessageInterface + */ + public function getMessage(int $number): MessageInterface; + + /** + * Get messages in this mailbox. + */ + public function getIterator(): MessageIteratorInterface; + + /** + * Add a message to the mailbox. + */ + public function addMessage(string $message, string $options = null, DateTimeInterface $internalDate = null): bool; + + /** + * Returns a tree of threaded message for the current Mailbox. + * + * @return array + */ + public function getThread(): array; + + /** + * Bulk move messages. + * + * @param array|MessageIterator|string $numbers Message numbers + * @param MailboxInterface $mailbox Destination Mailbox to move the messages to + * + * @throws \Ddeboer\Imap\Exception\MessageMoveException + */ + public function move($numbers, self $mailbox): void; + + /** + * Bulk copy messages. + * + * @param array|MessageIterator|string $numbers Message numbers + * @param MailboxInterface $mailbox Destination Mailbox to copy the messages to + * + * @throws \Ddeboer\Imap\Exception\MessageCopyException + */ + public function copy($numbers, self $mailbox): void; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message.php new file mode 100644 index 0000000000..1a9d16e732 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message.php @@ -0,0 +1,267 @@ +structureLoaded) { + return; + } + $this->structureLoaded = true; + + $messageNumber = $this->getNumber(); + + $errorMessage = null; + $errorNumber = 0; + \set_error_handler(static function ($nr, $message) use (&$errorMessage, &$errorNumber): bool { + $errorMessage = $message; + $errorNumber = $nr; + + return true; + }); + + $structure = \imap_fetchstructure( + $this->resource->getStream(), + $messageNumber, + \FT_UID + ); + + \restore_error_handler(); + + if (!$structure instanceof \stdClass) { + throw new MessageStructureException(\sprintf( + 'Message "%s" structure is empty: %s', + $messageNumber, + $errorMessage + ), $errorNumber); + } + + $this->setStructure($structure); + } + + protected function assertMessageExists(int $messageNumber): void + { + if (true === $this->messageNumberVerified) { + return; + } + $this->messageNumberVerified = true; + + $msgno = null; + \set_error_handler(static function (): bool { + return true; + }); + + $msgno = \imap_msgno($this->resource->getStream(), $messageNumber); + + \restore_error_handler(); + + if (\is_numeric($msgno) && $msgno > 0) { + $this->imapMsgNo = $msgno; + + return; + } + + throw new MessageDoesNotExistException(\sprintf( + 'Message "%s" does not exist', + $messageNumber + )); + } + + private function getMsgNo(): int + { + // Triggers assertMessageExists() + $this->getNumber(); + + return $this->imapMsgNo; + } + + public function getRawHeaders(): string + { + if (null === $this->rawHeaders) { + $rawHeaders = \imap_fetchheader($this->resource->getStream(), $this->getNumber(), \FT_UID); + + if (false === $rawHeaders) { + throw new ImapFetchheaderException('imap_fetchheader failed'); + } + + $this->rawHeaders = $rawHeaders; + } + + return $this->rawHeaders; + } + + public function getRawMessage(): string + { + if (null === $this->rawMessage) { + $this->rawMessage = $this->doGetContent(''); + } + + return $this->rawMessage; + } + + public function getHeaders(): Message\Headers + { + if (null === $this->headers) { + // imap_headerinfo is much faster than imap_fetchheader + // imap_headerinfo returns only a subset of all mail headers, + // but it does include the message flags. + $headers = \imap_headerinfo($this->resource->getStream(), $this->getMsgNo()); + if (false === $headers) { + // @see https://github.com/ddeboer/imap/issues/358 + throw new InvalidHeadersException(\sprintf('Message "%s" has invalid headers', $this->getNumber())); + } + $this->headers = new Message\Headers($headers); + } + + return $this->headers; + } + + /** + * Clearmessage headers. + */ + private function clearHeaders(): void + { + $this->headers = null; + } + + public function isRecent(): ?string + { + $recent = $this->getHeaders()->get('recent'); + \assert(null === $recent || \is_string($recent)); + + return $recent; + } + + public function isUnseen(): bool + { + return 'U' === $this->getHeaders()->get('unseen'); + } + + public function isFlagged(): bool + { + return 'F' === $this->getHeaders()->get('flagged'); + } + + public function isAnswered(): bool + { + return 'A' === $this->getHeaders()->get('answered'); + } + + public function isDeleted(): bool + { + return 'D' === $this->getHeaders()->get('deleted'); + } + + public function isDraft(): bool + { + return 'X' === $this->getHeaders()->get('draft'); + } + + public function isSeen(): bool + { + return 'N' !== $this->getHeaders()->get('recent') && 'U' !== $this->getHeaders()->get('unseen'); + } + + public function maskAsSeen(): bool + { + \trigger_error(\sprintf('%s is deprecated and will be removed in 2.0. Use %s::markAsSeen instead.', __METHOD__, __CLASS__), \E_USER_DEPRECATED); + + return $this->markAsSeen(); + } + + public function markAsSeen(): bool + { + return $this->setFlag('\\Seen'); + } + + public function copy(MailboxInterface $mailbox): void + { + // 'deleted' header changed, force to reload headers, would be better to set deleted flag to true on header + $this->clearHeaders(); + + if (!\imap_mail_copy($this->resource->getStream(), (string) $this->getNumber(), $mailbox->getEncodedName(), \CP_UID)) { + throw new MessageCopyException(\sprintf('Message "%s" cannot be copied to "%s"', $this->getNumber(), $mailbox->getName())); + } + } + + public function move(MailboxInterface $mailbox): void + { + // 'deleted' header changed, force to reload headers, would be better to set deleted flag to true on header + $this->clearHeaders(); + + if (!\imap_mail_move($this->resource->getStream(), (string) $this->getNumber(), $mailbox->getEncodedName(), \CP_UID)) { + throw new MessageMoveException(\sprintf('Message "%s" cannot be moved to "%s"', $this->getNumber(), $mailbox->getName())); + } + } + + public function delete(): void + { + // 'deleted' header changed, force to reload headers, would be better to set deleted flag to true on header + $this->clearHeaders(); + + if (!\imap_delete($this->resource->getStream(), (string) $this->getNumber(), \FT_UID)) { + throw new MessageDeleteException(\sprintf('Message "%s" cannot be deleted', $this->getNumber())); + } + } + + public function undelete(): void + { + // 'deleted' header changed, force to reload headers, would be better to set deleted flag to false on header + $this->clearHeaders(); + if (!\imap_undelete($this->resource->getStream(), (string) $this->getNumber(), \FT_UID)) { + throw new MessageUndeleteException(\sprintf('Message "%s" cannot be undeleted', $this->getNumber())); + } + } + + public function setFlag(string $flag): bool + { + $result = \imap_setflag_full($this->resource->getStream(), (string) $this->getNumber(), $flag, \ST_UID); + + $this->clearHeaders(); + + return $result; + } + + public function clearFlag(string $flag): bool + { + $result = \imap_clearflag_full($this->resource->getStream(), (string) $this->getNumber(), $flag, \ST_UID); + + $this->clearHeaders(); + + return $result; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php new file mode 100644 index 0000000000..0f022cc470 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php @@ -0,0 +1,308 @@ + + */ + final public function getId(): ?string + { + $messageId = $this->getHeaders()->get('message_id'); + \assert(null === $messageId || \is_string($messageId)); + + return $messageId; + } + + /** + * Get message sender (from headers). + */ + final public function getFrom(): ?EmailAddress + { + $from = $this->getHeaders()->get('from'); + \assert(null === $from || \is_array($from)); + + return null !== $from ? $this->decodeEmailAddress($from[0]) : null; + } + + /** + * Get To recipients. + * + * @return EmailAddress[] Empty array in case message has no To: recipients + */ + final public function getTo(): array + { + $emails = $this->getHeaders()->get('to'); + \assert(null === $emails || \is_array($emails)); + + return $this->decodeEmailAddresses($emails ?? []); + } + + /** + * Get Cc recipients. + * + * @return EmailAddress[] Empty array in case message has no CC: recipients + */ + final public function getCc(): array + { + $emails = $this->getHeaders()->get('cc'); + \assert(null === $emails || \is_array($emails)); + + return $this->decodeEmailAddresses($emails ?? []); + } + + /** + * Get Bcc recipients. + * + * @return EmailAddress[] Empty array in case message has no BCC: recipients + */ + final public function getBcc(): array + { + $emails = $this->getHeaders()->get('bcc'); + \assert(null === $emails || \is_array($emails)); + + return $this->decodeEmailAddresses($emails ?? []); + } + + /** + * Get Reply-To recipients. + * + * @return EmailAddress[] Empty array in case message has no Reply-To: recipients + */ + final public function getReplyTo(): array + { + $emails = $this->getHeaders()->get('reply_to'); + \assert(null === $emails || \is_array($emails)); + + return $this->decodeEmailAddresses($emails ?? []); + } + + /** + * Get Sender. + * + * @return EmailAddress[] Empty array in case message has no Sender: recipients + */ + final public function getSender(): array + { + $emails = $this->getHeaders()->get('sender'); + \assert(null === $emails || \is_array($emails)); + + return $this->decodeEmailAddresses($emails ?? []); + } + + /** + * Get Return-Path. + * + * @return EmailAddress[] Empty array in case message has no Return-Path: recipients + */ + final public function getReturnPath(): array + { + $emails = $this->getHeaders()->get('return_path'); + \assert(null === $emails || \is_array($emails)); + + return $this->decodeEmailAddresses($emails ?? []); + } + + /** + * Get date (from headers). + */ + final public function getDate(): ?\DateTimeImmutable + { + /** @var null|string $dateHeader */ + $dateHeader = $this->getHeaders()->get('date'); + if (null === $dateHeader) { + return null; + } + + $alteredValue = $dateHeader; + $alteredValue = \str_replace(',', '', $alteredValue); + $alteredValue = (string) \preg_replace('/^[a-zA-Z]+ ?/', '', $alteredValue); + $alteredValue = (string) \preg_replace('/\(.*\)/', '', $alteredValue); + $alteredValue = (string) \preg_replace('/\<.*\>/', '', $alteredValue); + $alteredValue = (string) \preg_replace('/\bUT\b/', 'UTC', $alteredValue); + if (0 === \preg_match('/\d\d:\d\d:\d\d.* [\+\-]\d\d:?\d\d/', $alteredValue)) { + $alteredValue .= ' +0000'; + } + // Handle numeric months + $alteredValue = (string) \preg_replace('/^(\d\d) (\d\d) (\d\d(?:\d\d)?) /', '$3-$2-$1 ', $alteredValue); + + try { + $date = new \DateTimeImmutable($alteredValue); + } catch (\Throwable $ex) { + throw new InvalidDateHeaderException(\sprintf('Invalid Date header found: "%s"', $dateHeader), 0, $ex); + } + + return $date; + } + + /** + * Get message size (from headers). + * + * @return null|int|string + */ + final public function getSize() + { + $size = $this->getHeaders()->get('size'); + \assert(null === $size || \is_int($size) || \is_string($size)); + + return $size; + } + + /** + * Get message subject (from headers). + */ + final public function getSubject(): ?string + { + $subject = $this->getHeaders()->get('subject'); + \assert(null === $subject || \is_string($subject)); + + return $subject; + } + + /** + * Get message In-Reply-To (from headers). + * + * @return string[] + */ + final public function getInReplyTo(): array + { + $inReplyTo = $this->getHeaders()->get('in_reply_to'); + \assert(null === $inReplyTo || \is_string($inReplyTo)); + + return null !== $inReplyTo ? \explode(' ', $inReplyTo) : []; + } + + /** + * Get message References (from headers). + * + * @return string[] + */ + final public function getReferences(): array + { + $references = $this->getHeaders()->get('references'); + \assert(null === $references || \is_string($references)); + + return null !== $references ? \explode(' ', $references) : []; + } + + /** + * Get body HTML. + */ + final public function getBodyHtml(): ?string + { + $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST); + foreach ($iterator as $part) { + if (self::SUBTYPE_HTML === $part->getSubtype()) { + return $part->getDecodedContent(); + } + } + + // If message has no parts and is HTML, return content of message itself. + if (self::SUBTYPE_HTML === $this->getSubtype()) { + return $this->getDecodedContent(); + } + + return null; + } + + /** + * Get body text. + */ + final public function getBodyText(): ?string + { + $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST); + foreach ($iterator as $part) { + if (self::SUBTYPE_PLAIN === $part->getSubtype()) { + return $part->getDecodedContent(); + } + } + + // If message has no parts, return content of message itself. + if (self::SUBTYPE_PLAIN === $this->getSubtype()) { + return $this->getDecodedContent(); + } + + return null; + } + + /** + * Get attachments (if any) linked to this e-mail. + * + * @return AttachmentInterface[] + */ + final public function getAttachments(): array + { + if (null === $this->attachments) { + $this->attachments = self::gatherAttachments($this); + } + + return $this->attachments; + } + + /** + * @param PartInterface $part + * + * @return Attachment[] + */ + private static function gatherAttachments(PartInterface $part): array + { + $attachments = []; + foreach ($part->getParts() as $childPart) { + if ($childPart instanceof Attachment) { + $attachments[] = $childPart; + } + if ($childPart->hasChildren()) { + $attachments = \array_merge($attachments, self::gatherAttachments($childPart)); + } + } + + return $attachments; + } + + /** + * Does this message have attachments? + */ + final public function hasAttachments(): bool + { + return \count($this->getAttachments()) > 0; + } + + /** + * @param \stdClass[] $addresses + * + * @return EmailAddress[] + */ + private function decodeEmailAddresses(array $addresses): array + { + $return = []; + foreach ($addresses as $address) { + if (isset($address->mailbox)) { + $return[] = $this->decodeEmailAddress($address); + } + } + + return $return; + } + + private function decodeEmailAddress(\stdClass $value): EmailAddress + { + return new EmailAddress($value->mailbox, $value->host, $value->personal); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php new file mode 100644 index 0000000000..0647133c5d --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php @@ -0,0 +1,436 @@ + self::TYPE_TEXT, + \TYPEMULTIPART => self::TYPE_MULTIPART, + \TYPEMESSAGE => self::TYPE_MESSAGE, + \TYPEAPPLICATION => self::TYPE_APPLICATION, + \TYPEAUDIO => self::TYPE_AUDIO, + \TYPEIMAGE => self::TYPE_IMAGE, + \TYPEVIDEO => self::TYPE_VIDEO, + \TYPEMODEL => self::TYPE_MODEL, + \TYPEOTHER => self::TYPE_OTHER, + ]; + + private const ENCODINGS_MAP = [ + \ENC7BIT => self::ENCODING_7BIT, + \ENC8BIT => self::ENCODING_8BIT, + \ENCBINARY => self::ENCODING_BINARY, + \ENCBASE64 => self::ENCODING_BASE64, + \ENCQUOTEDPRINTABLE => self::ENCODING_QUOTED_PRINTABLE, + ]; + + private const ATTACHMENT_KEYS = [ + 'name' => true, + 'filename' => true, + 'name*' => true, + 'filename*' => true, + ]; + + protected ImapResourceInterface $resource; + private bool $structureParsed = false; + /** + * @var AbstractPart[] + */ + private array $parts = []; + private string $partNumber; + private int $messageNumber; + private \stdClass $structure; + private Parameters $parameters; + private ?string $type = null; + private ?string $subtype = null; + private ?string $encoding = null; + private ?string $disposition = null; + private ?string $description = null; + /** @var null|int|string */ + private $bytes; + private ?string $lines = null; + private ?string $content = null; + private ?string $decodedContent = null; + private int $key = 0; + + /** + * Constructor. + * + * @param ImapResourceInterface $resource IMAP resource + * @param int $messageNumber Message number + * @param string $partNumber Part number + * @param \stdClass $structure Part structure + */ + public function __construct( + ImapResourceInterface $resource, + int $messageNumber, + string $partNumber, + \stdClass $structure + ) { + $this->resource = $resource; + $this->messageNumber = $messageNumber; + $this->partNumber = $partNumber; + $this->setStructure($structure); + } + + final public function getNumber(): int + { + $this->assertMessageExists($this->messageNumber); + + return $this->messageNumber; + } + + /** + * Ensure message exists. + */ + protected function assertMessageExists(int $messageNumber): void + { + } + + /** + * @param \stdClass $structure Part structure + */ + final protected function setStructure(\stdClass $structure): void + { + $this->structure = $structure; + } + + final public function getStructure(): \stdClass + { + $this->lazyLoadStructure(); + + return $this->structure; + } + + /** + * Lazy load structure. + */ + protected function lazyLoadStructure(): void + { + } + + final public function getParameters(): Parameters + { + $this->lazyParseStructure(); + + return $this->parameters; + } + + final public function getCharset(): ?string + { + $this->lazyParseStructure(); + + $charset = $this->parameters->get('charset'); + \assert(null === $charset || \is_string($charset)); + + return '' !== $charset ? $charset : null; + } + + final public function getType(): ?string + { + $this->lazyParseStructure(); + + return $this->type; + } + + final public function getSubtype(): ?string + { + $this->lazyParseStructure(); + + return $this->subtype; + } + + final public function getEncoding(): ?string + { + $this->lazyParseStructure(); + + return $this->encoding; + } + + final public function getDisposition(): ?string + { + $this->lazyParseStructure(); + + return $this->disposition; + } + + final public function getDescription(): ?string + { + $this->lazyParseStructure(); + + return $this->description; + } + + final public function getBytes() + { + $this->lazyParseStructure(); + + return $this->bytes; + } + + final public function getLines(): ?string + { + $this->lazyParseStructure(); + + return $this->lines; + } + + final public function getContent(): string + { + if (null === $this->content) { + $this->content = $this->doGetContent($this->getContentPartNumber()); + } + + return $this->content; + } + + /** + * Get content part number. + */ + protected function getContentPartNumber(): string + { + return $this->partNumber; + } + + final public function getPartNumber(): string + { + return $this->partNumber; + } + + final public function getDecodedContent(): string + { + if (null === $this->decodedContent) { + if (self::ENCODING_UNKNOWN === $this->getEncoding()) { + throw new UnexpectedEncodingException('Cannot decode a content with an uknown encoding'); + } + + $content = $this->getContent(); + if (self::ENCODING_BASE64 === $this->getEncoding()) { + $content = \base64_decode($content, false); + } elseif (self::ENCODING_QUOTED_PRINTABLE === $this->getEncoding()) { + $content = \quoted_printable_decode($content); + } + + if (false === $content) { + throw new UnexpectedEncodingException('Cannot decode content'); + } + + // If this part is a text part, convert its charset to UTF-8. + // We don't want to decode an attachment's charset. + if (!$this instanceof Attachment && null !== $this->getCharset() && self::TYPE_TEXT === $this->getType()) { + $content = Transcoder::decode($content, $this->getCharset()); + } + + $this->decodedContent = $content; + } + + return $this->decodedContent; + } + + /** + * Get raw message content. + */ + final protected function doGetContent(string $partNumber): string + { + $return = \imap_fetchbody( + $this->resource->getStream(), + $this->getNumber(), + $partNumber, + \FT_UID | \FT_PEEK + ); + + if (false === $return) { + throw new ImapFetchbodyException('imap_fetchbody failed'); + } + + return $return; + } + + final public function getParts(): array + { + $this->lazyParseStructure(); + + return $this->parts; + } + + /** + * Get current child part. + * + * @return mixed + */ + #[\ReturnTypeWillChange] + final public function current() + { + $this->lazyParseStructure(); + + return $this->parts[$this->key]; + } + + #[\ReturnTypeWillChange] + final public function getChildren() + { + return $this->current(); + } + + #[\ReturnTypeWillChange] + final public function hasChildren() + { + $this->lazyParseStructure(); + + return \count($this->parts) > 0; + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] + final public function key() + { + return $this->key; + } + + #[\ReturnTypeWillChange] + final public function next() + { + ++$this->key; + } + + #[\ReturnTypeWillChange] + final public function rewind() + { + $this->key = 0; + } + + #[\ReturnTypeWillChange] + final public function valid() + { + $this->lazyParseStructure(); + + return isset($this->parts[$this->key]); + } + + /** + * Parse part structure. + */ + private function lazyParseStructure(): void + { + if (true === $this->structureParsed) { + return; + } + $this->structureParsed = true; + + $this->lazyLoadStructure(); + + $this->type = self::TYPES_MAP[$this->structure->type] ?? self::TYPE_UNKNOWN; + + // In our context, \ENCOTHER is as useful as an unknown encoding + $this->encoding = self::ENCODINGS_MAP[$this->structure->encoding] ?? self::ENCODING_UNKNOWN; + if (isset($this->structure->subtype)) { + $this->subtype = $this->structure->subtype; + } + + if (isset($this->structure->bytes)) { + $this->bytes = $this->structure->bytes; + } + if ($this->structure->ifdisposition) { + $this->disposition = $this->structure->disposition; + } + if ($this->structure->ifdescription) { + $this->description = $this->structure->description; + } + + $this->parameters = new Parameters(); + if ($this->structure->ifparameters) { + $this->parameters->add($this->structure->parameters); + } + + if ($this->structure->ifdparameters) { + $this->parameters->add($this->structure->dparameters); + } + + // When the message is not multipart and the body is the attachment content + // Prevents infinite recursion + if (self::isAttachment($this->structure) && !$this instanceof Attachment) { + $this->parts[] = new Attachment($this->resource, $this->getNumber(), '1', $this->structure); + } + + if (isset($this->structure->parts)) { + $parts = $this->structure->parts; + // https://secure.php.net/manual/en/function.imap-fetchbody.php#89002 + if ($this instanceof Attachment && $this->isEmbeddedMessage() && 1 === \count($parts) && \TYPEMULTIPART === $parts[0]->type) { + $parts = $parts[0]->parts; + } + foreach ($parts as $key => $partStructure) { + $partNumber = (!$this instanceof Message) ? $this->partNumber . '.' : ''; + $partNumber .= (string) ($key + 1); + + $newPartClass = self::isAttachment($partStructure) + ? Attachment::class + : SimplePart::class + ; + + $this->parts[] = new $newPartClass($this->resource, $this->getNumber(), $partNumber, $partStructure); + } + } + } + + /** + * Check if the given part is an attachment. + */ + private static function isAttachment(\stdClass $part): bool + { + if (isset(self::TYPES_MAP[$part->type]) && self::TYPE_MULTIPART === self::TYPES_MAP[$part->type]) { + return false; + } + + // Attachment with correct Content-Disposition header + if ($part->ifdisposition) { + if ('attachment' === \strtolower($part->disposition)) { + return true; + } + + if ( + 'inline' === \strtolower($part->disposition) + && self::SUBTYPE_PLAIN !== \strtoupper($part->subtype) + && self::SUBTYPE_HTML !== \strtoupper($part->subtype) + ) { + return true; + } + } + + // Attachment without Content-Disposition header + if ($part->ifparameters) { + foreach ($part->parameters as $parameter) { + if (isset(self::ATTACHMENT_KEYS[\strtolower($parameter->attribute)])) { + return true; + } + } + } + + /* + if ($part->ifdparameters) { + foreach ($part->dparameters as $parameter) { + if (isset(self::$attachmentKeys[\strtolower($parameter->attribute)])) { + return true; + } + } + } + */ + + if (self::SUBTYPE_RFC822 === \strtoupper($part->subtype)) { + return true; + } + + return false; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php new file mode 100644 index 0000000000..b134b8d914 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php @@ -0,0 +1,53 @@ +getParameters()->get('filename'); + if (null === $filename || '' === $filename) { + $filename = $this->getParameters()->get('name'); + } + \assert(null === $filename || \is_string($filename)); + + return $filename; + } + + public function getSize() + { + $size = $this->getParameters()->get('size'); + if (\is_numeric($size)) { + $size = (int) $size; + } + \assert(null === $size || \is_int($size)); + + return $size; + } + + public function isEmbeddedMessage(): bool + { + return self::TYPE_MESSAGE === $this->getType(); + } + + public function getEmbeddedMessage(): EmbeddedMessageInterface + { + if (!$this->isEmbeddedMessage()) { + throw new NotEmbeddedMessageException(\sprintf( + 'Attachment "%s" in message "%s" is not embedded message', + $this->getPartNumber(), + $this->getNumber() + )); + } + + return new EmbeddedMessage($this->resource, $this->getNumber(), $this->getPartNumber(), $this->getStructure()->parts[0]); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php new file mode 100644 index 0000000000..286e188851 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php @@ -0,0 +1,39 @@ + + */ + public function getEmbeddedMessage(): EmbeddedMessageInterface; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/BasicMessageInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/BasicMessageInterface.php new file mode 100644 index 0000000000..83dbd17f89 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/BasicMessageInterface.php @@ -0,0 +1,134 @@ + + */ + public function getId(): ?string; + + /** + * Get message sender (from headers). + */ + public function getFrom(): ?EmailAddress; + + /** + * Get To recipients. + * + * @return EmailAddress[] Empty array in case message has no To: recipients + */ + public function getTo(): array; + + /** + * Get Cc recipients. + * + * @return EmailAddress[] Empty array in case message has no CC: recipients + */ + public function getCc(): array; + + /** + * Get Bcc recipients. + * + * @return EmailAddress[] Empty array in case message has no BCC: recipients + */ + public function getBcc(): array; + + /** + * Get Reply-To recipients. + * + * @return EmailAddress[] Empty array in case message has no Reply-To: recipients + */ + public function getReplyTo(): array; + + /** + * Get Sender. + * + * @return EmailAddress[] Empty array in case message has no Sender: recipients + */ + public function getSender(): array; + + /** + * Get Return-Path. + * + * @return EmailAddress[] Empty array in case message has no Return-Path: recipients + */ + public function getReturnPath(): array; + + /** + * Get date (from headers). + */ + public function getDate(): ?\DateTimeImmutable; + + /** + * Get message size (from headers). + * + * @return null|int|string + */ + public function getSize(); + + /** + * Get message subject (from headers). + */ + public function getSubject(): ?string; + + /** + * Get message In-Reply-To (from headers). + * + * @return string[] + */ + public function getInReplyTo(): array; + + /** + * Get message References (from headers). + * + * @return string[] + */ + public function getReferences(): array; + + /** + * Get body HTML. + * + * @return null|string Null if message has no HTML message part + */ + public function getBodyHtml(): ?string; + + /** + * Get body text. + */ + public function getBodyText(): ?string; + + /** + * Get attachments (if any) linked to this e-mail. + * + * @return AttachmentInterface[] + */ + public function getAttachments(): array; + + /** + * Does this message have attachments? + */ + public function hasAttachments(): bool; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php new file mode 100644 index 0000000000..4833487dd3 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php @@ -0,0 +1,70 @@ +mailbox = $mailbox; + $this->hostname = $hostname; + $this->name = $name; + $this->address = null; + + if (null !== $hostname) { + $this->address = $mailbox . '@' . $hostname; + } + } + + /** + * @return null|string + */ + public function getAddress() + { + return $this->address; + } + + /** + * Returns address with person name. + */ + public function getFullAddress(): string + { + $address = \sprintf('%s@%s', $this->mailbox, $this->hostname); + if (null !== $this->name) { + $address = \sprintf('"%s" <%s>', \addcslashes($this->name, '"'), $address); + } + + return $address; + } + + public function getMailbox(): string + { + return $this->mailbox; + } + + /** + * @return null|string + */ + public function getHostname() + { + return $this->hostname; + } + + /** + * @return null|string + */ + public function getName() + { + return $this->name; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php new file mode 100644 index 0000000000..20d9715f26 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php @@ -0,0 +1,53 @@ +headers) { + $this->headers = new Headers(\imap_rfc822_parse_headers($this->getRawHeaders())); + } + + return $this->headers; + } + + public function getRawHeaders(): string + { + if (null === $this->rawHeaders) { + $rawHeaders = \explode("\r\n\r\n", $this->getRawMessage(), 2); + $this->rawHeaders = \current($rawHeaders); + } + + return $this->rawHeaders; + } + + public function getRawMessage(): string + { + if (null === $this->rawMessage) { + $this->rawMessage = $this->doGetContent($this->getPartNumber()); + } + + return $this->rawMessage; + } + + /** + * Get content part number. + */ + protected function getContentPartNumber(): string + { + $partNumber = $this->getPartNumber(); + if (0 === \count($this->getParts())) { + $partNumber .= '.1'; + } + + return $partNumber; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessageInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessageInterface.php new file mode 100644 index 0000000000..c685edf94c --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessageInterface.php @@ -0,0 +1,9 @@ + $value) { + $this[$key] = $this->parseHeader($key, $value); + } + } + + /** + * Get header. + * + * @return null|int|\stdClass[]|string + */ + public function get(string $key) + { + return parent::get(\strtolower($key)); + } + + /** + * Parse header. + * + * @param int|\stdClass[]|string $value + * + * @return int|\stdClass[]|string + */ + private function parseHeader(string $key, $value) + { + switch ($key) { + case 'msgno': + \assert(\is_string($value)); + + return (int) $value; + case 'from': + case 'to': + case 'cc': + case 'bcc': + case 'reply_to': + case 'sender': + case 'return_path': + \assert(\is_array($value)); + /** @var \stdClass $address */ + foreach ($value as $address) { + if (isset($address->mailbox)) { + $address->host = $address->host ?? null; + $address->personal = isset($address->personal) ? $this->decode($address->personal) : null; + } + } + + return $value; + case 'date': + case 'subject': + \assert(\is_string($value)); + + return $this->decode($value); + } + + return $value; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php new file mode 100644 index 0000000000..37ed8166a7 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php @@ -0,0 +1,70 @@ + + */ +class Parameters extends \ArrayIterator +{ + /** + * @var array + */ + private static array $attachmentCustomKeys = [ + 'name*' => 'name', + 'filename*' => 'filename', + ]; + + /** + * @param \stdClass[] $parameters + */ + public function add(array $parameters = []): void + { + foreach ($parameters as $parameter) { + $key = \strtolower($parameter->attribute); + if (isset(self::$attachmentCustomKeys[$key])) { + $key = self::$attachmentCustomKeys[$key]; + } + $value = $this->decode($parameter->value); + $this[$key] = $value; + } + } + + /** + * @return null|int|\stdClass[]|string + */ + public function get(string $key) + { + return $this[$key] ?? null; + } + + final protected function decode(string $value): string + { + $parts = \imap_mime_header_decode($value); + if (!\is_array($parts)) { + return $value; + } + + $decoded = ''; + foreach ($parts as $part) { + $text = $part->text; + if ('default' !== $part->charset) { + $text = Transcoder::decode($text, $part->charset); + } + // RFC2231 + if (1 === \preg_match('/^(?[^\']+)\'[^\']*?\'(?.+)$/', $text, $matches)) { + $hasInvalidChars = 1 === \preg_match('#[^%a-zA-Z0-9\-_\.\+]#', $matches['urltext']); + $hasEscapedChars = 1 === \preg_match('#%[a-zA-Z0-9]{2}#', $matches['urltext']); + if (!$hasInvalidChars && $hasEscapedChars) { + $text = Transcoder::decode(\urldecode($matches['urltext']), $matches['encoding']); + } + } + + $decoded .= $text; + } + + return $decoded; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php new file mode 100644 index 0000000000..70a83f2ce4 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php @@ -0,0 +1,112 @@ + 'Shift_JIS', + '129' => 'EUC-KR', + '134' => 'GB2312', + '136' => 'Big5', + '161' => 'windows-1253', + '162' => 'windows-1254', + '177' => 'windows-1255', + '178' => 'windows-1256', + '186' => 'windows-1257', + '204' => 'windows-1251', + '222' => 'windows-874', + '238' => 'windows-1250', + '5601' => 'EUC-KR', + '646' => 'us-ascii', + '850' => 'IBM850', + '852' => 'IBM852', + '855' => 'IBM855', + '857' => 'IBM857', + '862' => 'IBM862', + '864' => 'IBM864', + '864i' => 'IBM864i', + '866' => 'IBM866', + 'ansi-1251' => 'windows-1251', + 'ansi_x3.4-1968' => 'us-ascii', + 'arabic' => 'ISO-8859-6', + 'ascii' => 'us-ascii', + 'asmo-708' => 'ISO-8859-6', + 'big5-hkscs' => 'Big5', + 'chinese' => 'GB2312', + 'cn-big5' => 'Big5', + 'cns11643' => 'x-euc-tw', + 'cp-866' => 'IBM866', + 'cp1250' => 'windows-1250', + 'cp1251' => 'windows-1251', + 'cp1252' => 'windows-1252', + 'cp1253' => 'windows-1253', + 'cp1254' => 'windows-1254', + 'cp1255' => 'windows-1255', + 'cp1256' => 'windows-1256', + 'cp1257' => 'windows-1257', + 'cp1258' => 'windows-1258', + 'cp819' => 'ISO-8859-1', + 'cp850' => 'IBM850', + 'cp852' => 'IBM852', + 'cp855' => 'IBM855', + 'cp857' => 'IBM857', + 'cp862' => 'IBM862', + 'cp864' => 'IBM864', + 'cp864i' => 'IBM864i', + 'cp866' => 'IBM866', + 'cp932' => 'Shift_JIS', + 'csbig5' => 'Big5', + 'cseucjpkdfmtjapanese' => 'EUC-JP', + 'cseuckr' => 'EUC-KR', + 'cseucpkdfmtjapanese' => 'EUC-JP', + 'csgb2312' => 'GB2312', + 'csibm850' => 'IBM850', + 'csibm852' => 'IBM852', + 'csibm855' => 'IBM855', + 'csibm857' => 'IBM857', + 'csibm862' => 'IBM862', + 'csibm864' => 'IBM864', + 'csibm864i' => 'IBM864i', + 'csibm866' => 'IBM866', + 'csiso103t618bit' => 'T.61-8bit', + 'csiso111ecmacyrillic' => 'ISO-IR-111', + 'csiso2022jp' => 'ISO-2022-JP', + 'csiso2022jp2' => 'ISO-2022-JP', + 'csiso2022kr' => 'ISO-2022-KR', + 'csiso58gb231280' => 'GB2312', + 'csiso88596e' => 'ISO-8859-6-E', + 'csiso88596i' => 'ISO-8859-6-I', + 'csiso88598e' => 'ISO-8859-8-E', + 'csiso88598i' => 'ISO-8859-8-I', + 'csisolatin1' => 'ISO-8859-1', + 'csisolatin2' => 'ISO-8859-2', + 'csisolatin3' => 'ISO-8859-3', + 'csisolatin4' => 'ISO-8859-4', + 'csisolatin5' => 'ISO-8859-9', + 'csisolatin6' => 'ISO-8859-10', + 'csisolatin9' => 'ISO-8859-15', + 'csisolatinarabic' => 'ISO-8859-6', + 'csisolatincyrillic' => 'ISO-8859-5', + 'csisolatingreek' => 'ISO-8859-7', + 'csisolatinhebrew' => 'ISO-8859-8', + 'cskoi8r' => 'KOI8-R', + 'csksc56011987' => 'EUC-KR', + 'csmacintosh' => 'x-mac-roman', + 'csshiftjis' => 'Shift_JIS', + 'csueckr' => 'EUC-KR', + 'csunicode' => 'UTF-16BE', + 'csunicode11' => 'UTF-16BE', + 'csunicode11utf7' => 'UTF-7', + 'csunicodeascii' => 'UTF-16BE', + 'csunicodelatin1' => 'UTF-16BE', + 'csviqr' => 'VIQR', + 'csviscii' => 'VISCII', + 'cyrillic' => 'ISO-8859-5', + 'dos-874' => 'windows-874', + 'ecma-114' => 'ISO-8859-6', + 'ecma-118' => 'ISO-8859-7', + 'ecma-cyrillic' => 'ISO-IR-111', + 'elot_928' => 'ISO-8859-7', + 'gb_2312' => 'GB2312', + 'gb_2312-80' => 'GB2312', + 'greek' => 'ISO-8859-7', + 'greek8' => 'ISO-8859-7', + 'hebrew' => 'ISO-8859-8', + 'ibm-864' => 'IBM864', + 'ibm-864i' => 'IBM864i', + 'ibm819' => 'ISO-8859-1', + 'ibm874' => 'windows-874', + 'iso-10646' => 'UTF-16BE', + 'iso-10646-j-1' => 'UTF-16BE', + 'iso-10646-ucs-2' => 'UTF-16BE', + 'iso-10646-ucs-4' => 'UTF-32BE', + 'iso-10646-ucs-basic' => 'UTF-16BE', + 'iso-10646-unicode-latin1' => 'UTF-16BE', + 'iso-2022-cn-ext' => 'ISO-2022-CN', + 'iso-2022-jp-2' => 'ISO-2022-JP', + 'iso-8859-8i' => 'ISO-8859-8-I', + 'iso-ir-100' => 'ISO-8859-1', + 'iso-ir-101' => 'ISO-8859-2', + 'iso-ir-103' => 'T.61-8bit', + 'iso-ir-109' => 'ISO-8859-3', + 'iso-ir-110' => 'ISO-8859-4', + 'iso-ir-126' => 'ISO-8859-7', + 'iso-ir-127' => 'ISO-8859-6', + 'iso-ir-138' => 'ISO-8859-8', + 'iso-ir-144' => 'ISO-8859-5', + 'iso-ir-148' => 'ISO-8859-9', + 'iso-ir-149' => 'EUC-KR', + 'iso-ir-157' => 'ISO-8859-10', + 'iso-ir-58' => 'GB2312', + 'iso8859-1' => 'ISO-8859-1', + 'iso8859-10' => 'ISO-8859-10', + 'iso8859-11' => 'ISO-8859-11', + 'iso8859-13' => 'ISO-8859-13', + 'iso8859-14' => 'ISO-8859-14', + 'iso8859-15' => 'ISO-8859-15', + 'iso8859-2' => 'ISO-8859-2', + 'iso8859-3' => 'ISO-8859-3', + 'iso8859-4' => 'ISO-8859-4', + 'iso8859-5' => 'ISO-8859-5', + 'iso8859-6' => 'ISO-8859-6', + 'iso8859-7' => 'ISO-8859-7', + 'iso8859-8' => 'ISO-8859-8', + 'iso8859-9' => 'ISO-8859-9', + 'iso88591' => 'ISO-8859-1', + 'iso885910' => 'ISO-8859-10', + 'iso885911' => 'ISO-8859-11', + 'iso885912' => 'ISO-8859-12', + 'iso885913' => 'ISO-8859-13', + 'iso885914' => 'ISO-8859-14', + 'iso885915' => 'ISO-8859-15', + 'iso88592' => 'ISO-8859-2', + 'iso88593' => 'ISO-8859-3', + 'iso88594' => 'ISO-8859-4', + 'iso88595' => 'ISO-8859-5', + 'iso88596' => 'ISO-8859-6', + 'iso88597' => 'ISO-8859-7', + 'iso88598' => 'ISO-8859-8', + 'iso88599' => 'ISO-8859-9', + 'iso_8859-1' => 'ISO-8859-1', + 'iso_8859-15' => 'ISO-8859-15', + 'iso_8859-1:1987' => 'ISO-8859-1', + 'iso_8859-2' => 'ISO-8859-2', + 'iso_8859-2:1987' => 'ISO-8859-2', + 'iso_8859-3' => 'ISO-8859-3', + 'iso_8859-3:1988' => 'ISO-8859-3', + 'iso_8859-4' => 'ISO-8859-4', + 'iso_8859-4:1988' => 'ISO-8859-4', + 'iso_8859-5' => 'ISO-8859-5', + 'iso_8859-5:1988' => 'ISO-8859-5', + 'iso_8859-6' => 'ISO-8859-6', + 'iso_8859-6:1987' => 'ISO-8859-6', + 'iso_8859-7' => 'ISO-8859-7', + 'iso_8859-7:1987' => 'ISO-8859-7', + 'iso_8859-8' => 'ISO-8859-8', + 'iso_8859-8:1988' => 'ISO-8859-8', + 'iso_8859-9' => 'ISO-8859-9', + 'iso_8859-9:1989' => 'ISO-8859-9', + 'koi' => 'KOI8-R', + 'koi8' => 'KOI8-R', + 'koi8-ru' => 'KOI8-U', + 'koi8_r' => 'KOI8-R', + 'korean' => 'EUC-KR', + 'ks_c_5601-1987' => 'EUC-KR', + 'ks_c_5601-1989' => 'EUC-KR', + 'ksc5601' => 'EUC-KR', + 'ksc_5601' => 'EUC-KR', + 'l1' => 'ISO-8859-1', + 'l2' => 'ISO-8859-2', + 'l3' => 'ISO-8859-3', + 'l4' => 'ISO-8859-4', + 'l5' => 'ISO-8859-9', + 'l6' => 'ISO-8859-10', + 'l9' => 'ISO-8859-15', + 'latin1' => 'ISO-8859-1', + 'latin2' => 'ISO-8859-2', + 'latin3' => 'ISO-8859-3', + 'latin4' => 'ISO-8859-4', + 'latin5' => 'ISO-8859-9', + 'latin6' => 'ISO-8859-10', + 'logical' => 'ISO-8859-8-I', + 'mac' => 'x-mac-roman', + 'macintosh' => 'x-mac-roman', + 'ms932' => 'Shift_JIS', + 'ms_kanji' => 'Shift_JIS', + 'shift-jis' => 'Shift_JIS', + 'sjis' => 'Shift_JIS', + 'sun_eu_greek' => 'ISO-8859-7', + 't.61' => 'T.61-8bit', + 'tis620' => 'TIS-620', + 'unicode-1-1-utf-7' => 'UTF-7', + 'unicode-1-1-utf-8' => 'UTF-8', + 'unicode-2-0-utf-7' => 'UTF-7', + 'visual' => 'ISO-8859-8', + 'windows-31j' => 'Shift_JIS', + 'windows-949' => 'EUC-KR', + 'x-cp1250' => 'windows-1250', + 'x-cp1251' => 'windows-1251', + 'x-cp1252' => 'windows-1252', + 'x-cp1253' => 'windows-1253', + 'x-cp1254' => 'windows-1254', + 'x-cp1255' => 'windows-1255', + 'x-cp1256' => 'windows-1256', + 'x-cp1257' => 'windows-1257', + 'x-cp1258' => 'windows-1258', + 'x-euc-jp' => 'EUC-JP', + 'x-gbk' => 'gbk', + 'x-iso-10646-ucs-2-be' => 'UTF-16BE', + 'x-iso-10646-ucs-2-le' => 'UTF-16LE', + 'x-iso-10646-ucs-4-be' => 'UTF-32BE', + 'x-iso-10646-ucs-4-le' => 'UTF-32LE', + 'x-mac-ce' => 'windows-1250', + 'x-sjis' => 'Shift_JIS', + 'x-unicode-2-0-utf-7' => 'UTF-7', + 'x-x-big5' => 'Big5', + 'zh_cn.euc' => 'GB2312', + 'zh_tw-big5' => 'Big5', + 'zh_tw-euc' => 'x-euc-tw', + ]; + + /** + * Decode text to UTF-8. + * + * @param string $text Text to decode + * @param string $fromCharset Original charset + */ + public static function decode(string $text, string $fromCharset): string + { + static $utf8Aliases = [ + 'unicode-1-1-utf-8' => true, + 'utf8' => true, + 'utf-8' => true, + 'UTF8' => true, + 'UTF-8' => true, + ]; + + if (isset($utf8Aliases[$fromCharset])) { + return $text; + } + + $originalFromCharset = $fromCharset; + $lowercaseFromCharset = \strtolower($fromCharset); + if (isset(self::CHARSET_ALIASES[$lowercaseFromCharset])) { + $fromCharset = self::CHARSET_ALIASES[$lowercaseFromCharset]; + } + + \set_error_handler(static function (): bool { + return true; + }); + + $iconvDecodedText = \iconv($fromCharset, 'UTF-8', $text); + if (false === $iconvDecodedText) { + $iconvDecodedText = \iconv($originalFromCharset, 'UTF-8', $text); + } + + \restore_error_handler(); + + if (false !== $iconvDecodedText) { + return $iconvDecodedText; + } + + $errorMessage = null; + $errorNumber = 0; + \set_error_handler(static function ($nr, $message) use (&$errorMessage, &$errorNumber): bool { + $errorMessage = $message; + $errorNumber = $nr; + + return true; + }); + + $decodedText = ''; + + try { + $decodedText = \mb_convert_encoding($text, 'UTF-8', $fromCharset); + } catch (\Error $error) { + $errorMessage = $error->getMessage(); + } + + \restore_error_handler(); + + if (null !== $errorMessage) { + throw new UnsupportedCharsetException(\sprintf( + 'Unsupported charset "%s"%s: %s', + $originalFromCharset, + ($fromCharset !== $originalFromCharset) ? \sprintf(' (alias found: "%s")', $fromCharset) : '', + $errorMessage + ), $errorNumber); + } + + return $decodedText; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/MessageInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageInterface.php new file mode 100644 index 0000000000..e8fe5f7617 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageInterface.php @@ -0,0 +1,110 @@ + + */ +final class MessageIterator extends \ArrayIterator implements MessageIteratorInterface +{ + private ImapResourceInterface $resource; + + /** + * Constructor. + * + * @param ImapResourceInterface $resource IMAP resource + * @param int[] $messageNumbers Array of message numbers + */ + public function __construct(ImapResourceInterface $resource, array $messageNumbers) + { + $this->resource = $resource; + + parent::__construct($messageNumbers); + } + + /** + * Get current message. + * + * @return MessageInterface + */ + public function current(): MessageInterface + { + $current = parent::current(); + if (!\is_int($current)) { + throw new Exception\OutOfBoundsException(\sprintf( + 'The current value "%s" isn\'t an integer and doesn\'t represent a message;' + . ' try to cycle this "%s" with a native php function like foreach or with the method getArrayCopy(),' + . ' or check it by calling the methods valid().', + \is_object($current) ? \get_class($current) : \gettype($current), + static::class + )); + } + + return new Message($this->resource, $current); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIteratorInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIteratorInterface.php new file mode 100644 index 0000000000..736084b18e --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIteratorInterface.php @@ -0,0 +1,20 @@ + + */ +interface MessageIteratorInterface extends \Iterator, \Countable +{ + /** + * Get current message. + * + * @return MessageInterface + */ + public function current(): MessageInterface; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractDate.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractDate.php new file mode 100644 index 0000000000..61bd05e7d5 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractDate.php @@ -0,0 +1,47 @@ +date = $date; + $this->dateFormat = $dateFormat; + } + + /** + * Converts the condition to a string that can be sent to the IMAP server. + */ + final public function toString(): string + { + return \sprintf('%s "%s"', $this->getKeyword(), $this->date->format($this->dateFormat)); + } + + /** + * Returns the keyword that the condition represents. + */ + abstract protected function getKeyword(): string; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractText.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractText.php new file mode 100644 index 0000000000..8b605ce224 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractText.php @@ -0,0 +1,40 @@ +text = $text; + } + + /** + * Converts the condition to a string that can be sent to the IMAP server. + */ + final public function toString(): string + { + return \sprintf('%s "%s"', $this->getKeyword(), $this->text); + } + + /** + * Returns the keyword that the condition represents. + */ + abstract protected function getKeyword(): string; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/ConditionInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/ConditionInterface.php new file mode 100644 index 0000000000..3aed28c9cf --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/ConditionInterface.php @@ -0,0 +1,16 @@ +addCondition($condition); + } + } + + /** + * Adds a new condition to the expression. + * + * @param ConditionInterface $condition the condition to be added + * + * @return void + */ + private function addCondition(ConditionInterface $condition) + { + $this->conditions[] = $condition; + } + + /** + * Returns the keyword that the condition represents. + */ + public function toString(): string + { + $conditions = \array_map(static function (ConditionInterface $condition): string { + return $condition->toString(); + }, $this->conditions); + + return \sprintf('( %s )', \implode(' OR ', $conditions)); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/RawExpression.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/RawExpression.php new file mode 100644 index 0000000000..deda65d355 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/RawExpression.php @@ -0,0 +1,29 @@ +expression = $expression; + } + + public function toString(): string + { + return $this->expression; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Deleted.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Deleted.php new file mode 100644 index 0000000000..b74581e03f --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Deleted.php @@ -0,0 +1,22 @@ +conditions[] = $condition; + + return $this; + } + + /** + * Converts the expression to a string that can be sent to the IMAP server. + */ + public function toString(): string + { + $conditions = \array_map(static function (ConditionInterface $condition): string { + return $condition->toString(); + }, $this->conditions); + + return \implode(' ', $conditions); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php new file mode 100644 index 0000000000..41a953d9fc --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php @@ -0,0 +1,126 @@ +hostname = $hostname; + $this->port = $port; + $this->flags = '' !== $flags ? '/' . \ltrim($flags, '/') : ''; + $this->parameters = $parameters; + $this->options = $options; + $this->retries = $retries; + } + + /** + * Authenticate connection. + * + * @param string $username Username + * @param string $password Password + * + * @throws AuthenticationFailedException + */ + public function authenticate(string $username, string $password): ConnectionInterface + { + $errorMessage = null; + $errorNumber = 0; + \set_error_handler(static function ($nr, $message) use (&$errorMessage, &$errorNumber): bool { + $errorMessage = $message; + $errorNumber = $nr; + + return true; + }); + + $resource = \imap_open( + $this->getServerString(), + $username, + $password, + $this->options, + $this->retries, + $this->parameters + ); + + \restore_error_handler(); + + if (false === $resource || null !== $errorMessage) { + throw new AuthenticationFailedException(\sprintf( + 'Authentication failed for user "%s"%s', + $username, + null !== $errorMessage ? ': ' . $errorMessage : '' + ), $errorNumber); + } + + $check = \imap_check($resource); + + if (false === $check) { + throw new ResourceCheckFailureException('Resource check failure'); + } + + $mailbox = $check->Mailbox; + $connection = $mailbox; + $curlyPosition = \strpos($mailbox, '}'); + if (false !== $curlyPosition) { + $connection = \substr($mailbox, 0, $curlyPosition + 1); + } + + // These are necessary to get rid of PHP throwing IMAP errors + \imap_errors(); + \imap_alerts(); + + return new Connection(new ImapResource($resource), $connection); + } + + /** + * Glues hostname, port and flags and returns result. + */ + private function getServerString(): string + { + return \sprintf( + '{%s%s%s}', + $this->hostname, + '' !== $this->port ? ':' . $this->port : '', + $this->flags + ); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/ServerInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/ServerInterface.php new file mode 100644 index 0000000000..f7e95f9685 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/ServerInterface.php @@ -0,0 +1,19 @@ + + */ +final class RawMessageIterator extends \ArrayIterator implements MessageIteratorInterface +{ + /** + * @return MessageInterface + */ + public function current(): MessageInterface + { + return parent::current(); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/.gitattributes b/data/web/inc/lib/vendor/directorytree/ldaprecord/.gitattributes new file mode 100644 index 0000000000..2a0e9d261a --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/.gitattributes @@ -0,0 +1 @@ +tests/ export-ignore diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/FUNDING.yml b/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/FUNDING.yml new file mode 100644 index 0000000000..ad129a4046 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [stevebauman] diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/ISSUE_TEMPLATE/bug_report.md b/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..aad61b9737 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,16 @@ +--- +name: Bug report +about: Create a report to help improve LdapRecord +assignees: '' + +--- + + +**Environment:** + - LDAP Server Type: [e.g. ActiveDirectory / OpenLDAP / FreeIPA] + - PHP Version: [e.g. 8.1 / 8.2] + +**Describe the bug:** diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/ISSUE_TEMPLATE/feature_request.md b/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..68e5bcd78a --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,10 @@ +--- +name: Feature request +about: Suggest an idea for LdapRecord +title: "[Feature]" +labels: enhancement +assignees: '' + +--- + +**Describe the feature you'd like:** diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/ISSUE_TEMPLATE/support---help-request.md b/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/ISSUE_TEMPLATE/support---help-request.md new file mode 100644 index 0000000000..b97a977cb9 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/ISSUE_TEMPLATE/support---help-request.md @@ -0,0 +1,20 @@ +--- +name: Support / help request +about: Request help using LdapRecord (requires sponsorship) +title: "[Support]" +labels: question +assignees: '' + +--- + + + + + + +**Environment:** + - LDAP Server Type: [e.g. ActiveDirectory / OpenLDAP / FreeIPA] + - PHP Version: [e.g. 8.1 / 8.2] diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/workflows/run-cs-fix.yml b/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/workflows/run-cs-fix.yml new file mode 100644 index 0000000000..80cd34d616 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/workflows/run-cs-fix.yml @@ -0,0 +1,34 @@ +name: run-cs-fix + +on: + push: + +jobs: + lint: + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + php: [8.3] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: json, dom, curl, libxml, mbstring + coverage: none + + - name: Install Pint + run: composer global require laravel/pint + + - name: Run Pint + run: pint + + - name: Commit linted files + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "Fix code style" diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/workflows/run-integration-tests.yml b/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/workflows/run-integration-tests.yml new file mode 100644 index 0000000000..de67e91c95 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/workflows/run-integration-tests.yml @@ -0,0 +1,62 @@ +name: run-integration-tests + +on: + push: + pull_request: + schedule: + - cron: "0 0 * * *" + +jobs: + run-tests: + runs-on: ${{ matrix.os }} + + services: + ldap: + image: osixia/openldap:1.4.0 + env: + LDAP_TLS_VERIFY_CLIENT: try + LDAP_OPENLDAP_UID: 1000 + LDAP_OPENLDAP_GID: 1000 + LDAP_ORGANISATION: Local + LDAP_DOMAIN: local.com + LDAP_ADMIN_PASSWORD: secret + ports: + - 389:389 + - 636:636 + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + php: [8.1, 8.2, 8.3] + + name: ${{ matrix.os }} - P${{ matrix.php }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: ~/.composer/cache/files + key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} + + - name: Set ldap.conf file permissions + run: sudo chown -R $USER:$USER /etc/ldap/ldap.conf + + - name: Create ldap.conf file disabling TLS verification + run: sudo echo "TLS_REQCERT never" > "/etc/ldap/ldap.conf" + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: ldap, json + coverage: none + + - name: Install dependencies + run: composer update --prefer-dist --no-interaction + + - name: Execute tests + run: vendor/bin/phpunit --testsuite Integration diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/workflows/run-tests.yml b/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/workflows/run-tests.yml new file mode 100644 index 0000000000..8781e918a4 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/.github/workflows/run-tests.yml @@ -0,0 +1,41 @@ +name: run-tests + +on: + push: + pull_request: + schedule: + - cron: "0 0 * * *" + +jobs: + run-tests: + runs-on: ${{ matrix.os }} + name: ${{ matrix.os }} - P${{ matrix.php }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + php: [8.1, 8.2, 8.3, 8.4] + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: ~/.composer/cache/files + key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: ldap, json + coverage: none + + - name: Install dependencies + run: composer update --prefer-dist --no-interaction + + - name: Execute tests + run: vendor/bin/phpunit --testsuite Unit diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/.gitignore b/data/web/inc/lib/vendor/directorytree/ldaprecord/.gitignore new file mode 100644 index 0000000000..e288894fac --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/.gitignore @@ -0,0 +1,6 @@ +vendor +composer.lock +psalm.phar +.php_cs.cache +.phpunit.result.cache +.php-cs-fixer.cache \ No newline at end of file diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/composer.json b/data/web/inc/lib/vendor/directorytree/ldaprecord/composer.json new file mode 100644 index 0000000000..97e84cd5ec --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/composer.json @@ -0,0 +1,64 @@ +{ + "name": "directorytree/ldaprecord", + "type": "library", + "description": "A fully-featured LDAP ORM.", + "homepage": "https://www.ldaprecord.com", + "keywords": [ + "active directory", + "directory", + "ad", + "ldap", + "windows", + "adldap", + "adldap2", + "ldaprecord", + "orm" + ], + "license": "MIT", + "support": { + "docs": "https://ldaprecord.com", + "issues": "https://github.com/DirectoryTree/LdapRecord/issues", + "source": "https://github.com/DirectoryTree/LdapRecord", + "email": "steven_bauman@outlook.com" + }, + "authors": [ + { + "name": "Steve Bauman", + "email": "steven_bauman@outlook.com", + "role": "Developer" + } + ], + "require": { + "php": ">=8.1", + "ext-ldap": "*", + "ext-json": "*", + "ext-iconv": "*", + "psr/log": "*", + "nesbot/carbon": "*", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/collections": "^8.0|^9.0|^10.0|^11.0|^12.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "mockery/mockery": "^1.0", + "spatie/ray": "^1.24", + "laravel/pint": "^1.6", + "fakerphp/faker": "^1.21" + }, + "archive": { + "exclude": [ + "/tests" + ] + }, + "autoload": { + "psr-4": { + "LdapRecord\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "LdapRecord\\Tests\\": "tests/" + } + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/docker-compose.yml b/data/web/inc/lib/vendor/directorytree/ldaprecord/docker-compose.yml new file mode 100644 index 0000000000..1abf0979d1 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/docker-compose.yml @@ -0,0 +1,33 @@ +services: + ldap: + image: osixia/openldap:1.4.0 + container_name: ldap + restart: always + hostname: local.com + environment: + LDAP_TLS_VERIFY_CLIENT: try + LDAP_OPENLDAP_UID: 1000 + LDAP_OPENLDAP_GID: 1000 + LDAP_ORGANISATION: Local + LDAP_DOMAIN : local.com + LDAP_ADMIN_PASSWORD: secret + ports: + - "389:389" + - "636:636" + networks: + - local + + ldapadmin: + image: osixia/phpldapadmin:0.9.0 + container_name: ldapadmin + environment: + PHPLDAPADMIN_LDAP_HOSTS: ldap + restart: always + ports: + - "6443:443" + networks: + - local + +networks: + local: + driver: bridge diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/license.md b/data/web/inc/lib/vendor/directorytree/ldaprecord/license.md new file mode 100644 index 0000000000..c25dc6032b --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/license.md @@ -0,0 +1,8 @@ +The MIT License (MIT) +Copyright ยฉ Steve Bauman + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the โ€œSoftwareโ€), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED โ€œAS ISโ€, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/phpunit.xml b/data/web/inc/lib/vendor/directorytree/ldaprecord/phpunit.xml new file mode 100644 index 0000000000..aed09e4d6b --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/phpunit.xml @@ -0,0 +1,20 @@ + + + + + ./tests/Unit + + + ./tests/Integration + + + diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/readme.md b/data/web/inc/lib/vendor/directorytree/ldaprecord/readme.md new file mode 100644 index 0000000000..4e34bd7273 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/readme.md @@ -0,0 +1,91 @@ +

    + +

    + +

    + + + + +

    + +

    + Working with LDAP doesn't need to be hard. +

    + +

    + LdapRecord is a fully-featured Active Record + ORM that makes working with LDAP directories a breeze ๐Ÿƒ +

    + +

    + Quickstart + ยท + Documentation + ยท + Laravel Integration + ยท + Post a Question +

    + +--- + +โฒ **Up and Running Fast** + +Connect to your LDAP servers and start running queries in a matter of minutes. + +๐Ÿ’ก **Fluent Filter Builder** + +Find the LDAP objects you're looking for with a fluent LDAP filter builder. + +๐Ÿ’ผ **Multi-Domain Ready** + +Built-in connection management allows you to access multiple domains without breaking a sweat. + +๐Ÿ”ฅ **Supercharged Active Record** + +Create and modify LDAP objects with minimal code. + +--- + +

    +Active Directory Features +

    + +๐Ÿšช **Enable / Disable Accounts** + +Detect and assign User Account Control values on accounts with the fluent [Account Control builder](https://ldaprecord.com/docs/core/v3/active-directory/users/#uac). + +๐Ÿ”‘ **Reset / Change Passwords** + +Built-in support for [changing](https://ldaprecord.com/docs/core/v3/active-directory/users/#changing-passwords) and [resetting](https://ldaprecord.com/docs/core/v3/active-directory/users/#resetting-passwords) passwords on Active Directory accounts. + +๐Ÿ—‘ **Restore Deleted Objects** + +Seamlessly access your Active Directory recycle bin and [restore deleted objects](https://ldaprecord.com/docs/core/v3/models/#restoring-deleted-models). + +--- + +

    LdapRecord is Supportwareโ„ข

    + +

    If you require support using LdapRecord, a sponsorship is required :pray:

    + +

    Thank you for your understanding :heart:

    + +--- + +

    Security Vulnerabilities

    + +

    If you discover a security vulnerability within LdapRecord, please send an e-mail to Steve Bauman via steven_bauman@outlook.com.

    + +

    All security vulnerabilities will be promptly addressed.

    + +--- + +

    Credits

    + +

    This package is directly inspired from Laravel's Eloquent, and most features are direct ports to an LDAP equivalent.

    + +

    I am forever grateful for the work Taylor Otwell has produced.

    + +

    If you can, support his work by purchasing a sponsorship, or one of his many Laravel based services.

    diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/BindException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/BindException.php new file mode 100644 index 0000000000..d87abc1cd0 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/BindException.php @@ -0,0 +1,10 @@ +connection = $connection; + $this->username = $username; + $this->password = $password; + } + + /** + * Get the event's connection. + */ + public function getConnection(): LdapInterface + { + return $this->connection; + } + + /** + * Get the authentication event's username. + */ + public function getUsername(): ?string + { + return $this->username; + } + + /** + * Get the authentication event's password. + */ + public function getPassword(): ?string + { + return $this->password; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Events/Failed.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Events/Failed.php new file mode 100644 index 0000000000..b260491699 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Events/Failed.php @@ -0,0 +1,33 @@ +exception = $exception; + } + + /** + * Get the exception that was thrown during the bind attempt. + */ + public function getException(): BindException + { + return $this->exception; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Events/Passed.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Events/Passed.php new file mode 100644 index 0000000000..2442f3eb8b --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Events/Passed.php @@ -0,0 +1,8 @@ +connection = $connection; + $this->configuration = $configuration; + } + + /** + * Attempt binding a user to the LDAP server. + * + * @throws UsernameRequiredException + * @throws PasswordRequiredException + */ + public function attempt(string $username, string $password, bool $stayBound = false): bool + { + switch (true) { + case empty($username): + throw new UsernameRequiredException('A username must be specified.'); + case empty($password): + throw new PasswordRequiredException('A password must be specified.'); + } + + $this->fireAuthEvent('attempting', $username, $password); + + try { + $this->bind($username, $password); + + $bound = true; + + $this->fireAuthEvent('passed', $username, $password); + } catch (BindException) { + $bound = false; + } + + if (! $stayBound) { + $this->bindAsConfiguredUser(); + } + + return $bound; + } + + /** + * Attempt binding a user to the LDAP server. Supports sasl and anonymous binding. + * + * @throws BindException + * @throws \LdapRecord\ConnectionException + */ + public function bind(?string $username = null, ?string $password = null): void + { + $this->fireAuthEvent('binding', $username, $password); + + // Prior to binding, we will upgrade our connectivity to TLS on our current + // connection and ensure we are not already bound before upgrading. + // This is to prevent subsequent upgrading on several binds. + if ($this->connection->isUsingTLS() && ! $this->connection->isSecure()) { + $this->connection->startTLS(); + } + + try { + if (! $this->authenticate($username, $password)) { + throw new Exception($this->connection->getLastError(), $this->connection->errNo()); + } + + $this->fireAuthEvent('bound', $username, $password); + } catch (Exception $e) { + $exception = BindException::withDetailedError($e, $this->connection->getDetailedError()); + + $this->fireAuthEvent('failed', $username, $password, $exception); + + throw $exception; + } + } + + /** + * Authenticate by binding to the LDAP server. + * + * @throws \LdapRecord\ConnectionException + */ + protected function authenticate(?string $username = null, ?string $password = null): bool + { + if ($this->configuration->get('use_sasl') ?? false) { + return $this->connection->saslBind( + $username, $password, $this->configuration->get('sasl_options') + ); + } + + return $this->connection->bind($username, $password)->successful(); + } + + /** + * Bind to the LDAP server using the configured username and password. + * + * @throws BindException + * @throws \LdapRecord\ConnectionException + * @throws \LdapRecord\Configuration\ConfigurationException + */ + public function bindAsConfiguredUser(): void + { + $this->bind( + $this->configuration->get('username'), + $this->configuration->get('password') + ); + } + + /** + * Get the event dispatcher instance. + */ + public function getDispatcher(): ?DispatcherInterface + { + return $this->events; + } + + /** + * Set the event dispatcher instance. + */ + public function setDispatcher(?DispatcherInterface $dispatcher = null): void + { + $this->events = $dispatcher; + } + + /** + * Fire an authentication event. + */ + protected function fireAuthEvent(string $name, ?string $username = null, ?string $password = null, ...$args): void + { + if (isset($this->events)) { + $event = implode('\\', [Events::class, ucfirst($name)]); + + $this->events->fire(new $event($this->connection, $username, $password, ...$args)); + } + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/PasswordRequiredException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/PasswordRequiredException.php new file mode 100644 index 0000000000..7b2bbd1029 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/PasswordRequiredException.php @@ -0,0 +1,10 @@ + [], + + // The global LDAP operation timeout limit in seconds. + 'timeout' => 5, + + // The LDAP version to utilize. + 'version' => 3, + + // The port to use for connecting to your hosts. + 'port' => LdapInterface::PORT, + + // The protocol to use for connecting to your hosts (ldap:// or ldaps://). + 'protocol' => null, + + // The base distinguished name of your domain. + 'base_dn' => '', + + // The username to use for binding. + 'username' => '', + + // The password to use for binding. + 'password' => '', + + // Whether to use SSL when connecting. + 'use_ssl' => false, + + // Whether to use TLS when connecting. + 'use_tls' => false, + + // Whether to use SASL when connecting. + 'use_sasl' => false, + + // Whether to allow password changes over plaintext. + 'allow_insecure_password_changes' => false, + + // SASL options + 'sasl_options' => [ + 'mech' => null, + 'realm' => null, + 'authc_id' => null, + 'authz_id' => null, + 'props' => null, + ], + + // Whether follow referrals is enabled when performing LDAP operations. + 'follow_referrals' => false, + + // Custom LDAP options. + 'options' => [], + ]; + + /** + * Constructor. + * + * @throws ConfigurationException When an option value given is an invalid type. + */ + public function __construct(array $options = []) + { + $this->options = array_merge($this->options, static::$extended); + + foreach ($options as $key => $value) { + $this->set($key, $value); + } + } + + /** + * Extend the configuration with a custom option, or override an existing. + */ + public static function extend(string $option, mixed $default = null): void + { + static::$extended[$option] = $default; + } + + /** + * Flush the extended configuration options. + */ + public static function flushExtended(): void + { + static::$extended = []; + } + + /** + * Get all configuration options. + */ + public function all(): array + { + return $this->options; + } + + /** + * Set a configuration option. + * + * @throws ConfigurationException When an option value given is an invalid type. + */ + public function set(string $key, mixed $value): void + { + if ($this->validate($key, $value)) { + $this->options[$key] = $value; + } + } + + /** + * Get the value for the specified configuration options. + * + * @throws ConfigurationException When the option specified does not exist. + */ + public function get(string $key): mixed + { + if (! $this->has($key)) { + throw new ConfigurationException("Option $key does not exist."); + } + + return $this->options[$key]; + } + + /** + * Checks if a configuration option exists. + */ + public function has(string $key): bool + { + return array_key_exists($key, $this->options); + } + + /** + * Validate the configuration option. + * + * @throws ConfigurationException When an option value given is an invalid type. + */ + protected function validate(string $key, mixed $value): bool + { + $default = $this->get($key); + + if (is_array($default)) { + $validator = new Validators\ArrayValidator($key, $value); + } elseif (is_int($default)) { + $validator = new Validators\IntegerValidator($key, $value); + } elseif (is_bool($default)) { + $validator = new Validators\BooleanValidator($key, $value); + } else { + $validator = new Validators\StringOrNullValidator($key, $value); + } + + return $validator->validate(); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/ArrayValidator.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/ArrayValidator.php new file mode 100644 index 0000000000..1324d16819 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/ArrayValidator.php @@ -0,0 +1,19 @@ +value); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/BooleanValidator.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/BooleanValidator.php new file mode 100644 index 0000000000..1156859d3a --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/BooleanValidator.php @@ -0,0 +1,19 @@ +value); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/IntegerValidator.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/IntegerValidator.php new file mode 100644 index 0000000000..d7015888e6 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/IntegerValidator.php @@ -0,0 +1,19 @@ +value); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/StringOrNullValidator.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/StringOrNullValidator.php new file mode 100644 index 0000000000..5ca453c74d --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/StringOrNullValidator.php @@ -0,0 +1,19 @@ +value) || is_null($this->value); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/Validator.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/Validator.php new file mode 100644 index 0000000000..25aedcbfa2 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/Validators/Validator.php @@ -0,0 +1,63 @@ +key = $key; + $this->value = $value; + } + + /** + * Determine if the validation rule passes. + */ + abstract public function passes(): bool; + + /** + * Validate the configuration value. + * + * @throws ConfigurationException + */ + public function validate(): bool + { + if (! $this->passes()) { + $this->fail(); + } + + return true; + } + + /** + * Throw a configuration exception. + * + * @throws ConfigurationException + */ + protected function fail(): void + { + throw new ConfigurationException( + str_replace(':option', $this->key, $this->message) + ); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Connection.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Connection.php new file mode 100644 index 0000000000..e04393a27c --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Connection.php @@ -0,0 +1,451 @@ +setConfiguration($config); + + $this->setLdapConnection($ldap ?? new Ldap); + + $this->failed = function () { + $this->dispatch(new Events\ConnectionFailed($this)); + }; + + $this->authGuardResolver = function () { + return new Guard($this->ldap, $this->configuration); + }; + } + + /** + * Set the connection configuration. + * + * @throws Configuration\ConfigurationException + */ + public function setConfiguration(DomainConfiguration|array $config = []): void + { + if (! $config instanceof DomainConfiguration) { + $config = new DomainConfiguration($config); + } + + $this->configuration = $config; + + $this->hosts = $this->configuration->get('hosts'); + + $this->host = reset($this->hosts); + } + + /** + * Set the LDAP connection. + */ + public function setLdapConnection(LdapInterface $ldap): void + { + $this->ldap = $ldap; + } + + /** + * Set the event dispatcher. + */ + public function setDispatcher(DispatcherInterface $dispatcher): void + { + $this->dispatcher = $dispatcher; + } + + /** + * Get the event dispatcher. + */ + public function getDispatcher(): ?DispatcherInterface + { + return $this->dispatcher; + } + + /** + * Initialize the LDAP connection. + */ + public function initialize(): void + { + $this->configure(); + + $this->ldap->connect( + $this->host, + $this->configuration->get('port'), + $this->configuration->get('protocol') + ); + } + + /** + * Configure the LDAP connection. + */ + protected function configure(): void + { + if ($this->configuration->get('use_ssl')) { + $this->ldap->ssl(); + } elseif ($this->configuration->get('use_tls')) { + $this->ldap->tls(); + } + + $this->ldap->setOptions(array_replace( + $this->configuration->get('options'), + [ + LDAP_OPT_PROTOCOL_VERSION => $this->configuration->get('version'), + LDAP_OPT_NETWORK_TIMEOUT => $this->configuration->get('timeout'), + LDAP_OPT_REFERRALS => $this->configuration->get('follow_referrals'), + ] + )); + } + + /** + * Set the cache store. + */ + public function setCache(CacheInterface $store): void + { + $this->cache = new Cache($store); + } + + /** + * Get the cache store. + */ + public function getCache(): ?Cache + { + return $this->cache; + } + + /** + * Get the LDAP configuration instance. + */ + public function getConfiguration(): DomainConfiguration + { + return $this->configuration; + } + + /** + * Get the LDAP connection instance. + */ + public function getLdapConnection(): LdapInterface + { + return $this->ldap; + } + + /** + * Set the auth guard resolver callback. + */ + public function setGuardResolver(Closure $callback): void + { + $this->authGuardResolver = $callback; + } + + /** + * Bind to the LDAP server. + * + * If no username or password is specified, then the configured credentials are used. + * + * @throws Auth\BindException + * @throws LdapRecordException + */ + public function connect(?string $username = null, ?string $password = null): void + { + $attempt = function () use ($username, $password) { + $this->dispatch(new Events\Connecting($this)); + + is_null($username) && is_null($password) + ? $this->auth()->bindAsConfiguredUser() + : $this->auth()->bind($username, $password); + + $this->dispatch(new Events\Connected($this)); + + $this->retryingInitialConnection = false; + }; + + try { + $this->runOperationCallback($attempt); + } catch (LdapRecordException $e) { + $this->retryingInitialConnection = true; + + $this->retryOnNextHost($e, $attempt); + } + } + + /** + * Reconnect to the LDAP server. + * + * @throws Auth\BindException + * @throws ConnectionException + * @throws LdapRecordException + */ + public function reconnect(): void + { + $this->reinitialize(); + + $this->connect(); + } + + /** + * Reinitialize the connection. + */ + protected function reinitialize(): void + { + $this->disconnect(); + + $this->initialize(); + } + + /** + * Clone the connection. + */ + public function replicate(): static + { + return new static($this->configuration, new $this->ldap); + } + + /** + * Disconnect from the LDAP server. + */ + public function disconnect(): void + { + $this->ldap->close(); + } + + /** + * Dispatch an event. + */ + public function dispatch(object $event): void + { + if (isset($this->dispatcher)) { + $this->dispatcher->dispatch($event); + } + } + + /** + * Get the attempted hosts that failed connecting to. + */ + public function attempted(): array + { + return $this->attempted; + } + + /** + * Perform the operation on the LDAP connection. + */ + public function run(Closure $operation): mixed + { + try { + // Before running the operation, we will check if the current + // connection is bound and connect if necessary. Otherwise, + // some LDAP operations will not be executed properly. + if (! $this->isConnected()) { + $this->connect(); + } + + return $this->runOperationCallback($operation); + } catch (LdapRecordException $e) { + if ($exception = $this->getExceptionForCauseOfFailure($e)) { + throw $exception; + } + + return $this->tryAgainIfCausedByLostConnection($e, $operation); + } + } + + /** + * Perform the operation on an isolated LDAP connection. + */ + public function isolate(Closure $operation): mixed + { + $connection = $this->replicate(); + + try { + return $operation($connection); + } finally { + $connection->disconnect(); + } + } + + /** + * Attempt to get an exception for the cause of failure. + */ + protected function getExceptionForCauseOfFailure(LdapRecordException $e): ?LdapRecordException + { + switch (true) { + case $this->errorContainsMessage($e->getMessage(), 'Already exists'): + return Exceptions\AlreadyExistsException::withDetailedError($e, $e->getDetailedError()); + case $this->errorContainsMessage($e->getMessage(), 'Insufficient access'): + return Exceptions\InsufficientAccessException::withDetailedError($e, $e->getDetailedError()); + case $this->errorContainsMessage($e->getMessage(), 'Constraint violation'): + return Exceptions\ConstraintViolationException::withDetailedError($e, $e->getDetailedError()); + default: + return null; + } + } + + /** + * Run the operation callback on the current LDAP connection. + */ + protected function runOperationCallback(Closure $operation): mixed + { + return $operation($this->ldap); + } + + /** + * Get a new auth guard instance. + */ + public function auth(): Guard + { + if (! $this->ldap->isConnected()) { + $this->initialize(); + } + + $guard = call_user_func($this->authGuardResolver); + + $guard->setDispatcher( + Container::getInstance()->getDispatcher() + ); + + return $guard; + } + + /** + * Get a new query builder for the connection. + */ + public function query(): Builder + { + return (new Builder($this)) + ->setCache($this->cache) + ->setBaseDn($this->configuration->get('base_dn')); + } + + /** + * Determine if the LDAP connection is bound. + */ + public function isConnected(): bool + { + return $this->ldap->isBound(); + } + + /** + * Attempt to retry an LDAP operation if due to a lost connection. + * + * @throws LdapRecordException + */ + protected function tryAgainIfCausedByLostConnection(LdapRecordException $e, Closure $operation): mixed + { + // If the operation failed due to a lost or failed connection, + // we'll attempt reconnecting and running the operation again + // underneath the same host, and then move onto the next. + if ($this->causedByLostConnection($e->getMessage())) { + return $this->retry($operation); + } + + throw $e; + } + + /** + * Retry the operation on the current host. + * + * @throws LdapRecordException + */ + protected function retry(Closure $operation): mixed + { + try { + $this->retryingInitialConnection + ? $this->reinitialize() + : $this->reconnect(); + + return $this->runOperationCallback($operation); + } catch (LdapRecordException $e) { + return $this->retryOnNextHost($e, $operation); + } + } + + /** + * Attempt the operation again on the next host. + * + * @throws LdapRecordException + */ + protected function retryOnNextHost(LdapRecordException $e, Closure $operation): mixed + { + $this->attempted[$this->host] = Carbon::now(); + + if (($key = array_search($this->host, $this->hosts)) !== false) { + unset($this->hosts[$key]); + } + + if ($next = reset($this->hosts)) { + $this->host = $next; + + return $this->tryAgainIfCausedByLostConnection($e, $operation); + } + + call_user_func($this->failed, $this->ldap); + + throw $e; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/ConnectionException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/ConnectionException.php new file mode 100644 index 0000000000..81691bbd93 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/ConnectionException.php @@ -0,0 +1,8 @@ +dispatcher = $dispatcher; + } + + /** + * Add a new connection. + */ + public function addConnection(Connection $connection, ?string $name = null): void + { + $this->connections[$name ?? $this->default] = $connection; + + if ($this->dispatcher) { + $connection->setDispatcher($this->dispatcher); + } + } + + /** + * Remove a connection by its name. + */ + public function removeConnection(string $name): void + { + unset($this->connections[$name]); + } + + /** + * Get all the registered connections. + * + * @return Connection[] + */ + public function getConnections(): array + { + return $this->connections; + } + + /** + * Get a connection by its name or return the default. + * + * @throws ContainerException If the given connection does not exist. + */ + public function getConnection(?string $name = null): Connection + { + if ($this->hasConnection($name = $name ?? $this->default)) { + return $this->connections[$name]; + } + + throw new ContainerException("The LDAP connection [$name] does not exist."); + } + + /** + * Get the default connection. + */ + public function getDefaultConnection(): Connection + { + return $this->getConnection($this->default); + } + + /** + * Set the default connection by its name. + */ + public function setDefaultConnection(string $name): void + { + $this->default = $name; + } + + /** + * Get the default connection name. + */ + public function getDefaultConnectionName(): string + { + return $this->default; + } + + /** + * Determine if a connection exists. + */ + public function hasConnection(string $name): bool + { + return array_key_exists($name, $this->connections); + } + + /** + * Flush the manager of all instances and connections. + */ + public function flush(): void + { + $this->logger = null; + + $this->connections = []; + + $this->dispatcher->forgetAll(); + } + + /** + * Get the logger instance. + */ + public function getLogger(): ?LoggerInterface + { + return $this->logger; + } + + /** + * Set the event logger to use. + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + + $this->initEventLogger(); + } + + /** + * Initialize the event logger. + */ + protected function initEventLogger(): void + { + $logger = $this->newEventLogger(); + + foreach ($this->listen as $event) { + $this->dispatcher->listen($event, function ($eventName, $events) use ($logger) { + foreach ($events as $event) { + $logger->log($event); + } + }); + } + } + + /** + * Make a new event logger instance. + */ + protected function newEventLogger(): Logger + { + return new Logger($this->logger); + } + + /** + * Unset the logger instance. + */ + public function unsetLogger(): void + { + $this->logger = null; + } + + /** + * Get the event dispatcher. + */ + public function getDispatcher(): ?DispatcherInterface + { + return $this->dispatcher; + } + + /** + * Set the event dispatcher. + */ + public function setDispatcher(DispatcherInterface $dispatcher): void + { + $this->dispatcher = $dispatcher; + } + + /** + * Unset the event dispatcher. + */ + public function unsetDispatcher(): void + { + $this->dispatcher = null; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Container.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Container.php new file mode 100644 index 0000000000..5a3bd7f4c6 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Container.php @@ -0,0 +1,81 @@ +{$method}(...$parameters); + } + + /** + * Constructor. + */ + public function __construct(ConnectionManager $manager = new ConnectionManager) + { + $this->manager = $manager; + } + + /** + * Forward missing method calls onto the connection manager. + */ + public function __call(string $method, array $parameters): mixed + { + return $this->manager->{$method}(...$parameters); + } + + /** + * Set the current container instance available globally. + */ + public function setAsGlobal(): void + { + static::setInstance($this); + } + + /** + * Get the connection manager. + */ + public function getConnectionManager(): ConnectionManager + { + return $this->manager; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/ContainerException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/ContainerException.php new file mode 100644 index 0000000000..0ab29cf0f6 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/ContainerException.php @@ -0,0 +1,8 @@ +errorCode; + } + + /** + * Get the LDAP error message. + */ + public function getErrorMessage(): string + { + return $this->errorMessage; + } + + /** + * Get the LDAP diagnostic message. + */ + public function getDiagnosticMessage(): ?string + { + return $this->diagnosticMessage; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/DetectsErrors.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/DetectsErrors.php new file mode 100644 index 0000000000..bb6f675f01 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/DetectsErrors.php @@ -0,0 +1,52 @@ +errorContainsMessage($error, ["Can't contact LDAP server", 'Operations error']); + } + + /** + * Determine if the error was caused by lack of pagination support. + */ + protected function causedByPaginationSupport(string $error): bool + { + return $this->errorContainsMessage($error, 'No server controls in result'); + } + + /** + * Determine if the error was caused by a size limit warning. + */ + protected function causedBySizeLimit(string $error): bool + { + return $this->errorContainsMessage($error, ['Partial search results returned', 'Size limit exceeded']); + } + + /** + * Determine if the error was caused by a "No such object" warning. + */ + protected function causedByNoSuchObject(string $error): bool + { + return $this->errorContainsMessage($error, ['No such object']); + } + + /** + * Determine if the error contains any of the messages. + */ + protected function errorContainsMessage(string $error, string|array $messages = []): bool + { + foreach ((array) $messages as $message) { + if (str_contains($error, $message)) { + return true; + } + } + + return false; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/EscapesValues.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/EscapesValues.php new file mode 100644 index 0000000000..088b50013a --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/EscapesValues.php @@ -0,0 +1,16 @@ +connection = $connection; + } + + /** + * Get the connection pertaining to the event. + */ + public function getConnection(): Connection + { + return $this->connection; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/ConnectionFailed.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/ConnectionFailed.php new file mode 100644 index 0000000000..7e110c1f1b --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/ConnectionFailed.php @@ -0,0 +1,8 @@ +setupWildcardListen($event, $listener); + } else { + $this->listeners[$event][] = $this->makeListener($listener); + } + } + } + + /** + * Setup a wildcard listener callback. + */ + protected function setupWildcardListen(string $event, mixed $listener): void + { + $this->wildcards[$event][] = $this->makeListener($listener, true); + + $this->wildcardsCache = []; + } + + /** + * {@inheritdoc} + */ + public function hasListeners(string $event): bool + { + return isset($this->listeners[$event]) || isset($this->wildcards[$event]); + } + + /** + * {@inheritdoc} + */ + public function until(string|object $event, mixed $payload = []): mixed + { + return $this->dispatch($event, $payload, true); + } + + /** + * {@inheritdoc} + */ + public function fire(string|object $event, mixed $payload = [], bool $halt = false): void + { + $this->dispatch($event, $payload, $halt); + } + + /** + * {@inheritdoc} + */ + public function dispatch(string|object $event, mixed $payload = [], $halt = false): mixed + { + // When the given "event" is actually an object we will assume it is an event + // object and use the class as the event name and this event itself as the + // payload to the handler, which makes object based events quite simple. + [$event, $payload] = $this->parseEventAndPayload( + $event, + $payload + ); + + $responses = []; + + foreach ($this->getListeners($event) as $listener) { + $response = $listener($event, $payload); + + // If a response is returned from the listener and event halting is enabled + // we will just return this response, and not call the rest of the event + // listeners. Otherwise we will add the response on the response list. + if ($halt && ! is_null($response)) { + return $response; + } + + // If a boolean false is returned from a listener, we will stop propagating + // the event to any further listeners down in the chain, else we keep on + // looping through the listeners and firing every one in our sequence. + if ($response === false) { + break; + } + + $responses[] = $response; + } + + return $halt ? null : $responses; + } + + /** + * Parse the given event and payload and prepare them for dispatching. + */ + protected function parseEventAndPayload(string|object $event, mixed $payload): array + { + if (is_object($event)) { + [$payload, $event] = [[$event], get_class($event)]; + } + + return [$event, Arr::wrap($payload)]; + } + + /** + * {@inheritdoc} + */ + public function getListeners(string $event): array + { + $listeners = $this->listeners[$event] ?? []; + + $listeners = array_merge( + $listeners, + $this->wildcardsCache[$event] ?? $this->getWildcardListeners($event) + ); + + return class_exists($event, false) + ? $this->addInterfaceListeners($event, $listeners) + : $listeners; + } + + /** + * Get the wildcard listeners for the event. + */ + protected function getWildcardListeners(string $event): array + { + $wildcards = []; + + foreach ($this->wildcards as $key => $listeners) { + if (Str::is($key, $event)) { + $wildcards = array_merge($wildcards, $listeners); + } + } + + return $this->wildcardsCache[$event] = $wildcards; + } + + /** + * Add the listeners for the event's interfaces to the given array. + */ + protected function addInterfaceListeners(string $eventName, array $listeners = []): array + { + foreach (class_implements($eventName) as $interface) { + if (isset($this->listeners[$interface])) { + foreach ($this->listeners[$interface] as $names) { + $listeners = array_merge($listeners, (array) $names); + } + } + } + + return $listeners; + } + + /** + * Register an event listener with the dispatcher. + */ + public function makeListener(Closure|string $listener, bool $wildcard = false): Closure + { + if (is_string($listener)) { + return $this->createClassListener($listener, $wildcard); + } + + return function ($event, $payload) use ($listener, $wildcard) { + if ($wildcard) { + return $listener($event, $payload); + } + + return $listener(...array_values($payload)); + }; + } + + /** + * Create a class based listener. + */ + protected function createClassListener(string $listener, bool $wildcard = false): Closure + { + return function ($event, $payload) use ($listener, $wildcard) { + if ($wildcard) { + return call_user_func($this->createClassCallable($listener), $event, $payload); + } + + return call_user_func_array( + $this->createClassCallable($listener), + $payload + ); + }; + } + + /** + * Create the class based event callable. + */ + protected function createClassCallable(string $listener): callable + { + [$class, $method] = $this->parseListenerCallback($listener); + + return [new $class, $method]; + } + + /** + * Parse the class listener into class and method. + */ + protected function parseListenerCallback(string $listener): array + { + return str_contains($listener, '@') + ? explode('@', $listener, 2) + : [$listener, 'handle']; + } + + /** + * {@inheritdoc} + */ + public function forget(string $event): void + { + if (str_contains($event, '*')) { + unset($this->wildcards[$event]); + } else { + unset($this->listeners[$event]); + } + + foreach ($this->wildcardsCache as $key => $listeners) { + if (Str::is($event, $key)) { + unset($this->wildcardsCache[$key]); + } + } + } + + /** + * Remove all the listeners from the dispatcher. + */ + public function forgetAll(): void + { + $listeners = array_merge( + $this->listeners, $this->wildcards + ); + + foreach (array_keys($listeners) as $listener) { + $this->forget($listener); + } + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/DispatcherInterface.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/DispatcherInterface.php new file mode 100644 index 0000000000..71de892c00 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/DispatcherInterface.php @@ -0,0 +1,46 @@ +logger = $logger; + } + + /** + * Logs the given event. + */ + public function log($event): void + { + switch (true) { + case $event instanceof AuthEvent: + $this->auth($event); + break; + case $event instanceof ModelEvent: + $this->model($event); + break; + case $event instanceof QueryEvent: + $this->query($event); + break; + } + } + + /** + * Logs an authentication event. + */ + public function auth(AuthEvent $event): void + { + if (isset($this->logger)) { + $connection = $event->getConnection(); + + $message = "LDAP ({$connection->getHost()})" + ." - Operation: {$this->getOperationName($event)}" + ." - Username: {$event->getUsername()}"; + + $result = null; + $type = 'info'; + + if (is_a($event, Failed::class)) { + $type = 'warning'; + $result = " - Reason: {$connection->getLastError()}"; + } + + $this->logger->$type($message.$result); + } + } + + /** + * Logs a model event. + */ + public function model(ModelEvent $event): void + { + if (isset($this->logger)) { + $model = $event->getModel(); + + $on = get_class($model); + + $connection = $model->getConnection()->getLdapConnection(); + + $message = "LDAP ({$connection->getHost()})" + ." - Operation: {$this->getOperationName($event)}" + ." - On: {$on}" + ." - Distinguished Name: {$model->getDn()}"; + + $this->logger->info($message); + } + } + + /** + * Logs a query event. + */ + public function query(QueryEvent $event): void + { + if (isset($this->logger)) { + $query = $event->getQuery(); + + $connection = $query->getConnection()->getLdapConnection(); + + $selected = implode(',', $query->getSelects()); + + $message = "LDAP ({$connection->getHost()})" + ." - Operation: {$this->getOperationName($event)}" + ." - Base DN: {$query->getBaseDn()}" + ." - Filter: {$query->getQuery()}" + ." - Selected: ({$selected})" + ." - Time Elapsed: {$event->getTime()}"; + + $this->logger->info($message); + } + } + + /** + * Get the operational name of the given event. + */ + protected function getOperationName($event): string + { + return (new ReflectionClass($event))->getShortName(); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/NullDispatcher.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/NullDispatcher.php new file mode 100644 index 0000000000..31f95cb77e --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/NullDispatcher.php @@ -0,0 +1,80 @@ +dispatcher = $dispatcher; + } + + /** + * Register an event listener with the dispatcher. + */ + public function listen(string|array $events, mixed $listener): void + { + $this->dispatcher->listen($events, $listener); + } + + /** + * Determine if a given event has listeners. + */ + public function hasListeners(string $event): bool + { + return $this->dispatcher->hasListeners($event); + } + + /** + * Fire an event until the first non-null response is returned. + */ + public function until(string|object $event, mixed $payload = []): ?array + { + return null; + } + + /** + * Fire an event and call the listeners. + */ + public function fire(string|object $event, mixed $payload = [], bool $halt = false): void {} + + /** + * Fire an event and call the listeners. + */ + public function dispatch(string|object $event, mixed $payload = [], $halt = false): mixed + { + return null; + } + + /** + * Get all the listeners for a given event name. + */ + public function getListeners(string $event): array + { + return $this->dispatcher->getListeners($event); + } + + /** + * Remove a set of listeners from the dispatcher. + */ + public function forget(string $event): void + { + $this->dispatcher->forget($event); + } + + /** + * Remove all the listeners from the dispatcher. + */ + public function forgetAll(): void + { + $this->dispatcher->forgetAll(); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Exceptions/AlreadyExistsException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Exceptions/AlreadyExistsException.php new file mode 100644 index 0000000000..2298caff10 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Exceptions/AlreadyExistsException.php @@ -0,0 +1,10 @@ +detailedError) && $this->errorContainsMessage($this->detailedError->getDiagnosticMessage(), '0000052D'); + } + + /** + * Determine if the exception was generated due to an incorrect password. + */ + public function causedByIncorrectPassword(): bool + { + return isset($this->detailedError) && $this->errorContainsMessage($this->detailedError->getDiagnosticMessage(), '00000056'); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Exceptions/InsufficientAccessException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Exceptions/InsufficientAccessException.php new file mode 100644 index 0000000000..89c55fda11 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Exceptions/InsufficientAccessException.php @@ -0,0 +1,10 @@ +useSSL; + } + + /** + * {@inheritdoc} + */ + public function isUsingTLS(): bool + { + return $this->useTLS; + } + + /** + * {@inheritdoc} + */ + public function isBound(): bool + { + return $this->bound; + } + + /** + * {@inheritdoc} + */ + public function isSecure(): bool + { + return $this->secure; + } + + /** + * {@inheritdoc} + */ + public function isConnected(): bool + { + return ! is_null($this->connection); + } + + /** + * {@inheritdoc} + */ + public function canChangePasswords(): bool + { + return $this->isUsingSSL() || $this->isUsingTLS(); + } + + /** + * {@inheritdoc} + */ + public function ssl(bool $enabled = true): static + { + $this->useSSL = $enabled; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function tls(bool $enabled = true): static + { + $this->useTLS = $enabled; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options = []): void + { + foreach ($options as $option => $value) { + $this->setOption($option, $value); + } + } + + /** + * {@inheritdoc} + */ + public function getHost(): ?string + { + return $this->host; + } + + /** + * {@inheritdoc} + */ + public function getConnection(): ?Connection + { + return $this->connection; + } + + /** + * {@inheritdoc} + */ + public function getProtocol(): string + { + return $this->protocol ?: ( + $this->isUsingSSL() + ? LdapInterface::PROTOCOL_SSL + : LdapInterface::PROTOCOL + ); + } + + /** + * {@inheritdoc} + */ + public function getExtendedError(): ?string + { + return $this->getDiagnosticMessage(); + } + + /** + * Handle the bind response. + */ + protected function handleBindResponse(LdapResultResponse $response): void + { + $this->bound = $response->successful(); + + $this->secure = $this->secure ?: $this->bound && $this->isUsingSSL(); + } + + /** + * Convert warnings to exceptions for the given operation. + * + * @throws LdapRecordException + */ + protected function executeFailableOperation(Closure $operation): mixed + { + // If some older versions of PHP, errors are reported instead of throwing + // exceptions, which could be a significant detriment to our application. + // Here, we will enforce these operations to throw exceptions instead. + set_error_handler(function (int $severity, string $message, string $file, int $line): bool { + if (! $this->shouldBypassError($message)) { + throw new ErrorException($message, $severity, $severity, $file, $line); + } + + return true; + }); + + try { + if (($result = $operation()) !== false) { + return $result; + } + + // If the failed query operation was a based on a query being executed + // -- such as a search, read, or list, then we can safely return + // the failed response here and prevent throwing an exception. + if ($this->shouldBypassFailure($method = debug_backtrace()[1]['function'])) { + return $result; + } + + throw new Exception("LDAP operation [$method] failed."); + } catch (ErrorException $e) { + throw LdapRecordException::withDetailedError($e, $this->getDetailedError()); + } finally { + restore_error_handler(); + } + } + + /** + * Determine if the failed operation should be bypassed. + */ + protected function shouldBypassFailure(string $method): bool + { + return in_array($method, ['search', 'read', 'list']); + } + + /** + * Determine if the error should be bypassed. + */ + protected function shouldBypassError(string $error): bool + { + return $this->causedByPaginationSupport($error) || $this->causedBySizeLimit($error) || $this->causedByNoSuchObject($error); + } + + /** + * Generates an LDAP connection string for each host given. + */ + protected function makeConnectionUris(array|string $hosts, string|int $port): string + { + // If an attempt to connect via SSL protocol is being performed, + // and we are still using the default port, we will swap it + // for the default SSL port, for developer convenience. + if ($this->isUsingSSL() && $port == LdapInterface::PORT) { + $port = LdapInterface::PORT_SSL; + } + + // The blank space here is intentional. PHP's LDAP extension + // requires additional hosts to be seperated by a blank + // space, so that it can parse each individually. + return implode(' ', $this->assembleHostUris($hosts, $port)); + } + + /** + * Assemble the host URI strings. + */ + protected function assembleHostUris(array|string $hosts, string|int $port): array + { + return array_map(fn ($host) => "{$this->getProtocol()}{$host}:{$port}", (array) $hosts); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Ldap.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Ldap.php new file mode 100644 index 0000000000..7f2be60b66 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Ldap.php @@ -0,0 +1,462 @@ +executeFailableOperation(function () use ($result) { + return ldap_get_entries($this->connection, $result); + }); + } + + /** + * Retrieves the first entry from a search result. + * + * @see http://php.net/manual/en/function.ldap-first-entry.php + * + * @param \Ldap\Result $result + */ + public function getFirstEntry(mixed $result): mixed + { + return $this->executeFailableOperation(function () use ($result) { + return ldap_first_entry($this->connection, $result); + }); + } + + /** + * Retrieves the next entry from a search result. + * + * @see http://php.net/manual/en/function.ldap-next-entry.php + * + * @param \Ldap\ResultEntry $entry + */ + public function getNextEntry(mixed $entry): mixed + { + return $this->executeFailableOperation(function () use ($entry) { + return ldap_next_entry($this->connection, $entry); + }); + } + + /** + * Retrieves the ldap entry's attributes. + * + * @see http://php.net/manual/en/function.ldap-get-attributes.php + * + * @param \Ldap\ResultEntry $entry + */ + public function getAttributes(mixed $entry): array|false + { + return $this->executeFailableOperation(function () use ($entry) { + return ldap_get_attributes($this->connection, $entry); + }); + } + + /** + * {@inheritDoc} + */ + public function countEntries(mixed $result): int + { + return $this->executeFailableOperation(function () use ($result) { + return ldap_count_entries($this->connection, $result); + }); + } + + /** + * {@inheritDoc} + */ + public function compare(string $dn, string $attribute, string $value, ?array $controls = null): bool|int + { + return $this->executeFailableOperation(function () use ($dn, $attribute, $value, $controls) { + return ldap_compare($this->connection, $dn, $attribute, $value, $controls); + }); + } + + /** + * {@inheritdoc} + */ + public function getLastError(): ?string + { + if (! $this->connection) { + return null; + } + + return ldap_error($this->connection); + } + + /** + * {@inheritdoc} + */ + public function getDetailedError(): ?DetailedError + { + if (! $number = $this->errNo()) { + return null; + } + + $this->getOption(LDAP_OPT_DIAGNOSTIC_MESSAGE, $message); + + return new DetailedError($number, $this->err2Str($number), $message); + } + + /** + * {@inheritDoc} + */ + public function getValuesLen(mixed $entry, string $attribute): array|false + { + return $this->executeFailableOperation(function () use ($entry, $attribute) { + return ldap_get_values_len($this->connection, $entry, $attribute); + }); + } + + /** + * {@inheritdoc} + */ + public function setOption(int $option, mixed $value): bool + { + return ldap_set_option($this->connection, $option, $value); + } + + /** + * {@inheritdoc} + */ + public function getOption(int $option, mixed &$value = null): mixed + { + ldap_get_option($this->connection, $option, $value); + + return $value; + } + + /** + * Set a callback function to do re-binds on referral chasing. + * + * @see http://php.net/manual/en/function.ldap-set-rebind-proc.php + */ + public function setRebindCallback(callable $callback): bool + { + return ldap_set_rebind_proc($this->connection, $callback); + } + + /** + * {@inheritdoc} + */ + public function startTLS(): bool + { + return $this->secure = $this->executeFailableOperation(function () { + return ldap_start_tls($this->connection); + }); + } + + /** + * {@inheritdoc} + */ + public function connect(string|array $hosts = [], int $port = 389, ?string $protocol = null): bool + { + $this->bound = false; + $this->protocol = $protocol; + $this->host = $this->makeConnectionUris($hosts, $port); + + $this->connection = $this->executeFailableOperation(function () { + return ldap_connect($this->host); + }); + + return $this->connection instanceof RawLdapConnection; + } + + /** + * {@inheritdoc} + */ + public function close(): bool + { + $result = false; + + if ($this->connection instanceof RawLdapConnection) { + $result = @ldap_close($this->connection); + } + + $this->bound = false; + $this->secure = false; + + $this->host = null; + $this->protocol = null; + $this->connection = null; + + return $result; + } + + /** + * {@inheritdoc} + */ + public function search(string $dn, string $filter, array $fields, bool $onlyAttributes = false, int $size = 0, int $time = 0, int $deref = LDAP_DEREF_NEVER, ?array $controls = null): mixed + { + return $this->executeFailableOperation(function () use ( + $dn, + $filter, + $fields, + $onlyAttributes, + $size, + $time, + $deref, + $controls + ) { + return ldap_search($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time, $deref, $controls); + }); + } + + /** + * {@inheritdoc} + */ + public function list(string $dn, string $filter, array $fields, bool $onlyAttributes = false, int $size = 0, int $time = 0, int $deref = LDAP_DEREF_NEVER, ?array $controls = null): mixed + { + return $this->executeFailableOperation(function () use ( + $dn, + $filter, + $fields, + $onlyAttributes, + $size, + $time, + $deref, + $controls + ) { + return ldap_list($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time, $deref, $controls); + }); + } + + /** + * {@inheritdoc} + */ + public function read(string $dn, string $filter, array $fields, bool $onlyAttributes = false, int $size = 0, int $time = 0, int $deref = LDAP_DEREF_NEVER, ?array $controls = null): mixed + { + return $this->executeFailableOperation(function () use ( + $dn, + $filter, + $fields, + $onlyAttributes, + $size, + $time, + $deref, + $controls + ) { + return ldap_read($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time, $deref, $controls); + }); + } + + /** + * {@inheritdoc} + */ + public function parseResult(mixed $result, int &$errorCode = 0, ?string &$dn = null, ?string &$errorMessage = null, ?array &$referrals = null, ?array &$controls = null): LdapResultResponse|false + { + if (ldap_parse_result($this->connection, $result, $errorCode, $dn, $errorMessage, $referrals, $controls)) { + return new LdapResultResponse( + $errorCode, + $dn, + $errorMessage, + $referrals, + $controls + ); + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function bind(?string $dn = null, ?string $password = null, ?array $controls = null): LdapResultResponse + { + /** @var \LDAP\Result $result */ + $result = $this->executeFailableOperation(function () use ($dn, $password, $controls) { + return ldap_bind_ext($this->connection, $dn, $password ? html_entity_decode($password) : null, $controls); + }); + + $response = $this->parseResult($result); + + $this->handleBindResponse($response); + + return $response; + } + + /** + * {@inheritDoc} + */ + public function saslBind(?string $dn = null, ?string $password = null, array $options = []): bool + { + return $this->executeFailableOperation(function () use ($dn, $password, $options) { + $options = array_merge([ + 'mech' => null, + 'realm' => null, + 'authc_id' => null, + 'authz_id' => null, + 'props' => null, + ], $options); + + return $this->bound = ldap_sasl_bind( + $this->connection, + $dn, + $password ? html_entity_decode($password) : null, + $options['mech'], + $options['realm'], + $options['authc_id'], + $options['authz_id'], + $options['props'], + ); + }); + } + + /** + * {@inheritdoc} + */ + public function add(string $dn, array $entry): bool + { + return $this->executeFailableOperation(function () use ($dn, $entry) { + return ldap_add($this->connection, $dn, $entry); + }); + } + + /** + * {@inheritdoc} + */ + public function delete(string $dn): bool + { + return $this->executeFailableOperation(function () use ($dn) { + return ldap_delete($this->connection, $dn); + }); + } + + /** + * {@inheritdoc} + */ + public function rename(string $dn, string $newRdn, string $newParent, bool $deleteOldRdn = false): bool + { + return $this->executeFailableOperation(function () use ( + $dn, + $newRdn, + $newParent, + $deleteOldRdn + ) { + return ldap_rename($this->connection, $dn, $newRdn, $newParent, $deleteOldRdn); + }); + } + + /** + * {@inheritdoc} + */ + public function modify(string $dn, array $entry): bool + { + return $this->executeFailableOperation(function () use ($dn, $entry) { + return ldap_modify($this->connection, $dn, $entry); + }); + } + + /** + * {@inheritdoc} + */ + public function modifyBatch(string $dn, array $values): bool + { + return $this->executeFailableOperation(function () use ($dn, $values) { + return ldap_modify_batch($this->connection, $dn, $values); + }); + } + + /** + * {@inheritdoc} + */ + public function modAdd(string $dn, array $entry): bool + { + return $this->executeFailableOperation(function () use ($dn, $entry) { + return ldap_mod_add($this->connection, $dn, $entry); + }); + } + + /** + * {@inheritdoc} + */ + public function modReplace(string $dn, array $entry): bool + { + return $this->executeFailableOperation(function () use ($dn, $entry) { + return ldap_mod_replace($this->connection, $dn, $entry); + }); + } + + /** + * {@inheritdoc} + */ + public function modDelete(string $dn, array $entry): bool + { + return $this->executeFailableOperation(function () use ($dn, $entry) { + return ldap_mod_del($this->connection, $dn, $entry); + }); + } + + /** + * {@inheritdoc} + */ + public function freeResult(mixed $result): bool + { + return ldap_free_result($result); + } + + /** + * {@inheritdoc} + */ + public function errNo(): ?int + { + return $this->connection ? ldap_errno($this->connection) : null; + } + + /** + * {@inheritdoc} + */ + public function err2Str(int $number): string + { + return ldap_err2str($number); + } + + /** + * Get the extended error hex code of the last command. + */ + public function getExtendedErrorHex(): ?string + { + if (preg_match("/(?<=data\s).*?(?=,)/", $this->getExtendedError(), $code)) { + return $code[0]; + } + + return null; + } + + /** + * Get the extended error code of the last command. + */ + public function getExtendedErrorCode(): string|false + { + return $this->extractDiagnosticCode($this->getExtendedError()); + } + + /** + * Extract the diagnostic code from the message. + */ + public function extractDiagnosticCode(string $message): string|false + { + preg_match('/^([\da-fA-F]+):/', $message, $matches); + + return $matches[1] ?? false; + } + + /** + * {@inheritdoc} + */ + public function getDiagnosticMessage(): ?string + { + $this->getOption(LDAP_OPT_ERROR_STRING, $message); + + return $message; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapInterface.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapInterface.php new file mode 100644 index 0000000000..735f858ca6 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapInterface.php @@ -0,0 +1,630 @@ +getMessage(), $e->getCode(), $e))->setDetailedError($error); + } + + /** + * Set the detailed error. + */ + public function setDetailedError(?DetailedError $error = null): static + { + $this->detailedError = $error; + + return $this; + } + + /** + * Get the detailed error. + */ + public function getDetailedError(): ?DetailedError + { + return $this->detailedError; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapResultResponse.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapResultResponse.php new file mode 100644 index 0000000000..c0c1e8306c --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapResultResponse.php @@ -0,0 +1,33 @@ +errorCode === 0; + } + + /** + * Determine if the LDAP response indicates a failed status. + */ + public function failed(): bool + { + return ! $this->successful(); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Computer.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Computer.php new file mode 100644 index 0000000000..3900c61a95 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Computer.php @@ -0,0 +1,50 @@ +hasMany(Group::class, 'member')->with($this->primaryGroup()); + } + + /** + * The primary group relationship. + */ + public function primaryGroup(): HasOnePrimaryGroup + { + return $this->hasOnePrimaryGroup(Group::class, 'primarygroupid'); + } + + /** + * The managed by relationship. + */ + public function managedBy(): HasOne + { + return $this->hasOne([Contact::class, Group::class, User::class], 'managedby'); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Concerns/HasAccountControl.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Concerns/HasAccountControl.php new file mode 100644 index 0000000000..425cc548fc --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Concerns/HasAccountControl.php @@ -0,0 +1,46 @@ +isDisabled(); + } + + /** + * Determine if the user's account is disabled. + */ + public function isDisabled(): bool + { + return $this->accountControl()->hasFlag(AccountControl::ACCOUNTDISABLE); + } + + /** + * Get the user's account control. + */ + public function accountControl(): AccountControl + { + return new AccountControl( + $this->getFirstAttribute('userAccountControl') + ); + } + + /** + * Set the user's account control attribute. + */ + public function setUserAccountControlAttribute(mixed $value): void + { + if ($value instanceof AccountControl) { + $value = $value->getValue(); + } + + $this->attributes['useraccountcontrol'] = [(int) $value]; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Concerns/HasPrimaryGroup.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Concerns/HasPrimaryGroup.php new file mode 100644 index 0000000000..c8e69915aa --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Concerns/HasPrimaryGroup.php @@ -0,0 +1,16 @@ +newQuery(), $this, $related, $relationKey, $foreignKey); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Contact.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Contact.php new file mode 100644 index 0000000000..9087f058e9 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Contact.php @@ -0,0 +1,26 @@ +hasMany(Group::class, 'member'); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Container.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Container.php new file mode 100644 index 0000000000..e17fb1f70f --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Container.php @@ -0,0 +1,14 @@ + 'windows', + 'whencreated' => 'windows', + 'dscorepropagationdata' => 'windows', + ]; + + /** + * The attribute key that contains the Object SID. + */ + protected string $sidKey = 'objectsid'; + + /** + * {@inheritdoc} + */ + public function getObjectSidKey(): string + { + return $this->sidKey; + } + + /** + * {@inheritdoc} + */ + public function getObjectSid(): ?string + { + return $this->getFirstAttribute($this->sidKey); + } + + /** + * {@inheritdoc} + */ + public function getConvertedSid($sid = null): ?string + { + try { + return $this->newObjectSid( + (string) ($sid ?? $this->getObjectSid()) + ); + } catch (InvalidArgumentException) { + return null; + } + } + + /** + * {@inheritdoc} + */ + public function getBinarySid($sid = null): ?string + { + try { + return $this->newObjectSid( + $sid ?? $this->getObjectSid() + )->getBinary(); + } catch (InvalidArgumentException) { + return null; + } + } + + /** + * Make a new object Sid instance. + */ + protected function newObjectSid(string $value): Sid + { + return new Sid($value); + } + + /** + * Create a new query builder. + */ + public function newQueryBuilder(Connection $connection): ActiveDirectoryBuilder + { + return new ActiveDirectoryBuilder($connection); + } + + /** + * Determine if the object is deleted. + */ + public function isDeleted(): bool + { + return strtoupper((string) $this->getFirstAttribute('isDeleted')) === 'TRUE'; + } + + /** + * Restore a deleted object. + * + * @throws \LdapRecord\LdapRecordException + */ + public function restore(?string $newParentDn = null): bool + { + if (! $this->isDeleted()) { + return false; + } + + $root = $newParentDn ?? $this->getDefaultRestoreLocation(); + $rdn = explode('\0A', $this->getDn(), 2)[0]; + $newDn = implode(',', [$rdn, $root]); + + // We will initialize a model listener for the "updated" event to set + // the models distinguished name so all attributes are synchronized + // properly after the model has been successfully restored. + $this->listenForModelEvent(Updated::class, function (Updated $event) use ($newDn) { + if ($this->is($event->getModel())) { + $this->setDn($newDn); + } + }); + + $this->setRawAttribute('distinguishedname', $newDn); + + $this->save(['isDeleted' => null]); + + return true; + } + + /** + * Get the objects restore location. + */ + protected function getDefaultRestoreLocation(): ?string + { + return $this->getFirstAttribute('lastKnownParent') ?? $this->getParentDn($this->getParentDn($this->getDn())); + } + + /** + * Convert the attributes for JSON serialization. + */ + protected function convertAttributesForJson(array $attributes = []): array + { + $attributes = parent::convertAttributesForJson($attributes); + + // If the model has a SID set, we need to convert it to its + // string format, due to it being in binary. Otherwise, + // we will receive a JSON serialization exception. + if (isset($attributes[$this->sidKey])) { + $attributes[$this->sidKey] = [$this->getConvertedSid( + Arr::first($attributes[$this->sidKey]) + )]; + } + + return $attributes; + } + + /** + * Convert the attributes from JSON serialization. + */ + protected function convertAttributesFromJson(array $attributes = []): array + { + $attributes = parent::convertAttributesFromJson($attributes); + + if (isset($attributes[$this->sidKey])) { + $attributes[$this->sidKey] = [$this->getBinarySid( + Arr::first($attributes[$this->sidKey]) + )]; + } + + return $attributes; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/ExchangeDatabase.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/ExchangeDatabase.php new file mode 100644 index 0000000000..e32f820094 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/ExchangeDatabase.php @@ -0,0 +1,21 @@ +hasMany(Group::class, 'member'); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Group.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Group.php new file mode 100644 index 0000000000..165bd90f8f --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Group.php @@ -0,0 +1,58 @@ +hasMany(static::class, 'member'); + } + + /** + * The members relationship. + */ + public function members(): HasMany + { + return $this->hasMany([ + static::class, User::class, Contact::class, Computer::class, + ], 'memberof') + ->using($this, 'member') + ->with($this->primaryGroupMembers()); + } + + /** + * The primary group members relationship. + */ + public function primaryGroupMembers(): HasMany + { + return $this->hasMany([ + static::class, User::class, Contact::class, Computer::class, + ], 'primarygroupid', 'rid'); + } + + /** + * Get the RID of the group. + */ + public function getRidAttribute(): array + { + return array_filter([ + last(explode('-', (string) $this->getConvertedSid())), + ]); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/OrganizationalUnit.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/OrganizationalUnit.php new file mode 100644 index 0000000000..0801d10804 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/OrganizationalUnit.php @@ -0,0 +1,22 @@ +query->findBySid( + $this->getParentModelObjectSid() + ); + } + + /** + * Get the foreign value from the given model. + * + * Retrieves the last RID from the models Object SID. + */ + protected function getForeignValueFromModel(Model $model): ?string + { + $objectSidComponents = explode('-', $model->getConvertedSid()); + + return end($objectSidComponents); + } + + /** + * Get the parent relationship models converted object sid. + */ + protected function getParentModelObjectSid(): string + { + return preg_replace( + '/\d+$/', + $this->parent->getFirstAttribute($this->relationKey), + $this->parent->getConvertedSid() + ); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Scopes/HasServerRoleAttribute.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Scopes/HasServerRoleAttribute.php new file mode 100644 index 0000000000..24a2387d3a --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Scopes/HasServerRoleAttribute.php @@ -0,0 +1,18 @@ +whereHas('serverRole'); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Scopes/InConfigurationContext.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Scopes/InConfigurationContext.php new file mode 100644 index 0000000000..b7942cf4d9 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Scopes/InConfigurationContext.php @@ -0,0 +1,32 @@ +in($this->getConfigurationNamingContext($model)); + } + + /** + * Get the LDAP server configuration naming context distinguished name. + * + * @throws \LdapRecord\Models\ModelNotFoundException + */ + protected function getConfigurationNamingContext(Model $model) + { + return Entry::getRootDse($model->getConnectionName()) + ->getFirstAttribute('configurationNamingContext'); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Scopes/RejectComputerObjectClass.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Scopes/RejectComputerObjectClass.php new file mode 100644 index 0000000000..30545b802a --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Scopes/RejectComputerObjectClass.php @@ -0,0 +1,18 @@ +where('objectclass', '!=', 'computer'); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/User.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/User.php new file mode 100644 index 0000000000..3faec38822 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/User.php @@ -0,0 +1,144 @@ + 'windows-int', + 'lastlogoff' => 'windows-int', + 'pwdlastset' => 'windows-int', + 'lockouttime' => 'windows-int', + 'accountexpires' => 'windows-int', + 'badpasswordtime' => 'windows-int', + 'lastlogontimestamp' => 'windows-int', + ]; + + /** + * {@inheritdoc} + */ + protected static function boot(): void + { + parent::boot(); + + // Here we will add a global scope to reject the 'computer' object + // class. This is needed due to computer objects containing all + // of the ActiveDirectory 'user' object classes. Without + // this scope, they would be included in results. + static::addGlobalScope(new RejectComputerObjectClass); + } + + /** + * Get the unique identifier for the user. + */ + public function getAuthIdentifier(): ?string + { + return $this->getConvertedGuid(); + } + + /** + * The groups relationship. + * + * Retrieves groups that the user is a part of. + */ + public function groups(): HasMany + { + return $this->hasMany(Group::class, 'member')->with($this->primaryGroup()); + } + + /** + * The manager relationship. + * + * Retrieves the manager of the user. + */ + public function manager(): HasOne + { + return $this->hasOne(static::class, 'manager'); + } + + /** + * The primary group relationship of the current user. + * + * Retrieves the primary group the user is a part of. + */ + public function primaryGroup(): HasOne + { + return $this->hasOnePrimaryGroup(Group::class, 'primarygroupid'); + } + + /** + * Scopes the query to exchange mailbox users. + */ + public function scopeWhereHasMailbox(Builder $query): Builder + { + return $query->whereHas('msExchMailboxGuid'); + } + + /** + * Scopes the query to users having a lockout value set. + */ + public function scopeWhereHasLockout(Builder $query): Builder + { + return $query->where('lockoutTime', '>=', 1); + } + + /** + * Determine if the user is locked out using the domains LockoutDuration group policy value. + * + * @see https://ldapwiki.com/wiki/Active%20Directory%20Account%20Lockout + * @see https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/account-lockout-duration + */ + public function isLockedOut(string|int $localTimezone, ?int $durationInMinutes = null): bool + { + $time = $this->getFirstAttribute('lockouttime'); + + if (! $time instanceof Carbon) { + return false; + } + + is_int($localTimezone) + ? $time->addMinutes($localTimezone) + : $time->setTimezone($localTimezone)->addMinutes($durationInMinutes ?: 0); + + return ! $time->isPast(); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/AccountControl.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/AccountControl.php new file mode 100644 index 0000000000..9024b934e0 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/AccountControl.php @@ -0,0 +1,419 @@ + + */ + protected array $flags = []; + + /** + * Constructor. + */ + public function __construct(?int $flag = null) + { + if (! is_null($flag)) { + $this->applyFlags($flag); + } + } + + /** + * Get the value when cast to string. + */ + public function __toString(): string + { + return (string) $this->getValue(); + } + + /** + * Get the value when cast to int. + */ + public function __toInt(): int + { + return $this->getValue(); + } + + /** + * Set a flag on the account control. + */ + public function setFlag(int $flag): static + { + // Use the value as a key so if the same value + // is used, it will always be overwritten + $this->flags[$flag] = $flag; + + return $this; + } + + /** + * Unset a flag from the account control. + */ + public function unsetFlag(int $flag): static + { + unset($this->flags[$flag]); + + return $this; + } + + /** + * Extract and apply several flags. + */ + public function applyFlags(int $flags): void + { + $this->setFlags($this->extractFlags($flags)); + } + + /** + * Determine if the account control contains the given flag(s). + */ + public function hasFlag(int $flag): bool + { + // Here we will extract the given flag into an array + // of possible flags. This will allow us to see if + // our AccountControl object contains any of them. + $flagsUsed = array_intersect( + $this->extractFlags($flag), + $this->flags + ); + + return in_array($flag, $flagsUsed); + } + + /** + * Determine if the account control does not contain the given flag(s). + */ + public function doesntHaveFlag(int $flag): bool + { + return ! $this->hasFlag($flag); + } + + /** + * Generate an LDAP filter based on the current value. + */ + public function filter(): string + { + return sprintf('(UserAccountControl:1.2.840.113556.1.4.803:=%s)', $this); + } + + /** + * The logon script will be run. + */ + public function setRunLoginScript(): static + { + return $this->setFlag(static::SCRIPT); + } + + /** + * The user account is locked. + */ + public function setAccountIsLocked(): static + { + return $this->setFlag(static::LOCKOUT); + } + + /** + * The user account is disabled. + */ + public function setAccountIsDisabled(): static + { + return $this->setFlag(static::ACCOUNTDISABLE); + } + + /** + * This is an account for users whose primary account is in another domain. + * + * This account provides user access to this domain, but not to any domain that + * trusts this domain. This is sometimes referred to as a local user account. + */ + public function setAccountIsTemporary(): static + { + return $this->setFlag(static::TEMP_DUPLICATE_ACCOUNT); + } + + /** + * This is a default account type that represents a typical user. + */ + public function setAccountIsNormal(): static + { + return $this->setFlag(static::NORMAL_ACCOUNT); + } + + /** + * This is a permit to trust an account for a system domain that trusts other domains. + */ + public function setAccountIsForInterdomain(): static + { + return $this->setFlag(static::INTERDOMAIN_TRUST_ACCOUNT); + } + + /** + * This is a computer account for a computer that is running Microsoft + * Windows NT 4.0 Workstation, Microsoft Windows NT 4.0 Server, Microsoft + * Windows 2000 Professional, or Windows 2000 Server and is a member of this domain. + */ + public function setAccountIsForWorkstation(): static + { + return $this->setFlag(static::WORKSTATION_TRUST_ACCOUNT); + } + + /** + * This is a computer account for a domain controller that is a member of this domain. + */ + public function setAccountIsForServer(): static + { + return $this->setFlag(static::SERVER_TRUST_ACCOUNT); + } + + /** + * This is an MNS logon account. + */ + public function setAccountIsMnsLogon(): static + { + return $this->setFlag(static::MNS_LOGON_ACCOUNT); + } + + /** + * (Windows 2000/Windows Server 2003) This account does + * not require Kerberos pre-authentication for logging on. + */ + public function setAccountDoesNotRequirePreAuth(): static + { + return $this->setFlag(static::DONT_REQ_PREAUTH); + } + + /** + * When this flag is set, it forces the user to log on by using a smart card. + */ + public function setAccountRequiresSmartCard(): static + { + return $this->setFlag(static::SMARTCARD_REQUIRED); + } + + /** + * (Windows Server 2008/Windows Server 2008 R2) The account is a read-only domain controller (RODC). + * + * This is a security-sensitive setting. Removing this setting from an RODC compromises security on that server. + */ + public function setAccountIsReadOnly(): static + { + return $this->setFlag(static::PARTIAL_SECRETS_ACCOUNT); + } + + /** + * The home folder is required. + */ + public function setHomeFolderIsRequired(): static + { + return $this->setFlag(static::HOMEDIR_REQUIRED); + } + + /** + * No password is required. + */ + public function setPasswordIsNotRequired(): static + { + return $this->setFlag(static::PASSWD_NOTREQD); + } + + /** + * The user cannot change the password. This is a permission on the user's object. + * + * For information about how to programmatically set this permission, visit the following link: + * + * @see http://msdn2.microsoft.com/en-us/library/aa746398.aspx + */ + public function setPasswordCannotBeChanged(): static + { + return $this->setFlag(static::PASSWD_CANT_CHANGE); + } + + /** + * Represents the password, which should never expire on the account. + */ + public function setPasswordDoesNotExpire(): static + { + return $this->setFlag(static::DONT_EXPIRE_PASSWORD); + } + + /** + * (Windows 2000/Windows Server 2003) The user's password has expired. + */ + public function setPasswordIsExpired(): static + { + return $this->setFlag(static::PASSWORD_EXPIRED); + } + + /** + * The user can send an encrypted password. + */ + public function setAllowEncryptedTextPassword(): static + { + return $this->setFlag(static::ENCRYPTED_TEXT_PWD_ALLOWED); + } + + /** + * When this flag is set, the service account (the user or computer account) + * under which a service runs is trusted for Kerberos delegation. + * + * Any such service can impersonate a client requesting the service. + * + * To enable a service for Kerberos delegation, you must set this + * flag on the userAccountControl property of the service account. + */ + public function setTrustForDelegation(): static + { + return $this->setFlag(static::TRUSTED_FOR_DELEGATION); + } + + /** + * (Windows 2000/Windows Server 2003) The account is enabled for delegation. + * + * This is a security-sensitive setting. Accounts that have this option enabled + * should be tightly controlled. This setting lets a service that runs under the + * account assume a client's identity and authenticate as that user to other remote + * servers on the network. + */ + public function setTrustToAuthForDelegation(): static + { + return $this->setFlag(static::TRUSTED_TO_AUTH_FOR_DELEGATION); + } + + /** + * When this flag is set, the security context of the user is not delegated to a + * service even if the service account is set as trusted for Kerberos delegation. + */ + public function setDoNotTrustForDelegation(): static + { + return $this->setFlag(static::NOT_DELEGATED); + } + + /** + * (Windows 2000/Windows Server 2003) Restrict this principal to + * use only Data Encryption Standard (DES) encryption types for keys. + */ + public function setUseDesKeyOnly(): static + { + return $this->setFlag(static::USE_DES_KEY_ONLY); + } + + /** + * Get the account control value. + */ + public function getValue(): int + { + return array_sum($this->flags); + } + + /** + * Get the account control flag values. + * + * @return array + */ + public function getFlags(): array + { + return $this->flags; + } + + /** + * Set the account control values. + * + * @param array $flags + */ + public function setFlags(array $flags): void + { + $this->flags = $flags; + } + + /** + * Get all flags that are currently applied to the value. + */ + public function getAppliedFlags(): array + { + $flags = $this->getAllFlags(); + + $applied = []; + + foreach ($flags as $name => $flag) { + if ($this->hasFlag($flag)) { + $applied[$name] = $flag; + } + } + + return $applied; + } + + /** + * Get all possible account control flags. + */ + public function getAllFlags(): array + { + return (new ReflectionClass(__CLASS__))->getConstants(); + } + + /** + * Extracts the given flag into an array of flags used. + */ + protected function extractFlags(int $flag): array + { + $flags = []; + + for ($i = 0; $i <= 26; $i++) { + if ($flag & (1 << $i)) { + $flags[1 << $i] = 1 << $i; + } + } + + return $flags; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/DistinguishedName.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/DistinguishedName.php new file mode 100644 index 0000000000..09391091b5 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/DistinguishedName.php @@ -0,0 +1,338 @@ +value = trim((string) $value); + } + + /** + * Get the distinguished name value. + */ + public function __toString(): string + { + return $this->value; + } + + /** + * Alias of the "build" method. + */ + public static function of(?string $value = null): DistinguishedNameBuilder + { + return static::build($value); + } + + /** + * Get a new DN builder object from the given DN. + */ + public static function build(?string $value = null): DistinguishedNameBuilder + { + return new DistinguishedNameBuilder($value); + } + + /** + * Make a new distinguished name instance. + */ + public static function make(?string $value = null): static + { + return new static($value); + } + + /** + * Determine if the given value is a valid distinguished name. + */ + public static function isValid(?string $value = null): bool + { + return ! static::make($value)->isEmpty(); + } + + /** + * Explode a distinguished name into relative distinguished names. + */ + public static function explode(string $dn): array + { + $components = ldap_explode_dn($dn, (int) $withoutAttributes = false); + + if (! is_array($components)) { + return []; + } + + if (! array_key_exists('count', $components)) { + return []; + } + + unset($components['count']); + + return $components; + } + + /** + * Explode the RDN into an attribute and value. + */ + public static function explodeRdn(string $rdn): array + { + return explode('=', $rdn, $limit = 2); + } + + /** + * Implode the component attribute and value into an RDN. + */ + public static function makeRdn(array $component): string + { + return implode('=', $component); + } + + /** + * Get the underlying value. + */ + public function get(): ?string + { + return $this->value; + } + + /** + * Set the underlying value. + */ + public function set(?string $value = null): static + { + $this->value = $value; + + return $this; + } + + /** + * Get the distinguished name values without attributes. + */ + public function values(): array + { + $values = []; + + foreach ($this->multi() as [, $value]) { + $values[] = EscapedValue::unescape($value); + } + + return $values; + } + + /** + * Get the distinguished name attributes without values. + */ + public function attributes(): array + { + $attributes = []; + + foreach ($this->multi() as [$attribute]) { + $attributes[] = $attribute; + } + + return $attributes; + } + + /** + * Get the distinguished name components with attributes. + */ + public function components(): array + { + $components = []; + + foreach ($this->multi() as [$attribute, $value]) { + // When a distinguished name is exploded, the values are automatically + // escaped. This cannot be opted out of. Here we will unescape + // the attribute value, then re-escape it to its original + // representation from the server using the "dn" flag. + $value = $this->escape(EscapedValue::unescape($value))->forDn(); + + $components[] = static::makeRdn([$attribute, $value]); + } + + return $components; + } + + /** + * Convert the distinguished name into an associative array. + */ + public function assoc(): array + { + $map = []; + + foreach ($this->multi() as [$attribute, $value]) { + $attribute = $this->normalize($attribute); + + array_key_exists($attribute, $map) + ? $map[$attribute][] = $value + : $map[$attribute] = [$value]; + } + + return $map; + } + + /** + * Split the RDNs into a multi-dimensional array. + */ + public function multi(): array + { + return array_map(fn ($rdn) => static::explodeRdn($rdn), $this->rdns()); + } + + /** + * Split the distinguished name into an array of unescaped RDN's. + */ + public function rdns(): array + { + return static::explode($this->value); + } + + /** + * Get the first RDNs value. + */ + public function name(): ?string + { + return Arr::first($this->values()); + } + + /** + * Get the first RDNs attribute. + */ + public function head(): ?string + { + return Arr::first($this->attributes()); + } + + /** + * Get the relative distinguished name. + */ + public function relative(): ?string + { + return Arr::first($this->components()); + } + + /** + * Alias of relative(). + * + * Get the first RDN from the distinguished name. + */ + public function first(): ?string + { + return $this->relative(); + } + + /** + * Get the parent distinguished name. + */ + public function parent(): ?string + { + $components = $this->components(); + + array_shift($components); + + return implode(',', $components) ?: null; + } + + /** + * Determine if the distinguished name is empty. + */ + public function isEmpty(): bool + { + return empty( + array_filter( + array_map('trim', $this->values()) + ) + ); + } + + /** + * Determine if the distinguished name is not empty. + */ + public function isNotEmpty(): bool + { + return ! $this->isEmpty(); + } + + /** + * Determine if the current distinguished name is a parent of the given child. + */ + public function isParentOf(self $child): bool + { + return $child->isChildOf($this); + } + + /** + * Determine if the current distinguished name is a child of the given parent. + */ + public function isChildOf(self $parent): bool + { + if (! $this->isComparable($this->parent(), $parent->get())) { + return false; + } + + return $this->normalize($this->parent()) === $this->normalize($parent->get()); + } + + /** + * Determine if the current distinguished name is an ancestor of the descendant. + */ + public function isAncestorOf(self $descendant): bool + { + return $descendant->isDescendantOf($this); + } + + /** + * Determine if the current distinguished name is a descendant of the ancestor. + */ + public function isDescendantOf(self $ancestor): bool + { + if (! $this->isComparable($this->parent(), $ancestor->get())) { + return false; + } + + return str_ends_with( + $this->normalize($this->parent()), + $this->normalize($ancestor->get()) + ); + } + + /** + * Determine if the current distinguished name is a sibling of the given distinguished name. + */ + public function isSiblingOf(self $sibling): bool + { + if (! $this->isComparable($this->parent(), $sibling->parent())) { + return false; + } + + return $this->normalize($this->parent()) === $this->normalize($sibling->parent()); + } + + /** + * Determine if the distinguished names are comparable. + */ + protected function isComparable(?string $first, ?string $second): bool + { + return static::make($first)->isNotEmpty() && static::make($second)->isNotEmpty(); + } + + /** + * Normalize the string value. + */ + protected function normalize(string $value): string + { + return strtolower($value); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/DistinguishedNameBuilder.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/DistinguishedNameBuilder.php new file mode 100644 index 0000000000..7345cb92d8 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/DistinguishedNameBuilder.php @@ -0,0 +1,195 @@ +components = array_map( + fn ($rdn) => DistinguishedName::explodeRdn($rdn), + DistinguishedName::make($dn)->components() + ); + } + + /** + * Forward missing method calls onto the Distinguished Name object. + */ + public function __call(string $method, array $args): mixed + { + return $this->get()->{$method}(...$args); + } + + /** + * Get the distinguished name value. + */ + public function __toString(): string + { + return (string) $this->get(); + } + + /** + * Prepend an RDN onto the DN. + */ + public function prepend(array|string $attribute, ?string $value = null): static + { + array_unshift( + $this->components, + ...$this->componentize($attribute, $value) + ); + + return $this; + } + + /** + * Append an RDN onto the DN. + */ + public function append(array|string $attribute, ?string $value = null): static + { + array_push( + $this->components, + ...$this->componentize($attribute, $value) + ); + + return $this; + } + + /** + * Componentize the attribute and value. + */ + protected function componentize(array|string $attribute, ?string $value = null): array + { + // Here we will make the assumption that an array of + // RDN's have been given if the value is null, and + // attempt to break them into their components. + if (is_null($value)) { + $attributes = Arr::wrap($attribute); + + $components = array_map([$this, 'makeComponentizedArray'], $attributes); + } else { + $components = [[$attribute, $value]]; + } + + return array_map(function ($component) { + [$attribute, $value] = $component; + + return $this->makeAppendableComponent($attribute, $value); + }, $components); + } + + /** + * Make a componentized array by exploding the value if it's a string. + */ + protected function makeComponentizedArray(array|string $value): array + { + return is_array($value) ? $value : DistinguishedName::explodeRdn($value); + } + + /** + * Make an appendable component array from the attribute and value. + */ + protected function makeAppendableComponent(string|array $attribute, ?string $value = null): array + { + return [trim($attribute), $this->escape(trim($value))->forDn()]; + } + + /** + * Pop an RDN off of the end of the DN. + */ + public function pop(int $amount = 1, ?array &$removed = null): static + { + $removed = array_map( + fn ($component) => DistinguishedName::makeRdn($component), + array_splice($this->components, -$amount, $amount) + ); + + return $this; + } + + /** + * Shift an RDN off of the beginning of the DN. + */ + public function shift(int $amount = 1, ?array &$removed = null): static + { + $removed = array_map( + fn ($component) => DistinguishedName::makeRdn($component), + array_splice($this->components, 0, $amount) + ); + + return $this; + } + + /** + * Whether to output the DN in reverse. + */ + public function reverse(): static + { + $this->reverse = true; + + return $this; + } + + /** + * Get the components of the DN. + */ + public function components(?string $type = null): array + { + return is_null($type) + ? $this->components + : $this->componentsOfType($type); + } + + /** + * Get the components of a particular type. + */ + protected function componentsOfType(string $type): array + { + $components = array_filter($this->components, fn ($component) => ( + ([$name] = $component) && strtolower($name) === strtolower($type) + )); + + return array_values($components); + } + + /** + * Get the fully qualified DN. + */ + public function get(): DistinguishedName + { + return new DistinguishedName($this->build()); + } + + /** + * Build the distinguished name from the components. + */ + protected function build(): string + { + $components = $this->reverse + ? array_reverse($this->components) + : $this->components; + + return implode(',', array_map( + fn ($component) => DistinguishedName::makeRdn($component), + $components + )); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/EscapedValue.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/EscapedValue.php new file mode 100644 index 0000000000..c28d95c20d --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/EscapedValue.php @@ -0,0 +1,109 @@ +value = $value; + $this->ignore = $ignore; + $this->flags = $flags; + } + + /** + * Un-escapes a hexadecimal string into its original string representation. + */ + public static function unescape(string $value): string + { + return preg_replace_callback( + '/\\\([0-9A-Fa-f]{2})/', + fn ($matches) => chr(hexdec($matches[1])), + $value + ); + } + + /** + * Get the escaped value. + */ + public function __toString(): string + { + return $this->get(); + } + + /** + * Get the escaped value. + */ + public function get(): string + { + return ldap_escape((string) $this->value, $this->ignore, $this->flags); + } + + /** + * Get the raw (unescaped) value. + */ + public function getRaw(): mixed + { + return $this->value; + } + + /** + * Set the characters to exclude from being escaped. + */ + public function ignore(string $characters): static + { + $this->ignore = $characters; + + return $this; + } + + /** + * Prepare the value to be escaped for use in a distinguished name. + */ + public function forDn(): static + { + $this->flags = LDAP_ESCAPE_DN; + + return $this; + } + + /** + * Prepare the value to be escaped for use in a filter. + */ + public function forFilter(): static + { + $this->flags = LDAP_ESCAPE_FILTER; + + return $this; + } + + /** + * Prepare the value to be escaped for use in a distinguished name and filter. + */ + public function forDnAndFilter(): static + { + $this->flags = LDAP_ESCAPE_FILTER + LDAP_ESCAPE_DN; + + return $this; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Guid.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Guid.php new file mode 100644 index 0000000000..dfde164293 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Guid.php @@ -0,0 +1,155 @@ + + * + * @see https://github.com/ldaptools/ldaptools + */ + protected array $octetSections = [ + [6, 4, 2, 0], + [10, 8], + [14, 12], + [16, 18, 20, 22, 24, 26, 28, 30], + ]; + + /** + * Determine if the specified GUID is valid. + */ + public static function isValid(string $guid): bool + { + return (bool) preg_match('/^([0-9a-fA-F]){8}(-([0-9a-fA-F]){4}){3}-([0-9a-fA-F]){12}$/', $guid); + } + + /** + * Constructor. + * + * @throws InvalidArgumentException + */ + public function __construct(string $value) + { + if (static::isValid($value)) { + $this->value = $value; + } elseif ($value = $this->binaryGuidToString($value)) { + $this->value = $value; + } else { + throw new InvalidArgumentException('Invalid Binary / String GUID.'); + } + } + + /** + * Get the string value of the GUID. + */ + public function __toString(): string + { + return $this->getValue(); + } + + /** + * Get the string value of the GUID. + */ + public function getValue(): string + { + return $this->value; + } + + /** + * Get the binary representation of the GUID string. + */ + public function getBinary(): string + { + return hex2bin($this->getHex()); + } + + /** + * Get the encoded hexadecimal representation of the GUID string. + */ + public function getEncodedHex(): string + { + return '\\'.implode('\\', str_split($this->getHex(), 2)); + } + + /** + * Get the hexadecimal representation of the GUID string. + */ + public function getHex(): string + { + return implode($this->getOctetSections()); + } + + /** + * Get the octect sections of the GUID. + */ + protected function getOctetSections(): array + { + $sections = []; + + $guid = str_replace('-', '', $this->value); + + foreach ($this->octetSections as $section) { + $sections[] = $this->parseSection($guid, $section, true); + } + + return $sections; + } + + /** + * Get the string variant of a binary GUID. + */ + protected function binaryGuidToString(string $binary): ?string + { + if (trim($binary) === '') { + return null; + } + + $hex = unpack('H*hex', $binary)['hex']; + + $hex1 = substr($hex, -26, 2).substr($hex, -28, 2).substr($hex, -30, 2).substr($hex, -32, 2); + $hex2 = substr($hex, -22, 2).substr($hex, -24, 2); + $hex3 = substr($hex, -18, 2).substr($hex, -20, 2); + $hex4 = substr($hex, -16, 4); + $hex5 = substr($hex, -12, 12); + + return sprintf('%s-%s-%s-%s-%s', $hex1, $hex2, $hex3, $hex4, $hex5); + } + + /** + * Get the specified section of the hexadecimal string. + * + * @author Chad Sikorra + * + * @see https://github.com/ldaptools/ldaptools + * + * @param string $hex The full hex string. + * @param array $sections An array of start and length (unless octet is true, then length is always 2). + * @param bool $octet Whether this is for octet string form. + * @return string The concatenated sections in upper-case. + */ + protected function parseSection(string $hex, array $sections, bool $octet = false): string + { + $parsedString = ''; + + foreach ($sections as $section) { + $start = $octet ? $section : $section[0]; + + $length = $octet ? 2 : $section[1]; + + $parsedString .= substr($hex, $start, $length); + } + + return $parsedString; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/MbString.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/MbString.php new file mode 100644 index 0000000000..c6eb4985e5 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/MbString.php @@ -0,0 +1,62 @@ + ['$1$', 12], + static::CRYPT_SALT_TYPE_SHA256 => ['$5$', 16], + static::CRYPT_SALT_TYPE_SHA512 => ['$6$', 16], + default => throw new InvalidArgumentException("Invalid crypt type [$type]."), + }; + } + + /** + * Attempt to retrieve the hash method used for the password. + */ + public static function getHashMethod(string $password): ?string + { + if (! preg_match('/^\{(\w+)\}/', $password, $matches)) { + return null; + } + + return $matches[1]; + } + + /** + * Attempt to retrieve the hash method and algorithm used for the password. + */ + public static function getHashMethodAndAlgo(string $password): ?array + { + if (! preg_match('/^\{(\w+)\}\$([0-9a-z]{1})\$/', $password, $matches)) { + return null; + } + + return [$matches[1], $matches[2]]; + } + + /** + * Attempt to retrieve a salt from the encrypted password. + * + * @throws LdapRecordException + */ + public static function getSalt(string $encryptedPassword): string + { + // crypt() methods. + if (preg_match('/^\{(\w+)\}(\$.*\$).*$/', $encryptedPassword, $matches)) { + return $matches[2]; + } + + // All other methods. + if (preg_match('/{([^}]+)}(.*)/', $encryptedPassword, $matches)) { + return substr(base64_decode($matches[2]), -4); + } + + throw new LdapRecordException('Could not extract salt from encrypted password.'); + } + + /** + * Determine if the hash method requires a salt to be given. + * + * @throws \ReflectionException + */ + public static function hashMethodRequiresSalt(string $method): bool + { + $parameters = (new ReflectionMethod(static::class, $method))->getParameters(); + + foreach ($parameters as $parameter) { + if ($parameter->name === 'salt') { + return true; + } + } + + return false; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Sid.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Sid.php new file mode 100644 index 0000000000..08c36267c3 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Sid.php @@ -0,0 +1,123 @@ +value = $value; + } elseif ($value = $this->binarySidToString($value)) { + $this->value = $value; + } else { + throw new InvalidArgumentException('Invalid Binary / String SID.'); + } + } + + /** + * Get the string value of the SID. + */ + public function __toString(): string + { + return $this->getValue(); + } + + /** + * Get the string value of the SID. + */ + public function getValue(): string + { + return $this->value; + } + + /** + * Get the binary variant of the SID. + */ + public function getBinary(): string + { + $sid = explode('-', ltrim($this->value, 'S-')); + + $level = (int) array_shift($sid); + + $authority = (int) array_shift($sid); + + $subAuthorities = array_map('intval', $sid); + + $params = array_merge( + ['C2xxNV*', $level, count($subAuthorities), $authority], + $subAuthorities + ); + + return call_user_func_array('pack', $params); + } + + /** + * Get the string variant of a binary SID. + */ + protected function binarySidToString(string $binary): ?string + { + if (trim($binary) === '') { + return null; + } + + // Revision - 8bit unsigned int (C1) + // Count - 8bit unsigned int (C1) + // 2 null bytes + // ID - 32bit unsigned long, big-endian order + $sid = @unpack('C1rev/C1count/x2/N1id', $binary); + + if (! isset($sid['id']) || ! isset($sid['rev'])) { + return null; + } + + $revisionLevel = $sid['rev']; + + $identifierAuthority = $sid['id']; + + $subs = $sid['count'] ?? 0; + + $sidHex = $subs ? bin2hex($binary) : ''; + + $subAuthorities = []; + + // The sub-authorities depend on the count, so only get as + // many as the count, regardless of data beyond it. + for ($i = 0; $i < $subs; $i++) { + $data = implode(array_reverse( + str_split( + substr($sidHex, 16 + ($i * 8), 8), + 2 + ) + )); + + $subAuthorities[] = hexdec($data); + } + + // Tack on the 'S-' and glue it all together... + return 'S-'.$revisionLevel.'-'.$identifierAuthority.implode( + preg_filter('/^/', '-', $subAuthorities) + ); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSProperty.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSProperty.php new file mode 100644 index 0000000000..dd2d0ebdab --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSProperty.php @@ -0,0 +1,336 @@ + ['001011', '011010'], + 'Y' => ['001110', '011010'], + ]; + + /** + * The nibble header. + */ + public const NIBBLE_HEADER = '1110'; + + /** + * Conversion factor needed for time values in the TSPropertyArray (stored in microseconds). + */ + public const TIME_CONVERSION = 60 * 1000; + + /** + * A simple map to help determine how the property needs to be decoded/encoded from/to its binary value. + * + * There are some names that are simple repeats but have 'W' at the end. Not sure as to what that signifies. I + * cannot find any information on them in Microsoft documentation. However, their values appear to stay in sync with + * their non 'W' counterparts. But not doing so when manipulating the data manually does not seem to affect anything. + * This probably needs more investigation. + */ + protected array $propTypes = [ + 'string' => [ + 'CtxWFHomeDir', + 'CtxWFHomeDirW', + 'CtxWFHomeDirDrive', + 'CtxWFHomeDirDriveW', + 'CtxInitialProgram', + 'CtxInitialProgramW', + 'CtxWFProfilePath', + 'CtxWFProfilePathW', + 'CtxWorkDirectory', + 'CtxWorkDirectoryW', + 'CtxCallbackNumber', + ], + 'time' => [ + 'CtxMaxDisconnectionTime', + 'CtxMaxConnectionTime', + 'CtxMaxIdleTime', + ], + 'int' => [ + 'CtxCfgFlags1', + 'CtxCfgPresent', + 'CtxKeyboardLayout', + 'CtxMinEncryptionLevel', + 'CtxNWLogonServer', + 'CtxShadow', + ], + ]; + + /** + * The property name. + */ + protected ?string $name = null; + + /** + * The property value. + */ + protected string|int|null $value = null; + + /** + * The property value type. + */ + protected int $valueType = 1; + + /** + * Pass binary TSProperty data to construct its object representation. + */ + public function __construct(string|int|null $value = null) + { + if ($value) { + $this->decode(bin2hex($value)); + } + } + + /** + * Set the name for the TSProperty. + */ + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + /** + * Get the name for the TSProperty. + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * Set the value for the TSProperty. + */ + public function setValue(string|int $value): static + { + $this->value = $value; + + return $this; + } + + /** + * Get the value for the TSProperty. + */ + public function getValue(): string|int|null + { + return $this->value; + } + + /** + * Convert the TSProperty name/value back to its binary + * representation for the userParameters blob. + */ + public function toBinary(): string + { + $name = bin2hex($this->name); + + $binValue = $this->getEncodedValueForProp($this->name, $this->value); + + $valueLen = strlen(bin2hex($binValue)) / 3; + + $binary = hex2bin( + $this->dec2hex(strlen($name)) + .$this->dec2hex($valueLen) + .$this->dec2hex($this->valueType) + .$name + ); + + return $binary.$binValue; + } + + /** + * Given a TSProperty blob, decode the name/value/type/etc. + */ + protected function decode(string $tsProperty): void + { + $nameLength = hexdec(substr($tsProperty, 0, 2)); + + // 1 data byte is 3 encoded bytes + $valueLength = hexdec(substr($tsProperty, 2, 2)) * 3; + + $this->valueType = hexdec(substr($tsProperty, 4, 2)); + $this->name = pack('H*', substr($tsProperty, 6, $nameLength)); + $this->value = $this->getDecodedValueForProp($this->name, substr($tsProperty, 6 + $nameLength, $valueLength)); + } + + /** + * Based on the property name/value in question, get its encoded form. + */ + protected function getEncodedValueForProp(string $propName, string|int $propValue): string + { + if (in_array($propName, $this->propTypes['string'])) { + // Simple strings are null terminated. Unsure if this is + // needed or simply a product of how ADUC does stuff? + $value = $this->encodePropValue($propValue."\0", true); + } elseif (in_array($propName, $this->propTypes['time'])) { + // Needs to be in microseconds (assuming it is in minute format)... + $value = $this->encodePropValue($propValue * self::TIME_CONVERSION); + } else { + $value = $this->encodePropValue($propValue); + } + + return $value; + } + + /** + * Based on the property name in question, get its actual value from the binary blob value. + */ + protected function getDecodedValueForProp(string $propName, string $propValue): string|int + { + if (in_array($propName, $this->propTypes['string'])) { + // Strip away null terminators. I think this should + // be desired, otherwise it just ends in confusion. + $value = str_replace("\0", '', $this->decodePropValue($propValue, true)); + } elseif (in_array($propName, $this->propTypes['time'])) { + // Convert from microseconds to minutes (how ADUC displays + // it anyway, and seems the most practical). + $value = hexdec($this->decodePropValue($propValue)) / self::TIME_CONVERSION; + } elseif (in_array($propName, $this->propTypes['int'])) { + $value = hexdec($this->decodePropValue($propValue)); + } else { + $value = $this->decodePropValue($propValue); + } + + return $value; + } + + /** + * Decode the property by inspecting the nibbles of each blob, checking + * the control, and adding up the results into a final value. + * + * @param bool $string Whether this is simple string data. + */ + protected function decodePropValue(string $hex, bool $string = false): string + { + $decodePropValue = ''; + + $blobs = str_split($hex, 6); + + foreach ($blobs as $blob) { + $bin = decbin(hexdec($blob)); + + $controlY = substr($bin, 4, 6); + $nibbleY = substr($bin, 10, 4); + $controlX = substr($bin, 14, 6); + $nibbleX = substr($bin, 20, 4); + + $byte = $this->nibbleControl($nibbleX, $controlX).$this->nibbleControl($nibbleY, $controlY); + + if ($string) { + $decodePropValue .= MbString::chr(bindec($byte)); + } else { + $decodePropValue = $this->dec2hex(bindec($byte)).$decodePropValue; + } + } + + return $decodePropValue; + } + + /** + * Get the encoded property value as a binary blob. + */ + protected function encodePropValue(string $value, bool $string = false): string + { + // An int must be properly padded. (then split and reversed). + // For a string, we just split the chars. This seems + // to be the easiest way to handle UTF-8 characters + // instead of trying to work with their hex values. + $chars = $string ? MbString::split($value) : array_reverse(str_split($this->dec2hex($value, 8), 2)); + + $encoded = ''; + + foreach ($chars as $char) { + // Get the bits for the char. Using this method to ensure it is fully padded. + $bits = sprintf('%08b', $string ? MbString::ord($char) : hexdec($char)); + $nibbleX = substr($bits, 0, 4); + $nibbleY = substr($bits, 4, 4); + + // Construct the value with the header, high nibble, then low nibble. + $value = self::NIBBLE_HEADER; + + foreach (['Y' => $nibbleY, 'X' => $nibbleX] as $nibbleType => $nibble) { + $value .= $this->getNibbleWithControl($nibbleType, $nibble); + } + + // Convert it back to a binary bit stream + foreach ([0, 8, 16] as $start) { + $encoded .= $this->packBitString(substr($value, $start, 8), 8); + } + } + + return $encoded; + } + + /** + * PHP's pack() function has no 'b' or 'B' template. This is + * a workaround that turns a literal bit-string into a + * packed byte-string with 8 bits per byte. + */ + protected function packBitString(string $bits, int $len): string + { + $bits = substr($bits, 0, $len); + // Pad input with zeros to next multiple of 4 above $len + $bits = str_pad($bits, 4 * (int) (($len + 3) / 4), '0'); + + // Split input into chunks of 4 bits, convert each to hex and pack them + $nibbles = str_split($bits, 4); + foreach ($nibbles as $i => $nibble) { + $nibbles[$i] = base_convert($nibble, 2, 16); + } + + return pack('H*', implode('', $nibbles)); + } + + /** + * Based on the control, adjust the nibble accordingly. + */ + protected function nibbleControl(string $nibble, string $control): string + { + // This control stays constant for the low/high nibbles, + // so it doesn't matter which we compare to + if ($control == self::NIBBLE_CONTROL['X'][1]) { + $dec = bindec($nibble); + $dec += 9; + $nibble = str_pad(decbin($dec), 4, '0', STR_PAD_LEFT); + } + + return $nibble; + } + + /** + * Get the nibble value with the control prefixed. + * + * If the nibble dec is <= 9, the control X equals 001011 and Y equals 001110, otherwise if the nibble dec is > 9 + * the control for X or Y equals 011010. Additionally, if the dec value of the nibble is > 9, then the nibble value + * must be subtracted by 9 before the final value is constructed. + * + * @param string $nibbleType Either X or Y + */ + protected function getNibbleWithControl(string $nibbleType, string $nibble): string + { + $dec = bindec($nibble); + + if ($dec > 9) { + $dec -= 9; + $control = self::NIBBLE_CONTROL[$nibbleType][1]; + } else { + $control = self::NIBBLE_CONTROL[$nibbleType][0]; + } + + return $control.sprintf('%04d', decbin($dec)); + } + + /** + * Need to make sure hex values are always an even length, so pad as needed. + * + * @param int $padLength The hex string must be padded to this length (with zeros). + */ + protected function dec2hex(int $int, int $padLength = 2): string + { + return str_pad(dechex($int), $padLength, 0, STR_PAD_LEFT); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSPropertyArray.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSPropertyArray.php new file mode 100644 index 0000000000..a81018143a --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSPropertyArray.php @@ -0,0 +1,255 @@ + 2953518677, + 'CtxWFProfilePath' => '', + 'CtxWFProfilePathW' => '', + 'CtxWFHomeDir' => '', + 'CtxWFHomeDirW' => '', + 'CtxWFHomeDirDrive' => '', + 'CtxWFHomeDirDriveW' => '', + 'CtxShadow' => 1, + 'CtxMaxDisconnectionTime' => 0, + 'CtxMaxConnectionTime' => 0, + 'CtxMaxIdleTime' => 0, + 'CtxWorkDirectory' => '', + 'CtxWorkDirectoryW' => '', + 'CtxCfgFlags1' => 2418077696, + 'CtxInitialProgram' => '', + 'CtxInitialProgramW' => '', + ]; + + /** + * The default data that occurs before the TSPropertyArray (CtxCfgPresent with a bunch of spaces...?). + */ + protected string $defaultPreBinary = '43747843666750726573656e742020202020202020202020202020202020202020202020202020202020202020202020'; + + /** + * The TSProperty array. + * + * @var TSProperty[] + */ + protected array $tsProperty = []; + + /** + * The TSProperty signature. + */ + protected string $signature = self::VALID_SIGNATURE; + + /** + * Binary data that occurs before the TSPropertyArray data in userParameters. + */ + protected string $preBinary = ''; + + /** + * Binary data that occurs after the TSPropertyArray data in userParameters. + */ + protected string $postBinary = ''; + + /** + * Construct in one of the following ways:. + * + * - Pass an array of TSProperty key => value pairs (See DEFAULTS constant). + * - Pass the userParameters binary value. The object representation of that will be decoded and constructed. + * - Pass nothing and a default set of TSProperty key => value pairs will be used (See DEFAULTS constant). + */ + public function __construct(mixed $tsPropertyArray = null) + { + $this->preBinary = hex2bin($this->defaultPreBinary); + + if (is_null($tsPropertyArray) || is_array($tsPropertyArray)) { + $tsPropertyArray = $tsPropertyArray ?: self::DEFAULTS; + + foreach ($tsPropertyArray as $key => $value) { + $tsProperty = new TSProperty; + + $this->tsProperty[$key] = $tsProperty->setName($key)->setValue($value); + } + } else { + $this->decodeUserParameters($tsPropertyArray); + } + } + + /** + * Check if a specific TSProperty exists by its property name. + */ + public function has(string $propName): bool + { + return array_key_exists(strtolower($propName), array_change_key_case($this->tsProperty)); + } + + /** + * Get a TSProperty object by its property name (ie. CtxWFProfilePath). + */ + public function get(string $propName): TSProperty + { + $this->validateProp($propName); + + return $this->getTsPropObj($propName); + } + + /** + * Add a TSProperty object. If it already exists, it will be overwritten. + */ + public function add(TSProperty $tsProperty): static + { + $this->tsProperty[$tsProperty->getName()] = $tsProperty; + + return $this; + } + + /** + * Remove a TSProperty by its property name (i.e. CtxMinEncryptionLevel). + */ + public function remove(string $propName): static + { + foreach (array_keys($this->tsProperty) as $property) { + if (strtolower($propName) == strtolower($property)) { + unset($this->tsProperty[$property]); + } + } + + return $this; + } + + /** + * Set the value for a specific TSProperty by its name. + */ + public function set(string $propName, string|int $propValue): static + { + $this->validateProp($propName); + + $this->getTsPropObj($propName)->setValue($propValue); + + return $this; + } + + /** + * Get the full binary representation of the userParameters containing the TSPropertyArray data. + */ + public function toBinary(): string + { + $binary = $this->preBinary; + + $binary .= hex2bin(str_pad(dechex(MbString::ord($this->signature)), 2, 0, STR_PAD_LEFT)); + + $binary .= hex2bin(str_pad(dechex(count($this->tsProperty)), 2, 0, STR_PAD_LEFT)); + + foreach ($this->tsProperty as $tsProperty) { + $binary .= $tsProperty->toBinary(); + } + + return $binary.$this->postBinary; + } + + /** + * Get a simple associative array containing of all TSProperty names and values. + */ + public function toArray(): array + { + $userParameters = []; + + foreach ($this->tsProperty as $property => $tsPropObj) { + $userParameters[$property] = $tsPropObj->getValue(); + } + + return $userParameters; + } + + /** + * Get all TSProperty objects. + * + * @return TSProperty[] + */ + public function getTSProperties(): array + { + return $this->tsProperty; + } + + /** + * Validates that the given property name exists. + */ + protected function validateProp(string $propName): void + { + if (! $this->has($propName)) { + throw new InvalidArgumentException(sprintf('TSProperty for "%s" does not exist.', $propName)); + } + } + + /** + * Get the TS property object for the given property. + */ + protected function getTsPropObj(string $propName): TSProperty + { + return array_change_key_case($this->tsProperty)[strtolower($propName)]; + } + + /** + * Get an associative array with all the userParameters property names and values. + */ + protected function decodeUserParameters(string $userParameters): void + { + $userParameters = bin2hex($userParameters); + + // Save the 96-byte array of reserved data, so as to not ruin anything that may be stored there. + $this->preBinary = hex2bin(substr($userParameters, 0, 96)); + // The signature is a 2-byte unicode character at the front + $this->signature = MbString::chr(hexdec(substr($userParameters, 96, 2))); + // This asserts the validity of the tsPropertyArray data. For some reason 'P' means valid... + if ($this->signature != self::VALID_SIGNATURE) { + throw new InvalidArgumentException('Invalid TSPropertyArray data'); + } + + // The property count is a 2-byte unsigned integer indicating the number of elements for the tsPropertyArray + // It starts at position 98. The actual variable data begins at position 100. + $length = $this->addTSPropData(substr($userParameters, 100), hexdec(substr($userParameters, 98, 2))); + + // Reserved data length + (count and sig length == 4) + the added lengths of the TSPropertyArray + // This saves anything after that variable TSPropertyArray data, so as to not squash anything stored there + if (strlen($userParameters) > (96 + 4 + $length)) { + $this->postBinary = hex2bin(substr($userParameters, 96 + 4 + $length)); + } + } + + /** + * Given the start of TSPropertyArray hex data, and the count for the number + * of TSProperty structures in contains, parse and split out the + * individual TSProperty structures. Return the full length + * of the TSPropertyArray data. + * + * @return int The length of the data in the TSPropertyArray + */ + protected function addTSPropData(string $tsPropertyArray, int $tsPropCount): int + { + $length = 0; + + for ($i = 0; $i < $tsPropCount; $i++) { + // Prop length = name length + value length + type length + the space for the length data. + $propLength = hexdec(substr($tsPropertyArray, $length, 2)) + (hexdec(substr($tsPropertyArray, $length + 2, 2)) * 3) + 6; + + $tsProperty = new TSProperty(hex2bin(substr($tsPropertyArray, $length, $propLength))); + + $this->tsProperty[$tsProperty->getName()] = $tsProperty; + + $length += $propLength; + } + + return $length; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Timestamp.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Timestamp.php new file mode 100644 index 0000000000..4046eb7156 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Timestamp.php @@ -0,0 +1,204 @@ +setType($type); + } + + /** + * Set the type of timestamp to convert from / to. + * + * @throws LdapRecordException + */ + public function setType(string $type): void + { + if (! in_array($type, $this->types)) { + throw new LdapRecordException("Unrecognized LDAP date type [$type]"); + } + + $this->type = $type; + } + + /** + * Converts the value to an LDAP date string. + * + * @throws LdapRecordException + */ + public function fromDateTime(mixed $value): int|string + { + $value = is_array($value) ? reset($value) : $value; + + // If the value is being converted to a windows integer format, but it + // is already in that format, we will simply return the value back. + if ($this->type === Timestamp::TYPE_WINDOWS_INT && $this->valueIsWindowsIntegerType($value)) { + return $value; + } + // If the value is numeric, we will assume it's a UNIX timestamp. + elseif (is_numeric($value)) { + $value = Carbon::createFromTimestamp($value); + } + // If a string is given, we will pass it into a new carbon instance. + elseif (is_string($value)) { + $value = Carbon::parse($value); + } + // If a date object is given, we will convert it to a carbon instance. + elseif ($value instanceof DateTime) { + $value = Carbon::instance($value); + } + + return match ($this->type) { + Timestamp::TYPE_LDAP => $this->convertDateTimeToLdapTime($value), + Timestamp::TYPE_WINDOWS => $this->convertDateTimeToWindows($value), + Timestamp::TYPE_WINDOWS_INT => $this->convertDateTimeToWindowsInteger($value), + default => throw new LdapRecordException("Unrecognized date type [{$this->type}]"), + }; + } + + /** + * Determine if the value given is in Windows Integer (NTFS Filetime) format. + */ + protected function valueIsWindowsIntegerType(mixed $value): bool + { + return is_numeric($value) && in_array(strlen((string) $value), [18, 19]); + } + + /** + * Converts the LDAP timestamp value to a Carbon instance. + * + * @throws LdapRecordException + */ + public function toDateTime(mixed $value): Carbon|int|false + { + $value = is_array($value) ? reset($value) : $value; + + if ($value instanceof CarbonInterface || $value instanceof DateTime) { + return Carbon::instance($value); + } + + $value = match ($this->type) { + Timestamp::TYPE_LDAP => $this->convertLdapTimeToDateTime($value), + Timestamp::TYPE_WINDOWS => $this->convertWindowsTimeToDateTime($value), + Timestamp::TYPE_WINDOWS_INT => $this->convertWindowsIntegerTimeToDateTime($value), + default => throw new LdapRecordException("Unrecognized date type [{$this->type}]"), + }; + + return $value instanceof DateTime ? Carbon::instance($value) : $value; + } + + /** + * Converts standard LDAP timestamps to a date time object. + */ + protected function convertLdapTimeToDateTime(string $value): DateTime|false + { + return DateTime::createFromFormat(match (true) { + str_ends_with($value, '.000Z') => 'YmdHis.000\Z', + str_ends_with($value, '.0Z') => 'YmdHis.0\Z', + str_ends_with($value, 'Z') => 'YmdHis\Z', + default => 'YmdHisT', + }, $value); + } + + /** + * Converts date objects to a standard LDAP timestamp. + */ + protected function convertDateTimeToLdapTime(DateTime $date): string + { + return $date->format( + $date->getOffset() == 0 + ? 'YmdHis\Z' + : 'YmdHisO' + ); + } + + /** + * Converts standard windows timestamps to a date time object. + */ + protected function convertWindowsTimeToDateTime(string $value): DateTime|false + { + return DateTime::createFromFormat(match (true) { + str_ends_with($value, '.0Z') => 'YmdHis.0\Z', + default => 'YmdHis.0T' + }, $value, new DateTimeZone('UTC')); + } + + /** + * Converts date objects to a windows timestamp. + */ + protected function convertDateTimeToWindows(DateTime $date): string + { + return $date->format( + $date->getOffset() == 0 + ? 'YmdHis.0\Z' + : 'YmdHis.0O' + ); + } + + /** + * Converts standard windows integer dates to a date time object. + * + * @throws \Exception + */ + protected function convertWindowsIntegerTimeToDateTime(string|int|null $value = null): DateTime|int|false + { + if (is_null($value) || $value === '') { + return false; + } + + if ($value == 0) { + return (int) $value; + } + + if ($value == static::WINDOWS_INT_MAX) { + return (int) $value; + } + + return (new DateTime)->setTimestamp( + (int) ($value / 10000000) - 11644473600 + ); + } + + /** + * Converts date objects to a windows integer timestamp. + */ + protected function convertDateTimeToWindowsInteger(DateTime $date): int + { + return ($date->getTimestamp() + 11644473600) * 10000000; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/BatchModification.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/BatchModification.php new file mode 100644 index 0000000000..3b153095d5 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/BatchModification.php @@ -0,0 +1,251 @@ +setAttribute($attribute) + ->setType($type) + ->setValues($values); + } + + /** + * Make a new batch modification instance. + */ + public static function make(?string $attribute = null, ?int $type = null, array $values = []): self + { + return new static($attribute, $type, $values); + } + + /** + * Set the original value of the attribute before modification. + */ + public function setOriginal(array|string $original = []): static + { + $this->original = $this->normalizeAttributeValues($original); + + return $this; + } + + /** + * Get the original value of the attribute before modification. + */ + public function getOriginal(): array + { + return $this->original; + } + + /** + * Set the attribute of the modification. + */ + public function setAttribute(?string $attribute = null): static + { + $this->attribute = $attribute; + + return $this; + } + + /** + * Get the attribute of the modification. + */ + public function getAttribute(): string + { + return $this->attribute; + } + + /** + * Set the values of the modification. + */ + public function setValues(array $values = []): static + { + // Null and empty values must also not be added to a batch + // modification. Passing null or empty values will result + // in an exception when trying to save the modification. + $this->values = array_filter($this->normalizeAttributeValues($values), function ($value) { + return is_numeric($value) && $this->valueIsResetInteger((int) $value) || ! empty($value); + }); + + return $this; + } + + /** + * Normalize all the attribute values. + */ + protected function normalizeAttributeValues(array|string $values = []): array + { + // We must convert all the values to strings. Only strings can + // be used in batch modifications, otherwise we will we will + // receive an LDAP exception while attempting to save. + return array_map('strval', (array) $values); + } + + /** + * Get the values of the modification. + */ + public function getValues(): array + { + return $this->values; + } + + /** + * Set the type of the modification. + */ + public function setType(?int $type = null): static + { + if (is_null($type)) { + return $this; + } + + if (! $this->isValidType($type)) { + throw new InvalidArgumentException('Given batch modification type is invalid.'); + } + + $this->type = $type; + + return $this; + } + + /** + * Get the type of the modification. + */ + public function getType(): ?int + { + return $this->type; + } + + /** + * Determine if the batch modification is valid in its current state. + */ + public function isValid(): bool + { + return ! is_null($this->get()); + } + + /** + * Builds the type of modification automatically + * based on the current and original values. + */ + public function build(): static + { + return match (true) { + empty($this->original) && empty($this->values) => $this, + + ! empty($this->original) && empty($this->values) => $this->setType(LDAP_MODIFY_BATCH_REMOVE_ALL), + + empty($this->original) && ! empty($this->values) => $this->setType(LDAP_MODIFY_BATCH_ADD), + + default => $this->determineBatchTypeFromOriginal(), + }; + } + + /** + * Determine the batch modification type from the original values. + */ + protected function determineBatchTypeFromOriginal(): static + { + $added = $this->getAddedValues(); + $removed = $this->getRemovedValues(); + + return match (true) { + ! empty($added) && ! empty($removed) => $this->setType(LDAP_MODIFY_BATCH_REPLACE), + + ! empty($added) => $this->setValues($added)->setType(LDAP_MODIFY_BATCH_ADD), + + ! empty($removed) => $this->setValues($removed)->setType(LDAP_MODIFY_BATCH_REMOVE), + + default => $this, + }; + } + + /** + * Get the values that were added to the attribute. + */ + protected function getAddedValues(): array + { + return array_values( + array_diff($this->values, $this->original) + ); + } + + /** + * Get the values that were removed from the attribute. + */ + protected function getRemovedValues(): array + { + return array_values( + array_diff($this->original, $this->values) + ); + } + + /** + * Get the batch modification array. + */ + public function get(): ?array + { + return match ($this->type) { + LDAP_MODIFY_BATCH_REMOVE_ALL => [ + static::KEY_ATTRIB => $this->attribute, + static::KEY_MODTYPE => $this->type, + ], + + LDAP_MODIFY_BATCH_REMOVE, LDAP_MODIFY_BATCH_ADD, LDAP_MODIFY_BATCH_REPLACE => [ + static::KEY_ATTRIB => $this->attribute, + static::KEY_MODTYPE => $this->type, + static::KEY_VALUES => $this->values, + ], + + default => null, + }; + } + + /** + * Determine if the given modtype is valid. + */ + protected function isValidType(int $type): bool + { + return in_array($type, [ + LDAP_MODIFY_BATCH_REMOVE_ALL, + LDAP_MODIFY_BATCH_REMOVE, + LDAP_MODIFY_BATCH_REPLACE, + LDAP_MODIFY_BATCH_ADD, + ]); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Collection.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Collection.php new file mode 100644 index 0000000000..a0d8126a0b --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Collection.php @@ -0,0 +1,110 @@ +map(function (Model $model) { + return $model->getDn(); + }); + } + + /** + * Determine if the collection contains all the given models, or any models. + * + * @param QueryCollection|Model|array|string|null $models + */ + public function exists(mixed $models = null): bool + { + $models = $this->getArrayableModels($models); + + // If any arguments were given and the result set is + // empty, we can simply return false here. We can't + // verify the existence of models without results. + if (func_num_args() > 0 && empty(array_filter($models))) { + return false; + } + + if (! $models) { + return parent::isNotEmpty(); + } + + foreach ($models as $model) { + $exists = parent::contains(function (Model $related) use ($model) { + return $this->compareModelWithRelated($model, $related); + }); + + if (! $exists) { + return false; + } + } + + return true; + } + + /** + * Determine if any of the given models are contained in the collection. + */ + public function contains($key, $operator = null, $value = null): bool + { + if (func_num_args() > 1 || $key instanceof Closure) { + return parent::contains(...func_get_args()); + } + + foreach ($this->getArrayableModels($key) as $model) { + $exists = parent::contains(function (Model $related) use ($model) { + return $this->compareModelWithRelated($model, $related); + }); + + if ($exists) { + return true; + } + } + + return false; + } + + /** + * Get the provided models as an array. + */ + protected function getArrayableModels(mixed $models = null): array + { + if ($models instanceof QueryCollection) { + return $models->all(); + } + + return Arr::wrap($models); + } + + /** + * Compare the related model with the given. + */ + protected function compareModelWithRelated(Model|string $model, Model $related): bool + { + if (is_string($model)) { + return $this->isValidDn($model) + ? strcasecmp($related->getDn(), $model) === 0 + : strcasecmp($related->getName(), $model) === 0; + } + + return $related->is($model); + } + + /** + * Determine if the given string is a valid distinguished name. + */ + protected function isValidDn(string $dn): bool + { + return ! empty((new DistinguishedName($dn))->components()); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/CanAuthenticate.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/CanAuthenticate.php new file mode 100644 index 0000000000..e52657a444 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/CanAuthenticate.php @@ -0,0 +1,65 @@ +guidKey; + } + + /** + * Get the unique identifier for the user. + */ + public function getAuthIdentifier(): string + { + return $this->getConvertedGuid( + $this->getFirstAttribute($this->getAuthIdentifierName()) + ); + } + + /** + * Get the password for the user. + */ + public function getAuthPassword(): string + { + return ''; + } + + /** + * Get the name of the password attribute for the user. + */ + public function getAuthPasswordName(): string + { + return 'password'; + } + + /** + * Get the token value for the "remember me" session. + */ + public function getRememberToken(): string + { + return ''; + } + + /** + * Set the token value for the "remember me" session. + */ + public function setRememberToken($value): void + { + // Do nothing. + } + + /** + * Get the column name for the "remember me" token. + */ + public function getRememberTokenName(): string + { + return ''; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasAttributes.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasAttributes.php new file mode 100644 index 0000000000..67b4cf7037 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasAttributes.php @@ -0,0 +1,1078 @@ + 'ldap', + 'modifytimestamp' => 'ldap', + ]; + + /** + * The cache of the mutated attributes for each class. + */ + protected static array $mutatorCache = []; + + /** + * Convert the model's original attributes to an array. + */ + public function originalToArray(): array + { + return $this->encodeAttributes( + $this->convertAttributesForJson($this->original) + ); + } + + /** + * Convert the model's attributes to an array. + */ + public function attributesToArray(): array + { + // Here we will replace our LDAP formatted dates with + // properly formatted ones, so dates do not need to + // be converted manually after being returned. + $attributes = $this->addDateAttributesToArray( + $this->getArrayableAttributes() + ); + + $attributes = $this->addMutatedAttributesToArray( + $attributes, + $this->getMutatedAttributes() + ); + + // Before we go ahead and encode each value, we'll attempt + // converting any necessary attribute values to ensure + // they can be encoded, such as GUIDs and SIDs. + $attributes = $this->convertAttributesForJson($attributes); + + // Here we will grab all of the appended, calculated attributes to this model + // as these attributes are not really in the attributes array, but are run + // when we need to array or JSON the model for convenience to the coder. + foreach ($this->getArrayableAppends() as $key) { + $attributes[$key] = $this->mutateAttributeForArray($key, null); + } + + // Now we will go through each attribute to make sure it is + // properly encoded. If attributes aren't in UTF-8, we will + // encounter JSON encoding errors upon model serialization. + return $this->encodeAttributes($attributes); + } + + /** + * Convert the model's serialized original attributes to their original form. + */ + public function arrayToOriginal(array $attributes): array + { + $attributes = $this->decodeAttributes($attributes); + + return $this->convertAttributesFromJson($attributes); + } + + /** + * Convert the model's serialized attributes to their original form. + */ + public function arrayToAttributes(array $attributes): array + { + $attributes = $this->restoreDateAttributesFromArray($attributes); + + $attributes = $this->decodeAttributes($attributes); + + return $this->convertAttributesFromJson($attributes); + } + + /** + * Add the date attributes to the attributes array. + */ + protected function addDateAttributesToArray(array $attributes): array + { + foreach ($this->getDates() as $attribute => $type) { + if (! isset($attributes[$attribute])) { + continue; + } + + $date = $this->asDateTime($attributes[$attribute], $type); + + $attributes[$attribute] = $date instanceof Carbon + ? Arr::wrap($this->serializeDate($date)) + : $attributes[$attribute]; + } + + return $attributes; + } + + /** + * Restore the date attributes to their true value from serialized attributes. + */ + protected function restoreDateAttributesFromArray(array $attributes): array + { + foreach ($this->getDates() as $attribute => $type) { + if (! isset($attributes[$attribute])) { + continue; + } + + $date = $this->fromDateTime($attributes[$attribute], $type); + + $attributes[$attribute] = Arr::wrap($date); + } + + return $attributes; + } + + /** + * Prepare a date for array / JSON serialization. + */ + protected function serializeDate(DateTimeInterface $date): string + { + return $date->format($this->getDateFormat()); + } + + /** + * Recursively UTF-8 encode the given attributes. + */ + protected function encodeAttributes($attributes): array + { + array_walk_recursive($attributes, function (&$value) { + $value = $this->encodeValue($value); + }); + + return $attributes; + } + + /** + * Recursively UTF-8 decode the given attributes. + */ + public function decodeAttributes(array $attributes): array + { + array_walk_recursive($attributes, function (&$value) { + $value = $this->decodeValue($value); + }); + + return $attributes; + } + + /** + * Encode the value for serialization. + */ + protected function encodeValue(string $value): string + { + // If we are able to detect the encoding, we will + // encode only the attributes that need to be, + // so that we do not double encode values. + if (MbString::isLoaded() && MbString::isUtf8($value)) { + return $value; + } + + return mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1'); + } + + /** + * Decode the value from serialization. + */ + protected function decodeValue(string $value): string + { + if (MbString::isLoaded() && ! MbString::isUtf8($value)) { + return mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1'); + } + + return $value; + } + + /** + * Add the mutated attributes to the attributes array. + */ + protected function addMutatedAttributesToArray(array $attributes, array $mutatedAttributes): array + { + foreach ($mutatedAttributes as $key) { + // We want to spin through all the mutated attributes for this model and call + // the mutator for the attribute. We cache off every mutated attributes so + // we don't have to constantly check on attributes that actually change. + if (! Arr::exists($attributes, $key)) { + continue; + } + + // Next, we will call the mutator for this attribute so that we can get these + // mutated attribute's actual values. After we finish mutating each of the + // attributes we will return this final array of the mutated attributes. + $attributes[$key] = $this->mutateAttributeForArray( + $key, + $attributes[$key] + ); + } + + return $attributes; + } + + /** + * Set the model's original attributes with the model's current attributes. + */ + public function syncOriginal(): static + { + $this->original = $this->attributes; + + return $this; + } + + /** + * Sync the changed attributes. + */ + public function syncChanges(): static + { + $this->changes = $this->getDirty(); + + return $this; + } + + /** + * Fills the entry with the supplied attributes. + */ + public function fill(array $attributes = []): static + { + foreach ($attributes as $key => $value) { + $this->setAttribute($key, $value); + } + + return $this; + } + + /** + * Get the models attribute by its key. + */ + public function getAttribute(?string $key = null, mixed $default = null): mixed + { + if (! $key) { + return null; + } + + return $this->getAttributeValue($key, $default); + } + + /** + * Get an attribute's value. + */ + public function getAttributeValue(string $key, mixed $default = null): mixed + { + $key = $this->normalizeAttributeKey($key); + $value = $this->getAttributeFromArray($key); + + if ($this->hasGetMutator($key)) { + return $this->getMutatedAttributeValue($key, $value); + } + + if ($this->isDateAttribute($key) && ! is_null($value)) { + return $this->asDateTime(Arr::first($value), $this->getDates()[$key]); + } + + if ($this->isCastedAttribute($key) && ! is_null($value)) { + return $this->castAttribute($key, $value); + } + + return is_null($value) ? $default : $value; + } + + /** + * Get the model's raw attribute value. + */ + public function getRawAttribute(string $key, mixed $default = null): mixed + { + return Arr::get($this->attributes, $this->normalizeAttributeKey($key), $default); + } + + /** + * Determine if the given attribute is a date. + */ + public function isDateAttribute(string $key): bool + { + return array_key_exists($key, $this->getDates()); + } + + /** + * Get the attributes that should be mutated to dates. + */ + public function getDates(): array + { + // Since array string keys can be unique depending + // on casing differences, we need to normalize the + // array key case so they are merged properly. + return array_merge( + array_change_key_case($this->defaultDates), + array_change_key_case($this->dates), + array_change_key_case($this->getDateCasts()), + ); + } + + /** + * Get the attributes casts that should be mutated to dates. + */ + protected function getDateCasts(): array + { + return array_map(function (string $cast) { + return explode(':', $cast, 2)[1] ?? throw new RuntimeException( + "Invalid date cast [$cast]. A date cast must be in the format 'datetime:format'." + ); + }, array_filter($this->getCasts(), function ($cast) { + return $this->isDateTimeCast($cast); + })); + } + + /** + * Convert the given date value to an LDAP compatible value. + * + * @throws LdapRecordException + */ + public function fromDateTime(mixed $value, string $type): float|int|string + { + return (new Timestamp($type))->fromDateTime($value); + } + + /** + * Convert the given LDAP date value to a Carbon instance. + * + * @throws LdapRecordException + */ + public function asDateTime(mixed $value, string $type): Carbon|int|false + { + return (new Timestamp($type))->toDateTime($value); + } + + /** + * Determine whether an attribute should be cast to a native type. + */ + public function hasCast(string $key, array|string|null $types = null): bool + { + if (array_key_exists($key, $this->getCasts())) { + return ! $types || in_array($this->getCastType($key), (array) $types, true); + } + + return false; + } + + /** + * Get the attributes that should be cast to their native types. + */ + protected function getCasts(): array + { + return array_change_key_case($this->casts); + } + + /** + * Determine whether a value is JSON castable for inbound manipulation. + */ + protected function isJsonCastable(string $key): bool + { + return $this->hasCast($key, ['array', 'json', 'object', 'collection']); + } + + /** + * Get the type of cast for a model attribute. + */ + protected function getCastType(string $key): string + { + if ($this->isDecimalCast($this->getCasts()[$key])) { + return 'decimal'; + } + + if ($this->isDateTimeCast($this->getCasts()[$key])) { + return 'datetime'; + } + + return trim(strtolower($this->getCasts()[$key])); + } + + /** + * Determine if the cast is a decimal. + */ + protected function isDecimalCast(string $cast): bool + { + return strncmp($cast, 'decimal:', 8) === 0; + } + + /** + * Determine if the cast is a datetime. + */ + protected function isDateTimeCast(string $cast): bool + { + return strncmp($cast, 'datetime:', 8) === 0; + } + + /** + * Determine if the given attribute must be casted. + */ + protected function isCastedAttribute(string $key): bool + { + return array_key_exists($key, array_change_key_case($this->casts)); + } + + /** + * Cast an attribute to a native PHP type. + */ + protected function castAttribute(string $key, ?array $value): mixed + { + $value = $this->castRequiresArrayValue($key) ? $value : Arr::first($value); + + if (is_null($value)) { + return $value; + } + + switch ($this->getCastType($key)) { + case 'int': + case 'integer': + return (int) $value; + case 'real': + case 'float': + case 'double': + return $this->fromFloat($value); + case 'decimal': + return $this->asDecimal($value, explode(':', $this->getCasts()[$key], 2)[1]); + case 'string': + return (string) $value; + case 'bool': + case 'boolean': + return $this->asBoolean($value); + case 'object': + return $this->fromJson($value, true); + case 'array': + case 'json': + return $this->fromJson($value); + case 'collection': + return $this->newCollection($value); + case 'datetime': + return $this->asDateTime($value, explode(':', $this->getCasts()[$key], 2)[1]); + default: + return $value; + } + } + + /** + * Determine if the cast type requires the first attribute value. + */ + protected function castRequiresArrayValue(string $key): bool + { + return $this->getCastType($key) === 'collection'; + } + + /** + * Cast the given attribute to JSON. + */ + protected function castAttributeAsJson(string $key, mixed $value): string + { + $value = $this->asJson($value); + + if ($value === false) { + $class = get_class($this); + $message = json_last_error_msg(); + + throw new Exception("Unable to encode attribute [$key] for model [$class] to JSON: $message."); + } + + return $value; + } + + /** + * Cast the given attribute to an LDAP primitive type. + */ + protected function castAttributeAsPrimitive(string $key, mixed $value): string + { + return match ($this->getCastType($key)) { + 'bool', 'boolean' => $this->fromBoolean($value), + default => (string) $value, + }; + } + + /** + * Convert the model to its JSON representation. + */ + public function toJson(): string + { + return json_encode($this); + } + + /** + * Encode the given value as JSON. + */ + protected function asJson(mixed $value): string|false + { + return json_encode($value); + } + + /** + * Decode the given JSON back into an array or object. + */ + public function fromJson(string $value, bool $asObject = false): mixed + { + return json_decode($value, ! $asObject); + } + + /** + * Decode the given float. + */ + public function fromFloat(float $value): float + { + return match ((string) $value) { + 'NaN' => NAN, + 'Infinity' => INF, + '-Infinity' => -INF, + default => $value, + }; + } + + /** + * Cast the value from an LDAP boolean string to a primitive boolean. + */ + protected function asBoolean(mixed $value): bool + { + return match (strtolower($value)) { + 'true' => true, + 'false' => false, + default => (bool) $value, + }; + } + + /** + * Cast the value from a primitive boolean to an LDAP boolean string. + */ + protected function fromBoolean(mixed $value): string + { + if (is_string($value)) { + $value = $this->asBoolean($value); + } + + return $value ? 'TRUE' : 'FALSE'; + } + + /** + * Cast a decimal value as a string. + */ + protected function asDecimal(float $value, int $decimals): string + { + return number_format($value, $decimals, '.', ''); + } + + /** + * Get an attribute array of all arrayable attributes. + */ + protected function getArrayableAttributes(): array + { + return $this->getArrayableItems($this->attributes); + } + + /** + * Get an attribute array of all arrayable values. + */ + protected function getArrayableItems(array $values): array + { + if (count($visible = $this->getVisible()) > 0) { + $values = array_intersect_key($values, array_flip($visible)); + } + + if (count($hidden = $this->getHidden()) > 0) { + $values = array_diff_key($values, array_flip($hidden)); + } + + return $values; + } + + /** + * Get all the appendable values that are arrayable. + */ + protected function getArrayableAppends(): array + { + if (empty($this->appends)) { + return []; + } + + return $this->getArrayableItems( + array_combine($this->appends, $this->appends) + ); + } + + /** + * Get the format for date serialization. + */ + public function getDateFormat(): string + { + return $this->dateFormat ?: DateTimeInterface::ISO8601; + } + + /** + * Set the date format used by the model for serialization. + */ + public function setDateFormat(string $format): static + { + $this->dateFormat = $format; + + return $this; + } + + /** + * Get an attribute from the $attributes array. + */ + protected function getAttributeFromArray(string $key): mixed + { + return $this->getNormalizedAttributes()[$key] ?? null; + } + + /** + * Get the attributes with their keys normalized. + */ + protected function getNormalizedAttributes(): array + { + return array_change_key_case($this->attributes); + } + + /** + * Get the first attribute by the specified key. + */ + public function getFirstAttribute(string $key, mixed $default = null): mixed + { + return Arr::first( + Arr::wrap($this->getAttribute($key, $default)), + ); + } + + /** + * Returns all the model's attributes. + */ + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * Set an attribute value by the specified key. + */ + public function setAttribute(string $key, mixed $value): static + { + $key = $this->normalizeAttributeKey($key); + + if ($this->hasSetMutator($key)) { + return $this->setMutatedAttributeValue($key, $value); + } + + if ( + $value && + $this->isDateAttribute($key) && + ! $this->valueIsResetInteger($value) + ) { + $value = (string) $this->fromDateTime($value, $this->getDates()[$key]); + } + + if ($this->isJsonCastable($key) && ! is_null($value)) { + $value = $this->castAttributeAsJson($key, $value); + } elseif ($this->hasCast($key) && ! is_null($value)) { + $value = $this->castAttributeAsPrimitive($key, $value); + } + + $this->attributes[$key] = Arr::wrap($value); + + return $this; + } + + /** + * Set an attribute on the model. No checking is done. + */ + public function setRawAttribute(string $key, mixed $value): static + { + $key = $this->normalizeAttributeKey($key); + + $this->attributes[$key] = Arr::wrap($value); + + return $this; + } + + /** + * Set the models first attribute value. + */ + public function setFirstAttribute(string $key, mixed $value): static + { + return $this->setAttribute($key, Arr::wrap($value)); + } + + /** + * Add a unique value to the given attribute. + */ + public function addAttributeValue(string $key, mixed $value): static + { + return $this->setRawAttribute($key, array_unique(array_merge( + $this->getRawAttribute($key, []), + Arr::wrap($value) + ))); + } + + /** + * Remove a unique value from the given attribute. + */ + public function removeAttributeValue(string $key, mixed $value): static + { + $values = $this->getRawAttribute($key, []); + + foreach (Arr::wrap($value) as $value) { + $index = array_search($value, $values); + + if ($index !== false) { + unset($values[$index]); + } + } + + return $this->setRawAttribute($key, array_values($values)); + } + + /** + * Determine if a get mutator exists for an attribute. + */ + public function hasGetMutator(string $key): bool + { + return method_exists($this, 'get'.$this->getMutatorMethodName($key).'Attribute'); + } + + /** + * Determine if a set mutator exists for an attribute. + */ + public function hasSetMutator(string $key): bool + { + return method_exists($this, 'set'.$this->getMutatorMethodName($key).'Attribute'); + } + + /** + * Set the value of an attribute using its mutator. + */ + protected function setMutatedAttributeValue(string $key, mixed $value): static + { + $this->{'set'.$this->getMutatorMethodName($key).'Attribute'}($value); + + return $this; + } + + /** + * Get the value of an attribute using its mutator. + */ + protected function getMutatedAttributeValue(string $key, mixed $value): mixed + { + return $this->{'get'.$this->getMutatorMethodName($key).'Attribute'}($value); + } + + /** + * Get the mutator attribute method name. + * + * Hyphenated attributes will use pascal cased methods. + */ + protected function getMutatorMethodName(string $key): string + { + $key = ucwords(str_replace('-', ' ', $key)); + + return str_replace(' ', '', $key); + } + + /** + * Get the value of an attribute using its mutator for array conversion. + */ + protected function mutateAttributeForArray(string $key, mixed $value): array + { + return Arr::wrap( + $this->getMutatedAttributeValue($key, $value) + ); + } + + /** + * Set the raw model attributes. + * + * Used when constructing an existing LDAP record. + */ + public function setRawAttributes(array $attributes = []): static + { + // We will filter out those annoying 'count' keys + // returned with LDAP results and lowercase all + // root array keys to prevent any casing issues. + $raw = array_change_key_case($this->filterRawAttributes($attributes)); + + // Before setting the models attributes, we will filter + // out the attributes that contain an integer key. LDAP + // search results will contain integer keys that have + // attribute names as values. We don't need these. + $this->attributes = array_filter($raw, fn ($key) => ! is_int($key), ARRAY_FILTER_USE_KEY); + + // LDAP search results will contain the distinguished + // name inside of the `dn` key. We will retrieve this, + // and then set it on the model for accessibility. + if (Arr::exists($attributes, 'dn')) { + $this->dn = Arr::accessible($attributes['dn']) + ? Arr::first($attributes['dn']) + : $attributes['dn']; + } + + $this->syncOriginal(); + + // Here we will set the exists attribute to true, + // since raw attributes are only set in the case + // of attributes being loaded by query results. + $this->exists = true; + + return $this; + } + + /** + * Filters the count key recursively from raw LDAP attributes. + */ + public function filterRawAttributes(array $attributes = [], array $keys = ['count', 'dn']): array + { + foreach ($keys as $key) { + unset($attributes[$key]); + } + + foreach ($attributes as $key => $value) { + $attributes[$key] = is_array($value) + ? $this->filterRawAttributes($value, $keys) + : $value; + } + + return $attributes; + } + + /** + * Determine if the model has the given attribute. + */ + public function hasAttribute(int|string $key): bool + { + return ($this->attributes[$this->normalizeAttributeKey($key)] ?? []) !== []; + } + + /** + * Get the number of attributes. + */ + public function countAttributes(): int + { + return count($this->getAttributes()); + } + + /** + * Get the model's original attributes. + */ + public function getOriginal(): array + { + return $this->original; + } + + /** + * Get the model's raw original attribute values. + */ + public function getRawOriginal(string $key, mixed $default = null): mixed + { + return Arr::get($this->original, $key, $default); + } + + /** + * Get the attributes that have been changed since last sync. + */ + public function getDirty(): array + { + $dirty = []; + + foreach ($this->attributes as $key => $value) { + if ($this->isDirty($key)) { + // We need to reset the array using array_values due to + // LDAP requiring consecutive indices (0, 1, 2 etc.). + // We would receive an exception otherwise. + $dirty[$key] = array_values($value); + } + } + + return $dirty; + } + + /** + * Get the attributes that have been changed since the model was last saved. + */ + public function getChanges(): array + { + return $this->changes; + } + + /** + * Determine if the given attribute is dirty. + */ + public function isDirty(string $key): bool + { + return ! $this->originalIsEquivalent($key); + } + + /** + * Determine if given attribute has remained the same. + */ + public function isClean(string $key): bool + { + return ! $this->isDirty($key); + } + + /** + * Discard attribute changes and reset the attributes to their original state. + */ + public function discardChanges(): static + { + [$this->attributes, $this->changes] = [$this->original, []]; + + return $this; + } + + /** + * Determine if the model or any of the given attribute(s) were changed when the model was last saved. + */ + public function wasChanged(array|string|null $attributes = null): bool + { + if (func_num_args() === 0) { + return count($this->changes) > 0; + } + + foreach ((array) $attributes as $attribute) { + if (array_key_exists($attribute, $this->changes)) { + return true; + } + } + + return false; + } + + /** + * Get the accessors being appended to the models array form. + */ + public function getAppends(): array + { + return $this->appends; + } + + /** + * Set the accessors to append to model arrays. + */ + public function setAppends(array $appends): static + { + $this->appends = $appends; + + return $this; + } + + /** + * Return whether the accessor attribute has been appended. + */ + public function hasAppended(string $attribute): bool + { + return in_array($attribute, $this->appends); + } + + /** + * Returns a normalized attribute key. + */ + public function normalizeAttributeKey(string $key): string + { + // Since LDAP supports hyphens in attribute names, + // we'll convert attributes being retrieved by + // underscores into hyphens for convenience. + return strtolower( + str_replace('_', '-', $key) + ); + } + + /** + * Determine if the new and old values for a given key are equivalent. + */ + protected function originalIsEquivalent(string $key): bool + { + if (! array_key_exists($key, $this->original)) { + return false; + } + + $current = $this->attributes[$key]; + $original = $this->original[$key]; + + if ($current === $original) { + return true; + } + + return is_numeric($current) && + is_numeric($original) && + strcmp((string) $current, (string) $original) === 0; + } + + /** + * Get the mutated attributes for a given instance. + */ + public function getMutatedAttributes(): array + { + $class = static::class; + + if (! isset(static::$mutatorCache[$class])) { + static::cacheMutatedAttributes($class); + } + + return static::$mutatorCache[$class]; + } + + /** + * Extract and cache all the mutated attributes of a class. + */ + public static function cacheMutatedAttributes(string $class): void + { + static::$mutatorCache[$class] = collect(static::getMutatorMethods($class)) + ->reject(fn ($match) => $match === 'First') + ->map(fn ($match) => lcfirst($match)) + ->all(); + } + + /** + * Get all of the attribute mutator methods. + */ + protected static function getMutatorMethods(string $class): array + { + preg_match_all('/(?<=^|;)get([^;]+?)Attribute(;|$)/', implode(';', get_class_methods($class)), $matches); + + return $matches[1]; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasEvents.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasEvents.php new file mode 100644 index 0000000000..7537f62f6e --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasEvents.php @@ -0,0 +1,73 @@ +getDispatcher(); + + if ($dispatcher) { + $container->setDispatcher( + new NullDispatcher($dispatcher) + ); + } + + try { + return $callback(); + } finally { + if ($dispatcher) { + $container->setDispatcher($dispatcher); + } + } + } + + /** + * Dispatch the given model events. + */ + protected function dispatch(array|string $events, array $args = []): void + { + foreach (Arr::wrap($events) as $name) { + $this->fireCustomModelEvent($name, $args); + } + } + + /** + * Fire a custom model event. + */ + protected function fireCustomModelEvent(string $name, array $args = []): void + { + $event = implode('\\', [Events::class, ucfirst($name)]); + + $this->fireModelEvent(new $event($this, ...$args)); + } + + /** + * Fire a model event. + */ + protected function fireModelEvent(Event $event): void + { + static::getConnectionContainer()->getDispatcher()->fire($event); + } + + /** + * Listen to a model event. + */ + protected function listenForModelEvent(string $event, Closure $listener): void + { + static::getConnectionContainer()->getDispatcher()->listen($event, $listener); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasGlobalScopes.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasGlobalScopes.php new file mode 100644 index 0000000000..855ba37c35 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasGlobalScopes.php @@ -0,0 +1,63 @@ +assertSecureConnection(); + + // Here we will attempt to determine the password hash method in use + // by parsing the users hashed password (if it as available). If a + // method is determined, we will override the default here. + if (! ($method = $this->determinePasswordHashMethod())) { + $method = $this->getPasswordHashMethod(); + } + + // If the password given is an array, we can assume we + // are changing the password for the current user. + if (is_array($password)) { + $this->setChangedPassword( + $this->getHashedPassword($method, $password[0], $this->getPasswordSalt($method)), + $this->getHashedPassword($method, $password[1]), + $this->getPasswordAttributeName() + ); + } + // Otherwise, we will assume the password is being + // reset, overwriting the one currently in place. + else { + $this->setPassword( + $this->getHashedPassword($method, $password), + $this->getPasswordAttributeName() + ); + } + } + + /** + * Alias for setting the password on the user. + * + * @throws ConnectionException + */ + public function setUnicodepwdAttribute(array|string $password): void + { + $this->setPasswordAttribute($password); + } + + /** + * An accessor for retrieving the user's hashed password value. + */ + public function getPasswordAttribute(): ?string + { + return $this->getAttribute($this->getPasswordAttributeName())[0] ?? null; + } + + /** + * Get the name of the attribute that contains the user's password. + */ + public function getPasswordAttributeName(): string + { + if (property_exists($this, 'passwordAttribute')) { + return $this->passwordAttribute; + } + + if (method_exists($this, 'passwordAttribute')) { + return $this->passwordAttribute(); + } + + return 'unicodepwd'; + } + + /** + * Get the name of the method to use for hashing the user's password. + */ + public function getPasswordHashMethod(): string + { + if (property_exists($this, 'passwordHashMethod')) { + return $this->passwordHashMethod; + } + + if (method_exists($this, 'passwordHashMethod')) { + return $this->passwordHashMethod(); + } + + return 'encode'; + } + + /** + * Set the changed password. + */ + protected function setChangedPassword(string $oldPassword, string $newPassword, string $attribute): void + { + // Create batch modification for removing the old password. + $this->addModification( + $this->newBatchModification( + $attribute, + LDAP_MODIFY_BATCH_REMOVE, + [$oldPassword] + ) + ); + + // Create batch modification for adding the new password. + $this->addModification( + $this->newBatchModification( + $attribute, + LDAP_MODIFY_BATCH_ADD, + [$newPassword] + ) + ); + } + + /** + * Set the password on the model. + */ + protected function setPassword(string $password, string $attribute): void + { + if (! $this->exists) { + $this->setRawAttribute($attribute, $password); + + return; + } + + $this->addModification( + $this->newBatchModification( + $attribute, + LDAP_MODIFY_BATCH_REPLACE, + [$password] + ) + ); + } + + /** + * Encode / hash the given password. + * + * @throws LdapRecordException + */ + protected function getHashedPassword(string $method, string $password, ?string $salt = null): string + { + if (! method_exists(Password::class, $method)) { + throw new LdapRecordException("Password hashing method [{$method}] does not exist."); + } + + if (Password::hashMethodRequiresSalt($method)) { + return Password::{$method}($password, $salt); + } + + return Password::{$method}($password); + } + + /** + * Validates that the current LDAP connection is secure. + * + * @throws ConnectionException + */ + protected function assertSecureConnection(): void + { + $connection = $this->getConnection(); + + $config = $connection->getConfiguration(); + + if ($config->get('allow_insecure_password_changes') === true) { + return; + } + + if ($connection->isConnected()) { + $secure = $connection->getLdapConnection()->canChangePasswords(); + } else { + $secure = $config->get('use_ssl') || $config->get('use_tls'); + } + + if (! $secure) { + throw new ConnectionException( + 'You must be connected to your LDAP server with TLS or SSL to perform this operation.' + ); + } + } + + /** + * Attempt to retrieve the password's salt. + */ + public function getPasswordSalt(string $method): ?string + { + if (! Password::hashMethodRequiresSalt($method)) { + return null; + } + + return Password::getSalt($this->password); + } + + /** + * Determine the password hash method to use from the users current password. + * + * @return string|void + */ + public function determinePasswordHashMethod() + { + if (! $password = $this->password) { + return; + } + + if (! $method = Password::getHashMethod($password)) { + return; + } + + [,$algo] = array_pad( + Password::getHashMethodAndAlgo($password) ?? [], + $length = 2, + $value = null + ); + + return match ((int) $algo) { + Password::CRYPT_SALT_TYPE_MD5 => 'md5'.$method, + Password::CRYPT_SALT_TYPE_SHA256 => 'sha256'.$method, + Password::CRYPT_SALT_TYPE_SHA512 => 'sha512'.$method, + default => $method, + }; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasRelationships.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasRelationships.php new file mode 100644 index 0000000000..e81c3a15e9 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasRelationships.php @@ -0,0 +1,68 @@ +newQuery(), $this, $related, $relationKey, $foreignKey); + } + + /** + * Returns a new has many relationship. + */ + public function hasMany(array|string $related, string $relationKey, string $foreignKey = 'dn'): HasMany + { + return new HasMany($this->newQuery(), $this, $related, $relationKey, $foreignKey, $this->guessRelationshipName()); + } + + /** + * Returns a new has many in relationship. + */ + public function hasManyIn(array|string $related, string $relationKey, string $foreignKey = 'dn'): HasManyIn + { + return new HasManyIn($this->newQuery(), $this, $related, $relationKey, $foreignKey, $this->guessRelationshipName()); + } + + /** + * Get a relationship by its name. + */ + public function getRelation(?string $relationName = null): ?Relation + { + if (is_null($relationName)) { + return null; + } + + if (! method_exists($this, $relationName)) { + return null; + } + + if (! $relation = $this->{$relationName}()) { + return null; + } + + if (! $relation instanceof Relation) { + return null; + } + + return $relation; + } + + /** + * Get the relationships name. + */ + protected function guessRelationshipName(): ?string + { + return Arr::last(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3))['function']; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasScopes.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasScopes.php new file mode 100644 index 0000000000..7420620cef --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasScopes.php @@ -0,0 +1,35 @@ +in($this->getDn())->list(); + } + + /** + * Begin querying the direct ancestors of the model. + */ + public function ancestors(): Builder + { + $parent = $this->getParentDn($this->getDn()); + + return $this->in($this->getParentDn($parent))->list(); + } + + /** + * Begin querying the direct siblings of the model. + */ + public function siblings(): Builder + { + return $this->in($this->getParentDn($this->getDn()))->list(); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HidesAttributes.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HidesAttributes.php new file mode 100644 index 0000000000..533e8fc58f --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HidesAttributes.php @@ -0,0 +1,115 @@ +normalizeAttributeKey(...), + $this->hidden + ); + } + + /** + * Set the hidden attributes for the model. + */ + public function setHidden(array $hidden): static + { + $this->hidden = $hidden; + + return $this; + } + + /** + * Add hidden attributes for the model. + */ + public function addHidden(array|string|null $attributes = null): void + { + $this->hidden = array_merge( + $this->hidden, + is_array($attributes) ? $attributes : func_get_args() + ); + } + + /** + * Get the visible attributes for the model. + */ + public function getVisible(): array + { + return array_map( + $this->normalizeAttributeKey(...), + $this->visible + ); + } + + /** + * Set the visible attributes for the model. + */ + public function setVisible(array $visible): static + { + $this->visible = $visible; + + return $this; + } + + /** + * Add visible attributes for the model. + */ + public function addVisible(array|string|null $attributes = null): void + { + $this->visible = array_merge( + $this->visible, + is_array($attributes) ? $attributes : func_get_args() + ); + } + + /** + * Make the given, typically hidden, attributes visible. + */ + public function makeVisible(array|string $attributes): static + { + $this->hidden = array_diff($this->hidden, (array) $attributes); + + if (! empty($this->visible)) { + $this->addVisible($attributes); + } + + return $this; + } + + /** + * Make the given, typically visible, attributes hidden. + */ + public function makeHidden(array|string $attributes): static + { + $attributes = (array) $attributes; + + $this->visible = array_diff($this->visible, $attributes); + + $this->hidden = array_unique(array_merge($this->hidden, $attributes)); + + return $this; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/SerializesAndRestoresPropertyValues.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/SerializesAndRestoresPropertyValues.php new file mode 100644 index 0000000000..bc4fa28cda --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/SerializesAndRestoresPropertyValues.php @@ -0,0 +1,39 @@ +originalToArray(); + } + + if ($property === 'attributes') { + return $this->attributesToArray(); + } + + return $value; + } + + /** + * Get the unserialized property value after deserialization. + */ + protected function getUnserializedPropertyValue(string $property, mixed $value): mixed + { + if ($property === 'original') { + return $this->arrayToOriginal($value); + } + + if ($property === 'attributes') { + return $this->arrayToAttributes($value); + } + + return $value; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/SerializesProperties.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/SerializesProperties.php new file mode 100644 index 0000000000..65428fa40e --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/SerializesProperties.php @@ -0,0 +1,131 @@ +getProperties(); + + foreach ($properties as $property) { + $property->setValue($this, $this->getSerializedPropertyValue( + $property->getName(), + $this->getPropertyValue($property) + )); + } + + return array_values(array_filter( + array_map(fn ($p) => $p->isStatic() ? null : $p->getName(), $properties) + )); + } + + /** + * Restore the attributes after serialization. + */ + public function __wakeup(): void + { + foreach ((new ReflectionClass($this))->getProperties() as $property) { + if ($property->isStatic()) { + continue; + } + + $property->setValue($this, $this->getUnserializedPropertyValue( + $property->getName(), + $this->getPropertyValue($property) + )); + } + } + + /** + * Prepare the model for serialization. + */ + public function __serialize(): array + { + $values = []; + + $properties = (new ReflectionClass($this))->getProperties(); + + $class = get_class($this); + + foreach ($properties as $property) { + if ($property->isStatic()) { + continue; + } + + $property->setAccessible(true); + + if (! $property->isInitialized($this)) { + continue; + } + + $name = $property->getName(); + + if ($property->isPrivate()) { + $name = "\0{$class}\0{$name}"; + } elseif ($property->isProtected()) { + $name = "\0*\0{$name}"; + } + + $values[$name] = $this->getSerializedPropertyValue( + $property->getName(), + $this->getPropertyValue($property) + ); + } + + return $values; + } + + /** + * Restore the model after serialization. + */ + public function __unserialize(array $values): void + { + $properties = (new ReflectionClass($this))->getProperties(); + + $class = get_class($this); + + foreach ($properties as $property) { + if ($property->isStatic()) { + continue; + } + + $name = $property->getName(); + + if ($property->isPrivate()) { + $name = "\0{$class}\0{$name}"; + } elseif ($property->isProtected()) { + $name = "\0*\0{$name}"; + } + + if (! array_key_exists($name, $values)) { + continue; + } + + $property->setAccessible(true); + + $property->setValue( + $this, + $this->getUnserializedPropertyValue($property->getName(), $values[$name]) + ); + } + } + + /** + * Get the property value for the given property. + */ + protected function getPropertyValue(ReflectionProperty $property): mixed + { + $property->setAccessible(true); + + return $property->getValue($this); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DetectsResetIntegers.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DetectsResetIntegers.php new file mode 100644 index 0000000000..1f2e826bf5 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DetectsResetIntegers.php @@ -0,0 +1,18 @@ +model = $model; + } + + /** + * Get the model that generated the event. + */ + public function getModel(): Model + { + return $this->model; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Events/Renamed.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Events/Renamed.php new file mode 100644 index 0000000000..0f02b6d51c --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Events/Renamed.php @@ -0,0 +1,8 @@ +rdn = $rdn; + $this->newParentDn = $newParentDn; + } + + /** + * Get the models RDN. + */ + public function getRdn(): string + { + return $this->rdn; + } + + /** + * Get the models parent DN. + */ + public function getNewParentDn(): string + { + return $this->newParentDn; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Events/Saved.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Events/Saved.php new file mode 100644 index 0000000000..cf9c5ad333 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Events/Saved.php @@ -0,0 +1,8 @@ + 'ldap', + 'krbpasswordexpiration' => 'ldap', + ]; + + /** + * {@inheritdoc} + */ + protected static function boot(): void + { + parent::boot(); + + // Here we'll add a global scope to all FreeIPA models to ensure the + // Entry UUID is always selected on each query. This attribute is + // virtual, so it must be manually selected to be included. + static::addGlobalScope(new AddEntryUuidToSelects); + } + + /** + * Create a new query builder. + */ + public function newQueryBuilder(Connection $connection): FreeIpaBuilder + { + return new FreeIpaBuilder($connection); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/FreeIPA/Group.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/FreeIPA/Group.php new file mode 100644 index 0000000000..9419775954 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/FreeIPA/Group.php @@ -0,0 +1,37 @@ +hasMany(self::class, 'member'); + } + + /** + * Retrieve the members of the group. + */ + public function members(): HasMany + { + return $this->hasMany(User::class, 'memberof')->using($this, 'member'); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/FreeIPA/Scopes/AddEntryUuidToSelects.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/FreeIPA/Scopes/AddEntryUuidToSelects.php new file mode 100644 index 0000000000..d35ed9141c --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/FreeIPA/Scopes/AddEntryUuidToSelects.php @@ -0,0 +1,20 @@ +columns) + ? $query->addSelect(['*', $model->getGuidKey()]) + : $query->addSelect($model->getGuidKey()); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/FreeIPA/User.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/FreeIPA/User.php new file mode 100644 index 0000000000..47964f0108 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/FreeIPA/User.php @@ -0,0 +1,26 @@ +hasMany(Group::class, 'member'); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Model.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Model.php new file mode 100644 index 0000000000..b17d0793a4 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Model.php @@ -0,0 +1,1403 @@ +bootIfNotBooted(); + + $this->fill($attributes); + } + + /** + * Check if the model needs to be booted and if so, do it. + */ + protected function bootIfNotBooted(): void + { + if (! isset(static::$booted[static::class])) { + static::$booted[static::class] = true; + + static::boot(); + } + } + + /** + * The "boot" method of the model. + */ + protected static function boot(): void + { + // + } + + /** + * Clear the list of booted models, so they will be re-booted. + */ + public static function clearBootedModels(): void + { + static::$booted = []; + + static::$globalScopes = []; + } + + /** + * Handle dynamic method calls into the model. + */ + public function __call(string $method, array $parameters): mixed + { + if (method_exists($this, $method)) { + return $this->$method(...$parameters); + } + + return $this->newQuery()->$method(...$parameters); + } + + /** + * Handle dynamic static method calls into the method. + */ + public static function __callStatic(string $method, array $parameters): mixed + { + return (new static)->$method(...$parameters); + } + + /** + * Get the models distinguished name. + */ + public function getDn(): ?string + { + return $this->dn; + } + + /** + * Set the model's distinguished name. + */ + public function setDn(?string $dn = null): static + { + $this->dn = $dn; + + return $this; + } + + /** + * A mutator for setting the model's distinguished name. + */ + public function setDnAttribute(string $dn): static + { + return $this->setRawAttribute('dn', $dn)->setDn($dn); + } + + /** + * A mutator for setting the model's distinguished name. + */ + public function setDistinguishedNameAttribute(string $dn): static + { + return $this->setRawAttribute('distinguishedname', $dn)->setDn($dn); + } + + /** + * Get the connection for the model. + */ + public function getConnection(): Connection + { + return static::resolveConnection($this->getConnectionName()); + } + + /** + * Get the current connection name for the model. + */ + public function getConnectionName(): ?string + { + return $this->connection; + } + + /** + * Set the connection associated with the model. + */ + public function setConnection(?string $name = null): static + { + $this->connection = $name; + + return $this; + } + + /** + * Make a new model instance. + */ + public static function make(array $attributes = []): static + { + return new static($attributes); + } + + /** + * Begin querying the model on a given connection. + */ + public static function on(?string $connection = null): Builder + { + $instance = new static; + + $instance->setConnection($connection); + + return $instance->newQuery(); + } + + /** + * Get all the models from the directory. + */ + public static function all(array|string $attributes = ['*']): array|Collection + { + return static::query()->select($attributes)->paginate(); + } + + /** + * Get the RootDSE (AD schema) record from the directory. + * + * @throws \LdapRecord\Models\ModelNotFoundException + */ + public static function getRootDse(?string $connection = null): Model + { + /** @var Model $model */ + $model = static::getRootDseModel(); + + return $model::on($connection ?? (new $model)->getConnectionName()) + ->in() + ->read() + ->whereHas('objectclass') + ->firstOrFail(); + } + + /** + * Get the root DSE model. + * + * @return class-string + */ + protected static function getRootDseModel(): string + { + $instance = new static; + + return match (true) { + $instance instanceof Types\ActiveDirectory => ActiveDirectory\Entry::class, + $instance instanceof Types\DirectoryServer => DirectoryServer\Entry::class, + $instance instanceof Types\OpenLDAP => OpenLDAP\Entry::class, + $instance instanceof Types\FreeIPA => FreeIPA\Entry::class, + default => Entry::class, + }; + } + + /** + * Begin querying the model. + */ + public static function query(): Builder + { + return (new static)->newQuery(); + } + + /** + * Get a new query for builder filtered by the current models object classes. + */ + public function newQuery(): Builder + { + return $this->registerModelScopes( + $this->newQueryWithoutScopes() + ); + } + + /** + * Get a new query builder that doesn't have any global scopes. + */ + public function newQueryWithoutScopes(): Builder + { + return static::resolveConnection( + $this->getConnectionName() + )->query()->model($this); + } + + /** + * Create a new query builder. + */ + public function newQueryBuilder(Connection $connection): Builder + { + return new Builder($connection); + } + + /** + * Create a new model instance. + */ + public function newInstance(array $attributes = []): static + { + return (new static($attributes))->setConnection($this->getConnectionName()); + } + + /** + * Resolve a connection instance. + */ + public static function resolveConnection(?string $connection = null): Connection + { + return static::getConnectionContainer()->getConnection($connection); + } + + /** + * Get the connection container. + */ + public static function getConnectionContainer(): Container + { + return static::$container ?? static::getDefaultConnectionContainer(); + } + + /** + * Get the default singleton container instance. + */ + public static function getDefaultConnectionContainer(): Container + { + return Container::getInstance(); + } + + /** + * Set the connection container. + */ + public static function setConnectionContainer(Container $container): void + { + static::$container = $container; + } + + /** + * Unset the connection container. + */ + public static function unsetConnectionContainer(): void + { + static::$container = null; + } + + /** + * Register the query scopes for this builder instance. + */ + public function registerModelScopes(Builder $builder): Builder + { + $this->applyObjectClassScopes($builder); + + $this->registerGlobalScopes($builder); + + return $builder; + } + + /** + * Register the global model scopes. + */ + public function registerGlobalScopes(Builder $builder): Builder + { + foreach ($this->getGlobalScopes() as $identifier => $scope) { + $builder->withGlobalScope($identifier, $scope); + } + + return $builder; + } + + /** + * Apply the model object class scopes to the given builder instance. + */ + public function applyObjectClassScopes(Builder $query): void + { + foreach (static::$objectClasses as $objectClass) { + $query->where('objectclass', '=', $objectClass); + } + } + + /** + * Get the models distinguished name when the model is converted to a string. + */ + public function __toString(): string + { + return (string) $this->getDn(); + } + + /** + * Returns a new batch modification. + */ + public function newBatchModification(?string $attribute = null, ?int $type = null, array $values = []): BatchModification + { + return new BatchModification($attribute, $type, $values); + } + + /** + * Returns a new collection with the specified items. + */ + public function newCollection(mixed $items = []): Collection + { + return new Collection($items); + } + + /** + * Dynamically retrieve attributes on the object. + */ + public function __get(string $key): mixed + { + return $this->getAttribute($key); + } + + /** + * Dynamically set attributes on the object. + */ + public function __set(string $key, mixed $value): void + { + $this->setAttribute($key, $value); + } + + /** + * Determine if the given offset exists. + */ + #[\ReturnTypeWillChange] + public function offsetExists(mixed $offset): bool + { + return ! is_null($this->getAttribute($offset)); + } + + /** + * Get the value for a given offset. + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset): mixed + { + return $this->getAttribute($offset); + } + + /** + * Set the value at the given offset. + */ + #[\ReturnTypeWillChange] + public function offsetSet(mixed $offset, mixed $value): void + { + $this->setAttribute($offset, $value); + } + + /** + * Unset the value at the given offset. + */ + #[\ReturnTypeWillChange] + public function offsetUnset(mixed $offset): void + { + unset($this->attributes[$offset]); + } + + /** + * Determine if an attribute exists on the model. + */ + public function __isset(string $key): bool + { + return $this->offsetExists($key); + } + + /** + * Unset an attribute on the model. + */ + public function __unset(string $key): void + { + $this->offsetUnset($key); + } + + /** + * Convert the model to its JSON encodeable array form. + */ + public function toArray(): array + { + return $this->attributesToArray(); + } + + /** + * Convert the model's attributes into JSON encodeable values. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize(): array + { + return $this->toArray(); + } + + /** + * Convert the attributes for JSON serialization. + */ + protected function convertAttributesForJson(array $attributes = []): array + { + // If the model has a GUID set, we need to convert it to its + // string format, due to it being in binary. Otherwise, + // we will receive a JSON serialization exception. + if (isset($attributes[$this->guidKey])) { + $attributes[$this->guidKey] = [$this->getConvertedGuid( + Arr::first($attributes[$this->guidKey]) + )]; + } + + return $attributes; + } + + /** + * Convert the attributes from JSON serialization. + */ + protected function convertAttributesFromJson(array $attributes = []): array + { + // Here we are converting the model's GUID and SID attributes + // back to their original values from serialization, so that + // their original value may be used and compared against. + if (isset($attributes[$this->guidKey])) { + $attributes[$this->guidKey] = [$this->getBinaryGuid( + Arr::first($attributes[$this->guidKey]) + )]; + } + + return $attributes; + } + + /** + * Reload a fresh model instance from the directory. + */ + public function fresh(): static|false + { + if (! $this->exists) { + return false; + } + + return $this->newQuery()->find($this->dn); + } + + /** + * Determine if two models have the same distinguished name and belong to the same connection. + */ + public function is(?Model $model = null): bool + { + return ! is_null($model) + && ! empty($this->dn) + && ! empty($model->getDn()) + && $this->dn == $model->getDn() + && $this->getConnectionName() == $model->getConnectionName(); + } + + /** + * Determine if two models are not the same. + */ + public function isNot(?Model $model = null): bool + { + return ! $this->is($model); + } + + /** + * Hydrate a new collection of models from search results. + */ + public function hydrate(array $records): Collection + { + return $this->newCollection($records)->transform(function ($attributes) { + if ($attributes instanceof static) { + return $attributes; + } + + return static::newInstance()->setRawAttributes($attributes); + }); + } + + /** + * Morph the model into a one of matching models using their object classes. + */ + public function morphInto(array $models, ?callable $resolver = null): Model + { + if (class_exists($model = $this->determineMorphModel($this, $models, $resolver))) { + return $this->convert(new $model); + } + + return $this; + } + + /** + * Morph the model into a one of matching models or throw an exception. + */ + public function morphIntoOrFail(array $models, ?callable $resolver = null): Model + { + $model = $this->morphInto($models, $resolver); + + if ($model instanceof $this) { + throw new RuntimeException( + 'The model could not be morphed into any of the given models.' + ); + } + + return $model; + } + + /** + * Determine the model to morph into from the given models. + * + * @return class-string|bool + */ + protected function determineMorphModel(Model $model, array $models, ?callable $resolver = null): string|bool + { + $morphModelMap = []; + + foreach ($models as $modelClass) { + $morphModelMap[$modelClass] = static::$morphCache[$modelClass] ??= $this->normalizeObjectClasses( + $modelClass::$objectClasses + ); + } + + $objectClasses = $this->normalizeObjectClasses( + $model->getObjectClasses() + ); + + $resolver ??= function (array $objectClasses, array $morphModelMap) { + return array_search($objectClasses, $morphModelMap); + }; + + return $resolver($objectClasses, $morphModelMap); + } + + /** + * Sort and normalize the object classes. + */ + protected function normalizeObjectClasses(array $classes): array + { + sort($classes); + + return array_map('strtolower', $classes); + } + + /** + * Converts the current model into the given model. + */ + public function convert(self $into): Model + { + $into->setDn($this->getDn()); + $into->setConnection($this->getConnectionName()); + + $this->exists + ? $into->setRawAttributes($this->getAttributes()) + : $into->fill($this->getAttributes()); + + return $into; + } + + /** + * Refreshes the current models attributes with the directory values. + */ + public function refresh(): bool + { + if ($model = $this->fresh()) { + $this->setRawAttributes($model->getAttributes()); + + return true; + } + + return false; + } + + /** + * Get the model's batch modifications to be processed. + */ + public function getModifications(): array + { + $builtModifications = []; + + foreach ($this->buildModificationsFromDirty() as $modification) { + $builtModifications[] = $modification->get(); + } + + return array_merge($this->modifications, $builtModifications); + } + + /** + * Set the models batch modifications. + */ + public function setModifications(array $modifications = []): static + { + $this->modifications = []; + + foreach ($modifications as $modification) { + $this->addModification($modification); + } + + return $this; + } + + /** + * Adds a batch modification to the model. + * + * @throws InvalidArgumentException + */ + public function addModification(BatchModification|array $mod = []): static + { + if ($mod instanceof BatchModification) { + $mod = $mod->get(); + } + + if ($this->isValidModification($mod)) { + $this->modifications[] = $mod; + + return $this; + } + + throw new InvalidArgumentException( + "The batch modification array does not include the mandatory 'attrib' or 'modtype' keys." + ); + } + + /** + * Get the model's guid attribute key name. + */ + public function getGuidKey(): string + { + return $this->guidKey; + } + + /** + * Get the model's ANR attributes for querying when incompatible with ANR. + */ + public function getAnrAttributes(): array + { + return ['cn', 'sn', 'uid', 'name', 'mail', 'givenname', 'displayname']; + } + + /** + * Get the name of the model, or the given DN. + */ + public function getName(?string $dn = null): ?string + { + return $this->newDn($dn ?? $this->dn)->name(); + } + + /** + * Get the head attribute of the model, or the given DN. + */ + public function getHead(?string $dn = null): ?string + { + return $this->newDn($dn ?? $this->dn)->head(); + } + + /** + * Get the RDN of the model, of the given DN. + */ + public function getRdn(?string $dn = null): ?string + { + return $this->newDn($dn ?? $this->dn)->relative(); + } + + /** + * Get the parent distinguished name of the model, or the given DN. + */ + public function getParentDn(?string $dn = null): ?string + { + return $this->newDn($dn ?? $this->dn)->parent(); + } + + /** + * Create a new distinguished name. + */ + public function newDn(?string $dn = null): DistinguishedName + { + if (! is_null($dn) && str_contains($dn, BaseBuilder::BASE_DN_PLACEHOLDER)) { + $dn = $this->newQuery()->substituteBaseDn($dn); + } + + return new DistinguishedName($dn); + } + + /** + * Get the model's object GUID key. + */ + public function getObjectGuidKey(): string + { + return $this->guidKey; + } + + /** + * Get the model's binary object GUID. + * + * @see https://msdn.microsoft.com/en-us/library/ms679021(v=vs.85).aspx + */ + public function getObjectGuid(): ?string + { + return $this->getFirstAttribute($this->guidKey); + } + + /** + * Get the model's object classes. + */ + public function getObjectClasses(): array + { + return $this->getAttribute('objectclass', static::$objectClasses); + } + + /** + * Get the model's string GUID. + */ + public function getConvertedGuid(?string $guid = null): ?string + { + try { + return $this->newObjectGuid( + (string) ($guid ?? $this->getObjectGuid()) + ); + } catch (InvalidArgumentException) { + return null; + } + } + + /** + * Get the model's binary GUID. + */ + public function getBinaryGuid(?string $guid = null): ?string + { + try { + return $this->newObjectGuid( + $guid ?? $this->getObjectGuid() + )->getBinary(); + } catch (InvalidArgumentException) { + return null; + } + } + + /** + * Make a new object Guid instance. + */ + protected function newObjectGuid(string $value): Guid + { + return new Guid($value); + } + + /** + * Determine if the current model is a direct descendant of the given. + */ + public function isChildOf(Model|string|null $parent = null): bool + { + return $this->newDn($this->getDn())->isChildOf( + $this->newDn((string) $parent) + ); + } + + /** + * Determine if the current model is a sibling of the given. + */ + public function isSiblingOf(Model|string|null $model = null): bool + { + return $this->newDn($this->getDn())->isSiblingOf( + $this->newDn((string) $model) + ); + } + + /** + * Determine if the current model is a direct ascendant of the given. + */ + public function isParentOf(Model|string|null $child = null): bool + { + return $this->newDn($this->getDn())->isParentOf( + $this->newDn((string) $child) + ); + } + + /** + * Determine if the current model is a descendant of the given. + */ + public function isDescendantOf(Model|string|null $model = null): bool + { + return $this->dnIsInside($this->getDn(), $model); + } + + /** + * Determine if the current model is a ancestor of the given. + */ + public function isAncestorOf(Model|string|null $model = null): bool + { + return $this->dnIsInside($model, $this->getDn()); + } + + /** + * Determine if the DN is inside the parent DN. + */ + protected function dnIsInside(Model|string|null $dn = null, Model|string|null $parentDn = null): bool + { + return $this->newDn((string) $dn)->isDescendantOf( + $this->newDn($parentDn) + ); + } + + /** + * Set the base DN of where the model should be created in. + */ + public function inside(Model|string $dn): static + { + $this->in = $dn instanceof self ? $dn->getDn() : $dn; + + return $this; + } + + /** + * Save the model to the directory without raising any events. + * + * @throws \LdapRecord\LdapRecordException + */ + public function saveQuietly(array $attributes = []): void + { + static::withoutEvents(function () use ($attributes) { + $this->save($attributes); + }); + } + + /** + * Save the model to the directory. + * + * @throws \LdapRecord\LdapRecordException + */ + public function save(array $attributes = []): void + { + $this->fill($attributes); + + $this->dispatch('saving'); + + $this->exists ? $this->performUpdate() : $this->performInsert(); + + $this->dispatch('saved'); + + $this->modifications = []; + + $this->in = null; + } + + /** + * Inserts the model into the directory. + * + * @throws \LdapRecord\LdapRecordException + */ + protected function performInsert(): void + { + // Here we will populate the models object classes if it + // does not already have any set. An LDAP object cannot + // be successfully created in the server without them. + if (! $this->hasAttribute('objectclass')) { + $this->setAttribute('objectclass', static::$objectClasses); + } + + $query = $this->newQuery(); + + // If the model does not currently have a distinguished + // name, we will attempt to generate one automatically + // using the current query builder's DN as the base. + if (empty($this->getDn())) { + $this->setDn($this->getCreatableDn()); + } + + $this->dispatch('creating'); + + // Some PHP versions prevent non-numerically indexed arrays + // from being sent to the server. To resolve this, we will + // convert the attributes to numerically indexed arrays. + $attributes = array_map('array_values', array_filter($this->getAttributes())); + + // Here we perform the insert of new object in the directory, + // but filter out any empty attributes before sending them + // to the server. LDAP servers will throw an exception if + // attributes have been given empty or null values. + $this->dn = $query->insertAndGetDn($this->getDn(), $attributes); + + $this->attributes = $attributes; + + $this->dispatch('created'); + + $this->syncOriginal(); + + $this->exists = true; + + $this->wasRecentlyCreated = true; + } + + /** + * Updates the model in the directory. + * + * @throws \LdapRecord\LdapRecordException + */ + protected function performUpdate(): void + { + if (! count($modifications = $this->getModifications())) { + return; + } + + $this->dispatch('updating'); + + $this->newQuery()->update($this->dn, $modifications); + + $this->dispatch('updated'); + + $this->syncChanges(); + + $this->syncOriginal(); + } + + /** + * Create the model in the directory. + * + * @throws \LdapRecord\LdapRecordException + */ + public static function create(array $attributes = []): static + { + $instance = new static($attributes); + + $instance->save(); + + return $instance; + } + + /** + * Add an attribute on the model with the given value. + * + * @throws ModelDoesNotExistException + * @throws \LdapRecord\LdapRecordException + */ + public function addAttribute(string $attribute, mixed $value): void + { + $this->assertExists(); + + $this->dispatch(['saving', 'updating']); + + $this->newQuery()->add($this->dn, [$attribute => (array) $value]); + + $this->addAttributeValue($attribute, $value); + + $this->dispatch(['updated', 'saved']); + } + + /** + * Update the model. + * + * @throws ModelDoesNotExistException + * @throws \LdapRecord\LdapRecordException + */ + public function update(array $attributes = []): void + { + $this->assertExists(); + + $this->save($attributes); + } + + /** + * Update the model attribute with the specified value. + * + * @throws ModelDoesNotExistException + * @throws \LdapRecord\LdapRecordException + */ + public function replaceAttribute(string $attribute, mixed $value): void + { + $this->assertExists(); + + $this->dispatch(['saving', 'updating']); + + $this->newQuery()->replace($this->dn, [$attribute => (array) $value]); + + $this->addAttributeValue($attribute, $value); + + $this->dispatch(['updated', 'saved']); + } + + /** + * Destroy the models for the given distinguished names. + * + * @throws \LdapRecord\LdapRecordException + */ + public static function destroy(mixed $dns, bool $recursive = false): int + { + $count = 0; + + $instance = new static; + + if ($dns instanceof Collection) { + $dns = $dns->modelDns()->toArray(); + } + + // Here we are iterating through each distinguished name and locating + // the associated model. While it's more resource intensive, we must + // do this in case of leaf nodes being given alongside any parent + // node, ensuring they can be deleted inside of the directory. + foreach ((array) $dns as $dn) { + if (! $model = $instance->find($dn)) { + continue; + } + + $model->delete($recursive); + + $count++; + } + + return $count; + } + + /** + * Delete the model from the directory. + * + * Throws a ModelNotFoundException if the current model does + * not exist or does not contain a distinguished name. + * + * @throws ModelDoesNotExistException + * @throws \LdapRecord\LdapRecordException + */ + public function delete(bool $recursive = false): void + { + $this->assertExists(); + + $this->dispatch('deleting'); + + if ($recursive) { + $this->deleteLeafNodes(); + } + + $this->newQuery()->delete($this->dn); + + // If the deletion is successful, we will mark the model + // as non-existing, and then fire the deleted event so + // developers can hook in and run further operations. + $this->exists = false; + + $this->dispatch('deleted'); + } + + /** + * Deletes leaf nodes that are attached to the model. + * + * @throws \LdapRecord\LdapRecordException + */ + protected function deleteLeafNodes(): void + { + $this->newQueryWithoutScopes() + ->in($this->dn) + ->list() + ->each(function (Model $model) { + $model->delete(recursive: true); + }); + } + + /** + * Remove an attribute on the model. + * + * @throws ModelDoesNotExistException + * @throws \LdapRecord\LdapRecordException + */ + public function removeAttribute(string $attribute, mixed $value = null): void + { + $this->removeAttributes([$attribute => $value]); + } + + /** + * Remove an attribute on the model. + * + * Remove specific values in attributes: + * + * ["memberuid" => "jdoe"] + * + * Remove an entire attribute: + * + * ["memberuid" => []] + * + * @throws ModelDoesNotExistException + * @throws \LdapRecord\LdapRecordException + */ + public function removeAttributes(array|string $attributes): void + { + $this->assertExists(); + + $attributes = $this->makeDeletableAttributes($attributes); + + $this->dispatch(['saving', 'updating']); + + $this->newQuery()->remove($this->dn, $attributes); + + foreach ($attributes as $attribute => $value) { + // If the attribute value is empty, we can assume the + // attribute was completely deleted from the model. + // We will pull the attribute out and continue on. + if (empty($value)) { + unset($this->attributes[$attribute]); + } + // Otherwise, only specific attribute values have been + // removed. We will determine which ones have been + // removed and update the attributes value. + elseif (Arr::exists($this->attributes, $attribute)) { + $this->attributes[$attribute] = array_values( + array_diff($this->attributes[$attribute], (array) $value) + ); + } + } + + $this->dispatch(['updated', 'saved']); + + $this->syncOriginal(); + } + + /** + * Make a deletable attribute array. + */ + protected function makeDeletableAttributes(string|array $attributes): array + { + $delete = []; + + foreach (Arr::wrap($attributes) as $key => $value) { + is_int($key) + ? $delete[$value] = [] + : $delete[$key] = Arr::wrap($value); + } + + return $delete; + } + + /** + * Move the model into the given new parent. + * + * For example: $user->move($ou); + * + * @throws UnexpectedValueException + * @throws ModelDoesNotExistException + * @throws \LdapRecord\LdapRecordException + */ + public function move(Model|string $newParentDn, bool $deleteOldRdn = true): void + { + $this->assertExists(); + + if (! $rdn = $this->getRdn()) { + throw new UnexpectedValueException('Current model does not contain an RDN to move.'); + } + + $this->rename($rdn, $newParentDn, $deleteOldRdn); + } + + /** + * Rename the model to a new RDN and new parent. + * + * @throws ModelDoesNotExistException + * @throws \LdapRecord\LdapRecordException + */ + public function rename(string $rdn, Model|string|null $newParentDn = null, bool $deleteOldRdn = true): void + { + $this->assertExists(); + + if ($newParentDn instanceof self) { + $newParentDn = $newParentDn->getDn(); + } + + if (is_null($newParentDn)) { + $newParentDn = $this->getParentDn($this->dn); + } + + // If the RDN we have been given is empty when parsed, we must + // have been given a string, with no attribute. In this case, + // we will create a new RDN using the current DN's head. + if ($this->newDn($rdn)->isEmpty()) { + $rdn = $this->getUpdateableRdn($rdn); + } + + // If the RDN and the new parent DN are the same as the current, + // we will simply return here to prevent a rename operation + // being sent, which would fail anyway in such case. + if ( + $rdn === $this->getRdn() + && $newParentDn === $this->getParentDn() + ) { + return; + } + + $this->dispatch('renaming', [$rdn, $newParentDn]); + + // If the model was successfully renamed, we will set + // its new DN so any further updates to the model + // can be performed without any issues. + $this->dn = $this->newQuery()->renameAndGetDn($this->dn, $rdn, $newParentDn, $deleteOldRdn); + + $map = $this->newDn($this->dn)->assoc(); + + // Here we'll populate the models new primary + // RDN attribute on the model so we do not + // have to re-synchronize with the server. + $modelNameAttribute = key($map); + + $this->attributes[$modelNameAttribute] + = $this->original[$modelNameAttribute] + = [reset($map[$modelNameAttribute])]; + + $this->dispatch('renamed'); + + $this->wasRecentlyRenamed = true; + } + + /** + * Get an updatable RDN for the model. + */ + public function getUpdateableRdn(string $name): string + { + return $this->getCreatableRdn($name, $this->newDn($this->dn)->head()); + } + + /** + * Get a distinguished name that is creatable for the model. + */ + public function getCreatableDn(?string $name = null, ?string $attribute = null): string + { + return implode(',', [ + $this->getCreatableRdn($name, $attribute), + $this->in ?? $this->newQuery()->getbaseDn(), + ]); + } + + /** + * Get a creatable (escaped) RDN for the model. + */ + public function getCreatableRdn(?string $name = null, ?string $attribute = null): string + { + $attribute = $attribute ?? $this->getCreatableRdnAttribute(); + + $name = $this->escape( + $name ?? $this->getFirstAttribute($attribute) + )->forDn(); + + return "$attribute=$name"; + } + + /** + * Get the creatable RDN attribute name. + */ + protected function getCreatableRdnAttribute(): string + { + return 'cn'; + } + + /** + * Determine if the given modification is valid. + */ + protected function isValidModification(mixed $mod): bool + { + return Arr::accessible($mod) + && Arr::exists($mod, BatchModification::KEY_MODTYPE) + && Arr::exists($mod, BatchModification::KEY_ATTRIB); + } + + /** + * Builds the models modifications from its dirty attributes. + * + * @return BatchModification[] + */ + protected function buildModificationsFromDirty(): array + { + $modifications = []; + + foreach ($this->getDirty() as $attribute => $values) { + $modification = $this->newBatchModification($attribute, null, (array) $values); + + if (Arr::exists($this->original, $attribute)) { + // If the attribute we're modifying has an original value, we will + // give the BatchModification object its values to automatically + // determine which type of LDAP operation we need to perform. + $modification->setOriginal($this->original[$attribute]); + } + + if (! $modification->build()->isValid()) { + continue; + } + + $modifications[] = $modification; + } + + return $modifications; + } + + /** + * Throw an exception if the model does not exist. + * + * @throws ModelDoesNotExistException + */ + protected function assertExists(): void + { + if (! $this->exists || is_null($this->dn)) { + throw ModelDoesNotExistException::forModel($this); + } + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ModelDoesNotExistException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ModelDoesNotExistException.php new file mode 100644 index 0000000000..98efa9865c --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ModelDoesNotExistException.php @@ -0,0 +1,43 @@ +setModel($model); + } + + /** + * Set the model instance that does not exist. + */ + public function setModel(Model $model): static + { + $this->model = $model; + + $class = get_class($model); + + $this->message = "Model [{$class}] does not exist."; + + return $this; + } + + /** + * Get the instance of the model that does not exist. + */ + public function getModel(): Model + { + return $this->model; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ModelNotFoundException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ModelNotFoundException.php new file mode 100644 index 0000000000..be88babbbe --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ModelNotFoundException.php @@ -0,0 +1,10 @@ +hasManyIn([static::class, User::class], 'uniquemember')->using($this, 'uniquemember'); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/OrganizationalUnit.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/OrganizationalUnit.php new file mode 100644 index 0000000000..e304237e73 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/OrganizationalUnit.php @@ -0,0 +1,22 @@ +columns) + ? $query->addSelect(['*', $model->getGuidKey()]) + : $query->addSelect($model->getGuidKey()); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/User.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/User.php new file mode 100644 index 0000000000..beaba9e4c6 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/User.php @@ -0,0 +1,50 @@ +getFirstAttribute($this->guidKey); + } + + /** + * The groups relationship. + */ + public function groups(): HasMany + { + return $this->hasMany(Group::class, 'uniquemember'); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/HasMany.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/HasMany.php new file mode 100644 index 0000000000..16e65b14c3 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/HasMany.php @@ -0,0 +1,140 @@ +pageSize = $pageSize; + + return $this; + } + + /** + * Paginate the relation using the given page size. + */ + public function paginate(int $pageSize = 1000): Collection + { + return $this->paginateOnceUsing($pageSize); + } + + /** + * Paginate the relation using the page size once. + */ + protected function paginateOnceUsing(int $pageSize): Collection + { + $size = $this->pageSize; + + $result = $this->setPageSize($pageSize)->get(); + + $this->pageSize = $size; + + return $result; + } + + /** + * Execute a callback over each result while chunking. + */ + public function each(Closure $callback, int $pageSize = 1000): bool + { + return $this->chunk($pageSize, function ($results) use ($callback) { + foreach ($results as $key => $value) { + if ($callback($value, $key) === false) { + return false; + } + } + + return true; + }); + } + + /** + * Chunk the relation results using the given callback. + */ + public function chunk(int $pageSize, Closure $callback): bool + { + return $this->chunkRelation($pageSize, $callback); + } + + /** + * Execute the callback over chunks of relation results. + */ + protected function chunkRelation(int $pageSize, Closure $callback, array $loaded = []): bool + { + return $this->getRelationQuery()->chunk($pageSize, function (Collection $results) use ($pageSize, $callback, $loaded) { + $models = $this->transformResults($results)->when($this->recursive, function (Collection $models) use ($loaded) { + return $models->reject(function (Model $model) use ($loaded) { + return in_array($model->getDn(), $loaded); + }); + }); + + if ($callback($models) === false) { + return false; + } + + $models->when($this->recursive, function (Collection $models) use ($pageSize, $callback, $loaded) { + $models->each(function (Model $model) use ($pageSize, $callback, $loaded) { + if ($relation = $model->getRelation($this->relationName)) { + $loaded[] = $model->getDn(); + + return $relation->recursive()->chunkRelation($pageSize, $callback, $loaded); + } + }); + }); + + return true; + }); + } + + /** + * Get the relationships results. + */ + public function getRelationResults(): Collection + { + return $this->transformResults( + $this->getRelationQuery()->paginate($this->pageSize) + ); + } + + /** + * Get the prepared relationship query. + */ + public function getRelationQuery(): Builder + { + $columns = $this->query->getSelects(); + + // We need to select the proper key to be able to retrieve its + // value from LDAP results. If we don't, we won't be able + // to properly attach / detach models from relation + // query results as the attribute will not exist. + $key = $this->using ? $this->usingKey : $this->relationKey; + + // If the * character is missing from the attributes to select, + // we will add the key to the attributes to select and also + // validate that the key isn't already being selected + // to prevent stacking on multiple relation calls. + if (! in_array('*', $columns) && ! in_array($key, $columns)) { + $this->query->addSelect($key); + } + + return $this->query->whereRaw( + $this->relationKey, + '=', + $this->getEscapedForeignValueFromModel($this->parent) + ); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/HasManyIn.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/HasManyIn.php new file mode 100644 index 0000000000..8b5d561f03 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/HasManyIn.php @@ -0,0 +1,24 @@ +parent->newCollection(); + + foreach ((array) $this->parent->getAttribute($this->relationKey) as $value) { + if ($value && $foreign = $this->getForeignModelByValue($value)) { + $results->push($foreign); + } + } + + return $this->transformResults($results); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/HasOne.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/HasOne.php new file mode 100644 index 0000000000..0199f21786 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/HasOne.php @@ -0,0 +1,43 @@ +getFirstAttributeValue($this->parent, $this->relationKey); + + $model = $relationValue ? $this->getForeignModelByValue($relationValue) : null; + + return $this->transformResults( + $this->parent->newCollection($model ? [$model] : null) + ); + } + + /** + * Attach a model instance to the parent model. + */ + public function attach(Model|string $model): void + { + $foreign = $model instanceof Model + ? $this->getForeignValueFromModel($model) + : $model; + + $this->parent->setAttribute($this->relationKey, $foreign)->save(); + } + + /** + * Detach the related model from the parent. + */ + public function detach(): void + { + $this->parent->setAttribute($this->relationKey, null)->save(); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/OneToMany.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/OneToMany.php new file mode 100644 index 0000000000..8eb88cbcfc --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/OneToMany.php @@ -0,0 +1,431 @@ + [ + 'Already exists', 'Type or value exists', + ], + 'detach' => [ + 'No such attribute', 'Server is unwilling to perform', + ], + ]; + + /** + * Constructor. + */ + public function __construct(Builder $query, Model $parent, array|string $related, string $relationKey, string $foreignKey, string $relationName) + { + $this->relationName = $relationName; + + parent::__construct($query, $parent, $related, $relationKey, $foreignKey); + } + + /** + * Set the model and attribute to use for attaching / detaching. + */ + public function using(Model $using, string $usingKey): static + { + $this->using = $using; + $this->usingKey = $usingKey; + + return $this; + } + + /** + * Set the relation to load with its parent. + */ + public function with(Relation $relation): static + { + $this->with = $relation; + + return $this; + } + + /** + * Whether to include recursive results. + */ + public function recursive(bool $recursive = true): static + { + $this->recursive = $recursive; + + return $this; + } + + /** + * Get the immediate relationships results. + */ + abstract public function getRelationResults(): Collection; + + /** + * Get the results of the relationship. + */ + public function getResults(): Collection + { + $results = $this->recursive + ? $this->getRecursiveResults() + : $this->getRelationResults(); + + return $results->merge( + $this->getMergingRelationResults() + ); + } + + /** + * Execute the callback excluding the merged query result. + */ + protected function onceWithoutMerging(Closure $callback): mixed + { + $merging = $this->with; + + $this->with = null; + + $result = $callback(); + + $this->with = $merging; + + return $result; + } + + /** + * Get the relation name. + */ + public function getRelationName(): string + { + return $this->relationName; + } + + /** + * Attach the model in the relation. + */ + public function attach(mixed $model): void + { + if (is_iterable($model)) { + array_map($this->attach(...), [...$model]); + + return; + } + + $this->attemptFailableOperation( + $this->buildAttachCallback($model), + $this->bypass['attach'], + $model + ); + } + + /** + * Build the attach callback. + */ + protected function buildAttachCallback(Model|string $model): Closure + { + return function () use ($model) { + $foreign = $this->getAttachableForeignValue($model); + + if ($this->using) { + $this->using->addAttribute($this->usingKey, $foreign); + + return; + } + + if (! $model instanceof Model) { + $model = $this->getForeignModelByValueOrFail($model); + } + + $model->addAttribute($this->relationKey, $foreign); + }; + } + + /** + * Get the foreign model by the given value, or fail. + * + * @throws ModelNotFoundException + */ + protected function getForeignModelByValueOrFail(string $model): Model + { + if (! is_null($model = $this->getForeignModelByValue($model))) { + return $model; + } + + throw ModelNotFoundException::forQuery( + $this->query->getUnescapedQuery(), + $this->query->getDn() + ); + } + + /** + * Detach the model from the relation. + */ + public function detach(mixed $model): void + { + if (is_iterable($model)) { + array_map($this->detach(...), [...$model]); + + return; + } + + $this->attemptFailableOperation( + $this->buildDetachCallback($model), + $this->bypass['detach'], + $model + ); + } + + /** + * Detach the model or delete the parent if the relation is empty. + */ + public function detachOrDeleteParent(Model|string $model): void + { + /** @var Collection $related */ + $related = $this->onceWithoutMerging(function () { + return $this->get('dn'); + }); + + if (! $related->exists($model)) { + return; + } + + if ($related->count() <= 1) { + $this->getParent()->delete(); + + return; + } + + $this->detach($model); + } + + /** + * Build the detach callback. + */ + protected function buildDetachCallback(Model|string $model): Closure + { + return function () use ($model) { + $foreign = $this->getAttachableForeignValue($model); + + if ($this->using) { + $this->using->removeAttribute($this->usingKey, $foreign); + + return; + } + + if (! $model instanceof Model) { + $model = $this->getForeignModelByValueOrFail($model); + } + + $model->removeAttribute($this->relationKey, $foreign); + }; + } + + /** + * Associate the model in the relation. + */ + public function associate(mixed $model): void + { + if (is_iterable($model)) { + array_map($this->associate(...), [...$model]); + + return; + } + + $foreign = $this->getAttachableForeignValue($model); + + if ($this->using) { + $this->using->addAttributeValue($this->usingKey, $foreign); + + return; + } + + if (! $model instanceof Model) { + $model = $this->getForeignModelByValueOrFail($model); + } + + $model->addAttributeValue($this->relationKey, $foreign); + } + + /** + * Dissociate the model in the relation. + */ + public function dissociate(mixed $model): void + { + if (is_iterable($model)) { + array_map($this->dissociate(...), [...$model]); + + return; + } + + $foreign = $this->getAttachableForeignValue($model); + + if ($this->using) { + $this->using->removeAttributeValue($this->usingKey, $foreign); + + return; + } + + if (! $model instanceof Model) { + $model = $this->getForeignModelByValueOrFail($model); + } + + $model->removeAttributeValue($this->relationKey, $foreign); + } + + /** + * Alias of "dissociate" method. + */ + public function disassociate(Model|string $model): void + { + $this->dissociate($model); + } + + /** + * Get the attachable foreign value from the model. + */ + protected function getAttachableForeignValue(Model|string $model): string + { + if ($model instanceof Model) { + return $this->using + ? $this->getForeignValueFromModel($model) + : $this->getParentForeignValue(); + } + + return $this->using ? $model : $this->getParentForeignValue(); + } + + /** + * Attempt a failable operation and return the value if successful. + * + * If a bypassable exception is encountered, the value will be returned. + * + * @throws LdapRecordException + */ + protected function attemptFailableOperation(Closure $operation, string|array $bypass, mixed $value): mixed + { + try { + $operation(); + + return $value; + } catch (LdapRecordException $e) { + if ($this->errorContainsMessage($e->getMessage(), $bypass)) { + return $value; + } + + throw $e; + } + } + + /** + * Detach all relation models. + */ + public function detachAll(): Collection + { + return $this->onceWithoutMerging( + fn () => $this->get()->each(function (Model $model) { + $this->detach($model); + }) + ); + } + + /** + * Detach all relation models or delete the model if its relation is empty. + */ + public function detachAllOrDelete(): Collection + { + return $this->onceWithoutMerging( + fn () => $this->get()->each(function (Model $model) { + $relation = $model->getRelation($this->relationName); + + if ($relation && $relation->count() >= 1) { + $model->delete(); + } else { + $this->detach($model); + } + }) + ); + } + + /** + * Get the results of the merging 'with' relation. + */ + protected function getMergingRelationResults(): Collection + { + return $this->with + ? $this->with->recursive($this->recursive)->get() + : $this->parent->newCollection(); + } + + /** + * Get the results for the model's relation recursively. + * + * @param string[] $loaded The distinguished names of models already loaded + */ + protected function getRecursiveResults(array $loaded = []): Collection + { + $results = $this->getRelationResults()->reject(fn (Model $model) => + // Here we will exclude the models that we have already + // loaded the recursive results for so we don't run + // into issues with circular relations in LDAP. + in_array($model->getDn(), $loaded) + ); + + foreach ($results as $model) { + $loaded[] = $model->getDn(); + + // Finally, we will fetch the related model's relations, + // passing along our loaded models, to ensure we do + // not attempt fetching already loaded relations. + $results = $results->merge( + $this->getRecursiveRelationResults($model, $loaded) + ); + } + + return $results; + } + + /** + * Get the recursive relation results for given model. + */ + protected function getRecursiveRelationResults(Model $model, array $loaded): Collection + { + return ($relation = $model->getRelation($this->relationName)) + ? $relation->getRecursiveResults($loaded) + : $model->newCollection(); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/Relation.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/Relation.php new file mode 100644 index 0000000000..b2d26100e7 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Relations/Relation.php @@ -0,0 +1,307 @@ +query = $query; + $this->parent = $parent; + $this->related = (array) $related; + $this->relationKey = $relationKey; + $this->foreignKey = $foreignKey; + + $this->initRelation(); + } + + /** + * Handle dynamic method calls to the relationship. + */ + public function __call(string $method, array $parameters): mixed + { + if (in_array($method, $this->passthru)) { + return $this->get('objectclass')->$method(...$parameters); + } + + $result = $this->query->$method(...$parameters); + + if ($result === $this->query) { + return $this; + } + + return $result; + } + + /** + * Set the callback to use for resolving models from relation results. + */ + public static function resolveModelsUsing(?Closure $callback = null): void + { + static::$modelResolver = $callback; + } + + /** + * Only return objects matching the related model's object classes. + */ + public function onlyRelated(): static + { + $relations = []; + + foreach ($this->related as $related) { + $relations[$related] = $related::$objectClasses; + } + + $relations = array_filter($relations); + + if (empty($relations)) { + return $this; + } + + $this->query->andFilter(function (Builder $query) use ($relations) { + foreach ($relations as $relation => $objectClasses) { + $query->whereIn('objectclass', $objectClasses); + } + }); + + return $this; + } + + /** + * Get the results of the relationship. + */ + abstract public function getResults(): Collection; + + /** + * Execute the relationship query. + */ + public function get(array|string $columns = ['*']): Collection + { + return $this->getResultsWithColumns($columns); + } + + /** + * Get the results of the relationship while selecting the given columns. + * + * If the query columns are empty, the given columns are applied. + */ + protected function getResultsWithColumns(array|string $columns): Collection + { + if (is_null($this->query->columns)) { + $this->query->select($columns); + } + + return $this->getResults(); + } + + /** + * Get the first result of the relationship. + */ + public function first(array|string $columns = ['*']): ?Model + { + return $this->get($columns)->first(); + } + + /** + * Prepare the relation query. + */ + public function initRelation(): static + { + $this->query + ->clearFilters() + ->withoutGlobalScopes() + ->setModel($this->getNewDefaultModel()); + + return $this; + } + + /** + * Set the underlying query for the relation. + */ + public function setQuery(Builder $query): static + { + $this->query = $query; + + $this->initRelation(); + + return $this; + } + + /** + * Get the underlying query for the relation. + */ + public function getQuery(): Builder + { + return $this->query; + } + + /** + * Get the parent model of the relation. + */ + public function getParent(): Model + { + return $this->parent; + } + + /** + * Get the relation attribute key. + */ + public function getRelationKey(): string + { + return $this->relationKey; + } + + /** + * Get the related model classes for the relation. + */ + public function getRelated(): array + { + return $this->related; + } + + /** + * Get the relation foreign attribute key. + */ + public function getForeignKey(): string + { + return $this->foreignKey; + } + + /** + * Get the class name of the default model. + */ + public function getDefaultModel(): string + { + return $this->default; + } + + /** + * Get a new instance of the default model on the relation. + */ + public function getNewDefaultModel(): Model + { + $model = new $this->default; + + $model->setConnection($this->parent->getConnectionName()); + + return $model; + } + + /** + * Get the foreign model by the given value. + */ + protected function getForeignModelByValue(string $value): ?Model + { + return $this->foreignKeyIsDistinguishedName() + ? $this->query->clearFilters()->find($value) + : $this->query->clearFilters()->findBy($this->foreignKey, $value); + } + + /** + * Get the escaped foreign key value for use in an LDAP filter from the model. + */ + protected function getEscapedForeignValueFromModel(Model $model): string + { + return $this->query->escape( + $this->getForeignValueFromModel($model) + )->forDnAndFilter(); + } + + /** + * Get the relation parents foreign value. + */ + protected function getParentForeignValue(): ?string + { + return $this->getForeignValueFromModel($this->parent); + } + + /** + * Get the foreign key value from the model. + */ + protected function getForeignValueFromModel(Model $model): ?string + { + return $this->foreignKeyIsDistinguishedName() + ? $model->getDn() + : $this->getFirstAttributeValue($model, $this->foreignKey); + } + + /** + * Get the first attribute value from the model. + */ + protected function getFirstAttributeValue(Model $model, string $attribute): mixed + { + return $model->getFirstAttribute($attribute); + } + + /** + * Transforms the results by converting the models into their related. + */ + protected function transformResults(Collection $results): Collection + { + return $results->transform( + fn (Model $entry) => $entry->morphInto($this->related, static::$modelResolver) + ); + } + + /** + * Determine if the foreign key is a distinguished name. + */ + protected function foreignKeyIsDistinguishedName(): bool + { + return in_array($this->foreignKey, ['dn', 'distinguishedname']); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Scope.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Scope.php new file mode 100644 index 0000000000..09c0c0c2c8 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Scope.php @@ -0,0 +1,13 @@ +storage[$key])) { + return $default; + } + + $item = $this->storage[$key]; + + $expiresAt = $item['expiresAt'] ?? 0; + + if ($expiresAt !== 0 && $this->currentTime() > $expiresAt) { + $this->delete($key); + + return $default; + } + + return $item['value']; + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null): bool + { + $this->storage[$key] = [ + 'value' => $value, + 'expiresAt' => $this->calculateExpiration($ttl), + ]; + + return true; + } + + /** + * Get the expiration time of the key. + */ + protected function calculateExpiration($seconds = null): int + { + return $this->toTimestamp($seconds); + } + + /** + * Get the UNIX timestamp for the given number of seconds. + */ + protected function toTimestamp($seconds = null): int + { + return $seconds > 0 ? $this->availableAt($seconds) : 0; + } + + /** + * {@inheritdoc} + */ + public function delete($key): bool + { + unset($this->storage[$key]); + + return true; + } + + /** + * {@inheritdoc} + */ + public function clear(): bool + { + $this->storage = []; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getMultiple($keys, $default = null): iterable + { + $values = []; + + foreach ($keys as $key) { + $values[$key] = $this->get($key, $default); + } + + return $values; + } + + /** + * {@inheritdoc} + */ + public function setMultiple($values, $ttl = null): bool + { + foreach ($values as $key => $value) { + $this->set($key, $value, $ttl); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple($keys): bool + { + foreach ($keys as $key) { + $this->delete($key); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function has($key): bool + { + return isset($this->storage[$key]); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Builder.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Builder.php new file mode 100644 index 0000000000..642cf32af6 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Builder.php @@ -0,0 +1,1683 @@ + [], + 'or' => [], + 'raw' => [], + ]; + + /** + * The LDAP server controls to be sent. + */ + public array $controls = []; + + /** + * The LDAP server controls that were processed. + */ + public array $controlsResponse = []; + + /** + * The size limit of the query. + */ + public int $limit = 0; + + /** + * Determine whether the current query is paginated. + */ + public bool $paginated = false; + + /** + * The distinguished name to perform searches upon. + */ + protected ?string $dn = null; + + /** + * The base distinguished name to perform searches inside. + */ + protected ?string $baseDn = null; + + /** + * The default query type. + */ + protected string $type = self::TYPE_SEARCH; + + /** + * Determine whether the query is nested. + */ + protected bool $nested = false; + + /** + * Determine whether the query should be cached. + */ + protected bool $caching = false; + + /** + * The custom cache key to use when caching results. + */ + protected ?string $cacheKey = null; + + /** + * How long the query should be cached until. + */ + protected ?DateTimeInterface $cacheUntil = null; + + /** + * Determine whether the query cache must be flushed. + */ + protected bool $flushCache = false; + + /** + * The current connection instance. + */ + protected Connection $connection; + + /** + * The current grammar instance. + */ + protected Grammar $grammar; + + /** + * The current cache instance. + */ + protected ?Cache $cache = null; + + /** + * Constructor. + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + $this->grammar = new Grammar; + } + + /** + * Set the current connection. + */ + public function setConnection(Connection $connection): static + { + $this->connection = $connection; + + return $this; + } + + /** + * Set the current filter grammar. + */ + public function setGrammar(Grammar $grammar): static + { + $this->grammar = $grammar; + + return $this; + } + + /** + * Set the cache to store query results. + */ + public function setCache(?Cache $cache = null): static + { + $this->cache = $cache; + + return $this; + } + + /** + * Returns a new Query Builder instance. + */ + public function newInstance(?string $baseDn = null): Builder + { + return (new static($this->connection))->setDn( + is_null($baseDn) ? $this->getDn() : $baseDn + ); + } + + /** + * Returns a new nested Query Builder instance. + */ + public function newNestedInstance(?Closure $closure = null): Builder + { + $query = $this->newInstance()->nested(); + + if ($closure) { + $closure($query); + } + + return $query; + } + + /** + * Executes the LDAP query. + */ + public function get(array|string $columns = ['*']): Collection|array + { + return $this->onceWithColumns( + Arr::wrap($columns), fn () => $this->query($this->getQuery()) + ); + } + + /** + * Execute the given callback while selecting the given columns. + * + * After running the callback, the columns are reset to the original value. + */ + protected function onceWithColumns(array $columns, Closure $callback): mixed + { + $original = $this->columns; + + if (is_null($original)) { + $this->columns = $columns; + } + + $result = $callback(); + + $this->columns = $original; + + return $result; + } + + /** + * Compile the query into an LDAP filter string. + */ + public function getQuery(): string + { + // We need to ensure we have at least one filter, as + // no query results will be returned otherwise. + if (count(array_filter($this->filters)) === 0) { + $this->whereHas('objectclass'); + } + + return $this->grammar->compile($this); + } + + /** + * Get the unescaped query. + */ + public function getUnescapedQuery(): string + { + return EscapedValue::unescape($this->getQuery()); + } + + /** + * Get the current Grammar instance. + */ + public function getGrammar(): Grammar + { + return $this->grammar; + } + + /** + * Get the current Cache instance. + */ + public function getCache(): ?Cache + { + return $this->cache; + } + + /** + * Get the current Connection instance. + */ + public function getConnection(): Connection + { + return $this->connection; + } + + /** + * Get the query type. + */ + public function getType(): string + { + return $this->type; + } + + /** + * Set the base distinguished name of the query. + */ + public function setBaseDn(Model|string|null $dn = null): static + { + $this->baseDn = $this->substituteBaseDn($dn); + + return $this; + } + + /** + * Get the base distinguished name of the query. + */ + public function getBaseDn(): ?string + { + return $this->baseDn; + } + + /** + * Get the distinguished name of the query. + */ + public function getDn(): ?string + { + return $this->dn; + } + + /** + * Set the distinguished name for the query. + */ + public function setDn(Model|string|null $dn = null): static + { + $this->dn = $this->substituteBaseDn($dn); + + return $this; + } + + /** + * Substitute the base DN string template for the current base. + */ + public function substituteBaseDn(Model|string|null $dn = null): string + { + return str_replace(static::BASE_DN_PLACEHOLDER, $this->baseDn ?? '', (string) $dn); + } + + /** + * Alias for setting the distinguished name for the query. + */ + public function in(Model|string|null $dn = null): static + { + return $this->setDn($dn); + } + + /** + * Set the size limit of the current query. + */ + public function limit(int $limit = 0): static + { + $this->limit = $limit; + + return $this; + } + + /** + * Returns a new query for the given model. + */ + public function model(Model $model): ModelBuilder + { + return $model->newQueryBuilder($this->connection) + ->setCache($this->connection->getCache()) + ->setBaseDn($this->baseDn) + ->setModel($model); + } + + /** + * Performs the specified query on the current LDAP connection. + */ + public function query(string $query): Collection|array + { + $start = microtime(true); + + // Here we will create the execution callback. This allows us + // to only execute an LDAP request if caching is disabled + // or if no cache of the given query exists yet. + $callback = fn () => $this->parse($this->run($query)); + + $results = $this->getCachedResponse($query, $callback); + + $this->logQuery($this, $this->type, $this->getElapsedTime($start)); + + return $this->process($results); + } + + /** + * Paginates the current LDAP query. + */ + public function paginate(int $pageSize = 1000, bool $isCritical = false): Collection|array + { + $this->paginated = true; + + $start = microtime(true); + + $query = $this->getQuery(); + + // Here we will create the pagination callback. This allows us + // to only execute an LDAP request if caching is disabled + // or if no cache of the given query exists yet. + $callback = fn () => $this->runPaginate($query, $pageSize, $isCritical); + + $pages = $this->getCachedResponse($query, $callback); + + $this->logQuery($this, self::TYPE_PAGINATE, $this->getElapsedTime($start)); + + return $this->process($pages); + } + + /** + * Runs the paginate operation with the given filter. + */ + protected function runPaginate(string $filter, int $perPage, bool $isCritical): array + { + return $this->connection->run( + fn (LdapInterface $ldap) => $this->newPaginator($filter, $perPage, $isCritical)->execute($ldap) + ); + } + + /** + * Make a new paginator instance. + */ + protected function newPaginator(string $filter, int $perPage, bool $isCritical): Paginator + { + return new Paginator($this, $filter, $perPage, $isCritical); + } + + /** + * Execute a callback over each item while chunking. + */ + public function each(Closure $callback, int $pageSize = 1000, bool $isCritical = false, bool $isolate = false): bool + { + return $this->chunk($pageSize, function ($results) use ($callback) { + foreach ($results as $key => $value) { + if ($callback($value, $key) === false) { + return false; + } + } + }, $isCritical, $isolate); + } + + /** + * Chunk the results of a paginated LDAP query. + */ + public function chunk(int $pageSize, Closure $callback, bool $isCritical = false, bool $isolate = false): bool + { + $this->limit(0); + + $start = microtime(true); + + $chunk = function (Builder $query) use ($pageSize, $callback, $isCritical) { + $page = 1; + + foreach ($query->runChunk($this->getQuery(), $pageSize, $isCritical) as $chunk) { + if ($callback($this->process($chunk), $page) === false) { + return false; + } + + $page++; + } + }; + + // Connection isolation creates a new, temporary connection for the pagination + // request to occur on. This allows connections that do not support executing + // other queries during a pagination request, to do so without interruption. + $isolate ? $this->connection->isolate( + fn (Connection $replicate) => $chunk($this->clone()->setConnection($replicate)) + ) : $chunk($this); + + $this->logQuery($this, self::TYPE_CHUNK, $this->getElapsedTime($start)); + + return true; + } + + /** + * Runs the chunk operation with the given filter. + */ + protected function runChunk(string $filter, int $perPage, bool $isCritical): Generator + { + return $this->connection->run( + fn (LdapInterface $ldap) => $this->newLazyPaginator($filter, $perPage, $isCritical)->execute($ldap) + ); + } + + /** + * Make a new lazy paginator instance. + */ + protected function newLazyPaginator(string $filter, int $perPage, bool $isCritical): LazyPaginator + { + return new LazyPaginator($this, $filter, $perPage, $isCritical); + } + + /** + * Create a slice of the LDAP query into a page. + */ + public function slice(int $page = 1, int $perPage = 100, string $orderBy = 'cn', string $orderByDir = 'asc'): Slice + { + $results = $this->forPage($page, $perPage, $orderBy, $orderByDir); + + $total = $this->controlsResponse[LDAP_CONTROL_VLVRESPONSE]['value']['count'] ?? 0; + + // Some LDAP servers seem to have an issue where the last result in a virtual + // list view will always be returned, regardless of the offset being larger + // than the result itself. In this case, we will manually return an empty + // response so that no objects are deceivingly included in the slice. + $objects = $page > max((int) ceil($total / $perPage), 1) + ? ($this instanceof ModelBuilder ? $this->model->newCollection() : []) + : $results; + + return new Slice($objects, $total, $perPage, $page); + } + + /** + * Get the results of a query for a given page. + */ + public function forPage(int $page = 1, int $perPage = 100, string $orderBy = 'cn', string $orderByDir = 'asc'): Collection|array + { + if (! $this->hasOrderBy()) { + $this->orderBy($orderBy, $orderByDir); + } + + $this->addControl(LDAP_CONTROL_VLVREQUEST, true, [ + 'before' => 0, + 'after' => $perPage - 1, + 'offset' => ($page * $perPage) - $perPage + 1, + 'count' => 0, + ]); + + return $this->get(); + } + + /** + * Processes and converts the given LDAP results into models. + */ + protected function process(array $results): mixed + { + unset($results['count']); + + if ($this->paginated) { + return $this->flattenPages($results); + } + + return $results; + } + + /** + * Flattens LDAP paged results into a single array. + */ + protected function flattenPages(array $pages): array + { + $records = []; + + foreach ($pages as $page) { + unset($page['count']); + + $records = array_merge($records, $page); + } + + return $records; + } + + /** + * Get the cached response or execute and cache the callback value. + */ + protected function getCachedResponse(string $query, Closure $callback): mixed + { + if ($this->cache && $this->caching) { + $key = $this->cacheKey ?? $this->getCacheKey($query); + + if ($this->flushCache) { + $this->cache->delete($key); + } + + return $this->cache->remember($key, $this->cacheUntil, $callback); + } + + try { + return $callback(); + } finally { + $this->caching = false; + $this->cacheKey = null; + $this->cacheUntil = null; + $this->flushCache = false; + } + } + + /** + * Runs the query operation with the given filter. + */ + public function run(string $filter): mixed + { + return $this->connection->run(function (LdapInterface $ldap) use ($filter) { + // We will avoid setting the controls during any pagination + // requests as it will clear the cookie we need to send + // to the server upon retrieving every page. + if (! $this->paginated) { + // Before running the query, we will set the LDAP server controls. This + // allows the controls to be automatically reset upon each new query + // that is conducted on the same connection during each request. + $ldap->setOption(LDAP_OPT_SERVER_CONTROLS, $this->controls); + } + + return $ldap->{$this->type}( + (string) ($this->dn ?? $this->baseDn), + $filter, + $this->getSelects(), + $onlyAttributes = false, + $this->limit + ); + }); + } + + /** + * Parses the given LDAP resource by retrieving its entries. + */ + public function parse(mixed $resource): array + { + if (! $resource) { + return []; + } + + return $this->connection->run(function (LdapInterface $ldap) use ($resource) { + $this->controlsResponse = $this->controls; + + // Process the server controls response. + $ldap->parseResult( + result: $resource, + controls: $this->controlsResponse + ); + + $entries = $ldap->getEntries($resource); + + // Free up memory. + if (is_resource($resource) || $resource instanceof Result) { + $ldap->freeResult($resource); + } + + return $entries; + }); + } + + /** + * Get the cache key. + */ + protected function getCacheKey(string $query): string + { + $host = $this->connection->run( + fn (LdapInterface $ldap) => $ldap->getHost() + ); + + $key = $host + .$this->type + .$this->getDn() + .$query + .implode($this->getSelects()) + .$this->limit + .$this->paginated; + + return md5($key); + } + + /** + * Get the first entry in a search result. + */ + public function first(array|string $columns = ['*']): Model|array|null + { + return Arr::first( + $this->limit(1)->get($columns) + ); + } + + /** + * Get the first entry in a search result. + * + * If no entry is found, an exception is thrown. + * + * @throws ObjectNotFoundException + */ + public function firstOrFail(array|string $columns = ['*']): Model|array + { + if (! $record = $this->first($columns)) { + $this->throwNotFoundException($this->getUnescapedQuery(), $this->dn); + } + + return $record; + } + + /** + * Get the first entry in a result, or execute the callback. + */ + public function firstOr(Closure $callback): mixed + { + return $this->first() ?: $callback(); + } + + /** + * Execute the query and get the first result if it's the sole matching record. + * + * @throws ObjectsNotFoundException + * @throws MultipleObjectsFoundException + */ + public function sole(array|string $columns = ['*']): Model|array + { + $result = $this->limit(2)->get($columns); + + if (empty($result)) { + throw new ObjectsNotFoundException; + } + + if (count($result) > 1) { + throw new MultipleObjectsFoundException; + } + + return reset($result); + } + + /** + * Determine if any results exist for the current query. + */ + public function exists(): bool + { + return ! is_null($this->first()); + } + + /** + * Determine if no results exist for the current query. + */ + public function doesntExist(): bool + { + return ! $this->exists(); + } + + /** + * Execute the given callback if no rows exist for the current query. + */ + public function existsOr(Closure $callback): mixed + { + return $this->exists() ? true : $callback(); + } + + /** + * Throws a not found exception. + * + * @throws ObjectNotFoundException + */ + protected function throwNotFoundException(string $query, ?string $dn = null): void + { + throw ObjectNotFoundException::forQuery($query, $dn); + } + + /** + * Finds a record by the specified attribute and value. + * + * @return Model|static|null + */ + public function findBy(string $attribute, string $value, array|string $columns = ['*']): Model|array|null + { + try { + return $this->findByOrFail($attribute, $value, $columns); + } catch (ObjectNotFoundException $e) { + return null; + } + } + + /** + * Finds a record by the specified attribute and value. + * + * If no record is found an exception is thrown. + * + * @throws ObjectNotFoundException + */ + public function findByOrFail(string $attribute, string $value, array|string $columns = ['*']): Model|array + { + return $this->whereEquals($attribute, $value)->firstOrFail($columns); + } + + /** + * Find many records by distinguished name. + */ + public function findMany(array|string $dns, array|string $columns = ['*']): Collection|array + { + if (empty($dns)) { + return $this->process([]); + } + + $objects = []; + + foreach ((array) $dns as $dn) { + if (! is_null($object = $this->find($dn, $columns))) { + $objects[] = $object; + } + } + + return $this->process($objects); + } + + /** + * Finds many records by the specified attribute. + */ + public function findManyBy(string $attribute, array $values = [], array|string $columns = ['*']): Collection|array + { + $query = $this->select($columns); + + foreach ($values as $value) { + $query->orWhere([$attribute => $value]); + } + + return $query->get(); + } + + /** + * Finds a record by its distinguished name. + */ + public function find(array|string $dn, array|string $columns = ['*']): Collection|Model|array|null + { + if (is_array($dn)) { + return $this->findMany($dn, $columns); + } + + try { + return $this->findOrFail($dn, $columns); + } catch (ObjectNotFoundException $e) { + return null; + } + } + + /** + * Finds a record by its distinguished name. + * + * Fails upon no records returned. + * + * @throws ObjectNotFoundException + */ + public function findOrFail(string $dn, array|string $columns = ['*']): Model|array + { + return $this->setDn($dn) + ->read() + ->whereHas('objectclass') + ->firstOrFail($columns); + } + + /** + * Adds the inserted fields to query on the current LDAP connection. + */ + public function select(array|string $columns = ['*']): static + { + $columns = is_array($columns) ? $columns : func_get_args(); + + if (! empty($columns)) { + $this->columns = $columns; + } + + return $this; + } + + /** + * Add a new select column to the query. + */ + public function addSelect(array|string $column): static + { + $column = is_array($column) ? $column : func_get_args(); + + $this->columns = array_merge((array) $this->columns, $column); + + return $this; + } + + /** + * Add an order by control to the query. + */ + public function orderBy(string $attribute, string $direction = 'asc', array $options = []): static + { + return $this->addControl(LDAP_CONTROL_SORTREQUEST, true, [ + [ + ...$options, + 'attr' => $attribute, + 'reverse' => $direction === 'desc', + ], + ]); + } + + /** + * Add an order by descending control to the query. + */ + public function orderByDesc(string $attribute, array $options = []): static + { + return $this->orderBy($attribute, 'desc', $options); + } + + /** + * Determine if the query has a sotr request control header. + */ + public function hasOrderBy(): bool + { + return $this->hasControl(LDAP_CONTROL_SORTREQUEST); + } + + /** + * Adds a raw filter to the current query. + */ + public function rawFilter(array|string $filters = []): static + { + $filters = is_array($filters) ? $filters : func_get_args(); + + foreach ($filters as $filter) { + $this->filters['raw'][] = $filter; + } + + return $this; + } + + /** + * Adds a nested 'and' filter to the current query. + */ + public function andFilter(Closure $closure): static + { + $query = $this->newNestedInstance($closure); + + return $this->rawFilter( + $this->grammar->compileAnd($query->getQuery()) + ); + } + + /** + * Adds a nested 'or' filter to the current query. + */ + public function orFilter(Closure $closure): static + { + $query = $this->newNestedInstance($closure); + + return $this->rawFilter( + $this->grammar->compileOr($query->getQuery()) + ); + } + + /** + * Adds a nested 'not' filter to the current query. + */ + public function notFilter(Closure $closure): static + { + $query = $this->newNestedInstance($closure); + + return $this->rawFilter( + $this->grammar->compileNot($query->getQuery()) + ); + } + + /** + * Adds a where clause to the current query. + * + * @throws InvalidArgumentException + */ + public function where(array|string $field, mixed $operator = null, mixed $value = null, string $boolean = 'and', bool $raw = false): static + { + if (is_array($field)) { + // If the field is an array, we will assume we have been + // provided with an array of key-value pairs and can + // add them each as their own separate where clause. + return $this->addArrayOfWheres($field, $boolean, $raw); + } + + // If we have been provided with two arguments not a "has" or + // "not has" operator, we'll assume the developer is creating + // an "equals" clause and set the proper operator in place. + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 && ! $this->operatorRequiresValue($operator) + ); + + if (! in_array($operator, $this->grammar->getOperators())) { + throw new InvalidArgumentException("Invalid LDAP filter operator [$operator]"); + } + + // We'll escape the value if raw isn't requested. + $value = $this->prepareWhereValue($field, $value, $raw); + + $field = $this->escape($field)->forDnAndFilter()->get(); + + $this->addFilter($boolean, compact('field', 'operator', 'value')); + + return $this; + } + + /** + * Prepare the value and operator for a where clause. + */ + public function prepareValueAndOperator(mixed $value, mixed $operator, bool $useDefault = false): array + { + if ($useDefault) { + return [$operator, '=']; + } + + return [$value, $operator]; + } + + /** + * Determine if the operator requires a value to be present. + */ + protected function operatorRequiresValue(mixed $operator): bool + { + return in_array($operator, ['*', '!*']); + } + + /** + * Prepare the value for being queried. + */ + protected function prepareWhereValue(string $field, mixed $value = null, bool $raw = false): string + { + return $raw ? $value : $this->escape($value)->get(); + } + + /** + * Adds a raw where clause to the current query. + * + * Values given to this method are not escaped. + */ + public function whereRaw(array|string $field, ?string $operator = null, mixed $value = null): static + { + return $this->where($field, $operator, $value, 'and', true); + } + + /** + * Adds a 'where equals' clause to the current query. + */ + public function whereEquals(string $field, string $value): static + { + return $this->where($field, '=', $value); + } + + /** + * Adds a 'where not equals' clause to the current query. + */ + public function whereNotEquals(string $field, string $value): static + { + return $this->where($field, '!', $value); + } + + /** + * Adds a 'where approximately equals' clause to the current query. + */ + public function whereApproximatelyEquals(string $field, string $value): static + { + return $this->where($field, '~=', $value); + } + + /** + * Adds a 'where has' clause to the current query. + */ + public function whereHas(string $field): static + { + return $this->where($field, '*'); + } + + /** + * Adds a 'where not has' clause to the current query. + */ + public function whereNotHas(string $field): static + { + return $this->where($field, '!*'); + } + + /** + * Adds a 'where contains' clause to the current query. + */ + public function whereContains(string $field, string $value): static + { + return $this->where($field, 'contains', $value); + } + + /** + * Adds a 'where contains' clause to the current query. + */ + public function whereNotContains(string $field, string $value): static + { + return $this->where($field, 'not_contains', $value); + } + + /** + * Query for entries that match any of the values provided for the given field. + */ + public function whereIn(string $field, array $values): static + { + if (empty($values)) { + // If the array of values is empty, we will + // add an empty OR filter to the query to + // ensure that no results are returned. + $this->rawFilter('(|)'); + + return $this; + } + + return $this->orFilter(function (Builder $query) use ($field, $values) { + foreach ($values as $value) { + $query->whereEquals($field, $value); + } + }); + } + + /** + * Adds a 'between' clause to the current query. + */ + public function whereBetween(string $field, array $values): static + { + return $this->where([ + [$field, '>=', $values[0]], + [$field, '<=', $values[1]], + ]); + } + + /** + * Adds a 'where starts with' clause to the current query. + */ + public function whereStartsWith(string $field, string $value): static + { + return $this->where($field, 'starts_with', $value); + } + + /** + * Adds a 'where *not* starts with' clause to the current query. + */ + public function whereNotStartsWith(string $field, string $value): static + { + return $this->where($field, 'not_starts_with', $value); + } + + /** + * Adds a 'where ends with' clause to the current query. + */ + public function whereEndsWith(string $field, string $value): static + { + return $this->where($field, 'ends_with', $value); + } + + /** + * Adds a 'where *not* ends with' clause to the current query. + */ + public function whereNotEndsWith(string $field, string $value): static + { + return $this->where($field, 'not_ends_with', $value); + } + + /** + * Only include deleted models in the results. + */ + public function whereDeleted(): static + { + return $this->withDeleted()->whereEquals('isDeleted', 'TRUE'); + } + + /** + * Set the LDAP control option to include deleted LDAP models. + */ + public function withDeleted(): static + { + return $this->addControl(LdapInterface::OID_SERVER_SHOW_DELETED, $isCritical = true); + } + + /** + * Add a server control to the query. + */ + public function addControl(string $oid, bool $isCritical = false, mixed $value = null): static + { + $this->controls[$oid] = compact('oid', 'isCritical', 'value'); + + return $this; + } + + /** + * Determine if the server control exists on the query. + */ + public function hasControl(string $oid): bool + { + return array_key_exists($oid, $this->controls); + } + + /** + * Adds an 'or where' clause to the current query. + */ + public function orWhere(array|string $field, ?string $operator = null, ?string $value = null): static + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 && ! $this->operatorRequiresValue($operator) + ); + + return $this->where($field, $operator, $value, 'or'); + } + + /** + * Adds a raw or where clause to the current query. + * + * Values given to this method are not escaped. + */ + public function orWhereRaw(array|string $field, ?string $operator = null, ?string $value = null): static + { + return $this->where($field, $operator, $value, 'or', true); + } + + /** + * Adds an 'or where has' clause to the current query. + */ + public function orWhereHas(string $field): static + { + return $this->orWhere($field, '*'); + } + + /** + * Adds a 'where not has' clause to the current query. + */ + public function orWhereNotHas(string $field): static + { + return $this->orWhere($field, '!*'); + } + + /** + * Adds an 'or where equals' clause to the current query. + */ + public function orWhereEquals(string $field, string $value): static + { + return $this->orWhere($field, '=', $value); + } + + /** + * Adds an 'or where not equals' clause to the current query. + */ + public function orWhereNotEquals(string $field, string $value): static + { + return $this->orWhere($field, '!', $value); + } + + /** + * Adds a 'or where approximately equals' clause to the current query. + */ + public function orWhereApproximatelyEquals(string $field, string $value): static + { + return $this->orWhere($field, '~=', $value); + } + + /** + * Adds an 'or where contains' clause to the current query. + */ + public function orWhereContains(string $field, string $value): static + { + return $this->orWhere($field, 'contains', $value); + } + + /** + * Adds an 'or where *not* contains' clause to the current query. + */ + public function orWhereNotContains(string $field, string $value): static + { + return $this->orWhere($field, 'not_contains', $value); + } + + /** + * Adds an 'or where starts with' clause to the current query. + */ + public function orWhereStartsWith(string $field, string $value): static + { + return $this->orWhere($field, 'starts_with', $value); + } + + /** + * Adds an 'or where *not* starts with' clause to the current query. + */ + public function orWhereNotStartsWith(string $field, string $value): static + { + return $this->orWhere($field, 'not_starts_with', $value); + } + + /** + * Adds an 'or where ends with' clause to the current query. + */ + public function orWhereEndsWith(string $field, string $value): static + { + return $this->orWhere($field, 'ends_with', $value); + } + + /** + * Adds an 'or where *not* ends with' clause to the current query. + */ + public function orWhereNotEndsWith(string $field, string $value): static + { + return $this->orWhere($field, 'not_ends_with', $value); + } + + /** + * Adds a filter binding onto the current query. + * + * @throws InvalidArgumentException + */ + public function addFilter(string $type, array $bindings): static + { + if (! array_key_exists($type, $this->filters)) { + throw new InvalidArgumentException("Filter type: [$type] is invalid."); + } + + // Each filter clause require key bindings to be set. We + // will validate this here to ensure all of them have + // been provided, or throw an exception otherwise. + if ($missing = $this->missingBindingKeys($bindings)) { + $keys = implode(', ', $missing); + + throw new InvalidArgumentException("Invalid filter bindings. Missing: [$keys] keys."); + } + + $this->filters[$type][] = $bindings; + + return $this; + } + + /** + * Extract any missing required binding keys. + */ + protected function missingBindingKeys(array $bindings): array + { + $required = array_flip(['field', 'operator', 'value']); + + $existing = array_intersect_key($required, $bindings); + + return array_keys(array_diff_key($required, $existing)); + } + + /** + * Get all the filters on the query. + */ + public function getFilters(): array + { + return $this->filters; + } + + /** + * Clear the query filters. + */ + public function clearFilters(): static + { + foreach (array_keys($this->filters) as $type) { + $this->filters[$type] = []; + } + + return $this; + } + + /** + * Determine if the query has attributes selected. + */ + public function hasSelects(): bool + { + return count($this->columns ?? []) > 0; + } + + /** + * Get the attributes to select on the search. + */ + public function getSelects(): array + { + $selects = $this->columns ?? ['*']; + + if (in_array('*', $selects)) { + return $selects; + } + + if (in_array('objectclass', $selects)) { + return $selects; + } + + // If the * character is not provided in the selected columns, + // we need to ensure we always select the object class, as + // this is used for constructing models properly. + $selects[] = 'objectclass'; + + return $selects; + } + + /** + * Set the query to search on the base distinguished name. + * + * This will result in one record being returned. + */ + public function read(): static + { + $this->type = self::TYPE_READ; + + return $this; + } + + /** + * Set the query to search one level on the base distinguished name. + */ + public function list(): static + { + $this->type = self::TYPE_LIST; + + return $this; + } + + /** + * Alias for the "search" method. + */ + public function recursive(): static + { + return $this->search(); + } + + /** + * Set the query to search the entire directory on the base distinguished name. + */ + public function search(): static + { + $this->type = self::TYPE_SEARCH; + + return $this; + } + + /** + * Whether to mark the current query as nested. + */ + public function nested(bool $nested = true): static + { + $this->nested = $nested; + + return $this; + } + + /** + * Enables caching on the current query until the given date. + * + * If flushing is enabled, the query cache will be flushed and then re-cached. + */ + public function cache(?DateTimeInterface $until = null, bool $flush = false, ?string $key = null): static + { + $this->caching = true; + $this->cacheKey = $key; + $this->cacheUntil = $until; + $this->flushCache = $flush; + + return $this; + } + + /** + * Determine if the query is nested. + */ + public function isNested(): bool + { + return $this->nested === true; + } + + /** + * Determine whether the query is paginated. + */ + public function isPaginated(): bool + { + return $this->paginated; + } + + /** + * Insert an entry into the directory. + * + * @throws LdapRecordException + */ + public function insert(string $dn, array $attributes): bool + { + return (bool) $this->insertAndGetDn($dn, $attributes); + } + + /** + * Insert an entry into the directory and get the inserted distinguished name. + * + * @throws LdapRecordException + */ + public function insertAndGetDn(string $dn, array $attributes): string|false + { + $dn = $this->substituteBaseDn($dn); + + if (empty($dn)) { + throw new LdapRecordException('A new LDAP object must have a distinguished name (dn).'); + } + + if (! array_key_exists('objectclass', $attributes)) { + throw new LdapRecordException( + 'A new LDAP object must contain at least one object class (objectclass) to be created.' + ); + } + + return $this->connection->run( + fn (LdapInterface $ldap) => $ldap->add($dn, $attributes) + ) ? $dn : false; + } + + /** + * Add attributes to an entry in the directory. + */ + public function add(string $dn, array $attributes): bool + { + return $this->connection->run( + fn (LdapInterface $ldap) => $ldap->modAdd($dn, $attributes) + ); + } + + /** + * Update the entry with the given modifications. + */ + public function update(string $dn, array $modifications): bool + { + return $this->connection->run( + fn (LdapInterface $ldap) => $ldap->modifyBatch($dn, $modifications) + ); + } + + /** + * Replace an entry's attributes in the directory. + */ + public function replace(string $dn, array $attributes): bool + { + return $this->connection->run( + fn (LdapInterface $ldap) => $ldap->modReplace($dn, $attributes) + ); + } + + /** + * Delete an entry from the directory. + */ + public function delete(string $dn): bool + { + return $this->connection->run( + fn (LdapInterface $ldap) => $ldap->delete($dn) + ); + } + + /** + * Remove attributes on the entry in the directory. + */ + public function remove(string $dn, array $attributes): bool + { + return $this->connection->run( + fn (LdapInterface $ldap) => $ldap->modDelete($dn, $attributes) + ); + } + + /** + * Rename an entry in the directory. + */ + public function rename(string $dn, string $rdn, string $newParentDn, bool $deleteOldRdn = true): bool + { + return (bool) $this->renameAndGetDn($dn, $rdn, $newParentDn, $deleteOldRdn); + } + + /** + * Rename an entry in the directory and get the new distinguished name. + */ + public function renameAndGetDn(string $dn, string $rdn, string $newParentDn, bool $deleteOldRdn = true): string|false + { + $newParentDn = $this->substituteBaseDn($newParentDn); + + return $this->connection->run( + fn (LdapInterface $ldap) => $ldap->rename($dn, $rdn, $newParentDn, $deleteOldRdn) + ) ? implode(',', [$rdn, $newParentDn]) : false; + } + + /** + * Clone the query. + */ + public function clone(): static + { + return clone $this; + } + + /** + * Handle dynamic method calls on the query builder. + * + * @throws BadMethodCallException + */ + public function __call(string $method, array $parameters): static + { + // If the beginning of the method being called contains + // 'where', we will assume a dynamic 'where' clause is + // being performed and pass the parameters to it. + if (str_starts_with($method, 'where')) { + return $this->dynamicWhere($method, $parameters); + } + + throw new BadMethodCallException(sprintf( + 'Call to undefined method %s::%s()', + static::class, + $method + )); + } + + /** + * Handles dynamic "where" clauses to the query. + * + * @return $this + */ + public function dynamicWhere(string $method, array $parameters): static + { + $finder = substr($method, 5); + + $segments = preg_split('/(And|Or)(?=[A-Z])/', $finder, -1, PREG_SPLIT_DELIM_CAPTURE); + + // The connector variable will determine which connector will be used for the + // query condition. We will change it as we come across new boolean values + // in the dynamic method strings, which could contain a number of these. + $connector = 'and'; + + $index = 0; + + foreach ($segments as $segment) { + // If the segment is not a boolean connector, we can assume it is a column's name + // and we will add it to the query as a new constraint as a where clause, then + // we can keep iterating through the dynamic method string's segments again. + if ($segment != 'And' && $segment != 'Or') { + $this->addDynamic($segment, $connector, $parameters, $index); + + $index++; + } + + // Otherwise, we will store the connector so we know how the next where clause we + // find in the query should be connected to the previous ones, meaning we will + // have the proper boolean connector to connect the next where clause found. + else { + $connector = $segment; + } + } + + return $this; + } + + /** + * Adds an array of wheres to the current query. + */ + protected function addArrayOfWheres(array $wheres, string $boolean, bool $raw): static + { + foreach ($wheres as $key => $value) { + if (is_numeric($key) && is_array($value)) { + // If the key is numeric and the value is an array, we'll + // assume we've been given an array with conditionals. + [$field, $condition] = $value; + + // Since a value is optional for some conditionals, we will + // try and retrieve the third parameter from the array, + // but is entirely optional. + $value = Arr::get($value, 2); + + $this->where($field, $condition, $value, $boolean); + } else { + // If the value is not an array, we will assume an equals clause. + $this->where($key, '=', $value, $boolean, $raw); + } + } + + return $this; + } + + /** + * Add a single dynamic where clause statement to the query. + */ + protected function addDynamic(string $segment, string $connector, array $parameters, int $index): void + { + // If no parameters were given to the dynamic where clause, + // we can assume a "has" attribute filter is being added. + if (count($parameters) === 0) { + $this->where(strtolower($segment), '*', null, strtolower($connector)); + } else { + $this->where(strtolower($segment), '=', $parameters[$index], strtolower($connector)); + } + } + + /** + * Logs the given executed query information by firing its query event. + */ + protected function logQuery(Builder $query, string $type, ?float $time = null): void + { + $args = [$query, $time]; + + $this->fireQueryEvent( + match ($type) { + self::TYPE_READ => new Events\Read(...$args), + self::TYPE_CHUNK => new Events\Chunk(...$args), + self::TYPE_LIST => new Events\Listing(...$args), + self::TYPE_PAGINATE => new Events\Paginate(...$args), + default => new Events\Search(...$args), + } + ); + } + + /** + * Fires the given query event. + */ + protected function fireQueryEvent(QueryExecuted $event): void + { + Container::getInstance()->getDispatcher()->fire($event); + } + + /** + * Get the elapsed time since a given starting point. + */ + protected function getElapsedTime(float $start): float + { + return round((microtime(true) - $start) * 1000, 2); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Cache.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Cache.php new file mode 100644 index 0000000000..184455cede --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Cache.php @@ -0,0 +1,72 @@ +store = $store; + } + + /** + * Get an item from the cache. + */ + public function get(string $key): mixed + { + return $this->store->get($key); + } + + /** + * Store an item in the cache. + */ + public function put(string $key, mixed $value, DateTimeInterface|DateInterval|int|null $ttl = null): bool + { + return $this->store->set($key, $value, $this->secondsUntil($ttl)); + } + + /** + * Get an item from the cache, or execute the given Closure and store the result. + */ + public function remember(string $key, DateTimeInterface|DateInterval|int|null $ttl, Closure $callback): mixed + { + if (! is_null($value = $this->get($key))) { + return $value; + } + + $this->put($key, $value = $callback(), $ttl); + + return $value; + } + + /** + * Delete an item from the cache. + */ + public function delete(string $key): bool + { + return $this->store->delete($key); + } + + /** + * Get the underlying cache store. + */ + public function store(): CacheInterface + { + return $this->store; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Collection.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Collection.php new file mode 100644 index 0000000000..9cda10cdbc --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Collection.php @@ -0,0 +1,24 @@ +useAsCallable($value)) { + /** @var callable $value */ + return $value; + } + + return fn ($item) => $item instanceof Model + ? $item->getFirstAttribute($value) + : data_get($item, $value); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Chunk.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Chunk.php new file mode 100644 index 0000000000..3cd36d8a4c --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Chunk.php @@ -0,0 +1,8 @@ +query = $query; + $this->time = $time; + } + + /** + * Get the LDAP filter that was used for the query. + */ + public function getQuery(): Builder + { + return $this->query; + } + + /** + * Get the number of milliseconds it took to execute the query. + */ + public function getTime(): ?float + { + return $this->time; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Read.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Read.php new file mode 100644 index 0000000000..510c4ca436 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Read.php @@ -0,0 +1,8 @@ +=', '<=', '~=', '=']; + + /** + * Constructor. + */ + public function __construct(string $filter) + { + $this->raw = $filter; + + [$this->attribute, $this->value] = $this->extractComponents($filter); + } + + /** + * Get the condition's attribute. + */ + public function getAttribute(): string + { + return $this->attribute; + } + + /** + * Get the condition's operator. + */ + public function getOperator(): string + { + return $this->operator; + } + + /** + * Get the condition's value. + */ + public function getValue(): string + { + return $this->value; + } + + /** + * Extract the condition components from the filter. + */ + protected function extractComponents(string $filter): array + { + $components = Str::whenContains( + $filter, + $this->operators, + fn ($operator, $filter) => explode($this->operator = $operator, $filter, 2), + fn ($filter) => throw new ParserException("Invalid query condition. No operator found in [$filter]"), + ); + + return $components; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/GroupNode.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/GroupNode.php new file mode 100644 index 0000000000..1af71369ef --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/GroupNode.php @@ -0,0 +1,48 @@ +raw = $filter; + + $this->operator = substr($filter, 0, 1); + + $this->nodes = Parser::parse($filter); + } + + /** + * Get the group's operator. + */ + public function getOperator(): string + { + return $this->operator; + } + + /** + * Get the group's sub-nodes. + * + * @return Node[] + */ + public function getNodes(): array + { + return $this->nodes; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/Node.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/Node.php new file mode 100644 index 0000000000..3e9c039f15 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/Node.php @@ -0,0 +1,24 @@ +raw; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/Parser.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/Parser.php new file mode 100644 index 0000000000..1fea1810da --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/Parser.php @@ -0,0 +1,139 @@ + '"("', 1 => '")"']; + + throw new ParserException( + sprintf('Unclosed filter group. Missing %s parenthesis', $errors[$open <=> $close]) + ); + } + + return static::buildNodes( + array_map('trim', static::match($string)) + ); + } + + /** + * Perform a match for all filters in the string. + */ + protected static function match(string $string): array + { + preg_match_all("/\((((?>[^()]+)|(?R))*)\)/", trim($string), $matches); + + return $matches[1] ?? []; + } + + /** + * Assemble the parsed nodes into a single filter. + * + * @param Node|Node[] $nodes + */ + public static function assemble(Node|array $nodes = []): string + { + return array_reduce(Arr::wrap($nodes), fn ($carry, Node $node) => ( + $carry .= static::compileNode($node) + )); + } + + /** + * Assemble the node into its string based format. + */ + protected static function compileNode(Node $node): string + { + switch (true) { + case $node instanceof GroupNode: + return static::wrap($node->getOperator().static::assemble($node->getNodes())); + case $node instanceof ConditionNode: + return static::wrap($node->getAttribute().$node->getOperator().$node->getValue()); + default: + return $node->getRaw(); + } + } + + /** + * Build an array of nodes from the given filters. + * + * @param string[] $filters + * @return (ConditionNode|GroupNode)[] + * + * @throws ParserException + */ + protected static function buildNodes(array $filters = []): array + { + return array_map(function ($filter) { + if (static::isWrapped($filter)) { + $filter = static::unwrap($filter); + } + + if (static::isGroup($filter) && ! Str::endsWith($filter, ')')) { + throw new ParserException(sprintf('Unclosed filter group [%s]', Str::afterLast($filter, ')'))); + } + + return static::isGroup($filter) + ? new GroupNode($filter) + : new ConditionNode($filter); + }, $filters); + } + + /** + * Count the open and close parenthesis of the sting. + */ + protected static function countParenthesis(string $string): array + { + return [Str::substrCount($string, '('), Str::substrCount($string, ')')]; + } + + /** + * Wrap the value in parentheses. + */ + protected static function wrap(string $value): string + { + return "($value)"; + } + + /** + * Recursively unwrwap the value from its parentheses. + */ + protected static function unwrap(string $value): string + { + $nodes = static::parse($value); + + $unwrapped = Arr::first($nodes); + + return $unwrapped instanceof Node ? $unwrapped->getRaw() : $value; + } + + /** + * Determine if the filter is wrapped. + */ + protected static function isWrapped(string $filter): bool + { + return Str::startsWith($filter, '(') && Str::endsWith($filter, ')'); + } + + /** + * Determine if the filter is a group. + */ + protected static function isGroup(string $filter): bool + { + return Str::startsWith($filter, ['&', '|', '!']); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/ParserException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/ParserException.php new file mode 100644 index 0000000000..3c3d68e4f9 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/ParserException.php @@ -0,0 +1,7 @@ + 'has', + '!*' => 'notHas', + '=' => 'equals', + '!' => 'doesNotEqual', + '!=' => 'doesNotEqual', + '>=' => 'greaterThanOrEquals', + '<=' => 'lessThanOrEquals', + '~=' => 'approximatelyEquals', + 'starts_with' => 'startsWith', + 'not_starts_with' => 'notStartsWith', + 'ends_with' => 'endsWith', + 'not_ends_with' => 'notEndsWith', + 'contains' => 'contains', + 'not_contains' => 'notContains', + ]; + + /** + * The query wrapper. + */ + protected ?string $wrapper = null; + + /** + * Get all the available operators. + */ + public function getOperators(): array + { + return array_keys($this->operators); + } + + /** + * Wraps a query string in brackets. + * + * Produces: (query) + */ + public function wrap(string $query, ?string $prefix = '(', ?string $suffix = ')'): string + { + return $prefix.$query.$suffix; + } + + /** + * Compiles the Builder instance into an LDAP query string. + */ + public function compile(Builder $query): string + { + if ($this->queryMustBeWrapped($query)) { + $this->wrapper = 'and'; + } + + $filter = $this->compileRaws($query) + .$this->compileWheres($query) + .$this->compileOrWheres($query); + + return match ($this->wrapper) { + 'and' => $this->compileAnd($filter), + 'or' => $this->compileOr($filter), + default => $filter, + }; + } + + /** + * Determine if the query must be wrapped in an encapsulating statement. + */ + protected function queryMustBeWrapped(Builder $query): bool + { + return ! $query->isNested() && $this->hasMultipleFilters($query); + } + + /** + * Assembles all the "raw" filters on the query. + */ + protected function compileRaws(Builder $builder): string + { + return $this->concatenate($builder->filters['raw']); + } + + /** + * Assembles all where clauses in the current wheres property. + */ + protected function compileWheres(Builder $builder, string $type = 'and'): string + { + $filter = ''; + + foreach ($builder->filters[$type] ?? [] as $where) { + $filter .= $this->compileWhere($where); + } + + return $filter; + } + + /** + * Assembles all or where clauses in the current orWheres property. + */ + protected function compileOrWheres(Builder $query): string + { + $filter = $this->compileWheres($query, 'or'); + + if (! $this->hasMultipleFilters($query)) { + return $filter; + } + + // Here we will detect whether the entire query can be + // wrapped inside of an "or" statement by checking + // how many filter statements exist for each type. + if ($this->queryCanBeWrappedInSingleOrStatement($query)) { + $this->wrapper = 'or'; + } else { + $filter = $this->compileOr($filter); + } + + return $filter; + } + + /** + * Determine if the query can be wrapped in a single or statement. + */ + protected function queryCanBeWrappedInSingleOrStatement(Builder $query): bool + { + return $this->has($query, 'or', '>=', 1) + && $this->has($query, 'and', '<=', 1) + && $this->has($query, 'raw', '=', 0); + } + + /** + * Concatenates filters into a single string. + */ + public function concatenate(array $bindings = []): string + { + // Filter out empty query segments. + return implode( + array_filter($bindings, [$this, 'bindingValueIsNotEmpty']) + ); + } + + /** + * Determine if the binding value is not empty. + */ + protected function bindingValueIsNotEmpty(string $value): bool + { + return ! empty($value); + } + + /** + * Determine if the query is using multiple filters. + */ + protected function hasMultipleFilters(Builder $query): bool + { + return $this->has($query, ['and', 'or', 'raw'], '>', 1); + } + + /** + * Determine if the query contains the given filter statement type. + */ + protected function has(Builder $query, array|string $type, string $operator = '>=', int $count = 1): bool + { + $types = (array) $type; + + $filters = 0; + + foreach ($types as $type) { + $filters += count($query->filters[$type] ?? []); + } + + return match ($operator) { + '>' => $filters > $count, + '>=' => $filters >= $count, + '<' => $filters < $count, + '<=' => $filters <= $count, + default => $filters == $count, + }; + } + + /** + * Returns a query string for equals. + * + * Produces: (field=value) + */ + public function compileEquals(string $field, string $value): string + { + return $this->wrap($field.'='.$value); + } + + /** + * Returns a query string for does not equal. + * + * Produces: (!(field=value)) + */ + public function compileDoesNotEqual(string $field, string $value): string + { + return $this->compileNot( + $this->compileEquals($field, $value) + ); + } + + /** + * Alias for does not equal operator (!=) operator. + * + * Produces: (!(field=value)) + */ + public function compileDoesNotEqualAlias(string $field, string $value): string + { + return $this->compileDoesNotEqual($field, $value); + } + + /** + * Returns a query string for greater than or equals. + * + * Produces: (field>=value) + */ + public function compileGreaterThanOrEquals(string $field, string $value): string + { + return $this->wrap("$field>=$value"); + } + + /** + * Returns a query string for less than or equals. + * + * Produces: (field<=value) + */ + public function compileLessThanOrEquals(string $field, string $value): string + { + return $this->wrap("$field<=$value"); + } + + /** + * Returns a query string for approximately equals. + * + * Produces: (field~=value) + */ + public function compileApproximatelyEquals(string $field, string $value): string + { + return $this->wrap("$field~=$value"); + } + + /** + * Returns a query string for starts with. + * + * Produces: (field=value*) + */ + public function compileStartsWith(string $field, string $value): string + { + return $this->wrap("$field=$value*"); + } + + /** + * Returns a query string for does not start with. + * + * Produces: (!(field=*value)) + */ + public function compileNotStartsWith(string $field, string $value): string + { + return $this->compileNot( + $this->compileStartsWith($field, $value) + ); + } + + /** + * Returns a query string for ends with. + * + * Produces: (field=*value) + */ + public function compileEndsWith(string $field, string $value): string + { + return $this->wrap("$field=*$value"); + } + + /** + * Returns a query string for does not end with. + * + * Produces: (!(field=value*)) + */ + public function compileNotEndsWith(string $field, string $value): string + { + return $this->compileNot($this->compileEndsWith($field, $value)); + } + + /** + * Returns a query string for contains. + * + * Produces: (field=*value*) + */ + public function compileContains(string $field, string $value): string + { + return $this->wrap("$field=*$value*"); + } + + /** + * Returns a query string for does not contain. + * + * Produces: (!(field=*value*)) + */ + public function compileNotContains(string $field, string $value): string + { + return $this->compileNot( + $this->compileContains($field, $value) + ); + } + + /** + * Returns a query string for a where has. + * + * Produces: (field=*) + */ + public function compileHas(string $field): string + { + return $this->wrap("$field=*"); + } + + /** + * Returns a query string for a where does not have. + * + * Produces: (!(field=*)) + */ + public function compileNotHas(string $field): string + { + return $this->compileNot( + $this->compileHas($field) + ); + } + + /** + * Wraps the inserted query inside an AND operator. + * + * Produces: (&query) + */ + public function compileAnd(string $query): string + { + return $query ? $this->wrap($query, '(&') : ''; + } + + /** + * Wraps the inserted query inside an OR operator. + * + * Produces: (|query) + */ + public function compileOr(string $query): string + { + return $query ? $this->wrap($query, '(|') : ''; + } + + /** + * Wraps the inserted query inside an NOT operator. + */ + public function compileNot(string $query): string + { + return $query ? $this->wrap($query, '(!') : ''; + } + + /** + * Assembles a single where query. + * + * @throws UnexpectedValueException + */ + protected function compileWhere(array $where): string + { + $method = $this->makeCompileMethod($where['operator']); + + return $this->{$method}($where['field'], $where['value']); + } + + /** + * Make the compile method name for the operator. + * + * @throws UnexpectedValueException + */ + protected function makeCompileMethod(string $operator): string + { + if (! $this->operatorExists($operator)) { + throw new UnexpectedValueException("Invalid LDAP filter operator ['$operator']"); + } + + return 'compile'.ucfirst($this->operators[$operator]); + } + + /** + * Determine if the operator exists. + */ + protected function operatorExists(string $operator): bool + { + return array_key_exists($operator, $this->operators); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/InteractsWithTime.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/InteractsWithTime.php new file mode 100644 index 0000000000..111c2c201c --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/InteractsWithTime.php @@ -0,0 +1,59 @@ +parseDateInterval($delay); + + return $delay instanceof DateTimeInterface + ? max(0, $delay->getTimestamp() - $this->currentTime()) + : (int) $delay; + } + + /** + * Get the "available at" UNIX timestamp. + */ + protected function availableAt(DateTimeInterface|DateInterval|int $delay = 0): int + { + $delay = $this->parseDateInterval($delay); + + return $delay instanceof DateTimeInterface + ? $delay->getTimestamp() + : Carbon::now()->addRealSeconds($delay)->getTimestamp(); + } + + /** + * If the given value is an interval, convert it to a DateTime instance. + */ + protected function parseDateInterval(DateTimeInterface|DateInterval|int|null $delay = null): DateTimeInterface|int|null + { + if ($delay instanceof DateInterval) { + $delay = Carbon::now()->add($delay); + } + + return $delay; + } + + /** + * Get the current system time as a UNIX timestamp. + */ + protected function currentTime(): int + { + return Carbon::now()->getTimestamp(); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/ActiveDirectoryBuilder.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/ActiveDirectoryBuilder.php new file mode 100644 index 0000000000..7fb8ab682c --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/ActiveDirectoryBuilder.php @@ -0,0 +1,194 @@ +findBySidOrFail($sid, $columns); + } catch (ModelNotFoundException) { + return null; + } + } + + /** + * Finds a record by its Object SID. + * + * Fails upon no records returned. + * + * @throws ModelNotFoundException + */ + public function findBySidOrFail(string $sid, array $columns = ['*']): Model + { + return $this->findByOrFail('objectsid', $sid, $columns); + } + + /** + * Adds a enabled filter to the current query. + */ + public function whereEnabled(): static + { + return $this->notFilter( + fn (self $query) => $query->whereDisabled() + ); + } + + /** + * Adds a disabled filter to the current query. + */ + public function whereDisabled(): static + { + return $this->rawFilter( + (new AccountControl)->setAccountIsDisabled()->filter() + ); + } + + /** + * Adds a 'where member' filter to the current query. + */ + public function whereMember(string $dn, bool $nested = false): static + { + return $this->nestedMatchQuery( + fn (string $attribute) => $this->whereEquals($attribute, $this->substituteBaseDn($dn)), + 'member', + $nested + ); + } + + /** + * Adds an 'or where member' filter to the current query. + */ + public function orWhereMember(string $dn, bool $nested = false): static + { + return $this->nestedMatchQuery( + fn (string $attribute) => $this->orWhereEquals($attribute, $this->substituteBaseDn($dn)), + 'member', + $nested + ); + } + + /** + * Adds a 'where member of' filter to the current query. + */ + public function whereMemberOf(string $dn, bool $nested = false): static + { + return $this->nestedMatchQuery( + fn (string $attribute) => $this->whereEquals($attribute, $this->substituteBaseDn($dn)), + 'memberof', + $nested + ); + } + + /** + * Adds a 'where not member of' filter to the current query. + */ + public function whereNotMemberof(string $dn, bool $nested = false): static + { + return $this->nestedMatchQuery( + fn (string $attribute) => $this->whereNotEquals($attribute, $this->substituteBaseDn($dn)), + 'memberof', + $nested + ); + } + + /** + * Adds an 'or where member of' filter to the current query. + */ + public function orWhereMemberOf(string $dn, bool $nested = false): static + { + return $this->nestedMatchQuery( + fn (string $attribute) => $this->orWhereEquals($attribute, $this->substituteBaseDn($dn)), + 'memberof', + $nested + ); + } + + /** + * Adds a 'or where not member of' filter to the current query. + */ + public function orWhereNotMemberof(string $dn, bool $nested = false): static + { + return $this->nestedMatchQuery( + fn (string $attribute) => $this->orWhereNotEquals($attribute, $this->substituteBaseDn($dn)), + 'memberof', + $nested + ); + } + + /** + * Adds a 'where manager' filter to the current query. + */ + public function whereManager(string $dn, bool $nested = false): static + { + return $this->nestedMatchQuery( + fn (string $attribute) => $this->whereEquals($attribute, $this->substituteBaseDn($dn)), + 'manager', + $nested + ); + } + + /** + * Adds a 'where not manager' filter to the current query. + */ + public function whereNotManager(string $dn, bool $nested = false): static + { + return $this->nestedMatchQuery( + fn (string $attribute) => $this->whereNotEquals($attribute, $this->substituteBaseDn($dn)), + 'manager', + $nested + ); + } + + /** + * Adds an 'or where manager' filter to the current query. + */ + public function orWhereManager(string $dn, bool $nested = false): static + { + return $this->nestedMatchQuery( + fn (string $attribute) => $this->orWhereEquals($attribute, $this->substituteBaseDn($dn)), + 'manager', + $nested + ); + } + + /** + * Adds an 'or where not manager' filter to the current query. + */ + public function orWhereNotManager(string $dn, bool $nested = false): static + { + return $this->nestedMatchQuery( + fn (string $attribute) => $this->orWhereNotEquals($attribute, $this->substituteBaseDn($dn)), + 'manager', + $nested + ); + } + + /** + * Execute the callback with a nested match attribute. + */ + protected function nestedMatchQuery(Closure $callback, string $attribute, bool $nested = false): static + { + return $callback( + $nested ? $this->makeNestedMatchAttribute($attribute) : $attribute + ); + } + + /** + * Make a "nested match" filter attribute for querying descendants. + */ + protected function makeNestedMatchAttribute(string $attribute): string + { + return sprintf('%s:%s:', $attribute, LdapInterface::OID_MATCHING_RULE_IN_CHAIN); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/Builder.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/Builder.php new file mode 100644 index 0000000000..741646e7ec --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/Builder.php @@ -0,0 +1,410 @@ +model, $scope = 'scope'.ucfirst($method))) { + return $this->callScope([$this->model, $scope], $parameters); + } + + return parent::__call($method, $parameters); + } + + /** + * Apply the given scope on the current builder instance. + */ + protected function callScope(callable $scope, array $parameters = []): static + { + array_unshift($parameters, $this); + + return $scope(...array_values($parameters)) ?? $this; + } + + /** + * Get the attributes to select on the search. + */ + public function getSelects(): array + { + // Here we will ensure the models GUID attribute is always + // selected. In some LDAP directories, the attribute is + // virtual and must be requested for specifically. + return array_values(array_unique( + array_merge([$this->model->getGuidKey()], parent::getSelects()) + )); + } + + /** + * Set the model instance for the model being queried. + */ + public function setModel(Model $model): static + { + $this->model = $model; + + return $this; + } + + /** + * Get the model being queried for. + */ + public function getModel(): Model + { + return $this->model; + } + + /** + * Get a new model query builder instance. + */ + public function newInstance(?string $baseDn = null): BaseBuilder + { + return parent::newInstance($baseDn)->model($this->model); + } + + /** + * {@inheritDoc} + */ + public function first(array|string $columns = ['*']): ?Model + { + return parent::first($columns); + } + + /** + * {@inheritDoc} + */ + public function firstOrFail(array|string $columns = ['*']): Model + { + return parent::firstOrFail($columns); + } + + /** + * {@inheritDoc} + */ + public function sole(array|string $columns = ['*']): Model + { + return parent::sole($columns); + } + + /** + * {@inheritDoc} + */ + public function find(array|string $dn, array|string $columns = ['*']): Model|Collection|null + { + return $this->afterScopes(fn () => parent::find($dn, $columns)); + } + + /** + * {@inheritDoc} + */ + public function findOrFail(string $dn, array|string $columns = ['*']): Model + { + return parent::findOrFail($dn, $columns); + } + + /** + * {@inheritDoc} + */ + public function findByOrFail(string $attribute, string $value, array|string $columns = ['*']): Model + { + return parent::findByOrFail($attribute, $value, $columns); + } + + /** + * {@inheritDoc} + */ + public function findBy(string $attribute, string $value, array|string $columns = ['*']): ?Model + { + return parent::findBy($attribute, $value, $columns); + } + + /** + * {@inheritDoc} + */ + public function findMany(array|string $dns, array|string $columns = ['*']): Collection + { + return parent::findMany($dns, $columns); + } + + /** + * {@inheritDoc} + */ + public function findManyBy(string $attribute, array $values = [], array|string $columns = ['*']): Collection + { + return parent::findManyBy($attribute, $values, $columns); + } + + /** + * Finds a record using ambiguous name resolution. + */ + public function findByAnr(array|string $value, array|string $columns = ['*']): Model|Collection|null + { + if (is_array($value)) { + return $this->findManyByAnr($value, $columns); + } + + // If the model is not compatible with ANR filters, + // we must construct an equivalent filter that + // the current LDAP server does support. + if (! $this->modelIsCompatibleWithAnr()) { + return $this->prepareAnrEquivalentQuery($value)->first($columns); + } + + return $this->findBy('anr', $value, $columns); + } + + /** + * Determine if the current model is compatible with ANR filters. + */ + protected function modelIsCompatibleWithAnr(): bool + { + return $this->model instanceof ActiveDirectory; + } + + /** + * Finds a record using ambiguous name resolution. + * + * If a record is not found, an exception is thrown. + * + * @throws ModelNotFoundException + */ + public function findByAnrOrFail(string $value, array|string $columns = ['*']): Model + { + if (! $entry = $this->findByAnr($value, $columns)) { + $this->throwNotFoundException($this->getUnescapedQuery(), $this->dn); + } + + return $entry; + } + + /** + * Throws a not found exception. + * + * @throws ModelNotFoundException + */ + protected function throwNotFoundException(string $query, ?string $dn = null): void + { + throw ModelNotFoundException::forQuery($query, $dn); + } + + /** + * Finds multiple records using ambiguous name resolution. + */ + public function findManyByAnr(array $values = [], array|string $columns = ['*']): Collection + { + $this->select($columns); + + if (! $this->modelIsCompatibleWithAnr()) { + foreach ($values as $value) { + $this->prepareAnrEquivalentQuery($value); + } + + return $this->get($columns); + } + + return $this->findManyBy('anr', $values); + } + + /** + * Creates an ANR equivalent query for LDAP distributions that do not support ANR. + */ + protected function prepareAnrEquivalentQuery(string $value): static + { + return $this->orFilter(function (BaseBuilder $query) use ($value) { + foreach ($this->model->getAnrAttributes() as $attribute) { + $query->whereEquals($attribute, $value); + } + }); + } + + /** + * Finds a record by its string GUID. + */ + public function findByGuid(string $guid, array|string $columns = ['*']): ?Model + { + try { + return $this->findByGuidOrFail($guid, $columns); + } catch (ModelNotFoundException $e) { + return null; + } + } + + /** + * Finds a record by its string GUID or throw an exception. + * + * @throws ModelNotFoundException + */ + public function findByGuidOrFail(string $guid, array|string $columns = ['*']): Model + { + if ($this->model instanceof ActiveDirectory) { + $guid = (new Guid($guid))->getEncodedHex(); + } + + return $this->whereRaw([ + $this->model->getGuidKey() => $guid, + ])->firstOrFail($columns); + } + + /** + * {@inheritdoc} + */ + public function getQuery(): string + { + return $this->afterScopes(fn () => parent::getQuery()); + } + + /** + * Apply the query scopes and execute the callback. + */ + protected function afterScopes(Closure $callback): mixed + { + $this->applyScopes(); + + return $callback(); + } + + /** + * Apply the global query scopes. + */ + public function applyScopes(): static + { + if (! $this->scopes) { + return $this; + } + + foreach ($this->scopes as $identifier => $scope) { + if (isset($this->appliedScopes[$identifier])) { + continue; + } + + $scope instanceof Scope + ? $scope->apply($this, $this->getModel()) + : $scope($this); + + $this->appliedScopes[$identifier] = $scope; + } + + return $this; + } + + /** + * Register a new global scope. + */ + public function withGlobalScope(string $identifier, Scope|Closure $scope): static + { + $this->scopes[$identifier] = $scope; + + return $this; + } + + /** + * Remove a registered global scope. + */ + public function withoutGlobalScope(Scope|string $scope): static + { + if (! is_string($scope)) { + $scope = get_class($scope); + } + + unset($this->scopes[$scope]); + + $this->removedScopes[] = $scope; + + return $this; + } + + /** + * Remove all or passed registered global scopes. + */ + public function withoutGlobalScopes(?array $scopes = null): static + { + if (! is_array($scopes)) { + $scopes = array_keys($this->scopes); + } + + foreach ($scopes as $scope) { + $this->withoutGlobalScope($scope); + } + + return $this; + } + + /** + * Get an array of global scopes that were removed from the query. + */ + public function removedScopes(): array + { + return $this->removedScopes; + } + + /** + * Get an array of the global scopes that were applied to the query. + */ + public function appliedScopes(): array + { + return $this->appliedScopes; + } + + /** + * Processes and converts the given LDAP results into models. + */ + protected function process(array $results): Collection + { + return $this->model->hydrate(parent::process($results)); + } + + /** + * {@inheritdoc} + */ + protected function prepareWhereValue(string $field, mixed $value = null, $raw = false): string + { + if ($value instanceof DateTime) { + $field = $this->model->normalizeAttributeKey($field); + + if (! $this->model->isDateAttribute($field)) { + throw new UnexpectedValueException( + "Cannot convert field [$field] to an LDAP timestamp. You must add this field as a model date." + .' Refer to https://ldaprecord.com/docs/core/v3/model-mutators/#date-mutators' + ); + } + + $value = $this->model->fromDateTime($value, $this->model->getDates()[$field]); + } + + return parent::prepareWhereValue($field, $value, $raw); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/FreeIpaBuilder.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/FreeIpaBuilder.php new file mode 100644 index 0000000000..cac5ac30b8 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/FreeIpaBuilder.php @@ -0,0 +1,22 @@ +rawFilter('(!(pwdAccountLockedTime=*))'); + } + + /** + * Adds a disabled filter to the current query. + */ + public function whereDisabled(): static + { + return $this->rawFilter('(pwdAccountLockedTime=*)'); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/OpenLdapBuilder.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/OpenLdapBuilder.php new file mode 100644 index 0000000000..e0a8d5ebcf --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/OpenLdapBuilder.php @@ -0,0 +1,22 @@ +rawFilter('(!(pwdAccountLockedTime=*))'); + } + + /** + * Adds a disabled filter to the current query. + */ + public function whereDisabled(): static + { + return $this->rawFilter('(pwdAccountLockedTime=*)'); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/MultipleObjectsFoundException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/MultipleObjectsFoundException.php new file mode 100644 index 0000000000..79ed1e3efd --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/MultipleObjectsFoundException.php @@ -0,0 +1,7 @@ +setQuery($query, $baseDn); + } + + /** + * Set the query that was used. + */ + public function setQuery(string $query, ?string $baseDn = null): static + { + $this->query = $query; + $this->baseDn = $baseDn; + $this->message = "No LDAP query results for filter: [$query] in: [$baseDn]"; + + return $this; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ObjectsNotFoundException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ObjectsNotFoundException.php new file mode 100644 index 0000000000..85edb7f001 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ObjectsNotFoundException.php @@ -0,0 +1,7 @@ +query = $query; + $this->filter = $filter; + $this->perPage = $perPage; + $this->isCritical = $isCritical; + } + + /** + * Execute the pagination request. + */ + public function execute(LdapInterface $ldap): mixed + { + $pages = []; + + $this->prepareServerControls(); + + do { + $this->applyServerControls($ldap); + + if (! $resource = $this->query->run($this->filter)) { + break; + } + + $this->updateServerControls($ldap, $resource); + + $pages[] = $this->query->parse($resource); + } while ($this->shouldContinue()); + + $this->resetServerControls($ldap); + + return $pages; + } + + /** + * Whether the paginator should continue iterating. + */ + protected function shouldContinue(): bool + { + $cookie = $this->fetchCookie(); + + return $cookie !== ''; + } + + /** + * Fetch the pagination cookie. + */ + abstract protected function fetchCookie(): ?string; + + /** + * Prepare the server controls before executing the pagination request. + */ + abstract protected function prepareServerControls(): void; + + /** + * Apply the server controls. + */ + abstract protected function applyServerControls(LdapInterface $ldap): void; + + /** + * Reset the server controls. + */ + abstract protected function resetServerControls(LdapInterface $ldap): void; + + /** + * Update the server controls. + */ + abstract protected function updateServerControls(LdapInterface $ldap, mixed $resource): void; +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/LazyPaginator.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/LazyPaginator.php new file mode 100644 index 0000000000..d770259eda --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/LazyPaginator.php @@ -0,0 +1,31 @@ +prepareServerControls(); + + do { + $this->applyServerControls($ldap); + + if (! $resource = $this->query->run($this->filter)) { + break; + } + + $this->updateServerControls($ldap, $resource); + + yield $this->query->parse($resource); + } while ($this->shouldContinue()); + + $this->resetServerControls($ldap); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/Paginator.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/Paginator.php new file mode 100644 index 0000000000..50671a77a9 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/Paginator.php @@ -0,0 +1,64 @@ +query->controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'] ?? null; + } + + /** + * {@inheritdoc} + */ + protected function prepareServerControls(): void + { + $this->query->addControl(LDAP_CONTROL_PAGEDRESULTS, $this->isCritical, [ + 'size' => $this->perPage, 'cookie' => '', + ]); + } + + /** + * {@inheritdoc} + */ + protected function applyServerControls(LdapInterface $ldap): void + { + $ldap->setOption(LDAP_OPT_SERVER_CONTROLS, $this->query->controls); + } + + /** + * {@inheritdoc} + */ + protected function updateServerControls(LdapInterface $ldap, mixed $resource): void + { + $controls = $this->query->controls; + + $response = $ldap->parseResult( + result: $resource, + controls: $controls + ); + + $controls = array_merge($controls, $response->controls ?? []); + + $cookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'] ?? ''; + + $this->query->controls[LDAP_CONTROL_PAGEDRESULTS]['value'] = [ + 'size' => $this->perPage, + 'cookie' => $cookie, + ]; + } + + /** + * {@inheritdoc} + */ + protected function resetServerControls(LdapInterface $ldap): void + { + unset($this->query->controls[LDAP_CONTROL_PAGEDRESULTS]); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Slice.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Slice.php new file mode 100644 index 0000000000..2921724ca9 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Slice.php @@ -0,0 +1,223 @@ +items = $items; + $this->total = $total; + $this->perPage = $perPage; + $this->lastPage = max((int) ceil($total / $perPage), 1); + $this->currentPage = $currentPage ?? 1; + } + + /** + * Get the slice of items being paginated. + */ + public function items(): Collection|array + { + return $this->items; + } + + /** + * Get the total number of items being paginated. + */ + public function total(): int + { + return $this->total; + } + + /** + * Get the number of items shown per page. + */ + public function perPage(): int + { + return $this->perPage; + } + + /** + * Determine if there are more items in the data source. + */ + public function hasMorePages(): bool + { + return $this->currentPage() < $this->lastPage(); + } + + /** + * Determine if there are enough items to split into multiple pages. + */ + public function hasPages(): bool + { + return $this->currentPage() != 1 || $this->hasMorePages(); + } + + /** + * Determine if the paginator is on the first page. + */ + public function onFirstPage(): bool + { + return $this->currentPage() <= 1; + } + + /** + * Determine if the paginator is on the last page. + */ + public function onLastPage(): bool + { + return ! $this->hasMorePages(); + } + + /** + * Get the current page. + */ + public function currentPage(): int + { + return $this->currentPage; + } + + /** + * Get the last page. + */ + public function lastPage(): int + { + return $this->lastPage; + } + + /** + * Get an iterator for the items. + */ + #[\ReturnTypeWillChange] + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->items); + } + + /** + * Determine if the list of items is empty. + */ + public function isEmpty(): bool + { + return empty($this->items); + } + + /** + * Determine if the list of items is not empty. + */ + public function isNotEmpty(): bool + { + return ! $this->isEmpty(); + } + + /** + * Get the number of items for the current page. + */ + #[\ReturnTypeWillChange] + public function count(): int + { + return count($this->items); + } + + /** + * Determine if the given item exists. + */ + #[\ReturnTypeWillChange] + public function offsetExists(mixed $offset): bool + { + return array_key_exists($offset, $this->items); + } + + /** + * Get the item at the given offset. + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset): mixed + { + return $this->items[$offset] ?? null; + } + + /** + * Set the item at the given offset. + */ + #[\ReturnTypeWillChange] + public function offsetSet(mixed $offset, mixed $value): void + { + $this->items[$offset] = $value; + } + + /** + * Unset the item at the given key. + */ + #[\ReturnTypeWillChange] + public function offsetUnset(mixed $offset): void + { + unset($this->items[$offset]); + } + + /** + * Convert the object into something JSON serializable. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize(): array + { + return $this->toArray(); + } + + /** + * Get the arrayable items. + */ + public function getArrayableItems(): array + { + return $this->items instanceof Collection + ? $this->items->all() + : $this->items; + } + + /** + * Get the instance as an array. + */ + public function toArray(): array + { + return [ + 'current_page' => $this->currentPage(), + 'data' => $this->getArrayableItems(), + 'last_page' => $this->lastPage(), + 'per_page' => $this->perPage(), + 'total' => $this->total(), + ]; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Support/Arr.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Support/Arr.php new file mode 100644 index 0000000000..6c3217a509 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Support/Arr.php @@ -0,0 +1,110 @@ +offsetExists($key); + } + + return array_key_exists($key, $array); + } + + /** + * If the given value is not an array and not null, wrap it in one. + */ + public static function wrap(mixed $value): array + { + if (is_null($value)) { + return []; + } + + return is_array($value) ? $value : [$value]; + } + + /** + * Get the first element in an array passing a given truth test. + */ + public static function first(iterable $array, ?callable $callback = null, mixed $default = null): mixed + { + if (is_null($callback)) { + if (empty($array)) { + return Helpers::value($default); + } + + foreach ($array as $item) { + return $item; + } + } + + foreach ($array as $key => $value) { + if ($callback($value, $key)) { + return $value; + } + } + + return Helpers::value($default); + } + + /** + * Get the last element in an array passing a given truth test. + * + * @param array $array + */ + public static function last(iterable $array, ?callable $callback = null, mixed $default = null): mixed + { + if (is_null($callback)) { + return empty($array) ? Helpers::value($default) : end($array); + } + + return static::first(array_reverse($array, true), $callback, $default); + } + + /** + * Get an item from an array using "dot" notation. + */ + public static function get(ArrayAccess|array $array, string|int|null $key, mixed $default = null): mixed + { + if (! static::accessible($array)) { + return Helpers::value($default); + } + + if (is_null($key)) { + return $array; + } + + if (static::exists($array, $key)) { + return $array[$key]; + } + + if (str_contains($key, '.')) { + return $array[$key] ?? Helpers::value($default); + } + + foreach (explode('.', $key) as $segment) { + if (static::accessible($array) && static::exists($array, $segment)) { + $array = $array[$segment]; + } else { + return Helpers::value($default); + } + } + + return $array; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Support/Helpers.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Support/Helpers.php new file mode 100644 index 0000000000..5b4bd46fb6 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Support/Helpers.php @@ -0,0 +1,16 @@ +configure(); + + return $connection; + } + + /** + * Set the user to authenticate as. + */ + public function actingAs(Model|string $user): static + { + $this->ldap->shouldAllowBindWith( + $user instanceof Model ? $user->getDn() : $user + ); + + return $this; + } + + /** + * Set the connection to bypass bind attempts as the configured user. + */ + public function shouldBeConnected(): static + { + $this->connected = true; + + $this->authGuardResolver = function () { + return new AuthGuardFake($this->ldap, $this->configuration); + }; + + return $this; + } + + /** + * Set the connection to attempt binding as the configured user. + */ + public function shouldNotBeConnected(): static + { + $this->connected = false; + + $this->authGuardResolver = function () { + return new Guard($this->ldap, $this->configuration); + }; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function isConnected(): bool + { + return $this->connected ?: parent::isConnected(); + } + + /** + * Perform tear down tasks on the fake. + * + * @throws LdapExpectationException + */ + public function tearDown(): void + { + $this->ldap->assertMinimumExpectationCounts(); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/DirectoryFake.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/DirectoryFake.php new file mode 100644 index 0000000000..90cbf7ae63 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/DirectoryFake.php @@ -0,0 +1,57 @@ +getConfiguration()->all() + ); + + Container::addConnection($fake, $name); + + return $fake; + } + + /** + * Replace all faked connections with their original. + */ + public static function tearDown(): void + { + foreach (static::$replaced as $name => $connection) { + Container::getConnection($name)->tearDown(); + } + + Container::flush(); + + static::$replaced = []; + } + + /** + * Make a connection fake. + */ + public static function makeConnectionFake(array $config = []): ConnectionFake + { + return ConnectionFake::make($config)->shouldBeConnected(); + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/LdapExpectation.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/LdapExpectation.php new file mode 100644 index 0000000000..2e010d7d72 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/LdapExpectation.php @@ -0,0 +1,324 @@ +method = $method; + } + + /** + * Set the arguments that the operation should receive. + */ + public function with(mixed $args): static + { + $this->args = array_map(function ($arg) { + if ($arg instanceof Closure) { + return new Callback($arg); + } + + if (! $arg instanceof Constraint) { + return new IsEqual($arg); + } + + return $arg; + }, is_array($args) ? $args : func_get_args()); + + return $this; + } + + /** + * Set the expected value to return. + */ + public function andReturn(mixed $value): static + { + $this->value = $value; + + return $this; + } + + /** + * Set the expected value to return true. + */ + public function andReturnTrue(): static + { + return $this->andReturn(true); + } + + /** + * Set the expected value to return false. + */ + public function andReturnFalse(): static + { + return $this->andReturn(false); + } + + /** + * The error message to return from the expectation. + */ + public function andReturnError(int $errorCode = 1, string $errorMessage = 'Unknown error', ?string $diagnosticMessage = null): static + { + $this->errors = true; + + $this->errorCode = $errorCode; + $this->errorMessage = $errorMessage; + $this->errorDiagnosticMessage = $diagnosticMessage; + + return $this; + } + + /** + * Return an error LDAP result response. + */ + public function andReturnErrorResponse(int $code = 1, ?string $errorMessage = null): static + { + return $this->andReturnResponse($code, $errorMessage); + } + + /** + * Return an LDAP result response. + */ + public function andReturnResponse( + int $errorCode = 0, + ?string $matchedDn = null, + ?string $errorMessage = null, + array $referrals = [], + array $controls = [] + ): static { + return $this->andReturn( + new LdapResultResponse($errorCode, $matchedDn, $errorMessage, $referrals, $controls) + ); + } + + /** + * Set the expected exception to throw. + */ + public function andThrow(string|Exception $exception): static + { + if (is_string($exception)) { + $exception = new LdapRecordException($exception); + } + + $this->exception = $exception; + + return $this; + } + + /** + * Set the expectation to be only called once. + */ + public function once(): static + { + return $this->times(); + } + + /** + * Set the expectation to be only called twice. + */ + public function twice(): static + { + return $this->times(2); + } + + /** + * Set the expectation to be called the given number of times. + */ + public function times(int $count = 1): static + { + $this->indefinitely = false; + + $this->originalCount = $this->count = $count; + + return $this; + } + + /** + * Get the method the expectation belongs to. + */ + public function getMethod(): string + { + if (is_null($this->method)) { + throw new UnexpectedValueException('An expectation must have a method.'); + } + + return $this->method; + } + + /** + * Get the expected call count. + */ + public function getExpectedCount(): int + { + return $this->count; + } + + /** + * Get the original expected call count. + */ + public function getOriginalExpectedCount(): int + { + return $this->originalCount; + } + + /** + * Get the count that the expectation was called. + */ + public function getCalledCount(): int + { + return $this->called; + } + + /** + * Get the expected arguments. + * + * @return Constraint[] + */ + public function getExpectedArgs(): array + { + return $this->args; + } + + /** + * Get the expected exception. + */ + public function getExpectedException(): ?Exception + { + return $this->exception; + } + + /** + * Get the expected value. + */ + public function getExpectedValue(): mixed + { + return $this->value; + } + + /** + * Determine whether the expectation can be called indefinitely. + */ + public function isIndefinite(): bool + { + return $this->indefinitely; + } + + /** + * Determine whether the expectation is returning an error. + */ + public function isReturningError(): bool + { + return $this->errors; + } + + /** + * Get the expected error code. + */ + public function getExpectedErrorCode(): int + { + return $this->errorCode; + } + + /** + * Get the expected error message. + */ + public function getExpectedErrorMessage(): ?string + { + return $this->errorMessage; + } + + /** + * Get the expected diagnostic message. + */ + public function getExpectedErrorDiagnosticMessage(): ?string + { + return $this->errorDiagnosticMessage; + } + + /** + * Decrement the expected count of the expectation. + */ + public function decrementExpectedCount(): static + { + if (! $this->indefinitely) { + $this->count -= 1; + } + + $this->called++; + + return $this; + } +} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/LdapExpectationException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/LdapExpectationException.php new file mode 100644 index 0000000000..74a3ebe33f --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/LdapExpectationException.php @@ -0,0 +1,7 @@ + + */ + protected array $expectations = []; + + /** + * The default fake error number. + */ + protected int $errNo = 1; + + /** + * The default fake last error string. + */ + protected string $lastError = 'Unknown error'; + + /** + * The default fake diagnostic message string. + */ + protected ?string $diagnosticMessage = null; + + /** + * Create a new expected operation. + */ + public static function operation(string $method): LdapExpectation + { + return new LdapExpectation($method); + } + + /** + * Set the fake LDAP host. + */ + public function setHost(string $host): void + { + $this->host = $host; + } + + /** + * Add an LDAP method expectation. + */ + public function expect(LdapExpectation|array $expectations = []): static + { + $expectations = Arr::wrap($expectations); + + foreach ($expectations as $key => $expectation) { + if (! is_int($key)) { + $operation = static::operation($key); + + $expectation instanceof Closure + ? $expectation($operation) + : $operation->andReturn($expectation); + + $expectation = $operation; + } + + if (! $expectation instanceof LdapExpectation) { + $expectation = static::operation($expectation); + } + + $this->expectations[$expectation->getMethod()][] = $expectation; + } + + return $this; + } + + /** + * Determine if the method has any expectations. + */ + public function hasExpectations(string $method): bool + { + return count($this->getExpectations($method)) > 0; + } + + /** + * Get expectations by method. + * + * @return LdapExpectation[] + */ + public function getExpectations(string $method): array + { + return $this->expectations[$method] ?? []; + } + + /** + * Remove an expectation by method and key. + */ + public function removeExpectation(string $method, int $key): void + { + unset($this->expectations[$method][$key]); + } + + /** + * Set the fake to allow any bind attempt. + */ + public function shouldAllowAnyBind(): static + { + return $this->expect( + static::operation('bind')->andReturnResponse() + ); + } + + /** + * Set the fake to allow any bind attempt with the given DN. + */ + public function shouldAllowBindWith(string $dn): static + { + return $this->expect( + static::operation('bind')->with($dn, PHPUnit::anything())->andReturnResponse() + ); + } + + /** + * Set the user that will pass binding. + * + * @deprecated Use shouldAllowBindWith instead. + */ + public function shouldAuthenticateWith(string $dn): static + { + return $this->shouldAllowBindWith($dn); + } + + /** + * Set the error number of a failed bind attempt. + */ + public function shouldReturnErrorNumber(int $number = 1): static + { + $this->errNo = $number; + + return $this; + } + + /** + * Set the last error of a failed bind attempt. + */ + public function shouldReturnError(string $message): static + { + $this->lastError = $message; + + return $this; + } + + /** + * Set the diagnostic message of a failed bind attempt. + */ + public function shouldReturnDiagnosticMessage(?string $message): static + { + $this->diagnosticMessage = $message; + + return $this; + } + + /** + * Return a fake error number. + */ + public function errNo(): int + { + return $this->errNo; + } + + /** + * Return a fake error. + */ + public function getLastError(): string + { + return $this->lastError; + } + + /** + * Return a fake diagnostic message. + */ + public function getDiagnosticMessage(): ?string + { + return $this->diagnosticMessage; + } + + /** + * Return a fake detailed error. + */ + public function getDetailedError(): DetailedError + { + return new DetailedError( + $this->errNo(), + $this->getLastError(), + $this->getDiagnosticMessage() + ); + } + + /** + * {@inheritdoc} + */ + public function getEntries(mixed $result): array + { + return $result; + } + + /** + * {@inheritDoc} + */ + public function getValuesLen(mixed $entry, string $attribute): array|false + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritDoc} + */ + public function compare(string $dn, string $attribute, string $value, ?array $controls = null): bool|int + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritDoc} + */ + public function setRebindCallback(callable $callback): bool + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritDoc} + */ + public function getFirstEntry(mixed $result): mixed + { + return $this->executeFailableOperation(function () use ($result) { + return ldap_first_entry($this->connection, $result); + }); + } + + /** + * {@inheritDoc} + */ + public function getNextEntry(mixed $entry): mixed + { + return $this->executeFailableOperation(function () use ($entry) { + return ldap_next_entry($this->connection, $entry); + }); + } + + /** + * {@inheritDoc} + */ + public function getAttributes(mixed $entry): array|false + { + return $this->executeFailableOperation(function () use ($entry) { + return ldap_get_attributes($this->connection, $entry); + }); + } + + /** + * {@inheritDoc} + */ + public function countEntries(mixed $result): int + { + return $this->executeFailableOperation(function () use ($result) { + return ldap_count_entries($this->connection, $result); + }); + } + + /** + * {@inheritdoc} + */ + public function isUsingSSL(): bool + { + return $this->hasExpectations(__FUNCTION__) + ? $this->resolveExpectation(__FUNCTION__) + : $this->useSSL; + } + + /** + * {@inheritdoc} + */ + public function isUsingTLS(): bool + { + return $this->hasExpectations(__FUNCTION__) + ? $this->resolveExpectation(__FUNCTION__) + : $this->useTLS; + } + + /** + * {@inheritdoc} + */ + public function isBound(): bool + { + return $this->hasExpectations(__FUNCTION__) + ? $this->resolveExpectation(__FUNCTION__) + : $this->bound; + } + + /** + * {@inheritdoc} + */ + public function setOption(int $option, mixed $value): bool + { + return $this->hasExpectations(__FUNCTION__) + ? $this->resolveExpectation(__FUNCTION__, func_get_args()) + : true; + } + + /** + * {@inheritdoc} + */ + public function getOption(int $option, mixed &$value = null): mixed + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function startTLS(): bool + { + return $this->secure = $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function connect(string|array $hosts = [], int $port = 389, ?string $protocol = null): bool + { + $this->bound = false; + $this->protocol = $protocol; + $this->host = $this->makeConnectionUris($hosts, $port); + + return $this->connection = $this->hasExpectations(__FUNCTION__) + ? $this->resolveExpectation(__FUNCTION__, func_get_args()) + : true; + } + + /** + * {@inheritdoc} + */ + public function close(): bool + { + $this->bound = false; + $this->secure = false; + + $this->host = null; + $this->protocol = null; + $this->connection = null; + + return $this->hasExpectations(__FUNCTION__) + ? $this->resolveExpectation(__FUNCTION__) + : true; + } + + /** + * {@inheritdoc} + */ + public function bind(?string $dn = null, ?string $password = null, ?array $controls = null): LdapResultResponse + { + $result = $this->resolveExpectation(__FUNCTION__, func_get_args()); + + $this->handleBindResponse($result); + + return $result; + } + + /** + * {@inheritDoc} + */ + public function saslBind(?string $dn = null, ?string $password = null, ?array $options = null): bool + { + return $this->bound = $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function search(string $dn, string $filter, array $fields, bool $onlyAttributes = false, int $size = 0, int $time = 0, int $deref = LDAP_DEREF_NEVER, ?array $controls = null): mixed + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function list(string $dn, string $filter, array $fields, bool $onlyAttributes = false, int $size = 0, int $time = 0, int $deref = LDAP_DEREF_NEVER, ?array $controls = null): mixed + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function read(string $dn, string $filter, array $fields, bool $onlyAttributes = false, int $size = 0, int $time = 0, int $deref = LDAP_DEREF_NEVER, ?array $controls = null): mixed + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function parseResult(mixed $result, int &$errorCode = 0, ?string &$dn = null, ?string &$errorMessage = null, ?array &$referrals = null, ?array &$controls = null): LdapResultResponse|false + { + return $this->hasExpectations(__FUNCTION__) + ? $this->resolveExpectation(__FUNCTION__, func_get_args()) + : new LdapResultResponse; + } + + /** + * {@inheritdoc} + */ + public function add(string $dn, array $entry): bool + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function delete(string $dn): bool + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function rename(string $dn, string $newRdn, string $newParent, bool $deleteOldRdn = false): bool + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function modify(string $dn, array $entry): bool + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function modifyBatch(string $dn, array $values): bool + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function modAdd(string $dn, array $entry): bool + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function modReplace(string $dn, array $entry): bool + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function modDelete(string $dn, array $entry): bool + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function freeResult(mixed $result): bool + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * {@inheritdoc} + */ + public function err2Str(int $number): string + { + return $this->resolveExpectation(__FUNCTION__, func_get_args()); + } + + /** + * Resolve the methods expectations. + * + * @throws LdapExpectationException + */ + protected function resolveExpectation(string $method, array $args = []): mixed + { + foreach ($this->getExpectations($method) as $key => $expectation) { + $this->assertMethodArgumentsMatch($method, $expectation->getExpectedArgs(), $args); + + $expectation->decrementExpectedCount(); + + if ($expectation->getExpectedCount() === 0) { + $this->removeExpectation($method, $key); + } + + if (! is_null($exception = $expectation->getExpectedException())) { + throw $exception; + } + + if ($expectation->isReturningError()) { + $this->applyExpectationError($expectation); + } + + return $expectation->getExpectedValue(); + } + + throw new LdapExpectationException("LDAP method [$method] was unexpected."); + } + + /** + * Apply the expectation error to the fake. + */ + protected function applyExpectationError(LdapExpectation $expectation): void + { + $this->shouldReturnError($expectation->getExpectedErrorMessage()); + $this->shouldReturnErrorNumber($expectation->getExpectedErrorCode()); + $this->shouldReturnDiagnosticMessage($expectation->getExpectedErrorDiagnosticMessage()); + } + + /** + * Assert that the expectations have been called their minimum amount of times. + * + * @throws LdapExpectationException + */ + public function assertMinimumExpectationCounts(): void + { + foreach ($this->expectations as $method => $expectations) { + foreach ($expectations as $expectation) { + if (! $expectation->isIndefinite() && $expectation->getExpectedCount()) { + $remaining = ($original = $expectation->getOriginalExpectedCount()) - $expectation->getExpectedCount(); + + throw new LdapExpectationException("Method [$method] should be called $original times but was called $remaining times."); + } + } + } + } + + /** + * Assert that the expected arguments match the operations arguments. + * + * @param Constraint[] $expectedArgs + */ + protected function assertMethodArgumentsMatch(string $method, array $expectedArgs = [], array $methodArgs = []): void + { + foreach ($expectedArgs as $key => $constraint) { + $argNumber = $key + 1; + + PHPUnit::assertArrayHasKey( + $key, + $methodArgs, + "LDAP method [$method] argument #$argNumber does not exist." + ); + + $constraint->evaluate( + $methodArgs[$key], + "LDAP method [$method] expectation failed." + ); + } + } +} diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/CHANGELOG.md b/data/web/inc/lib/vendor/firebase/php-jwt/CHANGELOG.md new file mode 100644 index 0000000000..35e97fe86b --- /dev/null +++ b/data/web/inc/lib/vendor/firebase/php-jwt/CHANGELOG.md @@ -0,0 +1,117 @@ +# Changelog + +## [6.5.0](https://github.com/firebase/php-jwt/compare/v6.4.0...v6.5.0) (2023-05-12) + + +### Bug Fixes + +* allow KID of '0' ([#505](https://github.com/firebase/php-jwt/issues/505)) ([9dc46a9](https://github.com/firebase/php-jwt/commit/9dc46a9c3e5801294249cfd2554c5363c9f9326a)) + + +### Miscellaneous Chores + +* drop support for PHP 7.3 ([#495](https://github.com/firebase/php-jwt/issues/495)) + +## [6.4.0](https://github.com/firebase/php-jwt/compare/v6.3.2...v6.4.0) (2023-02-08) + + +### Features + +* add support for W3C ES256K ([#462](https://github.com/firebase/php-jwt/issues/462)) ([213924f](https://github.com/firebase/php-jwt/commit/213924f51936291fbbca99158b11bd4ae56c2c95)) +* improve caching by only decoding jwks when necessary ([#486](https://github.com/firebase/php-jwt/issues/486)) ([78d3ed1](https://github.com/firebase/php-jwt/commit/78d3ed1073553f7d0bbffa6c2010009a0d483d5c)) + +## [6.3.2](https://github.com/firebase/php-jwt/compare/v6.3.1...v6.3.2) (2022-11-01) + + +### Bug Fixes + +* check kid before using as array index ([bad1b04](https://github.com/firebase/php-jwt/commit/bad1b040d0c736bbf86814c6b5ae614f517cf7bd)) + +## [6.3.1](https://github.com/firebase/php-jwt/compare/v6.3.0...v6.3.1) (2022-11-01) + + +### Bug Fixes + +* casing of GET for PSR compat ([#451](https://github.com/firebase/php-jwt/issues/451)) ([60b52b7](https://github.com/firebase/php-jwt/commit/60b52b71978790eafcf3b95cfbd83db0439e8d22)) +* string interpolation format for php 8.2 ([#446](https://github.com/firebase/php-jwt/issues/446)) ([2e07d8a](https://github.com/firebase/php-jwt/commit/2e07d8a1524d12b69b110ad649f17461d068b8f2)) + +## 6.3.0 / 2022-07-15 + + - Added ES256 support to JWK parsing ([#399](https://github.com/firebase/php-jwt/pull/399)) + - Fixed potential caching error in `CachedKeySet` by caching jwks as strings ([#435](https://github.com/firebase/php-jwt/pull/435)) + +## 6.2.0 / 2022-05-14 + + - Added `CachedKeySet` ([#397](https://github.com/firebase/php-jwt/pull/397)) + - Added `$defaultAlg` parameter to `JWT::parseKey` and `JWT::parseKeySet` ([#426](https://github.com/firebase/php-jwt/pull/426)). + +## 6.1.0 / 2022-03-23 + + - Drop support for PHP 5.3, 5.4, 5.5, 5.6, and 7.0 + - Add parameter typing and return types where possible + +## 6.0.0 / 2022-01-24 + + - **Backwards-Compatibility Breaking Changes**: See the [Release Notes](https://github.com/firebase/php-jwt/releases/tag/v6.0.0) for more information. + - New Key object to prevent key/algorithm type confusion (#365) + - Add JWK support (#273) + - Add ES256 support (#256) + - Add ES384 support (#324) + - Add Ed25519 support (#343) + +## 5.0.0 / 2017-06-26 +- Support RS384 and RS512. + See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)! +- Add an example for RS256 openssl. + See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)! +- Detect invalid Base64 encoding in signature. + See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)! +- Update `JWT::verify` to handle OpenSSL errors. + See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)! +- Add `array` type hinting to `decode` method + See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)! +- Add all JSON error types. + See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)! +- Bugfix 'kid' not in given key list. + See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)! +- Miscellaneous cleanup, documentation and test fixes. + See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115), + [#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and + [#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman), + [@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)! + +## 4.0.0 / 2016-07-17 +- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)! +- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)! +- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)! +- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)! + +## 3.0.0 / 2015-07-22 +- Minimum PHP version updated from `5.2.0` to `5.3.0`. +- Add `\Firebase\JWT` namespace. See +[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to +[@Dashron](https://github.com/Dashron)! +- Require a non-empty key to decode and verify a JWT. See +[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to +[@sjones608](https://github.com/sjones608)! +- Cleaner documentation blocks in the code. See +[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to +[@johanderuijter](https://github.com/johanderuijter)! + +## 2.2.0 / 2015-06-22 +- Add support for adding custom, optional JWT headers to `JWT::encode()`. See +[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to +[@mcocaro](https://github.com/mcocaro)! + +## 2.1.0 / 2015-05-20 +- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew +between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)! +- Add support for passing an object implementing the `ArrayAccess` interface for +`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)! + +## 2.0.0 / 2015-04-01 +- **Note**: It is strongly recommended that you update to > v2.0.0 to address + known security vulnerabilities in prior versions when both symmetric and + asymmetric keys are used together. +- Update signature for `JWT::decode(...)` to require an array of supported + algorithms to use when verifying token signatures. diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/LICENSE b/data/web/inc/lib/vendor/firebase/php-jwt/LICENSE new file mode 100644 index 0000000000..11c0146651 --- /dev/null +++ b/data/web/inc/lib/vendor/firebase/php-jwt/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2011, Neuman Vong + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the copyright holder nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/README.md b/data/web/inc/lib/vendor/firebase/php-jwt/README.md new file mode 100644 index 0000000000..f038266736 --- /dev/null +++ b/data/web/inc/lib/vendor/firebase/php-jwt/README.md @@ -0,0 +1,419 @@ +![Build Status](https://github.com/firebase/php-jwt/actions/workflows/tests.yml/badge.svg) +[![Latest Stable Version](https://poser.pugx.org/firebase/php-jwt/v/stable)](https://packagist.org/packages/firebase/php-jwt) +[![Total Downloads](https://poser.pugx.org/firebase/php-jwt/downloads)](https://packagist.org/packages/firebase/php-jwt) +[![License](https://poser.pugx.org/firebase/php-jwt/license)](https://packagist.org/packages/firebase/php-jwt) + +PHP-JWT +======= +A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to [RFC 7519](https://tools.ietf.org/html/rfc7519). + +Installation +------------ + +Use composer to manage your dependencies and download PHP-JWT: + +```bash +composer require firebase/php-jwt +``` + +Optionally, install the `paragonie/sodium_compat` package from composer if your +php is < 7.2 or does not have libsodium installed: + +```bash +composer require paragonie/sodium_compat +``` + +Example +------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +$key = 'example_key'; +$payload = [ + 'iss' => 'http://example.org', + 'aud' => 'http://example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +/** + * IMPORTANT: + * You must specify supported algorithms for your application. See + * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 + * for a list of spec-compliant algorithms. + */ +$jwt = JWT::encode($payload, $key, 'HS256'); +$decoded = JWT::decode($jwt, new Key($key, 'HS256')); + +print_r($decoded); + +/* + NOTE: This will now be an object instead of an associative array. To get + an associative array, you will need to cast it as such: +*/ + +$decoded_array = (array) $decoded; + +/** + * You can add a leeway to account for when there is a clock skew times between + * the signing and verifying servers. It is recommended that this leeway should + * not be bigger than a few minutes. + * + * Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef + */ +JWT::$leeway = 60; // $leeway in seconds +$decoded = JWT::decode($jwt, new Key($key, 'HS256')); +``` +Example encode/decode headers +------- +Decoding the JWT headers without verifying the JWT first is NOT recommended, and is not supported by +this library. This is because without verifying the JWT, the header values could have been tampered with. +Any value pulled from an unverified header should be treated as if it could be any string sent in from an +attacker. If this is something you still want to do in your application for whatever reason, it's possible to +decode the header values manually simply by calling `json_decode` and `base64_decode` on the JWT +header part: +```php +use Firebase\JWT\JWT; + +$key = 'example_key'; +$payload = [ + 'iss' => 'http://example.org', + 'aud' => 'http://example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$headers = [ + 'x-forwarded-for' => 'www.google.com' +]; + +// Encode headers in the JWT string +$jwt = JWT::encode($payload, $key, 'HS256', null, $headers); + +// Decode headers from the JWT string WITHOUT validation +// **IMPORTANT**: This operation is vulnerable to attacks, as the JWT has not yet been verified. +// These headers could be any value sent by an attacker. +list($headersB64, $payloadB64, $sig) = explode('.', $jwt); +$decoded = json_decode(base64_decode($headersB64), true); + +print_r($decoded); +``` +Example with RS256 (openssl) +---------------------------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +$privateKey = << 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$jwt = JWT::encode($payload, $privateKey, 'RS256'); +echo "Encode:\n" . print_r($jwt, true) . "\n"; + +$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256')); + +/* + NOTE: This will now be an object instead of an associative array. To get + an associative array, you will need to cast it as such: +*/ + +$decoded_array = (array) $decoded; +echo "Decode:\n" . print_r($decoded_array, true) . "\n"; +``` + +Example with a passphrase +------------------------- + +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +// Your passphrase +$passphrase = '[YOUR_PASSPHRASE]'; + +// Your private key file with passphrase +// Can be generated with "ssh-keygen -t rsa -m pem" +$privateKeyFile = '/path/to/key-with-passphrase.pem'; + +// Create a private key of type "resource" +$privateKey = openssl_pkey_get_private( + file_get_contents($privateKeyFile), + $passphrase +); + +$payload = [ + 'iss' => 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$jwt = JWT::encode($payload, $privateKey, 'RS256'); +echo "Encode:\n" . print_r($jwt, true) . "\n"; + +// Get public key from the private key, or pull from from a file. +$publicKey = openssl_pkey_get_details($privateKey)['key']; + +$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256')); +echo "Decode:\n" . print_r((array) $decoded, true) . "\n"; +``` + +Example with EdDSA (libsodium and Ed25519 signature) +---------------------------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +// Public and private keys are expected to be Base64 encoded. The last +// non-empty line is used so that keys can be generated with +// sodium_crypto_sign_keypair(). The secret keys generated by other tools may +// need to be adjusted to match the input expected by libsodium. + +$keyPair = sodium_crypto_sign_keypair(); + +$privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair)); + +$publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair)); + +$payload = [ + 'iss' => 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$jwt = JWT::encode($payload, $privateKey, 'EdDSA'); +echo "Encode:\n" . print_r($jwt, true) . "\n"; + +$decoded = JWT::decode($jwt, new Key($publicKey, 'EdDSA')); +echo "Decode:\n" . print_r((array) $decoded, true) . "\n"; +```` + +Example with multiple keys +-------------------------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +// Example RSA keys from previous example +// $privateKey1 = '...'; +// $publicKey1 = '...'; + +// Example EdDSA keys from previous example +// $privateKey2 = '...'; +// $publicKey2 = '...'; + +$payload = [ + 'iss' => 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$jwt1 = JWT::encode($payload, $privateKey1, 'RS256', 'kid1'); +$jwt2 = JWT::encode($payload, $privateKey2, 'EdDSA', 'kid2'); +echo "Encode 1:\n" . print_r($jwt1, true) . "\n"; +echo "Encode 2:\n" . print_r($jwt2, true) . "\n"; + +$keys = [ + 'kid1' => new Key($publicKey1, 'RS256'), + 'kid2' => new Key($publicKey2, 'EdDSA'), +]; + +$decoded1 = JWT::decode($jwt1, $keys); +$decoded2 = JWT::decode($jwt2, $keys); + +echo "Decode 1:\n" . print_r((array) $decoded1, true) . "\n"; +echo "Decode 2:\n" . print_r((array) $decoded2, true) . "\n"; +``` + +Using JWKs +---------- + +```php +use Firebase\JWT\JWK; +use Firebase\JWT\JWT; + +// Set of keys. The "keys" key is required. For example, the JSON response to +// this endpoint: https://www.gstatic.com/iap/verify/public_key-jwk +$jwks = ['keys' => []]; + +// JWK::parseKeySet($jwks) returns an associative array of **kid** to Firebase\JWT\Key +// objects. Pass this as the second parameter to JWT::decode. +JWT::decode($payload, JWK::parseKeySet($jwks)); +``` + +Using Cached Key Sets +--------------------- + +The `CachedKeySet` class can be used to fetch and cache JWKS (JSON Web Key Sets) from a public URI. +This has the following advantages: + +1. The results are cached for performance. +2. If an unrecognized key is requested, the cache is refreshed, to accomodate for key rotation. +3. If rate limiting is enabled, the JWKS URI will not make more than 10 requests a second. + +```php +use Firebase\JWT\CachedKeySet; +use Firebase\JWT\JWT; + +// The URI for the JWKS you wish to cache the results from +$jwksUri = 'https://www.gstatic.com/iap/verify/public_key-jwk'; + +// Create an HTTP client (can be any PSR-7 compatible HTTP client) +$httpClient = new GuzzleHttp\Client(); + +// Create an HTTP request factory (can be any PSR-17 compatible HTTP request factory) +$httpFactory = new GuzzleHttp\Psr\HttpFactory(); + +// Create a cache item pool (can be any PSR-6 compatible cache item pool) +$cacheItemPool = Phpfastcache\CacheManager::getInstance('files'); + +$keySet = new CachedKeySet( + $jwksUri, + $httpClient, + $httpFactory, + $cacheItemPool, + null, // $expiresAfter int seconds to set the JWKS to expire + true // $rateLimit true to enable rate limit of 10 RPS on lookup of invalid keys +); + +$jwt = 'eyJhbGci...'; // Some JWT signed by a key from the $jwkUri above +$decoded = JWT::decode($jwt, $keySet); +``` + +Miscellaneous +------------- + +#### Exception Handling + +When a call to `JWT::decode` is invalid, it will throw one of the following exceptions: + +```php +use Firebase\JWT\JWT; +use Firebase\JWT\SignatureInvalidException; +use Firebase\JWT\BeforeValidException; +use Firebase\JWT\ExpiredException; +use DomainException; +use InvalidArgumentException; +use UnexpectedValueException; + +try { + $decoded = JWT::decode($payload, $keys); +} catch (InvalidArgumentException $e) { + // provided key/key-array is empty or malformed. +} catch (DomainException $e) { + // provided algorithm is unsupported OR + // provided key is invalid OR + // unknown error thrown in openSSL or libsodium OR + // libsodium is required but not available. +} catch (SignatureInvalidException $e) { + // provided JWT signature verification failed. +} catch (BeforeValidException $e) { + // provided JWT is trying to be used before "nbf" claim OR + // provided JWT is trying to be used before "iat" claim. +} catch (ExpiredException $e) { + // provided JWT is trying to be used after "exp" claim. +} catch (UnexpectedValueException $e) { + // provided JWT is malformed OR + // provided JWT is missing an algorithm / using an unsupported algorithm OR + // provided JWT algorithm does not match provided key OR + // provided key ID in key/key-array is empty or invalid. +} +``` + +All exceptions in the `Firebase\JWT` namespace extend `UnexpectedValueException`, and can be simplified +like this: + +```php +try { + $decoded = JWT::decode($payload, $keys); +} catch (LogicException $e) { + // errors having to do with environmental setup or malformed JWT Keys +} catch (UnexpectedValueException $e) { + // errors having to do with JWT signature and claims +} +``` + +#### Casting to array + +The return value of `JWT::decode` is the generic PHP object `stdClass`. If you'd like to handle with arrays +instead, you can do the following: + +```php +// return type is stdClass +$decoded = JWT::decode($payload, $keys); + +// cast to array +$decoded = json_decode(json_encode($decoded), true); +``` + +Tests +----- +Run the tests using phpunit: + +```bash +$ pear install PHPUnit +$ phpunit --configuration phpunit.xml.dist +PHPUnit 3.7.10 by Sebastian Bergmann. +..... +Time: 0 seconds, Memory: 2.50Mb +OK (5 tests, 5 assertions) +``` + +New Lines in private keys +----- + +If your private key contains `\n` characters, be sure to wrap it in double quotes `""` +and not single quotes `''` in order to properly interpret the escaped characters. + +License +------- +[3-Clause BSD](http://opensource.org/licenses/BSD-3-Clause). diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/composer.json b/data/web/inc/lib/vendor/firebase/php-jwt/composer.json new file mode 100644 index 0000000000..e23dfe378d --- /dev/null +++ b/data/web/inc/lib/vendor/firebase/php-jwt/composer.json @@ -0,0 +1,42 @@ +{ + "name": "firebase/php-jwt", + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "php", + "jwt" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "license": "BSD-3-Clause", + "require": { + "php": "^7.4||^8.0" + }, + "suggest": { + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present", + "ext-sodium": "Support EdDSA (Ed25519) signatures" + }, + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.5||^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^1.0||^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + } +} diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/BeforeValidException.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/BeforeValidException.php new file mode 100644 index 0000000000..c147852b98 --- /dev/null +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/BeforeValidException.php @@ -0,0 +1,7 @@ + + */ +class CachedKeySet implements ArrayAccess +{ + /** + * @var string + */ + private $jwksUri; + /** + * @var ClientInterface + */ + private $httpClient; + /** + * @var RequestFactoryInterface + */ + private $httpFactory; + /** + * @var CacheItemPoolInterface + */ + private $cache; + /** + * @var ?int + */ + private $expiresAfter; + /** + * @var ?CacheItemInterface + */ + private $cacheItem; + /** + * @var array> + */ + private $keySet; + /** + * @var string + */ + private $cacheKey; + /** + * @var string + */ + private $cacheKeyPrefix = 'jwks'; + /** + * @var int + */ + private $maxKeyLength = 64; + /** + * @var bool + */ + private $rateLimit; + /** + * @var string + */ + private $rateLimitCacheKey; + /** + * @var int + */ + private $maxCallsPerMinute = 10; + /** + * @var string|null + */ + private $defaultAlg; + + public function __construct( + string $jwksUri, + ClientInterface $httpClient, + RequestFactoryInterface $httpFactory, + CacheItemPoolInterface $cache, + int $expiresAfter = null, + bool $rateLimit = false, + string $defaultAlg = null + ) { + $this->jwksUri = $jwksUri; + $this->httpClient = $httpClient; + $this->httpFactory = $httpFactory; + $this->cache = $cache; + $this->expiresAfter = $expiresAfter; + $this->rateLimit = $rateLimit; + $this->defaultAlg = $defaultAlg; + $this->setCacheKeys(); + } + + /** + * @param string $keyId + * @return Key + */ + public function offsetGet($keyId): Key + { + if (!$this->keyIdExists($keyId)) { + throw new OutOfBoundsException('Key ID not found'); + } + return JWK::parseKey($this->keySet[$keyId], $this->defaultAlg); + } + + /** + * @param string $keyId + * @return bool + */ + public function offsetExists($keyId): bool + { + return $this->keyIdExists($keyId); + } + + /** + * @param string $offset + * @param Key $value + */ + public function offsetSet($offset, $value): void + { + throw new LogicException('Method not implemented'); + } + + /** + * @param string $offset + */ + public function offsetUnset($offset): void + { + throw new LogicException('Method not implemented'); + } + + /** + * @return array + */ + private function formatJwksForCache(string $jwks): array + { + $jwks = json_decode($jwks, true); + + if (!isset($jwks['keys'])) { + throw new UnexpectedValueException('"keys" member must exist in the JWK Set'); + } + + if (empty($jwks['keys'])) { + throw new InvalidArgumentException('JWK Set did not contain any keys'); + } + + $keys = []; + foreach ($jwks['keys'] as $k => $v) { + $kid = isset($v['kid']) ? $v['kid'] : $k; + $keys[(string) $kid] = $v; + } + + return $keys; + } + + private function keyIdExists(string $keyId): bool + { + if (null === $this->keySet) { + $item = $this->getCacheItem(); + // Try to load keys from cache + if ($item->isHit()) { + // item found! retrieve it + $this->keySet = $item->get(); + // If the cached item is a string, the JWKS response was cached (previous behavior). + // Parse this into expected format array instead. + if (\is_string($this->keySet)) { + $this->keySet = $this->formatJwksForCache($this->keySet); + } + } + } + + if (!isset($this->keySet[$keyId])) { + if ($this->rateLimitExceeded()) { + return false; + } + $request = $this->httpFactory->createRequest('GET', $this->jwksUri); + $jwksResponse = $this->httpClient->sendRequest($request); + $this->keySet = $this->formatJwksForCache((string) $jwksResponse->getBody()); + + if (!isset($this->keySet[$keyId])) { + return false; + } + + $item = $this->getCacheItem(); + $item->set($this->keySet); + if ($this->expiresAfter) { + $item->expiresAfter($this->expiresAfter); + } + $this->cache->save($item); + } + + return true; + } + + private function rateLimitExceeded(): bool + { + if (!$this->rateLimit) { + return false; + } + + $cacheItem = $this->cache->getItem($this->rateLimitCacheKey); + if (!$cacheItem->isHit()) { + $cacheItem->expiresAfter(1); // # of calls are cached each minute + } + + $callsPerMinute = (int) $cacheItem->get(); + if (++$callsPerMinute > $this->maxCallsPerMinute) { + return true; + } + $cacheItem->set($callsPerMinute); + $this->cache->save($cacheItem); + return false; + } + + private function getCacheItem(): CacheItemInterface + { + if (\is_null($this->cacheItem)) { + $this->cacheItem = $this->cache->getItem($this->cacheKey); + } + + return $this->cacheItem; + } + + private function setCacheKeys(): void + { + if (empty($this->jwksUri)) { + throw new RuntimeException('JWKS URI is empty'); + } + + // ensure we do not have illegal characters + $key = preg_replace('|[^a-zA-Z0-9_\.!]|', '', $this->jwksUri); + + // add prefix + $key = $this->cacheKeyPrefix . $key; + + // Hash keys if they exceed $maxKeyLength of 64 + if (\strlen($key) > $this->maxKeyLength) { + $key = substr(hash('sha256', $key), 0, $this->maxKeyLength); + } + + $this->cacheKey = $key; + + if ($this->rateLimit) { + // add prefix + $rateLimitKey = $this->cacheKeyPrefix . 'ratelimit' . $key; + + // Hash keys if they exceed $maxKeyLength of 64 + if (\strlen($rateLimitKey) > $this->maxKeyLength) { + $rateLimitKey = substr(hash('sha256', $rateLimitKey), 0, $this->maxKeyLength); + } + + $this->rateLimitCacheKey = $rateLimitKey; + } + } +} diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/ExpiredException.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/ExpiredException.php new file mode 100644 index 0000000000..81ba52d43f --- /dev/null +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/ExpiredException.php @@ -0,0 +1,7 @@ + + * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD + * @link https://github.com/firebase/php-jwt + */ +class JWK +{ + private const OID = '1.2.840.10045.2.1'; + private const ASN1_OBJECT_IDENTIFIER = 0x06; + private const ASN1_SEQUENCE = 0x10; // also defined in JWT + private const ASN1_BIT_STRING = 0x03; + private const EC_CURVES = [ + 'P-256' => '1.2.840.10045.3.1.7', // Len: 64 + 'secp256k1' => '1.3.132.0.10', // Len: 64 + // 'P-384' => '1.3.132.0.34', // Len: 96 (not yet supported) + // 'P-521' => '1.3.132.0.35', // Len: 132 (not supported) + ]; + + /** + * Parse a set of JWK keys + * + * @param array $jwks The JSON Web Key Set as an associative array + * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the + * JSON Web Key Set + * + * @return array An associative array of key IDs (kid) to Key objects + * + * @throws InvalidArgumentException Provided JWK Set is empty + * @throws UnexpectedValueException Provided JWK Set was invalid + * @throws DomainException OpenSSL failure + * + * @uses parseKey + */ + public static function parseKeySet(array $jwks, string $defaultAlg = null): array + { + $keys = []; + + if (!isset($jwks['keys'])) { + throw new UnexpectedValueException('"keys" member must exist in the JWK Set'); + } + + if (empty($jwks['keys'])) { + throw new InvalidArgumentException('JWK Set did not contain any keys'); + } + + foreach ($jwks['keys'] as $k => $v) { + $kid = isset($v['kid']) ? $v['kid'] : $k; + if ($key = self::parseKey($v, $defaultAlg)) { + $keys[(string) $kid] = $key; + } + } + + if (0 === \count($keys)) { + throw new UnexpectedValueException('No supported algorithms found in JWK Set'); + } + + return $keys; + } + + /** + * Parse a JWK key + * + * @param array $jwk An individual JWK + * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the + * JSON Web Key Set + * + * @return Key The key object for the JWK + * + * @throws InvalidArgumentException Provided JWK is empty + * @throws UnexpectedValueException Provided JWK was invalid + * @throws DomainException OpenSSL failure + * + * @uses createPemFromModulusAndExponent + */ + public static function parseKey(array $jwk, string $defaultAlg = null): ?Key + { + if (empty($jwk)) { + throw new InvalidArgumentException('JWK must not be empty'); + } + + if (!isset($jwk['kty'])) { + throw new UnexpectedValueException('JWK must contain a "kty" parameter'); + } + + if (!isset($jwk['alg'])) { + if (\is_null($defaultAlg)) { + // The "alg" parameter is optional in a KTY, but an algorithm is required + // for parsing in this library. Use the $defaultAlg parameter when parsing the + // key set in order to prevent this error. + // @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4 + throw new UnexpectedValueException('JWK must contain an "alg" parameter'); + } + $jwk['alg'] = $defaultAlg; + } + + switch ($jwk['kty']) { + case 'RSA': + if (!empty($jwk['d'])) { + throw new UnexpectedValueException('RSA private keys are not supported'); + } + if (!isset($jwk['n']) || !isset($jwk['e'])) { + throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"'); + } + + $pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']); + $publicKey = \openssl_pkey_get_public($pem); + if (false === $publicKey) { + throw new DomainException( + 'OpenSSL error: ' . \openssl_error_string() + ); + } + return new Key($publicKey, $jwk['alg']); + case 'EC': + if (isset($jwk['d'])) { + // The key is actually a private key + throw new UnexpectedValueException('Key data must be for a public key'); + } + + if (empty($jwk['crv'])) { + throw new UnexpectedValueException('crv not set'); + } + + if (!isset(self::EC_CURVES[$jwk['crv']])) { + throw new DomainException('Unrecognised or unsupported EC curve'); + } + + if (empty($jwk['x']) || empty($jwk['y'])) { + throw new UnexpectedValueException('x and y not set'); + } + + $publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']); + return new Key($publicKey, $jwk['alg']); + default: + // Currently only RSA is supported + break; + } + + return null; + } + + /** + * Converts the EC JWK values to pem format. + * + * @param string $crv The EC curve (only P-256 is supported) + * @param string $x The EC x-coordinate + * @param string $y The EC y-coordinate + * + * @return string + */ + private static function createPemFromCrvAndXYCoordinates(string $crv, string $x, string $y): string + { + $pem = + self::encodeDER( + self::ASN1_SEQUENCE, + self::encodeDER( + self::ASN1_SEQUENCE, + self::encodeDER( + self::ASN1_OBJECT_IDENTIFIER, + self::encodeOID(self::OID) + ) + . self::encodeDER( + self::ASN1_OBJECT_IDENTIFIER, + self::encodeOID(self::EC_CURVES[$crv]) + ) + ) . + self::encodeDER( + self::ASN1_BIT_STRING, + \chr(0x00) . \chr(0x04) + . JWT::urlsafeB64Decode($x) + . JWT::urlsafeB64Decode($y) + ) + ); + + return sprintf( + "-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n", + wordwrap(base64_encode($pem), 64, "\n", true) + ); + } + + /** + * Create a public key represented in PEM format from RSA modulus and exponent information + * + * @param string $n The RSA modulus encoded in Base64 + * @param string $e The RSA exponent encoded in Base64 + * + * @return string The RSA public key represented in PEM format + * + * @uses encodeLength + */ + private static function createPemFromModulusAndExponent( + string $n, + string $e + ): string { + $mod = JWT::urlsafeB64Decode($n); + $exp = JWT::urlsafeB64Decode($e); + + $modulus = \pack('Ca*a*', 2, self::encodeLength(\strlen($mod)), $mod); + $publicExponent = \pack('Ca*a*', 2, self::encodeLength(\strlen($exp)), $exp); + + $rsaPublicKey = \pack( + 'Ca*a*a*', + 48, + self::encodeLength(\strlen($modulus) + \strlen($publicExponent)), + $modulus, + $publicExponent + ); + + // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption. + $rsaOID = \pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA + $rsaPublicKey = \chr(0) . $rsaPublicKey; + $rsaPublicKey = \chr(3) . self::encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey; + + $rsaPublicKey = \pack( + 'Ca*a*', + 48, + self::encodeLength(\strlen($rsaOID . $rsaPublicKey)), + $rsaOID . $rsaPublicKey + ); + + return "-----BEGIN PUBLIC KEY-----\r\n" . + \chunk_split(\base64_encode($rsaPublicKey), 64) . + '-----END PUBLIC KEY-----'; + } + + /** + * DER-encode the length + * + * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information. + * + * @param int $length + * @return string + */ + private static function encodeLength(int $length): string + { + if ($length <= 0x7F) { + return \chr($length); + } + + $temp = \ltrim(\pack('N', $length), \chr(0)); + + return \pack('Ca*', 0x80 | \strlen($temp), $temp); + } + + /** + * Encodes a value into a DER object. + * Also defined in Firebase\JWT\JWT + * + * @param int $type DER tag + * @param string $value the value to encode + * @return string the encoded object + */ + private static function encodeDER(int $type, string $value): string + { + $tag_header = 0; + if ($type === self::ASN1_SEQUENCE) { + $tag_header |= 0x20; + } + + // Type + $der = \chr($tag_header | $type); + + // Length + $der .= \chr(\strlen($value)); + + return $der . $value; + } + + /** + * Encodes a string into a DER-encoded OID. + * + * @param string $oid the OID string + * @return string the binary DER-encoded OID + */ + private static function encodeOID(string $oid): string + { + $octets = explode('.', $oid); + + // Get the first octet + $first = (int) array_shift($octets); + $second = (int) array_shift($octets); + $oid = \chr($first * 40 + $second); + + // Iterate over subsequent octets + foreach ($octets as $octet) { + if ($octet == 0) { + $oid .= \chr(0x00); + continue; + } + $bin = ''; + + while ($octet) { + $bin .= \chr(0x80 | ($octet & 0x7f)); + $octet >>= 7; + } + $bin[0] = $bin[0] & \chr(0x7f); + + // Convert to big endian if necessary + if (pack('V', 65534) == pack('L', 65534)) { + $oid .= strrev($bin); + } else { + $oid .= $bin; + } + } + + return $oid; + } +} diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/JWT.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/JWT.php new file mode 100644 index 0000000000..c83ff09905 --- /dev/null +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/JWT.php @@ -0,0 +1,642 @@ + + * @author Anant Narayanan + * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD + * @link https://github.com/firebase/php-jwt + */ +class JWT +{ + private const ASN1_INTEGER = 0x02; + private const ASN1_SEQUENCE = 0x10; + private const ASN1_BIT_STRING = 0x03; + + /** + * When checking nbf, iat or expiration times, + * we want to provide some extra leeway time to + * account for clock skew. + * + * @var int + */ + public static $leeway = 0; + + /** + * Allow the current timestamp to be specified. + * Useful for fixing a value within unit testing. + * Will default to PHP time() value if null. + * + * @var ?int + */ + public static $timestamp = null; + + /** + * @var array + */ + public static $supported_algs = [ + 'ES384' => ['openssl', 'SHA384'], + 'ES256' => ['openssl', 'SHA256'], + 'ES256K' => ['openssl', 'SHA256'], + 'HS256' => ['hash_hmac', 'SHA256'], + 'HS384' => ['hash_hmac', 'SHA384'], + 'HS512' => ['hash_hmac', 'SHA512'], + 'RS256' => ['openssl', 'SHA256'], + 'RS384' => ['openssl', 'SHA384'], + 'RS512' => ['openssl', 'SHA512'], + 'EdDSA' => ['sodium_crypto', 'EdDSA'], + ]; + + /** + * Decodes a JWT string into a PHP object. + * + * @param string $jwt The JWT + * @param Key|ArrayAccess|array $keyOrKeyArray The Key or associative array of key IDs + * (kid) to Key objects. + * If the algorithm used is asymmetric, this is + * the public key. + * Each Key object contains an algorithm and + * matching key. + * Supported algorithms are 'ES384','ES256', + * 'HS256', 'HS384', 'HS512', 'RS256', 'RS384' + * and 'RS512'. + * + * @return stdClass The JWT's payload as a PHP object + * + * @throws InvalidArgumentException Provided key/key-array was empty or malformed + * @throws DomainException Provided JWT is malformed + * @throws UnexpectedValueException Provided JWT was invalid + * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed + * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf' + * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat' + * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim + * + * @uses jsonDecode + * @uses urlsafeB64Decode + */ + public static function decode( + string $jwt, + $keyOrKeyArray + ): stdClass { + // Validate JWT + $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp; + + if (empty($keyOrKeyArray)) { + throw new InvalidArgumentException('Key may not be empty'); + } + $tks = \explode('.', $jwt); + if (\count($tks) !== 3) { + throw new UnexpectedValueException('Wrong number of segments'); + } + list($headb64, $bodyb64, $cryptob64) = $tks; + $headerRaw = static::urlsafeB64Decode($headb64); + if (null === ($header = static::jsonDecode($headerRaw))) { + throw new UnexpectedValueException('Invalid header encoding'); + } + $payloadRaw = static::urlsafeB64Decode($bodyb64); + if (null === ($payload = static::jsonDecode($payloadRaw))) { + throw new UnexpectedValueException('Invalid claims encoding'); + } + if (\is_array($payload)) { + // prevent PHP Fatal Error in edge-cases when payload is empty array + $payload = (object) $payload; + } + if (!$payload instanceof stdClass) { + throw new UnexpectedValueException('Payload must be a JSON object'); + } + $sig = static::urlsafeB64Decode($cryptob64); + if (empty($header->alg)) { + throw new UnexpectedValueException('Empty algorithm'); + } + if (empty(static::$supported_algs[$header->alg])) { + throw new UnexpectedValueException('Algorithm not supported'); + } + + $key = self::getKey($keyOrKeyArray, property_exists($header, 'kid') ? $header->kid : null); + + // Check the algorithm + if (!self::constantTimeEquals($key->getAlgorithm(), $header->alg)) { + // See issue #351 + throw new UnexpectedValueException('Incorrect key for this algorithm'); + } + if (\in_array($header->alg, ['ES256', 'ES256K', 'ES384'], true)) { + // OpenSSL expects an ASN.1 DER sequence for ES256/ES256K/ES384 signatures + $sig = self::signatureToDER($sig); + } + if (!self::verify("{$headb64}.{$bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) { + throw new SignatureInvalidException('Signature verification failed'); + } + + // Check the nbf if it is defined. This is the time that the + // token can actually be used. If it's not yet that time, abort. + if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) { + throw new BeforeValidException( + 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf) + ); + } + + // Check that this token has been created before 'now'. This prevents + // using tokens that have been created for later use (and haven't + // correctly used the nbf claim). + if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) { + throw new BeforeValidException( + 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat) + ); + } + + // Check if this token has expired. + if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { + throw new ExpiredException('Expired token'); + } + + return $payload; + } + + /** + * Converts and signs a PHP array into a JWT string. + * + * @param array $payload PHP array + * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. + * @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256', + * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512' + * @param string $keyId + * @param array $head An array with header elements to attach + * + * @return string A signed JWT + * + * @uses jsonEncode + * @uses urlsafeB64Encode + */ + public static function encode( + array $payload, + $key, + string $alg, + string $keyId = null, + array $head = null + ): string { + $header = ['typ' => 'JWT', 'alg' => $alg]; + if ($keyId !== null) { + $header['kid'] = $keyId; + } + if (isset($head) && \is_array($head)) { + $header = \array_merge($head, $header); + } + $segments = []; + $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($header)); + $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($payload)); + $signing_input = \implode('.', $segments); + + $signature = static::sign($signing_input, $key, $alg); + $segments[] = static::urlsafeB64Encode($signature); + + return \implode('.', $segments); + } + + /** + * Sign a string with a given key and algorithm. + * + * @param string $msg The message to sign + * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. + * @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256', + * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512' + * + * @return string An encrypted message + * + * @throws DomainException Unsupported algorithm or bad key was specified + */ + public static function sign( + string $msg, + $key, + string $alg + ): string { + if (empty(static::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + list($function, $algorithm) = static::$supported_algs[$alg]; + switch ($function) { + case 'hash_hmac': + if (!\is_string($key)) { + throw new InvalidArgumentException('key must be a string when using hmac'); + } + return \hash_hmac($algorithm, $msg, $key, true); + case 'openssl': + $signature = ''; + $success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line + if (!$success) { + throw new DomainException('OpenSSL unable to sign data'); + } + if ($alg === 'ES256' || $alg === 'ES256K') { + $signature = self::signatureFromDER($signature, 256); + } elseif ($alg === 'ES384') { + $signature = self::signatureFromDER($signature, 384); + } + return $signature; + case 'sodium_crypto': + if (!\function_exists('sodium_crypto_sign_detached')) { + throw new DomainException('libsodium is not available'); + } + if (!\is_string($key)) { + throw new InvalidArgumentException('key must be a string when using EdDSA'); + } + try { + // The last non-empty line is used as the key. + $lines = array_filter(explode("\n", $key)); + $key = base64_decode((string) end($lines)); + if (\strlen($key) === 0) { + throw new DomainException('Key cannot be empty string'); + } + return sodium_crypto_sign_detached($msg, $key); + } catch (Exception $e) { + throw new DomainException($e->getMessage(), 0, $e); + } + } + + throw new DomainException('Algorithm not supported'); + } + + /** + * Verify a signature with the message, key and method. Not all methods + * are symmetric, so we must have a separate verify and sign method. + * + * @param string $msg The original message (header and body) + * @param string $signature The original signature + * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey + * @param string $alg The algorithm + * + * @return bool + * + * @throws DomainException Invalid Algorithm, bad key, or OpenSSL failure + */ + private static function verify( + string $msg, + string $signature, + $keyMaterial, + string $alg + ): bool { + if (empty(static::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + + list($function, $algorithm) = static::$supported_algs[$alg]; + switch ($function) { + case 'openssl': + $success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); // @phpstan-ignore-line + if ($success === 1) { + return true; + } + if ($success === 0) { + return false; + } + // returns 1 on success, 0 on failure, -1 on error. + throw new DomainException( + 'OpenSSL error: ' . \openssl_error_string() + ); + case 'sodium_crypto': + if (!\function_exists('sodium_crypto_sign_verify_detached')) { + throw new DomainException('libsodium is not available'); + } + if (!\is_string($keyMaterial)) { + throw new InvalidArgumentException('key must be a string when using EdDSA'); + } + try { + // The last non-empty line is used as the key. + $lines = array_filter(explode("\n", $keyMaterial)); + $key = base64_decode((string) end($lines)); + if (\strlen($key) === 0) { + throw new DomainException('Key cannot be empty string'); + } + if (\strlen($signature) === 0) { + throw new DomainException('Signature cannot be empty string'); + } + return sodium_crypto_sign_verify_detached($signature, $msg, $key); + } catch (Exception $e) { + throw new DomainException($e->getMessage(), 0, $e); + } + case 'hash_hmac': + default: + if (!\is_string($keyMaterial)) { + throw new InvalidArgumentException('key must be a string when using hmac'); + } + $hash = \hash_hmac($algorithm, $msg, $keyMaterial, true); + return self::constantTimeEquals($hash, $signature); + } + } + + /** + * Decode a JSON string into a PHP object. + * + * @param string $input JSON string + * + * @return mixed The decoded JSON string + * + * @throws DomainException Provided string was invalid JSON + */ + public static function jsonDecode(string $input) + { + $obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING); + + if ($errno = \json_last_error()) { + self::handleJsonError($errno); + } elseif ($obj === null && $input !== 'null') { + throw new DomainException('Null result with non-null input'); + } + return $obj; + } + + /** + * Encode a PHP array into a JSON string. + * + * @param array $input A PHP array + * + * @return string JSON representation of the PHP array + * + * @throws DomainException Provided object could not be encoded to valid JSON + */ + public static function jsonEncode(array $input): string + { + if (PHP_VERSION_ID >= 50400) { + $json = \json_encode($input, \JSON_UNESCAPED_SLASHES); + } else { + // PHP 5.3 only + $json = \json_encode($input); + } + if ($errno = \json_last_error()) { + self::handleJsonError($errno); + } elseif ($json === 'null') { + throw new DomainException('Null result with non-null input'); + } + if ($json === false) { + throw new DomainException('Provided object could not be encoded to valid JSON'); + } + return $json; + } + + /** + * Decode a string with URL-safe Base64. + * + * @param string $input A Base64 encoded string + * + * @return string A decoded string + * + * @throws InvalidArgumentException invalid base64 characters + */ + public static function urlsafeB64Decode(string $input): string + { + $remainder = \strlen($input) % 4; + if ($remainder) { + $padlen = 4 - $remainder; + $input .= \str_repeat('=', $padlen); + } + return \base64_decode(\strtr($input, '-_', '+/')); + } + + /** + * Encode a string with URL-safe Base64. + * + * @param string $input The string you want encoded + * + * @return string The base64 encode of what you passed in + */ + public static function urlsafeB64Encode(string $input): string + { + return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_')); + } + + + /** + * Determine if an algorithm has been provided for each Key + * + * @param Key|ArrayAccess|array $keyOrKeyArray + * @param string|null $kid + * + * @throws UnexpectedValueException + * + * @return Key + */ + private static function getKey( + $keyOrKeyArray, + ?string $kid + ): Key { + if ($keyOrKeyArray instanceof Key) { + return $keyOrKeyArray; + } + + if (empty($kid) && $kid !== '0') { + throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); + } + + if ($keyOrKeyArray instanceof CachedKeySet) { + // Skip "isset" check, as this will automatically refresh if not set + return $keyOrKeyArray[$kid]; + } + + if (!isset($keyOrKeyArray[$kid])) { + throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); + } + + return $keyOrKeyArray[$kid]; + } + + /** + * @param string $left The string of known length to compare against + * @param string $right The user-supplied string + * @return bool + */ + public static function constantTimeEquals(string $left, string $right): bool + { + if (\function_exists('hash_equals')) { + return \hash_equals($left, $right); + } + $len = \min(self::safeStrlen($left), self::safeStrlen($right)); + + $status = 0; + for ($i = 0; $i < $len; $i++) { + $status |= (\ord($left[$i]) ^ \ord($right[$i])); + } + $status |= (self::safeStrlen($left) ^ self::safeStrlen($right)); + + return ($status === 0); + } + + /** + * Helper method to create a JSON error. + * + * @param int $errno An error number from json_last_error() + * + * @throws DomainException + * + * @return void + */ + private static function handleJsonError(int $errno): void + { + $messages = [ + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', + JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3 + ]; + throw new DomainException( + isset($messages[$errno]) + ? $messages[$errno] + : 'Unknown JSON error: ' . $errno + ); + } + + /** + * Get the number of bytes in cryptographic strings. + * + * @param string $str + * + * @return int + */ + private static function safeStrlen(string $str): int + { + if (\function_exists('mb_strlen')) { + return \mb_strlen($str, '8bit'); + } + return \strlen($str); + } + + /** + * Convert an ECDSA signature to an ASN.1 DER sequence + * + * @param string $sig The ECDSA signature to convert + * @return string The encoded DER object + */ + private static function signatureToDER(string $sig): string + { + // Separate the signature into r-value and s-value + $length = max(1, (int) (\strlen($sig) / 2)); + list($r, $s) = \str_split($sig, $length); + + // Trim leading zeros + $r = \ltrim($r, "\x00"); + $s = \ltrim($s, "\x00"); + + // Convert r-value and s-value from unsigned big-endian integers to + // signed two's complement + if (\ord($r[0]) > 0x7f) { + $r = "\x00" . $r; + } + if (\ord($s[0]) > 0x7f) { + $s = "\x00" . $s; + } + + return self::encodeDER( + self::ASN1_SEQUENCE, + self::encodeDER(self::ASN1_INTEGER, $r) . + self::encodeDER(self::ASN1_INTEGER, $s) + ); + } + + /** + * Encodes a value into a DER object. + * + * @param int $type DER tag + * @param string $value the value to encode + * + * @return string the encoded object + */ + private static function encodeDER(int $type, string $value): string + { + $tag_header = 0; + if ($type === self::ASN1_SEQUENCE) { + $tag_header |= 0x20; + } + + // Type + $der = \chr($tag_header | $type); + + // Length + $der .= \chr(\strlen($value)); + + return $der . $value; + } + + /** + * Encodes signature from a DER object. + * + * @param string $der binary signature in DER format + * @param int $keySize the number of bits in the key + * + * @return string the signature + */ + private static function signatureFromDER(string $der, int $keySize): string + { + // OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE + list($offset, $_) = self::readDER($der); + list($offset, $r) = self::readDER($der, $offset); + list($offset, $s) = self::readDER($der, $offset); + + // Convert r-value and s-value from signed two's compliment to unsigned + // big-endian integers + $r = \ltrim($r, "\x00"); + $s = \ltrim($s, "\x00"); + + // Pad out r and s so that they are $keySize bits long + $r = \str_pad($r, $keySize / 8, "\x00", STR_PAD_LEFT); + $s = \str_pad($s, $keySize / 8, "\x00", STR_PAD_LEFT); + + return $r . $s; + } + + /** + * Reads binary DER-encoded data and decodes into a single object + * + * @param string $der the binary data in DER format + * @param int $offset the offset of the data stream containing the object + * to decode + * + * @return array{int, string|null} the new offset and the decoded object + */ + private static function readDER(string $der, int $offset = 0): array + { + $pos = $offset; + $size = \strlen($der); + $constructed = (\ord($der[$pos]) >> 5) & 0x01; + $type = \ord($der[$pos++]) & 0x1f; + + // Length + $len = \ord($der[$pos++]); + if ($len & 0x80) { + $n = $len & 0x1f; + $len = 0; + while ($n-- && $pos < $size) { + $len = ($len << 8) | \ord($der[$pos++]); + } + } + + // Value + if ($type === self::ASN1_BIT_STRING) { + $pos++; // Skip the first contents octet (padding indicator) + $data = \substr($der, $pos, $len - 1); + $pos += $len - 1; + } elseif (!$constructed) { + $data = \substr($der, $pos, $len); + $pos += $len; + } else { + $data = null; + } + + return [$pos, $data]; + } +} diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/Key.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/Key.php new file mode 100644 index 0000000000..00cf7f2edf --- /dev/null +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/Key.php @@ -0,0 +1,64 @@ +keyMaterial = $keyMaterial; + $this->algorithm = $algorithm; + } + + /** + * Return the algorithm valid for this key + * + * @return string + */ + public function getAlgorithm(): string + { + return $this->algorithm; + } + + /** + * @return string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate + */ + public function getKeyMaterial() + { + return $this->keyMaterial; + } +} diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/SignatureInvalidException.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/SignatureInvalidException.php new file mode 100644 index 0000000000..d35dee9f18 --- /dev/null +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/SignatureInvalidException.php @@ -0,0 +1,7 @@ += 5.5 +* Updated to use PSR-7 + * Requires immutable messages, which basically means an event based system + owned by a request instance is no longer possible. + * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7). + * Removed the dependency on `guzzlehttp/streams`. These stream abstractions + are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7` + namespace. +* Added middleware and handler system + * Replaced the Guzzle event and subscriber system with a middleware system. + * No longer depends on RingPHP, but rather places the HTTP handlers directly + in Guzzle, operating on PSR-7 messages. + * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which + means the `guzzlehttp/retry-subscriber` is now obsolete. + * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`. +* Asynchronous responses + * No longer supports the `future` request option to send an async request. + Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`, + `getAsync`, etc.). + * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid + recursion required by chaining and forwarding react promises. See + https://github.com/guzzle/promises + * Added `requestAsync` and `sendAsync` to send request asynchronously. + * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests + asynchronously. +* Request options + * POST and form updates + * Added the `form_fields` and `form_files` request options. + * Removed the `GuzzleHttp\Post` namespace. + * The `body` request option no longer accepts an array for POST requests. + * The `exceptions` request option has been deprecated in favor of the + `http_errors` request options. + * The `save_to` request option has been deprecated in favor of `sink` request + option. +* Clients no longer accept an array of URI template string and variables for + URI variables. You will need to expand URI templates before passing them + into a client constructor or request method. +* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are + now magic methods that will send synchronous requests. +* Replaced `Utils.php` with plain functions in `functions.php`. +* Removed `GuzzleHttp\Collection`. +* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as + an array. +* Removed `GuzzleHttp\Query`. Query string handling is now handled using an + associative array passed into the `query` request option. The query string + is serialized using PHP's `http_build_query`. If you need more control, you + can pass the query string in as a string. +* `GuzzleHttp\QueryParser` has been replaced with the + `GuzzleHttp\Psr7\parse_query`. + +## 5.2.0 - 2015-01-27 + +* Added `AppliesHeadersInterface` to make applying headers to a request based + on the body more generic and not specific to `PostBodyInterface`. +* Reduced the number of stack frames needed to send requests. +* Nested futures are now resolved in the client rather than the RequestFsm +* Finishing state transitions is now handled in the RequestFsm rather than the + RingBridge. +* Added a guard in the Pool class to not use recursion for request retries. + +## 5.1.0 - 2014-12-19 + +* Pool class no longer uses recursion when a request is intercepted. +* The size of a Pool can now be dynamically adjusted using a callback. + See https://github.com/guzzle/guzzle/pull/943. +* Setting a request option to `null` when creating a request with a client will + ensure that the option is not set. This allows you to overwrite default + request options on a per-request basis. + See https://github.com/guzzle/guzzle/pull/937. +* Added the ability to limit which protocols are allowed for redirects by + specifying a `protocols` array in the `allow_redirects` request option. +* Nested futures due to retries are now resolved when waiting for synchronous + responses. See https://github.com/guzzle/guzzle/pull/947. +* `"0"` is now an allowed URI path. See + https://github.com/guzzle/guzzle/pull/935. +* `Query` no longer typehints on the `$query` argument in the constructor, + allowing for strings and arrays. +* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle + specific exceptions if necessary. + +## 5.0.3 - 2014-11-03 + +This change updates query strings so that they are treated as un-encoded values +by default where the value represents an un-encoded value to send over the +wire. A Query object then encodes the value before sending over the wire. This +means that even value query string values (e.g., ":") are url encoded. This +makes the Query class match PHP's http_build_query function. However, if you +want to send requests over the wire using valid query string characters that do +not need to be encoded, then you can provide a string to Url::setQuery() and +pass true as the second argument to specify that the query string is a raw +string that should not be parsed or encoded (unless a call to getQuery() is +subsequently made, forcing the query-string to be converted into a Query +object). + +## 5.0.2 - 2014-10-30 + +* Added a trailing `\r\n` to multipart/form-data payloads. See + https://github.com/guzzle/guzzle/pull/871 +* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs. +* Status codes are now returned as integers. See + https://github.com/guzzle/guzzle/issues/881 +* No longer overwriting an existing `application/x-www-form-urlencoded` header + when sending POST requests, allowing for customized headers. See + https://github.com/guzzle/guzzle/issues/877 +* Improved path URL serialization. + + * No longer double percent-encoding characters in the path or query string if + they are already encoded. + * Now properly encoding the supplied path to a URL object, instead of only + encoding ' ' and '?'. + * Note: This has been changed in 5.0.3 to now encode query string values by + default unless the `rawString` argument is provided when setting the query + string on a URL: Now allowing many more characters to be present in the + query string without being percent encoded. See https://tools.ietf.org/html/rfc3986#appendix-A + +## 5.0.1 - 2014-10-16 + +Bugfix release. + +* Fixed an issue where connection errors still returned response object in + error and end events event though the response is unusable. This has been + corrected so that a response is not returned in the `getResponse` method of + these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867 +* Fixed an issue where transfer statistics were not being populated in the + RingBridge. https://github.com/guzzle/guzzle/issues/866 + +## 5.0.0 - 2014-10-12 + +Adding support for non-blocking responses and some minor API cleanup. + +### New Features + +* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`. +* Added a public API for creating a default HTTP adapter. +* Updated the redirect plugin to be non-blocking so that redirects are sent + concurrently. Other plugins like this can now be updated to be non-blocking. +* Added a "progress" event so that you can get upload and download progress + events. +* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers + requests concurrently using a capped pool size as efficiently as possible. +* Added `hasListeners()` to EmitterInterface. +* Removed `GuzzleHttp\ClientInterface::sendAll` and marked + `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the + recommended way). + +### Breaking changes + +The breaking changes in this release are relatively minor. The biggest thing to +look out for is that request and response objects no longer implement fluent +interfaces. + +* Removed the fluent interfaces (i.e., `return $this`) from requests, + responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`, + `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and + `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of + why I did this: https://ocramius.github.io/blog/fluent-interfaces-are-evil/. + This also makes the Guzzle message interfaces compatible with the current + PSR-7 message proposal. +* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except + for the HTTP request functions from function.php, these functions are now + implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode` + moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to + `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to + `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be + `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php + caused problems for many users: they aren't PSR-4 compliant, require an + explicit include, and needed an if-guard to ensure that the functions are not + declared multiple times. +* Rewrote adapter layer. + * Removing all classes from `GuzzleHttp\Adapter`, these are now + implemented as callables that are stored in `GuzzleHttp\Ring\Client`. + * Removed the concept of "parallel adapters". Sending requests serially or + concurrently is now handled using a single adapter. + * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The + Transaction object now exposes the request, response, and client as public + properties. The getters and setters have been removed. +* Removed the "headers" event. This event was only useful for changing the + body a response once the headers of the response were known. You can implement + a similar behavior in a number of ways. One example might be to use a + FnStream that has access to the transaction being sent. For example, when the + first byte is written, you could check if the response headers match your + expectations, and if so, change the actual stream body that is being + written to. +* Removed the `asArray` parameter from + `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header + value as an array, then use the newly added `getHeaderAsArray()` method of + `MessageInterface`. This change makes the Guzzle interfaces compatible with + the PSR-7 interfaces. +* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add + custom request options using double-dispatch (this was an implementation + detail). Instead, you should now provide an associative array to the + constructor which is a mapping of the request option name mapping to a + function that applies the option value to a request. +* Removed the concept of "throwImmediately" from exceptions and error events. + This control mechanism was used to stop a transfer of concurrent requests + from completing. This can now be handled by throwing the exception or by + cancelling a pool of requests or each outstanding future request individually. +* Updated to "GuzzleHttp\Streams" 3.0. + * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a + `maxLen` parameter. This update makes the Guzzle streams project + compatible with the current PSR-7 proposal. + * `GuzzleHttp\Stream\Stream::__construct`, + `GuzzleHttp\Stream\Stream::factory`, and + `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second + argument. They now accept an associative array of options, including the + "size" key and "metadata" key which can be used to provide custom metadata. + +## 4.2.2 - 2014-09-08 + +* Fixed a memory leak in the CurlAdapter when reusing cURL handles. +* No longer using `request_fulluri` in stream adapter proxies. +* Relative redirects are now based on the last response, not the first response. + +## 4.2.1 - 2014-08-19 + +* Ensuring that the StreamAdapter does not always add a Content-Type header +* Adding automated github releases with a phar and zip + +## 4.2.0 - 2014-08-17 + +* Now merging in default options using a case-insensitive comparison. + Closes https://github.com/guzzle/guzzle/issues/767 +* Added the ability to automatically decode `Content-Encoding` response bodies + using the `decode_content` request option. This is set to `true` by default + to decode the response body if it comes over the wire with a + `Content-Encoding`. Set this value to `false` to disable decoding the + response content, and pass a string to provide a request `Accept-Encoding` + header and turn on automatic response decoding. This feature now allows you + to pass an `Accept-Encoding` header in the headers of a request but still + disable automatic response decoding. + Closes https://github.com/guzzle/guzzle/issues/764 +* Added the ability to throw an exception immediately when transferring + requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760 +* Updating guzzlehttp/streams dependency to ~2.1 +* No longer utilizing the now deprecated namespaced methods from the stream + package. + +## 4.1.8 - 2014-08-14 + +* Fixed an issue in the CurlFactory that caused setting the `stream=false` + request option to throw an exception. + See: https://github.com/guzzle/guzzle/issues/769 +* TransactionIterator now calls rewind on the inner iterator. + See: https://github.com/guzzle/guzzle/pull/765 +* You can now set the `Content-Type` header to `multipart/form-data` + when creating POST requests to force multipart bodies. + See https://github.com/guzzle/guzzle/issues/768 + +## 4.1.7 - 2014-08-07 + +* Fixed an error in the HistoryPlugin that caused the same request and response + to be logged multiple times when an HTTP protocol error occurs. +* Ensuring that cURL does not add a default Content-Type when no Content-Type + has been supplied by the user. This prevents the adapter layer from modifying + the request that is sent over the wire after any listeners may have already + put the request in a desired state (e.g., signed the request). +* Throwing an exception when you attempt to send requests that have the + "stream" set to true in parallel using the MultiAdapter. +* Only calling curl_multi_select when there are active cURL handles. This was + previously changed and caused performance problems on some systems due to PHP + always selecting until the maximum select timeout. +* Fixed a bug where multipart/form-data POST fields were not correctly + aggregated (e.g., values with "&"). + +## 4.1.6 - 2014-08-03 + +* Added helper methods to make it easier to represent messages as strings, + including getting the start line and getting headers as a string. + +## 4.1.5 - 2014-08-02 + +* Automatically retrying cURL "Connection died, retrying a fresh connect" + errors when possible. +* cURL implementation cleanup +* Allowing multiple event subscriber listeners to be registered per event by + passing an array of arrays of listener configuration. + +## 4.1.4 - 2014-07-22 + +* Fixed a bug that caused multi-part POST requests with more than one field to + serialize incorrectly. +* Paths can now be set to "0" +* `ResponseInterface::xml` now accepts a `libxml_options` option and added a + missing default argument that was required when parsing XML response bodies. +* A `save_to` stream is now created lazily, which means that files are not + created on disk unless a request succeeds. + +## 4.1.3 - 2014-07-15 + +* Various fixes to multipart/form-data POST uploads +* Wrapping function.php in an if-statement to ensure Guzzle can be used + globally and in a Composer install +* Fixed an issue with generating and merging in events to an event array +* POST headers are only applied before sending a request to allow you to change + the query aggregator used before uploading +* Added much more robust query string parsing +* Fixed various parsing and normalization issues with URLs +* Fixing an issue where multi-valued headers were not being utilized correctly + in the StreamAdapter + +## 4.1.2 - 2014-06-18 + +* Added support for sending payloads with GET requests + +## 4.1.1 - 2014-06-08 + +* Fixed an issue related to using custom message factory options in subclasses +* Fixed an issue with nested form fields in a multi-part POST +* Fixed an issue with using the `json` request option for POST requests +* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar` + +## 4.1.0 - 2014-05-27 + +* Added a `json` request option to easily serialize JSON payloads. +* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON. +* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`. +* Added the ability to provide an emitter to a client in the client constructor. +* Added the ability to persist a cookie session using $_SESSION. +* Added a trait that can be used to add event listeners to an iterator. +* Removed request method constants from RequestInterface. +* Fixed warning when invalid request start-lines are received. +* Updated MessageFactory to work with custom request option methods. +* Updated cacert bundle to latest build. + +4.0.2 (2014-04-16) +------------------ + +* Proxy requests using the StreamAdapter now properly use request_fulluri (#632) +* Added the ability to set scalars as POST fields (#628) + +## 4.0.1 - 2014-04-04 + +* The HTTP status code of a response is now set as the exception code of + RequestException objects. +* 303 redirects will now correctly switch from POST to GET requests. +* The default parallel adapter of a client now correctly uses the MultiAdapter. +* HasDataTrait now initializes the internal data array as an empty array so + that the toArray() method always returns an array. + +## 4.0.0 - 2014-03-29 + +* For information on changes and upgrading, see: + https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 +* Added `GuzzleHttp\batch()` as a convenience function for sending requests in + parallel without needing to write asynchronous code. +* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`. + You can now pass a callable or an array of associative arrays where each + associative array contains the "fn", "priority", and "once" keys. + +## 4.0.0.rc-2 - 2014-03-25 + +* Removed `getConfig()` and `setConfig()` from clients to avoid confusion + around whether things like base_url, message_factory, etc. should be able to + be retrieved or modified. +* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface +* functions.php functions were renamed using snake_case to match PHP idioms +* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and + `GUZZLE_CURL_SELECT_TIMEOUT` environment variables +* Added the ability to specify custom `sendAll()` event priorities +* Added the ability to specify custom stream context options to the stream + adapter. +* Added a functions.php function for `get_path()` and `set_path()` +* CurlAdapter and MultiAdapter now use a callable to generate curl resources +* MockAdapter now properly reads a body and emits a `headers` event +* Updated Url class to check if a scheme and host are set before adding ":" + and "//". This allows empty Url (e.g., "") to be serialized as "". +* Parsing invalid XML no longer emits warnings +* Curl classes now properly throw AdapterExceptions +* Various performance optimizations +* Streams are created with the faster `Stream\create()` function +* Marked deprecation_proxy() as internal +* Test server is now a collection of static methods on a class + +## 4.0.0-rc.1 - 2014-03-15 + +* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 + +## 3.8.1 - 2014-01-28 + +* Bug: Always using GET requests when redirecting from a 303 response +* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in + `Guzzle\Http\ClientInterface::setSslVerification()` +* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL +* Bug: The body of a request can now be set to `"0"` +* Sending PHP stream requests no longer forces `HTTP/1.0` +* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of + each sub-exception +* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than + clobbering everything). +* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators) +* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`. + For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`. +* Now properly escaping the regular expression delimiter when matching Cookie domains. +* Network access is now disabled when loading XML documents + +## 3.8.0 - 2013-12-05 + +* Added the ability to define a POST name for a file +* JSON response parsing now properly walks additionalProperties +* cURL error code 18 is now retried automatically in the BackoffPlugin +* Fixed a cURL error when URLs contain fragments +* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were + CurlExceptions +* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e) +* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS` +* Fixed a bug that was encountered when parsing empty header parameters +* UriTemplate now has a `setRegex()` method to match the docs +* The `debug` request parameter now checks if it is truthy rather than if it exists +* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin +* Added the ability to combine URLs using strict RFC 3986 compliance +* Command objects can now return the validation errors encountered by the command +* Various fixes to cache revalidation (#437 and 29797e5) +* Various fixes to the AsyncPlugin +* Cleaned up build scripts + +## 3.7.4 - 2013-10-02 + +* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430) +* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp + (see https://github.com/aws/aws-sdk-php/issues/147) +* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots +* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420) +* Updated the bundled cacert.pem (#419) +* OauthPlugin now supports adding authentication to headers or query string (#425) + +## 3.7.3 - 2013-09-08 + +* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and + `CommandTransferException`. +* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description +* Schemas are only injected into response models when explicitly configured. +* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of + an EntityBody. +* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator. +* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`. +* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody() +* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin +* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests +* Bug fix: Properly parsing headers that contain commas contained in quotes +* Bug fix: mimetype guessing based on a filename is now case-insensitive + +## 3.7.2 - 2013-08-02 + +* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander + See https://github.com/guzzle/guzzle/issues/371 +* Bug fix: Cookie domains are now matched correctly according to RFC 6265 + See https://github.com/guzzle/guzzle/issues/377 +* Bug fix: GET parameters are now used when calculating an OAuth signature +* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted +* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched +* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input. + See https://github.com/guzzle/guzzle/issues/379 +* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See + https://github.com/guzzle/guzzle/pull/380 +* cURL multi cleanup and optimizations + +## 3.7.1 - 2013-07-05 + +* Bug fix: Setting default options on a client now works +* Bug fix: Setting options on HEAD requests now works. See #352 +* Bug fix: Moving stream factory before send event to before building the stream. See #353 +* Bug fix: Cookies no longer match on IP addresses per RFC 6265 +* Bug fix: Correctly parsing header parameters that are in `<>` and quotes +* Added `cert` and `ssl_key` as request options +* `Host` header can now diverge from the host part of a URL if the header is set manually +* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter +* OAuth parameters are only added via the plugin if they aren't already set +* Exceptions are now thrown when a URL cannot be parsed +* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails +* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin + +## 3.7.0 - 2013-06-10 + +* See UPGRADING.md for more information on how to upgrade. +* Requests now support the ability to specify an array of $options when creating a request to more easily modify a + request. You can pass a 'request.options' configuration setting to a client to apply default request options to + every request created by a client (e.g. default query string variables, headers, curl options, etc.). +* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`. + See `Guzzle\Http\StaticClient::mount`. +* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests + created by a command (e.g. custom headers, query string variables, timeout settings, etc.). +* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the + headers of a response +* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key + (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`) +* ServiceBuilders now support storing and retrieving arbitrary data +* CachePlugin can now purge all resources for a given URI +* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource +* CachePlugin now uses the Vary header to determine if a resource is a cache hit +* `Guzzle\Http\Message\Response` now implements `\Serializable` +* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters +* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable +* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()` +* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size +* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message +* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older + Symfony users can still use the old version of Monolog. +* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`. + Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`. +* Several performance improvements to `Guzzle\Common\Collection` +* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +* Added `Guzzle\Stream\StreamInterface::isRepeatable` +* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`. +* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`. +* Removed `Guzzle\Http\ClientInterface::expandTemplate()` +* Removed `Guzzle\Http\ClientInterface::setRequestFactory()` +* Removed `Guzzle\Http\ClientInterface::getCurlMulti()` +* Removed `Guzzle\Http\Message\RequestInterface::canCache` +* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect` +* Removed `Guzzle\Http\Message\RequestInterface::isRedirect` +* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. +* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting + `Guzzle\Common\Version::$emitWarnings` to true. +* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use + `$request->getResponseBody()->isRepeatable()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. + These will work through Guzzle 4.0 +* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params]. +* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`. +* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. +* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +* Marked `Guzzle\Common\Collection::inject()` as deprecated. +* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');` +* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +* Always setting X-cache headers on cached responses +* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +* Added `CacheStorageInterface::purge($url)` +* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +## 3.6.0 - 2013-05-29 + +* ServiceDescription now implements ToArrayInterface +* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters +* Guzzle can now correctly parse incomplete URLs +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess +* Added the ability to cast Model objects to a string to view debug information. + +## 3.5.0 - 2013-05-13 + +* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times +* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove + itself from the EventDispatcher) +* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values +* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too +* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a + non-existent key +* Bug: All __call() method arguments are now required (helps with mocking frameworks) +* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference + to help with refcount based garbage collection of resources created by sending a request +* Deprecating ZF1 cache and log adapters. These will be removed in the next major version. +* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the + HistoryPlugin for a history. +* Added a `responseBody` alias for the `response_body` location +* Refactored internals to no longer rely on Response::getRequest() +* HistoryPlugin can now be cast to a string +* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests + and responses that are sent over the wire +* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects + +## 3.4.3 - 2013-04-30 + +* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response +* Added a check to re-extract the temp cacert bundle from the phar before sending each request + +## 3.4.2 - 2013-04-29 + +* Bug fix: Stream objects now work correctly with "a" and "a+" modes +* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present +* Bug fix: AsyncPlugin no longer forces HEAD requests +* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter +* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails +* Setting a response on a request will write to the custom request body from the response body if one is specified +* LogPlugin now writes to php://output when STDERR is undefined +* Added the ability to set multiple POST files for the same key in a single call +* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default +* Added the ability to queue CurlExceptions to the MockPlugin +* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send) +* Configuration loading now allows remote files + +## 3.4.1 - 2013-04-16 + +* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti + handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost. +* Exceptions are now properly grouped when sending requests in parallel +* Redirects are now properly aggregated when a multi transaction fails +* Redirects now set the response on the original object even in the event of a failure +* Bug fix: Model names are now properly set even when using $refs +* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax +* Added support for oauth_callback in OAuth signatures +* Added support for oauth_verifier in OAuth signatures +* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection + +## 3.4.0 - 2013-04-11 + +* Bug fix: URLs are now resolved correctly based on https://tools.ietf.org/html/rfc3986#section-5.2. #289 +* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289 +* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263 +* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264. +* Bug fix: Added `number` type to service descriptions. +* Bug fix: empty parameters are removed from an OAuth signature +* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header +* Bug fix: Fixed "array to string" error when validating a union of types in a service description +* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream +* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin. +* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs. +* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections. +* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if + the Content-Type can be determined based on the entity body or the path of the request. +* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder. +* Added support for a PSR-3 LogAdapter. +* Added a `command.after_prepare` event +* Added `oauth_callback` parameter to the OauthPlugin +* Added the ability to create a custom stream class when using a stream factory +* Added a CachingEntityBody decorator +* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized. +* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar. +* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies +* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This + means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use + POST fields or files (the latter is only used when emulating a form POST in the browser). +* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest + +## 3.3.1 - 2013-03-10 + +* Added the ability to create PHP streaming responses from HTTP requests +* Bug fix: Running any filters when parsing response headers with service descriptions +* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing +* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across + response location visitors. +* Bug fix: Removed the possibility of creating configuration files with circular dependencies +* RequestFactory::create() now uses the key of a POST file when setting the POST file name +* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set + +## 3.3.0 - 2013-03-03 + +* A large number of performance optimizations have been made +* Bug fix: Added 'wb' as a valid write mode for streams +* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned +* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()` +* BC: Removed `Guzzle\Http\Utils` class +* BC: Setting a service description on a client will no longer modify the client's command factories. +* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using + the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' +* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to + lowercase +* Operation parameter objects are now lazy loaded internally +* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses +* Added support for instantiating responseType=class responseClass classes. Classes must implement + `Guzzle\Service\Command\ResponseClassInterface` +* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These + additional properties also support locations and can be used to parse JSON responses where the outermost part of the + JSON is an array +* Added support for nested renaming of JSON models (rename sentAs to name) +* CachePlugin + * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error + * Debug headers can now added to cached response in the CachePlugin + +## 3.2.0 - 2013-02-14 + +* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients. +* URLs with no path no longer contain a "/" by default +* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url. +* BadResponseException no longer includes the full request and response message +* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface +* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface +* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription +* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list +* xmlEncoding can now be customized for the XML declaration of a XML service description operation +* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value + aggregation and no longer uses callbacks +* The URL encoding implementation of Guzzle\Http\QueryString can now be customized +* Bug fix: Filters were not always invoked for array service description parameters +* Bug fix: Redirects now use a target response body rather than a temporary response body +* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded +* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives + +## 3.1.2 - 2013-01-27 + +* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the + response body. For example, the XmlVisitor now parses the XML response into an array in the before() method. +* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent +* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444) +* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse() +* Setting default headers on a client after setting the user-agent will not erase the user-agent setting + +## 3.1.1 - 2013-01-20 + +* Adding wildcard support to Guzzle\Common\Collection::getPath() +* Adding alias support to ServiceBuilder configs +* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface + +## 3.1.0 - 2013-01-12 + +* BC: CurlException now extends from RequestException rather than BadResponseException +* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse() +* Added getData to ServiceDescriptionInterface +* Added context array to RequestInterface::setState() +* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http +* Bug: Adding required content-type when JSON request visitor adds JSON to a command +* Bug: Fixing the serialization of a service description with custom data +* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing + an array of successful and failed responses +* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection +* Added Guzzle\Http\IoEmittingEntityBody +* Moved command filtration from validators to location visitors +* Added `extends` attributes to service description parameters +* Added getModels to ServiceDescriptionInterface + +## 3.0.7 - 2012-12-19 + +* Fixing phar detection when forcing a cacert to system if null or true +* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()` +* Cleaning up `Guzzle\Common\Collection::inject` method +* Adding a response_body location to service descriptions + +## 3.0.6 - 2012-12-09 + +* CurlMulti performance improvements +* Adding setErrorResponses() to Operation +* composer.json tweaks + +## 3.0.5 - 2012-11-18 + +* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin +* Bug: Response body can now be a string containing "0" +* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert +* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs +* Added support for XML attributes in service description responses +* DefaultRequestSerializer now supports array URI parameter values for URI template expansion +* Added better mimetype guessing to requests and post files + +## 3.0.4 - 2012-11-11 + +* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value +* Bug: Cookies can now be added that have a name, domain, or value set to "0" +* Bug: Using the system cacert bundle when using the Phar +* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures +* Enhanced cookie jar de-duplication +* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added +* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies +* Added the ability to create any sort of hash for a stream rather than just an MD5 hash + +## 3.0.3 - 2012-11-04 + +* Implementing redirects in PHP rather than cURL +* Added PECL URI template extension and using as default parser if available +* Bug: Fixed Content-Length parsing of Response factory +* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams. +* Adding ToArrayInterface throughout library +* Fixing OauthPlugin to create unique nonce values per request + +## 3.0.2 - 2012-10-25 + +* Magic methods are enabled by default on clients +* Magic methods return the result of a command +* Service clients no longer require a base_url option in the factory +* Bug: Fixed an issue with URI templates where null template variables were being expanded + +## 3.0.1 - 2012-10-22 + +* Models can now be used like regular collection objects by calling filter, map, etc. +* Models no longer require a Parameter structure or initial data in the constructor +* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator` + +## 3.0.0 - 2012-10-15 + +* Rewrote service description format to be based on Swagger + * Now based on JSON schema + * Added nested input structures and nested response models + * Support for JSON and XML input and output models + * Renamed `commands` to `operations` + * Removed dot class notation + * Removed custom types +* Broke the project into smaller top-level namespaces to be more component friendly +* Removed support for XML configs and descriptions. Use arrays or JSON files. +* Removed the Validation component and Inspector +* Moved all cookie code to Guzzle\Plugin\Cookie +* Magic methods on a Guzzle\Service\Client now return the command un-executed. +* Calling getResult() or getResponse() on a command will lazily execute the command if needed. +* Now shipping with cURL's CA certs and using it by default +* Added previousResponse() method to response objects +* No longer sending Accept and Accept-Encoding headers on every request +* Only sending an Expect header by default when a payload is greater than 1MB +* Added/moved client options: + * curl.blacklist to curl.option.blacklist + * Added ssl.certificate_authority +* Added a Guzzle\Iterator component +* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin +* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin) +* Added a more robust caching plugin +* Added setBody to response objects +* Updating LogPlugin to use a more flexible MessageFormatter +* Added a completely revamped build process +* Cleaning up Collection class and removing default values from the get method +* Fixed ZF2 cache adapters + +## 2.8.8 - 2012-10-15 + +* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did + +## 2.8.7 - 2012-09-30 + +* Bug: Fixed config file aliases for JSON includes +* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests +* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload +* Bug: Hardening request and response parsing to account for missing parts +* Bug: Fixed PEAR packaging +* Bug: Fixed Request::getInfo +* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail +* Adding the ability for the namespace Iterator factory to look in multiple directories +* Added more getters/setters/removers from service descriptions +* Added the ability to remove POST fields from OAuth signatures +* OAuth plugin now supports 2-legged OAuth + +## 2.8.6 - 2012-09-05 + +* Added the ability to modify and build service descriptions +* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command +* Added a `json` parameter location +* Now allowing dot notation for classes in the CacheAdapterFactory +* Using the union of two arrays rather than an array_merge when extending service builder services and service params +* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references + in service builder config files. +* Services defined in two different config files that include one another will by default replace the previously + defined service, but you can now create services that extend themselves and merge their settings over the previous +* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like + '_default' with a default JSON configuration file. + +## 2.8.5 - 2012-08-29 + +* Bug: Suppressed empty arrays from URI templates +* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching +* Added support for HTTP responses that do not contain a reason phrase in the start-line +* AbstractCommand commands are now invokable +* Added a way to get the data used when signing an Oauth request before a request is sent + +## 2.8.4 - 2012-08-15 + +* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin +* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable. +* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream +* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream +* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5()) +* Added additional response status codes +* Removed SSL information from the default User-Agent header +* DELETE requests can now send an entity body +* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries +* Added the ability of the MockPlugin to consume mocked request bodies +* LogPlugin now exposes request and response objects in the extras array + +## 2.8.3 - 2012-07-30 + +* Bug: Fixed a case where empty POST requests were sent as GET requests +* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body +* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new +* Added multiple inheritance to service description commands +* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()` +* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything +* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles + +## 2.8.2 - 2012-07-24 + +* Bug: Query string values set to 0 are no longer dropped from the query string +* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()` +* Bug: `+` is now treated as an encoded space when parsing query strings +* QueryString and Collection performance improvements +* Allowing dot notation for class paths in filters attribute of a service descriptions + +## 2.8.1 - 2012-07-16 + +* Loosening Event Dispatcher dependency +* POST redirects can now be customized using CURLOPT_POSTREDIR + +## 2.8.0 - 2012-07-15 + +* BC: Guzzle\Http\Query + * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl) + * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding() + * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool) + * Changed the aggregation functions of QueryString to be static methods + * Can now use fromString() with querystrings that have a leading ? +* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters +* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body +* Cookies are no longer URL decoded by default +* Bug: URI template variables set to null are no longer expanded + +## 2.7.2 - 2012-07-02 + +* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser. +* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty() +* CachePlugin now allows for a custom request parameter function to check if a request can be cached +* Bug fix: CachePlugin now only caches GET and HEAD requests by default +* Bug fix: Using header glue when transferring headers over the wire +* Allowing deeply nested arrays for composite variables in URI templates +* Batch divisors can now return iterators or arrays + +## 2.7.1 - 2012-06-26 + +* Minor patch to update version number in UA string +* Updating build process + +## 2.7.0 - 2012-06-25 + +* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes. +* BC: Removed magic setX methods from commands +* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method +* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable. +* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity) +* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace +* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin +* Added the ability to set POST fields and files in a service description +* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method +* Adding a command.before_prepare event to clients +* Added BatchClosureTransfer and BatchClosureDivisor +* BatchTransferException now includes references to the batch divisor and transfer strategies +* Fixed some tests so that they pass more reliably +* Added Guzzle\Common\Log\ArrayLogAdapter + +## 2.6.6 - 2012-06-10 + +* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin +* BC: Removing Guzzle\Service\Command\CommandSet +* Adding generic batching system (replaces the batch queue plugin and command set) +* Updating ZF cache and log adapters and now using ZF's composer repository +* Bug: Setting the name of each ApiParam when creating through an ApiCommand +* Adding result_type, result_doc, deprecated, and doc_url to service descriptions +* Bug: Changed the default cookie header casing back to 'Cookie' + +## 2.6.5 - 2012-06-03 + +* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource() +* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from +* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data +* BC: Renaming methods in the CookieJarInterface +* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations +* Making the default glue for HTTP headers ';' instead of ',' +* Adding a removeValue to Guzzle\Http\Message\Header +* Adding getCookies() to request interface. +* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber() + +## 2.6.4 - 2012-05-30 + +* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class. +* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand +* Bug: Fixing magic method command calls on clients +* Bug: Email constraint only validates strings +* Bug: Aggregate POST fields when POST files are present in curl handle +* Bug: Fixing default User-Agent header +* Bug: Only appending or prepending parameters in commands if they are specified +* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes +* Allowing the use of dot notation for class namespaces when using instance_of constraint +* Added any_match validation constraint +* Added an AsyncPlugin +* Passing request object to the calculateWait method of the ExponentialBackoffPlugin +* Allowing the result of a command object to be changed +* Parsing location and type sub values when instantiating a service description rather than over and over at runtime + +## 2.6.3 - 2012-05-23 + +* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options. +* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields. +* You can now use an array of data when creating PUT request bodies in the request factory. +* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable. +* [Http] Adding support for Content-Type in multipart POST uploads per upload +* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1]) +* Adding more POST data operations for easier manipulation of POST data. +* You can now set empty POST fields. +* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files. +* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate. +* CS updates + +## 2.6.2 - 2012-05-19 + +* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method. + +## 2.6.1 - 2012-05-19 + +* [BC] Removing 'path' support in service descriptions. Use 'uri'. +* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache. +* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it. +* [BC] Removing Guzzle\Common\XmlElement. +* All commands, both dynamic and concrete, have ApiCommand objects. +* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits. +* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored. +* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible. + +## 2.6.0 - 2012-05-15 + +* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder +* [BC] Executing a Command returns the result of the command rather than the command +* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed. +* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args. +* [BC] Moving ResourceIterator* to Guzzle\Service\Resource +* [BC] Completely refactored ResourceIterators to iterate over a cloned command object +* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate +* [BC] Guzzle\Guzzle is now deprecated +* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject +* Adding Guzzle\Version class to give version information about Guzzle +* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate() +* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data +* ServiceDescription and ServiceBuilder are now cacheable using similar configs +* Changing the format of XML and JSON service builder configs. Backwards compatible. +* Cleaned up Cookie parsing +* Trimming the default Guzzle User-Agent header +* Adding a setOnComplete() method to Commands that is called when a command completes +* Keeping track of requests that were mocked in the MockPlugin +* Fixed a caching bug in the CacheAdapterFactory +* Inspector objects can be injected into a Command object +* Refactoring a lot of code and tests to be case insensitive when dealing with headers +* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL +* Adding the ability to set global option overrides to service builder configs +* Adding the ability to include other service builder config files from within XML and JSON files +* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method. + +## 2.5.0 - 2012-05-08 + +* Major performance improvements +* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated. +* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component. +* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}" +* Added the ability to passed parameters to all requests created by a client +* Added callback functionality to the ExponentialBackoffPlugin +* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies. +* Rewinding request stream bodies when retrying requests +* Exception is thrown when JSON response body cannot be decoded +* Added configurable magic method calls to clients and commands. This is off by default. +* Fixed a defect that added a hash to every parsed URL part +* Fixed duplicate none generation for OauthPlugin. +* Emitting an event each time a client is generated by a ServiceBuilder +* Using an ApiParams object instead of a Collection for parameters of an ApiCommand +* cache.* request parameters should be renamed to params.cache.* +* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle. +* Added the ability to disable type validation of service descriptions +* ServiceDescriptions and ServiceBuilders are now Serializable diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/LICENSE b/data/web/inc/lib/vendor/guzzlehttp/guzzle/LICENSE new file mode 100644 index 0000000000..fd2375d888 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/LICENSE @@ -0,0 +1,27 @@ +The MIT License (MIT) + +Copyright (c) 2011 Michael Dowling +Copyright (c) 2012 Jeremy Lindblom +Copyright (c) 2014 Graham Campbell +Copyright (c) 2015 Mรกrk Sรกgi-Kazรกr +Copyright (c) 2015 Tobias Schultze +Copyright (c) 2016 Tobias Nyholm +Copyright (c) 2016 George Mponos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/README.md b/data/web/inc/lib/vendor/guzzlehttp/guzzle/README.md new file mode 100644 index 0000000000..f287fa98d1 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/README.md @@ -0,0 +1,94 @@ +![Guzzle](.github/logo.png?raw=true) + +# Guzzle, PHP HTTP client + +[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases) +[![Build Status](https://img.shields.io/github/workflow/status/guzzle/guzzle/CI?label=ci%20build&style=flat-square)](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI) +[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle) + +Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and +trivial to integrate with web services. + +- Simple interface for building query strings, POST requests, streaming large + uploads, streaming large downloads, using HTTP cookies, uploading JSON data, + etc... +- Can send both synchronous and asynchronous requests using the same interface. +- Uses PSR-7 interfaces for requests, responses, and streams. This allows you + to utilize other PSR-7 compatible libraries with Guzzle. +- Supports PSR-18 allowing interoperability between other PSR-18 HTTP Clients. +- Abstracts away the underlying HTTP transport, allowing you to write + environment and transport agnostic code; i.e., no hard dependency on cURL, + PHP streams, sockets, or non-blocking event loops. +- Middleware system allows you to augment and compose client behavior. + +```php +$client = new \GuzzleHttp\Client(); +$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle'); + +echo $response->getStatusCode(); // 200 +echo $response->getHeaderLine('content-type'); // 'application/json; charset=utf8' +echo $response->getBody(); // '{"id": 1420053, "name": "guzzle", ...}' + +// Send an asynchronous request. +$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org'); +$promise = $client->sendAsync($request)->then(function ($response) { + echo 'I completed! ' . $response->getBody(); +}); + +$promise->wait(); +``` + +## Help and docs + +We use GitHub issues only to discuss bugs and new features. For support please refer to: + +- [Documentation](https://docs.guzzlephp.org) +- [Stack Overflow](https://stackoverflow.com/questions/tagged/guzzle) +- [#guzzle](https://app.slack.com/client/T0D2S9JCT/CE6UAAKL4) channel on [PHP-HTTP Slack](https://slack.httplug.io/) +- [Gitter](https://gitter.im/guzzle/guzzle) + + +## Installing Guzzle + +The recommended way to install Guzzle is through +[Composer](https://getcomposer.org/). + +```bash +composer require guzzlehttp/guzzle +``` + + +## Version Guidance + +| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version | +|---------|----------------|---------------------|--------------|---------------------|---------------------|-------|--------------| +| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >=5.3.3,<7.0 | +| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 | +| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 | +| 6.x | Security fixes | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 | +| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.2 | + +[guzzle-3-repo]: https://github.com/guzzle/guzzle3 +[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x +[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3 +[guzzle-6-repo]: https://github.com/guzzle/guzzle/tree/6.5 +[guzzle-7-repo]: https://github.com/guzzle/guzzle +[guzzle-3-docs]: https://guzzle3.readthedocs.io/ +[guzzle-5-docs]: https://docs.guzzlephp.org/en/5.3/ +[guzzle-6-docs]: https://docs.guzzlephp.org/en/6.5/ +[guzzle-7-docs]: https://docs.guzzlephp.org/en/latest/ + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/guzzle/security/policy) for more information. + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-guzzle?utm_source=packagist-guzzlehttp-guzzle&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/UPGRADING.md b/data/web/inc/lib/vendor/guzzlehttp/guzzle/UPGRADING.md new file mode 100644 index 0000000000..45417a7e1f --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/UPGRADING.md @@ -0,0 +1,1253 @@ +Guzzle Upgrade Guide +==================== + +6.0 to 7.0 +---------- + +In order to take advantage of the new features of PHP, Guzzle dropped the support +of PHP 5. The minimum supported PHP version is now PHP 7.2. Type hints and return +types for functions and methods have been added wherever possible. + +Please make sure: +- You are calling a function or a method with the correct type. +- If you extend a class of Guzzle; update all signatures on methods you override. + +#### Other backwards compatibility breaking changes + +- Class `GuzzleHttp\UriTemplate` is removed. +- Class `GuzzleHttp\Exception\SeekException` is removed. +- Classes `GuzzleHttp\Exception\BadResponseException`, `GuzzleHttp\Exception\ClientException`, + `GuzzleHttp\Exception\ServerException` can no longer be initialized with an empty + Response as argument. +- Class `GuzzleHttp\Exception\ConnectException` now extends `GuzzleHttp\Exception\TransferException` + instead of `GuzzleHttp\Exception\RequestException`. +- Function `GuzzleHttp\Exception\ConnectException::getResponse()` is removed. +- Function `GuzzleHttp\Exception\ConnectException::hasResponse()` is removed. +- Constant `GuzzleHttp\ClientInterface::VERSION` is removed. Added `GuzzleHttp\ClientInterface::MAJOR_VERSION` instead. +- Function `GuzzleHttp\Exception\RequestException::getResponseBodySummary` is removed. + Use `\GuzzleHttp\Psr7\get_message_body_summary` as an alternative. +- Function `GuzzleHttp\Cookie\CookieJar::getCookieValue` is removed. +- Request option `exception` is removed. Please use `http_errors`. +- Request option `save_to` is removed. Please use `sink`. +- Pool option `pool_size` is removed. Please use `concurrency`. +- We now look for environment variables in the `$_SERVER` super global, due to thread safety issues with `getenv`. We continue to fallback to `getenv` in CLI environments, for maximum compatibility. +- The `get`, `head`, `put`, `post`, `patch`, `delete`, `getAsync`, `headAsync`, `putAsync`, `postAsync`, `patchAsync`, and `deleteAsync` methods are now implemented as genuine methods on `GuzzleHttp\Client`, with strong typing. The original `__call` implementation remains unchanged for now, for maximum backwards compatibility, but won't be invoked under normal operation. +- The `log` middleware will log the errors with level `error` instead of `notice` +- Support for international domain names (IDN) is now disabled by default, and enabling it requires installing ext-intl, linked against a modern version of the C library (ICU 4.6 or higher). + +#### Native functions calls + +All internal native functions calls of Guzzle are now prefixed with a slash. This +change makes it impossible for method overloading by other libraries or applications. +Example: + +```php +// Before: +curl_version(); + +// After: +\curl_version(); +``` + +For the full diff you can check [here](https://github.com/guzzle/guzzle/compare/6.5.4..master). + +5.0 to 6.0 +---------- + +Guzzle now uses [PSR-7](https://www.php-fig.org/psr/psr-7/) for HTTP messages. +Due to the fact that these messages are immutable, this prompted a refactoring +of Guzzle to use a middleware based system rather than an event system. Any +HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be +updated to work with the new immutable PSR-7 request and response objects. Any +event listeners or subscribers need to be updated to become middleware +functions that wrap handlers (or are injected into a +`GuzzleHttp\HandlerStack`). + +- Removed `GuzzleHttp\BatchResults` +- Removed `GuzzleHttp\Collection` +- Removed `GuzzleHttp\HasDataTrait` +- Removed `GuzzleHttp\ToArrayInterface` +- The `guzzlehttp/streams` dependency has been removed. Stream functionality + is now present in the `GuzzleHttp\Psr7` namespace provided by the + `guzzlehttp/psr7` package. +- Guzzle no longer uses ReactPHP promises and now uses the + `guzzlehttp/promises` library. We use a custom promise library for three + significant reasons: + 1. React promises (at the time of writing this) are recursive. Promise + chaining and promise resolution will eventually blow the stack. Guzzle + promises are not recursive as they use a sort of trampolining technique. + Note: there has been movement in the React project to modify promises to + no longer utilize recursion. + 2. Guzzle needs to have the ability to synchronously block on a promise to + wait for a result. Guzzle promises allows this functionality (and does + not require the use of recursion). + 3. Because we need to be able to wait on a result, doing so using React + promises requires wrapping react promises with RingPHP futures. This + overhead is no longer needed, reducing stack sizes, reducing complexity, + and improving performance. +- `GuzzleHttp\Mimetypes` has been moved to a function in + `GuzzleHttp\Psr7\mimetype_from_extension` and + `GuzzleHttp\Psr7\mimetype_from_filename`. +- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query + strings must now be passed into request objects as strings, or provided to + the `query` request option when creating requests with clients. The `query` + option uses PHP's `http_build_query` to convert an array to a string. If you + need a different serialization technique, you will need to pass the query + string in as a string. There are a couple helper functions that will make + working with query strings easier: `GuzzleHttp\Psr7\parse_query` and + `GuzzleHttp\Psr7\build_query`. +- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware + system based on PSR-7, using RingPHP and it's middleware system as well adds + more complexity than the benefits it provides. All HTTP handlers that were + present in RingPHP have been modified to work directly with PSR-7 messages + and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces + complexity in Guzzle, removes a dependency, and improves performance. RingPHP + will be maintained for Guzzle 5 support, but will no longer be a part of + Guzzle 6. +- As Guzzle now uses a middleware based systems the event system and RingPHP + integration has been removed. Note: while the event system has been removed, + it is possible to add your own type of event system that is powered by the + middleware system. + - Removed the `Event` namespace. + - Removed the `Subscriber` namespace. + - Removed `Transaction` class + - Removed `RequestFsm` + - Removed `RingBridge` + - `GuzzleHttp\Subscriber\Cookie` is now provided by + `GuzzleHttp\Middleware::cookies` + - `GuzzleHttp\Subscriber\HttpError` is now provided by + `GuzzleHttp\Middleware::httpError` + - `GuzzleHttp\Subscriber\History` is now provided by + `GuzzleHttp\Middleware::history` + - `GuzzleHttp\Subscriber\Mock` is now provided by + `GuzzleHttp\Handler\MockHandler` + - `GuzzleHttp\Subscriber\Prepare` is now provided by + `GuzzleHttp\PrepareBodyMiddleware` + - `GuzzleHttp\Subscriber\Redirect` is now provided by + `GuzzleHttp\RedirectMiddleware` +- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in + `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone. +- Static functions in `GuzzleHttp\Utils` have been moved to namespaced + functions under the `GuzzleHttp` namespace. This requires either a Composer + based autoloader or you to include functions.php. +- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to + `GuzzleHttp\ClientInterface::getConfig`. +- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed. +- The `json` and `xml` methods of response objects has been removed. With the + migration to strictly adhering to PSR-7 as the interface for Guzzle messages, + adding methods to message interfaces would actually require Guzzle messages + to extend from PSR-7 messages rather then work with them directly. + +## Migrating to middleware + +The change to PSR-7 unfortunately required significant refactoring to Guzzle +due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event +system from plugins. The event system relied on mutability of HTTP messages and +side effects in order to work. With immutable messages, you have to change your +workflow to become more about either returning a value (e.g., functional +middlewares) or setting a value on an object. Guzzle v6 has chosen the +functional middleware approach. + +Instead of using the event system to listen for things like the `before` event, +you now create a stack based middleware function that intercepts a request on +the way in and the promise of the response on the way out. This is a much +simpler and more predictable approach than the event system and works nicely +with PSR-7 middleware. Due to the use of promises, the middleware system is +also asynchronous. + +v5: + +```php +use GuzzleHttp\Event\BeforeEvent; +$client = new GuzzleHttp\Client(); +// Get the emitter and listen to the before event. +$client->getEmitter()->on('before', function (BeforeEvent $e) { + // Guzzle v5 events relied on mutation + $e->getRequest()->setHeader('X-Foo', 'Bar'); +}); +``` + +v6: + +In v6, you can modify the request before it is sent using the `mapRequest` +middleware. The idiomatic way in v6 to modify the request/response lifecycle is +to setup a handler middleware stack up front and inject the handler into a +client. + +```php +use GuzzleHttp\Middleware; +// Create a handler stack that has all of the default middlewares attached +$handler = GuzzleHttp\HandlerStack::create(); +// Push the handler onto the handler stack +$handler->push(Middleware::mapRequest(function (RequestInterface $request) { + // Notice that we have to return a request object + return $request->withHeader('X-Foo', 'Bar'); +})); +// Inject the handler into the client +$client = new GuzzleHttp\Client(['handler' => $handler]); +``` + +## POST Requests + +This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params) +and `multipart` request options. `form_params` is an associative array of +strings or array of strings and is used to serialize an +`application/x-www-form-urlencoded` POST request. The +[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart) +option is now used to send a multipart/form-data POST request. + +`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add +POST files to a multipart/form-data request. + +The `body` option no longer accepts an array to send POST requests. Please use +`multipart` or `form_params` instead. + +The `base_url` option has been renamed to `base_uri`. + +4.x to 5.0 +---------- + +## Rewritten Adapter Layer + +Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send +HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor +is still supported, but it has now been renamed to `handler`. Instead of +passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP +`callable` that follows the RingPHP specification. + +## Removed Fluent Interfaces + +[Fluent interfaces were removed](https://ocramius.github.io/blog/fluent-interfaces-are-evil/) +from the following classes: + +- `GuzzleHttp\Collection` +- `GuzzleHttp\Url` +- `GuzzleHttp\Query` +- `GuzzleHttp\Post\PostBody` +- `GuzzleHttp\Cookie\SetCookie` + +## Removed functions.php + +Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following +functions can be used as replacements. + +- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode` +- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath` +- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path` +- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however, + deprecated in favor of using `GuzzleHttp\Pool::batch()`. + +The "procedural" global client has been removed with no replacement (e.g., +`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client` +object as a replacement. + +## `throwImmediately` has been removed + +The concept of "throwImmediately" has been removed from exceptions and error +events. This control mechanism was used to stop a transfer of concurrent +requests from completing. This can now be handled by throwing the exception or +by cancelling a pool of requests or each outstanding future request +individually. + +## headers event has been removed + +Removed the "headers" event. This event was only useful for changing the +body a response once the headers of the response were known. You can implement +a similar behavior in a number of ways. One example might be to use a +FnStream that has access to the transaction being sent. For example, when the +first byte is written, you could check if the response headers match your +expectations, and if so, change the actual stream body that is being +written to. + +## Updates to HTTP Messages + +Removed the `asArray` parameter from +`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header +value as an array, then use the newly added `getHeaderAsArray()` method of +`MessageInterface`. This change makes the Guzzle interfaces compatible with +the PSR-7 interfaces. + +3.x to 4.0 +---------- + +## Overarching changes: + +- Now requires PHP 5.4 or greater. +- No longer requires cURL to send requests. +- Guzzle no longer wraps every exception it throws. Only exceptions that are + recoverable are now wrapped by Guzzle. +- Various namespaces have been removed or renamed. +- No longer requiring the Symfony EventDispatcher. A custom event dispatcher + based on the Symfony EventDispatcher is + now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant + speed and functionality improvements). + +Changes per Guzzle 3.x namespace are described below. + +## Batch + +The `Guzzle\Batch` namespace has been removed. This is best left to +third-parties to implement on top of Guzzle's core HTTP library. + +## Cache + +The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement +has been implemented yet, but hoping to utilize a PSR cache interface). + +## Common + +- Removed all of the wrapped exceptions. It's better to use the standard PHP + library for unrecoverable exceptions. +- `FromConfigInterface` has been removed. +- `Guzzle\Common\Version` has been removed. The VERSION constant can be found + at `GuzzleHttp\ClientInterface::VERSION`. + +### Collection + +- `getAll` has been removed. Use `toArray` to convert a collection to an array. +- `inject` has been removed. +- `keySearch` has been removed. +- `getPath` no longer supports wildcard expressions. Use something better like + JMESPath for this. +- `setPath` now supports appending to an existing array via the `[]` notation. + +### Events + +Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses +`GuzzleHttp\Event\Emitter`. + +- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by + `GuzzleHttp\Event\EmitterInterface`. +- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by + `GuzzleHttp\Event\Emitter`. +- `Symfony\Component\EventDispatcher\Event` is replaced by + `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in + `GuzzleHttp\Event\EventInterface`. +- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and + `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the + event emitter of a request, client, etc. now uses the `getEmitter` method + rather than the `getDispatcher` method. + +#### Emitter + +- Use the `once()` method to add a listener that automatically removes itself + the first time it is invoked. +- Use the `listeners()` method to retrieve a list of event listeners rather than + the `getListeners()` method. +- Use `emit()` instead of `dispatch()` to emit an event from an emitter. +- Use `attach()` instead of `addSubscriber()` and `detach()` instead of + `removeSubscriber()`. + +```php +$mock = new Mock(); +// 3.x +$request->getEventDispatcher()->addSubscriber($mock); +$request->getEventDispatcher()->removeSubscriber($mock); +// 4.x +$request->getEmitter()->attach($mock); +$request->getEmitter()->detach($mock); +``` + +Use the `on()` method to add a listener rather than the `addListener()` method. + +```php +// 3.x +$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } ); +// 4.x +$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } ); +``` + +## Http + +### General changes + +- The cacert.pem certificate has been moved to `src/cacert.pem`. +- Added the concept of adapters that are used to transfer requests over the + wire. +- Simplified the event system. +- Sending requests in parallel is still possible, but batching is no longer a + concept of the HTTP layer. Instead, you must use the `complete` and `error` + events to asynchronously manage parallel request transfers. +- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`. +- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`. +- QueryAggregators have been rewritten so that they are simply callable + functions. +- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in + `functions.php` for an easy to use static client instance. +- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from + `GuzzleHttp\Exception\TransferException`. + +### Client + +Calling methods like `get()`, `post()`, `head()`, etc. no longer create and +return a request, but rather creates a request, sends the request, and returns +the response. + +```php +// 3.0 +$request = $client->get('/'); +$response = $request->send(); + +// 4.0 +$response = $client->get('/'); + +// or, to mirror the previous behavior +$request = $client->createRequest('GET', '/'); +$response = $client->send($request); +``` + +`GuzzleHttp\ClientInterface` has changed. + +- The `send` method no longer accepts more than one request. Use `sendAll` to + send multiple requests in parallel. +- `setUserAgent()` has been removed. Use a default request option instead. You + could, for example, do something like: + `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`. +- `setSslVerification()` has been removed. Use default request options instead, + like `$client->setConfig('defaults/verify', true)`. + +`GuzzleHttp\Client` has changed. + +- The constructor now accepts only an associative array. You can include a + `base_url` string or array to use a URI template as the base URL of a client. + You can also specify a `defaults` key that is an associative array of default + request options. You can pass an `adapter` to use a custom adapter, + `batch_adapter` to use a custom adapter for sending requests in parallel, or + a `message_factory` to change the factory used to create HTTP requests and + responses. +- The client no longer emits a `client.create_request` event. +- Creating requests with a client no longer automatically utilize a URI + template. You must pass an array into a creational method (e.g., + `createRequest`, `get`, `put`, etc.) in order to expand a URI template. + +### Messages + +Messages no longer have references to their counterparts (i.e., a request no +longer has a reference to it's response, and a response no loger has a +reference to its request). This association is now managed through a +`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to +these transaction objects using request events that are emitted over the +lifecycle of a request. + +#### Requests with a body + +- `GuzzleHttp\Message\EntityEnclosingRequest` and + `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The + separation between requests that contain a body and requests that do not + contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface` + handles both use cases. +- Any method that previously accepts a `GuzzleHttp\Response` object now accept a + `GuzzleHttp\Message\ResponseInterface`. +- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to + `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create + both requests and responses and is implemented in + `GuzzleHttp\Message\MessageFactory`. +- POST field and file methods have been removed from the request object. You + must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface` + to control the format of a POST body. Requests that are created using a + standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use + a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if + the method is POST and no body is provided. + +```php +$request = $client->createRequest('POST', '/'); +$request->getBody()->setField('foo', 'bar'); +$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r'))); +``` + +#### Headers + +- `GuzzleHttp\Message\Header` has been removed. Header values are now simply + represented by an array of values or as a string. Header values are returned + as a string by default when retrieving a header value from a message. You can + pass an optional argument of `true` to retrieve a header value as an array + of strings instead of a single concatenated string. +- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to + `GuzzleHttp\Post`. This interface has been simplified and now allows the + addition of arbitrary headers. +- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most + of the custom headers are now handled separately in specific + subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has + been updated to properly handle headers that contain parameters (like the + `Link` header). + +#### Responses + +- `GuzzleHttp\Message\Response::getInfo()` and + `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event + system to retrieve this type of information. +- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed. +- `GuzzleHttp\Message\Response::getMessage()` has been removed. +- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific + methods have moved to the CacheSubscriber. +- Header specific helper functions like `getContentMd5()` have been removed. + Just use `getHeader('Content-MD5')` instead. +- `GuzzleHttp\Message\Response::setRequest()` and + `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event + system to work with request and response objects as a transaction. +- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the + Redirect subscriber instead. +- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have + been removed. Use `getStatusCode()` instead. + +#### Streaming responses + +Streaming requests can now be created by a client directly, returning a +`GuzzleHttp\Message\ResponseInterface` object that contains a body stream +referencing an open PHP HTTP stream. + +```php +// 3.0 +use Guzzle\Stream\PhpStreamRequestFactory; +$request = $client->get('/'); +$factory = new PhpStreamRequestFactory(); +$stream = $factory->fromRequest($request); +$data = $stream->read(1024); + +// 4.0 +$response = $client->get('/', ['stream' => true]); +// Read some data off of the stream in the response body +$data = $response->getBody()->read(1024); +``` + +#### Redirects + +The `configureRedirects()` method has been removed in favor of a +`allow_redirects` request option. + +```php +// Standard redirects with a default of a max of 5 redirects +$request = $client->createRequest('GET', '/', ['allow_redirects' => true]); + +// Strict redirects with a custom number of redirects +$request = $client->createRequest('GET', '/', [ + 'allow_redirects' => ['max' => 5, 'strict' => true] +]); +``` + +#### EntityBody + +EntityBody interfaces and classes have been removed or moved to +`GuzzleHttp\Stream`. All classes and interfaces that once required +`GuzzleHttp\EntityBodyInterface` now require +`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no +longer uses `GuzzleHttp\EntityBody::factory` but now uses +`GuzzleHttp\Stream\Stream::factory` or even better: +`GuzzleHttp\Stream\create()`. + +- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface` +- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream` +- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream` +- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream` +- `Guzzle\Http\IoEmittyinEntityBody` has been removed. + +#### Request lifecycle events + +Requests previously submitted a large number of requests. The number of events +emitted over the lifecycle of a request has been significantly reduced to make +it easier to understand how to extend the behavior of a request. All events +emitted during the lifecycle of a request now emit a custom +`GuzzleHttp\Event\EventInterface` object that contains context providing +methods and a way in which to modify the transaction at that specific point in +time (e.g., intercept the request and set a response on the transaction). + +- `request.before_send` has been renamed to `before` and now emits a + `GuzzleHttp\Event\BeforeEvent` +- `request.complete` has been renamed to `complete` and now emits a + `GuzzleHttp\Event\CompleteEvent`. +- `request.sent` has been removed. Use `complete`. +- `request.success` has been removed. Use `complete`. +- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`. +- `request.exception` has been removed. Use `error`. +- `request.receive.status_line` has been removed. +- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to + maintain a status update. +- `curl.callback.write` has been removed. Use a custom `StreamInterface` to + intercept writes. +- `curl.callback.read` has been removed. Use a custom `StreamInterface` to + intercept reads. + +`headers` is a new event that is emitted after the response headers of a +request have been received before the body of the response is downloaded. This +event emits a `GuzzleHttp\Event\HeadersEvent`. + +You can intercept a request and inject a response using the `intercept()` event +of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and +`GuzzleHttp\Event\ErrorEvent` event. + +See: http://docs.guzzlephp.org/en/latest/events.html + +## Inflection + +The `Guzzle\Inflection` namespace has been removed. This is not a core concern +of Guzzle. + +## Iterator + +The `Guzzle\Iterator` namespace has been removed. + +- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and + `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of + Guzzle itself. +- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent + class is shipped with PHP 5.4. +- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because + it's easier to just wrap an iterator in a generator that maps values. + +For a replacement of these iterators, see https://github.com/nikic/iter + +## Log + +The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The +`Guzzle\Log` namespace has been removed. Guzzle now relies on +`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been +moved to `GuzzleHttp\Subscriber\Log\Formatter`. + +## Parser + +The `Guzzle\Parser` namespace has been removed. This was previously used to +make it possible to plug in custom parsers for cookies, messages, URI +templates, and URLs; however, this level of complexity is not needed in Guzzle +so it has been removed. + +- Cookie: Cookie parsing logic has been moved to + `GuzzleHttp\Cookie\SetCookie::fromString`. +- Message: Message parsing logic for both requests and responses has been moved + to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only + used in debugging or deserializing messages, so it doesn't make sense for + Guzzle as a library to add this level of complexity to parsing messages. +- UriTemplate: URI template parsing has been moved to + `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL + URI template library if it is installed. +- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously + it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary, + then developers are free to subclass `GuzzleHttp\Url`. + +## Plugin + +The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`. +Several plugins are shipping with the core Guzzle library under this namespace. + +- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar + code has moved to `GuzzleHttp\Cookie`. +- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin. +- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is + received. +- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin. +- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before + sending. This subscriber is attached to all requests by default. +- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin. + +The following plugins have been removed (third-parties are free to re-implement +these if needed): + +- `GuzzleHttp\Plugin\Async` has been removed. +- `GuzzleHttp\Plugin\CurlAuth` has been removed. +- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This + functionality should instead be implemented with event listeners that occur + after normal response parsing occurs in the guzzle/command package. + +The following plugins are not part of the core Guzzle package, but are provided +in separate repositories: + +- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler + to build custom retry policies using simple functions rather than various + chained classes. See: https://github.com/guzzle/retry-subscriber +- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to + https://github.com/guzzle/cache-subscriber +- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to + https://github.com/guzzle/log-subscriber +- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to + https://github.com/guzzle/message-integrity-subscriber +- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to + `GuzzleHttp\Subscriber\MockSubscriber`. +- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to + https://github.com/guzzle/oauth-subscriber + +## Service + +The service description layer of Guzzle has moved into two separate packages: + +- http://github.com/guzzle/command Provides a high level abstraction over web + services by representing web service operations using commands. +- http://github.com/guzzle/guzzle-services Provides an implementation of + guzzle/command that provides request serialization and response parsing using + Guzzle service descriptions. + +## Stream + +Stream have moved to a separate package available at +https://github.com/guzzle/streams. + +`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take +on the responsibilities of `Guzzle\Http\EntityBody` and +`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number +of methods implemented by the `StreamInterface` has been drastically reduced to +allow developers to more easily extend and decorate stream behavior. + +## Removed methods from StreamInterface + +- `getStream` and `setStream` have been removed to better encapsulate streams. +- `getMetadata` and `setMetadata` have been removed in favor of + `GuzzleHttp\Stream\MetadataStreamInterface`. +- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been + removed. This data is accessible when + using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`. +- `rewind` has been removed. Use `seek(0)` for a similar behavior. + +## Renamed methods + +- `detachStream` has been renamed to `detach`. +- `feof` has been renamed to `eof`. +- `ftell` has been renamed to `tell`. +- `readLine` has moved from an instance method to a static class method of + `GuzzleHttp\Stream\Stream`. + +## Metadata streams + +`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams +that contain additional metadata accessible via `getMetadata()`. +`GuzzleHttp\Stream\StreamInterface::getMetadata` and +`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed. + +## StreamRequestFactory + +The entire concept of the StreamRequestFactory has been removed. The way this +was used in Guzzle 3 broke the actual interface of sending streaming requests +(instead of getting back a Response, you got a StreamInterface). Streaming +PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`. + +3.6 to 3.7 +---------- + +### Deprecations + +- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.: + +```php +\Guzzle\Common\Version::$emitWarnings = true; +``` + +The following APIs and options have been marked as deprecated: + +- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +- Marked `Guzzle\Common\Collection::inject()` as deprecated. +- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use + `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or + `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` + +3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational +request methods. When paired with a client's configuration settings, these options allow you to specify default settings +for various aspects of a request. Because these options make other previous configuration options redundant, several +configuration options and methods of a client and AbstractCommand have been deprecated. + +- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`. +- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`. +- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')` +- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0 + + $command = $client->getCommand('foo', array( + 'command.headers' => array('Test' => '123'), + 'command.response_body' => '/path/to/file' + )); + + // Should be changed to: + + $command = $client->getCommand('foo', array( + 'command.request_options' => array( + 'headers' => array('Test' => '123'), + 'save_as' => '/path/to/file' + ) + )); + +### Interface changes + +Additions and changes (you will need to update any implementations or subclasses you may have created): + +- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +- Added `Guzzle\Stream\StreamInterface::isRepeatable` +- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. + +The following methods were removed from interfaces. All of these methods are still available in the concrete classes +that implement them, but you should update your code to use alternative methods: + +- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or + `$client->setDefaultOption('headers/{header_name}', 'value')`. or + `$client->setDefaultOption('headers', array('header_name' => 'value'))`. +- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`. +- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail. +- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin. +- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin. +- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin. + +### Cache plugin breaking changes + +- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +- Always setting X-cache headers on cached responses +- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +- Added `CacheStorageInterface::purge($url)` +- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +3.5 to 3.6 +---------- + +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). + For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader(). + Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request. +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Moved getLinks() from Response to just be used on a Link header object. + +If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the +HeaderInterface (e.g. toArray(), getAll(), etc.). + +### Interface changes + +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() + +### Removed deprecated functions + +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). + +### Deprecations + +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. + +### Other changes + +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess + +3.3 to 3.4 +---------- + +Base URLs of a client now follow the rules of https://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs. + +3.2 to 3.3 +---------- + +### Response::getEtag() quote stripping removed + +`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header + +### Removed `Guzzle\Http\Utils` + +The `Guzzle\Http\Utils` class was removed. This class was only used for testing. + +### Stream wrapper and type + +`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase. + +### curl.emit_io became emit_io + +Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the +'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' + +3.1 to 3.2 +---------- + +### CurlMulti is no longer reused globally + +Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added +to a single client can pollute requests dispatched from other clients. + +If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the +ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is +created. + +```php +$multi = new Guzzle\Http\Curl\CurlMulti(); +$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json'); +$builder->addListener('service_builder.create_client', function ($event) use ($multi) { + $event['client']->setCurlMulti($multi); +} +}); +``` + +### No default path + +URLs no longer have a default path value of '/' if no path was specified. + +Before: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com/ +``` + +After: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com +``` + +### Less verbose BadResponseException + +The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and +response information. You can, however, get access to the request and response object by calling `getRequest()` or +`getResponse()` on the exception object. + +### Query parameter aggregation + +Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a +setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is +responsible for handling the aggregation of multi-valued query string variables into a flattened hash. + +2.8 to 3.x +---------- + +### Guzzle\Service\Inspector + +Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig` + +**Before** + +```php +use Guzzle\Service\Inspector; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Inspector::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +**After** + +```php +use Guzzle\Common\Collection; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Collection::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +### Convert XML Service Descriptions to JSON + +**Before** + +```xml + + + + + + Get a list of groups + + + Uses a search query to get a list of groups + + + + Create a group + + + + + Delete a group by ID + + + + + + + Update a group + + + + + + +``` + +**After** + +```json +{ + "name": "Zendesk REST API v2", + "apiVersion": "2012-12-31", + "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users", + "operations": { + "list_groups": { + "httpMethod":"GET", + "uri": "groups.json", + "summary": "Get a list of groups" + }, + "search_groups":{ + "httpMethod":"GET", + "uri": "search.json?query=\"{query} type:group\"", + "summary": "Uses a search query to get a list of groups", + "parameters":{ + "query":{ + "location": "uri", + "description":"Zendesk Search Query", + "type": "string", + "required": true + } + } + }, + "create_group": { + "httpMethod":"POST", + "uri": "groups.json", + "summary": "Create a group", + "parameters":{ + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + }, + "delete_group": { + "httpMethod":"DELETE", + "uri": "groups/{id}.json", + "summary": "Delete a group", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to delete by ID", + "type": "integer", + "required": true + } + } + }, + "get_group": { + "httpMethod":"GET", + "uri": "groups/{id}.json", + "summary": "Get a ticket", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to get by ID", + "type": "integer", + "required": true + } + } + }, + "update_group": { + "httpMethod":"PUT", + "uri": "groups/{id}.json", + "summary": "Update a group", + "parameters":{ + "id": { + "location": "uri", + "description":"Group to update by ID", + "type": "integer", + "required": true + }, + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + } +} +``` + +### Guzzle\Service\Description\ServiceDescription + +Commands are now called Operations + +**Before** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getCommands(); // @returns ApiCommandInterface[] +$sd->hasCommand($name); +$sd->getCommand($name); // @returns ApiCommandInterface|null +$sd->addCommand($command); // @param ApiCommandInterface $command +``` + +**After** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getOperations(); // @returns OperationInterface[] +$sd->hasOperation($name); +$sd->getOperation($name); // @returns OperationInterface|null +$sd->addOperation($operation); // @param OperationInterface $operation +``` + +### Guzzle\Common\Inflection\Inflector + +Namespace is now `Guzzle\Inflection\Inflector` + +### Guzzle\Http\Plugin + +Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below. + +### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log + +Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively. + +**Before** + +```php +use Guzzle\Common\Log\ClosureLogAdapter; +use Guzzle\Http\Plugin\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $verbosity is an integer indicating desired message verbosity level +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE); +``` + +**After** + +```php +use Guzzle\Log\ClosureLogAdapter; +use Guzzle\Log\MessageFormatter; +use Guzzle\Plugin\Log\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $format is a string indicating desired message format -- @see MessageFormatter +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT); +``` + +### Guzzle\Http\Plugin\CurlAuthPlugin + +Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`. + +### Guzzle\Http\Plugin\ExponentialBackoffPlugin + +Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes. + +**Before** + +```php +use Guzzle\Http\Plugin\ExponentialBackoffPlugin; + +$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge( + ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429) + )); + +$client->addSubscriber($backoffPlugin); +``` + +**After** + +```php +use Guzzle\Plugin\Backoff\BackoffPlugin; +use Guzzle\Plugin\Backoff\HttpBackoffStrategy; + +// Use convenient factory method instead -- see implementation for ideas of what +// you can do with chaining backoff strategies +$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge( + HttpBackoffStrategy::getDefaultFailureCodes(), array(429) + )); +$client->addSubscriber($backoffPlugin); +``` + +### Known Issues + +#### [BUG] Accept-Encoding header behavior changed unintentionally. + +(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e) + +In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to +properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen. +See issue #217 for a workaround, or use a version containing the fix. diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/composer.json b/data/web/inc/lib/vendor/guzzlehttp/guzzle/composer.json new file mode 100644 index 0000000000..f369ce67ee --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/composer.json @@ -0,0 +1,105 @@ +{ + "name": "guzzlehttp/guzzle", + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "framework", + "http", + "rest", + "web service", + "curl", + "client", + "HTTP client", + "PSR-7", + "PSR-18" + ], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": "^7.2.5 || ^8.0", + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.9 || ^2.4", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "bamarni/composer-bin-plugin": "^1.8.1", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "config": { + "allow-plugins": { + "bamarni/composer-bin-plugin": true + }, + "preferred-install": "dist", + "sort-packages": true + }, + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "7.5-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\": "tests/" + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/BodySummarizer.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/BodySummarizer.php new file mode 100644 index 0000000000..6eca94ef97 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/BodySummarizer.php @@ -0,0 +1,28 @@ +truncateAt = $truncateAt; + } + + /** + * Returns a summarized message body. + */ + public function summarize(MessageInterface $message): ?string + { + return $this->truncateAt === null + ? \GuzzleHttp\Psr7\Message::bodySummary($message) + : \GuzzleHttp\Psr7\Message::bodySummary($message, $this->truncateAt); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php new file mode 100644 index 0000000000..3e02e036e3 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php @@ -0,0 +1,13 @@ + 'http://www.foo.com/1.0/', + * 'timeout' => 0, + * 'allow_redirects' => false, + * 'proxy' => '192.168.16.1:10' + * ]); + * + * Client configuration settings include the following options: + * + * - handler: (callable) Function that transfers HTTP requests over the + * wire. The function is called with a Psr7\Http\Message\RequestInterface + * and array of transfer options, and must return a + * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a + * Psr7\Http\Message\ResponseInterface on success. + * If no handler is provided, a default handler will be created + * that enables all of the request options below by attaching all of the + * default middleware to the handler. + * - base_uri: (string|UriInterface) Base URI of the client that is merged + * into relative URIs. Can be a string or instance of UriInterface. + * - **: any request option + * + * @param array $config Client configuration settings. + * + * @see \GuzzleHttp\RequestOptions for a list of available request options. + */ + public function __construct(array $config = []) + { + if (!isset($config['handler'])) { + $config['handler'] = HandlerStack::create(); + } elseif (!\is_callable($config['handler'])) { + throw new InvalidArgumentException('handler must be a callable'); + } + + // Convert the base_uri to a UriInterface + if (isset($config['base_uri'])) { + $config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']); + } + + $this->configureDefaults($config); + } + + /** + * @param string $method + * @param array $args + * + * @return PromiseInterface|ResponseInterface + * + * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0. + */ + public function __call($method, $args) + { + if (\count($args) < 1) { + throw new InvalidArgumentException('Magic request methods require a URI and optional options array'); + } + + $uri = $args[0]; + $opts = $args[1] ?? []; + + return \substr($method, -5) === 'Async' + ? $this->requestAsync(\substr($method, 0, -5), $uri, $opts) + : $this->request($method, $uri, $opts); + } + + /** + * Asynchronously send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + */ + public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface + { + // Merge the base URI into the request URI if needed. + $options = $this->prepareDefaults($options); + + return $this->transfer( + $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), + $options + ); + } + + /** + * Send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + * + * @throws GuzzleException + */ + public function send(RequestInterface $request, array $options = []): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->sendAsync($request, $options)->wait(); + } + + /** + * The HttpClient PSR (PSR-18) specify this method. + * + * @inheritDoc + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + $options[RequestOptions::ALLOW_REDIRECTS] = false; + $options[RequestOptions::HTTP_ERRORS] = false; + + return $this->sendAsync($request, $options)->wait(); + } + + /** + * Create and send an asynchronous HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + */ + public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface + { + $options = $this->prepareDefaults($options); + // Remove request modifying parameter because it can be done up-front. + $headers = $options['headers'] ?? []; + $body = $options['body'] ?? null; + $version = $options['version'] ?? '1.1'; + // Merge the URI into the base URI. + $uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options); + if (\is_array($body)) { + throw $this->invalidBody(); + } + $request = new Psr7\Request($method, $uri, $headers, $body, $version); + // Remove the option so that they are not doubly-applied. + unset($options['headers'], $options['body'], $options['version']); + + return $this->transfer($request, $options); + } + + /** + * Create and send an HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string $method HTTP method. + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + * + * @throws GuzzleException + */ + public function request(string $method, $uri = '', array $options = []): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->requestAsync($method, $uri, $options)->wait(); + } + + /** + * Get a client configuration option. + * + * These options include default request options of the client, a "handler" + * (if utilized by the concrete client), and a "base_uri" if utilized by + * the concrete client. + * + * @param string|null $option The config option to retrieve. + * + * @return mixed + * + * @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0. + */ + public function getConfig(?string $option = null) + { + return $option === null + ? $this->config + : ($this->config[$option] ?? null); + } + + private function buildUri(UriInterface $uri, array $config): UriInterface + { + if (isset($config['base_uri'])) { + $uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri); + } + + if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) { + $idnOptions = ($config['idn_conversion'] === true) ? \IDNA_DEFAULT : $config['idn_conversion']; + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + + return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; + } + + /** + * Configures the default options for a client. + */ + private function configureDefaults(array $config): void + { + $defaults = [ + 'allow_redirects' => RedirectMiddleware::$defaultSettings, + 'http_errors' => true, + 'decode_content' => true, + 'verify' => true, + 'cookies' => false, + 'idn_conversion' => false, + ]; + + // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set. + + // We can only trust the HTTP_PROXY environment variable in a CLI + // process due to the fact that PHP has no reliable mechanism to + // get environment variables that start with "HTTP_". + if (\PHP_SAPI === 'cli' && ($proxy = Utils::getenv('HTTP_PROXY'))) { + $defaults['proxy']['http'] = $proxy; + } + + if ($proxy = Utils::getenv('HTTPS_PROXY')) { + $defaults['proxy']['https'] = $proxy; + } + + if ($noProxy = Utils::getenv('NO_PROXY')) { + $cleanedNoProxy = \str_replace(' ', '', $noProxy); + $defaults['proxy']['no'] = \explode(',', $cleanedNoProxy); + } + + $this->config = $config + $defaults; + + if (!empty($config['cookies']) && $config['cookies'] === true) { + $this->config['cookies'] = new CookieJar(); + } + + // Add the default user-agent header. + if (!isset($this->config['headers'])) { + $this->config['headers'] = ['User-Agent' => Utils::defaultUserAgent()]; + } else { + // Add the User-Agent header if one was not already set. + foreach (\array_keys($this->config['headers']) as $name) { + if (\strtolower($name) === 'user-agent') { + return; + } + } + $this->config['headers']['User-Agent'] = Utils::defaultUserAgent(); + } + } + + /** + * Merges default options into the array. + * + * @param array $options Options to modify by reference + */ + private function prepareDefaults(array $options): array + { + $defaults = $this->config; + + if (!empty($defaults['headers'])) { + // Default headers are only added if they are not present. + $defaults['_conditional'] = $defaults['headers']; + unset($defaults['headers']); + } + + // Special handling for headers is required as they are added as + // conditional headers and as headers passed to a request ctor. + if (\array_key_exists('headers', $options)) { + // Allows default headers to be unset. + if ($options['headers'] === null) { + $defaults['_conditional'] = []; + unset($options['headers']); + } elseif (!\is_array($options['headers'])) { + throw new InvalidArgumentException('headers must be an array'); + } + } + + // Shallow merge defaults underneath options. + $result = $options + $defaults; + + // Remove null values. + foreach ($result as $k => $v) { + if ($v === null) { + unset($result[$k]); + } + } + + return $result; + } + + /** + * Transfers the given request and applies request options. + * + * The URI of the request is not modified and the request options are used + * as-is without merging in default options. + * + * @param array $options See \GuzzleHttp\RequestOptions. + */ + private function transfer(RequestInterface $request, array $options): PromiseInterface + { + $request = $this->applyOptions($request, $options); + /** @var HandlerStack $handler */ + $handler = $options['handler']; + + try { + return P\Create::promiseFor($handler($request, $options)); + } catch (\Exception $e) { + return P\Create::rejectionFor($e); + } + } + + /** + * Applies the array of request options to a request. + */ + private function applyOptions(RequestInterface $request, array &$options): RequestInterface + { + $modify = [ + 'set_headers' => [], + ]; + + if (isset($options['headers'])) { + if (array_keys($options['headers']) === range(0, count($options['headers']) - 1)) { + throw new InvalidArgumentException('The headers array must have header name as keys.'); + } + $modify['set_headers'] = $options['headers']; + unset($options['headers']); + } + + if (isset($options['form_params'])) { + if (isset($options['multipart'])) { + throw new InvalidArgumentException('You cannot use ' + . 'form_params and multipart at the same time. Use the ' + . 'form_params option if you want to send application/' + . 'x-www-form-urlencoded requests, and the multipart ' + . 'option to send multipart/form-data requests.'); + } + $options['body'] = \http_build_query($options['form_params'], '', '&'); + unset($options['form_params']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + if (isset($options['multipart'])) { + $options['body'] = new Psr7\MultipartStream($options['multipart']); + unset($options['multipart']); + } + + if (isset($options['json'])) { + $options['body'] = Utils::jsonEncode($options['json']); + unset($options['json']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/json'; + } + + if (!empty($options['decode_content']) + && $options['decode_content'] !== true + ) { + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']); + $modify['set_headers']['Accept-Encoding'] = $options['decode_content']; + } + + if (isset($options['body'])) { + if (\is_array($options['body'])) { + throw $this->invalidBody(); + } + $modify['body'] = Psr7\Utils::streamFor($options['body']); + unset($options['body']); + } + + if (!empty($options['auth']) && \is_array($options['auth'])) { + $value = $options['auth']; + $type = isset($value[2]) ? \strtolower($value[2]) : 'basic'; + switch ($type) { + case 'basic': + // Ensure that we don't have the header in different case and set the new value. + $modify['set_headers'] = Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']); + $modify['set_headers']['Authorization'] = 'Basic ' + . \base64_encode("$value[0]:$value[1]"); + break; + case 'digest': + // @todo: Do not rely on curl + $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST; + $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + case 'ntlm': + $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM; + $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + } + } + + if (isset($options['query'])) { + $value = $options['query']; + if (\is_array($value)) { + $value = \http_build_query($value, '', '&', \PHP_QUERY_RFC3986); + } + if (!\is_string($value)) { + throw new InvalidArgumentException('query must be a string or array'); + } + $modify['query'] = $value; + unset($options['query']); + } + + // Ensure that sink is not an invalid value. + if (isset($options['sink'])) { + // TODO: Add more sink validation? + if (\is_bool($options['sink'])) { + throw new InvalidArgumentException('sink must not be a boolean'); + } + } + + $request = Psr7\Utils::modifyRequest($request, $modify); + if ($request->getBody() instanceof Psr7\MultipartStream) { + // Use a multipart/form-data POST if a Content-Type is not set. + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' + . $request->getBody()->getBoundary(); + } + + // Merge in conditional headers if they are not present. + if (isset($options['_conditional'])) { + // Build up the changes so it's in a single clone of the message. + $modify = []; + foreach ($options['_conditional'] as $k => $v) { + if (!$request->hasHeader($k)) { + $modify['set_headers'][$k] = $v; + } + } + $request = Psr7\Utils::modifyRequest($request, $modify); + // Don't pass this internal value along to middleware/handlers. + unset($options['_conditional']); + } + + return $request; + } + + /** + * Return an InvalidArgumentException with pre-set message. + */ + private function invalidBody(): InvalidArgumentException + { + return new InvalidArgumentException('Passing in the "body" request ' + . 'option as an array to send a request is not supported. ' + . 'Please use the "form_params" request option to send a ' + . 'application/x-www-form-urlencoded request, or the "multipart" ' + . 'request option to send a multipart/form-data request.'); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ClientInterface.php new file mode 100644 index 0000000000..6aaee61afc --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ClientInterface.php @@ -0,0 +1,84 @@ +request('GET', $uri, $options); + } + + /** + * Create and send an HTTP HEAD request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function head($uri, array $options = []): ResponseInterface + { + return $this->request('HEAD', $uri, $options); + } + + /** + * Create and send an HTTP PUT request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function put($uri, array $options = []): ResponseInterface + { + return $this->request('PUT', $uri, $options); + } + + /** + * Create and send an HTTP POST request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function post($uri, array $options = []): ResponseInterface + { + return $this->request('POST', $uri, $options); + } + + /** + * Create and send an HTTP PATCH request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function patch($uri, array $options = []): ResponseInterface + { + return $this->request('PATCH', $uri, $options); + } + + /** + * Create and send an HTTP DELETE request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function delete($uri, array $options = []): ResponseInterface + { + return $this->request('DELETE', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + abstract public function requestAsync(string $method, $uri, array $options = []): PromiseInterface; + + /** + * Create and send an asynchronous HTTP GET request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function getAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('GET', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP HEAD request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function headAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('HEAD', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP PUT request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function putAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('PUT', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP POST request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function postAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('POST', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP PATCH request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function patchAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('PATCH', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP DELETE request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function deleteAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('DELETE', $uri, $options); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php new file mode 100644 index 0000000000..9985a9814d --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php @@ -0,0 +1,317 @@ +strictMode = $strictMode; + + foreach ($cookieArray as $cookie) { + if (!($cookie instanceof SetCookie)) { + $cookie = new SetCookie($cookie); + } + $this->setCookie($cookie); + } + } + + /** + * Create a new Cookie jar from an associative array and domain. + * + * @param array $cookies Cookies to create the jar from + * @param string $domain Domain to set the cookies to + */ + public static function fromArray(array $cookies, string $domain): self + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie([ + 'Domain' => $domain, + 'Name' => $name, + 'Value' => $value, + 'Discard' => true + ])); + } + + return $cookieJar; + } + + /** + * Evaluate if this cookie should be persisted to storage + * that survives between requests. + * + * @param SetCookie $cookie Being evaluated. + * @param bool $allowSessionCookies If we should persist session cookies + */ + public static function shouldPersist(SetCookie $cookie, bool $allowSessionCookies = false): bool + { + if ($cookie->getExpires() || $allowSessionCookies) { + if (!$cookie->getDiscard()) { + return true; + } + } + + return false; + } + + /** + * Finds and returns the cookie based on the name + * + * @param string $name cookie name to search for + * + * @return SetCookie|null cookie that was found or null if not found + */ + public function getCookieByName(string $name): ?SetCookie + { + foreach ($this->cookies as $cookie) { + if ($cookie->getName() !== null && \strcasecmp($cookie->getName(), $name) === 0) { + return $cookie; + } + } + + return null; + } + + /** + * @inheritDoc + */ + public function toArray(): array + { + return \array_map(static function (SetCookie $cookie): array { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + + /** + * @inheritDoc + */ + public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void + { + if (!$domain) { + $this->cookies = []; + return; + } elseif (!$path) { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($domain): bool { + return !$cookie->matchesDomain($domain); + } + ); + } elseif (!$name) { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($path, $domain): bool { + return !($cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } else { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($path, $domain, $name) { + return !($cookie->getName() == $name && + $cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } + } + + /** + * @inheritDoc + */ + public function clearSessionCookies(): void + { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie): bool { + return !$cookie->getDiscard() && $cookie->getExpires(); + } + ); + } + + /** + * @inheritDoc + */ + public function setCookie(SetCookie $cookie): bool + { + // If the name string is empty (but not 0), ignore the set-cookie + // string entirely. + $name = $cookie->getName(); + if (!$name && $name !== '0') { + return false; + } + + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== true) { + if ($this->strictMode) { + throw new \RuntimeException('Invalid cookie: ' . $result); + } + $this->removeCookieIfEmpty($cookie); + return false; + } + + // Resolve conflicts with previously set cookies + foreach ($this->cookies as $i => $c) { + // Two cookies are identical, when their path, and domain are + // identical. + if ($c->getPath() != $cookie->getPath() || + $c->getDomain() != $cookie->getDomain() || + $c->getName() != $cookie->getName() + ) { + continue; + } + + // The previously set cookie is a discard cookie and this one is + // not so allow the new cookie to be set + if (!$cookie->getDiscard() && $c->getDiscard()) { + unset($this->cookies[$i]); + continue; + } + + // If the new cookie's expiration is further into the future, then + // replace the old cookie + if ($cookie->getExpires() > $c->getExpires()) { + unset($this->cookies[$i]); + continue; + } + + // If the value has changed, we better change it + if ($cookie->getValue() !== $c->getValue()) { + unset($this->cookies[$i]); + continue; + } + + // The cookie exists, so no need to continue + return false; + } + + $this->cookies[] = $cookie; + + return true; + } + + public function count(): int + { + return \count($this->cookies); + } + + /** + * @return \ArrayIterator + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator(\array_values($this->cookies)); + } + + public function extractCookies(RequestInterface $request, ResponseInterface $response): void + { + if ($cookieHeader = $response->getHeader('Set-Cookie')) { + foreach ($cookieHeader as $cookie) { + $sc = SetCookie::fromString($cookie); + if (!$sc->getDomain()) { + $sc->setDomain($request->getUri()->getHost()); + } + if (0 !== \strpos($sc->getPath(), '/')) { + $sc->setPath($this->getCookiePathFromRequest($request)); + } + if (!$sc->matchesDomain($request->getUri()->getHost())) { + continue; + } + // Note: At this point `$sc->getDomain()` being a public suffix should + // be rejected, but we don't want to pull in the full PSL dependency. + $this->setCookie($sc); + } + } + } + + /** + * Computes cookie path following RFC 6265 section 5.1.4 + * + * @link https://tools.ietf.org/html/rfc6265#section-5.1.4 + */ + private function getCookiePathFromRequest(RequestInterface $request): string + { + $uriPath = $request->getUri()->getPath(); + if ('' === $uriPath) { + return '/'; + } + if (0 !== \strpos($uriPath, '/')) { + return '/'; + } + if ('/' === $uriPath) { + return '/'; + } + $lastSlashPos = \strrpos($uriPath, '/'); + if (0 === $lastSlashPos || false === $lastSlashPos) { + return '/'; + } + + return \substr($uriPath, 0, $lastSlashPos); + } + + public function withCookieHeader(RequestInterface $request): RequestInterface + { + $values = []; + $uri = $request->getUri(); + $scheme = $uri->getScheme(); + $host = $uri->getHost(); + $path = $uri->getPath() ?: '/'; + + foreach ($this->cookies as $cookie) { + if ($cookie->matchesPath($path) && + $cookie->matchesDomain($host) && + !$cookie->isExpired() && + (!$cookie->getSecure() || $scheme === 'https') + ) { + $values[] = $cookie->getName() . '=' + . $cookie->getValue(); + } + } + + return $values + ? $request->withHeader('Cookie', \implode('; ', $values)) + : $request; + } + + /** + * If a cookie already exists and the server asks to set it again with a + * null value, the cookie must be deleted. + */ + private function removeCookieIfEmpty(SetCookie $cookie): void + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->clear( + $cookie->getDomain(), + $cookie->getPath(), + $cookie->getName() + ); + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php new file mode 100644 index 0000000000..7df374b5be --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -0,0 +1,79 @@ + + */ +interface CookieJarInterface extends \Countable, \IteratorAggregate +{ + /** + * Create a request with added cookie headers. + * + * If no matching cookies are found in the cookie jar, then no Cookie + * header is added to the request and the same request is returned. + * + * @param RequestInterface $request Request object to modify. + * + * @return RequestInterface returns the modified request. + */ + public function withCookieHeader(RequestInterface $request): RequestInterface; + + /** + * Extract cookies from an HTTP response and store them in the CookieJar. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface $response Response that was received + */ + public function extractCookies(RequestInterface $request, ResponseInterface $response): void; + + /** + * Sets a cookie in the cookie jar. + * + * @param SetCookie $cookie Cookie to set. + * + * @return bool Returns true on success or false on failure + */ + public function setCookie(SetCookie $cookie): bool; + + /** + * Remove cookies currently held in the cookie jar. + * + * Invoking this method without arguments will empty the whole cookie jar. + * If given a $domain argument only cookies belonging to that domain will + * be removed. If given a $domain and $path argument, cookies belonging to + * the specified path within that domain are removed. If given all three + * arguments, then the cookie with the specified name, path and domain is + * removed. + * + * @param string|null $domain Clears cookies matching a domain + * @param string|null $path Clears cookies matching a domain and path + * @param string|null $name Clears cookies matching a domain, path, and name + */ + public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void; + + /** + * Discard all sessions cookies. + * + * Removes cookies that don't have an expire field or a have a discard + * field set to true. To be called when the user agent shuts down according + * to RFC 2965. + */ + public function clearSessionCookies(): void; + + /** + * Converts the cookie jar to an array. + */ + public function toArray(): array; +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php new file mode 100644 index 0000000000..290236d543 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php @@ -0,0 +1,101 @@ +filename = $cookieFile; + $this->storeSessionCookies = $storeSessionCookies; + + if (\file_exists($cookieFile)) { + $this->load($cookieFile); + } + } + + /** + * Saves the file when shutting down + */ + public function __destruct() + { + $this->save($this->filename); + } + + /** + * Saves the cookies to a file. + * + * @param string $filename File to save + * + * @throws \RuntimeException if the file cannot be found or created + */ + public function save(string $filename): void + { + $json = []; + /** @var SetCookie $cookie */ + foreach ($this as $cookie) { + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $jsonStr = Utils::jsonEncode($json); + if (false === \file_put_contents($filename, $jsonStr, \LOCK_EX)) { + throw new \RuntimeException("Unable to save file {$filename}"); + } + } + + /** + * Load cookies from a JSON formatted file. + * + * Old cookies are kept unless overwritten by newly loaded ones. + * + * @param string $filename Cookie file to load. + * + * @throws \RuntimeException if the file cannot be loaded. + */ + public function load(string $filename): void + { + $json = \file_get_contents($filename); + if (false === $json) { + throw new \RuntimeException("Unable to load file {$filename}"); + } + if ($json === '') { + return; + } + + $data = Utils::jsonDecode($json, true); + if (\is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (\is_scalar($data) && !empty($data)) { + throw new \RuntimeException("Invalid cookie file: {$filename}"); + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php new file mode 100644 index 0000000000..5d51ca9820 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -0,0 +1,77 @@ +sessionKey = $sessionKey; + $this->storeSessionCookies = $storeSessionCookies; + $this->load(); + } + + /** + * Saves cookies to session when shutting down + */ + public function __destruct() + { + $this->save(); + } + + /** + * Save cookies to the client session + */ + public function save(): void + { + $json = []; + /** @var SetCookie $cookie */ + foreach ($this as $cookie) { + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $_SESSION[$this->sessionKey] = \json_encode($json); + } + + /** + * Load the contents of the client session into the data array + */ + protected function load(): void + { + if (!isset($_SESSION[$this->sessionKey])) { + return; + } + $data = \json_decode($_SESSION[$this->sessionKey], true); + if (\is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (\strlen($data)) { + throw new \RuntimeException("Invalid cookie data"); + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php new file mode 100644 index 0000000000..a613c77bf4 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -0,0 +1,446 @@ + null, + 'Value' => null, + 'Domain' => null, + 'Path' => '/', + 'Max-Age' => null, + 'Expires' => null, + 'Secure' => false, + 'Discard' => false, + 'HttpOnly' => false + ]; + + /** + * @var array Cookie data + */ + private $data; + + /** + * Create a new SetCookie object from a string. + * + * @param string $cookie Set-Cookie header string + */ + public static function fromString(string $cookie): self + { + // Create the default return array + $data = self::$defaults; + // Explode the cookie string using a series of semicolons + $pieces = \array_filter(\array_map('trim', \explode(';', $cookie))); + // The name of the cookie (first kvp) must exist and include an equal sign. + if (!isset($pieces[0]) || \strpos($pieces[0], '=') === false) { + return new self($data); + } + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + $cookieParts = \explode('=', $part, 2); + $key = \trim($cookieParts[0]); + $value = isset($cookieParts[1]) + ? \trim($cookieParts[1], " \n\r\t\0\x0B") + : true; + + // Only check for non-cookies when cookies have been found + if (!isset($data['Name'])) { + $data['Name'] = $key; + $data['Value'] = $value; + } else { + foreach (\array_keys(self::$defaults) as $search) { + if (!\strcasecmp($search, $key)) { + $data[$search] = $value; + continue 2; + } + } + $data[$key] = $value; + } + } + + return new self($data); + } + + /** + * @param array $data Array of cookie data provided by a Cookie parser + */ + public function __construct(array $data = []) + { + /** @var array|null $replaced will be null in case of replace error */ + $replaced = \array_replace(self::$defaults, $data); + if ($replaced === null) { + throw new \InvalidArgumentException('Unable to replace the default values for the Cookie.'); + } + + $this->data = $replaced; + // Extract the Expires value and turn it into a UNIX timestamp if needed + if (!$this->getExpires() && $this->getMaxAge()) { + // Calculate the Expires date + $this->setExpires(\time() + $this->getMaxAge()); + } elseif (null !== ($expires = $this->getExpires()) && !\is_numeric($expires)) { + $this->setExpires($expires); + } + } + + public function __toString() + { + $str = $this->data['Name'] . '=' . ($this->data['Value'] ?? '') . '; '; + foreach ($this->data as $k => $v) { + if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) { + if ($k === 'Expires') { + $str .= 'Expires=' . \gmdate('D, d M Y H:i:s \G\M\T', $v) . '; '; + } else { + $str .= ($v === true ? $k : "{$k}={$v}") . '; '; + } + } + } + + return \rtrim($str, '; '); + } + + public function toArray(): array + { + return $this->data; + } + + /** + * Get the cookie name. + * + * @return string + */ + public function getName() + { + return $this->data['Name']; + } + + /** + * Set the cookie name. + * + * @param string $name Cookie name + */ + public function setName($name): void + { + if (!is_string($name)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Name'] = (string) $name; + } + + /** + * Get the cookie value. + * + * @return string|null + */ + public function getValue() + { + return $this->data['Value']; + } + + /** + * Set the cookie value. + * + * @param string $value Cookie value + */ + public function setValue($value): void + { + if (!is_string($value)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Value'] = (string) $value; + } + + /** + * Get the domain. + * + * @return string|null + */ + public function getDomain() + { + return $this->data['Domain']; + } + + /** + * Set the domain of the cookie. + * + * @param string|null $domain + */ + public function setDomain($domain): void + { + if (!is_string($domain) && null !== $domain) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Domain'] = null === $domain ? null : (string) $domain; + } + + /** + * Get the path. + * + * @return string + */ + public function getPath() + { + return $this->data['Path']; + } + + /** + * Set the path of the cookie. + * + * @param string $path Path of the cookie + */ + public function setPath($path): void + { + if (!is_string($path)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Path'] = (string) $path; + } + + /** + * Maximum lifetime of the cookie in seconds. + * + * @return int|null + */ + public function getMaxAge() + { + return null === $this->data['Max-Age'] ? null : (int) $this->data['Max-Age']; + } + + /** + * Set the max-age of the cookie. + * + * @param int|null $maxAge Max age of the cookie in seconds + */ + public function setMaxAge($maxAge): void + { + if (!is_int($maxAge) && null !== $maxAge) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Max-Age'] = $maxAge === null ? null : (int) $maxAge; + } + + /** + * The UNIX timestamp when the cookie Expires. + * + * @return string|int|null + */ + public function getExpires() + { + return $this->data['Expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire. + * + * @param int|string|null $timestamp Unix timestamp or any English textual datetime description. + */ + public function setExpires($timestamp): void + { + if (!is_int($timestamp) && !is_string($timestamp) && null !== $timestamp) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int, string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Expires'] = null === $timestamp ? null : (\is_numeric($timestamp) ? (int) $timestamp : \strtotime((string) $timestamp)); + } + + /** + * Get whether or not this is a secure cookie. + * + * @return bool + */ + public function getSecure() + { + return $this->data['Secure']; + } + + /** + * Set whether or not the cookie is secure. + * + * @param bool $secure Set to true or false if secure + */ + public function setSecure($secure): void + { + if (!is_bool($secure)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Secure'] = (bool) $secure; + } + + /** + * Get whether or not this is a session cookie. + * + * @return bool|null + */ + public function getDiscard() + { + return $this->data['Discard']; + } + + /** + * Set whether or not this is a session cookie. + * + * @param bool $discard Set to true or false if this is a session cookie + */ + public function setDiscard($discard): void + { + if (!is_bool($discard)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Discard'] = (bool) $discard; + } + + /** + * Get whether or not this is an HTTP only cookie. + * + * @return bool + */ + public function getHttpOnly() + { + return $this->data['HttpOnly']; + } + + /** + * Set whether or not this is an HTTP only cookie. + * + * @param bool $httpOnly Set to true or false if this is HTTP only + */ + public function setHttpOnly($httpOnly): void + { + if (!is_bool($httpOnly)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['HttpOnly'] = (bool) $httpOnly; + } + + /** + * Check if the cookie matches a path value. + * + * A request-path path-matches a given cookie-path if at least one of + * the following conditions holds: + * + * - The cookie-path and the request-path are identical. + * - The cookie-path is a prefix of the request-path, and the last + * character of the cookie-path is %x2F ("/"). + * - The cookie-path is a prefix of the request-path, and the first + * character of the request-path that is not included in the cookie- + * path is a %x2F ("/") character. + * + * @param string $requestPath Path to check against + */ + public function matchesPath(string $requestPath): bool + { + $cookiePath = $this->getPath(); + + // Match on exact matches or when path is the default empty "/" + if ($cookiePath === '/' || $cookiePath == $requestPath) { + return true; + } + + // Ensure that the cookie-path is a prefix of the request path. + if (0 !== \strpos($requestPath, $cookiePath)) { + return false; + } + + // Match if the last character of the cookie-path is "/" + if (\substr($cookiePath, -1, 1) === '/') { + return true; + } + + // Match if the first character not included in cookie path is "/" + return \substr($requestPath, \strlen($cookiePath), 1) === '/'; + } + + /** + * Check if the cookie matches a domain value. + * + * @param string $domain Domain to check against + */ + public function matchesDomain(string $domain): bool + { + $cookieDomain = $this->getDomain(); + if (null === $cookieDomain) { + return true; + } + + // Remove the leading '.' as per spec in RFC 6265. + // https://tools.ietf.org/html/rfc6265#section-5.2.3 + $cookieDomain = \ltrim(\strtolower($cookieDomain), '.'); + + $domain = \strtolower($domain); + + // Domain not set or exact match. + if ('' === $cookieDomain || $domain === $cookieDomain) { + return true; + } + + // Matching the subdomain according to RFC 6265. + // https://tools.ietf.org/html/rfc6265#section-5.1.3 + if (\filter_var($domain, \FILTER_VALIDATE_IP)) { + return false; + } + + return (bool) \preg_match('/\.' . \preg_quote($cookieDomain, '/') . '$/', $domain); + } + + /** + * Check if the cookie is expired. + */ + public function isExpired(): bool + { + return $this->getExpires() !== null && \time() > $this->getExpires(); + } + + /** + * Check if the cookie is valid according to RFC 6265. + * + * @return bool|string Returns true if valid or an error message if invalid + */ + public function validate() + { + $name = $this->getName(); + if ($name === '') { + return 'The cookie name must not be empty'; + } + + // Check if any of the invalid characters are present in the cookie name + if (\preg_match( + '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', + $name + )) { + return 'Cookie name must not contain invalid characters: ASCII ' + . 'Control characters (0-31;127), space, tab and the ' + . 'following characters: ()<>@,;:\"/?={}'; + } + + // Value must not be null. 0 and empty string are valid. Empty strings + // are technically against RFC 6265, but known to happen in the wild. + $value = $this->getValue(); + if ($value === null) { + return 'The cookie value must not be empty'; + } + + // Domains must not be empty, but can be 0. "0" is not a valid internet + // domain, but may be used as server name in a private network. + $domain = $this->getDomain(); + if ($domain === null || $domain === '') { + return 'The cookie domain must not be empty'; + } + + return true; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php new file mode 100644 index 0000000000..a80956c9d2 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php @@ -0,0 +1,39 @@ +request = $request; + $this->handlerContext = $handlerContext; + } + + /** + * Get the request that caused the exception + */ + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Get contextual information about the error from the underlying handler. + * + * The contents of this array will vary depending on which handler you are + * using. It may also be just an empty array. Relying on this data will + * couple you to a specific handler, but can give more debug information + * when needed. + */ + public function getHandlerContext(): array + { + return $this->handlerContext; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php new file mode 100644 index 0000000000..fa3ed6998c --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php @@ -0,0 +1,9 @@ +getStatusCode() : 0; + parent::__construct($message, $code, $previous); + $this->request = $request; + $this->response = $response; + $this->handlerContext = $handlerContext; + } + + /** + * Wrap non-RequestExceptions with a RequestException + */ + public static function wrapException(RequestInterface $request, \Throwable $e): RequestException + { + return $e instanceof RequestException ? $e : new RequestException($e->getMessage(), $request, null, $e); + } + + /** + * Factory method to create a new exception with a normalized error message + * + * @param RequestInterface $request Request sent + * @param ResponseInterface $response Response received + * @param \Throwable|null $previous Previous exception + * @param array $handlerContext Optional handler context + * @param BodySummarizerInterface|null $bodySummarizer Optional body summarizer + */ + public static function create( + RequestInterface $request, + ResponseInterface $response = null, + \Throwable $previous = null, + array $handlerContext = [], + BodySummarizerInterface $bodySummarizer = null + ): self { + if (!$response) { + return new self( + 'Error completing request', + $request, + null, + $previous, + $handlerContext + ); + } + + $level = (int) \floor($response->getStatusCode() / 100); + if ($level === 4) { + $label = 'Client error'; + $className = ClientException::class; + } elseif ($level === 5) { + $label = 'Server error'; + $className = ServerException::class; + } else { + $label = 'Unsuccessful request'; + $className = __CLASS__; + } + + $uri = $request->getUri(); + $uri = static::obfuscateUri($uri); + + // Client Error: `GET /` resulted in a `404 Not Found` response: + // ... (truncated) + $message = \sprintf( + '%s: `%s %s` resulted in a `%s %s` response', + $label, + $request->getMethod(), + $uri->__toString(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + + $summary = ($bodySummarizer ?? new BodySummarizer())->summarize($response); + + if ($summary !== null) { + $message .= ":\n{$summary}\n"; + } + + return new $className($message, $request, $response, $previous, $handlerContext); + } + + /** + * Obfuscates URI if there is a username and a password present + */ + private static function obfuscateUri(UriInterface $uri): UriInterface + { + $userInfo = $uri->getUserInfo(); + + if (false !== ($pos = \strpos($userInfo, ':'))) { + return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***'); + } + + return $uri; + } + + /** + * Get the request that caused the exception + */ + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Get the associated response + */ + public function getResponse(): ?ResponseInterface + { + return $this->response; + } + + /** + * Check if a response was received + */ + public function hasResponse(): bool + { + return $this->response !== null; + } + + /** + * Get contextual information about the error from the underlying handler. + * + * The contents of this array will vary depending on which handler you are + * using. It may also be just an empty array. Relying on this data will + * couple you to a specific handler, but can give more debug information + * when needed. + */ + public function getHandlerContext(): array + { + return $this->handlerContext; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php new file mode 100644 index 0000000000..8055e067c0 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php @@ -0,0 +1,10 @@ +maxHandles = $maxHandles; + } + + public function create(RequestInterface $request, array $options): EasyHandle + { + if (isset($options['curl']['body_as_string'])) { + $options['_body_as_string'] = $options['curl']['body_as_string']; + unset($options['curl']['body_as_string']); + } + + $easy = new EasyHandle; + $easy->request = $request; + $easy->options = $options; + $conf = $this->getDefaultConf($easy); + $this->applyMethod($easy, $conf); + $this->applyHandlerOptions($easy, $conf); + $this->applyHeaders($easy, $conf); + unset($conf['_headers']); + + // Add handler options from the request configuration options + if (isset($options['curl'])) { + $conf = \array_replace($conf, $options['curl']); + } + + $conf[\CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy); + $easy->handle = $this->handles ? \array_pop($this->handles) : \curl_init(); + curl_setopt_array($easy->handle, $conf); + + return $easy; + } + + public function release(EasyHandle $easy): void + { + $resource = $easy->handle; + unset($easy->handle); + + if (\count($this->handles) >= $this->maxHandles) { + \curl_close($resource); + } else { + // Remove all callback functions as they can hold onto references + // and are not cleaned up by curl_reset. Using curl_setopt_array + // does not work for some reason, so removing each one + // individually. + \curl_setopt($resource, \CURLOPT_HEADERFUNCTION, null); + \curl_setopt($resource, \CURLOPT_READFUNCTION, null); + \curl_setopt($resource, \CURLOPT_WRITEFUNCTION, null); + \curl_setopt($resource, \CURLOPT_PROGRESSFUNCTION, null); + \curl_reset($resource); + $this->handles[] = $resource; + } + } + + /** + * Completes a cURL transaction, either returning a response promise or a + * rejected promise. + * + * @param callable(RequestInterface, array): PromiseInterface $handler + * @param CurlFactoryInterface $factory Dictates how the handle is released + */ + public static function finish(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface + { + if (isset($easy->options['on_stats'])) { + self::invokeStats($easy); + } + + if (!$easy->response || $easy->errno) { + return self::finishError($handler, $easy, $factory); + } + + // Return the response if it is present and there is no error. + $factory->release($easy); + + // Rewind the body of the response if possible. + $body = $easy->response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + + return new FulfilledPromise($easy->response); + } + + private static function invokeStats(EasyHandle $easy): void + { + $curlStats = \curl_getinfo($easy->handle); + $curlStats['appconnect_time'] = \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME); + $stats = new TransferStats( + $easy->request, + $easy->response, + $curlStats['total_time'], + $easy->errno, + $curlStats + ); + ($easy->options['on_stats'])($stats); + } + + /** + * @param callable(RequestInterface, array): PromiseInterface $handler + */ + private static function finishError(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface + { + // Get error information and release the handle to the factory. + $ctx = [ + 'errno' => $easy->errno, + 'error' => \curl_error($easy->handle), + 'appconnect_time' => \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME), + ] + \curl_getinfo($easy->handle); + $ctx[self::CURL_VERSION_STR] = \curl_version()['version']; + $factory->release($easy); + + // Retry when nothing is present or when curl failed to rewind. + if (empty($easy->options['_err_message']) && (!$easy->errno || $easy->errno == 65)) { + return self::retryFailedRewind($handler, $easy, $ctx); + } + + return self::createRejection($easy, $ctx); + } + + private static function createRejection(EasyHandle $easy, array $ctx): PromiseInterface + { + static $connectionErrors = [ + \CURLE_OPERATION_TIMEOUTED => true, + \CURLE_COULDNT_RESOLVE_HOST => true, + \CURLE_COULDNT_CONNECT => true, + \CURLE_SSL_CONNECT_ERROR => true, + \CURLE_GOT_NOTHING => true, + ]; + + if ($easy->createResponseException) { + return P\Create::rejectionFor( + new RequestException( + 'An error was encountered while creating the response', + $easy->request, + $easy->response, + $easy->createResponseException, + $ctx + ) + ); + } + + // If an exception was encountered during the onHeaders event, then + // return a rejected promise that wraps that exception. + if ($easy->onHeadersException) { + return P\Create::rejectionFor( + new RequestException( + 'An error was encountered during the on_headers event', + $easy->request, + $easy->response, + $easy->onHeadersException, + $ctx + ) + ); + } + + $message = \sprintf( + 'cURL error %s: %s (%s)', + $ctx['errno'], + $ctx['error'], + 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html' + ); + $uriString = (string) $easy->request->getUri(); + if ($uriString !== '' && false === \strpos($ctx['error'], $uriString)) { + $message .= \sprintf(' for %s', $uriString); + } + + // Create a connection exception if it was a specific error code. + $error = isset($connectionErrors[$easy->errno]) + ? new ConnectException($message, $easy->request, null, $ctx) + : new RequestException($message, $easy->request, $easy->response, null, $ctx); + + return P\Create::rejectionFor($error); + } + + /** + * @return array + */ + private function getDefaultConf(EasyHandle $easy): array + { + $conf = [ + '_headers' => $easy->request->getHeaders(), + \CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), + \CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), + \CURLOPT_RETURNTRANSFER => false, + \CURLOPT_HEADER => false, + \CURLOPT_CONNECTTIMEOUT => 150, + ]; + + if (\defined('CURLOPT_PROTOCOLS')) { + $conf[\CURLOPT_PROTOCOLS] = \CURLPROTO_HTTP | \CURLPROTO_HTTPS; + } + + $version = $easy->request->getProtocolVersion(); + if ($version == 1.1) { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1; + } elseif ($version == 2.0) { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0; + } else { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0; + } + + return $conf; + } + + private function applyMethod(EasyHandle $easy, array &$conf): void + { + $body = $easy->request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size > 0) { + $this->applyBody($easy->request, $easy->options, $conf); + return; + } + + $method = $easy->request->getMethod(); + if ($method === 'PUT' || $method === 'POST') { + // See https://tools.ietf.org/html/rfc7230#section-3.3.2 + if (!$easy->request->hasHeader('Content-Length')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; + } + } elseif ($method === 'HEAD') { + $conf[\CURLOPT_NOBODY] = true; + unset( + $conf[\CURLOPT_WRITEFUNCTION], + $conf[\CURLOPT_READFUNCTION], + $conf[\CURLOPT_FILE], + $conf[\CURLOPT_INFILE] + ); + } + } + + private function applyBody(RequestInterface $request, array $options, array &$conf): void + { + $size = $request->hasHeader('Content-Length') + ? (int) $request->getHeaderLine('Content-Length') + : null; + + // Send the body as a string if the size is less than 1MB OR if the + // [curl][body_as_string] request value is set. + if (($size !== null && $size < 1000000) || !empty($options['_body_as_string'])) { + $conf[\CURLOPT_POSTFIELDS] = (string) $request->getBody(); + // Don't duplicate the Content-Length header + $this->removeHeader('Content-Length', $conf); + $this->removeHeader('Transfer-Encoding', $conf); + } else { + $conf[\CURLOPT_UPLOAD] = true; + if ($size !== null) { + $conf[\CURLOPT_INFILESIZE] = $size; + $this->removeHeader('Content-Length', $conf); + } + $body = $request->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + $conf[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body) { + return $body->read($length); + }; + } + + // If the Expect header is not present, prevent curl from adding it + if (!$request->hasHeader('Expect')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Expect:'; + } + + // cURL sometimes adds a content-type by default. Prevent this. + if (!$request->hasHeader('Content-Type')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + } + + private function applyHeaders(EasyHandle $easy, array &$conf): void + { + foreach ($conf['_headers'] as $name => $values) { + foreach ($values as $value) { + $value = (string) $value; + if ($value === '') { + // cURL requires a special format for empty headers. + // See https://github.com/guzzle/guzzle/issues/1882 for more details. + $conf[\CURLOPT_HTTPHEADER][] = "$name;"; + } else { + $conf[\CURLOPT_HTTPHEADER][] = "$name: $value"; + } + } + } + + // Remove the Accept header if one was not set + if (!$easy->request->hasHeader('Accept')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Accept:'; + } + } + + /** + * Remove a header from the options array. + * + * @param string $name Case-insensitive header to remove + * @param array $options Array of options to modify + */ + private function removeHeader(string $name, array &$options): void + { + foreach (\array_keys($options['_headers']) as $key) { + if (!\strcasecmp($key, $name)) { + unset($options['_headers'][$key]); + return; + } + } + } + + private function applyHandlerOptions(EasyHandle $easy, array &$conf): void + { + $options = $easy->options; + if (isset($options['verify'])) { + if ($options['verify'] === false) { + unset($conf[\CURLOPT_CAINFO]); + $conf[\CURLOPT_SSL_VERIFYHOST] = 0; + $conf[\CURLOPT_SSL_VERIFYPEER] = false; + } else { + $conf[\CURLOPT_SSL_VERIFYHOST] = 2; + $conf[\CURLOPT_SSL_VERIFYPEER] = true; + if (\is_string($options['verify'])) { + // Throw an error if the file/folder/link path is not valid or doesn't exist. + if (!\file_exists($options['verify'])) { + throw new \InvalidArgumentException("SSL CA bundle not found: {$options['verify']}"); + } + // If it's a directory or a link to a directory use CURLOPT_CAPATH. + // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO. + if ( + \is_dir($options['verify']) || + ( + \is_link($options['verify']) === true && + ($verifyLink = \readlink($options['verify'])) !== false && + \is_dir($verifyLink) + ) + ) { + $conf[\CURLOPT_CAPATH] = $options['verify']; + } else { + $conf[\CURLOPT_CAINFO] = $options['verify']; + } + } + } + } + + if (!isset($options['curl'][\CURLOPT_ENCODING]) && !empty($options['decode_content'])) { + $accept = $easy->request->getHeaderLine('Accept-Encoding'); + if ($accept) { + $conf[\CURLOPT_ENCODING] = $accept; + } else { + // The empty string enables all available decoders and implicitly + // sets a matching 'Accept-Encoding' header. + $conf[\CURLOPT_ENCODING] = ''; + // But as the user did not specify any acceptable encodings we need + // to overwrite this implicit header with an empty one. + $conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; + } + } + + if (!isset($options['sink'])) { + // Use a default temp stream if no sink was set. + $options['sink'] = \GuzzleHttp\Psr7\Utils::tryFopen('php://temp', 'w+'); + } + $sink = $options['sink']; + if (!\is_string($sink)) { + $sink = \GuzzleHttp\Psr7\Utils::streamFor($sink); + } elseif (!\is_dir(\dirname($sink))) { + // Ensure that the directory exists before failing in curl. + throw new \RuntimeException(\sprintf('Directory %s does not exist for sink value of %s', \dirname($sink), $sink)); + } else { + $sink = new LazyOpenStream($sink, 'w+'); + } + $easy->sink = $sink; + $conf[\CURLOPT_WRITEFUNCTION] = static function ($ch, $write) use ($sink): int { + return $sink->write($write); + }; + + $timeoutRequiresNoSignal = false; + if (isset($options['timeout'])) { + $timeoutRequiresNoSignal |= $options['timeout'] < 1; + $conf[\CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000; + } + + // CURL default value is CURL_IPRESOLVE_WHATEVER + if (isset($options['force_ip_resolve'])) { + if ('v4' === $options['force_ip_resolve']) { + $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V4; + } elseif ('v6' === $options['force_ip_resolve']) { + $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V6; + } + } + + if (isset($options['connect_timeout'])) { + $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1; + $conf[\CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000; + } + + if ($timeoutRequiresNoSignal && \strtoupper(\substr(\PHP_OS, 0, 3)) !== 'WIN') { + $conf[\CURLOPT_NOSIGNAL] = true; + } + + if (isset($options['proxy'])) { + if (!\is_array($options['proxy'])) { + $conf[\CURLOPT_PROXY] = $options['proxy']; + } else { + $scheme = $easy->request->getUri()->getScheme(); + if (isset($options['proxy'][$scheme])) { + $host = $easy->request->getUri()->getHost(); + if (!isset($options['proxy']['no']) || !Utils::isHostInNoProxy($host, $options['proxy']['no'])) { + $conf[\CURLOPT_PROXY] = $options['proxy'][$scheme]; + } + } + } + } + + if (isset($options['cert'])) { + $cert = $options['cert']; + if (\is_array($cert)) { + $conf[\CURLOPT_SSLCERTPASSWD] = $cert[1]; + $cert = $cert[0]; + } + if (!\file_exists($cert)) { + throw new \InvalidArgumentException("SSL certificate not found: {$cert}"); + } + # OpenSSL (versions 0.9.3 and later) also support "P12" for PKCS#12-encoded files. + # see https://curl.se/libcurl/c/CURLOPT_SSLCERTTYPE.html + $ext = pathinfo($cert, \PATHINFO_EXTENSION); + if (preg_match('#^(der|p12)$#i', $ext)) { + $conf[\CURLOPT_SSLCERTTYPE] = strtoupper($ext); + } + $conf[\CURLOPT_SSLCERT] = $cert; + } + + if (isset($options['ssl_key'])) { + if (\is_array($options['ssl_key'])) { + if (\count($options['ssl_key']) === 2) { + [$sslKey, $conf[\CURLOPT_SSLKEYPASSWD]] = $options['ssl_key']; + } else { + [$sslKey] = $options['ssl_key']; + } + } + + $sslKey = $sslKey ?? $options['ssl_key']; + + if (!\file_exists($sslKey)) { + throw new \InvalidArgumentException("SSL private key not found: {$sslKey}"); + } + $conf[\CURLOPT_SSLKEY] = $sslKey; + } + + if (isset($options['progress'])) { + $progress = $options['progress']; + if (!\is_callable($progress)) { + throw new \InvalidArgumentException('progress client option must be callable'); + } + $conf[\CURLOPT_NOPROGRESS] = false; + $conf[\CURLOPT_PROGRESSFUNCTION] = static function ($resource, int $downloadSize, int $downloaded, int $uploadSize, int $uploaded) use ($progress) { + $progress($downloadSize, $downloaded, $uploadSize, $uploaded); + }; + } + + if (!empty($options['debug'])) { + $conf[\CURLOPT_STDERR] = Utils::debugResource($options['debug']); + $conf[\CURLOPT_VERBOSE] = true; + } + } + + /** + * This function ensures that a response was set on a transaction. If one + * was not set, then the request is retried if possible. This error + * typically means you are sending a payload, curl encountered a + * "Connection died, retrying a fresh connect" error, tried to rewind the + * stream, and then encountered a "necessary data rewind wasn't possible" + * error, causing the request to be sent through curl_multi_info_read() + * without an error status. + * + * @param callable(RequestInterface, array): PromiseInterface $handler + */ + private static function retryFailedRewind(callable $handler, EasyHandle $easy, array $ctx): PromiseInterface + { + try { + // Only rewind if the body has been read from. + $body = $easy->request->getBody(); + if ($body->tell() > 0) { + $body->rewind(); + } + } catch (\RuntimeException $e) { + $ctx['error'] = 'The connection unexpectedly failed without ' + . 'providing an error. The request would have been retried, ' + . 'but attempting to rewind the request body failed. ' + . 'Exception: ' . $e; + return self::createRejection($easy, $ctx); + } + + // Retry no more than 3 times before giving up. + if (!isset($easy->options['_curl_retries'])) { + $easy->options['_curl_retries'] = 1; + } elseif ($easy->options['_curl_retries'] == 2) { + $ctx['error'] = 'The cURL request was retried 3 times ' + . 'and did not succeed. The most likely reason for the failure ' + . 'is that cURL was unable to rewind the body of the request ' + . 'and subsequent retries resulted in the same error. Turn on ' + . 'the debug option to see what went wrong. See ' + . 'https://bugs.php.net/bug.php?id=47204 for more information.'; + return self::createRejection($easy, $ctx); + } else { + $easy->options['_curl_retries']++; + } + + return $handler($easy->request, $easy->options); + } + + private function createHeaderFn(EasyHandle $easy): callable + { + if (isset($easy->options['on_headers'])) { + $onHeaders = $easy->options['on_headers']; + + if (!\is_callable($onHeaders)) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + } else { + $onHeaders = null; + } + + return static function ($ch, $h) use ( + $onHeaders, + $easy, + &$startingResponse + ) { + $value = \trim($h); + if ($value === '') { + $startingResponse = true; + try { + $easy->createResponse(); + } catch (\Exception $e) { + $easy->createResponseException = $e; + return -1; + } + if ($onHeaders !== null) { + try { + $onHeaders($easy->response); + } catch (\Exception $e) { + // Associate the exception with the handle and trigger + // a curl header write error by returning 0. + $easy->onHeadersException = $e; + return -1; + } + } + } elseif ($startingResponse) { + $startingResponse = false; + $easy->headers = [$value]; + } else { + $easy->headers[] = $value; + } + return \strlen($h); + }; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php new file mode 100644 index 0000000000..fe57ed5d57 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php @@ -0,0 +1,25 @@ +factory = $options['handle_factory'] + ?? new CurlFactory(3); + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (isset($options['delay'])) { + \usleep($options['delay'] * 1000); + } + + $easy = $this->factory->create($request, $options); + \curl_exec($easy->handle); + $easy->errno = \curl_errno($easy->handle); + + return CurlFactory::finish($this, $easy, $this->factory); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php new file mode 100644 index 0000000000..4356d02473 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -0,0 +1,262 @@ + An array of delay times, indexed by handle id in `addRequest`. + * + * @see CurlMultiHandler::addRequest + */ + private $delays = []; + + /** + * @var array An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt() + */ + private $options = []; + + /** + * This handler accepts the following options: + * + * - handle_factory: An optional factory used to create curl handles + * - select_timeout: Optional timeout (in seconds) to block before timing + * out while selecting curl handles. Defaults to 1 second. + * - options: An associative array of CURLMOPT_* options and + * corresponding values for curl_multi_setopt() + */ + public function __construct(array $options = []) + { + $this->factory = $options['handle_factory'] ?? new CurlFactory(50); + + if (isset($options['select_timeout'])) { + $this->selectTimeout = $options['select_timeout']; + } elseif ($selectTimeout = Utils::getenv('GUZZLE_CURL_SELECT_TIMEOUT')) { + @trigger_error('Since guzzlehttp/guzzle 7.2.0: Using environment variable GUZZLE_CURL_SELECT_TIMEOUT is deprecated. Use option "select_timeout" instead.', \E_USER_DEPRECATED); + $this->selectTimeout = (int) $selectTimeout; + } else { + $this->selectTimeout = 1; + } + + $this->options = $options['options'] ?? []; + } + + /** + * @param string $name + * + * @return resource|\CurlMultiHandle + * + * @throws \BadMethodCallException when another field as `_mh` will be gotten + * @throws \RuntimeException when curl can not initialize a multi handle + */ + public function __get($name) + { + if ($name !== '_mh') { + throw new \BadMethodCallException("Can not get other property as '_mh'."); + } + + $multiHandle = \curl_multi_init(); + + if (false === $multiHandle) { + throw new \RuntimeException('Can not initialize curl multi handle.'); + } + + $this->_mh = $multiHandle; + + foreach ($this->options as $option => $value) { + // A warning is raised in case of a wrong option. + curl_multi_setopt($this->_mh, $option, $value); + } + + return $this->_mh; + } + + public function __destruct() + { + if (isset($this->_mh)) { + \curl_multi_close($this->_mh); + unset($this->_mh); + } + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $easy = $this->factory->create($request, $options); + $id = (int) $easy->handle; + + $promise = new Promise( + [$this, 'execute'], + function () use ($id) { + return $this->cancel($id); + } + ); + + $this->addRequest(['easy' => $easy, 'deferred' => $promise]); + + return $promise; + } + + /** + * Ticks the curl event loop. + */ + public function tick(): void + { + // Add any delayed handles if needed. + if ($this->delays) { + $currentTime = Utils::currentTime(); + foreach ($this->delays as $id => $delay) { + if ($currentTime >= $delay) { + unset($this->delays[$id]); + \curl_multi_add_handle( + $this->_mh, + $this->handles[$id]['easy']->handle + ); + } + } + } + + // Step through the task queue which may add additional requests. + P\Utils::queue()->run(); + + if ($this->active && \curl_multi_select($this->_mh, $this->selectTimeout) === -1) { + // Perform a usleep if a select returns -1. + // See: https://bugs.php.net/bug.php?id=61141 + \usleep(250); + } + + while (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM); + + $this->processMessages(); + } + + /** + * Runs until all outstanding connections have completed. + */ + public function execute(): void + { + $queue = P\Utils::queue(); + + while ($this->handles || !$queue->isEmpty()) { + // If there are no transfers, then sleep for the next delay + if (!$this->active && $this->delays) { + \usleep($this->timeToNext()); + } + $this->tick(); + } + } + + private function addRequest(array $entry): void + { + $easy = $entry['easy']; + $id = (int) $easy->handle; + $this->handles[$id] = $entry; + if (empty($easy->options['delay'])) { + \curl_multi_add_handle($this->_mh, $easy->handle); + } else { + $this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000); + } + } + + /** + * Cancels a handle from sending and removes references to it. + * + * @param int $id Handle ID to cancel and remove. + * + * @return bool True on success, false on failure. + */ + private function cancel($id): bool + { + if (!is_int($id)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an integer to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + // Cannot cancel if it has been processed. + if (!isset($this->handles[$id])) { + return false; + } + + $handle = $this->handles[$id]['easy']->handle; + unset($this->delays[$id], $this->handles[$id]); + \curl_multi_remove_handle($this->_mh, $handle); + \curl_close($handle); + + return true; + } + + private function processMessages(): void + { + while ($done = \curl_multi_info_read($this->_mh)) { + if ($done['msg'] !== \CURLMSG_DONE) { + // if it's not done, then it would be premature to remove the handle. ref https://github.com/guzzle/guzzle/pull/2892#issuecomment-945150216 + continue; + } + $id = (int) $done['handle']; + \curl_multi_remove_handle($this->_mh, $done['handle']); + + if (!isset($this->handles[$id])) { + // Probably was cancelled. + continue; + } + + $entry = $this->handles[$id]; + unset($this->handles[$id], $this->delays[$id]); + $entry['easy']->errno = $done['result']; + $entry['deferred']->resolve( + CurlFactory::finish($this, $entry['easy'], $this->factory) + ); + } + } + + private function timeToNext(): int + { + $currentTime = Utils::currentTime(); + $nextTime = \PHP_INT_MAX; + foreach ($this->delays as $time) { + if ($time < $nextTime) { + $nextTime = $time; + } + } + + return ((int) \max(0, $nextTime - $currentTime)) * 1000000; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php new file mode 100644 index 0000000000..224344d7c8 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php @@ -0,0 +1,112 @@ +headers); + + $normalizedKeys = Utils::normalizeHeaderKeys($headers); + + if (!empty($this->options['decode_content']) && isset($normalizedKeys['content-encoding'])) { + $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; + unset($headers[$normalizedKeys['content-encoding']]); + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; + + $bodyLength = (int) $this->sink->getSize(); + if ($bodyLength) { + $headers[$normalizedKeys['content-length']] = $bodyLength; + } else { + unset($headers[$normalizedKeys['content-length']]); + } + } + } + + // Attach a response to the easy handle with the parsed headers. + $this->response = new Response( + $status, + $headers, + $this->sink, + $ver, + $reason + ); + } + + /** + * @param string $name + * + * @return void + * + * @throws \BadMethodCallException + */ + public function __get($name) + { + $msg = $name === 'handle' ? 'The EasyHandle has been released' : 'Invalid property: ' . $name; + throw new \BadMethodCallException($msg); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php new file mode 100644 index 0000000000..a0988845fd --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php @@ -0,0 +1,42 @@ +|null $queue The parameters to be passed to the append function, as an indexed array. + * @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled. + * @param callable|null $onRejected Callback to invoke when the return value is rejected. + */ + public function __construct(array $queue = null, callable $onFulfilled = null, callable $onRejected = null) + { + $this->onFulfilled = $onFulfilled; + $this->onRejected = $onRejected; + + if ($queue) { + // array_values included for BC + $this->append(...array_values($queue)); + } + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (!$this->queue) { + throw new \OutOfBoundsException('Mock queue is empty'); + } + + if (isset($options['delay']) && \is_numeric($options['delay'])) { + \usleep((int) $options['delay'] * 1000); + } + + $this->lastRequest = $request; + $this->lastOptions = $options; + $response = \array_shift($this->queue); + + if (isset($options['on_headers'])) { + if (!\is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + try { + $options['on_headers']($response); + } catch (\Exception $e) { + $msg = 'An error was encountered during the on_headers event'; + $response = new RequestException($msg, $request, $response, $e); + } + } + + if (\is_callable($response)) { + $response = $response($request, $options); + } + + $response = $response instanceof \Throwable + ? P\Create::rejectionFor($response) + : P\Create::promiseFor($response); + + return $response->then( + function (?ResponseInterface $value) use ($request, $options) { + $this->invokeStats($request, $options, $value); + if ($this->onFulfilled) { + ($this->onFulfilled)($value); + } + + if ($value !== null && isset($options['sink'])) { + $contents = (string) $value->getBody(); + $sink = $options['sink']; + + if (\is_resource($sink)) { + \fwrite($sink, $contents); + } elseif (\is_string($sink)) { + \file_put_contents($sink, $contents); + } elseif ($sink instanceof StreamInterface) { + $sink->write($contents); + } + } + + return $value; + }, + function ($reason) use ($request, $options) { + $this->invokeStats($request, $options, null, $reason); + if ($this->onRejected) { + ($this->onRejected)($reason); + } + return P\Create::rejectionFor($reason); + } + ); + } + + /** + * Adds one or more variadic requests, exceptions, callables, or promises + * to the queue. + * + * @param mixed ...$values + */ + public function append(...$values): void + { + foreach ($values as $value) { + if ($value instanceof ResponseInterface + || $value instanceof \Throwable + || $value instanceof PromiseInterface + || \is_callable($value) + ) { + $this->queue[] = $value; + } else { + throw new \TypeError('Expected a Response, Promise, Throwable or callable. Found ' . Utils::describeType($value)); + } + } + } + + /** + * Get the last received request. + */ + public function getLastRequest(): ?RequestInterface + { + return $this->lastRequest; + } + + /** + * Get the last received request options. + */ + public function getLastOptions(): array + { + return $this->lastOptions; + } + + /** + * Returns the number of remaining items in the queue. + */ + public function count(): int + { + return \count($this->queue); + } + + public function reset(): void + { + $this->queue = []; + } + + /** + * @param mixed $reason Promise or reason. + */ + private function invokeStats( + RequestInterface $request, + array $options, + ResponseInterface $response = null, + $reason = null + ): void { + if (isset($options['on_stats'])) { + $transferTime = $options['transfer_time'] ?? 0; + $stats = new TransferStats($request, $response, $transferTime, $reason); + ($options['on_stats'])($stats); + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php new file mode 100644 index 0000000000..f045b526c5 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php @@ -0,0 +1,51 @@ +withoutHeader('Expect'); + + // Append a content-length header if body size is zero to match + // cURL's behavior. + if (0 === $request->getBody()->getSize()) { + $request = $request->withHeader('Content-Length', '0'); + } + + return $this->createResponse( + $request, + $options, + $this->createStream($request, $options), + $startTime + ); + } catch (\InvalidArgumentException $e) { + throw $e; + } catch (\Exception $e) { + // Determine if the error was a networking error. + $message = $e->getMessage(); + // This list can probably get more comprehensive. + if (false !== \strpos($message, 'getaddrinfo') // DNS lookup failed + || false !== \strpos($message, 'Connection refused') + || false !== \strpos($message, "couldn't connect to host") // error on HHVM + || false !== \strpos($message, "connection attempt failed") + ) { + $e = new ConnectException($e->getMessage(), $request, $e); + } else { + $e = RequestException::wrapException($request, $e); + } + $this->invokeStats($options, $request, $startTime, null, $e); + + return P\Create::rejectionFor($e); + } + } + + private function invokeStats( + array $options, + RequestInterface $request, + ?float $startTime, + ResponseInterface $response = null, + \Throwable $error = null + ): void { + if (isset($options['on_stats'])) { + $stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []); + ($options['on_stats'])($stats); + } + } + + /** + * @param resource $stream + */ + private function createResponse(RequestInterface $request, array $options, $stream, ?float $startTime): PromiseInterface + { + $hdrs = $this->lastHeaders; + $this->lastHeaders = []; + + try { + [$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($hdrs); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered while creating the response', $request, null, $e) + ); + } + + [$stream, $headers] = $this->checkDecode($options, $headers, $stream); + $stream = Psr7\Utils::streamFor($stream); + $sink = $stream; + + if (\strcasecmp('HEAD', $request->getMethod())) { + $sink = $this->createSink($stream, $options); + } + + try { + $response = new Psr7\Response($status, $headers, $sink, $ver, $reason); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered while creating the response', $request, null, $e) + ); + } + + if (isset($options['on_headers'])) { + try { + $options['on_headers']($response); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered during the on_headers event', $request, $response, $e) + ); + } + } + + // Do not drain when the request is a HEAD request because they have + // no body. + if ($sink !== $stream) { + $this->drain($stream, $sink, $response->getHeaderLine('Content-Length')); + } + + $this->invokeStats($options, $request, $startTime, $response, null); + + return new FulfilledPromise($response); + } + + private function createSink(StreamInterface $stream, array $options): StreamInterface + { + if (!empty($options['stream'])) { + return $stream; + } + + $sink = $options['sink'] ?? Psr7\Utils::tryFopen('php://temp', 'r+'); + + return \is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\Utils::streamFor($sink); + } + + /** + * @param resource $stream + */ + private function checkDecode(array $options, array $headers, $stream): array + { + // Automatically decode responses when instructed. + if (!empty($options['decode_content'])) { + $normalizedKeys = Utils::normalizeHeaderKeys($headers); + if (isset($normalizedKeys['content-encoding'])) { + $encoding = $headers[$normalizedKeys['content-encoding']]; + if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { + $stream = new Psr7\InflateStream(Psr7\Utils::streamFor($stream)); + $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; + + // Remove content-encoding header + unset($headers[$normalizedKeys['content-encoding']]); + + // Fix content-length header + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; + $length = (int) $stream->getSize(); + if ($length === 0) { + unset($headers[$normalizedKeys['content-length']]); + } else { + $headers[$normalizedKeys['content-length']] = [$length]; + } + } + } + } + } + + return [$stream, $headers]; + } + + /** + * Drains the source stream into the "sink" client option. + * + * @param string $contentLength Header specifying the amount of + * data to read. + * + * @throws \RuntimeException when the sink option is invalid. + */ + private function drain(StreamInterface $source, StreamInterface $sink, string $contentLength): StreamInterface + { + // If a content-length header is provided, then stop reading once + // that number of bytes has been read. This can prevent infinitely + // reading from a stream when dealing with servers that do not honor + // Connection: Close headers. + Psr7\Utils::copyToStream( + $source, + $sink, + (\strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1 + ); + + $sink->seek(0); + $source->close(); + + return $sink; + } + + /** + * Create a resource and check to ensure it was created successfully + * + * @param callable $callback Callable that returns stream resource + * + * @return resource + * + * @throws \RuntimeException on error + */ + private function createResource(callable $callback) + { + $errors = []; + \set_error_handler(static function ($_, $msg, $file, $line) use (&$errors): bool { + $errors[] = [ + 'message' => $msg, + 'file' => $file, + 'line' => $line + ]; + return true; + }); + + try { + $resource = $callback(); + } finally { + \restore_error_handler(); + } + + if (!$resource) { + $message = 'Error creating resource: '; + foreach ($errors as $err) { + foreach ($err as $key => $value) { + $message .= "[$key] $value" . \PHP_EOL; + } + } + throw new \RuntimeException(\trim($message)); + } + + return $resource; + } + + /** + * @return resource + */ + private function createStream(RequestInterface $request, array $options) + { + static $methods; + if (!$methods) { + $methods = \array_flip(\get_class_methods(__CLASS__)); + } + + if (!\in_array($request->getUri()->getScheme(), ['http', 'https'])) { + throw new RequestException(\sprintf("The scheme '%s' is not supported.", $request->getUri()->getScheme()), $request); + } + + // HTTP/1.1 streams using the PHP stream wrapper require a + // Connection: close header + if ($request->getProtocolVersion() == '1.1' + && !$request->hasHeader('Connection') + ) { + $request = $request->withHeader('Connection', 'close'); + } + + // Ensure SSL is verified by default + if (!isset($options['verify'])) { + $options['verify'] = true; + } + + $params = []; + $context = $this->getDefaultContext($request); + + if (isset($options['on_headers']) && !\is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + + if (!empty($options)) { + foreach ($options as $key => $value) { + $method = "add_{$key}"; + if (isset($methods[$method])) { + $this->{$method}($request, $context, $value, $params); + } + } + } + + if (isset($options['stream_context'])) { + if (!\is_array($options['stream_context'])) { + throw new \InvalidArgumentException('stream_context must be an array'); + } + $context = \array_replace_recursive($context, $options['stream_context']); + } + + // Microsoft NTLM authentication only supported with curl handler + if (isset($options['auth'][2]) && 'ntlm' === $options['auth'][2]) { + throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler'); + } + + $uri = $this->resolveHost($request, $options); + + $contextResource = $this->createResource( + static function () use ($context, $params) { + return \stream_context_create($context, $params); + } + ); + + return $this->createResource( + function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) { + $resource = @\fopen((string) $uri, 'r', false, $contextResource); + $this->lastHeaders = $http_response_header ?? []; + + if (false === $resource) { + throw new ConnectException(sprintf('Connection refused for URI %s', $uri), $request, null, $context); + } + + if (isset($options['read_timeout'])) { + $readTimeout = $options['read_timeout']; + $sec = (int) $readTimeout; + $usec = ($readTimeout - $sec) * 100000; + \stream_set_timeout($resource, $sec, $usec); + } + + return $resource; + } + ); + } + + private function resolveHost(RequestInterface $request, array $options): UriInterface + { + $uri = $request->getUri(); + + if (isset($options['force_ip_resolve']) && !\filter_var($uri->getHost(), \FILTER_VALIDATE_IP)) { + if ('v4' === $options['force_ip_resolve']) { + $records = \dns_get_record($uri->getHost(), \DNS_A); + if (false === $records || !isset($records[0]['ip'])) { + throw new ConnectException(\sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request); + } + return $uri->withHost($records[0]['ip']); + } + if ('v6' === $options['force_ip_resolve']) { + $records = \dns_get_record($uri->getHost(), \DNS_AAAA); + if (false === $records || !isset($records[0]['ipv6'])) { + throw new ConnectException(\sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request); + } + return $uri->withHost('[' . $records[0]['ipv6'] . ']'); + } + } + + return $uri; + } + + private function getDefaultContext(RequestInterface $request): array + { + $headers = ''; + foreach ($request->getHeaders() as $name => $value) { + foreach ($value as $val) { + $headers .= "$name: $val\r\n"; + } + } + + $context = [ + 'http' => [ + 'method' => $request->getMethod(), + 'header' => $headers, + 'protocol_version' => $request->getProtocolVersion(), + 'ignore_errors' => true, + 'follow_location' => 0, + ], + 'ssl' => [ + 'peer_name' => $request->getUri()->getHost(), + ], + ]; + + $body = (string) $request->getBody(); + + if (!empty($body)) { + $context['http']['content'] = $body; + // Prevent the HTTP handler from adding a Content-Type header. + if (!$request->hasHeader('Content-Type')) { + $context['http']['header'] .= "Content-Type:\r\n"; + } + } + + $context['http']['header'] = \rtrim($context['http']['header']); + + return $context; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_proxy(RequestInterface $request, array &$options, $value, array &$params): void + { + $uri = null; + + if (!\is_array($value)) { + $uri = $value; + } else { + $scheme = $request->getUri()->getScheme(); + if (isset($value[$scheme])) { + if (!isset($value['no']) || !Utils::isHostInNoProxy($request->getUri()->getHost(), $value['no'])) { + $uri = $value[$scheme]; + } + } + } + + if (!$uri) { + return; + } + + $parsed = $this->parse_proxy($uri); + $options['http']['proxy'] = $parsed['proxy']; + + if ($parsed['auth']) { + if (!isset($options['http']['header'])) { + $options['http']['header'] = []; + } + $options['http']['header'] .= "\r\nProxy-Authorization: {$parsed['auth']}"; + } + } + + /** + * Parses the given proxy URL to make it compatible with the format PHP's stream context expects. + */ + private function parse_proxy(string $url): array + { + $parsed = \parse_url($url); + + if ($parsed !== false && isset($parsed['scheme']) && $parsed['scheme'] === 'http') { + if (isset($parsed['host']) && isset($parsed['port'])) { + $auth = null; + if (isset($parsed['user']) && isset($parsed['pass'])) { + $auth = \base64_encode("{$parsed['user']}:{$parsed['pass']}"); + } + + return [ + 'proxy' => "tcp://{$parsed['host']}:{$parsed['port']}", + 'auth' => $auth ? "Basic {$auth}" : null, + ]; + } + } + + // Return proxy as-is. + return [ + 'proxy' => $url, + 'auth' => null, + ]; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_timeout(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value > 0) { + $options['http']['timeout'] = $value; + } + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_verify(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value === false) { + $options['ssl']['verify_peer'] = false; + $options['ssl']['verify_peer_name'] = false; + + return; + } + + if (\is_string($value)) { + $options['ssl']['cafile'] = $value; + if (!\file_exists($value)) { + throw new \RuntimeException("SSL CA bundle not found: $value"); + } + } elseif ($value !== true) { + throw new \InvalidArgumentException('Invalid verify request option'); + } + + $options['ssl']['verify_peer'] = true; + $options['ssl']['verify_peer_name'] = true; + $options['ssl']['allow_self_signed'] = false; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_cert(RequestInterface $request, array &$options, $value, array &$params): void + { + if (\is_array($value)) { + $options['ssl']['passphrase'] = $value[1]; + $value = $value[0]; + } + + if (!\file_exists($value)) { + throw new \RuntimeException("SSL certificate not found: {$value}"); + } + + $options['ssl']['local_cert'] = $value; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_progress(RequestInterface $request, array &$options, $value, array &$params): void + { + self::addNotification( + $params, + static function ($code, $a, $b, $c, $transferred, $total) use ($value) { + if ($code == \STREAM_NOTIFY_PROGRESS) { + // The upload progress cannot be determined. Use 0 for cURL compatibility: + // https://curl.se/libcurl/c/CURLOPT_PROGRESSFUNCTION.html + $value($total, $transferred, 0, 0); + } + } + ); + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_debug(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value === false) { + return; + } + + static $map = [ + \STREAM_NOTIFY_CONNECT => 'CONNECT', + \STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', + \STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', + \STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', + \STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', + \STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', + \STREAM_NOTIFY_PROGRESS => 'PROGRESS', + \STREAM_NOTIFY_FAILURE => 'FAILURE', + \STREAM_NOTIFY_COMPLETED => 'COMPLETED', + \STREAM_NOTIFY_RESOLVE => 'RESOLVE', + ]; + static $args = ['severity', 'message', 'message_code', 'bytes_transferred', 'bytes_max']; + + $value = Utils::debugResource($value); + $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment(''); + self::addNotification( + $params, + static function (int $code, ...$passed) use ($ident, $value, $map, $args): void { + \fprintf($value, '<%s> [%s] ', $ident, $map[$code]); + foreach (\array_filter($passed) as $i => $v) { + \fwrite($value, $args[$i] . ': "' . $v . '" '); + } + \fwrite($value, "\n"); + } + ); + } + + private static function addNotification(array &$params, callable $notify): void + { + // Wrap the existing function if needed. + if (!isset($params['notification'])) { + $params['notification'] = $notify; + } else { + $params['notification'] = self::callArray([ + $params['notification'], + $notify + ]); + } + } + + private static function callArray(array $functions): callable + { + return static function (...$args) use ($functions) { + foreach ($functions as $fn) { + $fn(...$args); + } + }; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/HandlerStack.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/HandlerStack.php new file mode 100644 index 0000000000..e0a1d11913 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/HandlerStack.php @@ -0,0 +1,275 @@ +push(Middleware::httpErrors(), 'http_errors'); + $stack->push(Middleware::redirect(), 'allow_redirects'); + $stack->push(Middleware::cookies(), 'cookies'); + $stack->push(Middleware::prepareBody(), 'prepare_body'); + + return $stack; + } + + /** + * @param (callable(RequestInterface, array): PromiseInterface)|null $handler Underlying HTTP handler. + */ + public function __construct(callable $handler = null) + { + $this->handler = $handler; + } + + /** + * Invokes the handler stack as a composed handler + * + * @return ResponseInterface|PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $handler = $this->resolve(); + + return $handler($request, $options); + } + + /** + * Dumps a string representation of the stack. + * + * @return string + */ + public function __toString() + { + $depth = 0; + $stack = []; + + if ($this->handler !== null) { + $stack[] = "0) Handler: " . $this->debugCallable($this->handler); + } + + $result = ''; + foreach (\array_reverse($this->stack) as $tuple) { + $depth++; + $str = "{$depth}) Name: '{$tuple[1]}', "; + $str .= "Function: " . $this->debugCallable($tuple[0]); + $result = "> {$str}\n{$result}"; + $stack[] = $str; + } + + foreach (\array_keys($stack) as $k) { + $result .= "< {$stack[$k]}\n"; + } + + return $result; + } + + /** + * Set the HTTP handler that actually returns a promise. + * + * @param callable(RequestInterface, array): PromiseInterface $handler Accepts a request and array of options and + * returns a Promise. + */ + public function setHandler(callable $handler): void + { + $this->handler = $handler; + $this->cached = null; + } + + /** + * Returns true if the builder has a handler. + */ + public function hasHandler(): bool + { + return $this->handler !== null ; + } + + /** + * Unshift a middleware to the bottom of the stack. + * + * @param callable(callable): callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function unshift(callable $middleware, ?string $name = null): void + { + \array_unshift($this->stack, [$middleware, $name]); + $this->cached = null; + } + + /** + * Push a middleware to the top of the stack. + * + * @param callable(callable): callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function push(callable $middleware, string $name = ''): void + { + $this->stack[] = [$middleware, $name]; + $this->cached = null; + } + + /** + * Add a middleware before another middleware by name. + * + * @param string $findName Middleware to find + * @param callable(callable): callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function before(string $findName, callable $middleware, string $withName = ''): void + { + $this->splice($findName, $withName, $middleware, true); + } + + /** + * Add a middleware after another middleware by name. + * + * @param string $findName Middleware to find + * @param callable(callable): callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function after(string $findName, callable $middleware, string $withName = ''): void + { + $this->splice($findName, $withName, $middleware, false); + } + + /** + * Remove a middleware by instance or name from the stack. + * + * @param callable|string $remove Middleware to remove by instance or name. + */ + public function remove($remove): void + { + if (!is_string($remove) && !is_callable($remove)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a callable or string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->cached = null; + $idx = \is_callable($remove) ? 0 : 1; + $this->stack = \array_values(\array_filter( + $this->stack, + static function ($tuple) use ($idx, $remove) { + return $tuple[$idx] !== $remove; + } + )); + } + + /** + * Compose the middleware and handler into a single callable function. + * + * @return callable(RequestInterface, array): PromiseInterface + */ + public function resolve(): callable + { + if ($this->cached === null) { + if (($prev = $this->handler) === null) { + throw new \LogicException('No handler has been specified'); + } + + foreach (\array_reverse($this->stack) as $fn) { + /** @var callable(RequestInterface, array): PromiseInterface $prev */ + $prev = $fn[0]($prev); + } + + $this->cached = $prev; + } + + return $this->cached; + } + + private function findByName(string $name): int + { + foreach ($this->stack as $k => $v) { + if ($v[1] === $name) { + return $k; + } + } + + throw new \InvalidArgumentException("Middleware not found: $name"); + } + + /** + * Splices a function into the middleware list at a specific position. + */ + private function splice(string $findName, string $withName, callable $middleware, bool $before): void + { + $this->cached = null; + $idx = $this->findByName($findName); + $tuple = [$middleware, $withName]; + + if ($before) { + if ($idx === 0) { + \array_unshift($this->stack, $tuple); + } else { + $replacement = [$tuple, $this->stack[$idx]]; + \array_splice($this->stack, $idx, 1, $replacement); + } + } elseif ($idx === \count($this->stack) - 1) { + $this->stack[] = $tuple; + } else { + $replacement = [$this->stack[$idx], $tuple]; + \array_splice($this->stack, $idx, 1, $replacement); + } + } + + /** + * Provides a debug string for a given callable. + * + * @param callable|string $fn Function to write as a string. + */ + private function debugCallable($fn): string + { + if (\is_string($fn)) { + return "callable({$fn})"; + } + + if (\is_array($fn)) { + return \is_string($fn[0]) + ? "callable({$fn[0]}::{$fn[1]})" + : "callable(['" . \get_class($fn[0]) . "', '{$fn[1]}'])"; + } + + /** @var object $fn */ + return 'callable(' . \spl_object_hash($fn) . ')'; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/MessageFormatter.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/MessageFormatter.php new file mode 100644 index 0000000000..da499547f1 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/MessageFormatter.php @@ -0,0 +1,198 @@ +>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; + public const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}'; + + /** + * @var string Template used to format log messages + */ + private $template; + + /** + * @param string $template Log message template + */ + public function __construct(?string $template = self::CLF) + { + $this->template = $template ?: self::CLF; + } + + /** + * Returns a formatted message string. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface|null $response Response that was received + * @param \Throwable|null $error Exception that was received + */ + public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string + { + $cache = []; + + /** @var string */ + return \preg_replace_callback( + '/{\s*([A-Za-z_\-\.0-9]+)\s*}/', + function (array $matches) use ($request, $response, $error, &$cache) { + if (isset($cache[$matches[1]])) { + return $cache[$matches[1]]; + } + + $result = ''; + switch ($matches[1]) { + case 'request': + $result = Psr7\Message::toString($request); + break; + case 'response': + $result = $response ? Psr7\Message::toString($response) : ''; + break; + case 'req_headers': + $result = \trim($request->getMethod() + . ' ' . $request->getRequestTarget()) + . ' HTTP/' . $request->getProtocolVersion() . "\r\n" + . $this->headers($request); + break; + case 'res_headers': + $result = $response ? + \sprintf( + 'HTTP/%s %d %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ) . "\r\n" . $this->headers($response) + : 'NULL'; + break; + case 'req_body': + $result = $request->getBody()->__toString(); + break; + case 'res_body': + if (!$response instanceof ResponseInterface) { + $result = 'NULL'; + break; + } + + $body = $response->getBody(); + + if (!$body->isSeekable()) { + $result = 'RESPONSE_NOT_LOGGEABLE'; + break; + } + + $result = $response->getBody()->__toString(); + break; + case 'ts': + case 'date_iso_8601': + $result = \gmdate('c'); + break; + case 'date_common_log': + $result = \date('d/M/Y:H:i:s O'); + break; + case 'method': + $result = $request->getMethod(); + break; + case 'version': + $result = $request->getProtocolVersion(); + break; + case 'uri': + case 'url': + $result = $request->getUri()->__toString(); + break; + case 'target': + $result = $request->getRequestTarget(); + break; + case 'req_version': + $result = $request->getProtocolVersion(); + break; + case 'res_version': + $result = $response + ? $response->getProtocolVersion() + : 'NULL'; + break; + case 'host': + $result = $request->getHeaderLine('Host'); + break; + case 'hostname': + $result = \gethostname(); + break; + case 'code': + $result = $response ? $response->getStatusCode() : 'NULL'; + break; + case 'phrase': + $result = $response ? $response->getReasonPhrase() : 'NULL'; + break; + case 'error': + $result = $error ? $error->getMessage() : 'NULL'; + break; + default: + // handle prefixed dynamic headers + if (\strpos($matches[1], 'req_header_') === 0) { + $result = $request->getHeaderLine(\substr($matches[1], 11)); + } elseif (\strpos($matches[1], 'res_header_') === 0) { + $result = $response + ? $response->getHeaderLine(\substr($matches[1], 11)) + : 'NULL'; + } + } + + $cache[$matches[1]] = $result; + return $result; + }, + $this->template + ); + } + + /** + * Get headers from message as string + */ + private function headers(MessageInterface $message): string + { + $result = ''; + foreach ($message->getHeaders() as $name => $values) { + $result .= $name . ': ' . \implode(', ', $values) . "\r\n"; + } + + return \trim($result); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php new file mode 100644 index 0000000000..a39ac248ee --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php @@ -0,0 +1,18 @@ +withCookieHeader($request); + return $handler($request, $options) + ->then( + static function (ResponseInterface $response) use ($cookieJar, $request): ResponseInterface { + $cookieJar->extractCookies($request, $response); + return $response; + } + ); + }; + }; + } + + /** + * Middleware that throws exceptions for 4xx or 5xx responses when the + * "http_errors" request option is set to true. + * + * @param BodySummarizerInterface|null $bodySummarizer The body summarizer to use in exception messages. + * + * @return callable(callable): callable Returns a function that accepts the next handler. + */ + public static function httpErrors(BodySummarizerInterface $bodySummarizer = null): callable + { + return static function (callable $handler) use ($bodySummarizer): callable { + return static function ($request, array $options) use ($handler, $bodySummarizer) { + if (empty($options['http_errors'])) { + return $handler($request, $options); + } + return $handler($request, $options)->then( + static function (ResponseInterface $response) use ($request, $bodySummarizer) { + $code = $response->getStatusCode(); + if ($code < 400) { + return $response; + } + throw RequestException::create($request, $response, null, [], $bodySummarizer); + } + ); + }; + }; + } + + /** + * Middleware that pushes history data to an ArrayAccess container. + * + * @param array|\ArrayAccess $container Container to hold the history (by reference). + * + * @return callable(callable): callable Returns a function that accepts the next handler. + * + * @throws \InvalidArgumentException if container is not an array or ArrayAccess. + */ + public static function history(&$container): callable + { + if (!\is_array($container) && !$container instanceof \ArrayAccess) { + throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess'); + } + + return static function (callable $handler) use (&$container): callable { + return static function (RequestInterface $request, array $options) use ($handler, &$container) { + return $handler($request, $options)->then( + static function ($value) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => $value, + 'error' => null, + 'options' => $options + ]; + return $value; + }, + static function ($reason) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => null, + 'error' => $reason, + 'options' => $options + ]; + return P\Create::rejectionFor($reason); + } + ); + }; + }; + } + + /** + * Middleware that invokes a callback before and after sending a request. + * + * The provided listener cannot modify or alter the response. It simply + * "taps" into the chain to be notified before returning the promise. The + * before listener accepts a request and options array, and the after + * listener accepts a request, options array, and response promise. + * + * @param callable $before Function to invoke before forwarding the request. + * @param callable $after Function invoked after forwarding. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function tap(callable $before = null, callable $after = null): callable + { + return static function (callable $handler) use ($before, $after): callable { + return static function (RequestInterface $request, array $options) use ($handler, $before, $after) { + if ($before) { + $before($request, $options); + } + $response = $handler($request, $options); + if ($after) { + $after($request, $options, $response); + } + return $response; + }; + }; + } + + /** + * Middleware that handles request redirects. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function redirect(): callable + { + return static function (callable $handler): RedirectMiddleware { + return new RedirectMiddleware($handler); + }; + } + + /** + * Middleware that retries requests based on the boolean result of + * invoking the provided "decider" function. + * + * If no delay function is provided, a simple implementation of exponential + * backoff will be utilized. + * + * @param callable $decider Function that accepts the number of retries, + * a request, [response], and [exception] and + * returns true if the request is to be retried. + * @param callable $delay Function that accepts the number of retries and + * returns the number of milliseconds to delay. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function retry(callable $decider, callable $delay = null): callable + { + return static function (callable $handler) use ($decider, $delay): RetryMiddleware { + return new RetryMiddleware($decider, $handler, $delay); + }; + } + + /** + * Middleware that logs requests, responses, and errors using a message + * formatter. + * + * @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests. + * + * @param LoggerInterface $logger Logs messages. + * @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings. + * @param string $logLevel Level at which to log requests. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable + { + // To be compatible with Guzzle 7.1.x we need to allow users to pass a MessageFormatter + if (!$formatter instanceof MessageFormatter && !$formatter instanceof MessageFormatterInterface) { + throw new \LogicException(sprintf('Argument 2 to %s::log() must be of type %s', self::class, MessageFormatterInterface::class)); + } + + return static function (callable $handler) use ($logger, $formatter, $logLevel): callable { + return static function (RequestInterface $request, array $options = []) use ($handler, $logger, $formatter, $logLevel) { + return $handler($request, $options)->then( + static function ($response) use ($logger, $request, $formatter, $logLevel): ResponseInterface { + $message = $formatter->format($request, $response); + $logger->log($logLevel, $message); + return $response; + }, + static function ($reason) use ($logger, $request, $formatter): PromiseInterface { + $response = $reason instanceof RequestException ? $reason->getResponse() : null; + $message = $formatter->format($request, $response, P\Create::exceptionFor($reason)); + $logger->error($message); + return P\Create::rejectionFor($reason); + } + ); + }; + }; + } + + /** + * This middleware adds a default content-type if possible, a default + * content-length or transfer-encoding header, and the expect header. + */ + public static function prepareBody(): callable + { + return static function (callable $handler): PrepareBodyMiddleware { + return new PrepareBodyMiddleware($handler); + }; + } + + /** + * Middleware that applies a map function to the request before passing to + * the next handler. + * + * @param callable $fn Function that accepts a RequestInterface and returns + * a RequestInterface. + */ + public static function mapRequest(callable $fn): callable + { + return static function (callable $handler) use ($fn): callable { + return static function (RequestInterface $request, array $options) use ($handler, $fn) { + return $handler($fn($request), $options); + }; + }; + } + + /** + * Middleware that applies a map function to the resolved promise's + * response. + * + * @param callable $fn Function that accepts a ResponseInterface and + * returns a ResponseInterface. + */ + public static function mapResponse(callable $fn): callable + { + return static function (callable $handler) use ($fn): callable { + return static function (RequestInterface $request, array $options) use ($handler, $fn) { + return $handler($request, $options)->then($fn); + }; + }; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Pool.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Pool.php new file mode 100644 index 0000000000..6277c61fbc --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Pool.php @@ -0,0 +1,125 @@ + $rfn) { + if ($rfn instanceof RequestInterface) { + yield $key => $client->sendAsync($rfn, $opts); + } elseif (\is_callable($rfn)) { + yield $key => $rfn($opts); + } else { + throw new \InvalidArgumentException('Each value yielded by the iterator must be a Psr7\Http\Message\RequestInterface or a callable that returns a promise that fulfills with a Psr7\Message\Http\ResponseInterface object.'); + } + } + }; + + $this->each = new EachPromise($requests(), $config); + } + + /** + * Get promise + */ + public function promise(): PromiseInterface + { + return $this->each->promise(); + } + + /** + * Sends multiple requests concurrently and returns an array of responses + * and exceptions that uses the same ordering as the provided requests. + * + * IMPORTANT: This method keeps every request and response in memory, and + * as such, is NOT recommended when sending a large number or an + * indeterminate number of requests concurrently. + * + * @param ClientInterface $client Client used to send the requests + * @param array|\Iterator $requests Requests to send concurrently. + * @param array $options Passes through the options available in + * {@see \GuzzleHttp\Pool::__construct} + * + * @return array Returns an array containing the response or an exception + * in the same order that the requests were sent. + * + * @throws \InvalidArgumentException if the event format is incorrect. + */ + public static function batch(ClientInterface $client, $requests, array $options = []): array + { + $res = []; + self::cmpCallback($options, 'fulfilled', $res); + self::cmpCallback($options, 'rejected', $res); + $pool = new static($client, $requests, $options); + $pool->promise()->wait(); + \ksort($res); + + return $res; + } + + /** + * Execute callback(s) + */ + private static function cmpCallback(array &$options, string $name, array &$results): void + { + if (!isset($options[$name])) { + $options[$name] = static function ($v, $k) use (&$results) { + $results[$k] = $v; + }; + } else { + $currentFn = $options[$name]; + $options[$name] = static function ($v, $k) use (&$results, $currentFn) { + $currentFn($v, $k); + $results[$k] = $v; + }; + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php new file mode 100644 index 0000000000..7ca6283380 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php @@ -0,0 +1,104 @@ +nextHandler = $nextHandler; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $fn = $this->nextHandler; + + // Don't do anything if the request has no body. + if ($request->getBody()->getSize() === 0) { + return $fn($request, $options); + } + + $modify = []; + + // Add a default content-type if possible. + if (!$request->hasHeader('Content-Type')) { + if ($uri = $request->getBody()->getMetadata('uri')) { + if (is_string($uri) && $type = Psr7\MimeType::fromFilename($uri)) { + $modify['set_headers']['Content-Type'] = $type; + } + } + } + + // Add a default content-length or transfer-encoding header. + if (!$request->hasHeader('Content-Length') + && !$request->hasHeader('Transfer-Encoding') + ) { + $size = $request->getBody()->getSize(); + if ($size !== null) { + $modify['set_headers']['Content-Length'] = $size; + } else { + $modify['set_headers']['Transfer-Encoding'] = 'chunked'; + } + } + + // Add the expect header if needed. + $this->addExpectHeader($request, $options, $modify); + + return $fn(Psr7\Utils::modifyRequest($request, $modify), $options); + } + + /** + * Add expect header + */ + private function addExpectHeader(RequestInterface $request, array $options, array &$modify): void + { + // Determine if the Expect header should be used + if ($request->hasHeader('Expect')) { + return; + } + + $expect = $options['expect'] ?? null; + + // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0 + if ($expect === false || $request->getProtocolVersion() < 1.1) { + return; + } + + // The expect header is unconditionally enabled + if ($expect === true) { + $modify['set_headers']['Expect'] = '100-Continue'; + return; + } + + // By default, send the expect header when the payload is > 1mb + if ($expect === null) { + $expect = 1048576; + } + + // Always add if the body cannot be rewound, the size cannot be + // determined, or the size is greater than the cutoff threshold + $body = $request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { + $modify['set_headers']['Expect'] = '100-Continue'; + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php new file mode 100644 index 0000000000..f67d448beb --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php @@ -0,0 +1,228 @@ + 5, + 'protocols' => ['http', 'https'], + 'strict' => false, + 'referer' => false, + 'track_redirects' => false, + ]; + + /** + * @var callable(RequestInterface, array): PromiseInterface + */ + private $nextHandler; + + /** + * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke. + */ + public function __construct(callable $nextHandler) + { + $this->nextHandler = $nextHandler; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $fn = $this->nextHandler; + + if (empty($options['allow_redirects'])) { + return $fn($request, $options); + } + + if ($options['allow_redirects'] === true) { + $options['allow_redirects'] = self::$defaultSettings; + } elseif (!\is_array($options['allow_redirects'])) { + throw new \InvalidArgumentException('allow_redirects must be true, false, or array'); + } else { + // Merge the default settings with the provided settings + $options['allow_redirects'] += self::$defaultSettings; + } + + if (empty($options['allow_redirects']['max'])) { + return $fn($request, $options); + } + + return $fn($request, $options) + ->then(function (ResponseInterface $response) use ($request, $options) { + return $this->checkRedirect($request, $options, $response); + }); + } + + /** + * @return ResponseInterface|PromiseInterface + */ + public function checkRedirect(RequestInterface $request, array $options, ResponseInterface $response) + { + if (\strpos((string) $response->getStatusCode(), '3') !== 0 + || !$response->hasHeader('Location') + ) { + return $response; + } + + $this->guardMax($request, $response, $options); + $nextRequest = $this->modifyRequest($request, $options, $response); + + // If authorization is handled by curl, unset it if URI is cross-origin. + if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $nextRequest->getUri()) && defined('\CURLOPT_HTTPAUTH')) { + unset( + $options['curl'][\CURLOPT_HTTPAUTH], + $options['curl'][\CURLOPT_USERPWD] + ); + } + + if (isset($options['allow_redirects']['on_redirect'])) { + ($options['allow_redirects']['on_redirect'])( + $request, + $response, + $nextRequest->getUri() + ); + } + + $promise = $this($nextRequest, $options); + + // Add headers to be able to track history of redirects. + if (!empty($options['allow_redirects']['track_redirects'])) { + return $this->withTracking( + $promise, + (string) $nextRequest->getUri(), + $response->getStatusCode() + ); + } + + return $promise; + } + + /** + * Enable tracking on promise. + */ + private function withTracking(PromiseInterface $promise, string $uri, int $statusCode): PromiseInterface + { + return $promise->then( + static function (ResponseInterface $response) use ($uri, $statusCode) { + // Note that we are pushing to the front of the list as this + // would be an earlier response than what is currently present + // in the history header. + $historyHeader = $response->getHeader(self::HISTORY_HEADER); + $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER); + \array_unshift($historyHeader, $uri); + \array_unshift($statusHeader, (string) $statusCode); + + return $response->withHeader(self::HISTORY_HEADER, $historyHeader) + ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader); + } + ); + } + + /** + * Check for too many redirects. + * + * @throws TooManyRedirectsException Too many redirects. + */ + private function guardMax(RequestInterface $request, ResponseInterface $response, array &$options): void + { + $current = $options['__redirect_count'] + ?? 0; + $options['__redirect_count'] = $current + 1; + $max = $options['allow_redirects']['max']; + + if ($options['__redirect_count'] > $max) { + throw new TooManyRedirectsException("Will not follow more than {$max} redirects", $request, $response); + } + } + + public function modifyRequest(RequestInterface $request, array $options, ResponseInterface $response): RequestInterface + { + // Request modifications to apply. + $modify = []; + $protocols = $options['allow_redirects']['protocols']; + + // Use a GET request if this is an entity enclosing request and we are + // not forcing RFC compliance, but rather emulating what all browsers + // would do. + $statusCode = $response->getStatusCode(); + if ($statusCode == 303 || + ($statusCode <= 302 && !$options['allow_redirects']['strict']) + ) { + $safeMethods = ['GET', 'HEAD', 'OPTIONS']; + $requestMethod = $request->getMethod(); + + $modify['method'] = in_array($requestMethod, $safeMethods) ? $requestMethod : 'GET'; + $modify['body'] = ''; + } + + $uri = self::redirectUri($request, $response, $protocols); + if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) { + $idnOptions = ($options['idn_conversion'] === true) ? \IDNA_DEFAULT : $options['idn_conversion']; + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + + $modify['uri'] = $uri; + Psr7\Message::rewindBody($request); + + // Add the Referer header if it is told to do so and only + // add the header if we are not redirecting from https to http. + if ($options['allow_redirects']['referer'] + && $modify['uri']->getScheme() === $request->getUri()->getScheme() + ) { + $uri = $request->getUri()->withUserInfo(''); + $modify['set_headers']['Referer'] = (string) $uri; + } else { + $modify['remove_headers'][] = 'Referer'; + } + + // Remove Authorization and Cookie headers if URI is cross-origin. + if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $modify['uri'])) { + $modify['remove_headers'][] = 'Authorization'; + $modify['remove_headers'][] = 'Cookie'; + } + + return Psr7\Utils::modifyRequest($request, $modify); + } + + /** + * Set the appropriate URL on the request based on the location header. + */ + private static function redirectUri( + RequestInterface $request, + ResponseInterface $response, + array $protocols + ): UriInterface { + $location = Psr7\UriResolver::resolve( + $request->getUri(), + new Psr7\Uri($response->getHeaderLine('Location')) + ); + + // Ensure that the redirect URI is allowed based on the protocols. + if (!\in_array($location->getScheme(), $protocols)) { + throw new BadResponseException(\sprintf('Redirect URI, %s, does not use one of the allowed redirect protocols: %s', $location, \implode(', ', $protocols)), $request, $response); + } + + return $location; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RequestOptions.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RequestOptions.php new file mode 100644 index 0000000000..20b31bc207 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RequestOptions.php @@ -0,0 +1,264 @@ +decider = $decider; + $this->nextHandler = $nextHandler; + $this->delay = $delay ?: __CLASS__ . '::exponentialDelay'; + } + + /** + * Default exponential backoff delay function. + * + * @return int milliseconds. + */ + public static function exponentialDelay(int $retries): int + { + return (int) \pow(2, $retries - 1) * 1000; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (!isset($options['retries'])) { + $options['retries'] = 0; + } + + $fn = $this->nextHandler; + return $fn($request, $options) + ->then( + $this->onFulfilled($request, $options), + $this->onRejected($request, $options) + ); + } + + /** + * Execute fulfilled closure + */ + private function onFulfilled(RequestInterface $request, array $options): callable + { + return function ($value) use ($request, $options) { + if (!($this->decider)( + $options['retries'], + $request, + $value, + null + )) { + return $value; + } + return $this->doRetry($request, $options, $value); + }; + } + + /** + * Execute rejected closure + */ + private function onRejected(RequestInterface $req, array $options): callable + { + return function ($reason) use ($req, $options) { + if (!($this->decider)( + $options['retries'], + $req, + null, + $reason + )) { + return P\Create::rejectionFor($reason); + } + return $this->doRetry($req, $options); + }; + } + + private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null): PromiseInterface + { + $options['delay'] = ($this->delay)(++$options['retries'], $response, $request); + + return $this($request, $options); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/TransferStats.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/TransferStats.php new file mode 100644 index 0000000000..93fa334c8d --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/TransferStats.php @@ -0,0 +1,133 @@ +request = $request; + $this->response = $response; + $this->transferTime = $transferTime; + $this->handlerErrorData = $handlerErrorData; + $this->handlerStats = $handlerStats; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Returns the response that was received (if any). + */ + public function getResponse(): ?ResponseInterface + { + return $this->response; + } + + /** + * Returns true if a response was received. + */ + public function hasResponse(): bool + { + return $this->response !== null; + } + + /** + * Gets handler specific error data. + * + * This might be an exception, a integer representing an error code, or + * anything else. Relying on this value assumes that you know what handler + * you are using. + * + * @return mixed + */ + public function getHandlerErrorData() + { + return $this->handlerErrorData; + } + + /** + * Get the effective URI the request was sent to. + */ + public function getEffectiveUri(): UriInterface + { + return $this->request->getUri(); + } + + /** + * Get the estimated time the request was being transferred by the handler. + * + * @return float|null Time in seconds. + */ + public function getTransferTime(): ?float + { + return $this->transferTime; + } + + /** + * Gets an array of all of the handler specific transfer data. + */ + public function getHandlerStats(): array + { + return $this->handlerStats; + } + + /** + * Get a specific handler statistic from the handler by name. + * + * @param string $stat Handler specific transfer stat to retrieve. + * + * @return mixed|null + */ + public function getHandlerStat(string $stat) + { + return $this->handlerStats[$stat] ?? null; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Utils.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Utils.php new file mode 100644 index 0000000000..e355f3212f --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Utils.php @@ -0,0 +1,385 @@ +getHost()) { + $asciiHost = self::idnToAsci($uri->getHost(), $options, $info); + if ($asciiHost === false) { + $errorBitSet = $info['errors'] ?? 0; + + $errorConstants = array_filter(array_keys(get_defined_constants()), static function (string $name): bool { + return substr($name, 0, 11) === 'IDNA_ERROR_'; + }); + + $errors = []; + foreach ($errorConstants as $errorConstant) { + if ($errorBitSet & constant($errorConstant)) { + $errors[] = $errorConstant; + } + } + + $errorMessage = 'IDN conversion failed'; + if ($errors) { + $errorMessage .= ' (errors: ' . implode(', ', $errors) . ')'; + } + + throw new InvalidArgumentException($errorMessage); + } + if ($uri->getHost() !== $asciiHost) { + // Replace URI only if the ASCII version is different + $uri = $uri->withHost($asciiHost); + } + } + + return $uri; + } + + /** + * @internal + */ + public static function getenv(string $name): ?string + { + if (isset($_SERVER[$name])) { + return (string) $_SERVER[$name]; + } + + if (\PHP_SAPI === 'cli' && ($value = \getenv($name)) !== false && $value !== null) { + return (string) $value; + } + + return null; + } + + /** + * @return string|false + */ + private static function idnToAsci(string $domain, int $options, ?array &$info = []) + { + if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) { + return \idn_to_ascii($domain, $options, \INTL_IDNA_VARIANT_UTS46, $info); + } + + throw new \Error('ext-idn or symfony/polyfill-intl-idn not loaded or too old'); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/functions.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/functions.php new file mode 100644 index 0000000000..a70d2cbf3f --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/functions.php @@ -0,0 +1,167 @@ + +Copyright (c) 2015 Graham Campbell +Copyright (c) 2017 Tobias Schultze +Copyright (c) 2020 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/README.md b/data/web/inc/lib/vendor/guzzlehttp/promises/README.md new file mode 100644 index 0000000000..1ea667ab9e --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/README.md @@ -0,0 +1,546 @@ +# Guzzle Promises + +[Promises/A+](https://promisesaplus.com/) implementation that handles promise +chaining and resolution iteratively, allowing for "infinite" promise chaining +while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/) +for a general introduction to promises. + +- [Features](#features) +- [Quick start](#quick-start) +- [Synchronous wait](#synchronous-wait) +- [Cancellation](#cancellation) +- [API](#api) + - [Promise](#promise) + - [FulfilledPromise](#fulfilledpromise) + - [RejectedPromise](#rejectedpromise) +- [Promise interop](#promise-interop) +- [Implementation notes](#implementation-notes) + + +## Features + +- [Promises/A+](https://promisesaplus.com/) implementation. +- Promise resolution and chaining is handled iteratively, allowing for + "infinite" promise chaining. +- Promises have a synchronous `wait` method. +- Promises can be cancelled. +- Works with any object that has a `then` function. +- C# style async/await coroutine promises using + `GuzzleHttp\Promise\Coroutine::of()`. + + +## Quick Start + +A *promise* represents the eventual result of an asynchronous operation. The +primary way of interacting with a promise is through its `then` method, which +registers callbacks to receive either a promise's eventual value or the reason +why the promise cannot be fulfilled. + +### Callbacks + +Callbacks are registered with the `then` method by providing an optional +`$onFulfilled` followed by an optional `$onRejected` function. + + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then( + // $onFulfilled + function ($value) { + echo 'The promise was fulfilled.'; + }, + // $onRejected + function ($reason) { + echo 'The promise was rejected.'; + } +); +``` + +*Resolving* a promise means that you either fulfill a promise with a *value* or +reject a promise with a *reason*. Resolving a promise triggers callbacks +registered with the promise's `then` method. These callbacks are triggered +only once and in the order in which they were added. + +### Resolving a Promise + +Promises are fulfilled using the `resolve($value)` method. Resolving a promise +with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger +all of the onFulfilled callbacks (resolving a promise with a rejected promise +will reject the promise and trigger the `$onRejected` callbacks). + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(function ($value) { + // Return a value and don't break the chain + return "Hello, " . $value; + }) + // This then is executed after the first then and receives the value + // returned from the first then. + ->then(function ($value) { + echo $value; + }); + +// Resolving the promise triggers the $onFulfilled callbacks and outputs +// "Hello, reader." +$promise->resolve('reader.'); +``` + +### Promise Forwarding + +Promises can be chained one after the other. Each then in the chain is a new +promise. The return value of a promise is what's forwarded to the next +promise in the chain. Returning a promise in a `then` callback will cause the +subsequent promises in the chain to only be fulfilled when the returned promise +has been fulfilled. The next promise in the chain will be invoked with the +resolved value of the promise. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$nextPromise = new Promise(); + +$promise + ->then(function ($value) use ($nextPromise) { + echo $value; + return $nextPromise; + }) + ->then(function ($value) { + echo $value; + }); + +// Triggers the first callback and outputs "A" +$promise->resolve('A'); +// Triggers the second callback and outputs "B" +$nextPromise->resolve('B'); +``` + +### Promise Rejection + +When a promise is rejected, the `$onRejected` callbacks are invoked with the +rejection reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + echo $reason; +}); + +$promise->reject('Error!'); +// Outputs "Error!" +``` + +### Rejection Forwarding + +If an exception is thrown in an `$onRejected` callback, subsequent +`$onRejected` callbacks are invoked with the thrown exception as the reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + throw new Exception($reason); +})->then(null, function ($reason) { + assert($reason->getMessage() === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +You can also forward a rejection down the promise chain by returning a +`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or +`$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + return new RejectedPromise($reason); +})->then(null, function ($reason) { + assert($reason === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +If an exception is not thrown in a `$onRejected` callback and the callback +does not return a rejected promise, downstream `$onFulfilled` callbacks are +invoked using the value returned from the `$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(null, function ($reason) { + return "It's ok"; + }) + ->then(function ($value) { + assert($value === "It's ok"); + }); + +$promise->reject('Error!'); +``` + + +## Synchronous Wait + +You can synchronously force promises to complete using a promise's `wait` +method. When creating a promise, you can provide a wait function that is used +to synchronously force a promise to complete. When a wait function is invoked +it is expected to deliver a value to the promise or reject the promise. If the +wait function does not deliver a value, then an exception is thrown. The wait +function provided to a promise constructor is invoked when the `wait` function +of the promise is called. + +```php +$promise = new Promise(function () use (&$promise) { + $promise->resolve('foo'); +}); + +// Calling wait will return the value of the promise. +echo $promise->wait(); // outputs "foo" +``` + +If an exception is encountered while invoking the wait function of a promise, +the promise is rejected with the exception and the exception is thrown. + +```php +$promise = new Promise(function () use (&$promise) { + throw new Exception('foo'); +}); + +$promise->wait(); // throws the exception. +``` + +Calling `wait` on a promise that has been fulfilled will not trigger the wait +function. It will simply return the previously resolved value. + +```php +$promise = new Promise(function () { die('this is not called!'); }); +$promise->resolve('foo'); +echo $promise->wait(); // outputs "foo" +``` + +Calling `wait` on a promise that has been rejected will throw an exception. If +the rejection reason is an instance of `\Exception` the reason is thrown. +Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason +can be obtained by calling the `getReason` method of the exception. + +```php +$promise = new Promise(); +$promise->reject('foo'); +$promise->wait(); +``` + +> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo' + +### Unwrapping a Promise + +When synchronously waiting on a promise, you are joining the state of the +promise into the current state of execution (i.e., return the value of the +promise if it was fulfilled or throw an exception if it was rejected). This is +called "unwrapping" the promise. Waiting on a promise will by default unwrap +the promise state. + +You can force a promise to resolve and *not* unwrap the state of the promise +by passing `false` to the first argument of the `wait` function: + +```php +$promise = new Promise(); +$promise->reject('foo'); +// This will not throw an exception. It simply ensures the promise has +// been resolved. +$promise->wait(false); +``` + +When unwrapping a promise, the resolved value of the promise will be waited +upon until the unwrapped value is not a promise. This means that if you resolve +promise A with a promise B and unwrap promise A, the value returned by the +wait function will be the value delivered to promise B. + +**Note**: when you do not unwrap the promise, no value is returned. + + +## Cancellation + +You can cancel a promise that has not yet been fulfilled using the `cancel()` +method of a promise. When creating a promise you can provide an optional +cancel function that when invoked cancels the action of computing a resolution +of the promise. + + +## API + +### Promise + +When creating a promise object, you can provide an optional `$waitFn` and +`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is +expected to resolve the promise. `$cancelFn` is a function with no arguments +that is expected to cancel the computation of a promise. It is invoked when the +`cancel()` method of a promise is called. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise( + function () use (&$promise) { + $promise->resolve('waited'); + }, + function () { + // do something that will cancel the promise computation (e.g., close + // a socket, cancel a database query, etc...) + } +); + +assert('waited' === $promise->wait()); +``` + +A promise has the following methods: + +- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface` + + Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler. + +- `otherwise(callable $onRejected) : PromiseInterface` + + Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled. + +- `wait($unwrap = true) : mixed` + + Synchronously waits on the promise to complete. + + `$unwrap` controls whether or not the value of the promise is returned for a + fulfilled promise or if an exception is thrown if the promise is rejected. + This is set to `true` by default. + +- `cancel()` + + Attempts to cancel the promise if possible. The promise being cancelled and + the parent most ancestor that has not yet been resolved will also be + cancelled. Any promises waiting on the cancelled promise to resolve will also + be cancelled. + +- `getState() : string` + + Returns the state of the promise. One of `pending`, `fulfilled`, or + `rejected`. + +- `resolve($value)` + + Fulfills the promise with the given `$value`. + +- `reject($reason)` + + Rejects the promise with the given `$reason`. + + +### FulfilledPromise + +A fulfilled promise can be created to represent a promise that has been +fulfilled. + +```php +use GuzzleHttp\Promise\FulfilledPromise; + +$promise = new FulfilledPromise('value'); + +// Fulfilled callbacks are immediately invoked. +$promise->then(function ($value) { + echo $value; +}); +``` + + +### RejectedPromise + +A rejected promise can be created to represent a promise that has been +rejected. + +```php +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new RejectedPromise('Error'); + +// Rejected callbacks are immediately invoked. +$promise->then(null, function ($reason) { + echo $reason; +}); +``` + + +## Promise Interoperability + +This library works with foreign promises that have a `then` method. This means +you can use Guzzle promises with [React promises](https://github.com/reactphp/promise) +for example. When a foreign promise is returned inside of a then method +callback, promise resolution will occur recursively. + +```php +// Create a React promise +$deferred = new React\Promise\Deferred(); +$reactPromise = $deferred->promise(); + +// Create a Guzzle promise that is fulfilled with a React promise. +$guzzlePromise = new GuzzleHttp\Promise\Promise(); +$guzzlePromise->then(function ($value) use ($reactPromise) { + // Do something something with the value... + // Return the React promise + return $reactPromise; +}); +``` + +Please note that wait and cancel chaining is no longer possible when forwarding +a foreign promise. You will need to wrap a third-party promise with a Guzzle +promise in order to utilize wait and cancel functions with foreign promises. + + +### Event Loop Integration + +In order to keep the stack size constant, Guzzle promises are resolved +asynchronously using a task queue. When waiting on promises synchronously, the +task queue will be automatically run to ensure that the blocking promise and +any forwarded promises are resolved. When using promises asynchronously in an +event loop, you will need to run the task queue on each tick of the loop. If +you do not run the task queue, then promises will not be resolved. + +You can run the task queue using the `run()` method of the global task queue +instance. + +```php +// Get the global task queue +$queue = GuzzleHttp\Promise\Utils::queue(); +$queue->run(); +``` + +For example, you could use Guzzle promises with React using a periodic timer: + +```php +$loop = React\EventLoop\Factory::create(); +$loop->addPeriodicTimer(0, [$queue, 'run']); +``` + +*TODO*: Perhaps adding a `futureTick()` on each tick would be faster? + + +## Implementation Notes + +### Promise Resolution and Chaining is Handled Iteratively + +By shuffling pending handlers from one owner to another, promises are +resolved iteratively, allowing for "infinite" then chaining. + +```php +then(function ($v) { + // The stack size remains constant (a good thing) + echo xdebug_get_stack_depth() . ', '; + return $v + 1; + }); +} + +$parent->resolve(0); +var_dump($p->wait()); // int(1000) + +``` + +When a promise is fulfilled or rejected with a non-promise value, the promise +then takes ownership of the handlers of each child promise and delivers values +down the chain without using recursion. + +When a promise is resolved with another promise, the original promise transfers +all of its pending handlers to the new promise. When the new promise is +eventually resolved, all of the pending handlers are delivered the forwarded +value. + +### A Promise is the Deferred + +Some promise libraries implement promises using a deferred object to represent +a computation and a promise object to represent the delivery of the result of +the computation. This is a nice separation of computation and delivery because +consumers of the promise cannot modify the value that will be eventually +delivered. + +One side effect of being able to implement promise resolution and chaining +iteratively is that you need to be able for one promise to reach into the state +of another promise to shuffle around ownership of handlers. In order to achieve +this without making the handlers of a promise publicly mutable, a promise is +also the deferred value, allowing promises of the same parent class to reach +into and modify the private properties of promises of the same type. While this +does allow consumers of the value to modify the resolution or rejection of the +deferred, it is a small price to pay for keeping the stack size constant. + +```php +$promise = new Promise(); +$promise->then(function ($value) { echo $value; }); +// The promise is the deferred value, so you can deliver a value to it. +$promise->resolve('foo'); +// prints "foo" +``` + + +## Upgrading from Function API + +A static API was first introduced in 1.4.0, in order to mitigate problems with +functions conflicting between global and local copies of the package. The +function API will be removed in 2.0.0. A migration table has been provided here +for your convenience: + +| Original Function | Replacement Method | +|----------------|----------------| +| `queue` | `Utils::queue` | +| `task` | `Utils::task` | +| `promise_for` | `Create::promiseFor` | +| `rejection_for` | `Create::rejectionFor` | +| `exception_for` | `Create::exceptionFor` | +| `iter_for` | `Create::iterFor` | +| `inspect` | `Utils::inspect` | +| `inspect_all` | `Utils::inspectAll` | +| `unwrap` | `Utils::unwrap` | +| `all` | `Utils::all` | +| `some` | `Utils::some` | +| `any` | `Utils::any` | +| `settle` | `Utils::settle` | +| `each` | `Each::of` | +| `each_limit` | `Each::ofLimit` | +| `each_limit_all` | `Each::ofLimitAll` | +| `!is_fulfilled` | `Is::pending` | +| `is_fulfilled` | `Is::fulfilled` | +| `is_rejected` | `Is::rejected` | +| `is_settled` | `Is::settled` | +| `coroutine` | `Coroutine::of` | + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/promises/security/policy) for more information. + + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-promises?utm_source=packagist-guzzlehttp-promises&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/composer.json b/data/web/inc/lib/vendor/guzzlehttp/promises/composer.json new file mode 100644 index 0000000000..c959fb32b4 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/composer.json @@ -0,0 +1,58 @@ +{ + "name": "guzzlehttp/promises", + "description": "Guzzle promises library", + "keywords": ["promise"], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Promise\\Tests\\": "tests/" + } + }, + "scripts": { + "test": "vendor/bin/simple-phpunit", + "test-ci": "vendor/bin/simple-phpunit --coverage-text" + }, + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "config": { + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/AggregateException.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/AggregateException.php new file mode 100644 index 0000000000..d2b5712b92 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/AggregateException.php @@ -0,0 +1,17 @@ +then(function ($v) { echo $v; }); + * + * @param callable $generatorFn Generator function to wrap into a promise. + * + * @return Promise + * + * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration + */ +final class Coroutine implements PromiseInterface +{ + /** + * @var PromiseInterface|null + */ + private $currentPromise; + + /** + * @var Generator + */ + private $generator; + + /** + * @var Promise + */ + private $result; + + public function __construct(callable $generatorFn) + { + $this->generator = $generatorFn(); + $this->result = new Promise(function () { + while (isset($this->currentPromise)) { + $this->currentPromise->wait(); + } + }); + try { + $this->nextCoroutine($this->generator->current()); + } catch (\Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * Create a new coroutine. + * + * @return self + */ + public static function of(callable $generatorFn) + { + return new self($generatorFn); + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + return $this->result->then($onFulfilled, $onRejected); + } + + public function otherwise(callable $onRejected) + { + return $this->result->otherwise($onRejected); + } + + public function wait($unwrap = true) + { + return $this->result->wait($unwrap); + } + + public function getState() + { + return $this->result->getState(); + } + + public function resolve($value) + { + $this->result->resolve($value); + } + + public function reject($reason) + { + $this->result->reject($reason); + } + + public function cancel() + { + $this->currentPromise->cancel(); + $this->result->cancel(); + } + + private function nextCoroutine($yielded) + { + $this->currentPromise = Create::promiseFor($yielded) + ->then([$this, '_handleSuccess'], [$this, '_handleFailure']); + } + + /** + * @internal + */ + public function _handleSuccess($value) + { + unset($this->currentPromise); + try { + $next = $this->generator->send($value); + if ($this->generator->valid()) { + $this->nextCoroutine($next); + } else { + $this->result->resolve($value); + } + } catch (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * @internal + */ + public function _handleFailure($reason) + { + unset($this->currentPromise); + try { + $nextYield = $this->generator->throw(Create::exceptionFor($reason)); + // The throw was caught, so keep iterating on the coroutine + $this->nextCoroutine($nextYield); + } catch (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/Create.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/Create.php new file mode 100644 index 0000000000..8d038e9c1c --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/Create.php @@ -0,0 +1,84 @@ +then([$promise, 'resolve'], [$promise, 'reject']); + return $promise; + } + + return new FulfilledPromise($value); + } + + /** + * Creates a rejected promise for a reason if the reason is not a promise. + * If the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + * + * @return PromiseInterface + */ + public static function rejectionFor($reason) + { + if ($reason instanceof PromiseInterface) { + return $reason; + } + + return new RejectedPromise($reason); + } + + /** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception|\Throwable + */ + public static function exceptionFor($reason) + { + if ($reason instanceof \Exception || $reason instanceof \Throwable) { + return $reason; + } + + return new RejectionException($reason); + } + + /** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + */ + public static function iterFor($value) + { + if ($value instanceof \Iterator) { + return $value; + } + + if (is_array($value)) { + return new \ArrayIterator($value); + } + + return new \ArrayIterator([$value]); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/Each.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/Each.php new file mode 100644 index 0000000000..1dda354993 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/Each.php @@ -0,0 +1,90 @@ + $onFulfilled, + 'rejected' => $onRejected + ]))->promise(); + } + + /** + * Like of, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow + * for dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + */ + public static function ofLimit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null + ) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected, + 'concurrency' => $concurrency + ]))->promise(); + } + + /** + * Like limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * + * @return PromiseInterface + */ + public static function ofLimitAll( + $iterable, + $concurrency, + callable $onFulfilled = null + ) { + return each_limit( + $iterable, + $concurrency, + $onFulfilled, + function ($reason, $idx, PromiseInterface $aggregate) { + $aggregate->reject($reason); + } + ); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/EachPromise.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/EachPromise.php new file mode 100644 index 0000000000..280d79950d --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/EachPromise.php @@ -0,0 +1,247 @@ +iterable = Create::iterFor($iterable); + + if (isset($config['concurrency'])) { + $this->concurrency = $config['concurrency']; + } + + if (isset($config['fulfilled'])) { + $this->onFulfilled = $config['fulfilled']; + } + + if (isset($config['rejected'])) { + $this->onRejected = $config['rejected']; + } + } + + /** @psalm-suppress InvalidNullableReturnType */ + public function promise() + { + if ($this->aggregate) { + return $this->aggregate; + } + + try { + $this->createPromise(); + /** @psalm-assert Promise $this->aggregate */ + $this->iterable->rewind(); + $this->refillPending(); + } catch (\Throwable $e) { + $this->aggregate->reject($e); + } catch (\Exception $e) { + $this->aggregate->reject($e); + } + + /** + * @psalm-suppress NullableReturnStatement + * @phpstan-ignore-next-line + */ + return $this->aggregate; + } + + private function createPromise() + { + $this->mutex = false; + $this->aggregate = new Promise(function () { + if ($this->checkIfFinished()) { + return; + } + reset($this->pending); + // Consume a potentially fluctuating list of promises while + // ensuring that indexes are maintained (precluding array_shift). + while ($promise = current($this->pending)) { + next($this->pending); + $promise->wait(); + if (Is::settled($this->aggregate)) { + return; + } + } + }); + + // Clear the references when the promise is resolved. + $clearFn = function () { + $this->iterable = $this->concurrency = $this->pending = null; + $this->onFulfilled = $this->onRejected = null; + $this->nextPendingIndex = 0; + }; + + $this->aggregate->then($clearFn, $clearFn); + } + + private function refillPending() + { + if (!$this->concurrency) { + // Add all pending promises. + while ($this->addPending() && $this->advanceIterator()); + return; + } + + // Add only up to N pending promises. + $concurrency = is_callable($this->concurrency) + ? call_user_func($this->concurrency, count($this->pending)) + : $this->concurrency; + $concurrency = max($concurrency - count($this->pending), 0); + // Concurrency may be set to 0 to disallow new promises. + if (!$concurrency) { + return; + } + // Add the first pending promise. + $this->addPending(); + // Note this is special handling for concurrency=1 so that we do + // not advance the iterator after adding the first promise. This + // helps work around issues with generators that might not have the + // next value to yield until promise callbacks are called. + while (--$concurrency + && $this->advanceIterator() + && $this->addPending()); + } + + private function addPending() + { + if (!$this->iterable || !$this->iterable->valid()) { + return false; + } + + $promise = Create::promiseFor($this->iterable->current()); + $key = $this->iterable->key(); + + // Iterable keys may not be unique, so we use a counter to + // guarantee uniqueness + $idx = $this->nextPendingIndex++; + + $this->pending[$idx] = $promise->then( + function ($value) use ($idx, $key) { + if ($this->onFulfilled) { + call_user_func( + $this->onFulfilled, + $value, + $key, + $this->aggregate + ); + } + $this->step($idx); + }, + function ($reason) use ($idx, $key) { + if ($this->onRejected) { + call_user_func( + $this->onRejected, + $reason, + $key, + $this->aggregate + ); + } + $this->step($idx); + } + ); + + return true; + } + + private function advanceIterator() + { + // Place a lock on the iterator so that we ensure to not recurse, + // preventing fatal generator errors. + if ($this->mutex) { + return false; + } + + $this->mutex = true; + + try { + $this->iterable->next(); + $this->mutex = false; + return true; + } catch (\Throwable $e) { + $this->aggregate->reject($e); + $this->mutex = false; + return false; + } catch (\Exception $e) { + $this->aggregate->reject($e); + $this->mutex = false; + return false; + } + } + + private function step($idx) + { + // If the promise was already resolved, then ignore this step. + if (Is::settled($this->aggregate)) { + return; + } + + unset($this->pending[$idx]); + + // Only refill pending promises if we are not locked, preventing the + // EachPromise to recursively invoke the provided iterator, which + // cause a fatal error: "Cannot resume an already running generator" + if ($this->advanceIterator() && !$this->checkIfFinished()) { + // Add more pending promises if possible. + $this->refillPending(); + } + } + + private function checkIfFinished() + { + if (!$this->pending && !$this->iterable->valid()) { + // Resolve the promise if there's nothing left to do. + $this->aggregate->resolve(null); + return true; + } + + return false; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/FulfilledPromise.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/FulfilledPromise.php new file mode 100644 index 0000000000..98f72a62a6 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/FulfilledPromise.php @@ -0,0 +1,84 @@ +value = $value; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // Return itself if there is no onFulfilled function. + if (!$onFulfilled) { + return $this; + } + + $queue = Utils::queue(); + $p = new Promise([$queue, 'run']); + $value = $this->value; + $queue->add(static function () use ($p, $value, $onFulfilled) { + if (Is::pending($p)) { + try { + $p->resolve($onFulfilled($value)); + } catch (\Throwable $e) { + $p->reject($e); + } catch (\Exception $e) { + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + return $unwrap ? $this->value : null; + } + + public function getState() + { + return self::FULFILLED; + } + + public function resolve($value) + { + if ($value !== $this->value) { + throw new \LogicException("Cannot resolve a fulfilled promise"); + } + } + + public function reject($reason) + { + throw new \LogicException("Cannot reject a fulfilled promise"); + } + + public function cancel() + { + // pass + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/Is.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/Is.php new file mode 100644 index 0000000000..c3ed8d0143 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/Is.php @@ -0,0 +1,46 @@ +getState() === PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled or rejected. + * + * @return bool + */ + public static function settled(PromiseInterface $promise) + { + return $promise->getState() !== PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled. + * + * @return bool + */ + public static function fulfilled(PromiseInterface $promise) + { + return $promise->getState() === PromiseInterface::FULFILLED; + } + + /** + * Returns true if a promise is rejected. + * + * @return bool + */ + public static function rejected(PromiseInterface $promise) + { + return $promise->getState() === PromiseInterface::REJECTED; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/Promise.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/Promise.php new file mode 100644 index 0000000000..75939057b6 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/Promise.php @@ -0,0 +1,278 @@ +waitFn = $waitFn; + $this->cancelFn = $cancelFn; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + if ($this->state === self::PENDING) { + $p = new Promise(null, [$this, 'cancel']); + $this->handlers[] = [$p, $onFulfilled, $onRejected]; + $p->waitList = $this->waitList; + $p->waitList[] = $this; + return $p; + } + + // Return a fulfilled promise and immediately invoke any callbacks. + if ($this->state === self::FULFILLED) { + $promise = Create::promiseFor($this->result); + return $onFulfilled ? $promise->then($onFulfilled) : $promise; + } + + // It's either cancelled or rejected, so return a rejected promise + // and immediately invoke any callbacks. + $rejection = Create::rejectionFor($this->result); + return $onRejected ? $rejection->then(null, $onRejected) : $rejection; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true) + { + $this->waitIfPending(); + + if ($this->result instanceof PromiseInterface) { + return $this->result->wait($unwrap); + } + if ($unwrap) { + if ($this->state === self::FULFILLED) { + return $this->result; + } + // It's rejected so "unwrap" and throw an exception. + throw Create::exceptionFor($this->result); + } + } + + public function getState() + { + return $this->state; + } + + public function cancel() + { + if ($this->state !== self::PENDING) { + return; + } + + $this->waitFn = $this->waitList = null; + + if ($this->cancelFn) { + $fn = $this->cancelFn; + $this->cancelFn = null; + try { + $fn(); + } catch (\Throwable $e) { + $this->reject($e); + } catch (\Exception $e) { + $this->reject($e); + } + } + + // Reject the promise only if it wasn't rejected in a then callback. + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject(new CancellationException('Promise has been cancelled')); + } + } + + public function resolve($value) + { + $this->settle(self::FULFILLED, $value); + } + + public function reject($reason) + { + $this->settle(self::REJECTED, $reason); + } + + private function settle($state, $value) + { + if ($this->state !== self::PENDING) { + // Ignore calls with the same resolution. + if ($state === $this->state && $value === $this->result) { + return; + } + throw $this->state === $state + ? new \LogicException("The promise is already {$state}.") + : new \LogicException("Cannot change a {$this->state} promise to {$state}"); + } + + if ($value === $this) { + throw new \LogicException('Cannot fulfill or reject a promise with itself'); + } + + // Clear out the state of the promise but stash the handlers. + $this->state = $state; + $this->result = $value; + $handlers = $this->handlers; + $this->handlers = null; + $this->waitList = $this->waitFn = null; + $this->cancelFn = null; + + if (!$handlers) { + return; + } + + // If the value was not a settled promise or a thenable, then resolve + // it in the task queue using the correct ID. + if (!is_object($value) || !method_exists($value, 'then')) { + $id = $state === self::FULFILLED ? 1 : 2; + // It's a success, so resolve the handlers in the queue. + Utils::queue()->add(static function () use ($id, $value, $handlers) { + foreach ($handlers as $handler) { + self::callHandler($id, $value, $handler); + } + }); + } elseif ($value instanceof Promise && Is::pending($value)) { + // We can just merge our handlers onto the next promise. + $value->handlers = array_merge($value->handlers, $handlers); + } else { + // Resolve the handlers when the forwarded promise is resolved. + $value->then( + static function ($value) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(1, $value, $handler); + } + }, + static function ($reason) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(2, $reason, $handler); + } + } + ); + } + } + + /** + * Call a stack of handlers using a specific callback index and value. + * + * @param int $index 1 (resolve) or 2 (reject). + * @param mixed $value Value to pass to the callback. + * @param array $handler Array of handler data (promise and callbacks). + */ + private static function callHandler($index, $value, array $handler) + { + /** @var PromiseInterface $promise */ + $promise = $handler[0]; + + // The promise may have been cancelled or resolved before placing + // this thunk in the queue. + if (Is::settled($promise)) { + return; + } + + try { + if (isset($handler[$index])) { + /* + * If $f throws an exception, then $handler will be in the exception + * stack trace. Since $handler contains a reference to the callable + * itself we get a circular reference. We clear the $handler + * here to avoid that memory leak. + */ + $f = $handler[$index]; + unset($handler); + $promise->resolve($f($value)); + } elseif ($index === 1) { + // Forward resolution values as-is. + $promise->resolve($value); + } else { + // Forward rejections down the chain. + $promise->reject($value); + } + } catch (\Throwable $reason) { + $promise->reject($reason); + } catch (\Exception $reason) { + $promise->reject($reason); + } + } + + private function waitIfPending() + { + if ($this->state !== self::PENDING) { + return; + } elseif ($this->waitFn) { + $this->invokeWaitFn(); + } elseif ($this->waitList) { + $this->invokeWaitList(); + } else { + // If there's no wait function, then reject the promise. + $this->reject('Cannot wait on a promise that has ' + . 'no internal wait function. You must provide a wait ' + . 'function when constructing the promise to be able to ' + . 'wait on a promise.'); + } + + Utils::queue()->run(); + + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject('Invoking the wait callback did not resolve the promise'); + } + } + + private function invokeWaitFn() + { + try { + $wfn = $this->waitFn; + $this->waitFn = null; + $wfn(true); + } catch (\Exception $reason) { + if ($this->state === self::PENDING) { + // The promise has not been resolved yet, so reject the promise + // with the exception. + $this->reject($reason); + } else { + // The promise was already resolved, so there's a problem in + // the application. + throw $reason; + } + } + } + + private function invokeWaitList() + { + $waitList = $this->waitList; + $this->waitList = null; + + foreach ($waitList as $result) { + do { + $result->waitIfPending(); + $result = $result->result; + } while ($result instanceof Promise); + + if ($result instanceof PromiseInterface) { + $result->wait(false); + } + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/PromiseInterface.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/PromiseInterface.php new file mode 100644 index 0000000000..e598331435 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/PromiseInterface.php @@ -0,0 +1,97 @@ +reason = $reason; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // If there's no onRejected callback then just return self. + if (!$onRejected) { + return $this; + } + + $queue = Utils::queue(); + $reason = $this->reason; + $p = new Promise([$queue, 'run']); + $queue->add(static function () use ($p, $reason, $onRejected) { + if (Is::pending($p)) { + try { + // Return a resolved promise if onRejected does not throw. + $p->resolve($onRejected($reason)); + } catch (\Throwable $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } catch (\Exception $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + if ($unwrap) { + throw Create::exceptionFor($this->reason); + } + + return null; + } + + public function getState() + { + return self::REJECTED; + } + + public function resolve($value) + { + throw new \LogicException("Cannot resolve a rejected promise"); + } + + public function reject($reason) + { + if ($reason !== $this->reason) { + throw new \LogicException("Cannot reject a rejected promise"); + } + } + + public function cancel() + { + // pass + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/RejectionException.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/RejectionException.php new file mode 100644 index 0000000000..e2f137707d --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/RejectionException.php @@ -0,0 +1,48 @@ +reason = $reason; + + $message = 'The promise was rejected'; + + if ($description) { + $message .= ' with reason: ' . $description; + } elseif (is_string($reason) + || (is_object($reason) && method_exists($reason, '__toString')) + ) { + $message .= ' with reason: ' . $this->reason; + } elseif ($reason instanceof \JsonSerializable) { + $message .= ' with reason: ' + . json_encode($this->reason, JSON_PRETTY_PRINT); + } + + parent::__construct($message); + } + + /** + * Returns the rejection reason. + * + * @return mixed + */ + public function getReason() + { + return $this->reason; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/TaskQueue.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/TaskQueue.php new file mode 100644 index 0000000000..f0fba2c594 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/TaskQueue.php @@ -0,0 +1,67 @@ +run(); + */ +class TaskQueue implements TaskQueueInterface +{ + private $enableShutdown = true; + private $queue = []; + + public function __construct($withShutdown = true) + { + if ($withShutdown) { + register_shutdown_function(function () { + if ($this->enableShutdown) { + // Only run the tasks if an E_ERROR didn't occur. + $err = error_get_last(); + if (!$err || ($err['type'] ^ E_ERROR)) { + $this->run(); + } + } + }); + } + } + + public function isEmpty() + { + return !$this->queue; + } + + public function add(callable $task) + { + $this->queue[] = $task; + } + + public function run() + { + while ($task = array_shift($this->queue)) { + /** @var callable $task */ + $task(); + } + } + + /** + * The task queue will be run and exhausted by default when the process + * exits IFF the exit is not the result of a PHP E_ERROR error. + * + * You can disable running the automatic shutdown of the queue by calling + * this function. If you disable the task queue shutdown process, then you + * MUST either run the task queue (as a result of running your event loop + * or manually using the run() method) or wait on each outstanding promise. + * + * Note: This shutdown will occur before any destructors are triggered. + */ + public function disableShutdown() + { + $this->enableShutdown = false; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/TaskQueueInterface.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/TaskQueueInterface.php new file mode 100644 index 0000000000..723d4d54eb --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/TaskQueueInterface.php @@ -0,0 +1,24 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\Utils::queue()->run(); + * } + * + * + * @param TaskQueueInterface $assign Optionally specify a new queue instance. + * + * @return TaskQueueInterface + */ + public static function queue(TaskQueueInterface $assign = null) + { + static $queue; + + if ($assign) { + $queue = $assign; + } elseif (!$queue) { + $queue = new TaskQueue(); + } + + return $queue; + } + + /** + * Adds a function to run in the task queue when it is next `run()` and + * returns a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + * + * @return PromiseInterface + */ + public static function task(callable $task) + { + $queue = self::queue(); + $promise = new Promise([$queue, 'run']); + $queue->add(function () use ($task, $promise) { + try { + if (Is::pending($promise)) { + $promise->resolve($task()); + } + } catch (\Throwable $e) { + $promise->reject($e); + } catch (\Exception $e) { + $promise->reject($e); + } + }); + + return $promise; + } + + /** + * Synchronously waits on a promise to resolve and returns an inspection + * state array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the + * array will contain a "value" key mapping to the fulfilled value of the + * promise. If the promise is rejected, the array will contain a "reason" + * key mapping to the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + * + * @return array + */ + public static function inspect(PromiseInterface $promise) + { + try { + return [ + 'state' => PromiseInterface::FULFILLED, + 'value' => $promise->wait() + ]; + } catch (RejectionException $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()]; + } catch (\Throwable $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } catch (\Exception $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } + } + + /** + * Waits on all of the provided promises, but does not unwrap rejected + * promises as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + * + * @return array + */ + public static function inspectAll($promises) + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = inspect($promise); + } + + return $results; + } + + /** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same + * order the promises were provided). An exception is thrown if any of the + * promises are rejected. + * + * @param iterable $promises Iterable of PromiseInterface objects to wait on. + * + * @return array + * + * @throws \Exception on error + * @throws \Throwable on error in PHP >=7 + */ + public static function unwrap($promises) + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = $promise->wait(); + } + + return $results; + } + + /** + * Given an array of promises, return a promise that is fulfilled when all + * the items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution. + * + * @return PromiseInterface + */ + public static function all($promises, $recursive = false) + { + $results = []; + $promise = Each::of( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = $value; + }, + function ($reason, $idx, Promise $aggregate) { + $aggregate->reject($reason); + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); + + if (true === $recursive) { + $promise = $promise->then(function ($results) use ($recursive, &$promises) { + foreach ($promises as $promise) { + if (Is::pending($promise)) { + return self::all($promises, $recursive); + } + } + return $results; + }); + } + + return $promise; + } + + /** + * Initiate a competitive race between multiple promises or values (values + * will become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise + * is fulfilled with an array that contains the fulfillment values of the + * winners in order of resolution. + * + * This promise is rejected with a {@see AggregateException} if the number + * of fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ + public static function some($count, $promises) + { + $results = []; + $rejections = []; + + return Each::of( + $promises, + function ($value, $idx, PromiseInterface $p) use (&$results, $count) { + if (Is::settled($p)) { + return; + } + $results[$idx] = $value; + if (count($results) >= $count) { + $p->resolve(null); + } + }, + function ($reason) use (&$rejections) { + $rejections[] = $reason; + } + )->then( + function () use (&$results, &$rejections, $count) { + if (count($results) !== $count) { + throw new AggregateException( + 'Not enough promises to fulfill count', + $rejections + ); + } + ksort($results); + return array_values($results); + } + ); + } + + /** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ + public static function any($promises) + { + return self::some(1, $promises)->then(function ($values) { + return $values[0]; + }); + } + + /** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ + public static function settle($promises) + { + $results = []; + + return Each::of( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; + }, + function ($reason, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/functions.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/functions.php new file mode 100644 index 0000000000..c03d39d021 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/functions.php @@ -0,0 +1,363 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\queue()->run(); + * } + * + * + * @param TaskQueueInterface $assign Optionally specify a new queue instance. + * + * @return TaskQueueInterface + * + * @deprecated queue will be removed in guzzlehttp/promises:2.0. Use Utils::queue instead. + */ +function queue(TaskQueueInterface $assign = null) +{ + return Utils::queue($assign); +} + +/** + * Adds a function to run in the task queue when it is next `run()` and returns + * a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + * + * @return PromiseInterface + * + * @deprecated task will be removed in guzzlehttp/promises:2.0. Use Utils::task instead. + */ +function task(callable $task) +{ + return Utils::task($task); +} + +/** + * Creates a promise for a value if the value is not a promise. + * + * @param mixed $value Promise or value. + * + * @return PromiseInterface + * + * @deprecated promise_for will be removed in guzzlehttp/promises:2.0. Use Create::promiseFor instead. + */ +function promise_for($value) +{ + return Create::promiseFor($value); +} + +/** + * Creates a rejected promise for a reason if the reason is not a promise. If + * the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + * + * @return PromiseInterface + * + * @deprecated rejection_for will be removed in guzzlehttp/promises:2.0. Use Create::rejectionFor instead. + */ +function rejection_for($reason) +{ + return Create::rejectionFor($reason); +} + +/** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception|\Throwable + * + * @deprecated exception_for will be removed in guzzlehttp/promises:2.0. Use Create::exceptionFor instead. + */ +function exception_for($reason) +{ + return Create::exceptionFor($reason); +} + +/** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + * + * @deprecated iter_for will be removed in guzzlehttp/promises:2.0. Use Create::iterFor instead. + */ +function iter_for($value) +{ + return Create::iterFor($value); +} + +/** + * Synchronously waits on a promise to resolve and returns an inspection state + * array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the array + * will contain a "value" key mapping to the fulfilled value of the promise. If + * the promise is rejected, the array will contain a "reason" key mapping to + * the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + * + * @return array + * + * @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspect instead. + */ +function inspect(PromiseInterface $promise) +{ + return Utils::inspect($promise); +} + +/** + * Waits on all of the provided promises, but does not unwrap rejected promises + * as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + * + * @return array + * + * @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspectAll instead. + */ +function inspect_all($promises) +{ + return Utils::inspectAll($promises); +} + +/** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same order + * the promises were provided). An exception is thrown if any of the promises + * are rejected. + * + * @param iterable $promises Iterable of PromiseInterface objects to wait on. + * + * @return array + * + * @throws \Exception on error + * @throws \Throwable on error in PHP >=7 + * + * @deprecated unwrap will be removed in guzzlehttp/promises:2.0. Use Utils::unwrap instead. + */ +function unwrap($promises) +{ + return Utils::unwrap($promises); +} + +/** + * Given an array of promises, return a promise that is fulfilled when all the + * items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution. + * + * @return PromiseInterface + * + * @deprecated all will be removed in guzzlehttp/promises:2.0. Use Utils::all instead. + */ +function all($promises, $recursive = false) +{ + return Utils::all($promises, $recursive); +} + +/** + * Initiate a competitive race between multiple promises or values (values will + * become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise is + * fulfilled with an array that contains the fulfillment values of the winners + * in order of resolution. + * + * This promise is rejected with a {@see AggregateException} if the number of + * fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * + * @deprecated some will be removed in guzzlehttp/promises:2.0. Use Utils::some instead. + */ +function some($count, $promises) +{ + return Utils::some($count, $promises); +} + +/** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * + * @deprecated any will be removed in guzzlehttp/promises:2.0. Use Utils::any instead. + */ +function any($promises) +{ + return Utils::any($promises); +} + +/** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * + * @deprecated settle will be removed in guzzlehttp/promises:2.0. Use Utils::settle instead. + */ +function settle($promises) +{ + return Utils::settle($promises); +} + +/** + * Given an iterator that yields promises or values, returns a promise that is + * fulfilled with a null value when the iterator has been consumed or the + * aggregate promise has been fulfilled or rejected. + * + * $onFulfilled is a function that accepts the fulfilled value, iterator index, + * and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate if needed. + * + * $onRejected is a function that accepts the rejection reason, iterator index, + * and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate if needed. + * + * @param mixed $iterable Iterator or array to iterate over. + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + * + * @deprecated each will be removed in guzzlehttp/promises:2.0. Use Each::of instead. + */ +function each( + $iterable, + callable $onFulfilled = null, + callable $onRejected = null +) { + return Each::of($iterable, $onFulfilled, $onRejected); +} + +/** + * Like each, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow for + * dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + * + * @deprecated each_limit will be removed in guzzlehttp/promises:2.0. Use Each::ofLimit instead. + */ +function each_limit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null +) { + return Each::ofLimit($iterable, $concurrency, $onFulfilled, $onRejected); +} + +/** + * Like each_limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * + * @return PromiseInterface + * + * @deprecated each_limit_all will be removed in guzzlehttp/promises:2.0. Use Each::ofLimitAll instead. + */ +function each_limit_all( + $iterable, + $concurrency, + callable $onFulfilled = null +) { + return Each::ofLimitAll($iterable, $concurrency, $onFulfilled); +} + +/** + * Returns true if a promise is fulfilled. + * + * @return bool + * + * @deprecated is_fulfilled will be removed in guzzlehttp/promises:2.0. Use Is::fulfilled instead. + */ +function is_fulfilled(PromiseInterface $promise) +{ + return Is::fulfilled($promise); +} + +/** + * Returns true if a promise is rejected. + * + * @return bool + * + * @deprecated is_rejected will be removed in guzzlehttp/promises:2.0. Use Is::rejected instead. + */ +function is_rejected(PromiseInterface $promise) +{ + return Is::rejected($promise); +} + +/** + * Returns true if a promise is fulfilled or rejected. + * + * @return bool + * + * @deprecated is_settled will be removed in guzzlehttp/promises:2.0. Use Is::settled instead. + */ +function is_settled(PromiseInterface $promise) +{ + return Is::settled($promise); +} + +/** + * Create a new coroutine. + * + * @see Coroutine + * + * @return PromiseInterface + * + * @deprecated coroutine will be removed in guzzlehttp/promises:2.0. Use Coroutine::of instead. + */ +function coroutine(callable $generatorFn) +{ + return Coroutine::of($generatorFn); +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/functions_include.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/functions_include.php new file mode 100644 index 0000000000..34cd1710aa --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/functions_include.php @@ -0,0 +1,6 @@ +withPath('foo')->withHost('example.com')` will throw an exception + because the path of a URI with an authority must start with a slash "/" or be empty + - `(new Uri())->withScheme('http')` will return `'http://localhost'` + +### Deprecated + +- `Uri::resolve` in favor of `UriResolver::resolve` +- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments` + +### Fixed + +- `Stream::read` when length parameter <= 0. +- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory. +- `ServerRequest::getUriFromGlobals` when `Host` header contains port. +- Compatibility of URIs with `file` scheme and empty host. + + +## [1.3.1] - 2016-06-25 + +### Fixed + +- `Uri::__toString` for network path references, e.g. `//example.org`. +- Missing lowercase normalization for host. +- Handling of URI components in case they are `'0'` in a lot of places, + e.g. as a user info password. +- `Uri::withAddedHeader` to correctly merge headers with different case. +- Trimming of header values in `Uri::withAddedHeader`. Header values may + be surrounded by whitespace which should be ignored according to RFC 7230 + Section 3.2.4. This does not apply to header names. +- `Uri::withAddedHeader` with an array of header values. +- `Uri::resolve` when base path has no slash and handling of fragment. +- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the + key/value both in encoded as well as decoded form to those methods. This is + consistent with withPath, withQuery etc. +- `ServerRequest::withoutAttribute` when attribute value is null. + + +## [1.3.0] - 2016-04-13 + +### Added + +- Remaining interfaces needed for full PSR7 compatibility + (ServerRequestInterface, UploadedFileInterface, etc.). +- Support for stream_for from scalars. + +### Changed + +- Can now extend Uri. + +### Fixed +- A bug in validating request methods by making it more permissive. + + +## [1.2.3] - 2016-02-18 + +### Fixed + +- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote + streams, which can sometimes return fewer bytes than requested with `fread`. +- Handling of gzipped responses with FNAME headers. + + +## [1.2.2] - 2016-01-22 + +### Added + +- Support for URIs without any authority. +- Support for HTTP 451 'Unavailable For Legal Reasons.' +- Support for using '0' as a filename. +- Support for including non-standard ports in Host headers. + + +## [1.2.1] - 2015-11-02 + +### Changes + +- Now supporting negative offsets when seeking to SEEK_END. + + +## [1.2.0] - 2015-08-15 + +### Changed + +- Body as `"0"` is now properly added to a response. +- Now allowing forward seeking in CachingStream. +- Now properly parsing HTTP requests that contain proxy targets in + `parse_request`. +- functions.php is now conditionally required. +- user-info is no longer dropped when resolving URIs. + + +## [1.1.0] - 2015-06-24 + +### Changed + +- URIs can now be relative. +- `multipart/form-data` headers are now overridden case-insensitively. +- URI paths no longer encode the following characters because they are allowed + in URIs: "(", ")", "*", "!", "'" +- A port is no longer added to a URI when the scheme is missing and no port is + present. + + +## 1.0.0 - 2015-05-19 + +Initial release. + +Currently unsupported: + +- `Psr\Http\Message\ServerRequestInterface` +- `Psr\Http\Message\UploadedFileInterface` + + + +[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0 +[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2 +[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1 +[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0 +[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1 +[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0 +[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1 +[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0 +[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3 +[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2 +[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0 diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/LICENSE b/data/web/inc/lib/vendor/guzzlehttp/psr7/LICENSE new file mode 100644 index 0000000000..51c7ec81cb --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/LICENSE @@ -0,0 +1,26 @@ +The MIT License (MIT) + +Copyright (c) 2015 Michael Dowling +Copyright (c) 2015 Mรกrk Sรกgi-Kazรกr +Copyright (c) 2015 Graham Campbell +Copyright (c) 2016 Tobias Schultze +Copyright (c) 2016 George Mponos +Copyright (c) 2018 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/README.md b/data/web/inc/lib/vendor/guzzlehttp/psr7/README.md new file mode 100644 index 0000000000..9566a7d472 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/README.md @@ -0,0 +1,880 @@ +# PSR-7 Message Implementation + +This repository contains a full [PSR-7](https://www.php-fig.org/psr/psr-7/) +message implementation, several stream decorators, and some helpful +functionality like query string parsing. + +![CI](https://github.com/guzzle/psr7/workflows/CI/badge.svg) +![Static analysis](https://github.com/guzzle/psr7/workflows/Static%20analysis/badge.svg) + + +# Installation + +```shell +composer require guzzlehttp/psr7 +``` + +# Stream implementation + +This package comes with a number of stream implementations and stream +decorators. + + +## AppendStream + +`GuzzleHttp\Psr7\AppendStream` + +Reads from multiple streams, one after the other. + +```php +use GuzzleHttp\Psr7; + +$a = Psr7\Utils::streamFor('abc, '); +$b = Psr7\Utils::streamFor('123.'); +$composed = new Psr7\AppendStream([$a, $b]); + +$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me')); + +echo $composed; // abc, 123. Above all listen to me. +``` + + +## BufferStream + +`GuzzleHttp\Psr7\BufferStream` + +Provides a buffer stream that can be written to fill a buffer, and read +from to remove bytes from the buffer. + +This stream returns a "hwm" metadata value that tells upstream consumers +what the configured high water mark of the stream is, or the maximum +preferred size of the buffer. + +```php +use GuzzleHttp\Psr7; + +// When more than 1024 bytes are in the buffer, it will begin returning +// false to writes. This is an indication that writers should slow down. +$buffer = new Psr7\BufferStream(1024); +``` + + +## CachingStream + +The CachingStream is used to allow seeking over previously read bytes on +non-seekable streams. This can be useful when transferring a non-seekable +entity body fails due to needing to rewind the stream (for example, resulting +from a redirect). Data that is read from the remote stream will be buffered in +a PHP temp stream so that previously read bytes are cached first in memory, +then on disk. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r')); +$stream = new Psr7\CachingStream($original); + +$stream->read(1024); +echo $stream->tell(); +// 1024 + +$stream->seek(0); +echo $stream->tell(); +// 0 +``` + + +## DroppingStream + +`GuzzleHttp\Psr7\DroppingStream` + +Stream decorator that begins dropping data once the size of the underlying +stream becomes too full. + +```php +use GuzzleHttp\Psr7; + +// Create an empty stream +$stream = Psr7\Utils::streamFor(); + +// Start dropping data when the stream has more than 10 bytes +$dropping = new Psr7\DroppingStream($stream, 10); + +$dropping->write('01234567890123456789'); +echo $stream; // 0123456789 +``` + + +## FnStream + +`GuzzleHttp\Psr7\FnStream` + +Compose stream implementations based on a hash of functions. + +Allows for easy testing and extension of a provided stream without needing +to create a concrete class for a simple extension point. + +```php + +use GuzzleHttp\Psr7; + +$stream = Psr7\Utils::streamFor('hi'); +$fnStream = Psr7\FnStream::decorate($stream, [ + 'rewind' => function () use ($stream) { + echo 'About to rewind - '; + $stream->rewind(); + echo 'rewound!'; + } +]); + +$fnStream->rewind(); +// Outputs: About to rewind - rewound! +``` + + +## InflateStream + +`GuzzleHttp\Psr7\InflateStream` + +Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content. + +This stream decorator converts the provided stream to a PHP stream resource, +then appends the zlib.inflate filter. The stream is then converted back +to a Guzzle stream resource to be used as a Guzzle stream. + + +## LazyOpenStream + +`GuzzleHttp\Psr7\LazyOpenStream` + +Lazily reads or writes to a file that is opened only after an IO operation +take place on the stream. + +```php +use GuzzleHttp\Psr7; + +$stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); +// The file has not yet been opened... + +echo $stream->read(10); +// The file is opened and read from only when needed. +``` + + +## LimitStream + +`GuzzleHttp\Psr7\LimitStream` + +LimitStream can be used to read a subset or slice of an existing stream object. +This can be useful for breaking a large file into smaller pieces to be sent in +chunks (e.g. Amazon S3's multipart upload API). + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+')); +echo $original->getSize(); +// >>> 1048576 + +// Limit the size of the body to 1024 bytes and start reading from byte 2048 +$stream = new Psr7\LimitStream($original, 1024, 2048); +echo $stream->getSize(); +// >>> 1024 +echo $stream->tell(); +// >>> 0 +``` + + +## MultipartStream + +`GuzzleHttp\Psr7\MultipartStream` + +Stream that when read returns bytes for a streaming multipart or +multipart/form-data stream. + + +## NoSeekStream + +`GuzzleHttp\Psr7\NoSeekStream` + +NoSeekStream wraps a stream and does not allow seeking. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); +$noSeek = new Psr7\NoSeekStream($original); + +echo $noSeek->read(3); +// foo +var_export($noSeek->isSeekable()); +// false +$noSeek->seek(0); +var_export($noSeek->read(3)); +// NULL +``` + + +## PumpStream + +`GuzzleHttp\Psr7\PumpStream` + +Provides a read only stream that pumps data from a PHP callable. + +When invoking the provided callable, the PumpStream will pass the amount of +data requested to read to the callable. The callable can choose to ignore +this value and return fewer or more bytes than requested. Any extra data +returned by the provided callable is buffered internally until drained using +the read() function of the PumpStream. The provided callable MUST return +false when there is no more data to read. + + +## Implementing stream decorators + +Creating a stream decorator is very easy thanks to the +`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that +implement `Psr\Http\Message\StreamInterface` by proxying to an underlying +stream. Just `use` the `StreamDecoratorTrait` and implement your custom +methods. + +For example, let's say we wanted to call a specific function each time the last +byte is read from a stream. This could be implemented by overriding the +`read()` method. + +```php +use Psr\Http\Message\StreamInterface; +use GuzzleHttp\Psr7\StreamDecoratorTrait; + +class EofCallbackStream implements StreamInterface +{ + use StreamDecoratorTrait; + + private $callback; + + private $stream; + + public function __construct(StreamInterface $stream, callable $cb) + { + $this->stream = $stream; + $this->callback = $cb; + } + + public function read($length) + { + $result = $this->stream->read($length); + + // Invoke the callback when EOF is hit. + if ($this->eof()) { + call_user_func($this->callback); + } + + return $result; + } +} +``` + +This decorator could be added to any existing stream and used like so: + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); + +$eofStream = new EofCallbackStream($original, function () { + echo 'EOF!'; +}); + +$eofStream->read(2); +$eofStream->read(1); +// echoes "EOF!" +$eofStream->seek(0); +$eofStream->read(3); +// echoes "EOF!" +``` + + +## PHP StreamWrapper + +You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a +PSR-7 stream as a PHP stream resource. + +Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP +stream from a PSR-7 stream. + +```php +use GuzzleHttp\Psr7\StreamWrapper; + +$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!'); +$resource = StreamWrapper::getResource($stream); +echo fread($resource, 6); // outputs hello! +``` + + +# Static API + +There are various static methods available under the `GuzzleHttp\Psr7` namespace. + + +## `GuzzleHttp\Psr7\Message::toString` + +`public static function toString(MessageInterface $message): string` + +Returns the string representation of an HTTP message. + +```php +$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); +echo GuzzleHttp\Psr7\Message::toString($request); +``` + + +## `GuzzleHttp\Psr7\Message::bodySummary` + +`public static function bodySummary(MessageInterface $message, int $truncateAt = 120): string|null` + +Get a short summary of the message body. + +Will return `null` if the response is not printable. + + +## `GuzzleHttp\Psr7\Message::rewindBody` + +`public static function rewindBody(MessageInterface $message): void` + +Attempts to rewind a message body and throws an exception on failure. + +The body of the message will only be rewound if a call to `tell()` +returns a value other than `0`. + + +## `GuzzleHttp\Psr7\Message::parseMessage` + +`public static function parseMessage(string $message): array` + +Parses an HTTP message into an associative array. + +The array contains the "start-line" key containing the start line of +the message, "headers" key containing an associative array of header +array values, and a "body" key containing the body of the message. + + +## `GuzzleHttp\Psr7\Message::parseRequestUri` + +`public static function parseRequestUri(string $path, array $headers): string` + +Constructs a URI for an HTTP request message. + + +## `GuzzleHttp\Psr7\Message::parseRequest` + +`public static function parseRequest(string $message): Request` + +Parses a request message string into a request object. + + +## `GuzzleHttp\Psr7\Message::parseResponse` + +`public static function parseResponse(string $message): Response` + +Parses a response message string into a response object. + + +## `GuzzleHttp\Psr7\Header::parse` + +`public static function parse(string|array $header): array` + +Parse an array of header values containing ";" separated data into an +array of associative arrays representing the header key value pair data +of the header. When a parameter does not contain a value, but just +contains a key, this function will inject a key with a '' string value. + + +## `GuzzleHttp\Psr7\Header::splitList` + +`public static function splitList(string|string[] $header): string[]` + +Splits a HTTP header defined to contain a comma-separated list into +each individual value: + +``` +$knownEtags = Header::splitList($request->getHeader('if-none-match')); +``` + +Example headers include `accept`, `cache-control` and `if-none-match`. + + +## `GuzzleHttp\Psr7\Header::normalize` (deprecated) + +`public static function normalize(string|array $header): array` + +`Header::normalize()` is deprecated in favor of [`Header::splitList()`](README.md#guzzlehttppsr7headersplitlist) +which performs the same operation with a cleaned up API and improved +documentation. + +Converts an array of header values that may contain comma separated +headers into an array of headers with no comma separated values. + + +## `GuzzleHttp\Psr7\Query::parse` + +`public static function parse(string $str, int|bool $urlEncoding = true): array` + +Parse a query string into an associative array. + +If multiple values are found for the same key, the value of that key +value pair will become an array. This function does not parse nested +PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2` +will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`. + + +## `GuzzleHttp\Psr7\Query::build` + +`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986): string` + +Build a query string from an array of key value pairs. + +This function can use the return value of `parse()` to build a query +string. This function does not modify the provided keys when an array is +encountered (like `http_build_query()` would). + + +## `GuzzleHttp\Psr7\Utils::caselessRemove` + +`public static function caselessRemove(iterable $keys, $keys, array $data): array` + +Remove the items given by the keys, case insensitively from the data. + + +## `GuzzleHttp\Psr7\Utils::copyToStream` + +`public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void` + +Copy the contents of a stream into another stream until the given number +of bytes have been read. + + +## `GuzzleHttp\Psr7\Utils::copyToString` + +`public static function copyToString(StreamInterface $stream, int $maxLen = -1): string` + +Copy the contents of a stream into a string until the given number of +bytes have been read. + + +## `GuzzleHttp\Psr7\Utils::hash` + +`public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string` + +Calculate a hash of a stream. + +This method reads the entire stream to calculate a rolling hash, based on +PHP's `hash_init` functions. + + +## `GuzzleHttp\Psr7\Utils::modifyRequest` + +`public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface` + +Clone and modify a request with the given changes. + +This method is useful for reducing the number of clones needed to mutate +a message. + +- method: (string) Changes the HTTP method. +- set_headers: (array) Sets the given headers. +- remove_headers: (array) Remove the given headers. +- body: (mixed) Sets the given body. +- uri: (UriInterface) Set the URI. +- query: (string) Set the query string value of the URI. +- version: (string) Set the protocol version. + + +## `GuzzleHttp\Psr7\Utils::readLine` + +`public static function readLine(StreamInterface $stream, int $maxLength = null): string` + +Read a line from the stream up to the maximum allowed buffer length. + + +## `GuzzleHttp\Psr7\Utils::streamFor` + +`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface` + +Create a new stream based on the input type. + +Options is an associative array that can contain the following keys: + +- metadata: Array of custom metadata. +- size: Size of the stream. + +This method accepts the following `$resource` types: + +- `Psr\Http\Message\StreamInterface`: Returns the value as-is. +- `string`: Creates a stream object that uses the given string as the contents. +- `resource`: Creates a stream object that wraps the given PHP stream resource. +- `Iterator`: If the provided value implements `Iterator`, then a read-only + stream object will be created that wraps the given iterable. Each time the + stream is read from, data from the iterator will fill a buffer and will be + continuously called until the buffer is equal to the requested read size. + Subsequent read calls will first read from the buffer and then call `next` + on the underlying iterator until it is exhausted. +- `object` with `__toString()`: If the object has the `__toString()` method, + the object will be cast to a string and then a stream will be returned that + uses the string value. +- `NULL`: When `null` is passed, an empty stream object is returned. +- `callable` When a callable is passed, a read-only stream object will be + created that invokes the given callable. The callable is invoked with the + number of suggested bytes to read. The callable can return any number of + bytes, but MUST return `false` when there is no more data to return. The + stream object that wraps the callable will invoke the callable until the + number of requested bytes are available. Any additional bytes will be + buffered and used in subsequent reads. + +```php +$stream = GuzzleHttp\Psr7\Utils::streamFor('foo'); +$stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r')); + +$generator = function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield ' '; + } +} + +$stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100)); +``` + + +## `GuzzleHttp\Psr7\Utils::tryFopen` + +`public static function tryFopen(string $filename, string $mode): resource` + +Safely opens a PHP stream resource using a filename. + +When fopen fails, PHP normally raises a warning. This function adds an +error handler that checks for errors and throws an exception instead. + + +## `GuzzleHttp\Psr7\Utils::tryGetContents` + +`public static function tryGetContents(resource $stream): string` + +Safely gets the contents of a given stream. + +When stream_get_contents fails, PHP normally raises a warning. This +function adds an error handler that checks for errors and throws an +exception instead. + + +## `GuzzleHttp\Psr7\Utils::uriFor` + +`public static function uriFor(string|UriInterface $uri): UriInterface` + +Returns a UriInterface for the given value. + +This function accepts a string or UriInterface and returns a +UriInterface for the given value. If the value is already a +UriInterface, it is returned as-is. + + +## `GuzzleHttp\Psr7\MimeType::fromFilename` + +`public static function fromFilename(string $filename): string|null` + +Determines the mimetype of a file by looking at its extension. + + +## `GuzzleHttp\Psr7\MimeType::fromExtension` + +`public static function fromExtension(string $extension): string|null` + +Maps a file extensions to a mimetype. + + +## Upgrading from Function API + +The static API was first introduced in 1.7.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API was removed in 2.0.0. A migration table has been provided here for your convenience: + +| Original Function | Replacement Method | +|----------------|----------------| +| `str` | `Message::toString` | +| `uri_for` | `Utils::uriFor` | +| `stream_for` | `Utils::streamFor` | +| `parse_header` | `Header::parse` | +| `normalize_header` | `Header::normalize` | +| `modify_request` | `Utils::modifyRequest` | +| `rewind_body` | `Message::rewindBody` | +| `try_fopen` | `Utils::tryFopen` | +| `copy_to_string` | `Utils::copyToString` | +| `copy_to_stream` | `Utils::copyToStream` | +| `hash` | `Utils::hash` | +| `readline` | `Utils::readLine` | +| `parse_request` | `Message::parseRequest` | +| `parse_response` | `Message::parseResponse` | +| `parse_query` | `Query::parse` | +| `build_query` | `Query::build` | +| `mimetype_from_filename` | `MimeType::fromFilename` | +| `mimetype_from_extension` | `MimeType::fromExtension` | +| `_parse_message` | `Message::parseMessage` | +| `_parse_request_uri` | `Message::parseRequestUri` | +| `get_message_body_summary` | `Message::bodySummary` | +| `_caseless_remove` | `Utils::caselessRemove` | + + +# Additional URI Methods + +Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class, +this library also provides additional functionality when working with URIs as static methods. + +## URI Types + +An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference. +An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI, +the base URI. Relative references can be divided into several forms according to +[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2): + +- network-path references, e.g. `//example.com/path` +- absolute-path references, e.g. `/path` +- relative-path references, e.g. `subpath` + +The following methods can be used to identify the type of the URI. + +### `GuzzleHttp\Psr7\Uri::isAbsolute` + +`public static function isAbsolute(UriInterface $uri): bool` + +Whether the URI is absolute, i.e. it has a scheme. + +### `GuzzleHttp\Psr7\Uri::isNetworkPathReference` + +`public static function isNetworkPathReference(UriInterface $uri): bool` + +Whether the URI is a network-path reference. A relative reference that begins with two slash characters is +termed an network-path reference. + +### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference` + +`public static function isAbsolutePathReference(UriInterface $uri): bool` + +Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is +termed an absolute-path reference. + +### `GuzzleHttp\Psr7\Uri::isRelativePathReference` + +`public static function isRelativePathReference(UriInterface $uri): bool` + +Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is +termed a relative-path reference. + +### `GuzzleHttp\Psr7\Uri::isSameDocumentReference` + +`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool` + +Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its +fragment component, identical to the base URI. When no base URI is given, only an empty URI reference +(apart from its fragment) is considered a same-document reference. + +## URI Components + +Additional methods to work with URI components. + +### `GuzzleHttp\Psr7\Uri::isDefaultPort` + +`public static function isDefaultPort(UriInterface $uri): bool` + +Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null +or the standard port. This method can be used independently of the implementation. + +### `GuzzleHttp\Psr7\Uri::composeComponents` + +`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string` + +Composes a URI reference string from its various components according to +[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called +manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`. + +### `GuzzleHttp\Psr7\Uri::fromParts` + +`public static function fromParts(array $parts): UriInterface` + +Creates a URI from a hash of [`parse_url`](https://www.php.net/manual/en/function.parse-url.php) components. + + +### `GuzzleHttp\Psr7\Uri::withQueryValue` + +`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface` + +Creates a new URI with a specific query string value. Any existing query string values that exactly match the +provided key are removed and replaced with the given key value pair. A value of null will set the query string +key without a value, e.g. "key" instead of "key=value". + +### `GuzzleHttp\Psr7\Uri::withQueryValues` + +`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface` + +Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an +associative array of key => value. + +### `GuzzleHttp\Psr7\Uri::withoutQueryValue` + +`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface` + +Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the +provided key are removed. + +## Cross-Origin Detection + +`GuzzleHttp\Psr7\UriComparator` provides methods to determine if a modified URL should be considered cross-origin. + +### `GuzzleHttp\Psr7\UriComparator::isCrossOrigin` + +`public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool` + +Determines if a modified URL should be considered cross-origin with respect to an original URL. + +## Reference Resolution + +`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according +to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers +do when resolving a link in a website based on the current request URI. + +### `GuzzleHttp\Psr7\UriResolver::resolve` + +`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface` + +Converts the relative URI into a new URI that is resolved against the base URI. + +### `GuzzleHttp\Psr7\UriResolver::removeDotSegments` + +`public static function removeDotSegments(string $path): string` + +Removes dot segments from a path and returns the new path according to +[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4). + +### `GuzzleHttp\Psr7\UriResolver::relativize` + +`public static function relativize(UriInterface $base, UriInterface $target): UriInterface` + +Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve(): + +```php +(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) +``` + +One use-case is to use the current request URI as base URI and then generate relative links in your documents +to reduce the document size or offer self-contained downloadable document archives. + +```php +$base = new Uri('http://example.com/a/b/'); +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. +echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. +``` + +## Normalization and Comparison + +`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to +[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6). + +### `GuzzleHttp\Psr7\UriNormalizer::normalize` + +`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface` + +Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface. +This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask +of normalizations to apply. The following normalizations are available: + +- `UriNormalizer::PRESERVING_NORMALIZATIONS` + + Default normalizations which only include the ones that preserve semantics. + +- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING` + + All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized. + + Example: `http://example.org/a%c2%b1b` โ†’ `http://example.org/a%C2%B1b` + +- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS` + + Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of + ALPHA (%41โ€“%5A and %61โ€“%7A), DIGIT (%30โ€“%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should + not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved + characters by URI normalizers. + + Example: `http://example.org/%7Eusern%61me/` โ†’ `http://example.org/~username/` + +- `UriNormalizer::CONVERT_EMPTY_PATH` + + Converts the empty path to "/" for http and https URIs. + + Example: `http://example.org` โ†’ `http://example.org/` + +- `UriNormalizer::REMOVE_DEFAULT_HOST` + + Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host + "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to + RFC 3986. + + Example: `file://localhost/myfile` โ†’ `file:///myfile` + +- `UriNormalizer::REMOVE_DEFAULT_PORT` + + Removes the default port of the given URI scheme from the URI. + + Example: `http://example.org:80/` โ†’ `http://example.org/` + +- `UriNormalizer::REMOVE_DOT_SEGMENTS` + + Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would + change the semantics of the URI reference. + + Example: `http://example.org/../a/b/../c/./d.html` โ†’ `http://example.org/a/c/d.html` + +- `UriNormalizer::REMOVE_DUPLICATE_SLASHES` + + Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes + and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization + may change the semantics. Encoded slashes (%2F) are not removed. + + Example: `http://example.org//foo///bar.html` โ†’ `http://example.org/foo/bar.html` + +- `UriNormalizer::SORT_QUERY_PARAMETERS` + + Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be + significant (this is not defined by the standard). So this normalization is not safe and may change the semantics + of the URI. + + Example: `?lang=en&article=fred` โ†’ `?article=fred&lang=en` + +### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent` + +`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool` + +Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given +`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent. +This of course assumes they will be resolved against the same base URI. If this is not the case, determination of +equivalence or difference of relative references does not mean anything. + + +## Version Guidance + +| Version | Status | PHP Version | +|---------|----------------|------------------| +| 1.x | Security fixes | >=5.4,<8.1 | +| 2.x | Latest | ^7.2.5 \|\| ^8.0 | + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/psr7/security/policy) for more information. + + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-psr7?utm_source=packagist-guzzlehttp-psr7&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/composer.json b/data/web/inc/lib/vendor/guzzlehttp/psr7/composer.json new file mode 100644 index 0000000000..c0e7236c3e --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/composer.json @@ -0,0 +1,93 @@ +{ + "name": "guzzlehttp/psr7", + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "request", + "response", + "message", + "stream", + "http", + "uri", + "url", + "psr-7" + ], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\Psr7\\": "tests/" + } + }, + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "config": { + "allow-plugins": { + "bamarni/composer-bin-plugin": true + }, + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/AppendStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/AppendStream.php new file mode 100644 index 0000000000..cbcfaee65d --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/AppendStream.php @@ -0,0 +1,248 @@ +addStream($stream); + } + } + + public function __toString(): string + { + try { + $this->rewind(); + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + /** + * Add a stream to the AppendStream + * + * @param StreamInterface $stream Stream to append. Must be readable. + * + * @throws \InvalidArgumentException if the stream is not readable + */ + public function addStream(StreamInterface $stream): void + { + if (!$stream->isReadable()) { + throw new \InvalidArgumentException('Each stream must be readable'); + } + + // The stream is only seekable if all streams are seekable + if (!$stream->isSeekable()) { + $this->seekable = false; + } + + $this->streams[] = $stream; + } + + public function getContents(): string + { + return Utils::copyToString($this); + } + + /** + * Closes each attached stream. + */ + public function close(): void + { + $this->pos = $this->current = 0; + $this->seekable = true; + + foreach ($this->streams as $stream) { + $stream->close(); + } + + $this->streams = []; + } + + /** + * Detaches each attached stream. + * + * Returns null as it's not clear which underlying stream resource to return. + */ + public function detach() + { + $this->pos = $this->current = 0; + $this->seekable = true; + + foreach ($this->streams as $stream) { + $stream->detach(); + } + + $this->streams = []; + + return null; + } + + public function tell(): int + { + return $this->pos; + } + + /** + * Tries to calculate the size by adding the size of each stream. + * + * If any of the streams do not return a valid number, then the size of the + * append stream cannot be determined and null is returned. + */ + public function getSize(): ?int + { + $size = 0; + + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + + return $size; + } + + public function eof(): bool + { + return !$this->streams || + ($this->current >= count($this->streams) - 1 && + $this->streams[$this->current]->eof()); + } + + public function rewind(): void + { + $this->seek(0); + } + + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + */ + public function seek($offset, $whence = SEEK_SET): void + { + if (!$this->seekable) { + throw new \RuntimeException('This AppendStream is not seekable'); + } elseif ($whence !== SEEK_SET) { + throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); + } + + $this->pos = $this->current = 0; + + // Rewind each stream + foreach ($this->streams as $i => $stream) { + try { + $stream->rewind(); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to seek stream ' + . $i . ' of the AppendStream', 0, $e); + } + } + + // Seek to the actual position by reading from each stream + while ($this->pos < $offset && !$this->eof()) { + $result = $this->read(min(8096, $offset - $this->pos)); + if ($result === '') { + break; + } + } + } + + /** + * Reads from all of the appended streams until the length is met or EOF. + */ + public function read($length): string + { + $buffer = ''; + $total = count($this->streams) - 1; + $remaining = $length; + $progressToNext = false; + + while ($remaining > 0) { + // Progress to the next stream if needed. + if ($progressToNext || $this->streams[$this->current]->eof()) { + $progressToNext = false; + if ($this->current === $total) { + break; + } + $this->current++; + } + + $result = $this->streams[$this->current]->read($remaining); + + if ($result === '') { + $progressToNext = true; + continue; + } + + $buffer .= $result; + $remaining = $length - strlen($buffer); + } + + $this->pos += strlen($buffer); + + return $buffer; + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return false; + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function write($string): int + { + throw new \RuntimeException('Cannot write to an AppendStream'); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return $key ? null : []; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/BufferStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/BufferStream.php new file mode 100644 index 0000000000..21be8c0a9d --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/BufferStream.php @@ -0,0 +1,149 @@ +hwm = $hwm; + } + + public function __toString(): string + { + return $this->getContents(); + } + + public function getContents(): string + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close(): void + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + + return null; + } + + public function getSize(): ?int + { + return strlen($this->buffer); + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return true; + } + + public function isSeekable(): bool + { + return false; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a BufferStream'); + } + + public function eof(): bool + { + return strlen($this->buffer) === 0; + } + + public function tell(): int + { + throw new \RuntimeException('Cannot determine the position of a BufferStream'); + } + + /** + * Reads data from the buffer. + */ + public function read($length): string + { + $currentLength = strlen($this->buffer); + + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + } + + return $result; + } + + /** + * Writes data to the buffer. + */ + public function write($string): int + { + $this->buffer .= $string; + + if (strlen($this->buffer) >= $this->hwm) { + return 0; + } + + return strlen($string); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + if ($key === 'hwm') { + return $this->hwm; + } + + return $key ? null : []; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/CachingStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/CachingStream.php new file mode 100644 index 0000000000..f34722cff5 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/CachingStream.php @@ -0,0 +1,153 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+')); + } + + public function getSize(): ?int + { + $remoteSize = $this->remoteStream->getSize(); + + if (null === $remoteSize) { + return null; + } + + return max($this->stream->getSize(), $remoteSize); + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + if ($whence === SEEK_SET) { + $byte = $offset; + } elseif ($whence === SEEK_CUR) { + $byte = $offset + $this->tell(); + } elseif ($whence === SEEK_END) { + $size = $this->remoteStream->getSize(); + if ($size === null) { + $size = $this->cacheEntireStream(); + } + $byte = $size + $offset; + } else { + throw new \InvalidArgumentException('Invalid whence'); + } + + $diff = $byte - $this->stream->getSize(); + + if ($diff > 0) { + // Read the remoteStream until we have read in at least the amount + // of bytes requested, or we reach the end of the file. + while ($diff > 0 && !$this->remoteStream->eof()) { + $this->read($diff); + $diff = $byte - $this->stream->getSize(); + } + } else { + // We can just do a normal seek since we've already seen this byte. + $this->stream->seek($byte); + } + } + + public function read($length): string + { + // Perform a regular read on any previously read data from the buffer + $data = $this->stream->read($length); + $remaining = $length - strlen($data); + + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have + // been filled from the remote stream, then we must skip bytes on + // the remote stream to emulate overwriting bytes from that + // position. This mimics the behavior of other PHP stream wrappers. + $remoteData = $this->remoteStream->read( + $remaining + $this->skipReadBytes + ); + + if ($this->skipReadBytes) { + $len = strlen($remoteData); + $remoteData = substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); + } + + $data .= $remoteData; + $this->stream->write($remoteData); + } + + return $data; + } + + public function write($string): int + { + // When appending to the end of the currently read stream, you'll want + // to skip bytes from being read from the remote stream to emulate + // other stream wrappers. Basically replacing bytes of data of a fixed + // length. + $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->stream->write($string); + } + + public function eof(): bool + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close(): void + { + $this->remoteStream->close(); + $this->stream->close(); + } + + private function cacheEntireStream(): int + { + $target = new FnStream(['write' => 'strlen']); + Utils::copyToStream($this, $target); + + return $this->tell(); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/DroppingStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/DroppingStream.php new file mode 100644 index 0000000000..6e3d209d0e --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/DroppingStream.php @@ -0,0 +1,49 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + + public function write($string): int + { + $diff = $this->maxLength - $this->stream->getSize(); + + // Begin returning 0 when the underlying stream is too large. + if ($diff <= 0) { + return 0; + } + + // Write the stream or a subset of the stream if needed. + if (strlen($string) < $diff) { + return $this->stream->write($string); + } + + return $this->stream->write(substr($string, 0, $diff)); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php new file mode 100644 index 0000000000..3a084779a8 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php @@ -0,0 +1,14 @@ + */ + private $methods; + + /** + * @param array $methods Hash of method name to a callable. + */ + public function __construct(array $methods) + { + $this->methods = $methods; + + // Create the functions on the class + foreach ($methods as $name => $fn) { + $this->{'_fn_' . $name} = $fn; + } + } + + /** + * Lazily determine which methods are not implemented. + * + * @throws \BadMethodCallException + */ + public function __get(string $name): void + { + throw new \BadMethodCallException(str_replace('_fn_', '', $name) + . '() is not implemented in the FnStream'); + } + + /** + * The close method is called on the underlying stream only if possible. + */ + public function __destruct() + { + if (isset($this->_fn_close)) { + call_user_func($this->_fn_close); + } + } + + /** + * An unserialize would allow the __destruct to run when the unserialized value goes out of scope. + * + * @throws \LogicException + */ + public function __wakeup(): void + { + throw new \LogicException('FnStream should never be unserialized'); + } + + /** + * Adds custom functionality to an underlying stream by intercepting + * specific method calls. + * + * @param StreamInterface $stream Stream to decorate + * @param array $methods Hash of method name to a closure + * + * @return FnStream + */ + public static function decorate(StreamInterface $stream, array $methods) + { + // If any of the required methods were not provided, then simply + // proxy to the decorated stream. + foreach (array_diff(self::SLOTS, array_keys($methods)) as $diff) { + /** @var callable $callable */ + $callable = [$stream, $diff]; + $methods[$diff] = $callable; + } + + return new self($methods); + } + + public function __toString(): string + { + try { + return call_user_func($this->_fn___toString); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function close(): void + { + call_user_func($this->_fn_close); + } + + public function detach() + { + return call_user_func($this->_fn_detach); + } + + public function getSize(): ?int + { + return call_user_func($this->_fn_getSize); + } + + public function tell(): int + { + return call_user_func($this->_fn_tell); + } + + public function eof(): bool + { + return call_user_func($this->_fn_eof); + } + + public function isSeekable(): bool + { + return call_user_func($this->_fn_isSeekable); + } + + public function rewind(): void + { + call_user_func($this->_fn_rewind); + } + + public function seek($offset, $whence = SEEK_SET): void + { + call_user_func($this->_fn_seek, $offset, $whence); + } + + public function isWritable(): bool + { + return call_user_func($this->_fn_isWritable); + } + + public function write($string): int + { + return call_user_func($this->_fn_write, $string); + } + + public function isReadable(): bool + { + return call_user_func($this->_fn_isReadable); + } + + public function read($length): string + { + return call_user_func($this->_fn_read, $length); + } + + public function getContents(): string + { + return call_user_func($this->_fn_getContents); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return call_user_func($this->_fn_getMetadata, $key); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Header.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Header.php new file mode 100644 index 0000000000..4d7005b224 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Header.php @@ -0,0 +1,134 @@ +]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); + } else { + $part[] = trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + } + + return $params; + } + + /** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param string|array $header Header to normalize. + * + * @deprecated Use self::splitList() instead. + */ + public static function normalize($header): array + { + $result = []; + foreach ((array) $header as $value) { + foreach (self::splitList($value) as $parsed) { + $result[] = $parsed; + } + } + + return $result; + } + + /** + * Splits a HTTP header defined to contain a comma-separated list into + * each individual value. Empty values will be removed. + * + * Example headers include 'accept', 'cache-control' and 'if-none-match'. + * + * This method must not be used to parse headers that are not defined as + * a list, such as 'user-agent' or 'set-cookie'. + * + * @param string|string[] $values Header value as returned by MessageInterface::getHeader() + * + * @return string[] + */ + public static function splitList($values): array + { + if (!\is_array($values)) { + $values = [$values]; + } + + $result = []; + foreach ($values as $value) { + if (!\is_string($value)) { + throw new \TypeError('$header must either be a string or an array containing strings.'); + } + + $v = ''; + $isQuoted = false; + $isEscaped = false; + for ($i = 0, $max = \strlen($value); $i < $max; $i++) { + if ($isEscaped) { + $v .= $value[$i]; + $isEscaped = false; + + continue; + } + + if (!$isQuoted && $value[$i] === ',') { + $v = \trim($v); + if ($v !== '') { + $result[] = $v; + } + + $v = ''; + continue; + } + + if ($isQuoted && $value[$i] === '\\') { + $isEscaped = true; + $v .= $value[$i]; + + continue; + } + if ($value[$i] === '"') { + $isQuoted = !$isQuoted; + $v .= $value[$i]; + + continue; + } + + $v .= $value[$i]; + } + + $v = \trim($v); + if ($v !== '') { + $result[] = $v; + } + } + + return $result; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/HttpFactory.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/HttpFactory.php new file mode 100644 index 0000000000..30be222fc0 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/HttpFactory.php @@ -0,0 +1,100 @@ +getSize(); + } + + return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); + } + + public function createStream(string $content = ''): StreamInterface + { + return Utils::streamFor($content); + } + + public function createStreamFromFile(string $file, string $mode = 'r'): StreamInterface + { + try { + $resource = Utils::tryFopen($file, $mode); + } catch (\RuntimeException $e) { + if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) { + throw new \InvalidArgumentException(sprintf('Invalid file opening mode "%s"', $mode), 0, $e); + } + + throw $e; + } + + return Utils::streamFor($resource); + } + + public function createStreamFromResource($resource): StreamInterface + { + return Utils::streamFor($resource); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + if (empty($method)) { + if (!empty($serverParams['REQUEST_METHOD'])) { + $method = $serverParams['REQUEST_METHOD']; + } else { + throw new \InvalidArgumentException('Cannot determine HTTP method'); + } + } + + return new ServerRequest($method, $uri, [], null, '1.1', $serverParams); + } + + public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface + { + return new Response($code, [], null, '1.1', $reasonPhrase); + } + + public function createRequest(string $method, $uri): RequestInterface + { + return new Request($method, $uri); + } + + public function createUri(string $uri = ''): UriInterface + { + return new Uri($uri); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/InflateStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/InflateStream.php new file mode 100644 index 0000000000..8e00f1c324 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/InflateStream.php @@ -0,0 +1,37 @@ + 15 + 32]); + $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource)); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/LazyOpenStream.php new file mode 100644 index 0000000000..f6c84904ed --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/LazyOpenStream.php @@ -0,0 +1,49 @@ +filename = $filename; + $this->mode = $mode; + + // unsetting the property forces the first access to go through + // __get(). + unset($this->stream); + } + + /** + * Creates the underlying stream lazily when required. + */ + protected function createStream(): StreamInterface + { + return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode)); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/LimitStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/LimitStream.php new file mode 100644 index 0000000000..fb2232557b --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/LimitStream.php @@ -0,0 +1,157 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + + public function eof(): bool + { + // Always return true if the underlying stream is EOF + if ($this->stream->eof()) { + return true; + } + + // No limit and the underlying stream is not at EOF + if ($this->limit === -1) { + return false; + } + + return $this->stream->tell() >= $this->offset + $this->limit; + } + + /** + * Returns the size of the limited subset of data + */ + public function getSize(): ?int + { + if (null === ($length = $this->stream->getSize())) { + return null; + } elseif ($this->limit === -1) { + return $length - $this->offset; + } else { + return min($this->limit, $length - $this->offset); + } + } + + /** + * Allow for a bounded seek on the read limited stream + */ + public function seek($offset, $whence = SEEK_SET): void + { + if ($whence !== SEEK_SET || $offset < 0) { + throw new \RuntimeException(sprintf( + 'Cannot seek to offset %s with whence %s', + $offset, + $whence + )); + } + + $offset += $this->offset; + + if ($this->limit !== -1) { + if ($offset > $this->offset + $this->limit) { + $offset = $this->offset + $this->limit; + } + } + + $this->stream->seek($offset); + } + + /** + * Give a relative tell() + */ + public function tell(): int + { + return $this->stream->tell() - $this->offset; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @throws \RuntimeException if the stream cannot be seeked. + */ + public function setOffset(int $offset): void + { + $current = $this->stream->tell(); + + if ($current !== $offset) { + // If the stream cannot seek to the offset position, then read to it + if ($this->stream->isSeekable()) { + $this->stream->seek($offset); + } elseif ($current > $offset) { + throw new \RuntimeException("Could not seek to stream offset $offset"); + } else { + $this->stream->read($offset - $current); + } + } + + $this->offset = $offset; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the + * stream. + * + * @param int $limit Number of bytes to allow to be read from the stream. + * Use -1 for no limit. + */ + public function setLimit(int $limit): void + { + $this->limit = $limit; + } + + public function read($length): string + { + if ($this->limit === -1) { + return $this->stream->read($length); + } + + // Check if the current position is less than the total allowed + // bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(min($remaining, $length)); + } + + return ''; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Message.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Message.php new file mode 100644 index 0000000000..c1e15f8267 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Message.php @@ -0,0 +1,246 @@ +getMethod() . ' ' + . $message->getRequestTarget()) + . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + + foreach ($message->getHeaders() as $name => $values) { + if (strtolower($name) === 'set-cookie') { + foreach ($values as $value) { + $msg .= "\r\n{$name}: " . $value; + } + } else { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + } + + return "{$msg}\r\n\r\n" . $message->getBody(); + } + + /** + * Get a short summary of the message body. + * + * Will return `null` if the response is not printable. + * + * @param MessageInterface $message The message to get the body summary + * @param int $truncateAt The maximum allowed size of the summary + */ + public static function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string + { + $body = $message->getBody(); + + if (!$body->isSeekable() || !$body->isReadable()) { + return null; + } + + $size = $body->getSize(); + + if ($size === 0) { + return null; + } + + $body->rewind(); + $summary = $body->read($truncateAt); + $body->rewind(); + + if ($size > $truncateAt) { + $summary .= ' (truncated...)'; + } + + // Matches any printable character, including unicode characters: + // letters, marks, numbers, punctuation, spacing, and separators. + if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary) !== 0) { + return null; + } + + return $summary; + } + + /** + * Attempts to rewind a message body and throws an exception on failure. + * + * The body of the message will only be rewound if a call to `tell()` + * returns a value other than `0`. + * + * @param MessageInterface $message Message to rewind + * + * @throws \RuntimeException + */ + public static function rewindBody(MessageInterface $message): void + { + $body = $message->getBody(); + + if ($body->tell()) { + $body->rewind(); + } + } + + /** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + */ + public static function parseMessage(string $message): array + { + if (!$message) { + throw new \InvalidArgumentException('Invalid message'); + } + + $message = ltrim($message, "\r\n"); + + $messageParts = preg_split("/\r?\n\r?\n/", $message, 2); + + if ($messageParts === false || count($messageParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing header delimiter'); + } + + [$rawHeaders, $body] = $messageParts; + $rawHeaders .= "\r\n"; // Put back the delimiter we split previously + $headerParts = preg_split("/\r?\n/", $rawHeaders, 2); + + if ($headerParts === false || count($headerParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing status line'); + } + + [$startLine, $rawHeaders] = $headerParts; + + if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') { + // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0 + $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders); + } + + /** @var array[] $headerLines */ + $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER); + + // If these aren't the same, then one line didn't match and there's an invalid header. + if ($count !== substr_count($rawHeaders, "\n")) { + // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4 + if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) { + throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding'); + } + + throw new \InvalidArgumentException('Invalid header syntax'); + } + + $headers = []; + + foreach ($headerLines as $headerLine) { + $headers[$headerLine[1]][] = $headerLine[2]; + } + + return [ + 'start-line' => $startLine, + 'headers' => $headers, + 'body' => $body, + ]; + } + + /** + * Constructs a URI for an HTTP request message. + * + * @param string $path Path from the start-line + * @param array $headers Array of headers (each value an array). + */ + public static function parseRequestUri(string $path, array $headers): string + { + $hostKey = array_filter(array_keys($headers), function ($k) { + // Numeric array keys are converted to int by PHP. + $k = (string) $k; + + return strtolower($k) === 'host'; + }); + + // If no host is found, then a full URI cannot be constructed. + if (!$hostKey) { + return $path; + } + + $host = $headers[reset($hostKey)][0]; + $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + + return $scheme . '://' . $host . '/' . ltrim($path, '/'); + } + + /** + * Parses a request message string into a request object. + * + * @param string $message Request message string. + */ + public static function parseRequest(string $message): RequestInterface + { + $data = self::parseMessage($message); + $matches = []; + if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { + throw new \InvalidArgumentException('Invalid request string'); + } + $parts = explode(' ', $data['start-line'], 3); + $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; + + $request = new Request( + $parts[0], + $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1], + $data['headers'], + $data['body'], + $version + ); + + return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); + } + + /** + * Parses a response message string into a response object. + * + * @param string $message Response message string. + */ + public static function parseResponse(string $message): ResponseInterface + { + $data = self::parseMessage($message); + // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space + // between status-code and reason-phrase is required. But browsers accept + // responses without space and reason as well. + if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { + throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']); + } + $parts = explode(' ', $data['start-line'], 3); + + return new Response( + (int) $parts[1], + $data['headers'], + $data['body'], + explode('/', $parts[0])[1], + $parts[2] ?? null + ); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MessageTrait.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MessageTrait.php new file mode 100644 index 0000000000..464bdfaa40 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MessageTrait.php @@ -0,0 +1,263 @@ + Map of all registered headers, as original name => array of values */ + private $headers = []; + + /** @var array Map of lowercase header name => original name at registration */ + private $headerNames = []; + + /** @var string */ + private $protocol = '1.1'; + + /** @var StreamInterface|null */ + private $stream; + + public function getProtocolVersion(): string + { + return $this->protocol; + } + + public function withProtocolVersion($version): MessageInterface + { + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = $version; + return $new; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function hasHeader($header): bool + { + return isset($this->headerNames[strtolower($header)]); + } + + public function getHeader($header): array + { + $header = strtolower($header); + + if (!isset($this->headerNames[$header])) { + return []; + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header): string + { + return implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value): MessageInterface + { + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + unset($new->headers[$new->headerNames[$normalized]]); + } + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + + return $new; + } + + public function withAddedHeader($header, $value): MessageInterface + { + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $new->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + } + + return $new; + } + + public function withoutHeader($header): MessageInterface + { + $normalized = strtolower($header); + + if (!isset($this->headerNames[$normalized])) { + return $this; + } + + $header = $this->headerNames[$normalized]; + + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + + return $new; + } + + public function getBody(): StreamInterface + { + if (!$this->stream) { + $this->stream = Utils::streamFor(''); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body): MessageInterface + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + return $new; + } + + /** + * @param array $headers + */ + private function setHeaders(array $headers): void + { + $this->headerNames = $this->headers = []; + foreach ($headers as $header => $value) { + // Numeric array keys are converted to int by PHP. + $header = (string) $header; + + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + + /** + * @param mixed $value + * + * @return string[] + */ + private function normalizeHeaderValue($value): array + { + if (!is_array($value)) { + return $this->trimAndValidateHeaderValues([$value]); + } + + if (count($value) === 0) { + throw new \InvalidArgumentException('Header value can not be an empty array.'); + } + + return $this->trimAndValidateHeaderValues($value); + } + + /** + * Trims whitespace from the header values. + * + * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field. + * + * header-field = field-name ":" OWS field-value OWS + * OWS = *( SP / HTAB ) + * + * @param mixed[] $values Header values + * + * @return string[] Trimmed header values + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + */ + private function trimAndValidateHeaderValues(array $values): array + { + return array_map(function ($value) { + if (!is_scalar($value) && null !== $value) { + throw new \InvalidArgumentException(sprintf( + 'Header value must be scalar or null but %s provided.', + is_object($value) ? get_class($value) : gettype($value) + )); + } + + $trimmed = trim((string) $value, " \t"); + $this->assertValue($trimmed); + + return $trimmed; + }, array_values($values)); + } + + /** + * @see https://tools.ietf.org/html/rfc7230#section-3.2 + * + * @param mixed $header + */ + private function assertHeader($header): void + { + if (!is_string($header)) { + throw new \InvalidArgumentException(sprintf( + 'Header name must be a string but %s provided.', + is_object($header) ? get_class($header) : gettype($header) + )); + } + + if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) { + throw new \InvalidArgumentException( + sprintf('"%s" is not valid header name.', $header) + ); + } + } + + /** + * @see https://tools.ietf.org/html/rfc7230#section-3.2 + * + * field-value = *( field-content / obs-fold ) + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + * field-vchar = VCHAR / obs-text + * VCHAR = %x21-7E + * obs-text = %x80-FF + * obs-fold = CRLF 1*( SP / HTAB ) + */ + private function assertValue(string $value): void + { + // The regular expression intentionally does not support the obs-fold production, because as + // per RFC 7230#3.2.4: + // + // A sender MUST NOT generate a message that includes + // line folding (i.e., that has any field-value that contains a match to + // the obs-fold rule) unless the message is intended for packaging + // within the message/http media type. + // + // Clients must not send a request with line folding and a server sending folded headers is + // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting + // folding is not likely to break any legitimate use case. + if (! preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) { + throw new \InvalidArgumentException( + sprintf('"%s" is not valid header value.', $value) + ); + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MimeType.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MimeType.php new file mode 100644 index 0000000000..0debbd18c3 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MimeType.php @@ -0,0 +1,1237 @@ + 'application/vnd.1000minds.decision-model+xml', + '3dml' => 'text/vnd.in3d.3dml', + '3ds' => 'image/x-3ds', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gp', + '3gpp' => 'video/3gpp', + '3mf' => 'model/3mf', + '7z' => 'application/x-7z-compressed', + '7zip' => 'application/x-7z-compressed', + '123' => 'application/vnd.lotus-1-2-3', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-acc', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/vnd.nokia.n-gage.ac+xml', + 'ac3' => 'audio/ac3', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'age' => 'application/vnd.age', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/pdf', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'amr' => 'audio/amr', + 'apk' => 'application/vnd.android.package-archive', + 'apng' => 'image/apng', + 'appcache' => 'text/cache-manifest', + 'application' => 'application/x-ms-application', + 'apr' => 'application/vnd.lotus-approach', + 'arc' => 'application/x-freearc', + 'arj' => 'application/x-arj', + 'asc' => 'application/pgp-signature', + 'asf' => 'video/x-ms-asf', + 'asm' => 'text/x-asm', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomdeleted' => 'application/atomdeleted+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/x-au', + 'avci' => 'image/avci', + 'avcs' => 'image/avcs', + 'avi' => 'video/x-msvideo', + 'avif' => 'image/avif', + 'aw' => 'application/applixware', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azv' => 'image/vnd.airzip.accelerator.azv', + 'azw' => 'application/vnd.amazon.ebook', + 'b16' => 'image/vnd.pco.b16', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bdoc' => 'application/x-bdoc', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'blb' => 'application/x-blorb', + 'blorb' => 'application/x-blorb', + 'bmi' => 'application/vnd.bmi', + 'bmml' => 'application/vnd.balsamiq.bmml+xml', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'bpmn' => 'application/octet-stream', + 'bsp' => 'model/vnd.valve.source.compiled-map', + 'btif' => 'image/prs.btif', + 'buffer' => 'application/octet-stream', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'cab' => 'application/vnd.ms-cab-compressed', + 'caf' => 'audio/x-caf', + 'cap' => 'application/vnd.tcpdump.pcap', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cb7' => 'application/x-cbr', + 'cba' => 'application/x-cbr', + 'cbr' => 'application/x-cbr', + 'cbt' => 'application/x-cbr', + 'cbz' => 'application/x-cbr', + 'cc' => 'text/x-c', + 'cco' => 'application/x-cocoa', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdfx' => 'application/cdfx+xml', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdr' => 'application/cdr', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfs' => 'application/x-cfs-compressed', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cjs' => 'application/node', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/octet-stream', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'coffee' => 'text/coffeescript', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpl' => 'application/cpl+xml', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'crx' => 'application/x-chrome-extension', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'csh' => 'application/x-csh', + 'csl' => 'application/vnd.citationstyles.style+xml', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'csr' => 'application/octet-stream', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dart' => 'application/vnd.dart', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dbf' => 'application/vnd.dbf', + 'dbk' => 'application/docbook+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'ddf' => 'application/vnd.syncml.dmddf+xml', + 'dds' => 'image/vnd.ms-dds', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dgc' => 'application/x-dgc-compressed', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'disposition-notification' => 'message/disposition-notification', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/octet-stream', + 'dmg' => 'application/x-apple-diskimage', + 'dmn' => 'application/octet-stream', + 'dmp' => 'application/vnd.tcpdump.pcap', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'drle' => 'image/dicom-rle', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvb' => 'video/vnd.dvb.file', + 'dvi' => 'application/x-dvi', + 'dwd' => 'application/atsc-dwd+xml', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ear' => 'application/java-archive', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'emf' => 'image/emf', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'emotionml' => 'application/emotionml+xml', + 'emz' => 'application/x-msmetafile', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es' => 'application/ecmascript', + 'es3' => 'application/vnd.eszigno3+xml', + 'esa' => 'application/vnd.osgi.subsystem', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'eva' => 'application/x-eva', + 'evy' => 'application/x-envoy', + 'exe' => 'application/octet-stream', + 'exi' => 'application/exi', + 'exp' => 'application/express', + 'exr' => 'image/aces', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/mp4', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fdt' => 'application/fdt+xml', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'fits' => 'image/fits', + 'flac' => 'audio/x-flac', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'fo' => 'application/vnd.software602.filler.form+xml', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gam' => 'application/x-tads', + 'gbr' => 'application/rpki-ghostbusters', + 'gca' => 'application/x-gca-compressed', + 'gdl' => 'model/vnd.gdl', + 'gdoc' => 'application/vnd.google-apps.document', + 'ged' => 'text/vnd.familysearch.gedcom', + 'geo' => 'application/vnd.dynageo', + 'geojson' => 'application/geo+json', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'glb' => 'model/gltf-binary', + 'gltf' => 'model/gltf+json', + 'gml' => 'application/gml+xml', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gpg' => 'application/gpg-keys', + 'gph' => 'application/vnd.flographit', + 'gpx' => 'application/gpx+xml', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gramps' => 'application/x-gramps-xml', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gsheet' => 'application/vnd.google-apps.spreadsheet', + 'gslides' => 'application/vnd.google-apps.presentation', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxf' => 'application/gxf', + 'gxt' => 'application/vnd.geonext', + 'gz' => 'application/gzip', + 'gzip' => 'application/gzip', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hbs' => 'text/x-handlebars-template', + 'hdd' => 'application/x-virtualbox-hdd', + 'hdf' => 'application/x-hdf', + 'heic' => 'image/heic', + 'heics' => 'image/heic-sequence', + 'heif' => 'image/heif', + 'heifs' => 'image/heif-sequence', + 'hej2' => 'image/hej2k', + 'held' => 'application/atsc-held+xml', + 'hh' => 'text/x-c', + 'hjson' => 'application/hjson', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hsj2' => 'image/hsj2', + 'htc' => 'text/x-component', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'img' => 'application/octet-stream', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ink' => 'application/inkml+xml', + 'inkml' => 'application/inkml+xml', + 'install' => 'application/x-install-instructions', + 'iota' => 'application/vnd.astraea-software.iota', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/x-iso9660-image', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'its' => 'application/its+xml', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jade' => 'text/jade', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'jardiff' => 'application/x-java-archive-diff', + 'java' => 'text/x-java-source', + 'jhc' => 'image/jphc', + 'jisp' => 'application/vnd.jisp', + 'jls' => 'image/jls', + 'jlt' => 'application/vnd.hp-jlyt', + 'jng' => 'image/x-jng', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jp2' => 'image/jp2', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpf' => 'image/jpx', + 'jpg' => 'image/jpeg', + 'jpg2' => 'image/jp2', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jph' => 'image/jph', + 'jpm' => 'video/jpm', + 'jpx' => 'image/jpx', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'json5' => 'application/json5', + 'jsonld' => 'application/ld+json', + 'jsonml' => 'application/jsonml+json', + 'jsx' => 'text/jsx', + 'jxr' => 'image/jxr', + 'jxra' => 'image/jxra', + 'jxrs' => 'image/jxrs', + 'jxs' => 'image/jxs', + 'jxsc' => 'image/jxsc', + 'jxsi' => 'image/jxsi', + 'jxss' => 'image/jxss', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kdb' => 'application/octet-stream', + 'kdbx' => 'application/x-keepass2', + 'key' => 'application/x-iwork-keynote-sffkey', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'kpxx' => 'application/vnd.ds-keypoint', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktx2' => 'image/ktx2', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'less' => 'text/less', + 'lgr' => 'application/lgr+xml', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'litcoffee' => 'text/coffeescript', + 'lnk' => 'application/x-ms-shortcut', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lua' => 'text/x-lua', + 'luac' => 'application/x-lua-bytecode', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm1v' => 'video/mpeg', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'text/plain', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/x-m4a', + 'm4p' => 'application/mp4', + 'm4s' => 'video/iso.segment', + 'm4u' => 'application/vnd.mpegurl', + 'm4v' => 'video/x-m4v', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm21' => 'application/mp21', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'maei' => 'application/mmt-aei+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'manifest' => 'text/cache-manifest', + 'map' => 'application/json', + 'mar' => 'application/octet-stream', + 'markdown' => 'text/markdown', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'md' => 'text/markdown', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'mdx' => 'text/mdx', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'metalink' => 'application/metalink+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mft' => 'application/rpki-manifest', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mie' => 'application/x-mie', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mjs' => 'application/javascript', + 'mk3d' => 'video/x-matroska', + 'mka' => 'audio/x-matroska', + 'mkd' => 'text/x-markdown', + 'mks' => 'video/x-matroska', + 'mkv' => 'video/x-matroska', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mml' => 'text/mathml', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mng' => 'video/x-mng', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mp21' => 'application/mp21', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpd' => 'application/dash+xml', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpf' => 'application/media-policy-dataset+xml', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msg' => 'application/vnd.ms-outlook', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msm' => 'application/octet-stream', + 'msp' => 'application/octet-stream', + 'msty' => 'application/vnd.muvee.style', + 'mtl' => 'model/mtl', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musd' => 'application/mmt-usd+xml', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mvt' => 'application/vnd.mapbox-vector-tile', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxmf' => 'audio/mobile-xmf', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'nfo' => 'text/x-nfo', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nitf' => 'application/vnd.nitf', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nq' => 'application/n-quads', + 'nsc' => 'application/x-conference', + 'nsf' => 'application/vnd.lotus-notes', + 'nt' => 'application/n-triples', + 'ntf' => 'application/vnd.nitf', + 'numbers' => 'application/x-iwork-numbers-sffnumbers', + 'nzb' => 'application/x-nzb', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'obgx' => 'application/vnd.openblox.game+xml', + 'obj' => 'model/obj', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogex' => 'model/vnd.opengex', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'omdoc' => 'application/omdoc+xml', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'opml' => 'text/x-opml', + 'oprc' => 'application/vnd.palm', + 'opus' => 'audio/ogg', + 'org' => 'text/x-org', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'osm' => 'application/vnd.openstreetmap.data+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'font/otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'ova' => 'application/x-virtualbox-ova', + 'ovf' => 'application/x-virtualbox-ovf', + 'owl' => 'application/rdf+xml', + 'oxps' => 'application/oxps', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p7a' => 'application/x-pkcs7-signature', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'p10' => 'application/x-pkcs10', + 'p12' => 'application/x-pkcs12', + 'pac' => 'application/x-ns-proxy-autoconfig', + 'pages' => 'application/x-iwork-pages-sffpages', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcap' => 'application/vnd.tcpdump.pcap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/x-pilot', + 'pde' => 'text/x-processing', + 'pdf' => 'application/pdf', + 'pem' => 'application/x-x509-user-cert', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp', + 'phar' => 'application/octet-stream', + 'php' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'phtml' => 'application/x-httpd-php', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'pkpass' => 'application/vnd.apple.pkpass', + 'pl' => 'application/x-perl', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pm' => 'application/x-perl', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppa' => 'application/vnd.ms-powerpoint', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'model/prc', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'provx' => 'application/provenance+xml', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'application/x-photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'pti' => 'image/prs.pti', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'raml' => 'application/raml+yaml', + 'rapd' => 'application/route-apd+xml', + 'rar' => 'application/x-rar', + 'ras' => 'image/x-cmu-raster', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'relo' => 'application/p2p-overlay+xml', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'ris' => 'application/x-research-info-systems', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'audio/x-pn-realaudio', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rmvb' => 'application/vnd.rn-realmedia-vbr', + 'rnc' => 'application/relax-ng-compact-syntax', + 'rng' => 'application/xml', + 'roa' => 'application/rpki-roa', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsa' => 'application/x-pkcs7', + 'rsat' => 'application/atsc-rsat+xml', + 'rsd' => 'application/rsd+xml', + 'rsheet' => 'application/urc-ressheet+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'text/rtf', + 'rtx' => 'text/richtext', + 'run' => 'application/x-makeself', + 'rusd' => 'application/route-usd+xml', + 'rv' => 'video/vnd.rn-realvideo', + 's' => 'text/x-asm', + 's3m' => 'audio/s3m', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sass' => 'text/x-sass', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scss' => 'text/x-scss', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'sea' => 'application/octet-stream', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'senmlx' => 'application/senml+xml', + 'sensmlx' => 'application/sensml+xml', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sfv' => 'text/x-sfv', + 'sgi' => 'image/sgi', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shex' => 'text/shex', + 'shf' => 'application/shf+xml', + 'shtml' => 'text/html', + 'sid' => 'image/x-mrsid-image', + 'sieve' => 'application/sieve', + 'sig' => 'application/pgp-signature', + 'sil' => 'audio/silk', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'siv' => 'application/sieve', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slim' => 'text/slim', + 'slm' => 'text/slim', + 'sls' => 'application/route-s-tsid+xml', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'smv' => 'video/x-smv', + 'smzip' => 'application/vnd.stepmania.package', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spdx' => 'text/spdx', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'sql' => 'application/x-sql', + 'src' => 'application/x-wais-source', + 'srt' => 'application/x-subrip', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'ssdl' => 'application/ssdl+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'sst' => 'application/octet-stream', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'model/stl', + 'stpx' => 'model/step+xml', + 'stpxz' => 'model/step-xml+zip', + 'stpz' => 'model/step+zip', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'styl' => 'text/stylus', + 'stylus' => 'text/stylus', + 'sub' => 'text/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'swidtag' => 'application/swid+xml', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 't3' => 'application/x-t3vm-image', + 't38' => 'image/t38', + 'taglet' => 'application/vnd.mynfc', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tap' => 'image/vnd.tencent.tap', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'td' => 'application/urc-targetdesc+xml', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'tfx' => 'image/tiff-fx', + 'tga' => 'image/x-tga', + 'tgz' => 'application/x-tar', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tk' => 'application/x-tcl', + 'tmo' => 'application/vnd.tmobile-livetv', + 'toml' => 'application/toml', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trig' => 'application/trig', + 'trm' => 'application/x-msterminal', + 'ts' => 'video/mp2t', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'font/collection', + 'ttf' => 'font/ttf', + 'ttl' => 'text/turtle', + 'ttml' => 'application/ttml+xml', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u3d' => 'model/u3d', + 'u8dsn' => 'message/global-delivery-status', + 'u8hdr' => 'message/global-headers', + 'u8mdn' => 'message/global-disposition-notification', + 'u8msg' => 'message/global', + 'u32' => 'application/x-authorware-bin', + 'ubj' => 'application/ubjson', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'ulx' => 'application/x-glulx', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'usdz' => 'model/vnd.usdz+zip', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvvz' => 'application/vnd.dece.zip', + 'uvx' => 'application/vnd.dece.unspecified', + 'uvz' => 'application/vnd.dece.zip', + 'vbox' => 'application/x-virtualbox-vbox', + 'vbox-extpack' => 'application/x-virtualbox-vbox-extpack', + 'vcard' => 'text/vcard', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vdi' => 'application/x-virtualbox-vdi', + 'vds' => 'model/vnd.sap.vds', + 'vhd' => 'application/x-virtualbox-vhd', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vlc' => 'application/videolan', + 'vmdk' => 'application/x-virtualbox-vmdk', + 'vob' => 'video/x-ms-vob', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtf' => 'image/vnd.valve.source.texture', + 'vtt' => 'text/vtt', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wadl' => 'application/vnd.sun.wadl+xml', + 'war' => 'application/java-archive', + 'wasm' => 'application/wasm', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'wdp' => 'image/vnd.ms-photo', + 'weba' => 'audio/webm', + 'webapp' => 'application/x-web-app-manifest+json', + 'webm' => 'video/webm', + 'webmanifest' => 'application/manifest+json', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wif' => 'application/watcherinfo+xml', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'image/wmf', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-msmetafile', + 'woff' => 'font/woff', + 'woff2' => 'font/woff2', + 'word' => 'application/msword', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsc' => 'message/vnd.wfa.wsc', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x3d' => 'model/x3d+xml', + 'x3db' => 'model/x3d+fastinfoset', + 'x3dbz' => 'model/x3d+binary', + 'x3dv' => 'model/x3d-vrml', + 'x3dvz' => 'model/x3d+vrml', + 'x3dz' => 'model/x3d+xml', + 'x32' => 'application/x-authorware-bin', + 'x_b' => 'model/vnd.parasolid.transmit.binary', + 'x_t' => 'model/vnd.parasolid.transmit.text', + 'xaml' => 'application/xaml+xml', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xav' => 'application/xcap-att+xml', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xca' => 'application/xcap-caps+xml', + 'xcs' => 'application/calendar+xml', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xel' => 'application/xcap-el+xml', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xl' => 'application/excel', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlf' => 'application/xliff+xml', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xm' => 'audio/xm', + 'xml' => 'application/xml', + 'xns' => 'application/xcap-ns+xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpl' => 'application/xproc+xml', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsd' => 'application/xml', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'xz' => 'application/x-xz', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'ymp' => 'text/x-suse-ymp', + 'z' => 'application/x-compress', + 'z1' => 'application/x-zmachine', + 'z2' => 'application/x-zmachine', + 'z3' => 'application/x-zmachine', + 'z4' => 'application/x-zmachine', + 'z5' => 'application/x-zmachine', + 'z6' => 'application/x-zmachine', + 'z7' => 'application/x-zmachine', + 'z8' => 'application/x-zmachine', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml', + 'zsh' => 'text/x-scriptzsh', + ]; + + /** + * Determines the mimetype of a file by looking at its extension. + * + * @link https://raw.githubusercontent.com/jshttp/mime-db/master/db.json + */ + public static function fromFilename(string $filename): ?string + { + return self::fromExtension(pathinfo($filename, PATHINFO_EXTENSION)); + } + + /** + * Maps a file extensions to a mimetype. + * + * @link https://raw.githubusercontent.com/jshttp/mime-db/master/db.json + */ + public static function fromExtension(string $extension): ?string + { + return self::MIME_TYPES[strtolower($extension)] ?? null; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MultipartStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MultipartStream.php new file mode 100644 index 0000000000..3e12b74d15 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MultipartStream.php @@ -0,0 +1,159 @@ +boundary = $boundary ?: bin2hex(random_bytes(20)); + $this->stream = $this->createStream($elements); + } + + public function getBoundary(): string + { + return $this->boundary; + } + + public function isWritable(): bool + { + return false; + } + + /** + * Get the headers needed before transferring the content of a POST file + * + * @param array $headers + */ + private function getHeaders(array $headers): string + { + $str = ''; + foreach ($headers as $key => $value) { + $str .= "{$key}: {$value}\r\n"; + } + + return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; + } + + /** + * Create the aggregate stream that will be used to upload the POST data + */ + protected function createStream(array $elements = []): StreamInterface + { + $stream = new AppendStream(); + + foreach ($elements as $element) { + if (!is_array($element)) { + throw new \UnexpectedValueException("An array is expected"); + } + $this->addElement($stream, $element); + } + + // Add the trailing boundary with CRLF + $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n")); + + return $stream; + } + + private function addElement(AppendStream $stream, array $element): void + { + foreach (['contents', 'name'] as $key) { + if (!array_key_exists($key, $element)) { + throw new \InvalidArgumentException("A '{$key}' key is required"); + } + } + + $element['contents'] = Utils::streamFor($element['contents']); + + if (empty($element['filename'])) { + $uri = $element['contents']->getMetadata('uri'); + if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') { + $element['filename'] = $uri; + } + } + + [$body, $headers] = $this->createElement( + $element['name'], + $element['contents'], + $element['filename'] ?? null, + $element['headers'] ?? [] + ); + + $stream->addStream(Utils::streamFor($this->getHeaders($headers))); + $stream->addStream($body); + $stream->addStream(Utils::streamFor("\r\n")); + } + + private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array + { + // Set a default content-disposition header if one was no provided + $disposition = $this->getHeader($headers, 'content-disposition'); + if (!$disposition) { + $headers['Content-Disposition'] = ($filename === '0' || $filename) + ? sprintf( + 'form-data; name="%s"; filename="%s"', + $name, + basename($filename) + ) + : "form-data; name=\"{$name}\""; + } + + // Set a default content-length header if one was no provided + $length = $this->getHeader($headers, 'content-length'); + if (!$length) { + if ($length = $stream->getSize()) { + $headers['Content-Length'] = (string) $length; + } + } + + // Set a default Content-Type if one was not supplied + $type = $this->getHeader($headers, 'content-type'); + if (!$type && ($filename === '0' || $filename)) { + if ($type = MimeType::fromFilename($filename)) { + $headers['Content-Type'] = $type; + } + } + + return [$stream, $headers]; + } + + private function getHeader(array $headers, string $key) + { + $lowercaseHeader = strtolower($key); + foreach ($headers as $k => $v) { + if (strtolower($k) === $lowercaseHeader) { + return $v; + } + } + + return null; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/NoSeekStream.php new file mode 100644 index 0000000000..161a224f02 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/NoSeekStream.php @@ -0,0 +1,28 @@ +source = $source; + $this->size = $options['size'] ?? null; + $this->metadata = $options['metadata'] ?? []; + $this->buffer = new BufferStream(); + } + + public function __toString(): string + { + try { + return Utils::copyToString($this); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function close(): void + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = 0; + $this->source = null; + + return null; + } + + public function getSize(): ?int + { + return $this->size; + } + + public function tell(): int + { + return $this->tellPos; + } + + public function eof(): bool + { + return $this->source === null; + } + + public function isSeekable(): bool + { + return false; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a PumpStream'); + } + + public function isWritable(): bool + { + return false; + } + + public function write($string): int + { + throw new \RuntimeException('Cannot write to a PumpStream'); + } + + public function isReadable(): bool + { + return true; + } + + public function read($length): string + { + $data = $this->buffer->read($length); + $readLen = strlen($data); + $this->tellPos += $readLen; + $remaining = $length - $readLen; + + if ($remaining) { + $this->pump($remaining); + $data .= $this->buffer->read($remaining); + $this->tellPos += strlen($data) - $readLen; + } + + return $data; + } + + public function getContents(): string + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + if (!$key) { + return $this->metadata; + } + + return $this->metadata[$key] ?? null; + } + + private function pump(int $length): void + { + if ($this->source) { + do { + $data = call_user_func($this->source, $length); + if ($data === false || $data === null) { + $this->source = null; + return; + } + $this->buffer->write($data); + $length -= strlen($data); + } while ($length > 0); + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Query.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Query.php new file mode 100644 index 0000000000..2faab3a889 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Query.php @@ -0,0 +1,113 @@ + '1', 'foo[b]' => '2'])`. + * + * @param string $str Query string to parse + * @param int|bool $urlEncoding How the query string is encoded + */ + public static function parse(string $str, $urlEncoding = true): array + { + $result = []; + + if ($str === '') { + return $result; + } + + if ($urlEncoding === true) { + $decoder = function ($value) { + return rawurldecode(str_replace('+', ' ', (string) $value)); + }; + } elseif ($urlEncoding === PHP_QUERY_RFC3986) { + $decoder = 'rawurldecode'; + } elseif ($urlEncoding === PHP_QUERY_RFC1738) { + $decoder = 'urldecode'; + } else { + $decoder = function ($str) { + return $str; + }; + } + + foreach (explode('&', $str) as $kvp) { + $parts = explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + if (!array_key_exists($key, $result)) { + $result[$key] = $value; + } else { + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + return $result; + } + + /** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of `parse()` to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like `http_build_query()` would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 + * to encode using RFC3986, or PHP_QUERY_RFC1738 + * to encode using RFC1738. + */ + public static function build(array $params, $encoding = PHP_QUERY_RFC3986): string + { + if (!$params) { + return ''; + } + + if ($encoding === false) { + $encoder = function (string $str): string { + return $str; + }; + } elseif ($encoding === PHP_QUERY_RFC3986) { + $encoder = 'rawurlencode'; + } elseif ($encoding === PHP_QUERY_RFC1738) { + $encoder = 'urlencode'; + } else { + throw new \InvalidArgumentException('Invalid type'); + } + + $qs = ''; + foreach ($params as $k => $v) { + $k = $encoder((string) $k); + if (!is_array($v)) { + $qs .= $k; + $v = is_bool($v) ? (int) $v : $v; + if ($v !== null) { + $qs .= '=' . $encoder((string) $v); + } + $qs .= '&'; + } else { + foreach ($v as $vv) { + $qs .= $k; + $vv = is_bool($vv) ? (int) $vv : $vv; + if ($vv !== null) { + $qs .= '=' . $encoder((string) $vv); + } + $qs .= '&'; + } + } + } + + return $qs ? (string) substr($qs, 0, -1) : ''; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Request.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Request.php new file mode 100644 index 0000000000..b17af66a2c --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Request.php @@ -0,0 +1,157 @@ + $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + */ + public function __construct( + string $method, + $uri, + array $headers = [], + $body = null, + string $version = '1.1' + ) { + $this->assertMethod($method); + if (!($uri instanceof UriInterface)) { + $uri = new Uri($uri); + } + + $this->method = strtoupper($method); + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!isset($this->headerNames['host'])) { + $this->updateHostFromUri(); + } + + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + } + + public function getRequestTarget(): string + { + if ($this->requestTarget !== null) { + return $this->requestTarget; + } + + $target = $this->uri->getPath(); + if ($target === '') { + $target = '/'; + } + if ($this->uri->getQuery() != '') { + $target .= '?' . $this->uri->getQuery(); + } + + return $target; + } + + public function withRequestTarget($requestTarget): RequestInterface + { + if (preg_match('#\s#', $requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; cannot contain whitespace' + ); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + return $new; + } + + public function getMethod(): string + { + return $this->method; + } + + public function withMethod($method): RequestInterface + { + $this->assertMethod($method); + $new = clone $this; + $new->method = strtoupper($method); + return $new; + } + + public function getUri(): UriInterface + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost || !isset($this->headerNames['host'])) { + $new->updateHostFromUri(); + } + + return $new; + } + + private function updateHostFromUri(): void + { + $host = $this->uri->getHost(); + + if ($host == '') { + return; + } + + if (($port = $this->uri->getPort()) !== null) { + $host .= ':' . $port; + } + + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $header = 'Host'; + $this->headerNames['host'] = 'Host'; + } + // Ensure Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + $this->headers = [$header => [$host]] + $this->headers; + } + + /** + * @param mixed $method + */ + private function assertMethod($method): void + { + if (!is_string($method) || $method === '') { + throw new InvalidArgumentException('Method must be a non-empty string.'); + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Response.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Response.php new file mode 100644 index 0000000000..4c6ee6f036 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Response.php @@ -0,0 +1,160 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ]; + + /** @var string */ + private $reasonPhrase; + + /** @var int */ + private $statusCode; + + /** + * @param int $status Status code + * @param array $headers Response headers + * @param string|resource|StreamInterface|null $body Response body + * @param string $version Protocol version + * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) + */ + public function __construct( + int $status = 200, + array $headers = [], + $body = null, + string $version = '1.1', + string $reason = null + ) { + $this->assertStatusCodeRange($status); + + $this->statusCode = $status; + + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + + $this->setHeaders($headers); + if ($reason == '' && isset(self::PHRASES[$this->statusCode])) { + $this->reasonPhrase = self::PHRASES[$this->statusCode]; + } else { + $this->reasonPhrase = (string) $reason; + } + + $this->protocol = $version; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function getReasonPhrase(): string + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = ''): ResponseInterface + { + $this->assertStatusCodeIsInteger($code); + $code = (int) $code; + $this->assertStatusCodeRange($code); + + $new = clone $this; + $new->statusCode = $code; + if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) { + $reasonPhrase = self::PHRASES[$new->statusCode]; + } + $new->reasonPhrase = (string) $reasonPhrase; + return $new; + } + + /** + * @param mixed $statusCode + */ + private function assertStatusCodeIsInteger($statusCode): void + { + if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) { + throw new \InvalidArgumentException('Status code must be an integer value.'); + } + } + + private function assertStatusCodeRange(int $statusCode): void + { + if ($statusCode < 100 || $statusCode >= 600) { + throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.'); + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Rfc7230.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Rfc7230.php new file mode 100644 index 0000000000..30224018d3 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Rfc7230.php @@ -0,0 +1,23 @@ +@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; + public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/ServerRequest.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/ServerRequest.php new file mode 100644 index 0000000000..b2aa382dad --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/ServerRequest.php @@ -0,0 +1,344 @@ + $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + * @param array $serverParams Typically the $_SERVER superglobal + */ + public function __construct( + string $method, + $uri, + array $headers = [], + $body = null, + string $version = '1.1', + array $serverParams = [] + ) { + $this->serverParams = $serverParams; + + parent::__construct($method, $uri, $headers, $body, $version); + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files An array which respect $_FILES structure + * + * @throws InvalidArgumentException for unrecognized values + */ + public static function normalizeFiles(array $files): array + { + $normalized = []; + + foreach ($files as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $normalized[$key] = $value; + } elseif (is_array($value) && isset($value['tmp_name'])) { + $normalized[$key] = self::createUploadedFileFromSpec($value); + } elseif (is_array($value)) { + $normalized[$key] = self::normalizeFiles($value); + continue; + } else { + throw new InvalidArgumentException('Invalid value in files specification'); + } + } + + return $normalized; + } + + /** + * Create and return an UploadedFile instance from a $_FILES specification. + * + * If the specification represents an array of values, this method will + * delegate to normalizeNestedFileSpec() and return that return value. + * + * @param array $value $_FILES struct + * + * @return UploadedFileInterface|UploadedFileInterface[] + */ + private static function createUploadedFileFromSpec(array $value) + { + if (is_array($value['tmp_name'])) { + return self::normalizeNestedFileSpec($value); + } + + return new UploadedFile( + $value['tmp_name'], + (int) $value['size'], + (int) $value['error'], + $value['name'], + $value['type'] + ); + } + + /** + * Normalize an array of file specifications. + * + * Loops through all nested files and returns a normalized array of + * UploadedFileInterface instances. + * + * @return UploadedFileInterface[] + */ + private static function normalizeNestedFileSpec(array $files = []): array + { + $normalizedFiles = []; + + foreach (array_keys($files['tmp_name']) as $key) { + $spec = [ + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key] ?? null, + 'error' => $files['error'][$key] ?? null, + 'name' => $files['name'][$key] ?? null, + 'type' => $files['type'][$key] ?? null, + ]; + $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); + } + + return $normalizedFiles; + } + + /** + * Return a ServerRequest populated with superglobals: + * $_GET + * $_POST + * $_COOKIE + * $_FILES + * $_SERVER + */ + public static function fromGlobals(): ServerRequestInterface + { + $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; + $headers = getallheaders(); + $uri = self::getUriFromGlobals(); + $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); + $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; + + $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); + + return $serverRequest + ->withCookieParams($_COOKIE) + ->withQueryParams($_GET) + ->withParsedBody($_POST) + ->withUploadedFiles(self::normalizeFiles($_FILES)); + } + + private static function extractHostAndPortFromAuthority(string $authority): array + { + $uri = 'http://' . $authority; + $parts = parse_url($uri); + if (false === $parts) { + return [null, null]; + } + + $host = $parts['host'] ?? null; + $port = $parts['port'] ?? null; + + return [$host, $port]; + } + + /** + * Get a Uri populated with values from $_SERVER. + */ + public static function getUriFromGlobals(): UriInterface + { + $uri = new Uri(''); + + $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); + + $hasPort = false; + if (isset($_SERVER['HTTP_HOST'])) { + [$host, $port] = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']); + if ($host !== null) { + $uri = $uri->withHost($host); + } + + if ($port !== null) { + $hasPort = true; + $uri = $uri->withPort($port); + } + } elseif (isset($_SERVER['SERVER_NAME'])) { + $uri = $uri->withHost($_SERVER['SERVER_NAME']); + } elseif (isset($_SERVER['SERVER_ADDR'])) { + $uri = $uri->withHost($_SERVER['SERVER_ADDR']); + } + + if (!$hasPort && isset($_SERVER['SERVER_PORT'])) { + $uri = $uri->withPort($_SERVER['SERVER_PORT']); + } + + $hasQuery = false; + if (isset($_SERVER['REQUEST_URI'])) { + $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2); + $uri = $uri->withPath($requestUriParts[0]); + if (isset($requestUriParts[1])) { + $hasQuery = true; + $uri = $uri->withQuery($requestUriParts[1]); + } + } + + if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) { + $uri = $uri->withQuery($_SERVER['QUERY_STRING']); + } + + return $uri; + } + + public function getServerParams(): array + { + return $this->serverParams; + } + + public function getUploadedFiles(): array + { + return $this->uploadedFiles; + } + + public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface + { + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + + return $new; + } + + public function getCookieParams(): array + { + return $this->cookieParams; + } + + public function withCookieParams(array $cookies): ServerRequestInterface + { + $new = clone $this; + $new->cookieParams = $cookies; + + return $new; + } + + public function getQueryParams(): array + { + return $this->queryParams; + } + + public function withQueryParams(array $query): ServerRequestInterface + { + $new = clone $this; + $new->queryParams = $query; + + return $new; + } + + /** + * {@inheritdoc} + * + * @return array|object|null + */ + public function getParsedBody() + { + return $this->parsedBody; + } + + public function withParsedBody($data): ServerRequestInterface + { + $new = clone $this; + $new->parsedBody = $data; + + return $new; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getAttribute($attribute, $default = null) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + public function withAttribute($attribute, $value): ServerRequestInterface + { + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + public function withoutAttribute($attribute): ServerRequestInterface + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $this; + } + + $new = clone $this; + unset($new->attributes[$attribute]); + + return $new; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Stream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Stream.php new file mode 100644 index 0000000000..ecd31861e1 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Stream.php @@ -0,0 +1,282 @@ +size = $options['size']; + } + + $this->customMetadata = $options['metadata'] ?? []; + $this->stream = $stream; + $meta = stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']); + $this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']); + $this->uri = $this->getMetadata('uri'); + } + + /** + * Closes the stream when the destructed + */ + public function __destruct() + { + $this->close(); + } + + public function __toString(): string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function getContents(): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + + return Utils::tryGetContents($this->stream); + } + + public function close(): void + { + if (isset($this->stream)) { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function getSize(): ?int + { + if ($this->size !== null) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + clearstatcache(true, $this->uri); + } + + $stats = fstat($this->stream); + if (is_array($stats) && isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } + + return null; + } + + public function isReadable(): bool + { + return $this->readable; + } + + public function isWritable(): bool + { + return $this->writable; + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function eof(): bool + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + return feof($this->stream); + } + + public function tell(): int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + $result = ftell($this->stream); + + if ($result === false) { + throw new \RuntimeException('Unable to determine stream position'); + } + + return $result; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + $whence = (int) $whence; + + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } + if (fseek($this->stream, $offset, $whence) === -1) { + throw new \RuntimeException('Unable to seek to stream position ' + . $offset . ' with whence ' . var_export($whence, true)); + } + } + + public function read($length): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + if (0 === $length) { + return ''; + } + + try { + $string = fread($this->stream, $length); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to read from stream', 0, $e); + } + + if (false === $string) { + throw new \RuntimeException('Unable to read from stream'); + } + + return $string; + } + + public function write($string): int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + // We can't know the size after writing anything + $this->size = null; + $result = fwrite($this->stream, $string); + + if ($result === false) { + throw new \RuntimeException('Unable to write to stream'); + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + if (!isset($this->stream)) { + return $key ? null : []; + } elseif (!$key) { + return $this->customMetadata + stream_get_meta_data($this->stream); + } elseif (isset($this->customMetadata[$key])) { + return $this->customMetadata[$key]; + } + + $meta = stream_get_meta_data($this->stream); + + return $meta[$key] ?? null; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php new file mode 100644 index 0000000000..56d4104d45 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -0,0 +1,155 @@ +stream = $stream; + } + + /** + * Magic method used to create a new stream if streams are not added in + * the constructor of a decorator (e.g., LazyOpenStream). + * + * @return StreamInterface + */ + public function __get(string $name) + { + if ($name === 'stream') { + $this->stream = $this->createStream(); + return $this->stream; + } + + throw new \UnexpectedValueException("$name not found on class"); + } + + public function __toString(): string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function getContents(): string + { + return Utils::copyToString($this); + } + + /** + * Allow decorators to implement custom methods + * + * @return mixed + */ + public function __call(string $method, array $args) + { + /** @var callable $callable */ + $callable = [$this->stream, $method]; + $result = call_user_func_array($callable, $args); + + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + + public function close(): void + { + $this->stream->close(); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function getSize(): ?int + { + return $this->stream->getSize(); + } + + public function eof(): bool + { + return $this->stream->eof(); + } + + public function tell(): int + { + return $this->stream->tell(); + } + + public function isReadable(): bool + { + return $this->stream->isReadable(); + } + + public function isWritable(): bool + { + return $this->stream->isWritable(); + } + + public function isSeekable(): bool + { + return $this->stream->isSeekable(); + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + $this->stream->seek($offset, $whence); + } + + public function read($length): string + { + return $this->stream->read($length); + } + + public function write($string): int + { + return $this->stream->write($string); + } + + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @throws \BadMethodCallException + */ + protected function createStream(): StreamInterface + { + throw new \BadMethodCallException('Not implemented'); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamWrapper.php new file mode 100644 index 0000000000..2a93464035 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamWrapper.php @@ -0,0 +1,175 @@ +isReadable()) { + $mode = $stream->isWritable() ? 'r+' : 'r'; + } elseif ($stream->isWritable()) { + $mode = 'w'; + } else { + throw new \InvalidArgumentException('The stream must be readable, ' + . 'writable, or both.'); + } + + return fopen('guzzle://stream', $mode, false, self::createStreamContext($stream)); + } + + /** + * Creates a stream context that can be used to open a stream as a php stream resource. + * + * @return resource + */ + public static function createStreamContext(StreamInterface $stream) + { + return stream_context_create([ + 'guzzle' => ['stream' => $stream] + ]); + } + + /** + * Registers the stream wrapper if needed + */ + public static function register(): void + { + if (!in_array('guzzle', stream_get_wrappers())) { + stream_wrapper_register('guzzle', __CLASS__); + } + } + + public function stream_open(string $path, string $mode, int $options, string &$opened_path = null): bool + { + $options = stream_context_get_options($this->context); + + if (!isset($options['guzzle']['stream'])) { + return false; + } + + $this->mode = $mode; + $this->stream = $options['guzzle']['stream']; + + return true; + } + + public function stream_read(int $count): string + { + return $this->stream->read($count); + } + + public function stream_write(string $data): int + { + return $this->stream->write($data); + } + + public function stream_tell(): int + { + return $this->stream->tell(); + } + + public function stream_eof(): bool + { + return $this->stream->eof(); + } + + public function stream_seek(int $offset, int $whence): bool + { + $this->stream->seek($offset, $whence); + + return true; + } + + /** + * @return resource|false + */ + public function stream_cast(int $cast_as) + { + $stream = clone($this->stream); + $resource = $stream->detach(); + + return $resource ?? false; + } + + /** + * @return array + */ + public function stream_stat(): array + { + static $modeMap = [ + 'r' => 33060, + 'rb' => 33060, + 'r+' => 33206, + 'w' => 33188, + 'wb' => 33188 + ]; + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => $modeMap[$this->mode], + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => $this->stream->getSize() ?: 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } + + /** + * @return array + */ + public function url_stat(string $path, int $flags): array + { + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => 0, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UploadedFile.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UploadedFile.php new file mode 100644 index 0000000000..b1521bcf86 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UploadedFile.php @@ -0,0 +1,211 @@ +setError($errorStatus); + $this->size = $size; + $this->clientFilename = $clientFilename; + $this->clientMediaType = $clientMediaType; + + if ($this->isOk()) { + $this->setStreamOrFile($streamOrFile); + } + } + + /** + * Depending on the value set file or stream variable + * + * @param StreamInterface|string|resource $streamOrFile + * + * @throws InvalidArgumentException + */ + private function setStreamOrFile($streamOrFile): void + { + if (is_string($streamOrFile)) { + $this->file = $streamOrFile; + } elseif (is_resource($streamOrFile)) { + $this->stream = new Stream($streamOrFile); + } elseif ($streamOrFile instanceof StreamInterface) { + $this->stream = $streamOrFile; + } else { + throw new InvalidArgumentException( + 'Invalid stream or file provided for UploadedFile' + ); + } + } + + /** + * @throws InvalidArgumentException + */ + private function setError(int $error): void + { + if (false === in_array($error, UploadedFile::ERRORS, true)) { + throw new InvalidArgumentException( + 'Invalid error status for UploadedFile' + ); + } + + $this->error = $error; + } + + private function isStringNotEmpty($param): bool + { + return is_string($param) && false === empty($param); + } + + /** + * Return true if there is no upload error + */ + private function isOk(): bool + { + return $this->error === UPLOAD_ERR_OK; + } + + public function isMoved(): bool + { + return $this->moved; + } + + /** + * @throws RuntimeException if is moved or not ok + */ + private function validateActive(): void + { + if (false === $this->isOk()) { + throw new RuntimeException('Cannot retrieve stream due to upload error'); + } + + if ($this->isMoved()) { + throw new RuntimeException('Cannot retrieve stream after it has already been moved'); + } + } + + public function getStream(): StreamInterface + { + $this->validateActive(); + + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + + /** @var string $file */ + $file = $this->file; + + return new LazyOpenStream($file, 'r+'); + } + + public function moveTo($targetPath): void + { + $this->validateActive(); + + if (false === $this->isStringNotEmpty($targetPath)) { + throw new InvalidArgumentException( + 'Invalid path provided for move operation; must be a non-empty string' + ); + } + + if ($this->file) { + $this->moved = PHP_SAPI === 'cli' + ? rename($this->file, $targetPath) + : move_uploaded_file($this->file, $targetPath); + } else { + Utils::copyToStream( + $this->getStream(), + new LazyOpenStream($targetPath, 'w') + ); + + $this->moved = true; + } + + if (false === $this->moved) { + throw new RuntimeException( + sprintf('Uploaded file could not be moved to %s', $targetPath) + ); + } + } + + public function getSize(): ?int + { + return $this->size; + } + + public function getError(): int + { + return $this->error; + } + + public function getClientFilename(): ?string + { + return $this->clientFilename; + } + + public function getClientMediaType(): ?string + { + return $this->clientMediaType; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Uri.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Uri.php new file mode 100644 index 0000000000..09e878d3d9 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Uri.php @@ -0,0 +1,740 @@ + 80, + 'https' => 443, + 'ftp' => 21, + 'gopher' => 70, + 'nntp' => 119, + 'news' => 119, + 'telnet' => 23, + 'tn3270' => 23, + 'imap' => 143, + 'pop' => 110, + 'ldap' => 389, + ]; + + /** + * Unreserved characters for use in a regex. + * + * @link https://tools.ietf.org/html/rfc3986#section-2.3 + */ + private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; + + /** + * Sub-delims for use in a regex. + * + * @link https://tools.ietf.org/html/rfc3986#section-2.2 + */ + private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; + private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26']; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + /** @var string|null String representation */ + private $composedComponents; + + public function __construct(string $uri = '') + { + if ($uri !== '') { + $parts = self::parse($uri); + if ($parts === false) { + throw new MalformedUriException("Unable to parse URI: $uri"); + } + $this->applyParts($parts); + } + } + /** + * UTF-8 aware \parse_url() replacement. + * + * The internal function produces broken output for non ASCII domain names + * (IDN) when used with locales other than "C". + * + * On the other hand, cURL understands IDN correctly only when UTF-8 locale + * is configured ("C.UTF-8", "en_US.UTF-8", etc.). + * + * @see https://bugs.php.net/bug.php?id=52923 + * @see https://www.php.net/manual/en/function.parse-url.php#114817 + * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING + * + * @return array|false + */ + private static function parse(string $url) + { + // If IPv6 + $prefix = ''; + if (preg_match('%^(.*://\[[0-9:a-f]+\])(.*?)$%', $url, $matches)) { + /** @var array{0:string, 1:string, 2:string} $matches */ + $prefix = $matches[1]; + $url = $matches[2]; + } + + /** @var string */ + $encodedUrl = preg_replace_callback( + '%[^:/@?&=#]+%usD', + static function ($matches) { + return urlencode($matches[0]); + }, + $url + ); + + $result = parse_url($prefix . $encodedUrl); + + if ($result === false) { + return false; + } + + return array_map('urldecode', $result); + } + + public function __toString(): string + { + if ($this->composedComponents === null) { + $this->composedComponents = self::composeComponents( + $this->scheme, + $this->getAuthority(), + $this->path, + $this->query, + $this->fragment + ); + } + + return $this->composedComponents; + } + + /** + * Composes a URI reference string from its various components. + * + * Usually this method does not need to be called manually but instead is used indirectly via + * `Psr\Http\Message\UriInterface::__toString`. + * + * PSR-7 UriInterface treats an empty component the same as a missing component as + * getQuery(), getFragment() etc. always return a string. This explains the slight + * difference to RFC 3986 Section 5.3. + * + * Another adjustment is that the authority separator is added even when the authority is missing/empty + * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with + * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But + * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to + * that format). + * + * @link https://tools.ietf.org/html/rfc3986#section-5.3 + */ + public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string + { + $uri = ''; + + // weak type checks to also accept null until we can add scalar type hints + if ($scheme != '') { + $uri .= $scheme . ':'; + } + + if ($authority != '' || $scheme === 'file') { + $uri .= '//' . $authority; + } + + if ($authority != '' && $path != '' && $path[0] != '/') { + $path = '/' . $path; + } + + $uri .= $path; + + if ($query != '') { + $uri .= '?' . $query; + } + + if ($fragment != '') { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * Whether the URI has the default port of the current scheme. + * + * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used + * independently of the implementation. + */ + public static function isDefaultPort(UriInterface $uri): bool + { + return $uri->getPort() === null + || (isset(self::DEFAULT_PORTS[$uri->getScheme()]) && $uri->getPort() === self::DEFAULT_PORTS[$uri->getScheme()]); + } + + /** + * Whether the URI is absolute, i.e. it has a scheme. + * + * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true + * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative + * to another URI, the base URI. Relative references can be divided into several forms: + * - network-path references, e.g. '//example.com/path' + * - absolute-path references, e.g. '/path' + * - relative-path references, e.g. 'subpath' + * + * @see Uri::isNetworkPathReference + * @see Uri::isAbsolutePathReference + * @see Uri::isRelativePathReference + * @link https://tools.ietf.org/html/rfc3986#section-4 + */ + public static function isAbsolute(UriInterface $uri): bool + { + return $uri->getScheme() !== ''; + } + + /** + * Whether the URI is a network-path reference. + * + * A relative reference that begins with two slash characters is termed an network-path reference. + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isNetworkPathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' && $uri->getAuthority() !== ''; + } + + /** + * Whether the URI is a absolute-path reference. + * + * A relative reference that begins with a single slash character is termed an absolute-path reference. + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isAbsolutePathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && isset($uri->getPath()[0]) + && $uri->getPath()[0] === '/'; + } + + /** + * Whether the URI is a relative-path reference. + * + * A relative reference that does not begin with a slash character is termed a relative-path reference. + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isRelativePathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); + } + + /** + * Whether the URI is a same-document reference. + * + * A same-document reference refers to a URI that is, aside from its fragment + * component, identical to the base URI. When no base URI is given, only an empty + * URI reference (apart from its fragment) is considered a same-document reference. + * + * @param UriInterface $uri The URI to check + * @param UriInterface|null $base An optional base URI to compare against + * + * @link https://tools.ietf.org/html/rfc3986#section-4.4 + */ + public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool + { + if ($base !== null) { + $uri = UriResolver::resolve($base, $uri); + + return ($uri->getScheme() === $base->getScheme()) + && ($uri->getAuthority() === $base->getAuthority()) + && ($uri->getPath() === $base->getPath()) + && ($uri->getQuery() === $base->getQuery()); + } + + return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === ''; + } + + /** + * Creates a new URI with a specific query string value removed. + * + * Any existing query string values that exactly match the provided key are + * removed. + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Query string key to remove. + */ + public static function withoutQueryValue(UriInterface $uri, string $key): UriInterface + { + $result = self::getFilteredQueryString($uri, [$key]); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a new URI with a specific query string value. + * + * Any existing query string values that exactly match the provided key are + * removed and replaced with the given key value pair. + * + * A value of null will set the query string key without a value, e.g. "key" + * instead of "key=value". + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Key to set. + * @param string|null $value Value to set + */ + public static function withQueryValue(UriInterface $uri, string $key, ?string $value): UriInterface + { + $result = self::getFilteredQueryString($uri, [$key]); + + $result[] = self::generateQueryString($key, $value); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a new URI with multiple specific query string values. + * + * It has the same behavior as withQueryValue() but for an associative array of key => value. + * + * @param UriInterface $uri URI to use as a base. + * @param array $keyValueArray Associative array of key and values + */ + public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface + { + $result = self::getFilteredQueryString($uri, array_keys($keyValueArray)); + + foreach ($keyValueArray as $key => $value) { + $result[] = self::generateQueryString((string) $key, $value !== null ? (string) $value : null); + } + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a URI from a hash of `parse_url` components. + * + * @link http://php.net/manual/en/function.parse-url.php + * + * @throws MalformedUriException If the components do not form a valid URI. + */ + public static function fromParts(array $parts): UriInterface + { + $uri = new self(); + $uri->applyParts($parts); + $uri->validateState(); + + return $uri; + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getAuthority(): string + { + $authority = $this->host; + if ($this->userInfo !== '') { + $authority = $this->userInfo . '@' . $authority; + } + + if ($this->port !== null) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + public function getUserInfo(): string + { + return $this->userInfo; + } + + public function getHost(): string + { + return $this->host; + } + + public function getPort(): ?int + { + return $this->port; + } + + public function getPath(): string + { + return $this->path; + } + + public function getQuery(): string + { + return $this->query; + } + + public function getFragment(): string + { + return $this->fragment; + } + + public function withScheme($scheme): UriInterface + { + $scheme = $this->filterScheme($scheme); + + if ($this->scheme === $scheme) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->composedComponents = null; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withUserInfo($user, $password = null): UriInterface + { + $info = $this->filterUserInfoComponent($user); + if ($password !== null) { + $info .= ':' . $this->filterUserInfoComponent($password); + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withHost($host): UriInterface + { + $host = $this->filterHost($host); + + if ($this->host === $host) { + return $this; + } + + $new = clone $this; + $new->host = $host; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withPort($port): UriInterface + { + $port = $this->filterPort($port); + + if ($this->port === $port) { + return $this; + } + + $new = clone $this; + $new->port = $port; + $new->composedComponents = null; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withPath($path): UriInterface + { + $path = $this->filterPath($path); + + if ($this->path === $path) { + return $this; + } + + $new = clone $this; + $new->path = $path; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withQuery($query): UriInterface + { + $query = $this->filterQueryAndFragment($query); + + if ($this->query === $query) { + return $this; + } + + $new = clone $this; + $new->query = $query; + $new->composedComponents = null; + + return $new; + } + + public function withFragment($fragment): UriInterface + { + $fragment = $this->filterQueryAndFragment($fragment); + + if ($this->fragment === $fragment) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + $new->composedComponents = null; + + return $new; + } + + public function jsonSerialize(): string + { + return $this->__toString(); + } + + /** + * Apply parse_url parts to a URI. + * + * @param array $parts Array of parse_url parts to apply. + */ + private function applyParts(array $parts): void + { + $this->scheme = isset($parts['scheme']) + ? $this->filterScheme($parts['scheme']) + : ''; + $this->userInfo = isset($parts['user']) + ? $this->filterUserInfoComponent($parts['user']) + : ''; + $this->host = isset($parts['host']) + ? $this->filterHost($parts['host']) + : ''; + $this->port = isset($parts['port']) + ? $this->filterPort($parts['port']) + : null; + $this->path = isset($parts['path']) + ? $this->filterPath($parts['path']) + : ''; + $this->query = isset($parts['query']) + ? $this->filterQueryAndFragment($parts['query']) + : ''; + $this->fragment = isset($parts['fragment']) + ? $this->filterQueryAndFragment($parts['fragment']) + : ''; + if (isset($parts['pass'])) { + $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']); + } + + $this->removeDefaultPort(); + } + + /** + * @param mixed $scheme + * + * @throws \InvalidArgumentException If the scheme is invalid. + */ + private function filterScheme($scheme): string + { + if (!is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param mixed $component + * + * @throws \InvalidArgumentException If the user info is invalid. + */ + private function filterUserInfoComponent($component): string + { + if (!is_string($component)) { + throw new \InvalidArgumentException('User info must be a string'); + } + + return preg_replace_callback( + '/(?:[^%' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . ']+|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $component + ); + } + + /** + * @param mixed $host + * + * @throws \InvalidArgumentException If the host is invalid. + */ + private function filterHost($host): string + { + if (!is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param mixed $port + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPort($port): ?int + { + if ($port === null) { + return null; + } + + $port = (int) $port; + if (0 > $port || 0xffff < $port) { + throw new \InvalidArgumentException( + sprintf('Invalid port: %d. Must be between 0 and 65535', $port) + ); + } + + return $port; + } + + /** + * @param string[] $keys + * + * @return string[] + */ + private static function getFilteredQueryString(UriInterface $uri, array $keys): array + { + $current = $uri->getQuery(); + + if ($current === '') { + return []; + } + + $decodedKeys = array_map('rawurldecode', $keys); + + return array_filter(explode('&', $current), function ($part) use ($decodedKeys) { + return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true); + }); + } + + private static function generateQueryString(string $key, ?string $value): string + { + // Query string separators ("=", "&") within the key or value need to be encoded + // (while preventing double-encoding) before setting the query string. All other + // chars that need percent-encoding will be encoded by withQuery(). + $queryString = strtr($key, self::QUERY_SEPARATORS_REPLACEMENT); + + if ($value !== null) { + $queryString .= '=' . strtr($value, self::QUERY_SEPARATORS_REPLACEMENT); + } + + return $queryString; + } + + private function removeDefaultPort(): void + { + if ($this->port !== null && self::isDefaultPort($this)) { + $this->port = null; + } + } + + /** + * Filters the path of a URI + * + * @param mixed $path + * + * @throws \InvalidArgumentException If the path is invalid. + */ + private function filterPath($path): string + { + if (!is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $path + ); + } + + /** + * Filters the query string or fragment of a URI. + * + * @param mixed $str + * + * @throws \InvalidArgumentException If the query or fragment is invalid. + */ + private function filterQueryAndFragment($str): string + { + if (!is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $str + ); + } + + private function rawurlencodeMatchZero(array $match): string + { + return rawurlencode($match[0]); + } + + private function validateState(): void + { + if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { + $this->host = self::HTTP_DEFAULT_HOST; + } + + if ($this->getAuthority() === '') { + if (0 === strpos($this->path, '//')) { + throw new MalformedUriException('The path of a URI without an authority must not start with two slashes "//"'); + } + if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) { + throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon'); + } + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriComparator.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriComparator.php new file mode 100644 index 0000000000..70c582aa0f --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriComparator.php @@ -0,0 +1,52 @@ +getHost(), $modified->getHost()) !== 0) { + return true; + } + + if ($original->getScheme() !== $modified->getScheme()) { + return true; + } + + if (self::computePort($original) !== self::computePort($modified)) { + return true; + } + + return false; + } + + private static function computePort(UriInterface $uri): int + { + $port = $uri->getPort(); + + if (null !== $port) { + return $port; + } + + return 'https' === $uri->getScheme() ? 443 : 80; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriNormalizer.php new file mode 100644 index 0000000000..e12971edda --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriNormalizer.php @@ -0,0 +1,220 @@ +getPath() === '' && + ($uri->getScheme() === 'http' || $uri->getScheme() === 'https') + ) { + $uri = $uri->withPath('/'); + } + + if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') { + $uri = $uri->withHost(''); + } + + if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) { + $uri = $uri->withPort(null); + } + + if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { + $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); + } + + if ($flags & self::REMOVE_DUPLICATE_SLASHES) { + $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath())); + } + + if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { + $queryKeyValues = explode('&', $uri->getQuery()); + sort($queryKeyValues); + $uri = $uri->withQuery(implode('&', $queryKeyValues)); + } + + return $uri; + } + + /** + * Whether two URIs can be considered equivalent. + * + * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also + * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be + * resolved against the same base URI. If this is not the case, determination of equivalence or difference of + * relative references does not mean anything. + * + * @param UriInterface $uri1 An URI to compare + * @param UriInterface $uri2 An URI to compare + * @param int $normalizations A bitmask of normalizations to apply, see constants + * + * @link https://tools.ietf.org/html/rfc3986#section-6.1 + */ + public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool + { + return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations); + } + + private static function capitalizePercentEncoding(UriInterface $uri): UriInterface + { + $regex = '/(?:%[A-Fa-f0-9]{2})++/'; + + $callback = function (array $match) { + return strtoupper($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private static function decodeUnreservedCharacters(UriInterface $uri): UriInterface + { + $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; + + $callback = function (array $match) { + return rawurldecode($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriResolver.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriResolver.php new file mode 100644 index 0000000000..426e5c9adf --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriResolver.php @@ -0,0 +1,211 @@ +getScheme() != '') { + return $rel->withPath(self::removeDotSegments($rel->getPath())); + } + + if ($rel->getAuthority() != '') { + $targetAuthority = $rel->getAuthority(); + $targetPath = self::removeDotSegments($rel->getPath()); + $targetQuery = $rel->getQuery(); + } else { + $targetAuthority = $base->getAuthority(); + if ($rel->getPath() === '') { + $targetPath = $base->getPath(); + $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); + } else { + if ($rel->getPath()[0] === '/') { + $targetPath = $rel->getPath(); + } else { + if ($targetAuthority != '' && $base->getPath() === '') { + $targetPath = '/' . $rel->getPath(); + } else { + $lastSlashPos = strrpos($base->getPath(), '/'); + if ($lastSlashPos === false) { + $targetPath = $rel->getPath(); + } else { + $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath(); + } + } + } + $targetPath = self::removeDotSegments($targetPath); + $targetQuery = $rel->getQuery(); + } + } + + return new Uri(Uri::composeComponents( + $base->getScheme(), + $targetAuthority, + $targetPath, + $targetQuery, + $rel->getFragment() + )); + } + + /** + * Returns the target URI as a relative reference from the base URI. + * + * This method is the counterpart to resolve(): + * + * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) + * + * One use-case is to use the current request URI as base URI and then generate relative links in your documents + * to reduce the document size or offer self-contained downloadable document archives. + * + * $base = new Uri('http://example.com/a/b/'); + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. + * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. + * + * This method also accepts a target that is already relative and will try to relativize it further. Only a + * relative-path reference will be returned as-is. + * + * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well + */ + public static function relativize(UriInterface $base, UriInterface $target): UriInterface + { + if ($target->getScheme() !== '' && + ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') + ) { + return $target; + } + + if (Uri::isRelativePathReference($target)) { + // As the target is already highly relative we return it as-is. It would be possible to resolve + // the target with `$target = self::resolve($base, $target);` and then try make it more relative + // by removing a duplicate query. But let's not do that automatically. + return $target; + } + + if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { + return $target->withScheme(''); + } + + // We must remove the path before removing the authority because if the path starts with two slashes, the URI + // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also + // invalid. + $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); + + if ($base->getPath() !== $target->getPath()) { + return $emptyPathUri->withPath(self::getRelativePath($base, $target)); + } + + if ($base->getQuery() === $target->getQuery()) { + // Only the target fragment is left. And it must be returned even if base and target fragment are the same. + return $emptyPathUri->withQuery(''); + } + + // If the base URI has a query but the target has none, we cannot return an empty path reference as it would + // inherit the base query component when resolving. + if ($target->getQuery() === '') { + $segments = explode('/', $target->getPath()); + /** @var string $lastSegment */ + $lastSegment = end($segments); + + return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); + } + + return $emptyPathUri; + } + + private static function getRelativePath(UriInterface $base, UriInterface $target): string + { + $sourceSegments = explode('/', $base->getPath()); + $targetSegments = explode('/', $target->getPath()); + array_pop($sourceSegments); + $targetLastSegment = array_pop($targetSegments); + foreach ($sourceSegments as $i => $segment) { + if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { + unset($sourceSegments[$i], $targetSegments[$i]); + } else { + break; + } + } + $targetSegments[] = $targetLastSegment; + $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments); + + // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name. + if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { + $relativePath = "./$relativePath"; + } elseif ('/' === $relativePath[0]) { + if ($base->getAuthority() != '' && $base->getPath() === '') { + // In this case an extra slash is added by resolve() automatically. So we must not add one here. + $relativePath = ".$relativePath"; + } else { + $relativePath = "./$relativePath"; + } + } + + return $relativePath; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Utils.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Utils.php new file mode 100644 index 0000000000..3a4cf39464 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Utils.php @@ -0,0 +1,459 @@ + $v) { + if (!is_string($k) || !in_array(strtolower($k), $keys)) { + $result[$k] = $v; + } + } + + return $result; + } + + /** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void + { + $bufferSize = 8192; + + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read($bufferSize))) { + break; + } + } + } else { + $remaining = $maxLen; + while ($remaining > 0 && !$source->eof()) { + $buf = $source->read(min($bufferSize, $remaining)); + $len = strlen($buf); + if (!$len) { + break; + } + $remaining -= $len; + $dest->write($buf); + } + } + } + + /** + * Copy the contents of a stream into a string until the given number of + * bytes have been read. + * + * @param StreamInterface $stream Stream to read + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToString(StreamInterface $stream, int $maxLen = -1): string + { + $buffer = ''; + + if ($maxLen === -1) { + while (!$stream->eof()) { + $buf = $stream->read(1048576); + if ($buf === '') { + break; + } + $buffer .= $buf; + } + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + if ($buf === '') { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; + } + + /** + * Calculate a hash of a stream. + * + * This method reads the entire stream to calculate a rolling hash, based + * on PHP's `hash_init` functions. + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @throws \RuntimeException on error. + */ + public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string + { + $pos = $stream->tell(); + + if ($pos > 0) { + $stream->rewind(); + } + + $ctx = hash_init($algo); + while (!$stream->eof()) { + hash_update($ctx, $stream->read(1048576)); + } + + $out = hash_final($ctx, $rawOutput); + $stream->seek($pos); + + return $out; + } + + /** + * Clone and modify a request with the given changes. + * + * This method is useful for reducing the number of clones needed to mutate + * a message. + * + * The changes can be one of: + * - method: (string) Changes the HTTP method. + * - set_headers: (array) Sets the given headers. + * - remove_headers: (array) Remove the given headers. + * - body: (mixed) Sets the given body. + * - uri: (UriInterface) Set the URI. + * - query: (string) Set the query string value of the URI. + * - version: (string) Set the protocol version. + * + * @param RequestInterface $request Request to clone and modify. + * @param array $changes Changes to apply. + */ + public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface + { + if (!$changes) { + return $request; + } + + $headers = $request->getHeaders(); + + if (!isset($changes['uri'])) { + $uri = $request->getUri(); + } else { + // Remove the host header if one is on the URI + if ($host = $changes['uri']->getHost()) { + $changes['set_headers']['Host'] = $host; + + if ($port = $changes['uri']->getPort()) { + $standardPorts = ['http' => 80, 'https' => 443]; + $scheme = $changes['uri']->getScheme(); + if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { + $changes['set_headers']['Host'] .= ':' . $port; + } + } + } + $uri = $changes['uri']; + } + + if (!empty($changes['remove_headers'])) { + $headers = self::caselessRemove($changes['remove_headers'], $headers); + } + + if (!empty($changes['set_headers'])) { + $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers); + $headers = $changes['set_headers'] + $headers; + } + + if (isset($changes['query'])) { + $uri = $uri->withQuery($changes['query']); + } + + if ($request instanceof ServerRequestInterface) { + $new = (new ServerRequest( + $changes['method'] ?? $request->getMethod(), + $uri, + $headers, + $changes['body'] ?? $request->getBody(), + $changes['version'] ?? $request->getProtocolVersion(), + $request->getServerParams() + )) + ->withParsedBody($request->getParsedBody()) + ->withQueryParams($request->getQueryParams()) + ->withCookieParams($request->getCookieParams()) + ->withUploadedFiles($request->getUploadedFiles()); + + foreach ($request->getAttributes() as $key => $value) { + $new = $new->withAttribute($key, $value); + } + + return $new; + } + + return new Request( + $changes['method'] ?? $request->getMethod(), + $uri, + $headers, + $changes['body'] ?? $request->getBody(), + $changes['version'] ?? $request->getProtocolVersion() + ); + } + + /** + * Read a line from the stream up to the maximum allowed buffer length. + * + * @param StreamInterface $stream Stream to read from + * @param int|null $maxLength Maximum buffer length + */ + public static function readLine(StreamInterface $stream, ?int $maxLength = null): string + { + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + if ('' === ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte === "\n" || ++$size === $maxLength - 1) { + break; + } + } + + return $buffer; + } + + /** + * Create a new stream based on the input type. + * + * Options is an associative array that can contain the following keys: + * - metadata: Array of custom metadata. + * - size: Size of the stream. + * + * This method accepts the following `$resource` types: + * - `Psr\Http\Message\StreamInterface`: Returns the value as-is. + * - `string`: Creates a stream object that uses the given string as the contents. + * - `resource`: Creates a stream object that wraps the given PHP stream resource. + * - `Iterator`: If the provided value implements `Iterator`, then a read-only + * stream object will be created that wraps the given iterable. Each time the + * stream is read from, data from the iterator will fill a buffer and will be + * continuously called until the buffer is equal to the requested read size. + * Subsequent read calls will first read from the buffer and then call `next` + * on the underlying iterator until it is exhausted. + * - `object` with `__toString()`: If the object has the `__toString()` method, + * the object will be cast to a string and then a stream will be returned that + * uses the string value. + * - `NULL`: When `null` is passed, an empty stream object is returned. + * - `callable` When a callable is passed, a read-only stream object will be + * created that invokes the given callable. The callable is invoked with the + * number of suggested bytes to read. The callable can return any number of + * bytes, but MUST return `false` when there is no more data to return. The + * stream object that wraps the callable will invoke the callable until the + * number of requested bytes are available. Any additional bytes will be + * buffered and used in subsequent reads. + * + * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data + * @param array{size?: int, metadata?: array} $options Additional options + * + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ + public static function streamFor($resource = '', array $options = []): StreamInterface + { + if (is_scalar($resource)) { + $stream = self::tryFopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, (string) $resource); + fseek($stream, 0); + } + return new Stream($stream, $options); + } + + switch (gettype($resource)) { + case 'resource': + /* + * The 'php://input' is a special stream with quirks and inconsistencies. + * We avoid using that stream by reading it into php://temp + */ + + /** @var resource $resource */ + if ((\stream_get_meta_data($resource)['uri'] ?? '') === 'php://input') { + $stream = self::tryFopen('php://temp', 'w+'); + stream_copy_to_stream($resource, $stream); + fseek($stream, 0); + $resource = $stream; + } + return new Stream($resource, $options); + case 'object': + /** @var object $resource */ + if ($resource instanceof StreamInterface) { + return $resource; + } elseif ($resource instanceof \Iterator) { + return new PumpStream(function () use ($resource) { + if (!$resource->valid()) { + return false; + } + $result = $resource->current(); + $resource->next(); + return $result; + }, $options); + } elseif (method_exists($resource, '__toString')) { + return self::streamFor((string) $resource, $options); + } + break; + case 'NULL': + return new Stream(self::tryFopen('php://temp', 'r+'), $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); + } + + /** + * Safely opens a PHP stream resource using a filename. + * + * When fopen fails, PHP normally raises a warning. This function adds an + * error handler that checks for errors and throws an exception instead. + * + * @param string $filename File to open + * @param string $mode Mode used to open the file + * + * @return resource + * + * @throws \RuntimeException if the file cannot be opened + */ + public static function tryFopen(string $filename, string $mode) + { + $ex = null; + set_error_handler(static function (int $errno, string $errstr) use ($filename, $mode, &$ex): bool { + $ex = new \RuntimeException(sprintf( + 'Unable to open "%s" using mode "%s": %s', + $filename, + $mode, + $errstr + )); + + return true; + }); + + try { + /** @var resource $handle */ + $handle = fopen($filename, $mode); + } catch (\Throwable $e) { + $ex = new \RuntimeException(sprintf( + 'Unable to open "%s" using mode "%s": %s', + $filename, + $mode, + $e->getMessage() + ), 0, $e); + } + + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $handle; + } + + /** + * Safely gets the contents of a given stream. + * + * When stream_get_contents fails, PHP normally raises a warning. This + * function adds an error handler that checks for errors and throws an + * exception instead. + * + * @param resource $stream + * + * @throws \RuntimeException if the stream cannot be read + */ + public static function tryGetContents($stream): string + { + $ex = null; + set_error_handler(static function (int $errno, string $errstr) use (&$ex): bool { + $ex = new \RuntimeException(sprintf( + 'Unable to read stream contents: %s', + $errstr + )); + + return true; + }); + + try { + /** @var string|false $contents */ + $contents = stream_get_contents($stream); + + if ($contents === false) { + $ex = new \RuntimeException('Unable to read stream contents'); + } + } catch (\Throwable $e) { + $ex = new \RuntimeException(sprintf( + 'Unable to read stream contents: %s', + $e->getMessage() + ), 0, $e); + } + + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $contents; + } + + /** + * Returns a UriInterface for the given value. + * + * This function accepts a string or UriInterface and returns a + * UriInterface for the given value. If the value is already a + * UriInterface, it is returned as-is. + * + * @param string|UriInterface $uri + * + * @throws \InvalidArgumentException + */ + public static function uriFor($uri): UriInterface + { + if ($uri instanceof UriInterface) { + return $uri; + } + + if (is_string($uri)) { + return new Uri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); + } +} diff --git a/data/web/inc/lib/vendor/illuminate/collections/Arr.php b/data/web/inc/lib/vendor/illuminate/collections/Arr.php new file mode 100644 index 0000000000..d83cf5f735 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/collections/Arr.php @@ -0,0 +1,939 @@ +all(); + } elseif (! is_array($values)) { + continue; + } + + $results[] = $values; + } + + return array_merge([], ...$results); + } + + /** + * Cross join the given arrays, returning all possible permutations. + * + * @param iterable ...$arrays + * @return array + */ + public static function crossJoin(...$arrays) + { + $results = [[]]; + + foreach ($arrays as $index => $array) { + $append = []; + + foreach ($results as $product) { + foreach ($array as $item) { + $product[$index] = $item; + + $append[] = $product; + } + } + + $results = $append; + } + + return $results; + } + + /** + * Divide an array into two arrays. One with keys and the other with values. + * + * @param array $array + * @return array + */ + public static function divide($array) + { + return [array_keys($array), array_values($array)]; + } + + /** + * Flatten a multi-dimensional associative array with dots. + * + * @param iterable $array + * @param string $prepend + * @return array + */ + public static function dot($array, $prepend = '') + { + $results = []; + + foreach ($array as $key => $value) { + if (is_array($value) && ! empty($value)) { + $results = array_merge($results, static::dot($value, $prepend.$key.'.')); + } else { + $results[$prepend.$key] = $value; + } + } + + return $results; + } + + /** + * Convert a flatten "dot" notation array into an expanded array. + * + * @param iterable $array + * @return array + */ + public static function undot($array) + { + $results = []; + + foreach ($array as $key => $value) { + static::set($results, $key, $value); + } + + return $results; + } + + /** + * Get all of the given array except for a specified array of keys. + * + * @param array $array + * @param array|string|int|float $keys + * @return array + */ + public static function except($array, $keys) + { + static::forget($array, $keys); + + return $array; + } + + /** + * Determine if the given key exists in the provided array. + * + * @param \ArrayAccess|array $array + * @param string|int $key + * @return bool + */ + public static function exists($array, $key) + { + if ($array instanceof Enumerable) { + return $array->has($key); + } + + if ($array instanceof ArrayAccess) { + return $array->offsetExists($key); + } + + if (is_float($key)) { + $key = (string) $key; + } + + return array_key_exists($key, $array); + } + + /** + * Return the first element in an array passing a given truth test. + * + * @param iterable $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function first($array, ?callable $callback = null, $default = null) + { + if (is_null($callback)) { + if (empty($array)) { + return value($default); + } + + foreach ($array as $item) { + return $item; + } + + return value($default); + } + + foreach ($array as $key => $value) { + if ($callback($value, $key)) { + return $value; + } + } + + return value($default); + } + + /** + * Return the last element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function last($array, ?callable $callback = null, $default = null) + { + if (is_null($callback)) { + return empty($array) ? value($default) : end($array); + } + + return static::first(array_reverse($array, true), $callback, $default); + } + + /** + * Take the first or last {$limit} items from an array. + * + * @param array $array + * @param int $limit + * @return array + */ + public static function take($array, $limit) + { + if ($limit < 0) { + return array_slice($array, $limit, abs($limit)); + } + + return array_slice($array, 0, $limit); + } + + /** + * Flatten a multi-dimensional array into a single level. + * + * @param iterable $array + * @param int $depth + * @return array + */ + public static function flatten($array, $depth = INF) + { + $result = []; + + foreach ($array as $item) { + $item = $item instanceof Collection ? $item->all() : $item; + + if (! is_array($item)) { + $result[] = $item; + } else { + $values = $depth === 1 + ? array_values($item) + : static::flatten($item, $depth - 1); + + foreach ($values as $value) { + $result[] = $value; + } + } + } + + return $result; + } + + /** + * Remove one or many array items from a given array using "dot" notation. + * + * @param array $array + * @param array|string|int|float $keys + * @return void + */ + public static function forget(&$array, $keys) + { + $original = &$array; + + $keys = (array) $keys; + + if (count($keys) === 0) { + return; + } + + foreach ($keys as $key) { + // if the exact key exists in the top-level, remove it + if (static::exists($array, $key)) { + unset($array[$key]); + + continue; + } + + $parts = explode('.', $key); + + // clean up before each pass + $array = &$original; + + while (count($parts) > 1) { + $part = array_shift($parts); + + if (isset($array[$part]) && static::accessible($array[$part])) { + $array = &$array[$part]; + } else { + continue 2; + } + } + + unset($array[array_shift($parts)]); + } + } + + /** + * Get an item from an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|int|null $key + * @param mixed $default + * @return mixed + */ + public static function get($array, $key, $default = null) + { + if (! static::accessible($array)) { + return value($default); + } + + if (is_null($key)) { + return $array; + } + + if (static::exists($array, $key)) { + return $array[$key]; + } + + if (! str_contains($key, '.')) { + return $array[$key] ?? value($default); + } + + foreach (explode('.', $key) as $segment) { + if (static::accessible($array) && static::exists($array, $segment)) { + $array = $array[$segment]; + } else { + return value($default); + } + } + + return $array; + } + + /** + * Check if an item or items exist in an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|array $keys + * @return bool + */ + public static function has($array, $keys) + { + $keys = (array) $keys; + + if (! $array || $keys === []) { + return false; + } + + foreach ($keys as $key) { + $subKeyArray = $array; + + if (static::exists($array, $key)) { + continue; + } + + foreach (explode('.', $key) as $segment) { + if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) { + $subKeyArray = $subKeyArray[$segment]; + } else { + return false; + } + } + } + + return true; + } + + /** + * Determine if any of the keys exist in an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|array $keys + * @return bool + */ + public static function hasAny($array, $keys) + { + if (is_null($keys)) { + return false; + } + + $keys = (array) $keys; + + if (! $array) { + return false; + } + + if ($keys === []) { + return false; + } + + foreach ($keys as $key) { + if (static::has($array, $key)) { + return true; + } + } + + return false; + } + + /** + * Determines if an array is associative. + * + * An array is "associative" if it doesn't have sequential numerical keys beginning with zero. + * + * @param array $array + * @return bool + */ + public static function isAssoc(array $array) + { + return ! array_is_list($array); + } + + /** + * Determines if an array is a list. + * + * An array is a "list" if all array keys are sequential integers starting from 0 with no gaps in between. + * + * @param array $array + * @return bool + */ + public static function isList($array) + { + return array_is_list($array); + } + + /** + * Join all items using a string. The final items can use a separate glue string. + * + * @param array $array + * @param string $glue + * @param string $finalGlue + * @return string + */ + public static function join($array, $glue, $finalGlue = '') + { + if ($finalGlue === '') { + return implode($glue, $array); + } + + if (count($array) === 0) { + return ''; + } + + if (count($array) === 1) { + return end($array); + } + + $finalItem = array_pop($array); + + return implode($glue, $array).$finalGlue.$finalItem; + } + + /** + * Key an associative array by a field or using a callback. + * + * @param array $array + * @param callable|array|string $keyBy + * @return array + */ + public static function keyBy($array, $keyBy) + { + return Collection::make($array)->keyBy($keyBy)->all(); + } + + /** + * Prepend the key names of an associative array. + * + * @param array $array + * @param string $prependWith + * @return array + */ + public static function prependKeysWith($array, $prependWith) + { + return static::mapWithKeys($array, fn ($item, $key) => [$prependWith.$key => $item]); + } + + /** + * Get a subset of the items from the given array. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function only($array, $keys) + { + return array_intersect_key($array, array_flip((array) $keys)); + } + + /** + * Select an array of values from an array. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function select($array, $keys) + { + $keys = static::wrap($keys); + + return static::map($array, function ($item) use ($keys) { + $result = []; + + foreach ($keys as $key) { + if (Arr::accessible($item) && Arr::exists($item, $key)) { + $result[$key] = $item[$key]; + } elseif (is_object($item) && isset($item->{$key})) { + $result[$key] = $item->{$key}; + } + } + + return $result; + }); + } + + /** + * Pluck an array of values from an array. + * + * @param iterable $array + * @param string|array|int|null $value + * @param string|array|null $key + * @return array + */ + public static function pluck($array, $value, $key = null) + { + $results = []; + + [$value, $key] = static::explodePluckParameters($value, $key); + + foreach ($array as $item) { + $itemValue = data_get($item, $value); + + // If the key is "null", we will just append the value to the array and keep + // looping. Otherwise we will key the array using the value of the key we + // received from the developer. Then we'll return the final array form. + if (is_null($key)) { + $results[] = $itemValue; + } else { + $itemKey = data_get($item, $key); + + if (is_object($itemKey) && method_exists($itemKey, '__toString')) { + $itemKey = (string) $itemKey; + } + + $results[$itemKey] = $itemValue; + } + } + + return $results; + } + + /** + * Explode the "value" and "key" arguments passed to "pluck". + * + * @param string|array $value + * @param string|array|null $key + * @return array + */ + protected static function explodePluckParameters($value, $key) + { + $value = is_string($value) ? explode('.', $value) : $value; + + $key = is_null($key) || is_array($key) ? $key : explode('.', $key); + + return [$value, $key]; + } + + /** + * Run a map over each of the items in the array. + * + * @param array $array + * @param callable $callback + * @return array + */ + public static function map(array $array, callable $callback) + { + $keys = array_keys($array); + + try { + $items = array_map($callback, $array, $keys); + } catch (ArgumentCountError) { + $items = array_map($callback, $array); + } + + return array_combine($keys, $items); + } + + /** + * Run an associative map over each of the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @template TKey + * @template TValue + * @template TMapWithKeysKey of array-key + * @template TMapWithKeysValue + * + * @param array $array + * @param callable(TValue, TKey): array $callback + * @return array + */ + public static function mapWithKeys(array $array, callable $callback) + { + $result = []; + + foreach ($array as $key => $value) { + $assoc = $callback($value, $key); + + foreach ($assoc as $mapKey => $mapValue) { + $result[$mapKey] = $mapValue; + } + } + + return $result; + } + + /** + * Push an item onto the beginning of an array. + * + * @param array $array + * @param mixed $value + * @param mixed $key + * @return array + */ + public static function prepend($array, $value, $key = null) + { + if (func_num_args() == 2) { + array_unshift($array, $value); + } else { + $array = [$key => $value] + $array; + } + + return $array; + } + + /** + * Get a value from the array, and remove it. + * + * @param array $array + * @param string|int $key + * @param mixed $default + * @return mixed + */ + public static function pull(&$array, $key, $default = null) + { + $value = static::get($array, $key, $default); + + static::forget($array, $key); + + return $value; + } + + /** + * Convert the array into a query string. + * + * @param array $array + * @return string + */ + public static function query($array) + { + return http_build_query($array, '', '&', PHP_QUERY_RFC3986); + } + + /** + * Get one or a specified number of random values from an array. + * + * @param array $array + * @param int|null $number + * @param bool $preserveKeys + * @return mixed + * + * @throws \InvalidArgumentException + */ + public static function random($array, $number = null, $preserveKeys = false) + { + $requested = is_null($number) ? 1 : $number; + + $count = count($array); + + if ($requested > $count) { + throw new InvalidArgumentException( + "You requested {$requested} items, but there are only {$count} items available." + ); + } + + if (is_null($number)) { + return $array[array_rand($array)]; + } + + if ((int) $number === 0) { + return []; + } + + $keys = array_rand($array, $number); + + $results = []; + + if ($preserveKeys) { + foreach ((array) $keys as $key) { + $results[$key] = $array[$key]; + } + } else { + foreach ((array) $keys as $key) { + $results[] = $array[$key]; + } + } + + return $results; + } + + /** + * Set an array item to a given value using "dot" notation. + * + * If no key is given to the method, the entire array will be replaced. + * + * @param array $array + * @param string|int|null $key + * @param mixed $value + * @return array + */ + public static function set(&$array, $key, $value) + { + if (is_null($key)) { + return $array = $value; + } + + $keys = explode('.', $key); + + foreach ($keys as $i => $key) { + if (count($keys) === 1) { + break; + } + + unset($keys[$i]); + + // If the key doesn't exist at this depth, we will just create an empty array + // to hold the next value, allowing us to create the arrays to hold final + // values at the correct depth. Then we'll keep digging into the array. + if (! isset($array[$key]) || ! is_array($array[$key])) { + $array[$key] = []; + } + + $array = &$array[$key]; + } + + $array[array_shift($keys)] = $value; + + return $array; + } + + /** + * Shuffle the given array and return the result. + * + * @param array $array + * @param int|null $seed + * @return array + */ + public static function shuffle($array, $seed = null) + { + if (is_null($seed)) { + shuffle($array); + } else { + mt_srand($seed); + shuffle($array); + mt_srand(); + } + + return $array; + } + + /** + * Sort the array using the given callback or "dot" notation. + * + * @param array $array + * @param callable|array|string|null $callback + * @return array + */ + public static function sort($array, $callback = null) + { + return Collection::make($array)->sortBy($callback)->all(); + } + + /** + * Sort the array in descending order using the given callback or "dot" notation. + * + * @param array $array + * @param callable|array|string|null $callback + * @return array + */ + public static function sortDesc($array, $callback = null) + { + return Collection::make($array)->sortByDesc($callback)->all(); + } + + /** + * Recursively sort an array by keys and values. + * + * @param array $array + * @param int $options + * @param bool $descending + * @return array + */ + public static function sortRecursive($array, $options = SORT_REGULAR, $descending = false) + { + foreach ($array as &$value) { + if (is_array($value)) { + $value = static::sortRecursive($value, $options, $descending); + } + } + + if (! array_is_list($array)) { + $descending + ? krsort($array, $options) + : ksort($array, $options); + } else { + $descending + ? rsort($array, $options) + : sort($array, $options); + } + + return $array; + } + + /** + * Recursively sort an array by keys and values in descending order. + * + * @param array $array + * @param int $options + * @return array + */ + public static function sortRecursiveDesc($array, $options = SORT_REGULAR) + { + return static::sortRecursive($array, $options, true); + } + + /** + * Conditionally compile classes from an array into a CSS class list. + * + * @param array $array + * @return string + */ + public static function toCssClasses($array) + { + $classList = static::wrap($array); + + $classes = []; + + foreach ($classList as $class => $constraint) { + if (is_numeric($class)) { + $classes[] = $constraint; + } elseif ($constraint) { + $classes[] = $class; + } + } + + return implode(' ', $classes); + } + + /** + * Conditionally compile styles from an array into a style list. + * + * @param array $array + * @return string + */ + public static function toCssStyles($array) + { + $styleList = static::wrap($array); + + $styles = []; + + foreach ($styleList as $class => $constraint) { + if (is_numeric($class)) { + $styles[] = Str::finish($constraint, ';'); + } elseif ($constraint) { + $styles[] = Str::finish($class, ';'); + } + } + + return implode(' ', $styles); + } + + /** + * Filter the array using the given callback. + * + * @param array $array + * @param callable $callback + * @return array + */ + public static function where($array, callable $callback) + { + return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); + } + + /** + * Filter items where the value is not null. + * + * @param array $array + * @return array + */ + public static function whereNotNull($array) + { + return static::where($array, fn ($value) => ! is_null($value)); + } + + /** + * If the given value is not an array and not null, wrap it in one. + * + * @param mixed $value + * @return array + */ + public static function wrap($value) + { + if (is_null($value)) { + return []; + } + + return is_array($value) ? $value : [$value]; + } +} diff --git a/data/web/inc/lib/vendor/illuminate/collections/Collection.php b/data/web/inc/lib/vendor/illuminate/collections/Collection.php new file mode 100644 index 0000000000..2f0c768f14 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/collections/Collection.php @@ -0,0 +1,1821 @@ + + * @implements \Illuminate\Support\Enumerable + */ +class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerable +{ + /** + * @use \Illuminate\Support\Traits\EnumeratesValues + */ + use EnumeratesValues, Macroable; + + /** + * The items contained in the collection. + * + * @var array + */ + protected $items = []; + + /** + * Create a new collection. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable|null $items + * @return void + */ + public function __construct($items = []) + { + $this->items = $this->getArrayableItems($items); + } + + /** + * Create a collection with the given range. + * + * @param int $from + * @param int $to + * @return static + */ + public static function range($from, $to) + { + return new static(range($from, $to)); + } + + /** + * Get all of the items in the collection. + * + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * Get a lazy collection for the items in this collection. + * + * @return \Illuminate\Support\LazyCollection + */ + public function lazy() + { + return new LazyCollection($this->items); + } + + /** + * Get the average value of a given key. + * + * @param (callable(TValue): float|int)|string|null $callback + * @return float|int|null + */ + public function avg($callback = null) + { + $callback = $this->valueRetriever($callback); + + $items = $this + ->map(fn ($value) => $callback($value)) + ->filter(fn ($value) => ! is_null($value)); + + if ($count = $items->count()) { + return $items->sum() / $count; + } + } + + /** + * Get the median of a given key. + * + * @param string|array|null $key + * @return float|int|null + */ + public function median($key = null) + { + $values = (isset($key) ? $this->pluck($key) : $this) + ->filter(fn ($item) => ! is_null($item)) + ->sort()->values(); + + $count = $values->count(); + + if ($count === 0) { + return; + } + + $middle = (int) ($count / 2); + + if ($count % 2) { + return $values->get($middle); + } + + return (new static([ + $values->get($middle - 1), $values->get($middle), + ]))->average(); + } + + /** + * Get the mode of a given key. + * + * @param string|array|null $key + * @return array|null + */ + public function mode($key = null) + { + if ($this->count() === 0) { + return; + } + + $collection = isset($key) ? $this->pluck($key) : $this; + + $counts = new static; + + $collection->each(fn ($value) => $counts[$value] = isset($counts[$value]) ? $counts[$value] + 1 : 1); + + $sorted = $counts->sort(); + + $highestValue = $sorted->last(); + + return $sorted->filter(fn ($value) => $value == $highestValue) + ->sort()->keys()->all(); + } + + /** + * Collapse the collection of items into a single array. + * + * @return static + */ + public function collapse() + { + return new static(Arr::collapse($this->items)); + } + + /** + * Determine if an item exists in the collection. + * + * @param (callable(TValue, TKey): bool)|TValue|string $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function contains($key, $operator = null, $value = null) + { + if (func_num_args() === 1) { + if ($this->useAsCallable($key)) { + $placeholder = new stdClass; + + return $this->first($key, $placeholder) !== $placeholder; + } + + return in_array($key, $this->items); + } + + return $this->contains($this->operatorForWhere(...func_get_args())); + } + + /** + * Determine if an item exists, using strict comparison. + * + * @param (callable(TValue): bool)|TValue|array-key $key + * @param TValue|null $value + * @return bool + */ + public function containsStrict($key, $value = null) + { + if (func_num_args() === 2) { + return $this->contains(fn ($item) => data_get($item, $key) === $value); + } + + if ($this->useAsCallable($key)) { + return ! is_null($this->first($key)); + } + + return in_array($key, $this->items, true); + } + + /** + * Determine if an item is not contained in the collection. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function doesntContain($key, $operator = null, $value = null) + { + return ! $this->contains(...func_get_args()); + } + + /** + * Cross join with the given lists, returning all possible permutations. + * + * @template TCrossJoinKey + * @template TCrossJoinValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$lists + * @return static> + */ + public function crossJoin(...$lists) + { + return new static(Arr::crossJoin( + $this->items, ...array_map([$this, 'getArrayableItems'], $lists) + )); + } + + /** + * Get the items in the collection that are not present in the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function diff($items) + { + return new static(array_diff($this->items, $this->getArrayableItems($items))); + } + + /** + * Get the items in the collection that are not present in the given items, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TValue, TValue): int $callback + * @return static + */ + public function diffUsing($items, callable $callback) + { + return new static(array_udiff($this->items, $this->getArrayableItems($items), $callback)); + } + + /** + * Get the items in the collection whose keys and values are not present in the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function diffAssoc($items) + { + return new static(array_diff_assoc($this->items, $this->getArrayableItems($items))); + } + + /** + * Get the items in the collection whose keys and values are not present in the given items, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TKey, TKey): int $callback + * @return static + */ + public function diffAssocUsing($items, callable $callback) + { + return new static(array_diff_uassoc($this->items, $this->getArrayableItems($items), $callback)); + } + + /** + * Get the items in the collection whose keys are not present in the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function diffKeys($items) + { + return new static(array_diff_key($this->items, $this->getArrayableItems($items))); + } + + /** + * Get the items in the collection whose keys are not present in the given items, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TKey, TKey): int $callback + * @return static + */ + public function diffKeysUsing($items, callable $callback) + { + return new static(array_diff_ukey($this->items, $this->getArrayableItems($items), $callback)); + } + + /** + * Retrieve duplicate items from the collection. + * + * @param (callable(TValue): bool)|string|null $callback + * @param bool $strict + * @return static + */ + public function duplicates($callback = null, $strict = false) + { + $items = $this->map($this->valueRetriever($callback)); + + $uniqueItems = $items->unique(null, $strict); + + $compare = $this->duplicateComparator($strict); + + $duplicates = new static; + + foreach ($items as $key => $value) { + if ($uniqueItems->isNotEmpty() && $compare($value, $uniqueItems->first())) { + $uniqueItems->shift(); + } else { + $duplicates[$key] = $value; + } + } + + return $duplicates; + } + + /** + * Retrieve duplicate items from the collection using strict comparison. + * + * @param (callable(TValue): bool)|string|null $callback + * @return static + */ + public function duplicatesStrict($callback = null) + { + return $this->duplicates($callback, true); + } + + /** + * Get the comparison function to detect duplicates. + * + * @param bool $strict + * @return callable(TValue, TValue): bool + */ + protected function duplicateComparator($strict) + { + if ($strict) { + return fn ($a, $b) => $a === $b; + } + + return fn ($a, $b) => $a == $b; + } + + /** + * Get all items except for those with the specified keys. + * + * @param \Illuminate\Support\Enumerable|array|string $keys + * @return static + */ + public function except($keys) + { + if (is_null($keys)) { + return new static($this->items); + } + + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } elseif (! is_array($keys)) { + $keys = func_get_args(); + } + + return new static(Arr::except($this->items, $keys)); + } + + /** + * Run a filter over each of the items. + * + * @param (callable(TValue, TKey): bool)|null $callback + * @return static + */ + public function filter(?callable $callback = null) + { + if ($callback) { + return new static(Arr::where($this->items, $callback)); + } + + return new static(array_filter($this->items)); + } + + /** + * Get the first item from the collection passing the given truth test. + * + * @template TFirstDefault + * + * @param (callable(TValue, TKey): bool)|null $callback + * @param TFirstDefault|(\Closure(): TFirstDefault) $default + * @return TValue|TFirstDefault + */ + public function first(?callable $callback = null, $default = null) + { + return Arr::first($this->items, $callback, $default); + } + + /** + * Get a flattened array of the items in the collection. + * + * @param int $depth + * @return static + */ + public function flatten($depth = INF) + { + return new static(Arr::flatten($this->items, $depth)); + } + + /** + * Flip the items in the collection. + * + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * Remove an item from the collection by key. + * + * \Illuminate\Contracts\Support\Arrayable|iterable|TKey $keys + * + * @return $this + */ + public function forget($keys) + { + foreach ($this->getArrayableItems($keys) as $key) { + $this->offsetUnset($key); + } + + return $this; + } + + /** + * Get an item from the collection by key. + * + * @template TGetDefault + * + * @param TKey $key + * @param TGetDefault|(\Closure(): TGetDefault) $default + * @return TValue|TGetDefault + */ + public function get($key, $default = null) + { + if (array_key_exists($key, $this->items)) { + return $this->items[$key]; + } + + return value($default); + } + + /** + * Get an item from the collection by key or add it to collection if it does not exist. + * + * @template TGetOrPutValue + * + * @param mixed $key + * @param TGetOrPutValue|(\Closure(): TGetOrPutValue) $value + * @return TValue|TGetOrPutValue + */ + public function getOrPut($key, $value) + { + if (array_key_exists($key, $this->items)) { + return $this->items[$key]; + } + + $this->offsetSet($key, $value = value($value)); + + return $value; + } + + /** + * Group an associative array by a field or using a callback. + * + * @param (callable(TValue, TKey): array-key)|array|string $groupBy + * @param bool $preserveKeys + * @return static> + */ + public function groupBy($groupBy, $preserveKeys = false) + { + if (! $this->useAsCallable($groupBy) && is_array($groupBy)) { + $nextGroups = $groupBy; + + $groupBy = array_shift($nextGroups); + } + + $groupBy = $this->valueRetriever($groupBy); + + $results = []; + + foreach ($this->items as $key => $value) { + $groupKeys = $groupBy($value, $key); + + if (! is_array($groupKeys)) { + $groupKeys = [$groupKeys]; + } + + foreach ($groupKeys as $groupKey) { + $groupKey = match (true) { + is_bool($groupKey) => (int) $groupKey, + $groupKey instanceof \BackedEnum => $groupKey->value, + $groupKey instanceof \Stringable => (string) $groupKey, + default => $groupKey, + }; + + if (! array_key_exists($groupKey, $results)) { + $results[$groupKey] = new static; + } + + $results[$groupKey]->offsetSet($preserveKeys ? $key : null, $value); + } + } + + $result = new static($results); + + if (! empty($nextGroups)) { + return $result->map->groupBy($nextGroups, $preserveKeys); + } + + return $result; + } + + /** + * Key an associative array by a field or using a callback. + * + * @param (callable(TValue, TKey): array-key)|array|string $keyBy + * @return static + */ + public function keyBy($keyBy) + { + $keyBy = $this->valueRetriever($keyBy); + + $results = []; + + foreach ($this->items as $key => $item) { + $resolvedKey = $keyBy($item, $key); + + if (is_object($resolvedKey)) { + $resolvedKey = (string) $resolvedKey; + } + + $results[$resolvedKey] = $item; + } + + return new static($results); + } + + /** + * Determine if an item exists in the collection by key. + * + * @param TKey|array $key + * @return bool + */ + public function has($key) + { + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $value) { + if (! array_key_exists($value, $this->items)) { + return false; + } + } + + return true; + } + + /** + * Determine if any of the keys exist in the collection. + * + * @param mixed $key + * @return bool + */ + public function hasAny($key) + { + if ($this->isEmpty()) { + return false; + } + + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $value) { + if ($this->has($value)) { + return true; + } + } + + return false; + } + + /** + * Concatenate values of a given key as a string. + * + * @param callable|string $value + * @param string|null $glue + * @return string + */ + public function implode($value, $glue = null) + { + if ($this->useAsCallable($value)) { + return implode($glue ?? '', $this->map($value)->all()); + } + + $first = $this->first(); + + if (is_array($first) || (is_object($first) && ! $first instanceof Stringable)) { + return implode($glue ?? '', $this->pluck($value)->all()); + } + + return implode($value ?? '', $this->items); + } + + /** + * Intersect the collection with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function intersect($items) + { + return new static(array_intersect($this->items, $this->getArrayableItems($items))); + } + + /** + * Intersect the collection with the given items, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TValue, TValue): int $callback + * @return static + */ + public function intersectUsing($items, callable $callback) + { + return new static(array_uintersect($this->items, $this->getArrayableItems($items), $callback)); + } + + /** + * Intersect the collection with the given items with additional index check. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function intersectAssoc($items) + { + return new static(array_intersect_assoc($this->items, $this->getArrayableItems($items))); + } + + /** + * Intersect the collection with the given items with additional index check, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TValue, TValue): int $callback + * @return static + */ + public function intersectAssocUsing($items, callable $callback) + { + return new static(array_intersect_uassoc($this->items, $this->getArrayableItems($items), $callback)); + } + + /** + * Intersect the collection with the given items by key. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function intersectByKeys($items) + { + return new static(array_intersect_key( + $this->items, $this->getArrayableItems($items) + )); + } + + /** + * Determine if the collection is empty or not. + * + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + /** + * Determine if the collection contains a single item. + * + * @return bool + */ + public function containsOneItem() + { + return $this->count() === 1; + } + + /** + * Join all items from the collection using a string. The final items can use a separate glue string. + * + * @param string $glue + * @param string $finalGlue + * @return string + */ + public function join($glue, $finalGlue = '') + { + if ($finalGlue === '') { + return $this->implode($glue); + } + + $count = $this->count(); + + if ($count === 0) { + return ''; + } + + if ($count === 1) { + return $this->last(); + } + + $collection = new static($this->items); + + $finalItem = $collection->pop(); + + return $collection->implode($glue).$finalGlue.$finalItem; + } + + /** + * Get the keys of the collection items. + * + * @return static + */ + public function keys() + { + return new static(array_keys($this->items)); + } + + /** + * Get the last item from the collection. + * + * @template TLastDefault + * + * @param (callable(TValue, TKey): bool)|null $callback + * @param TLastDefault|(\Closure(): TLastDefault) $default + * @return TValue|TLastDefault + */ + public function last(?callable $callback = null, $default = null) + { + return Arr::last($this->items, $callback, $default); + } + + /** + * Get the values of a given key. + * + * @param string|int|array $value + * @param string|null $key + * @return static + */ + public function pluck($value, $key = null) + { + return new static(Arr::pluck($this->items, $value, $key)); + } + + /** + * Run a map over each of the items. + * + * @template TMapValue + * + * @param callable(TValue, TKey): TMapValue $callback + * @return static + */ + public function map(callable $callback) + { + return new static(Arr::map($this->items, $callback)); + } + + /** + * Run a dictionary map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @template TMapToDictionaryKey of array-key + * @template TMapToDictionaryValue + * + * @param callable(TValue, TKey): array $callback + * @return static> + */ + public function mapToDictionary(callable $callback) + { + $dictionary = []; + + foreach ($this->items as $key => $item) { + $pair = $callback($item, $key); + + $key = key($pair); + + $value = reset($pair); + + if (! isset($dictionary[$key])) { + $dictionary[$key] = []; + } + + $dictionary[$key][] = $value; + } + + return new static($dictionary); + } + + /** + * Run an associative map over each of the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @template TMapWithKeysKey of array-key + * @template TMapWithKeysValue + * + * @param callable(TValue, TKey): array $callback + * @return static + */ + public function mapWithKeys(callable $callback) + { + return new static(Arr::mapWithKeys($this->items, $callback)); + } + + /** + * Merge the collection with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->getArrayableItems($items))); + } + + /** + * Recursively merge the collection with the given items. + * + * @template TMergeRecursiveValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function mergeRecursive($items) + { + return new static(array_merge_recursive($this->items, $this->getArrayableItems($items))); + } + + /** + * Create a collection by using this collection for keys and another for its values. + * + * @template TCombineValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values + * @return static + */ + public function combine($values) + { + return new static(array_combine($this->all(), $this->getArrayableItems($values))); + } + + /** + * Union the collection with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function union($items) + { + return new static($this->items + $this->getArrayableItems($items)); + } + + /** + * Create a new collection consisting of every n-th element. + * + * @param int $step + * @param int $offset + * @return static + */ + public function nth($step, $offset = 0) + { + $new = []; + + $position = 0; + + foreach ($this->slice($offset)->items as $item) { + if ($position % $step === 0) { + $new[] = $item; + } + + $position++; + } + + return new static($new); + } + + /** + * Get the items with the specified keys. + * + * @param \Illuminate\Support\Enumerable|array|string|null $keys + * @return static + */ + public function only($keys) + { + if (is_null($keys)) { + return new static($this->items); + } + + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } + + $keys = is_array($keys) ? $keys : func_get_args(); + + return new static(Arr::only($this->items, $keys)); + } + + /** + * Select specific values from the items within the collection. + * + * @param \Illuminate\Support\Enumerable|array|string|null $keys + * @return static + */ + public function select($keys) + { + if (is_null($keys)) { + return new static($this->items); + } + + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } + + $keys = is_array($keys) ? $keys : func_get_args(); + + return new static(Arr::select($this->items, $keys)); + } + + /** + * Get and remove the last N items from the collection. + * + * @param int $count + * @return static|TValue|null + */ + public function pop($count = 1) + { + if ($count === 1) { + return array_pop($this->items); + } + + if ($this->isEmpty()) { + return new static; + } + + $results = []; + + $collectionCount = $this->count(); + + foreach (range(1, min($count, $collectionCount)) as $item) { + array_push($results, array_pop($this->items)); + } + + return new static($results); + } + + /** + * Push an item onto the beginning of the collection. + * + * @param TValue $value + * @param TKey $key + * @return $this + */ + public function prepend($value, $key = null) + { + $this->items = Arr::prepend($this->items, ...func_get_args()); + + return $this; + } + + /** + * Push one or more items onto the end of the collection. + * + * @param TValue ...$values + * @return $this + */ + public function push(...$values) + { + foreach ($values as $value) { + $this->items[] = $value; + } + + return $this; + } + + /** + * Push all of the given items onto the collection. + * + * @template TConcatKey of array-key + * @template TConcatValue + * + * @param iterable $source + * @return static + */ + public function concat($source) + { + $result = new static($this); + + foreach ($source as $item) { + $result->push($item); + } + + return $result; + } + + /** + * Get and remove an item from the collection. + * + * @template TPullDefault + * + * @param TKey $key + * @param TPullDefault|(\Closure(): TPullDefault) $default + * @return TValue|TPullDefault + */ + public function pull($key, $default = null) + { + return Arr::pull($this->items, $key, $default); + } + + /** + * Put an item in the collection by key. + * + * @param TKey $key + * @param TValue $value + * @return $this + */ + public function put($key, $value) + { + $this->offsetSet($key, $value); + + return $this; + } + + /** + * Get one or a specified number of items randomly from the collection. + * + * @param (callable(self): int)|int|null $number + * @param bool $preserveKeys + * @return static|TValue + * + * @throws \InvalidArgumentException + */ + public function random($number = null, $preserveKeys = false) + { + if (is_null($number)) { + return Arr::random($this->items); + } + + if (is_callable($number)) { + return new static(Arr::random($this->items, $number($this), $preserveKeys)); + } + + return new static(Arr::random($this->items, $number, $preserveKeys)); + } + + /** + * Replace the collection items with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function replace($items) + { + return new static(array_replace($this->items, $this->getArrayableItems($items))); + } + + /** + * Recursively replace the collection items with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function replaceRecursive($items) + { + return new static(array_replace_recursive($this->items, $this->getArrayableItems($items))); + } + + /** + * Reverse items order. + * + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items, true)); + } + + /** + * Search the collection for a given value and return the corresponding key if successful. + * + * @param TValue|(callable(TValue,TKey): bool) $value + * @param bool $strict + * @return TKey|false + */ + public function search($value, $strict = false) + { + if (! $this->useAsCallable($value)) { + return array_search($value, $this->items, $strict); + } + + foreach ($this->items as $key => $item) { + if ($value($item, $key)) { + return $key; + } + } + + return false; + } + + /** + * Get and remove the first N items from the collection. + * + * @param int $count + * @return static|TValue|null + * + * @throws \InvalidArgumentException + */ + public function shift($count = 1) + { + if ($count < 0) { + throw new InvalidArgumentException('Number of shifted items may not be less than zero.'); + } + + if ($this->isEmpty()) { + return null; + } + + if ($count === 0) { + return new static; + } + + if ($count === 1) { + return array_shift($this->items); + } + + $results = []; + + $collectionCount = $this->count(); + + foreach (range(1, min($count, $collectionCount)) as $item) { + array_push($results, array_shift($this->items)); + } + + return new static($results); + } + + /** + * Shuffle the items in the collection. + * + * @param int|null $seed + * @return static + */ + public function shuffle($seed = null) + { + return new static(Arr::shuffle($this->items, $seed)); + } + + /** + * Create chunks representing a "sliding window" view of the items in the collection. + * + * @param int $size + * @param int $step + * @return static + */ + public function sliding($size = 2, $step = 1) + { + $chunks = floor(($this->count() - $size) / $step) + 1; + + return static::times($chunks, fn ($number) => $this->slice(($number - 1) * $step, $size)); + } + + /** + * Skip the first {$count} items. + * + * @param int $count + * @return static + */ + public function skip($count) + { + return $this->slice($count); + } + + /** + * Skip items in the collection until the given condition is met. + * + * @param TValue|callable(TValue,TKey): bool $value + * @return static + */ + public function skipUntil($value) + { + return new static($this->lazy()->skipUntil($value)->all()); + } + + /** + * Skip items in the collection while the given condition is met. + * + * @param TValue|callable(TValue,TKey): bool $value + * @return static + */ + public function skipWhile($value) + { + return new static($this->lazy()->skipWhile($value)->all()); + } + + /** + * Slice the underlying collection array. + * + * @param int $offset + * @param int|null $length + * @return static + */ + public function slice($offset, $length = null) + { + return new static(array_slice($this->items, $offset, $length, true)); + } + + /** + * Split a collection into a certain number of groups. + * + * @param int $numberOfGroups + * @return static + */ + public function split($numberOfGroups) + { + if ($this->isEmpty()) { + return new static; + } + + $groups = new static; + + $groupSize = floor($this->count() / $numberOfGroups); + + $remain = $this->count() % $numberOfGroups; + + $start = 0; + + for ($i = 0; $i < $numberOfGroups; $i++) { + $size = $groupSize; + + if ($i < $remain) { + $size++; + } + + if ($size) { + $groups->push(new static(array_slice($this->items, $start, $size))); + + $start += $size; + } + } + + return $groups; + } + + /** + * Split a collection into a certain number of groups, and fill the first groups completely. + * + * @param int $numberOfGroups + * @return static + */ + public function splitIn($numberOfGroups) + { + return $this->chunk(ceil($this->count() / $numberOfGroups)); + } + + /** + * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. + * + * @param (callable(TValue, TKey): bool)|string $key + * @param mixed $operator + * @param mixed $value + * @return TValue + * + * @throws \Illuminate\Support\ItemNotFoundException + * @throws \Illuminate\Support\MultipleItemsFoundException + */ + public function sole($key = null, $operator = null, $value = null) + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + $items = $this->unless($filter == null)->filter($filter); + + $count = $items->count(); + + if ($count === 0) { + throw new ItemNotFoundException; + } + + if ($count > 1) { + throw new MultipleItemsFoundException($count); + } + + return $items->first(); + } + + /** + * Get the first item in the collection but throw an exception if no matching items exist. + * + * @param (callable(TValue, TKey): bool)|string $key + * @param mixed $operator + * @param mixed $value + * @return TValue + * + * @throws \Illuminate\Support\ItemNotFoundException + */ + public function firstOrFail($key = null, $operator = null, $value = null) + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + $placeholder = new stdClass(); + + $item = $this->first($filter, $placeholder); + + if ($item === $placeholder) { + throw new ItemNotFoundException; + } + + return $item; + } + + /** + * Chunk the collection into chunks of the given size. + * + * @param int $size + * @return static + */ + public function chunk($size) + { + if ($size <= 0) { + return new static; + } + + $chunks = []; + + foreach (array_chunk($this->items, $size, true) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * Chunk the collection into chunks with a callback. + * + * @param callable(TValue, TKey, static): bool $callback + * @return static> + */ + public function chunkWhile(callable $callback) + { + return new static( + $this->lazy()->chunkWhile($callback)->mapInto(static::class) + ); + } + + /** + * Sort through each item with a callback. + * + * @param (callable(TValue, TValue): int)|null|int $callback + * @return static + */ + public function sort($callback = null) + { + $items = $this->items; + + $callback && is_callable($callback) + ? uasort($items, $callback) + : asort($items, $callback ?? SORT_REGULAR); + + return new static($items); + } + + /** + * Sort items in descending order. + * + * @param int $options + * @return static + */ + public function sortDesc($options = SORT_REGULAR) + { + $items = $this->items; + + arsort($items, $options); + + return new static($items); + } + + /** + * Sort the collection using the given callback. + * + * @param array|(callable(TValue, TKey): mixed)|string $callback + * @param int $options + * @param bool $descending + * @return static + */ + public function sortBy($callback, $options = SORT_REGULAR, $descending = false) + { + if (is_array($callback) && ! is_callable($callback)) { + return $this->sortByMany($callback, $options); + } + + $results = []; + + $callback = $this->valueRetriever($callback); + + // First we will loop through the items and get the comparator from a callback + // function which we were given. Then, we will sort the returned values and + // grab all the corresponding values for the sorted keys from this array. + foreach ($this->items as $key => $value) { + $results[$key] = $callback($value, $key); + } + + $descending ? arsort($results, $options) + : asort($results, $options); + + // Once we have sorted all of the keys in the array, we will loop through them + // and grab the corresponding model so we can set the underlying items list + // to the sorted version. Then we'll just return the collection instance. + foreach (array_keys($results) as $key) { + $results[$key] = $this->items[$key]; + } + + return new static($results); + } + + /** + * Sort the collection using multiple comparisons. + * + * @param array $comparisons + * @param int $options + * @return static + */ + protected function sortByMany(array $comparisons = [], int $options = SORT_REGULAR) + { + $items = $this->items; + + uasort($items, function ($a, $b) use ($comparisons, $options) { + foreach ($comparisons as $comparison) { + $comparison = Arr::wrap($comparison); + + $prop = $comparison[0]; + + $ascending = Arr::get($comparison, 1, true) === true || + Arr::get($comparison, 1, true) === 'asc'; + + if (! is_string($prop) && is_callable($prop)) { + $result = $prop($a, $b); + } else { + $values = [data_get($a, $prop), data_get($b, $prop)]; + + if (! $ascending) { + $values = array_reverse($values); + } + + if (($options & SORT_FLAG_CASE) === SORT_FLAG_CASE) { + if (($options & SORT_NATURAL) === SORT_NATURAL) { + $result = strnatcasecmp($values[0], $values[1]); + } else { + $result = strcasecmp($values[0], $values[1]); + } + } else { + $result = match ($options) { + SORT_NUMERIC => intval($values[0]) <=> intval($values[1]), + SORT_STRING => strcmp($values[0], $values[1]), + SORT_NATURAL => strnatcmp($values[0], $values[1]), + SORT_LOCALE_STRING => strcoll($values[0], $values[1]), + default => $values[0] <=> $values[1], + }; + } + } + + if ($result === 0) { + continue; + } + + return $result; + } + }); + + return new static($items); + } + + /** + * Sort the collection in descending order using the given callback. + * + * @param array|(callable(TValue, TKey): mixed)|string $callback + * @param int $options + * @return static + */ + public function sortByDesc($callback, $options = SORT_REGULAR) + { + if (is_array($callback) && ! is_callable($callback)) { + foreach ($callback as $index => $key) { + $comparison = Arr::wrap($key); + + $comparison[1] = 'desc'; + + $callback[$index] = $comparison; + } + } + + return $this->sortBy($callback, $options, true); + } + + /** + * Sort the collection keys. + * + * @param int $options + * @param bool $descending + * @return static + */ + public function sortKeys($options = SORT_REGULAR, $descending = false) + { + $items = $this->items; + + $descending ? krsort($items, $options) : ksort($items, $options); + + return new static($items); + } + + /** + * Sort the collection keys in descending order. + * + * @param int $options + * @return static + */ + public function sortKeysDesc($options = SORT_REGULAR) + { + return $this->sortKeys($options, true); + } + + /** + * Sort the collection keys using a callback. + * + * @param callable(TKey, TKey): int $callback + * @return static + */ + public function sortKeysUsing(callable $callback) + { + $items = $this->items; + + uksort($items, $callback); + + return new static($items); + } + + /** + * Splice a portion of the underlying collection array. + * + * @param int $offset + * @param int|null $length + * @param array $replacement + * @return static + */ + public function splice($offset, $length = null, $replacement = []) + { + if (func_num_args() === 1) { + return new static(array_splice($this->items, $offset)); + } + + return new static(array_splice($this->items, $offset, $length, $this->getArrayableItems($replacement))); + } + + /** + * Take the first or last {$limit} items. + * + * @param int $limit + * @return static + */ + public function take($limit) + { + if ($limit < 0) { + return $this->slice($limit, abs($limit)); + } + + return $this->slice(0, $limit); + } + + /** + * Take items in the collection until the given condition is met. + * + * @param TValue|callable(TValue,TKey): bool $value + * @return static + */ + public function takeUntil($value) + { + return new static($this->lazy()->takeUntil($value)->all()); + } + + /** + * Take items in the collection while the given condition is met. + * + * @param TValue|callable(TValue,TKey): bool $value + * @return static + */ + public function takeWhile($value) + { + return new static($this->lazy()->takeWhile($value)->all()); + } + + /** + * Transform each item in the collection using a callback. + * + * @param callable(TValue, TKey): TValue $callback + * @return $this + */ + public function transform(callable $callback) + { + $this->items = $this->map($callback)->all(); + + return $this; + } + + /** + * Flatten a multi-dimensional associative array with dots. + * + * @return static + */ + public function dot() + { + return new static(Arr::dot($this->all())); + } + + /** + * Convert a flatten "dot" notation array into an expanded array. + * + * @return static + */ + public function undot() + { + return new static(Arr::undot($this->all())); + } + + /** + * Return only unique items from the collection array. + * + * @param (callable(TValue, TKey): mixed)|string|null $key + * @param bool $strict + * @return static + */ + public function unique($key = null, $strict = false) + { + if (is_null($key) && $strict === false) { + return new static(array_unique($this->items, SORT_REGULAR)); + } + + $callback = $this->valueRetriever($key); + + $exists = []; + + return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) { + if (in_array($id = $callback($item, $key), $exists, $strict)) { + return true; + } + + $exists[] = $id; + }); + } + + /** + * Reset the keys on the underlying array. + * + * @return static + */ + public function values() + { + return new static(array_values($this->items)); + } + + /** + * Zip the collection together with one or more arrays. + * + * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]); + * => [[1, 4], [2, 5], [3, 6]] + * + * @template TZipValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$items + * @return static> + */ + public function zip($items) + { + $arrayableItems = array_map(fn ($items) => $this->getArrayableItems($items), func_get_args()); + + $params = array_merge([fn () => new static(func_get_args()), $this->items], $arrayableItems); + + return new static(array_map(...$params)); + } + + /** + * Pad collection to the specified length with a value. + * + * @template TPadValue + * + * @param int $size + * @param TPadValue $value + * @return static + */ + public function pad($size, $value) + { + return new static(array_pad($this->items, $size, $value)); + } + + /** + * Get an iterator for the items. + * + * @return \ArrayIterator + */ + public function getIterator(): Traversable + { + return new ArrayIterator($this->items); + } + + /** + * Count the number of items in the collection. + * + * @return int + */ + public function count(): int + { + return count($this->items); + } + + /** + * Count the number of items in the collection by a field or using a callback. + * + * @param (callable(TValue, TKey): array-key)|string|null $countBy + * @return static + */ + public function countBy($countBy = null) + { + return new static($this->lazy()->countBy($countBy)->all()); + } + + /** + * Add an item to the collection. + * + * @param TValue $item + * @return $this + */ + public function add($item) + { + $this->items[] = $item; + + return $this; + } + + /** + * Get a base Support collection instance from this collection. + * + * @return \Illuminate\Support\Collection + */ + public function toBase() + { + return new self($this); + } + + /** + * Determine if an item exists at an offset. + * + * @param TKey $key + * @return bool + */ + public function offsetExists($key): bool + { + return isset($this->items[$key]); + } + + /** + * Get an item at a given offset. + * + * @param TKey $key + * @return TValue + */ + public function offsetGet($key): mixed + { + return $this->items[$key]; + } + + /** + * Set the item at a given offset. + * + * @param TKey|null $key + * @param TValue $value + * @return void + */ + public function offsetSet($key, $value): void + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * Unset the item at a given offset. + * + * @param TKey $key + * @return void + */ + public function offsetUnset($key): void + { + unset($this->items[$key]); + } +} diff --git a/data/web/inc/lib/vendor/illuminate/collections/Enumerable.php b/data/web/inc/lib/vendor/illuminate/collections/Enumerable.php new file mode 100644 index 0000000000..806a6923f6 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/collections/Enumerable.php @@ -0,0 +1,1263 @@ + + * @extends \IteratorAggregate + */ +interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable +{ + /** + * Create a new collection instance if the value isn't one already. + * + * @template TMakeKey of array-key + * @template TMakeValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable|null $items + * @return static + */ + public static function make($items = []); + + /** + * Create a new instance by invoking the callback a given amount of times. + * + * @param int $number + * @param callable|null $callback + * @return static + */ + public static function times($number, ?callable $callback = null); + + /** + * Create a collection with the given range. + * + * @param int $from + * @param int $to + * @return static + */ + public static function range($from, $to); + + /** + * Wrap the given value in a collection if applicable. + * + * @template TWrapValue + * + * @param iterable|TWrapValue $value + * @return static + */ + public static function wrap($value); + + /** + * Get the underlying items from the given collection if applicable. + * + * @template TUnwrapKey of array-key + * @template TUnwrapValue + * + * @param array|static $value + * @return array + */ + public static function unwrap($value); + + /** + * Create a new instance with no items. + * + * @return static + */ + public static function empty(); + + /** + * Get all items in the enumerable. + * + * @return array + */ + public function all(); + + /** + * Alias for the "avg" method. + * + * @param (callable(TValue): float|int)|string|null $callback + * @return float|int|null + */ + public function average($callback = null); + + /** + * Get the median of a given key. + * + * @param string|array|null $key + * @return float|int|null + */ + public function median($key = null); + + /** + * Get the mode of a given key. + * + * @param string|array|null $key + * @return array|null + */ + public function mode($key = null); + + /** + * Collapse the items into a single enumerable. + * + * @return static + */ + public function collapse(); + + /** + * Alias for the "contains" method. + * + * @param (callable(TValue, TKey): bool)|TValue|string $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function some($key, $operator = null, $value = null); + + /** + * Determine if an item exists, using strict comparison. + * + * @param (callable(TValue): bool)|TValue|array-key $key + * @param TValue|null $value + * @return bool + */ + public function containsStrict($key, $value = null); + + /** + * Get the average value of a given key. + * + * @param (callable(TValue): float|int)|string|null $callback + * @return float|int|null + */ + public function avg($callback = null); + + /** + * Determine if an item exists in the enumerable. + * + * @param (callable(TValue, TKey): bool)|TValue|string $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function contains($key, $operator = null, $value = null); + + /** + * Determine if an item is not contained in the collection. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function doesntContain($key, $operator = null, $value = null); + + /** + * Cross join with the given lists, returning all possible permutations. + * + * @template TCrossJoinKey + * @template TCrossJoinValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$lists + * @return static> + */ + public function crossJoin(...$lists); + + /** + * Dump the collection and end the script. + * + * @param mixed ...$args + * @return never + */ + public function dd(...$args); + + /** + * Dump the collection. + * + * @return $this + */ + public function dump(); + + /** + * Get the items that are not present in the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function diff($items); + + /** + * Get the items that are not present in the given items, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TValue, TValue): int $callback + * @return static + */ + public function diffUsing($items, callable $callback); + + /** + * Get the items whose keys and values are not present in the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function diffAssoc($items); + + /** + * Get the items whose keys and values are not present in the given items, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TKey, TKey): int $callback + * @return static + */ + public function diffAssocUsing($items, callable $callback); + + /** + * Get the items whose keys are not present in the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function diffKeys($items); + + /** + * Get the items whose keys are not present in the given items, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TKey, TKey): int $callback + * @return static + */ + public function diffKeysUsing($items, callable $callback); + + /** + * Retrieve duplicate items. + * + * @param (callable(TValue): bool)|string|null $callback + * @param bool $strict + * @return static + */ + public function duplicates($callback = null, $strict = false); + + /** + * Retrieve duplicate items using strict comparison. + * + * @param (callable(TValue): bool)|string|null $callback + * @return static + */ + public function duplicatesStrict($callback = null); + + /** + * Execute a callback over each item. + * + * @param callable(TValue, TKey): mixed $callback + * @return $this + */ + public function each(callable $callback); + + /** + * Execute a callback over each nested chunk of items. + * + * @param callable $callback + * @return static + */ + public function eachSpread(callable $callback); + + /** + * Determine if all items pass the given truth test. + * + * @param (callable(TValue, TKey): bool)|TValue|string $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function every($key, $operator = null, $value = null); + + /** + * Get all items except for those with the specified keys. + * + * @param \Illuminate\Support\Enumerable|array $keys + * @return static + */ + public function except($keys); + + /** + * Run a filter over each of the items. + * + * @param (callable(TValue): bool)|null $callback + * @return static + */ + public function filter(?callable $callback = null); + + /** + * Apply the callback if the given "value" is (or resolves to) truthy. + * + * @template TWhenReturnType as null + * + * @param bool $value + * @param (callable($this): TWhenReturnType)|null $callback + * @param (callable($this): TWhenReturnType)|null $default + * @return $this|TWhenReturnType + */ + public function when($value, ?callable $callback = null, ?callable $default = null); + + /** + * Apply the callback if the collection is empty. + * + * @template TWhenEmptyReturnType + * + * @param (callable($this): TWhenEmptyReturnType) $callback + * @param (callable($this): TWhenEmptyReturnType)|null $default + * @return $this|TWhenEmptyReturnType + */ + public function whenEmpty(callable $callback, ?callable $default = null); + + /** + * Apply the callback if the collection is not empty. + * + * @template TWhenNotEmptyReturnType + * + * @param callable($this): TWhenNotEmptyReturnType $callback + * @param (callable($this): TWhenNotEmptyReturnType)|null $default + * @return $this|TWhenNotEmptyReturnType + */ + public function whenNotEmpty(callable $callback, ?callable $default = null); + + /** + * Apply the callback if the given "value" is (or resolves to) truthy. + * + * @template TUnlessReturnType + * + * @param bool $value + * @param (callable($this): TUnlessReturnType) $callback + * @param (callable($this): TUnlessReturnType)|null $default + * @return $this|TUnlessReturnType + */ + public function unless($value, callable $callback, ?callable $default = null); + + /** + * Apply the callback unless the collection is empty. + * + * @template TUnlessEmptyReturnType + * + * @param callable($this): TUnlessEmptyReturnType $callback + * @param (callable($this): TUnlessEmptyReturnType)|null $default + * @return $this|TUnlessEmptyReturnType + */ + public function unlessEmpty(callable $callback, ?callable $default = null); + + /** + * Apply the callback unless the collection is not empty. + * + * @template TUnlessNotEmptyReturnType + * + * @param callable($this): TUnlessNotEmptyReturnType $callback + * @param (callable($this): TUnlessNotEmptyReturnType)|null $default + * @return $this|TUnlessNotEmptyReturnType + */ + public function unlessNotEmpty(callable $callback, ?callable $default = null); + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param mixed $operator + * @param mixed $value + * @return static + */ + public function where($key, $operator = null, $value = null); + + /** + * Filter items where the value for the given key is null. + * + * @param string|null $key + * @return static + */ + public function whereNull($key = null); + + /** + * Filter items where the value for the given key is not null. + * + * @param string|null $key + * @return static + */ + public function whereNotNull($key = null); + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param mixed $value + * @return static + */ + public function whereStrict($key, $value); + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values + * @param bool $strict + * @return static + */ + public function whereIn($key, $values, $strict = false); + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values + * @return static + */ + public function whereInStrict($key, $values); + + /** + * Filter items such that the value of the given key is between the given values. + * + * @param string $key + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values + * @return static + */ + public function whereBetween($key, $values); + + /** + * Filter items such that the value of the given key is not between the given values. + * + * @param string $key + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values + * @return static + */ + public function whereNotBetween($key, $values); + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values + * @param bool $strict + * @return static + */ + public function whereNotIn($key, $values, $strict = false); + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values + * @return static + */ + public function whereNotInStrict($key, $values); + + /** + * Filter the items, removing any items that don't match the given type(s). + * + * @template TWhereInstanceOf + * + * @param class-string|array> $type + * @return static + */ + public function whereInstanceOf($type); + + /** + * Get the first item from the enumerable passing the given truth test. + * + * @template TFirstDefault + * + * @param (callable(TValue,TKey): bool)|null $callback + * @param TFirstDefault|(\Closure(): TFirstDefault) $default + * @return TValue|TFirstDefault + */ + public function first(?callable $callback = null, $default = null); + + /** + * Get the first item by the given key value pair. + * + * @param string $key + * @param mixed $operator + * @param mixed $value + * @return TValue|null + */ + public function firstWhere($key, $operator = null, $value = null); + + /** + * Get a flattened array of the items in the collection. + * + * @param int $depth + * @return static + */ + public function flatten($depth = INF); + + /** + * Flip the values with their keys. + * + * @return static + */ + public function flip(); + + /** + * Get an item from the collection by key. + * + * @template TGetDefault + * + * @param TKey $key + * @param TGetDefault|(\Closure(): TGetDefault) $default + * @return TValue|TGetDefault + */ + public function get($key, $default = null); + + /** + * Group an associative array by a field or using a callback. + * + * @param (callable(TValue, TKey): array-key)|array|string $groupBy + * @param bool $preserveKeys + * @return static> + */ + public function groupBy($groupBy, $preserveKeys = false); + + /** + * Key an associative array by a field or using a callback. + * + * @param (callable(TValue, TKey): array-key)|array|string $keyBy + * @return static + */ + public function keyBy($keyBy); + + /** + * Determine if an item exists in the collection by key. + * + * @param TKey|array $key + * @return bool + */ + public function has($key); + + /** + * Determine if any of the keys exist in the collection. + * + * @param mixed $key + * @return bool + */ + public function hasAny($key); + + /** + * Concatenate values of a given key as a string. + * + * @param callable|string $value + * @param string|null $glue + * @return string + */ + public function implode($value, $glue = null); + + /** + * Intersect the collection with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function intersect($items); + + /** + * Intersect the collection with the given items by key. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function intersectByKeys($items); + + /** + * Determine if the collection is empty or not. + * + * @return bool + */ + public function isEmpty(); + + /** + * Determine if the collection is not empty. + * + * @return bool + */ + public function isNotEmpty(); + + /** + * Determine if the collection contains a single item. + * + * @return bool + */ + public function containsOneItem(); + + /** + * Join all items from the collection using a string. The final items can use a separate glue string. + * + * @param string $glue + * @param string $finalGlue + * @return string + */ + public function join($glue, $finalGlue = ''); + + /** + * Get the keys of the collection items. + * + * @return static + */ + public function keys(); + + /** + * Get the last item from the collection. + * + * @template TLastDefault + * + * @param (callable(TValue, TKey): bool)|null $callback + * @param TLastDefault|(\Closure(): TLastDefault) $default + * @return TValue|TLastDefault + */ + public function last(?callable $callback = null, $default = null); + + /** + * Run a map over each of the items. + * + * @template TMapValue + * + * @param callable(TValue, TKey): TMapValue $callback + * @return static + */ + public function map(callable $callback); + + /** + * Run a map over each nested chunk of items. + * + * @param callable $callback + * @return static + */ + public function mapSpread(callable $callback); + + /** + * Run a dictionary map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @template TMapToDictionaryKey of array-key + * @template TMapToDictionaryValue + * + * @param callable(TValue, TKey): array $callback + * @return static> + */ + public function mapToDictionary(callable $callback); + + /** + * Run a grouping map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @template TMapToGroupsKey of array-key + * @template TMapToGroupsValue + * + * @param callable(TValue, TKey): array $callback + * @return static> + */ + public function mapToGroups(callable $callback); + + /** + * Run an associative map over each of the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @template TMapWithKeysKey of array-key + * @template TMapWithKeysValue + * + * @param callable(TValue, TKey): array $callback + * @return static + */ + public function mapWithKeys(callable $callback); + + /** + * Map a collection and flatten the result by a single level. + * + * @template TFlatMapKey of array-key + * @template TFlatMapValue + * + * @param callable(TValue, TKey): (\Illuminate\Support\Collection|array) $callback + * @return static + */ + public function flatMap(callable $callback); + + /** + * Map the values into a new class. + * + * @template TMapIntoValue + * + * @param class-string $class + * @return static + */ + public function mapInto($class); + + /** + * Merge the collection with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function merge($items); + + /** + * Recursively merge the collection with the given items. + * + * @template TMergeRecursiveValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function mergeRecursive($items); + + /** + * Create a collection by using this collection for keys and another for its values. + * + * @template TCombineValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values + * @return static + */ + public function combine($values); + + /** + * Union the collection with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function union($items); + + /** + * Get the min value of a given key. + * + * @param (callable(TValue):mixed)|string|null $callback + * @return mixed + */ + public function min($callback = null); + + /** + * Get the max value of a given key. + * + * @param (callable(TValue):mixed)|string|null $callback + * @return mixed + */ + public function max($callback = null); + + /** + * Create a new collection consisting of every n-th element. + * + * @param int $step + * @param int $offset + * @return static + */ + public function nth($step, $offset = 0); + + /** + * Get the items with the specified keys. + * + * @param \Illuminate\Support\Enumerable|array|string $keys + * @return static + */ + public function only($keys); + + /** + * "Paginate" the collection by slicing it into a smaller collection. + * + * @param int $page + * @param int $perPage + * @return static + */ + public function forPage($page, $perPage); + + /** + * Partition the collection into two arrays using the given callback or key. + * + * @param (callable(TValue, TKey): bool)|TValue|string $key + * @param mixed $operator + * @param mixed $value + * @return static, static> + */ + public function partition($key, $operator = null, $value = null); + + /** + * Push all of the given items onto the collection. + * + * @template TConcatKey of array-key + * @template TConcatValue + * + * @param iterable $source + * @return static + */ + public function concat($source); + + /** + * Get one or a specified number of items randomly from the collection. + * + * @param int|null $number + * @return static|TValue + * + * @throws \InvalidArgumentException + */ + public function random($number = null); + + /** + * Reduce the collection to a single value. + * + * @template TReduceInitial + * @template TReduceReturnType + * + * @param callable(TReduceInitial|TReduceReturnType, TValue, TKey): TReduceReturnType $callback + * @param TReduceInitial $initial + * @return TReduceReturnType + */ + public function reduce(callable $callback, $initial = null); + + /** + * Reduce the collection to multiple aggregate values. + * + * @param callable $callback + * @param mixed ...$initial + * @return array + * + * @throws \UnexpectedValueException + */ + public function reduceSpread(callable $callback, ...$initial); + + /** + * Replace the collection items with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function replace($items); + + /** + * Recursively replace the collection items with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function replaceRecursive($items); + + /** + * Reverse items order. + * + * @return static + */ + public function reverse(); + + /** + * Search the collection for a given value and return the corresponding key if successful. + * + * @param TValue|callable(TValue,TKey): bool $value + * @param bool $strict + * @return TKey|bool + */ + public function search($value, $strict = false); + + /** + * Shuffle the items in the collection. + * + * @param int|null $seed + * @return static + */ + public function shuffle($seed = null); + + /** + * Create chunks representing a "sliding window" view of the items in the collection. + * + * @param int $size + * @param int $step + * @return static + */ + public function sliding($size = 2, $step = 1); + + /** + * Skip the first {$count} items. + * + * @param int $count + * @return static + */ + public function skip($count); + + /** + * Skip items in the collection until the given condition is met. + * + * @param TValue|callable(TValue,TKey): bool $value + * @return static + */ + public function skipUntil($value); + + /** + * Skip items in the collection while the given condition is met. + * + * @param TValue|callable(TValue,TKey): bool $value + * @return static + */ + public function skipWhile($value); + + /** + * Get a slice of items from the enumerable. + * + * @param int $offset + * @param int|null $length + * @return static + */ + public function slice($offset, $length = null); + + /** + * Split a collection into a certain number of groups. + * + * @param int $numberOfGroups + * @return static + */ + public function split($numberOfGroups); + + /** + * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. + * + * @param (callable(TValue, TKey): bool)|string $key + * @param mixed $operator + * @param mixed $value + * @return TValue + * + * @throws \Illuminate\Support\ItemNotFoundException + * @throws \Illuminate\Support\MultipleItemsFoundException + */ + public function sole($key = null, $operator = null, $value = null); + + /** + * Get the first item in the collection but throw an exception if no matching items exist. + * + * @param (callable(TValue, TKey): bool)|string $key + * @param mixed $operator + * @param mixed $value + * @return TValue + * + * @throws \Illuminate\Support\ItemNotFoundException + */ + public function firstOrFail($key = null, $operator = null, $value = null); + + /** + * Chunk the collection into chunks of the given size. + * + * @param int $size + * @return static + */ + public function chunk($size); + + /** + * Chunk the collection into chunks with a callback. + * + * @param callable(TValue, TKey, static): bool $callback + * @return static> + */ + public function chunkWhile(callable $callback); + + /** + * Split a collection into a certain number of groups, and fill the first groups completely. + * + * @param int $numberOfGroups + * @return static + */ + public function splitIn($numberOfGroups); + + /** + * Sort through each item with a callback. + * + * @param (callable(TValue, TValue): int)|null|int $callback + * @return static + */ + public function sort($callback = null); + + /** + * Sort items in descending order. + * + * @param int $options + * @return static + */ + public function sortDesc($options = SORT_REGULAR); + + /** + * Sort the collection using the given callback. + * + * @param array|(callable(TValue, TKey): mixed)|string $callback + * @param int $options + * @param bool $descending + * @return static + */ + public function sortBy($callback, $options = SORT_REGULAR, $descending = false); + + /** + * Sort the collection in descending order using the given callback. + * + * @param array|(callable(TValue, TKey): mixed)|string $callback + * @param int $options + * @return static + */ + public function sortByDesc($callback, $options = SORT_REGULAR); + + /** + * Sort the collection keys. + * + * @param int $options + * @param bool $descending + * @return static + */ + public function sortKeys($options = SORT_REGULAR, $descending = false); + + /** + * Sort the collection keys in descending order. + * + * @param int $options + * @return static + */ + public function sortKeysDesc($options = SORT_REGULAR); + + /** + * Sort the collection keys using a callback. + * + * @param callable(TKey, TKey): int $callback + * @return static + */ + public function sortKeysUsing(callable $callback); + + /** + * Get the sum of the given values. + * + * @param (callable(TValue): mixed)|string|null $callback + * @return mixed + */ + public function sum($callback = null); + + /** + * Take the first or last {$limit} items. + * + * @param int $limit + * @return static + */ + public function take($limit); + + /** + * Take items in the collection until the given condition is met. + * + * @param TValue|callable(TValue,TKey): bool $value + * @return static + */ + public function takeUntil($value); + + /** + * Take items in the collection while the given condition is met. + * + * @param TValue|callable(TValue,TKey): bool $value + * @return static + */ + public function takeWhile($value); + + /** + * Pass the collection to the given callback and then return it. + * + * @param callable(TValue): mixed $callback + * @return $this + */ + public function tap(callable $callback); + + /** + * Pass the enumerable to the given callback and return the result. + * + * @template TPipeReturnType + * + * @param callable($this): TPipeReturnType $callback + * @return TPipeReturnType + */ + public function pipe(callable $callback); + + /** + * Pass the collection into a new class. + * + * @template TPipeIntoValue + * + * @param class-string $class + * @return TPipeIntoValue + */ + public function pipeInto($class); + + /** + * Pass the collection through a series of callable pipes and return the result. + * + * @param array $pipes + * @return mixed + */ + public function pipeThrough($pipes); + + /** + * Get the values of a given key. + * + * @param string|array $value + * @param string|null $key + * @return static + */ + public function pluck($value, $key = null); + + /** + * Create a collection of all elements that do not pass a given truth test. + * + * @param (callable(TValue, TKey): bool)|bool|TValue $callback + * @return static + */ + public function reject($callback = true); + + /** + * Convert a flatten "dot" notation array into an expanded array. + * + * @return static + */ + public function undot(); + + /** + * Return only unique items from the collection array. + * + * @param (callable(TValue, TKey): mixed)|string|null $key + * @param bool $strict + * @return static + */ + public function unique($key = null, $strict = false); + + /** + * Return only unique items from the collection array using strict comparison. + * + * @param (callable(TValue, TKey): mixed)|string|null $key + * @return static + */ + public function uniqueStrict($key = null); + + /** + * Reset the keys on the underlying array. + * + * @return static + */ + public function values(); + + /** + * Pad collection to the specified length with a value. + * + * @template TPadValue + * + * @param int $size + * @param TPadValue $value + * @return static + */ + public function pad($size, $value); + + /** + * Get the values iterator. + * + * @return \Traversable + */ + public function getIterator(): Traversable; + + /** + * Count the number of items in the collection. + * + * @return int + */ + public function count(): int; + + /** + * Count the number of items in the collection by a field or using a callback. + * + * @param (callable(TValue, TKey): array-key)|string|null $countBy + * @return static + */ + public function countBy($countBy = null); + + /** + * Zip the collection together with one or more arrays. + * + * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]); + * => [[1, 4], [2, 5], [3, 6]] + * + * @template TZipValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$items + * @return static> + */ + public function zip($items); + + /** + * Collect the values into a collection. + * + * @return \Illuminate\Support\Collection + */ + public function collect(); + + /** + * Get the collection of items as a plain array. + * + * @return array + */ + public function toArray(); + + /** + * Convert the object into something JSON serializable. + * + * @return mixed + */ + public function jsonSerialize(): mixed; + + /** + * Get the collection of items as JSON. + * + * @param int $options + * @return string + */ + public function toJson($options = 0); + + /** + * Get a CachingIterator instance. + * + * @param int $flags + * @return \CachingIterator + */ + public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING); + + /** + * Convert the collection to its string representation. + * + * @return string + */ + public function __toString(); + + /** + * Indicate that the model's string representation should be escaped when __toString is invoked. + * + * @param bool $escape + * @return $this + */ + public function escapeWhenCastingToString($escape = true); + + /** + * Add a method to the list of proxied methods. + * + * @param string $method + * @return void + */ + public static function proxy($method); + + /** + * Dynamically access collection proxies. + * + * @param string $key + * @return mixed + * + * @throws \Exception + */ + public function __get($key); +} diff --git a/data/web/inc/lib/vendor/illuminate/collections/HigherOrderCollectionProxy.php b/data/web/inc/lib/vendor/illuminate/collections/HigherOrderCollectionProxy.php new file mode 100644 index 0000000000..106356c3ac --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/collections/HigherOrderCollectionProxy.php @@ -0,0 +1,63 @@ +method = $method; + $this->collection = $collection; + } + + /** + * Proxy accessing an attribute onto the collection items. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->collection->{$this->method}(function ($value) use ($key) { + return is_array($value) ? $value[$key] : $value->{$key}; + }); + } + + /** + * Proxy a method call onto the collection items. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->collection->{$this->method}(function ($value) use ($method, $parameters) { + return $value->{$method}(...$parameters); + }); + } +} diff --git a/data/web/inc/lib/vendor/illuminate/collections/ItemNotFoundException.php b/data/web/inc/lib/vendor/illuminate/collections/ItemNotFoundException.php new file mode 100644 index 0000000000..05a51d9547 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/collections/ItemNotFoundException.php @@ -0,0 +1,9 @@ + + */ +class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable +{ + /** + * @use \Illuminate\Support\Traits\EnumeratesValues + */ + use EnumeratesValues, Macroable; + + /** + * The source from which to generate items. + * + * @var (Closure(): \Generator)|static|array + */ + public $source; + + /** + * Create a new lazy collection instance. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable|(Closure(): \Generator)|self|array|null $source + * @return void + */ + public function __construct($source = null) + { + if ($source instanceof Closure || $source instanceof self) { + $this->source = $source; + } elseif (is_null($source)) { + $this->source = static::empty(); + } elseif ($source instanceof Generator) { + throw new InvalidArgumentException( + 'Generators should not be passed directly to LazyCollection. Instead, pass a generator function.' + ); + } else { + $this->source = $this->getArrayableItems($source); + } + } + + /** + * Create a new collection instance if the value isn't one already. + * + * @template TMakeKey of array-key + * @template TMakeValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable|(Closure(): \Generator)|self|array|null $items + * @return static + */ + public static function make($items = []) + { + return new static($items); + } + + /** + * Create a collection with the given range. + * + * @param int $from + * @param int $to + * @return static + */ + public static function range($from, $to) + { + return new static(function () use ($from, $to) { + if ($from <= $to) { + for (; $from <= $to; $from++) { + yield $from; + } + } else { + for (; $from >= $to; $from--) { + yield $from; + } + } + }); + } + + /** + * Get all items in the enumerable. + * + * @return array + */ + public function all() + { + if (is_array($this->source)) { + return $this->source; + } + + return iterator_to_array($this->getIterator()); + } + + /** + * Eager load all items into a new lazy collection backed by an array. + * + * @return static + */ + public function eager() + { + return new static($this->all()); + } + + /** + * Cache values as they're enumerated. + * + * @return static + */ + public function remember() + { + $iterator = $this->getIterator(); + + $iteratorIndex = 0; + + $cache = []; + + return new static(function () use ($iterator, &$iteratorIndex, &$cache) { + for ($index = 0; true; $index++) { + if (array_key_exists($index, $cache)) { + yield $cache[$index][0] => $cache[$index][1]; + + continue; + } + + if ($iteratorIndex < $index) { + $iterator->next(); + + $iteratorIndex++; + } + + if (! $iterator->valid()) { + break; + } + + $cache[$index] = [$iterator->key(), $iterator->current()]; + + yield $cache[$index][0] => $cache[$index][1]; + } + }); + } + + /** + * Get the average value of a given key. + * + * @param (callable(TValue): float|int)|string|null $callback + * @return float|int|null + */ + public function avg($callback = null) + { + return $this->collect()->avg($callback); + } + + /** + * Get the median of a given key. + * + * @param string|array|null $key + * @return float|int|null + */ + public function median($key = null) + { + return $this->collect()->median($key); + } + + /** + * Get the mode of a given key. + * + * @param string|array|null $key + * @return array|null + */ + public function mode($key = null) + { + return $this->collect()->mode($key); + } + + /** + * Collapse the collection of items into a single array. + * + * @return static + */ + public function collapse() + { + return new static(function () { + foreach ($this as $values) { + if (is_array($values) || $values instanceof Enumerable) { + foreach ($values as $value) { + yield $value; + } + } + } + }); + } + + /** + * Determine if an item exists in the enumerable. + * + * @param (callable(TValue, TKey): bool)|TValue|string $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function contains($key, $operator = null, $value = null) + { + if (func_num_args() === 1 && $this->useAsCallable($key)) { + $placeholder = new stdClass; + + /** @var callable $key */ + return $this->first($key, $placeholder) !== $placeholder; + } + + if (func_num_args() === 1) { + $needle = $key; + + foreach ($this as $value) { + if ($value == $needle) { + return true; + } + } + + return false; + } + + return $this->contains($this->operatorForWhere(...func_get_args())); + } + + /** + * Determine if an item exists, using strict comparison. + * + * @param (callable(TValue): bool)|TValue|array-key $key + * @param TValue|null $value + * @return bool + */ + public function containsStrict($key, $value = null) + { + if (func_num_args() === 2) { + return $this->contains(fn ($item) => data_get($item, $key) === $value); + } + + if ($this->useAsCallable($key)) { + return ! is_null($this->first($key)); + } + + foreach ($this as $item) { + if ($item === $key) { + return true; + } + } + + return false; + } + + /** + * Determine if an item is not contained in the enumerable. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function doesntContain($key, $operator = null, $value = null) + { + return ! $this->contains(...func_get_args()); + } + + /** + * Cross join the given iterables, returning all possible permutations. + * + * @template TCrossJoinKey + * @template TCrossJoinValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$arrays + * @return static> + */ + public function crossJoin(...$arrays) + { + return $this->passthru('crossJoin', func_get_args()); + } + + /** + * Count the number of items in the collection by a field or using a callback. + * + * @param (callable(TValue, TKey): array-key)|string|null $countBy + * @return static + */ + public function countBy($countBy = null) + { + $countBy = is_null($countBy) + ? $this->identity() + : $this->valueRetriever($countBy); + + return new static(function () use ($countBy) { + $counts = []; + + foreach ($this as $key => $value) { + $group = $countBy($value, $key); + + if (empty($counts[$group])) { + $counts[$group] = 0; + } + + $counts[$group]++; + } + + yield from $counts; + }); + } + + /** + * Get the items that are not present in the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function diff($items) + { + return $this->passthru('diff', func_get_args()); + } + + /** + * Get the items that are not present in the given items, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TValue, TValue): int $callback + * @return static + */ + public function diffUsing($items, callable $callback) + { + return $this->passthru('diffUsing', func_get_args()); + } + + /** + * Get the items whose keys and values are not present in the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function diffAssoc($items) + { + return $this->passthru('diffAssoc', func_get_args()); + } + + /** + * Get the items whose keys and values are not present in the given items, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TKey, TKey): int $callback + * @return static + */ + public function diffAssocUsing($items, callable $callback) + { + return $this->passthru('diffAssocUsing', func_get_args()); + } + + /** + * Get the items whose keys are not present in the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function diffKeys($items) + { + return $this->passthru('diffKeys', func_get_args()); + } + + /** + * Get the items whose keys are not present in the given items, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TKey, TKey): int $callback + * @return static + */ + public function diffKeysUsing($items, callable $callback) + { + return $this->passthru('diffKeysUsing', func_get_args()); + } + + /** + * Retrieve duplicate items. + * + * @param (callable(TValue): bool)|string|null $callback + * @param bool $strict + * @return static + */ + public function duplicates($callback = null, $strict = false) + { + return $this->passthru('duplicates', func_get_args()); + } + + /** + * Retrieve duplicate items using strict comparison. + * + * @param (callable(TValue): bool)|string|null $callback + * @return static + */ + public function duplicatesStrict($callback = null) + { + return $this->passthru('duplicatesStrict', func_get_args()); + } + + /** + * Get all items except for those with the specified keys. + * + * @param \Illuminate\Support\Enumerable|array $keys + * @return static + */ + public function except($keys) + { + return $this->passthru('except', func_get_args()); + } + + /** + * Run a filter over each of the items. + * + * @param (callable(TValue, TKey): bool)|null $callback + * @return static + */ + public function filter(?callable $callback = null) + { + if (is_null($callback)) { + $callback = fn ($value) => (bool) $value; + } + + return new static(function () use ($callback) { + foreach ($this as $key => $value) { + if ($callback($value, $key)) { + yield $key => $value; + } + } + }); + } + + /** + * Get the first item from the enumerable passing the given truth test. + * + * @template TFirstDefault + * + * @param (callable(TValue): bool)|null $callback + * @param TFirstDefault|(\Closure(): TFirstDefault) $default + * @return TValue|TFirstDefault + */ + public function first(?callable $callback = null, $default = null) + { + $iterator = $this->getIterator(); + + if (is_null($callback)) { + if (! $iterator->valid()) { + return value($default); + } + + return $iterator->current(); + } + + foreach ($iterator as $key => $value) { + if ($callback($value, $key)) { + return $value; + } + } + + return value($default); + } + + /** + * Get a flattened list of the items in the collection. + * + * @param int $depth + * @return static + */ + public function flatten($depth = INF) + { + $instance = new static(function () use ($depth) { + foreach ($this as $item) { + if (! is_array($item) && ! $item instanceof Enumerable) { + yield $item; + } elseif ($depth === 1) { + yield from $item; + } else { + yield from (new static($item))->flatten($depth - 1); + } + } + }); + + return $instance->values(); + } + + /** + * Flip the items in the collection. + * + * @return static + */ + public function flip() + { + return new static(function () { + foreach ($this as $key => $value) { + yield $value => $key; + } + }); + } + + /** + * Get an item by key. + * + * @template TGetDefault + * + * @param TKey|null $key + * @param TGetDefault|(\Closure(): TGetDefault) $default + * @return TValue|TGetDefault + */ + public function get($key, $default = null) + { + if (is_null($key)) { + return; + } + + foreach ($this as $outerKey => $outerValue) { + if ($outerKey == $key) { + return $outerValue; + } + } + + return value($default); + } + + /** + * Group an associative array by a field or using a callback. + * + * @param (callable(TValue, TKey): array-key)|array|string $groupBy + * @param bool $preserveKeys + * @return static> + */ + public function groupBy($groupBy, $preserveKeys = false) + { + return $this->passthru('groupBy', func_get_args()); + } + + /** + * Key an associative array by a field or using a callback. + * + * @param (callable(TValue, TKey): array-key)|array|string $keyBy + * @return static + */ + public function keyBy($keyBy) + { + return new static(function () use ($keyBy) { + $keyBy = $this->valueRetriever($keyBy); + + foreach ($this as $key => $item) { + $resolvedKey = $keyBy($item, $key); + + if (is_object($resolvedKey)) { + $resolvedKey = (string) $resolvedKey; + } + + yield $resolvedKey => $item; + } + }); + } + + /** + * Determine if an item exists in the collection by key. + * + * @param mixed $key + * @return bool + */ + public function has($key) + { + $keys = array_flip(is_array($key) ? $key : func_get_args()); + $count = count($keys); + + foreach ($this as $key => $value) { + if (array_key_exists($key, $keys) && --$count == 0) { + return true; + } + } + + return false; + } + + /** + * Determine if any of the keys exist in the collection. + * + * @param mixed $key + * @return bool + */ + public function hasAny($key) + { + $keys = array_flip(is_array($key) ? $key : func_get_args()); + + foreach ($this as $key => $value) { + if (array_key_exists($key, $keys)) { + return true; + } + } + + return false; + } + + /** + * Concatenate values of a given key as a string. + * + * @param callable|string $value + * @param string|null $glue + * @return string + */ + public function implode($value, $glue = null) + { + return $this->collect()->implode(...func_get_args()); + } + + /** + * Intersect the collection with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function intersect($items) + { + return $this->passthru('intersect', func_get_args()); + } + + /** + * Intersect the collection with the given items, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TValue, TValue): int $callback + * @return static + */ + public function intersectUsing() + { + return $this->passthru('intersectUsing', func_get_args()); + } + + /** + * Intersect the collection with the given items with additional index check. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function intersectAssoc($items) + { + return $this->passthru('intersectAssoc', func_get_args()); + } + + /** + * Intersect the collection with the given items with additional index check, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TValue, TValue): int $callback + * @return static + */ + public function intersectAssocUsing($items, callable $callback) + { + return $this->passthru('intersectAssocUsing', func_get_args()); + } + + /** + * Intersect the collection with the given items by key. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function intersectByKeys($items) + { + return $this->passthru('intersectByKeys', func_get_args()); + } + + /** + * Determine if the items are empty or not. + * + * @return bool + */ + public function isEmpty() + { + return ! $this->getIterator()->valid(); + } + + /** + * Determine if the collection contains a single item. + * + * @return bool + */ + public function containsOneItem() + { + return $this->take(2)->count() === 1; + } + + /** + * Join all items from the collection using a string. The final items can use a separate glue string. + * + * @param string $glue + * @param string $finalGlue + * @return string + */ + public function join($glue, $finalGlue = '') + { + return $this->collect()->join(...func_get_args()); + } + + /** + * Get the keys of the collection items. + * + * @return static + */ + public function keys() + { + return new static(function () { + foreach ($this as $key => $value) { + yield $key; + } + }); + } + + /** + * Get the last item from the collection. + * + * @template TLastDefault + * + * @param (callable(TValue, TKey): bool)|null $callback + * @param TLastDefault|(\Closure(): TLastDefault) $default + * @return TValue|TLastDefault + */ + public function last(?callable $callback = null, $default = null) + { + $needle = $placeholder = new stdClass; + + foreach ($this as $key => $value) { + if (is_null($callback) || $callback($value, $key)) { + $needle = $value; + } + } + + return $needle === $placeholder ? value($default) : $needle; + } + + /** + * Get the values of a given key. + * + * @param string|array $value + * @param string|null $key + * @return static + */ + public function pluck($value, $key = null) + { + return new static(function () use ($value, $key) { + [$value, $key] = $this->explodePluckParameters($value, $key); + + foreach ($this as $item) { + $itemValue = data_get($item, $value); + + if (is_null($key)) { + yield $itemValue; + } else { + $itemKey = data_get($item, $key); + + if (is_object($itemKey) && method_exists($itemKey, '__toString')) { + $itemKey = (string) $itemKey; + } + + yield $itemKey => $itemValue; + } + } + }); + } + + /** + * Run a map over each of the items. + * + * @template TMapValue + * + * @param callable(TValue, TKey): TMapValue $callback + * @return static + */ + public function map(callable $callback) + { + return new static(function () use ($callback) { + foreach ($this as $key => $value) { + yield $key => $callback($value, $key); + } + }); + } + + /** + * Run a dictionary map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @template TMapToDictionaryKey of array-key + * @template TMapToDictionaryValue + * + * @param callable(TValue, TKey): array $callback + * @return static> + */ + public function mapToDictionary(callable $callback) + { + return $this->passthru('mapToDictionary', func_get_args()); + } + + /** + * Run an associative map over each of the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @template TMapWithKeysKey of array-key + * @template TMapWithKeysValue + * + * @param callable(TValue, TKey): array $callback + * @return static + */ + public function mapWithKeys(callable $callback) + { + return new static(function () use ($callback) { + foreach ($this as $key => $value) { + yield from $callback($value, $key); + } + }); + } + + /** + * Merge the collection with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function merge($items) + { + return $this->passthru('merge', func_get_args()); + } + + /** + * Recursively merge the collection with the given items. + * + * @template TMergeRecursiveValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function mergeRecursive($items) + { + return $this->passthru('mergeRecursive', func_get_args()); + } + + /** + * Create a collection by using this collection for keys and another for its values. + * + * @template TCombineValue + * + * @param \IteratorAggregate|array|(callable(): \Generator) $values + * @return static + */ + public function combine($values) + { + return new static(function () use ($values) { + $values = $this->makeIterator($values); + + $errorMessage = 'Both parameters should have an equal number of elements'; + + foreach ($this as $key) { + if (! $values->valid()) { + trigger_error($errorMessage, E_USER_WARNING); + + break; + } + + yield $key => $values->current(); + + $values->next(); + } + + if ($values->valid()) { + trigger_error($errorMessage, E_USER_WARNING); + } + }); + } + + /** + * Union the collection with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function union($items) + { + return $this->passthru('union', func_get_args()); + } + + /** + * Create a new collection consisting of every n-th element. + * + * @param int $step + * @param int $offset + * @return static + */ + public function nth($step, $offset = 0) + { + return new static(function () use ($step, $offset) { + $position = 0; + + foreach ($this->slice($offset) as $item) { + if ($position % $step === 0) { + yield $item; + } + + $position++; + } + }); + } + + /** + * Get the items with the specified keys. + * + * @param \Illuminate\Support\Enumerable|array|string $keys + * @return static + */ + public function only($keys) + { + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } elseif (! is_null($keys)) { + $keys = is_array($keys) ? $keys : func_get_args(); + } + + return new static(function () use ($keys) { + if (is_null($keys)) { + yield from $this; + } else { + $keys = array_flip($keys); + + foreach ($this as $key => $value) { + if (array_key_exists($key, $keys)) { + yield $key => $value; + + unset($keys[$key]); + + if (empty($keys)) { + break; + } + } + } + } + }); + } + + /** + * Select specific values from the items within the collection. + * + * @param \Illuminate\Support\Enumerable|array|string $keys + * @return static + */ + public function select($keys) + { + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } elseif (! is_null($keys)) { + $keys = is_array($keys) ? $keys : func_get_args(); + } + + return new static(function () use ($keys) { + if (is_null($keys)) { + yield from $this; + } else { + foreach ($this as $item) { + $result = []; + + foreach ($keys as $key) { + if (Arr::accessible($item) && Arr::exists($item, $key)) { + $result[$key] = $item[$key]; + } elseif (is_object($item) && isset($item->{$key})) { + $result[$key] = $item->{$key}; + } + } + + yield $result; + } + } + }); + } + + /** + * Push all of the given items onto the collection. + * + * @template TConcatKey of array-key + * @template TConcatValue + * + * @param iterable $source + * @return static + */ + public function concat($source) + { + return (new static(function () use ($source) { + yield from $this; + yield from $source; + }))->values(); + } + + /** + * Get one or a specified number of items randomly from the collection. + * + * @param int|null $number + * @return static|TValue + * + * @throws \InvalidArgumentException + */ + public function random($number = null) + { + $result = $this->collect()->random(...func_get_args()); + + return is_null($number) ? $result : new static($result); + } + + /** + * Replace the collection items with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function replace($items) + { + return new static(function () use ($items) { + $items = $this->getArrayableItems($items); + + foreach ($this as $key => $value) { + if (array_key_exists($key, $items)) { + yield $key => $items[$key]; + + unset($items[$key]); + } else { + yield $key => $value; + } + } + + foreach ($items as $key => $value) { + yield $key => $value; + } + }); + } + + /** + * Recursively replace the collection items with the given items. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ + public function replaceRecursive($items) + { + return $this->passthru('replaceRecursive', func_get_args()); + } + + /** + * Reverse items order. + * + * @return static + */ + public function reverse() + { + return $this->passthru('reverse', func_get_args()); + } + + /** + * Search the collection for a given value and return the corresponding key if successful. + * + * @param TValue|(callable(TValue,TKey): bool) $value + * @param bool $strict + * @return TKey|false + */ + public function search($value, $strict = false) + { + /** @var (callable(TValue,TKey): bool) $predicate */ + $predicate = $this->useAsCallable($value) + ? $value + : function ($item) use ($value, $strict) { + return $strict ? $item === $value : $item == $value; + }; + + foreach ($this as $key => $item) { + if ($predicate($item, $key)) { + return $key; + } + } + + return false; + } + + /** + * Shuffle the items in the collection. + * + * @param int|null $seed + * @return static + */ + public function shuffle($seed = null) + { + return $this->passthru('shuffle', func_get_args()); + } + + /** + * Create chunks representing a "sliding window" view of the items in the collection. + * + * @param int $size + * @param int $step + * @return static + */ + public function sliding($size = 2, $step = 1) + { + return new static(function () use ($size, $step) { + $iterator = $this->getIterator(); + + $chunk = []; + + while ($iterator->valid()) { + $chunk[$iterator->key()] = $iterator->current(); + + if (count($chunk) == $size) { + yield (new static($chunk))->tap(function () use (&$chunk, $step) { + $chunk = array_slice($chunk, $step, null, true); + }); + + // If the $step between chunks is bigger than each chunk's $size + // we will skip the extra items (which should never be in any + // chunk) before we continue to the next chunk in the loop. + if ($step > $size) { + $skip = $step - $size; + + for ($i = 0; $i < $skip && $iterator->valid(); $i++) { + $iterator->next(); + } + } + } + + $iterator->next(); + } + }); + } + + /** + * Skip the first {$count} items. + * + * @param int $count + * @return static + */ + public function skip($count) + { + return new static(function () use ($count) { + $iterator = $this->getIterator(); + + while ($iterator->valid() && $count--) { + $iterator->next(); + } + + while ($iterator->valid()) { + yield $iterator->key() => $iterator->current(); + + $iterator->next(); + } + }); + } + + /** + * Skip items in the collection until the given condition is met. + * + * @param TValue|callable(TValue,TKey): bool $value + * @return static + */ + public function skipUntil($value) + { + $callback = $this->useAsCallable($value) ? $value : $this->equality($value); + + return $this->skipWhile($this->negate($callback)); + } + + /** + * Skip items in the collection while the given condition is met. + * + * @param TValue|callable(TValue,TKey): bool $value + * @return static + */ + public function skipWhile($value) + { + $callback = $this->useAsCallable($value) ? $value : $this->equality($value); + + return new static(function () use ($callback) { + $iterator = $this->getIterator(); + + while ($iterator->valid() && $callback($iterator->current(), $iterator->key())) { + $iterator->next(); + } + + while ($iterator->valid()) { + yield $iterator->key() => $iterator->current(); + + $iterator->next(); + } + }); + } + + /** + * Get a slice of items from the enumerable. + * + * @param int $offset + * @param int|null $length + * @return static + */ + public function slice($offset, $length = null) + { + if ($offset < 0 || $length < 0) { + return $this->passthru('slice', func_get_args()); + } + + $instance = $this->skip($offset); + + return is_null($length) ? $instance : $instance->take($length); + } + + /** + * Split a collection into a certain number of groups. + * + * @param int $numberOfGroups + * @return static + */ + public function split($numberOfGroups) + { + return $this->passthru('split', func_get_args()); + } + + /** + * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. + * + * @param (callable(TValue, TKey): bool)|string $key + * @param mixed $operator + * @param mixed $value + * @return TValue + * + * @throws \Illuminate\Support\ItemNotFoundException + * @throws \Illuminate\Support\MultipleItemsFoundException + */ + public function sole($key = null, $operator = null, $value = null) + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + return $this + ->unless($filter == null) + ->filter($filter) + ->take(2) + ->collect() + ->sole(); + } + + /** + * Get the first item in the collection but throw an exception if no matching items exist. + * + * @param (callable(TValue, TKey): bool)|string $key + * @param mixed $operator + * @param mixed $value + * @return TValue + * + * @throws \Illuminate\Support\ItemNotFoundException + */ + public function firstOrFail($key = null, $operator = null, $value = null) + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + return $this + ->unless($filter == null) + ->filter($filter) + ->take(1) + ->collect() + ->firstOrFail(); + } + + /** + * Chunk the collection into chunks of the given size. + * + * @param int $size + * @return static + */ + public function chunk($size) + { + if ($size <= 0) { + return static::empty(); + } + + return new static(function () use ($size) { + $iterator = $this->getIterator(); + + while ($iterator->valid()) { + $chunk = []; + + while (true) { + $chunk[$iterator->key()] = $iterator->current(); + + if (count($chunk) < $size) { + $iterator->next(); + + if (! $iterator->valid()) { + break; + } + } else { + break; + } + } + + yield new static($chunk); + + $iterator->next(); + } + }); + } + + /** + * Split a collection into a certain number of groups, and fill the first groups completely. + * + * @param int $numberOfGroups + * @return static + */ + public function splitIn($numberOfGroups) + { + return $this->chunk(ceil($this->count() / $numberOfGroups)); + } + + /** + * Chunk the collection into chunks with a callback. + * + * @param callable(TValue, TKey, Collection): bool $callback + * @return static> + */ + public function chunkWhile(callable $callback) + { + return new static(function () use ($callback) { + $iterator = $this->getIterator(); + + $chunk = new Collection; + + if ($iterator->valid()) { + $chunk[$iterator->key()] = $iterator->current(); + + $iterator->next(); + } + + while ($iterator->valid()) { + if (! $callback($iterator->current(), $iterator->key(), $chunk)) { + yield new static($chunk); + + $chunk = new Collection; + } + + $chunk[$iterator->key()] = $iterator->current(); + + $iterator->next(); + } + + if ($chunk->isNotEmpty()) { + yield new static($chunk); + } + }); + } + + /** + * Sort through each item with a callback. + * + * @param (callable(TValue, TValue): int)|null|int $callback + * @return static + */ + public function sort($callback = null) + { + return $this->passthru('sort', func_get_args()); + } + + /** + * Sort items in descending order. + * + * @param int $options + * @return static + */ + public function sortDesc($options = SORT_REGULAR) + { + return $this->passthru('sortDesc', func_get_args()); + } + + /** + * Sort the collection using the given callback. + * + * @param array|(callable(TValue, TKey): mixed)|string $callback + * @param int $options + * @param bool $descending + * @return static + */ + public function sortBy($callback, $options = SORT_REGULAR, $descending = false) + { + return $this->passthru('sortBy', func_get_args()); + } + + /** + * Sort the collection in descending order using the given callback. + * + * @param array|(callable(TValue, TKey): mixed)|string $callback + * @param int $options + * @return static + */ + public function sortByDesc($callback, $options = SORT_REGULAR) + { + return $this->passthru('sortByDesc', func_get_args()); + } + + /** + * Sort the collection keys. + * + * @param int $options + * @param bool $descending + * @return static + */ + public function sortKeys($options = SORT_REGULAR, $descending = false) + { + return $this->passthru('sortKeys', func_get_args()); + } + + /** + * Sort the collection keys in descending order. + * + * @param int $options + * @return static + */ + public function sortKeysDesc($options = SORT_REGULAR) + { + return $this->passthru('sortKeysDesc', func_get_args()); + } + + /** + * Sort the collection keys using a callback. + * + * @param callable(TKey, TKey): int $callback + * @return static + */ + public function sortKeysUsing(callable $callback) + { + return $this->passthru('sortKeysUsing', func_get_args()); + } + + /** + * Take the first or last {$limit} items. + * + * @param int $limit + * @return static + */ + public function take($limit) + { + if ($limit < 0) { + return new static(function () use ($limit) { + $limit = abs($limit); + $ringBuffer = []; + $position = 0; + + foreach ($this as $key => $value) { + $ringBuffer[$position] = [$key, $value]; + $position = ($position + 1) % $limit; + } + + for ($i = 0, $end = min($limit, count($ringBuffer)); $i < $end; $i++) { + $pointer = ($position + $i) % $limit; + yield $ringBuffer[$pointer][0] => $ringBuffer[$pointer][1]; + } + }); + } + + return new static(function () use ($limit) { + $iterator = $this->getIterator(); + + while ($limit--) { + if (! $iterator->valid()) { + break; + } + + yield $iterator->key() => $iterator->current(); + + if ($limit) { + $iterator->next(); + } + } + }); + } + + /** + * Take items in the collection until the given condition is met. + * + * @param TValue|callable(TValue,TKey): bool $value + * @return static + */ + public function takeUntil($value) + { + /** @var callable(TValue, TKey): bool $callback */ + $callback = $this->useAsCallable($value) ? $value : $this->equality($value); + + return new static(function () use ($callback) { + foreach ($this as $key => $item) { + if ($callback($item, $key)) { + break; + } + + yield $key => $item; + } + }); + } + + /** + * Take items in the collection until a given point in time. + * + * @param \DateTimeInterface $timeout + * @return static + */ + public function takeUntilTimeout(DateTimeInterface $timeout) + { + $timeout = $timeout->getTimestamp(); + + return new static(function () use ($timeout) { + if ($this->now() >= $timeout) { + return; + } + + foreach ($this as $key => $value) { + yield $key => $value; + + if ($this->now() >= $timeout) { + break; + } + } + }); + } + + /** + * Take items in the collection while the given condition is met. + * + * @param TValue|callable(TValue,TKey): bool $value + * @return static + */ + public function takeWhile($value) + { + /** @var callable(TValue, TKey): bool $callback */ + $callback = $this->useAsCallable($value) ? $value : $this->equality($value); + + return $this->takeUntil(fn ($item, $key) => ! $callback($item, $key)); + } + + /** + * Pass each item in the collection to the given callback, lazily. + * + * @param callable(TValue, TKey): mixed $callback + * @return static + */ + public function tapEach(callable $callback) + { + return new static(function () use ($callback) { + foreach ($this as $key => $value) { + $callback($value, $key); + + yield $key => $value; + } + }); + } + + /** + * Flatten a multi-dimensional associative array with dots. + * + * @return static + */ + public function dot() + { + return $this->passthru('dot', []); + } + + /** + * Convert a flatten "dot" notation array into an expanded array. + * + * @return static + */ + public function undot() + { + return $this->passthru('undot', []); + } + + /** + * Return only unique items from the collection array. + * + * @param (callable(TValue, TKey): mixed)|string|null $key + * @param bool $strict + * @return static + */ + public function unique($key = null, $strict = false) + { + $callback = $this->valueRetriever($key); + + return new static(function () use ($callback, $strict) { + $exists = []; + + foreach ($this as $key => $item) { + if (! in_array($id = $callback($item, $key), $exists, $strict)) { + yield $key => $item; + + $exists[] = $id; + } + } + }); + } + + /** + * Reset the keys on the underlying array. + * + * @return static + */ + public function values() + { + return new static(function () { + foreach ($this as $item) { + yield $item; + } + }); + } + + /** + * Zip the collection together with one or more arrays. + * + * e.g. new LazyCollection([1, 2, 3])->zip([4, 5, 6]); + * => [[1, 4], [2, 5], [3, 6]] + * + * @template TZipValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$items + * @return static> + */ + public function zip($items) + { + $iterables = func_get_args(); + + return new static(function () use ($iterables) { + $iterators = Collection::make($iterables)->map(function ($iterable) { + return $this->makeIterator($iterable); + })->prepend($this->getIterator()); + + while ($iterators->contains->valid()) { + yield new static($iterators->map->current()); + + $iterators->each->next(); + } + }); + } + + /** + * Pad collection to the specified length with a value. + * + * @template TPadValue + * + * @param int $size + * @param TPadValue $value + * @return static + */ + public function pad($size, $value) + { + if ($size < 0) { + return $this->passthru('pad', func_get_args()); + } + + return new static(function () use ($size, $value) { + $yielded = 0; + + foreach ($this as $index => $item) { + yield $index => $item; + + $yielded++; + } + + while ($yielded++ < $size) { + yield $value; + } + }); + } + + /** + * Get the values iterator. + * + * @return \Traversable + */ + public function getIterator(): Traversable + { + return $this->makeIterator($this->source); + } + + /** + * Count the number of items in the collection. + * + * @return int + */ + public function count(): int + { + if (is_array($this->source)) { + return count($this->source); + } + + return iterator_count($this->getIterator()); + } + + /** + * Make an iterator from the given source. + * + * @template TIteratorKey of array-key + * @template TIteratorValue + * + * @param \IteratorAggregate|array|(callable(): \Generator) $source + * @return \Traversable + */ + protected function makeIterator($source) + { + if ($source instanceof IteratorAggregate) { + return $source->getIterator(); + } + + if (is_array($source)) { + return new ArrayIterator($source); + } + + if (is_callable($source)) { + $maybeTraversable = $source(); + + return $maybeTraversable instanceof Traversable + ? $maybeTraversable + : new ArrayIterator(Arr::wrap($maybeTraversable)); + } + + return new ArrayIterator((array) $source); + } + + /** + * Explode the "value" and "key" arguments passed to "pluck". + * + * @param string|string[] $value + * @param string|string[]|null $key + * @return array{string[],string[]|null} + */ + protected function explodePluckParameters($value, $key) + { + $value = is_string($value) ? explode('.', $value) : $value; + + $key = is_null($key) || is_array($key) ? $key : explode('.', $key); + + return [$value, $key]; + } + + /** + * Pass this lazy collection through a method on the collection class. + * + * @param string $method + * @param array $params + * @return static + */ + protected function passthru($method, array $params) + { + return new static(function () use ($method, $params) { + yield from $this->collect()->$method(...$params); + }); + } + + /** + * Get the current time. + * + * @return int + */ + protected function now() + { + return class_exists(Carbon::class) + ? Carbon::now()->timestamp + : time(); + } +} diff --git a/data/web/inc/lib/vendor/illuminate/collections/MultipleItemsFoundException.php b/data/web/inc/lib/vendor/illuminate/collections/MultipleItemsFoundException.php new file mode 100644 index 0000000000..d90d835b41 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/collections/MultipleItemsFoundException.php @@ -0,0 +1,40 @@ +count = $count; + + parent::__construct("$count items were found.", $code, $previous); + } + + /** + * Get the number of items found. + * + * @return int + */ + public function getCount() + { + return $this->count; + } +} diff --git a/data/web/inc/lib/vendor/illuminate/collections/Traits/EnumeratesValues.php b/data/web/inc/lib/vendor/illuminate/collections/Traits/EnumeratesValues.php new file mode 100644 index 0000000000..9718f5bc53 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/collections/Traits/EnumeratesValues.php @@ -0,0 +1,1152 @@ + + */ + protected static $proxies = [ + 'average', + 'avg', + 'contains', + 'doesntContain', + 'each', + 'every', + 'filter', + 'first', + 'flatMap', + 'groupBy', + 'keyBy', + 'map', + 'max', + 'min', + 'partition', + 'percentage', + 'reject', + 'skipUntil', + 'skipWhile', + 'some', + 'sortBy', + 'sortByDesc', + 'sum', + 'takeUntil', + 'takeWhile', + 'unique', + 'unless', + 'until', + 'when', + ]; + + /** + * Create a new collection instance if the value isn't one already. + * + * @template TMakeKey of array-key + * @template TMakeValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable|null $items + * @return static + */ + public static function make($items = []) + { + return new static($items); + } + + /** + * Wrap the given value in a collection if applicable. + * + * @template TWrapValue + * + * @param iterable|TWrapValue $value + * @return static + */ + public static function wrap($value) + { + return $value instanceof Enumerable + ? new static($value) + : new static(Arr::wrap($value)); + } + + /** + * Get the underlying items from the given collection if applicable. + * + * @template TUnwrapKey of array-key + * @template TUnwrapValue + * + * @param array|static $value + * @return array + */ + public static function unwrap($value) + { + return $value instanceof Enumerable ? $value->all() : $value; + } + + /** + * Create a new instance with no items. + * + * @return static + */ + public static function empty() + { + return new static([]); + } + + /** + * Create a new collection by invoking the callback a given amount of times. + * + * @template TTimesValue + * + * @param int $number + * @param (callable(int): TTimesValue)|null $callback + * @return static + */ + public static function times($number, ?callable $callback = null) + { + if ($number < 1) { + return new static; + } + + return static::range(1, $number) + ->unless($callback == null) + ->map($callback); + } + + /** + * Alias for the "avg" method. + * + * @param (callable(TValue): float|int)|string|null $callback + * @return float|int|null + */ + public function average($callback = null) + { + return $this->avg($callback); + } + + /** + * Alias for the "contains" method. + * + * @param (callable(TValue, TKey): bool)|TValue|string $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function some($key, $operator = null, $value = null) + { + return $this->contains(...func_get_args()); + } + + /** + * Dump the items and end the script. + * + * @param mixed ...$args + * @return never + */ + public function dd(...$args) + { + $this->dump(...$args); + + exit(1); + } + + /** + * Dump the items. + * + * @return $this + */ + public function dump() + { + (new Collection(func_get_args())) + ->push($this->all()) + ->each(function ($item) { + VarDumper::dump($item); + }); + + return $this; + } + + /** + * Execute a callback over each item. + * + * @param callable(TValue, TKey): mixed $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this as $key => $item) { + if ($callback($item, $key) === false) { + break; + } + } + + return $this; + } + + /** + * Execute a callback over each nested chunk of items. + * + * @param callable(...mixed): mixed $callback + * @return static + */ + public function eachSpread(callable $callback) + { + return $this->each(function ($chunk, $key) use ($callback) { + $chunk[] = $key; + + return $callback(...$chunk); + }); + } + + /** + * Determine if all items pass the given truth test. + * + * @param (callable(TValue, TKey): bool)|TValue|string $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function every($key, $operator = null, $value = null) + { + if (func_num_args() === 1) { + $callback = $this->valueRetriever($key); + + foreach ($this as $k => $v) { + if (! $callback($v, $k)) { + return false; + } + } + + return true; + } + + return $this->every($this->operatorForWhere(...func_get_args())); + } + + /** + * Get the first item by the given key value pair. + * + * @param callable|string $key + * @param mixed $operator + * @param mixed $value + * @return TValue|null + */ + public function firstWhere($key, $operator = null, $value = null) + { + return $this->first($this->operatorForWhere(...func_get_args())); + } + + /** + * Get a single key's value from the first matching item in the collection. + * + * @template TValueDefault + * + * @param string $key + * @param TValueDefault|(\Closure(): TValueDefault) $default + * @return TValue|TValueDefault + */ + public function value($key, $default = null) + { + if ($value = $this->firstWhere($key)) { + return data_get($value, $key, $default); + } + + return value($default); + } + + /** + * Ensure that every item in the collection is of the expected type. + * + * @template TEnsureOfType + * + * @param class-string|array> $type + * @return static + * + * @throws \UnexpectedValueException + */ + public function ensure($type) + { + $allowedTypes = is_array($type) ? $type : [$type]; + + return $this->each(function ($item) use ($allowedTypes) { + $itemType = get_debug_type($item); + + foreach ($allowedTypes as $allowedType) { + if ($itemType === $allowedType || $item instanceof $allowedType) { + return true; + } + } + + throw new UnexpectedValueException( + sprintf("Collection should only include [%s] items, but '%s' found.", implode(', ', $allowedTypes), $itemType) + ); + }); + } + + /** + * Determine if the collection is not empty. + * + * @return bool + */ + public function isNotEmpty() + { + return ! $this->isEmpty(); + } + + /** + * Run a map over each nested chunk of items. + * + * @template TMapSpreadValue + * + * @param callable(mixed...): TMapSpreadValue $callback + * @return static + */ + public function mapSpread(callable $callback) + { + return $this->map(function ($chunk, $key) use ($callback) { + $chunk[] = $key; + + return $callback(...$chunk); + }); + } + + /** + * Run a grouping map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @template TMapToGroupsKey of array-key + * @template TMapToGroupsValue + * + * @param callable(TValue, TKey): array $callback + * @return static> + */ + public function mapToGroups(callable $callback) + { + $groups = $this->mapToDictionary($callback); + + return $groups->map([$this, 'make']); + } + + /** + * Map a collection and flatten the result by a single level. + * + * @template TFlatMapKey of array-key + * @template TFlatMapValue + * + * @param callable(TValue, TKey): (\Illuminate\Support\Collection|array) $callback + * @return static + */ + public function flatMap(callable $callback) + { + return $this->map($callback)->collapse(); + } + + /** + * Map the values into a new class. + * + * @template TMapIntoValue + * + * @param class-string $class + * @return static + */ + public function mapInto($class) + { + return $this->map(fn ($value, $key) => new $class($value, $key)); + } + + /** + * Get the min value of a given key. + * + * @param (callable(TValue):mixed)|string|null $callback + * @return mixed + */ + public function min($callback = null) + { + $callback = $this->valueRetriever($callback); + + return $this->map(fn ($value) => $callback($value)) + ->filter(fn ($value) => ! is_null($value)) + ->reduce(fn ($result, $value) => is_null($result) || $value < $result ? $value : $result); + } + + /** + * Get the max value of a given key. + * + * @param (callable(TValue):mixed)|string|null $callback + * @return mixed + */ + public function max($callback = null) + { + $callback = $this->valueRetriever($callback); + + return $this->filter(fn ($value) => ! is_null($value))->reduce(function ($result, $item) use ($callback) { + $value = $callback($item); + + return is_null($result) || $value > $result ? $value : $result; + }); + } + + /** + * "Paginate" the collection by slicing it into a smaller collection. + * + * @param int $page + * @param int $perPage + * @return static + */ + public function forPage($page, $perPage) + { + $offset = max(0, ($page - 1) * $perPage); + + return $this->slice($offset, $perPage); + } + + /** + * Partition the collection into two arrays using the given callback or key. + * + * @param (callable(TValue, TKey): bool)|TValue|string $key + * @param TValue|string|null $operator + * @param TValue|null $value + * @return static, static> + */ + public function partition($key, $operator = null, $value = null) + { + $passed = []; + $failed = []; + + $callback = func_num_args() === 1 + ? $this->valueRetriever($key) + : $this->operatorForWhere(...func_get_args()); + + foreach ($this as $key => $item) { + if ($callback($item, $key)) { + $passed[$key] = $item; + } else { + $failed[$key] = $item; + } + } + + return new static([new static($passed), new static($failed)]); + } + + /** + * Calculate the percentage of items that pass a given truth test. + * + * @param (callable(TValue, TKey): bool) $callback + * @param int $precision + * @return float|null + */ + public function percentage(callable $callback, int $precision = 2) + { + if ($this->isEmpty()) { + return null; + } + + return round( + $this->filter($callback)->count() / $this->count() * 100, + $precision + ); + } + + /** + * Get the sum of the given values. + * + * @param (callable(TValue): mixed)|string|null $callback + * @return mixed + */ + public function sum($callback = null) + { + $callback = is_null($callback) + ? $this->identity() + : $this->valueRetriever($callback); + + return $this->reduce(fn ($result, $item) => $result + $callback($item), 0); + } + + /** + * Apply the callback if the collection is empty. + * + * @template TWhenEmptyReturnType + * + * @param (callable($this): TWhenEmptyReturnType) $callback + * @param (callable($this): TWhenEmptyReturnType)|null $default + * @return $this|TWhenEmptyReturnType + */ + public function whenEmpty(callable $callback, ?callable $default = null) + { + return $this->when($this->isEmpty(), $callback, $default); + } + + /** + * Apply the callback if the collection is not empty. + * + * @template TWhenNotEmptyReturnType + * + * @param callable($this): TWhenNotEmptyReturnType $callback + * @param (callable($this): TWhenNotEmptyReturnType)|null $default + * @return $this|TWhenNotEmptyReturnType + */ + public function whenNotEmpty(callable $callback, ?callable $default = null) + { + return $this->when($this->isNotEmpty(), $callback, $default); + } + + /** + * Apply the callback unless the collection is empty. + * + * @template TUnlessEmptyReturnType + * + * @param callable($this): TUnlessEmptyReturnType $callback + * @param (callable($this): TUnlessEmptyReturnType)|null $default + * @return $this|TUnlessEmptyReturnType + */ + public function unlessEmpty(callable $callback, ?callable $default = null) + { + return $this->whenNotEmpty($callback, $default); + } + + /** + * Apply the callback unless the collection is not empty. + * + * @template TUnlessNotEmptyReturnType + * + * @param callable($this): TUnlessNotEmptyReturnType $callback + * @param (callable($this): TUnlessNotEmptyReturnType)|null $default + * @return $this|TUnlessNotEmptyReturnType + */ + public function unlessNotEmpty(callable $callback, ?callable $default = null) + { + return $this->whenEmpty($callback, $default); + } + + /** + * Filter items by the given key value pair. + * + * @param callable|string $key + * @param mixed $operator + * @param mixed $value + * @return static + */ + public function where($key, $operator = null, $value = null) + { + return $this->filter($this->operatorForWhere(...func_get_args())); + } + + /** + * Filter items where the value for the given key is null. + * + * @param string|null $key + * @return static + */ + public function whereNull($key = null) + { + return $this->whereStrict($key, null); + } + + /** + * Filter items where the value for the given key is not null. + * + * @param string|null $key + * @return static + */ + public function whereNotNull($key = null) + { + return $this->where($key, '!==', null); + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param mixed $value + * @return static + */ + public function whereStrict($key, $value) + { + return $this->where($key, '===', $value); + } + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values + * @param bool $strict + * @return static + */ + public function whereIn($key, $values, $strict = false) + { + $values = $this->getArrayableItems($values); + + return $this->filter(fn ($item) => in_array(data_get($item, $key), $values, $strict)); + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values + * @return static + */ + public function whereInStrict($key, $values) + { + return $this->whereIn($key, $values, true); + } + + /** + * Filter items such that the value of the given key is between the given values. + * + * @param string $key + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values + * @return static + */ + public function whereBetween($key, $values) + { + return $this->where($key, '>=', reset($values))->where($key, '<=', end($values)); + } + + /** + * Filter items such that the value of the given key is not between the given values. + * + * @param string $key + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values + * @return static + */ + public function whereNotBetween($key, $values) + { + return $this->filter( + fn ($item) => data_get($item, $key) < reset($values) || data_get($item, $key) > end($values) + ); + } + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values + * @param bool $strict + * @return static + */ + public function whereNotIn($key, $values, $strict = false) + { + $values = $this->getArrayableItems($values); + + return $this->reject(fn ($item) => in_array(data_get($item, $key), $values, $strict)); + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values + * @return static + */ + public function whereNotInStrict($key, $values) + { + return $this->whereNotIn($key, $values, true); + } + + /** + * Filter the items, removing any items that don't match the given type(s). + * + * @template TWhereInstanceOf + * + * @param class-string|array> $type + * @return static + */ + public function whereInstanceOf($type) + { + return $this->filter(function ($value) use ($type) { + if (is_array($type)) { + foreach ($type as $classType) { + if ($value instanceof $classType) { + return true; + } + } + + return false; + } + + return $value instanceof $type; + }); + } + + /** + * Pass the collection to the given callback and return the result. + * + * @template TPipeReturnType + * + * @param callable($this): TPipeReturnType $callback + * @return TPipeReturnType + */ + public function pipe(callable $callback) + { + return $callback($this); + } + + /** + * Pass the collection into a new class. + * + * @template TPipeIntoValue + * + * @param class-string $class + * @return TPipeIntoValue + */ + public function pipeInto($class) + { + return new $class($this); + } + + /** + * Pass the collection through a series of callable pipes and return the result. + * + * @param array $callbacks + * @return mixed + */ + public function pipeThrough($callbacks) + { + return Collection::make($callbacks)->reduce( + fn ($carry, $callback) => $callback($carry), + $this, + ); + } + + /** + * Reduce the collection to a single value. + * + * @template TReduceInitial + * @template TReduceReturnType + * + * @param callable(TReduceInitial|TReduceReturnType, TValue, TKey): TReduceReturnType $callback + * @param TReduceInitial $initial + * @return TReduceReturnType + */ + public function reduce(callable $callback, $initial = null) + { + $result = $initial; + + foreach ($this as $key => $value) { + $result = $callback($result, $value, $key); + } + + return $result; + } + + /** + * Reduce the collection to multiple aggregate values. + * + * @param callable $callback + * @param mixed ...$initial + * @return array + * + * @throws \UnexpectedValueException + */ + public function reduceSpread(callable $callback, ...$initial) + { + $result = $initial; + + foreach ($this as $key => $value) { + $result = call_user_func_array($callback, array_merge($result, [$value, $key])); + + if (! is_array($result)) { + throw new UnexpectedValueException(sprintf( + "%s::reduceSpread expects reducer to return an array, but got a '%s' instead.", + class_basename(static::class), gettype($result) + )); + } + } + + return $result; + } + + /** + * Reduce an associative collection to a single value. + * + * @template TReduceWithKeysInitial + * @template TReduceWithKeysReturnType + * + * @param callable(TReduceWithKeysInitial|TReduceWithKeysReturnType, TValue, TKey): TReduceWithKeysReturnType $callback + * @param TReduceWithKeysInitial $initial + * @return TReduceWithKeysReturnType + */ + public function reduceWithKeys(callable $callback, $initial = null) + { + return $this->reduce($callback, $initial); + } + + /** + * Create a collection of all elements that do not pass a given truth test. + * + * @param (callable(TValue, TKey): bool)|bool|TValue $callback + * @return static + */ + public function reject($callback = true) + { + $useAsCallable = $this->useAsCallable($callback); + + return $this->filter(function ($value, $key) use ($callback, $useAsCallable) { + return $useAsCallable + ? ! $callback($value, $key) + : $value != $callback; + }); + } + + /** + * Pass the collection to the given callback and then return it. + * + * @param callable($this): mixed $callback + * @return $this + */ + public function tap(callable $callback) + { + $callback($this); + + return $this; + } + + /** + * Return only unique items from the collection array. + * + * @param (callable(TValue, TKey): mixed)|string|null $key + * @param bool $strict + * @return static + */ + public function unique($key = null, $strict = false) + { + $callback = $this->valueRetriever($key); + + $exists = []; + + return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) { + if (in_array($id = $callback($item, $key), $exists, $strict)) { + return true; + } + + $exists[] = $id; + }); + } + + /** + * Return only unique items from the collection array using strict comparison. + * + * @param (callable(TValue, TKey): mixed)|string|null $key + * @return static + */ + public function uniqueStrict($key = null) + { + return $this->unique($key, true); + } + + /** + * Collect the values into a collection. + * + * @return \Illuminate\Support\Collection + */ + public function collect() + { + return new Collection($this->all()); + } + + /** + * Get the collection of items as a plain array. + * + * @return array + */ + public function toArray() + { + return $this->map(fn ($value) => $value instanceof Arrayable ? $value->toArray() : $value)->all(); + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + public function jsonSerialize(): array + { + return array_map(function ($value) { + if ($value instanceof JsonSerializable) { + return $value->jsonSerialize(); + } elseif ($value instanceof Jsonable) { + return json_decode($value->toJson(), true); + } elseif ($value instanceof Arrayable) { + return $value->toArray(); + } + + return $value; + }, $this->all()); + } + + /** + * Get the collection of items as JSON. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } + + /** + * Get a CachingIterator instance. + * + * @param int $flags + * @return \CachingIterator + */ + public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING) + { + return new CachingIterator($this->getIterator(), $flags); + } + + /** + * Convert the collection to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->escapeWhenCastingToString + ? e($this->toJson()) + : $this->toJson(); + } + + /** + * Indicate that the model's string representation should be escaped when __toString is invoked. + * + * @param bool $escape + * @return $this + */ + public function escapeWhenCastingToString($escape = true) + { + $this->escapeWhenCastingToString = $escape; + + return $this; + } + + /** + * Add a method to the list of proxied methods. + * + * @param string $method + * @return void + */ + public static function proxy($method) + { + static::$proxies[] = $method; + } + + /** + * Dynamically access collection proxies. + * + * @param string $key + * @return mixed + * + * @throws \Exception + */ + public function __get($key) + { + if (! in_array($key, static::$proxies)) { + throw new Exception("Property [{$key}] does not exist on this collection instance."); + } + + return new HigherOrderCollectionProxy($this, $key); + } + + /** + * Results array of items from Collection or Arrayable. + * + * @param mixed $items + * @return array + */ + protected function getArrayableItems($items) + { + if (is_array($items)) { + return $items; + } + + return match (true) { + $items instanceof WeakMap => throw new InvalidArgumentException('Collections can not be created using instances of WeakMap.'), + $items instanceof Enumerable => $items->all(), + $items instanceof Arrayable => $items->toArray(), + $items instanceof Traversable => iterator_to_array($items), + $items instanceof Jsonable => json_decode($items->toJson(), true), + $items instanceof JsonSerializable => (array) $items->jsonSerialize(), + $items instanceof UnitEnum => [$items], + default => (array) $items, + }; + } + + /** + * Get an operator checker callback. + * + * @param callable|string $key + * @param string|null $operator + * @param mixed $value + * @return \Closure + */ + protected function operatorForWhere($key, $operator = null, $value = null) + { + if ($this->useAsCallable($key)) { + return $key; + } + + if (func_num_args() === 1) { + $value = true; + + $operator = '='; + } + + if (func_num_args() === 2) { + $value = $operator; + + $operator = '='; + } + + return function ($item) use ($key, $operator, $value) { + $retrieved = data_get($item, $key); + + $strings = array_filter([$retrieved, $value], function ($value) { + return is_string($value) || (is_object($value) && method_exists($value, '__toString')); + }); + + if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) { + return in_array($operator, ['!=', '<>', '!==']); + } + + switch ($operator) { + default: + case '=': + case '==': return $retrieved == $value; + case '!=': + case '<>': return $retrieved != $value; + case '<': return $retrieved < $value; + case '>': return $retrieved > $value; + case '<=': return $retrieved <= $value; + case '>=': return $retrieved >= $value; + case '===': return $retrieved === $value; + case '!==': return $retrieved !== $value; + case '<=>': return $retrieved <=> $value; + } + }; + } + + /** + * Determine if the given value is callable, but not a string. + * + * @param mixed $value + * @return bool + */ + protected function useAsCallable($value) + { + return ! is_string($value) && is_callable($value); + } + + /** + * Get a value retrieving callback. + * + * @param callable|string|null $value + * @return callable + */ + protected function valueRetriever($value) + { + if ($this->useAsCallable($value)) { + return $value; + } + + return fn ($item) => data_get($item, $value); + } + + /** + * Make a function to check an item's equality. + * + * @param mixed $value + * @return \Closure(mixed): bool + */ + protected function equality($value) + { + return fn ($item) => $item === $value; + } + + /** + * Make a function using another function, by negating its result. + * + * @param \Closure $callback + * @return \Closure + */ + protected function negate(Closure $callback) + { + return fn (...$params) => ! $callback(...$params); + } + + /** + * Make a function that returns what's passed to it. + * + * @return \Closure(TValue): TValue + */ + protected function identity() + { + return fn ($value) => $value; + } +} diff --git a/data/web/inc/lib/vendor/illuminate/collections/composer.json b/data/web/inc/lib/vendor/illuminate/collections/composer.json new file mode 100644 index 0000000000..5e11a788e7 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/collections/composer.json @@ -0,0 +1,42 @@ +{ + "name": "illuminate/collections", + "description": "The Illuminate Collections package.", + "license": "MIT", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "require": { + "php": "^8.1", + "illuminate/conditionable": "^10.0", + "illuminate/contracts": "^10.0", + "illuminate/macroable": "^10.0" + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + }, + "files": [ + "helpers.php" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^6.2)." + }, + "config": { + "sort-packages": true + }, + "minimum-stability": "dev" +} diff --git a/data/web/inc/lib/vendor/illuminate/collections/functions.php b/data/web/inc/lib/vendor/illuminate/collections/functions.php new file mode 100644 index 0000000000..6ccd9b3ed0 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/collections/functions.php @@ -0,0 +1,27 @@ + $value->value, + $value instanceof \UnitEnum => $value->name, + + default => $value ?? value($default), + }; + } +} diff --git a/data/web/inc/lib/vendor/illuminate/collections/helpers.php b/data/web/inc/lib/vendor/illuminate/collections/helpers.php new file mode 100644 index 0000000000..235edceb15 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/collections/helpers.php @@ -0,0 +1,226 @@ +|iterable|null $value + * @return \Illuminate\Support\Collection + */ + function collect($value = []) + { + return new Collection($value); + } +} + +if (! function_exists('data_fill')) { + /** + * Fill in data where it's missing. + * + * @param mixed $target + * @param string|array $key + * @param mixed $value + * @return mixed + */ + function data_fill(&$target, $key, $value) + { + return data_set($target, $key, $value, false); + } +} + +if (! function_exists('data_get')) { + /** + * Get an item from an array or object using "dot" notation. + * + * @param mixed $target + * @param string|array|int|null $key + * @param mixed $default + * @return mixed + */ + function data_get($target, $key, $default = null) + { + if (is_null($key)) { + return $target; + } + + $key = is_array($key) ? $key : explode('.', $key); + + foreach ($key as $i => $segment) { + unset($key[$i]); + + if (is_null($segment)) { + return $target; + } + + if ($segment === '*') { + if ($target instanceof Collection) { + $target = $target->all(); + } elseif (! is_iterable($target)) { + return value($default); + } + + $result = []; + + foreach ($target as $item) { + $result[] = data_get($item, $key); + } + + return in_array('*', $key) ? Arr::collapse($result) : $result; + } + + if (Arr::accessible($target) && Arr::exists($target, $segment)) { + $target = $target[$segment]; + } elseif (is_object($target) && isset($target->{$segment})) { + $target = $target->{$segment}; + } else { + return value($default); + } + } + + return $target; + } +} + +if (! function_exists('data_set')) { + /** + * Set an item on an array or object using dot notation. + * + * @param mixed $target + * @param string|array $key + * @param mixed $value + * @param bool $overwrite + * @return mixed + */ + function data_set(&$target, $key, $value, $overwrite = true) + { + $segments = is_array($key) ? $key : explode('.', $key); + + if (($segment = array_shift($segments)) === '*') { + if (! Arr::accessible($target)) { + $target = []; + } + + if ($segments) { + foreach ($target as &$inner) { + data_set($inner, $segments, $value, $overwrite); + } + } elseif ($overwrite) { + foreach ($target as &$inner) { + $inner = $value; + } + } + } elseif (Arr::accessible($target)) { + if ($segments) { + if (! Arr::exists($target, $segment)) { + $target[$segment] = []; + } + + data_set($target[$segment], $segments, $value, $overwrite); + } elseif ($overwrite || ! Arr::exists($target, $segment)) { + $target[$segment] = $value; + } + } elseif (is_object($target)) { + if ($segments) { + if (! isset($target->{$segment})) { + $target->{$segment} = []; + } + + data_set($target->{$segment}, $segments, $value, $overwrite); + } elseif ($overwrite || ! isset($target->{$segment})) { + $target->{$segment} = $value; + } + } else { + $target = []; + + if ($segments) { + data_set($target[$segment], $segments, $value, $overwrite); + } elseif ($overwrite) { + $target[$segment] = $value; + } + } + + return $target; + } +} + +if (! function_exists('data_forget')) { + /** + * Remove / unset an item from an array or object using "dot" notation. + * + * @param mixed $target + * @param string|array|int|null $key + * @return mixed + */ + function data_forget(&$target, $key) + { + $segments = is_array($key) ? $key : explode('.', $key); + + if (($segment = array_shift($segments)) === '*' && Arr::accessible($target)) { + if ($segments) { + foreach ($target as &$inner) { + data_forget($inner, $segments); + } + } + } elseif (Arr::accessible($target)) { + if ($segments && Arr::exists($target, $segment)) { + data_forget($target[$segment], $segments); + } else { + Arr::forget($target, $segment); + } + } elseif (is_object($target)) { + if ($segments && isset($target->{$segment})) { + data_forget($target->{$segment}, $segments); + } elseif (isset($target->{$segment})) { + unset($target->{$segment}); + } + } + + return $target; + } +} + +if (! function_exists('head')) { + /** + * Get the first element of an array. Useful for method chaining. + * + * @param array $array + * @return mixed + */ + function head($array) + { + return reset($array); + } +} + +if (! function_exists('last')) { + /** + * Get the last element from an array. + * + * @param array $array + * @return mixed + */ + function last($array) + { + return end($array); + } +} + +if (! function_exists('value')) { + /** + * Return the default value of the given value. + * + * @param mixed $value + * @param mixed ...$args + * @return mixed + */ + function value($value, ...$args) + { + return $value instanceof Closure ? $value(...$args) : $value; + } +} diff --git a/data/web/inc/lib/vendor/illuminate/conditionable/HigherOrderWhenProxy.php b/data/web/inc/lib/vendor/illuminate/conditionable/HigherOrderWhenProxy.php new file mode 100644 index 0000000000..579114cf19 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/conditionable/HigherOrderWhenProxy.php @@ -0,0 +1,109 @@ +target = $target; + } + + /** + * Set the condition on the proxy. + * + * @param bool $condition + * @return $this + */ + public function condition($condition) + { + [$this->condition, $this->hasCondition] = [$condition, true]; + + return $this; + } + + /** + * Indicate that the condition should be negated. + * + * @return $this + */ + public function negateConditionOnCapture() + { + $this->negateConditionOnCapture = true; + + return $this; + } + + /** + * Proxy accessing an attribute onto the target. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + if (! $this->hasCondition) { + $condition = $this->target->{$key}; + + return $this->condition($this->negateConditionOnCapture ? ! $condition : $condition); + } + + return $this->condition + ? $this->target->{$key} + : $this->target; + } + + /** + * Proxy a method call on the target. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (! $this->hasCondition) { + $condition = $this->target->{$method}(...$parameters); + + return $this->condition($this->negateConditionOnCapture ? ! $condition : $condition); + } + + return $this->condition + ? $this->target->{$method}(...$parameters) + : $this->target; + } +} diff --git a/data/web/inc/lib/vendor/illuminate/conditionable/LICENSE.md b/data/web/inc/lib/vendor/illuminate/conditionable/LICENSE.md new file mode 100644 index 0000000000..79810c848f --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/conditionable/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Taylor Otwell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/illuminate/conditionable/Traits/Conditionable.php b/data/web/inc/lib/vendor/illuminate/conditionable/Traits/Conditionable.php new file mode 100644 index 0000000000..5e3194bbcb --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/conditionable/Traits/Conditionable.php @@ -0,0 +1,73 @@ +condition($value); + } + + if ($value) { + return $callback($this, $value) ?? $this; + } elseif ($default) { + return $default($this, $value) ?? $this; + } + + return $this; + } + + /** + * Apply the callback if the given "value" is (or resolves to) falsy. + * + * @template TUnlessParameter + * @template TUnlessReturnType + * + * @param (\Closure($this): TUnlessParameter)|TUnlessParameter|null $value + * @param (callable($this, TUnlessParameter): TUnlessReturnType)|null $callback + * @param (callable($this, TUnlessParameter): TUnlessReturnType)|null $default + * @return $this|TUnlessReturnType + */ + public function unless($value = null, ?callable $callback = null, ?callable $default = null) + { + $value = $value instanceof Closure ? $value($this) : $value; + + if (func_num_args() === 0) { + return (new HigherOrderWhenProxy($this))->negateConditionOnCapture(); + } + + if (func_num_args() === 1) { + return (new HigherOrderWhenProxy($this))->condition(! $value); + } + + if (! $value) { + return $callback($this, $value) ?? $this; + } elseif ($default) { + return $default($this, $value) ?? $this; + } + + return $this; + } +} diff --git a/data/web/inc/lib/vendor/illuminate/conditionable/composer.json b/data/web/inc/lib/vendor/illuminate/conditionable/composer.json new file mode 100644 index 0000000000..40b0808060 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/conditionable/composer.json @@ -0,0 +1,33 @@ +{ + "name": "illuminate/conditionable", + "description": "The Illuminate Conditionable package.", + "license": "MIT", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "require": { + "php": "^8.0.2" + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "config": { + "sort-packages": true + }, + "minimum-stability": "dev" +} diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Auth/Access/Authorizable.php b/data/web/inc/lib/vendor/illuminate/contracts/Auth/Access/Authorizable.php new file mode 100644 index 0000000000..cedeb6ea34 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/contracts/Auth/Access/Authorizable.php @@ -0,0 +1,15 @@ +|CastsAttributes|CastsInboundAttributes + */ + public static function castUsing(array $arguments); +} diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/CastsAttributes.php b/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/CastsAttributes.php new file mode 100644 index 0000000000..89cec66a7a --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/CastsAttributes.php @@ -0,0 +1,34 @@ + $attributes + * @return TGet|null + */ + public function get(Model $model, string $key, mixed $value, array $attributes); + + /** + * Transform the attribute to its underlying model values. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $key + * @param TSet|null $value + * @param array $attributes + * @return mixed + */ + public function set(Model $model, string $key, mixed $value, array $attributes); +} diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/CastsInboundAttributes.php b/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/CastsInboundAttributes.php new file mode 100644 index 0000000000..6a3e2ef18f --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/CastsInboundAttributes.php @@ -0,0 +1,19 @@ +id = $id; + $this->class = $class; + $this->relations = $relations; + $this->connection = $connection; + } + + /** + * Specify the collection class that should be used when serializing / restoring collections. + * + * @param string|null $collectionClass + * @return $this + */ + public function useCollectionClass(?string $collectionClass) + { + $this->collectionClass = $collectionClass; + + return $this; + } +} diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Database/Query/Builder.php b/data/web/inc/lib/vendor/illuminate/contracts/Database/Query/Builder.php new file mode 100644 index 0000000000..e116ebf419 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/contracts/Database/Query/Builder.php @@ -0,0 +1,12 @@ + + */ + public function getQueueableIds(); + + /** + * Get the relationships of the entities being queued. + * + * @return array + */ + public function getQueueableRelations(); + + /** + * Get the connection of the entities being queued. + * + * @return string|null + */ + public function getQueueableConnection(); +} diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Queue/QueueableEntity.php b/data/web/inc/lib/vendor/illuminate/contracts/Queue/QueueableEntity.php new file mode 100644 index 0000000000..366f0c84fa --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/contracts/Queue/QueueableEntity.php @@ -0,0 +1,27 @@ + + */ + public function toArray(); +} diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Support/CanBeEscapedWhenCastToString.php b/data/web/inc/lib/vendor/illuminate/contracts/Support/CanBeEscapedWhenCastToString.php new file mode 100644 index 0000000000..e1be6fefac --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/contracts/Support/CanBeEscapedWhenCastToString.php @@ -0,0 +1,14 @@ +getMethods( + ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED + ); + + foreach ($methods as $method) { + if ($replace || ! static::hasMacro($method->name)) { + static::macro($method->name, $method->invoke($mixin)); + } + } + } + + /** + * Checks if macro is registered. + * + * @param string $name + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$macros[$name]); + } + + /** + * Flush the existing macros. + * + * @return void + */ + public static function flushMacros() + { + static::$macros = []; + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public static function __callStatic($method, $parameters) + { + if (! static::hasMacro($method)) { + throw new BadMethodCallException(sprintf( + 'Method %s::%s does not exist.', static::class, $method + )); + } + + $macro = static::$macros[$method]; + + if ($macro instanceof Closure) { + $macro = $macro->bindTo(null, static::class); + } + + return $macro(...$parameters); + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + if (! static::hasMacro($method)) { + throw new BadMethodCallException(sprintf( + 'Method %s::%s does not exist.', static::class, $method + )); + } + + $macro = static::$macros[$method]; + + if ($macro instanceof Closure) { + $macro = $macro->bindTo($this, static::class); + } + + return $macro(...$parameters); + } +} diff --git a/data/web/inc/lib/vendor/illuminate/macroable/composer.json b/data/web/inc/lib/vendor/illuminate/macroable/composer.json new file mode 100644 index 0000000000..231d2333a4 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/macroable/composer.json @@ -0,0 +1,33 @@ +{ + "name": "illuminate/macroable", + "description": "The Illuminate Macroable package.", + "license": "MIT", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "require": { + "php": "^8.1" + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "config": { + "sort-packages": true + }, + "minimum-stability": "dev" +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/LICENSE b/data/web/inc/lib/vendor/league/oauth2-client/LICENSE new file mode 100644 index 0000000000..7dfa39b7ce --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2020 Alex Bilbie + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/league/oauth2-client/README.md b/data/web/inc/lib/vendor/league/oauth2-client/README.md new file mode 100644 index 0000000000..cbb449d484 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/README.md @@ -0,0 +1,58 @@ +# OAuth 2.0 Client + +This package provides a base for integrating with [OAuth 2.0](http://oauth.net/2/) service providers. + +[![Gitter Chat](https://img.shields.io/badge/gitter-join_chat-brightgreen.svg?style=flat-square)](https://gitter.im/thephpleague/oauth2-client) +[![Source Code](https://img.shields.io/badge/source-thephpleague/oauth2--client-blue.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client) +[![Latest Version](https://img.shields.io/github/release/thephpleague/oauth2-client.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE) +[![Build Status](https://img.shields.io/github/actions/workflow/status/thephpleague/oauth2-client/continuous-integration.yml?label=CI&logo=github&style=flat-square)](https://github.com/thephpleague/oauth2-client/actions?query=workflow%3ACI) +[![Codecov Code Coverage](https://img.shields.io/codecov/c/gh/thephpleague/oauth2-client?label=codecov&logo=codecov&style=flat-square)](https://codecov.io/gh/thephpleague/oauth2-client) +[![Total Downloads](https://img.shields.io/packagist/dt/league/oauth2-client.svg?style=flat-square)](https://packagist.org/packages/league/oauth2-client) + +--- + +The OAuth 2.0 login flow, seen commonly around the web in the form of "Connect with Facebook/Google/etc." buttons, is a common integration added to web applications, but it can be tricky and tedious to do right. To help, we've created the `league/oauth2-client` package, which provides a base for integrating with various OAuth 2.0 providers, without overburdening your application with the concerns of [RFC 6749](http://tools.ietf.org/html/rfc6749). + +This OAuth 2.0 client library will work with any OAuth 2.0 provider that conforms to the OAuth 2.0 Authorization Framework. Out-of-the-box, we provide a `GenericProvider` class to connect to any service provider that uses [Bearer tokens](http://tools.ietf.org/html/rfc6750). See our [basic usage guide](https://oauth2-client.thephpleague.com/usage/) for examples using `GenericProvider`. + +Many service providers provide additional functionality above and beyond the OAuth 2.0 specification. For this reason, you may extend and wrap this library to support additional behavior. There are already many [official](https://oauth2-client.thephpleague.com/providers/league/) and [third-party](https://oauth2-client.thephpleague.com/providers/thirdparty/) provider clients available (e.g., Facebook, GitHub, Google, Instagram, LinkedIn, etc.). If your provider isn't in the list, feel free to add it. + +This package is compliant with [PSR-1][], [PSR-2][], [PSR-4][], and [PSR-7][]. If you notice compliance oversights, please send a patch via pull request. If you're interested in contributing to this library, please take a look at our [contributing guidelines](https://github.com/thephpleague/oauth2-client/blob/master/CONTRIBUTING.md). + +## Requirements + +We support the following versions of PHP: + +* PHP 8.1 +* PHP 8.0 +* PHP 7.4 +* PHP 7.3 +* PHP 7.2 +* PHP 7.1 +* PHP 7.0 +* PHP 5.6 + +## Provider Clients + +We provide a list of [official PHP League provider clients](https://oauth2-client.thephpleague.com/providers/league/), as well as [third-party provider clients](https://oauth2-client.thephpleague.com/providers/thirdparty/). + +To build your own provider client, please refer to "[Implementing a Provider Client](https://oauth2-client.thephpleague.com/providers/implementing/)." + +## Usage + +For usage and code examples, check out our [basic usage guide](https://oauth2-client.thephpleague.com/usage/). + +## Contributing + +Please see [our contributing guidelines](https://github.com/thephpleague/oauth2-client/blob/master/CONTRIBUTING.md) for details. + +## License + +The MIT License (MIT). Please see [LICENSE](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE) for more information. + + +[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md +[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md +[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md +[PSR-7]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md diff --git a/data/web/inc/lib/vendor/league/oauth2-client/composer.json b/data/web/inc/lib/vendor/league/oauth2-client/composer.json new file mode 100644 index 0000000000..59201f48fb --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/composer.json @@ -0,0 +1,58 @@ +{ + "name": "league/oauth2-client", + "description": "OAuth 2.0 Client Library", + "license": "MIT", + "config": { + "sort-packages": true + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0", + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "paragonie/random_compat": "^1 || ^2 || ^9.99" + }, + "require-dev": { + "mockery/mockery": "^1.3.5", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", + "squizlabs/php_codesniffer": "^2.3 || ^3.0" + }, + "keywords": [ + "oauth", + "oauth2", + "authorization", + "authentication", + "idp", + "identity", + "sso", + "single sign on" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + + ], + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\OAuth2\\Client\\Test\\": "test/src/" + } + }, + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/AbstractGrant.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/AbstractGrant.php new file mode 100644 index 0000000000..2c0244ba3d --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/AbstractGrant.php @@ -0,0 +1,80 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +use League\OAuth2\Client\Tool\RequiredParameterTrait; + +/** + * Represents a type of authorization grant. + * + * An authorization grant is a credential representing the resource + * owner's authorization (to access its protected resources) used by the + * client to obtain an access token. OAuth 2.0 defines four + * grant types -- authorization code, implicit, resource owner password + * credentials, and client credentials -- as well as an extensibility + * mechanism for defining additional types. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3 Authorization Grant (RFC 6749, ยง1.3) + */ +abstract class AbstractGrant +{ + use RequiredParameterTrait; + + /** + * Returns the name of this grant, eg. 'grant_name', which is used as the + * grant type when encoding URL query parameters. + * + * @return string + */ + abstract protected function getName(); + + /** + * Returns a list of all required request parameters. + * + * @return array + */ + abstract protected function getRequiredRequestParameters(); + + /** + * Returns this grant's name as its string representation. This allows for + * string interpolation when building URL query parameters. + * + * @return string + */ + public function __toString() + { + return $this->getName(); + } + + /** + * Prepares an access token request's parameters by checking that all + * required parameters are set, then merging with any given defaults. + * + * @param array $defaults + * @param array $options + * @return array + */ + public function prepareRequestParameters(array $defaults, array $options) + { + $defaults['grant_type'] = $this->getName(); + + $required = $this->getRequiredRequestParameters(); + $provided = array_merge($defaults, $options); + + $this->checkRequiredParameters($required, $provided); + + return $provided; + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/AuthorizationCode.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/AuthorizationCode.php new file mode 100644 index 0000000000..c49460c02d --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/AuthorizationCode.php @@ -0,0 +1,41 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents an authorization code grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.1 Authorization Code (RFC 6749, ยง1.3.1) + */ +class AuthorizationCode extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'authorization_code'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'code', + ]; + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/ClientCredentials.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/ClientCredentials.php new file mode 100644 index 0000000000..dc78c4fdab --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/ClientCredentials.php @@ -0,0 +1,39 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents a client credentials grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.4 Client Credentials (RFC 6749, ยง1.3.4) + */ +class ClientCredentials extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'client_credentials'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return []; + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php new file mode 100644 index 0000000000..c3c4e677b4 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php @@ -0,0 +1,26 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant\Exception; + +use InvalidArgumentException; + +/** + * Exception thrown if the grant does not extend from AbstractGrant. + * + * @see League\OAuth2\Client\Grant\AbstractGrant + */ +class InvalidGrantException extends InvalidArgumentException +{ +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/GrantFactory.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/GrantFactory.php new file mode 100644 index 0000000000..71990e83db --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/GrantFactory.php @@ -0,0 +1,104 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +use League\OAuth2\Client\Grant\Exception\InvalidGrantException; + +/** + * Represents a factory used when retrieving an authorization grant type. + */ +class GrantFactory +{ + /** + * @var array + */ + protected $registry = []; + + /** + * Defines a grant singleton in the registry. + * + * @param string $name + * @param AbstractGrant $grant + * @return self + */ + public function setGrant($name, AbstractGrant $grant) + { + $this->registry[$name] = $grant; + + return $this; + } + + /** + * Returns a grant singleton by name. + * + * If the grant has not be registered, a default grant will be loaded. + * + * @param string $name + * @return AbstractGrant + */ + public function getGrant($name) + { + if (empty($this->registry[$name])) { + $this->registerDefaultGrant($name); + } + + return $this->registry[$name]; + } + + /** + * Registers a default grant singleton by name. + * + * @param string $name + * @return self + */ + protected function registerDefaultGrant($name) + { + // PascalCase the grant. E.g: 'authorization_code' becomes 'AuthorizationCode' + $class = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $name))); + $class = 'League\\OAuth2\\Client\\Grant\\' . $class; + + $this->checkGrant($class); + + return $this->setGrant($name, new $class); + } + + /** + * Determines if a variable is a valid grant. + * + * @param mixed $class + * @return boolean + */ + public function isGrant($class) + { + return is_subclass_of($class, AbstractGrant::class); + } + + /** + * Checks if a variable is a valid grant. + * + * @throws InvalidGrantException + * @param mixed $class + * @return void + */ + public function checkGrant($class) + { + if (!$this->isGrant($class)) { + throw new InvalidGrantException(sprintf( + 'Grant "%s" must extend AbstractGrant', + is_object($class) ? get_class($class) : $class + )); + } + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/Password.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/Password.php new file mode 100644 index 0000000000..6543b2ebd1 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/Password.php @@ -0,0 +1,42 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents a resource owner password credentials grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.3 Resource Owner Password Credentials (RFC 6749, ยง1.3.3) + */ +class Password extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'password'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'username', + 'password', + ]; + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/RefreshToken.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/RefreshToken.php new file mode 100644 index 0000000000..8192182301 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Grant/RefreshToken.php @@ -0,0 +1,41 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents a refresh token grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-6 Refreshing an Access Token (RFC 6749, ยง6) + */ +class RefreshToken extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'refresh_token'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'refresh_token', + ]; + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php b/data/web/inc/lib/vendor/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php new file mode 100644 index 0000000000..3da4065682 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php @@ -0,0 +1,42 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\OptionProvider; + +use InvalidArgumentException; + +/** + * Add http basic auth into access token request options + * @link https://tools.ietf.org/html/rfc6749#section-2.3.1 + */ +class HttpBasicAuthOptionProvider extends PostAuthOptionProvider +{ + /** + * @inheritdoc + */ + public function getAccessTokenOptions($method, array $params) + { + if (empty($params['client_id']) || empty($params['client_secret'])) { + throw new InvalidArgumentException('clientId and clientSecret are required for http basic auth'); + } + + $encodedCredentials = base64_encode(sprintf('%s:%s', $params['client_id'], $params['client_secret'])); + unset($params['client_id'], $params['client_secret']); + + $options = parent::getAccessTokenOptions($method, $params); + $options['headers']['Authorization'] = 'Basic ' . $encodedCredentials; + + return $options; + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php b/data/web/inc/lib/vendor/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php new file mode 100644 index 0000000000..1126d25aa0 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php @@ -0,0 +1,30 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\OptionProvider; + +/** + * Interface for access token options provider + */ +interface OptionProviderInterface +{ + /** + * Builds request options used for requesting an access token. + * + * @param string $method + * @param array $params + * @return array + */ + public function getAccessTokenOptions($method, array $params); +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php b/data/web/inc/lib/vendor/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php new file mode 100644 index 0000000000..12d920ecf9 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php @@ -0,0 +1,51 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\OptionProvider; + +use League\OAuth2\Client\Provider\AbstractProvider; +use League\OAuth2\Client\Tool\QueryBuilderTrait; + +/** + * Provide options for access token + */ +class PostAuthOptionProvider implements OptionProviderInterface +{ + use QueryBuilderTrait; + + /** + * @inheritdoc + */ + public function getAccessTokenOptions($method, array $params) + { + $options = ['headers' => ['content-type' => 'application/x-www-form-urlencoded']]; + + if ($method === AbstractProvider::METHOD_POST) { + $options['body'] = $this->getAccessTokenBody($params); + } + + return $options; + } + + /** + * Returns the request body for requesting an access token. + * + * @param array $params + * @return string + */ + protected function getAccessTokenBody(array $params) + { + return $this->buildQueryString($params); + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/AbstractProvider.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/AbstractProvider.php new file mode 100644 index 0000000000..293a54d6e6 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/AbstractProvider.php @@ -0,0 +1,941 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +use GuzzleHttp\Client as HttpClient; +use GuzzleHttp\ClientInterface as HttpClientInterface; +use GuzzleHttp\Exception\BadResponseException; +use InvalidArgumentException; +use League\OAuth2\Client\Grant\AbstractGrant; +use League\OAuth2\Client\Grant\GrantFactory; +use League\OAuth2\Client\OptionProvider\OptionProviderInterface; +use League\OAuth2\Client\OptionProvider\PostAuthOptionProvider; +use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use League\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Token\AccessTokenInterface; +use League\OAuth2\Client\Tool\ArrayAccessorTrait; +use League\OAuth2\Client\Tool\GuardedPropertyTrait; +use League\OAuth2\Client\Tool\QueryBuilderTrait; +use League\OAuth2\Client\Tool\RequestFactory; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use UnexpectedValueException; + +/** + * Represents a service provider (authorization server). + * + * @link http://tools.ietf.org/html/rfc6749#section-1.1 Roles (RFC 6749, ยง1.1) + */ +abstract class AbstractProvider +{ + use ArrayAccessorTrait; + use GuardedPropertyTrait; + use QueryBuilderTrait; + + /** + * @var string|null Key used in a token response to identify the resource owner. + */ + const ACCESS_TOKEN_RESOURCE_OWNER_ID = null; + + /** + * @var string HTTP method used to fetch access tokens. + */ + const METHOD_GET = 'GET'; + + /** + * @var string HTTP method used to fetch access tokens. + */ + const METHOD_POST = 'POST'; + + /** + * @var string PKCE method used to fetch authorization token. + * The PKCE code challenge will be hashed with sha256 (recommended). + */ + const PKCE_METHOD_S256 = 'S256'; + + /** + * @var string PKCE method used to fetch authorization token. + * The PKCE code challenge will be sent as plain text, this is NOT recommended. + * Only use `plain` if no other option is possible. + */ + const PKCE_METHOD_PLAIN = 'plain'; + + /** + * @var string + */ + protected $clientId; + + /** + * @var string + */ + protected $clientSecret; + + /** + * @var string + */ + protected $redirectUri; + + /** + * @var string + */ + protected $state; + + /** + * @var string|null + */ + protected $pkceCode = null; + + /** + * @var GrantFactory + */ + protected $grantFactory; + + /** + * @var RequestFactory + */ + protected $requestFactory; + + /** + * @var HttpClientInterface + */ + protected $httpClient; + + /** + * @var OptionProviderInterface + */ + protected $optionProvider; + + /** + * Constructs an OAuth 2.0 service provider. + * + * @param array $options An array of options to set on this provider. + * Options include `clientId`, `clientSecret`, `redirectUri`, and `state`. + * Individual providers may introduce more options, as needed. + * @param array $collaborators An array of collaborators that may be used to + * override this provider's default behavior. Collaborators include + * `grantFactory`, `requestFactory`, and `httpClient`. + * Individual providers may introduce more collaborators, as needed. + */ + public function __construct(array $options = [], array $collaborators = []) + { + // We'll let the GuardedPropertyTrait handle mass assignment of incoming + // options, skipping any blacklisted properties defined in the provider + $this->fillProperties($options); + + if (empty($collaborators['grantFactory'])) { + $collaborators['grantFactory'] = new GrantFactory(); + } + $this->setGrantFactory($collaborators['grantFactory']); + + if (empty($collaborators['requestFactory'])) { + $collaborators['requestFactory'] = new RequestFactory(); + } + $this->setRequestFactory($collaborators['requestFactory']); + + if (empty($collaborators['httpClient'])) { + $client_options = $this->getAllowedClientOptions($options); + + $collaborators['httpClient'] = new HttpClient( + array_intersect_key($options, array_flip($client_options)) + ); + } + $this->setHttpClient($collaborators['httpClient']); + + if (empty($collaborators['optionProvider'])) { + $collaborators['optionProvider'] = new PostAuthOptionProvider(); + } + $this->setOptionProvider($collaborators['optionProvider']); + } + + /** + * Returns the list of options that can be passed to the HttpClient + * + * @param array $options An array of options to set on this provider. + * Options include `clientId`, `clientSecret`, `redirectUri`, and `state`. + * Individual providers may introduce more options, as needed. + * @return array The options to pass to the HttpClient constructor + */ + protected function getAllowedClientOptions(array $options) + { + $client_options = ['timeout', 'proxy']; + + // Only allow turning off ssl verification if it's for a proxy + if (!empty($options['proxy'])) { + $client_options[] = 'verify'; + } + + return $client_options; + } + + /** + * Sets the grant factory instance. + * + * @param GrantFactory $factory + * @return self + */ + public function setGrantFactory(GrantFactory $factory) + { + $this->grantFactory = $factory; + + return $this; + } + + /** + * Returns the current grant factory instance. + * + * @return GrantFactory + */ + public function getGrantFactory() + { + return $this->grantFactory; + } + + /** + * Sets the request factory instance. + * + * @param RequestFactory $factory + * @return self + */ + public function setRequestFactory(RequestFactory $factory) + { + $this->requestFactory = $factory; + + return $this; + } + + /** + * Returns the request factory instance. + * + * @return RequestFactory + */ + public function getRequestFactory() + { + return $this->requestFactory; + } + + /** + * Sets the HTTP client instance. + * + * @param HttpClientInterface $client + * @return self + */ + public function setHttpClient(HttpClientInterface $client) + { + $this->httpClient = $client; + + return $this; + } + + /** + * Returns the HTTP client instance. + * + * @return HttpClientInterface + */ + public function getHttpClient() + { + return $this->httpClient; + } + + /** + * Sets the option provider instance. + * + * @param OptionProviderInterface $provider + * @return self + */ + public function setOptionProvider(OptionProviderInterface $provider) + { + $this->optionProvider = $provider; + + return $this; + } + + /** + * Returns the option provider instance. + * + * @return OptionProviderInterface + */ + public function getOptionProvider() + { + return $this->optionProvider; + } + + /** + * Returns the current value of the state parameter. + * + * This can be accessed by the redirect handler during authorization. + * + * @return string + */ + public function getState() + { + return $this->state; + } + + /** + * Set the value of the pkceCode parameter. + * + * When using PKCE this should be set before requesting an access token. + * + * @param string $pkceCode + * @return self + */ + public function setPkceCode($pkceCode) + { + $this->pkceCode = $pkceCode; + return $this; + } + + /** + * Returns the current value of the pkceCode parameter. + * + * This can be accessed by the redirect handler during authorization. + * + * @return string|null + */ + public function getPkceCode() + { + return $this->pkceCode; + } + + /** + * Returns the base URL for authorizing a client. + * + * Eg. https://oauth.service.com/authorize + * + * @return string + */ + abstract public function getBaseAuthorizationUrl(); + + /** + * Returns the base URL for requesting an access token. + * + * Eg. https://oauth.service.com/token + * + * @param array $params + * @return string + */ + abstract public function getBaseAccessTokenUrl(array $params); + + /** + * Returns the URL for requesting the resource owner's details. + * + * @param AccessToken $token + * @return string + */ + abstract public function getResourceOwnerDetailsUrl(AccessToken $token); + + /** + * Returns a new random string to use as the state parameter in an + * authorization flow. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + protected function getRandomState($length = 32) + { + // Converting bytes to hex will always double length. Hence, we can reduce + // the amount of bytes by half to produce the correct length. + return bin2hex(random_bytes($length / 2)); + } + + /** + * Returns a new random string to use as PKCE code_verifier and + * hashed as code_challenge parameters in an authorization flow. + * Must be between 43 and 128 characters long. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + protected function getRandomPkceCode($length = 64) + { + return substr( + strtr( + base64_encode(random_bytes($length)), + '+/', + '-_' + ), + 0, + $length + ); + } + + /** + * Returns the default scopes used by this provider. + * + * This should only be the scopes that are required to request the details + * of the resource owner, rather than all the available scopes. + * + * @return array + */ + abstract protected function getDefaultScopes(); + + /** + * Returns the string that should be used to separate scopes when building + * the URL for requesting an access token. + * + * @return string Scope separator, defaults to ',' + */ + protected function getScopeSeparator() + { + return ','; + } + + /** + * @return string|null + */ + protected function getPkceMethod() + { + return null; + } + + /** + * Returns authorization parameters based on provided options. + * + * @param array $options + * @return array Authorization parameters + */ + protected function getAuthorizationParameters(array $options) + { + if (empty($options['state'])) { + $options['state'] = $this->getRandomState(); + } + + if (empty($options['scope'])) { + $options['scope'] = $this->getDefaultScopes(); + } + + $options += [ + 'response_type' => 'code', + 'approval_prompt' => 'auto' + ]; + + if (is_array($options['scope'])) { + $separator = $this->getScopeSeparator(); + $options['scope'] = implode($separator, $options['scope']); + } + + // Store the state as it may need to be accessed later on. + $this->state = $options['state']; + + $pkceMethod = $this->getPkceMethod(); + if (!empty($pkceMethod)) { + $this->pkceCode = $this->getRandomPkceCode(); + if ($pkceMethod === static::PKCE_METHOD_S256) { + $options['code_challenge'] = trim( + strtr( + base64_encode(hash('sha256', $this->pkceCode, true)), + '+/', + '-_' + ), + '=' + ); + } elseif ($pkceMethod === static::PKCE_METHOD_PLAIN) { + $options['code_challenge'] = $this->pkceCode; + } else { + throw new InvalidArgumentException('Unknown PKCE method "' . $pkceMethod . '".'); + } + $options['code_challenge_method'] = $pkceMethod; + } + + // Business code layer might set a different redirect_uri parameter + // depending on the context, leave it as-is + if (!isset($options['redirect_uri'])) { + $options['redirect_uri'] = $this->redirectUri; + } + + $options['client_id'] = $this->clientId; + + return $options; + } + + /** + * Builds the authorization URL's query string. + * + * @param array $params Query parameters + * @return string Query string + */ + protected function getAuthorizationQuery(array $params) + { + return $this->buildQueryString($params); + } + + /** + * Builds the authorization URL. + * + * @param array $options + * @return string Authorization URL + */ + public function getAuthorizationUrl(array $options = []) + { + $base = $this->getBaseAuthorizationUrl(); + $params = $this->getAuthorizationParameters($options); + $query = $this->getAuthorizationQuery($params); + + return $this->appendQuery($base, $query); + } + + /** + * Redirects the client for authorization. + * + * @param array $options + * @param callable|null $redirectHandler + * @return mixed + */ + public function authorize( + array $options = [], + callable $redirectHandler = null + ) { + $url = $this->getAuthorizationUrl($options); + if ($redirectHandler) { + return $redirectHandler($url, $this); + } + + // @codeCoverageIgnoreStart + header('Location: ' . $url); + exit; + // @codeCoverageIgnoreEnd + } + + /** + * Appends a query string to a URL. + * + * @param string $url The URL to append the query to + * @param string $query The HTTP query string + * @return string The resulting URL + */ + protected function appendQuery($url, $query) + { + $query = trim($query, '?&'); + + if ($query) { + $glue = strstr($url, '?') === false ? '?' : '&'; + return $url . $glue . $query; + } + + return $url; + } + + /** + * Returns the method to use when requesting an access token. + * + * @return string HTTP method + */ + protected function getAccessTokenMethod() + { + return self::METHOD_POST; + } + + /** + * Returns the key used in the access token response to identify the resource owner. + * + * @return string|null Resource owner identifier key + */ + protected function getAccessTokenResourceOwnerId() + { + return static::ACCESS_TOKEN_RESOURCE_OWNER_ID; + } + + /** + * Builds the access token URL's query string. + * + * @param array $params Query parameters + * @return string Query string + */ + protected function getAccessTokenQuery(array $params) + { + return $this->buildQueryString($params); + } + + /** + * Checks that a provided grant is valid, or attempts to produce one if the + * provided grant is a string. + * + * @param AbstractGrant|string $grant + * @return AbstractGrant + */ + protected function verifyGrant($grant) + { + if (is_string($grant)) { + return $this->grantFactory->getGrant($grant); + } + + $this->grantFactory->checkGrant($grant); + return $grant; + } + + /** + * Returns the full URL to use when requesting an access token. + * + * @param array $params Query parameters + * @return string + */ + protected function getAccessTokenUrl(array $params) + { + $url = $this->getBaseAccessTokenUrl($params); + + if ($this->getAccessTokenMethod() === self::METHOD_GET) { + $query = $this->getAccessTokenQuery($params); + return $this->appendQuery($url, $query); + } + + return $url; + } + + /** + * Returns a prepared request for requesting an access token. + * + * @param array $params Query string parameters + * @return RequestInterface + */ + protected function getAccessTokenRequest(array $params) + { + $method = $this->getAccessTokenMethod(); + $url = $this->getAccessTokenUrl($params); + $options = $this->optionProvider->getAccessTokenOptions($this->getAccessTokenMethod(), $params); + + return $this->getRequest($method, $url, $options); + } + + /** + * Requests an access token using a specified grant and option set. + * + * @param mixed $grant + * @param array $options + * @throws IdentityProviderException + * @return AccessTokenInterface + */ + public function getAccessToken($grant, array $options = []) + { + $grant = $this->verifyGrant($grant); + + $params = [ + 'client_id' => $this->clientId, + 'client_secret' => $this->clientSecret, + 'redirect_uri' => $this->redirectUri, + ]; + + if (!empty($this->pkceCode)) { + $params['code_verifier'] = $this->pkceCode; + } + + $params = $grant->prepareRequestParameters($params, $options); + $request = $this->getAccessTokenRequest($params); + $response = $this->getParsedResponse($request); + if (false === is_array($response)) { + throw new UnexpectedValueException( + 'Invalid response received from Authorization Server. Expected JSON.' + ); + } + $prepared = $this->prepareAccessTokenResponse($response); + $token = $this->createAccessToken($prepared, $grant); + + return $token; + } + + /** + * Returns a PSR-7 request instance that is not authenticated. + * + * @param string $method + * @param string $url + * @param array $options + * @return RequestInterface + */ + public function getRequest($method, $url, array $options = []) + { + return $this->createRequest($method, $url, null, $options); + } + + /** + * Returns an authenticated PSR-7 request instance. + * + * @param string $method + * @param string $url + * @param AccessTokenInterface|string|null $token + * @param array $options Any of "headers", "body", and "protocolVersion". + * @return RequestInterface + */ + public function getAuthenticatedRequest($method, $url, $token, array $options = []) + { + return $this->createRequest($method, $url, $token, $options); + } + + /** + * Creates a PSR-7 request instance. + * + * @param string $method + * @param string $url + * @param AccessTokenInterface|string|null $token + * @param array $options + * @return RequestInterface + */ + protected function createRequest($method, $url, $token, array $options) + { + $defaults = [ + 'headers' => $this->getHeaders($token), + ]; + + $options = array_merge_recursive($defaults, $options); + $factory = $this->getRequestFactory(); + + return $factory->getRequestWithOptions($method, $url, $options); + } + + /** + * Sends a request instance and returns a response instance. + * + * WARNING: This method does not attempt to catch exceptions caused by HTTP + * errors! It is recommended to wrap this method in a try/catch block. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + public function getResponse(RequestInterface $request) + { + return $this->getHttpClient()->send($request); + } + + /** + * Sends a request and returns the parsed response. + * + * @param RequestInterface $request + * @throws IdentityProviderException + * @return mixed + */ + public function getParsedResponse(RequestInterface $request) + { + try { + $response = $this->getResponse($request); + } catch (BadResponseException $e) { + $response = $e->getResponse(); + } + + $parsed = $this->parseResponse($response); + + $this->checkResponse($response, $parsed); + + return $parsed; + } + + /** + * Attempts to parse a JSON response. + * + * @param string $content JSON content from response body + * @return array Parsed JSON data + * @throws UnexpectedValueException if the content could not be parsed + */ + protected function parseJson($content) + { + $content = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new UnexpectedValueException(sprintf( + "Failed to parse JSON response: %s", + json_last_error_msg() + )); + } + + return $content; + } + + /** + * Returns the content type header of a response. + * + * @param ResponseInterface $response + * @return string Semi-colon separated join of content-type headers. + */ + protected function getContentType(ResponseInterface $response) + { + return join(';', (array) $response->getHeader('content-type')); + } + + /** + * Parses the response according to its content-type header. + * + * @throws UnexpectedValueException + * @param ResponseInterface $response + * @return array + */ + protected function parseResponse(ResponseInterface $response) + { + $content = (string) $response->getBody(); + $type = $this->getContentType($response); + + if (strpos($type, 'urlencoded') !== false) { + parse_str($content, $parsed); + return $parsed; + } + + // Attempt to parse the string as JSON regardless of content type, + // since some providers use non-standard content types. Only throw an + // exception if the JSON could not be parsed when it was expected to. + try { + return $this->parseJson($content); + } catch (UnexpectedValueException $e) { + if (strpos($type, 'json') !== false) { + throw $e; + } + + if ($response->getStatusCode() == 500) { + throw new UnexpectedValueException( + 'An OAuth server error was encountered that did not contain a JSON body', + 0, + $e + ); + } + + return $content; + } + } + + /** + * Checks a provider response for errors. + * + * @throws IdentityProviderException + * @param ResponseInterface $response + * @param array|string $data Parsed response data + * @return void + */ + abstract protected function checkResponse(ResponseInterface $response, $data); + + /** + * Prepares an parsed access token response for a grant. + * + * Custom mapping of expiration, etc should be done here. Always call the + * parent method when overloading this method. + * + * @param mixed $result + * @return array + */ + protected function prepareAccessTokenResponse(array $result) + { + if ($this->getAccessTokenResourceOwnerId() !== null) { + $result['resource_owner_id'] = $this->getValueByKey( + $result, + $this->getAccessTokenResourceOwnerId() + ); + } + return $result; + } + + /** + * Creates an access token from a response. + * + * The grant that was used to fetch the response can be used to provide + * additional context. + * + * @param array $response + * @param AbstractGrant $grant + * @return AccessTokenInterface + */ + protected function createAccessToken(array $response, AbstractGrant $grant) + { + return new AccessToken($response); + } + + /** + * Generates a resource owner object from a successful resource owner + * details request. + * + * @param array $response + * @param AccessToken $token + * @return ResourceOwnerInterface + */ + abstract protected function createResourceOwner(array $response, AccessToken $token); + + /** + * Requests and returns the resource owner of given access token. + * + * @param AccessToken $token + * @return ResourceOwnerInterface + */ + public function getResourceOwner(AccessToken $token) + { + $response = $this->fetchResourceOwnerDetails($token); + + return $this->createResourceOwner($response, $token); + } + + /** + * Requests resource owner details. + * + * @param AccessToken $token + * @return mixed + */ + protected function fetchResourceOwnerDetails(AccessToken $token) + { + $url = $this->getResourceOwnerDetailsUrl($token); + + $request = $this->getAuthenticatedRequest(self::METHOD_GET, $url, $token); + + $response = $this->getParsedResponse($request); + + if (false === is_array($response)) { + throw new UnexpectedValueException( + 'Invalid response received from Authorization Server. Expected JSON.' + ); + } + + return $response; + } + + /** + * Returns the default headers used by this provider. + * + * Typically this is used to set 'Accept' or 'Content-Type' headers. + * + * @return array + */ + protected function getDefaultHeaders() + { + return []; + } + + /** + * Returns the authorization headers used by this provider. + * + * Typically this is "Bearer" or "MAC". For more information see: + * http://tools.ietf.org/html/rfc6749#section-7.1 + * + * No default is provided, providers must overload this method to activate + * authorization headers. + * + * @param mixed|null $token Either a string or an access token instance + * @return array + */ + protected function getAuthorizationHeaders($token = null) + { + return []; + } + + /** + * Returns all headers used by this provider for a request. + * + * The request will be authenticated if an access token is provided. + * + * @param mixed|null $token object or string + * @return array + */ + public function getHeaders($token = null) + { + if ($token) { + return array_merge( + $this->getDefaultHeaders(), + $this->getAuthorizationHeaders($token) + ); + } + + return $this->getDefaultHeaders(); + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php new file mode 100644 index 0000000000..55cb438fb1 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php @@ -0,0 +1,48 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider\Exception; + +/** + * Exception thrown if the provider response contains errors. + */ +class IdentityProviderException extends \Exception +{ + /** + * @var mixed + */ + protected $response; + + /** + * @param string $message + * @param int $code + * @param mixed $response The response body + */ + public function __construct($message, $code, $response) + { + $this->response = $response; + + parent::__construct($message, $code); + } + + /** + * Returns the exception's response body. + * + * @return mixed + */ + public function getResponseBody() + { + return $this->response; + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/GenericProvider.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/GenericProvider.php new file mode 100644 index 0000000000..0fc95f2502 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/GenericProvider.php @@ -0,0 +1,247 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +use InvalidArgumentException; +use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use League\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Tool\BearerAuthorizationTrait; +use Psr\Http\Message\ResponseInterface; + +/** + * Represents a generic service provider that may be used to interact with any + * OAuth 2.0 service provider, using Bearer token authentication. + */ +class GenericProvider extends AbstractProvider +{ + use BearerAuthorizationTrait; + + /** + * @var string + */ + private $urlAuthorize; + + /** + * @var string + */ + private $urlAccessToken; + + /** + * @var string + */ + private $urlResourceOwnerDetails; + + /** + * @var string + */ + private $accessTokenMethod; + + /** + * @var string + */ + private $accessTokenResourceOwnerId; + + /** + * @var array|null + */ + private $scopes = null; + + /** + * @var string + */ + private $scopeSeparator; + + /** + * @var string + */ + private $responseError = 'error'; + + /** + * @var string + */ + private $responseCode; + + /** + * @var string + */ + private $responseResourceOwnerId = 'id'; + + /** + * @var string|null + */ + private $pkceMethod = null; + + /** + * @param array $options + * @param array $collaborators + */ + public function __construct(array $options = [], array $collaborators = []) + { + $this->assertRequiredOptions($options); + + $possible = $this->getConfigurableOptions(); + $configured = array_intersect_key($options, array_flip($possible)); + + foreach ($configured as $key => $value) { + $this->$key = $value; + } + + // Remove all options that are only used locally + $options = array_diff_key($options, $configured); + + parent::__construct($options, $collaborators); + } + + /** + * Returns all options that can be configured. + * + * @return array + */ + protected function getConfigurableOptions() + { + return array_merge($this->getRequiredOptions(), [ + 'accessTokenMethod', + 'accessTokenResourceOwnerId', + 'scopeSeparator', + 'responseError', + 'responseCode', + 'responseResourceOwnerId', + 'scopes', + 'pkceMethod', + ]); + } + + /** + * Returns all options that are required. + * + * @return array + */ + protected function getRequiredOptions() + { + return [ + 'urlAuthorize', + 'urlAccessToken', + 'urlResourceOwnerDetails', + ]; + } + + /** + * Verifies that all required options have been passed. + * + * @param array $options + * @return void + * @throws InvalidArgumentException + */ + private function assertRequiredOptions(array $options) + { + $missing = array_diff_key(array_flip($this->getRequiredOptions()), $options); + + if (!empty($missing)) { + throw new InvalidArgumentException( + 'Required options not defined: ' . implode(', ', array_keys($missing)) + ); + } + } + + /** + * @inheritdoc + */ + public function getBaseAuthorizationUrl() + { + return $this->urlAuthorize; + } + + /** + * @inheritdoc + */ + public function getBaseAccessTokenUrl(array $params) + { + return $this->urlAccessToken; + } + + /** + * @inheritdoc + */ + public function getResourceOwnerDetailsUrl(AccessToken $token) + { + return $this->urlResourceOwnerDetails; + } + + /** + * @inheritdoc + */ + public function getDefaultScopes() + { + return $this->scopes; + } + + /** + * @inheritdoc + */ + protected function getAccessTokenMethod() + { + return $this->accessTokenMethod ?: parent::getAccessTokenMethod(); + } + + /** + * @inheritdoc + */ + protected function getAccessTokenResourceOwnerId() + { + return $this->accessTokenResourceOwnerId ?: parent::getAccessTokenResourceOwnerId(); + } + + /** + * @inheritdoc + */ + protected function getScopeSeparator() + { + return $this->scopeSeparator ?: parent::getScopeSeparator(); + } + + /** + * @inheritdoc + */ + protected function getPkceMethod() + { + return $this->pkceMethod ?: parent::getPkceMethod(); + } + + /** + * @inheritdoc + */ + protected function checkResponse(ResponseInterface $response, $data) + { + if (!empty($data[$this->responseError])) { + $error = $data[$this->responseError]; + if (!is_string($error)) { + $error = var_export($error, true); + } + $code = $this->responseCode && !empty($data[$this->responseCode])? $data[$this->responseCode] : 0; + if (!is_int($code)) { + $code = intval($code); + } + throw new IdentityProviderException($error, $code, $data); + } + } + + /** + * @inheritdoc + */ + protected function createResourceOwner(array $response, AccessToken $token) + { + return new GenericResourceOwner($response, $this->responseResourceOwnerId); + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/GenericResourceOwner.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/GenericResourceOwner.php new file mode 100644 index 0000000000..f876614851 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/GenericResourceOwner.php @@ -0,0 +1,61 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +/** + * Represents a generic resource owner for use with the GenericProvider. + */ +class GenericResourceOwner implements ResourceOwnerInterface +{ + /** + * @var array + */ + protected $response; + + /** + * @var string + */ + protected $resourceOwnerId; + + /** + * @param array $response + * @param string $resourceOwnerId + */ + public function __construct(array $response, $resourceOwnerId) + { + $this->response = $response; + $this->resourceOwnerId = $resourceOwnerId; + } + + /** + * Returns the identifier of the authorized resource owner. + * + * @return mixed + */ + public function getId() + { + return $this->response[$this->resourceOwnerId]; + } + + /** + * Returns the raw resource owner response. + * + * @return array + */ + public function toArray() + { + return $this->response; + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/ResourceOwnerInterface.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/ResourceOwnerInterface.php new file mode 100644 index 0000000000..828442425f --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/ResourceOwnerInterface.php @@ -0,0 +1,36 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +/** + * Classes implementing `ResourceOwnerInterface` may be used to represent + * the resource owner authenticated with a service provider. + */ +interface ResourceOwnerInterface +{ + /** + * Returns the identifier of the authorized resource owner. + * + * @return mixed + */ + public function getId(); + + /** + * Return all of the owner details available as an array. + * + * @return array + */ + public function toArray(); +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Token/AccessToken.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Token/AccessToken.php new file mode 100644 index 0000000000..81533c3072 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Token/AccessToken.php @@ -0,0 +1,243 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Token; + +use InvalidArgumentException; +use RuntimeException; + +/** + * Represents an access token. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.4 Access Token (RFC 6749, ยง1.4) + */ +class AccessToken implements AccessTokenInterface, ResourceOwnerAccessTokenInterface +{ + /** + * @var string + */ + protected $accessToken; + + /** + * @var int + */ + protected $expires; + + /** + * @var string + */ + protected $refreshToken; + + /** + * @var string + */ + protected $resourceOwnerId; + + /** + * @var array + */ + protected $values = []; + + /** + * @var int + */ + private static $timeNow; + + /** + * Set the time now. This should only be used for testing purposes. + * + * @param int $timeNow the time in seconds since epoch + * @return void + */ + public static function setTimeNow($timeNow) + { + self::$timeNow = $timeNow; + } + + /** + * Reset the time now if it was set for test purposes. + * + * @return void + */ + public static function resetTimeNow() + { + self::$timeNow = null; + } + + /** + * @return int + */ + public function getTimeNow() + { + return self::$timeNow ? self::$timeNow : time(); + } + + /** + * Constructs an access token. + * + * @param array $options An array of options returned by the service provider + * in the access token request. The `access_token` option is required. + * @throws InvalidArgumentException if `access_token` is not provided in `$options`. + */ + public function __construct(array $options = []) + { + if (empty($options['access_token'])) { + throw new InvalidArgumentException('Required option not passed: "access_token"'); + } + + $this->accessToken = $options['access_token']; + + if (!empty($options['resource_owner_id'])) { + $this->resourceOwnerId = $options['resource_owner_id']; + } + + if (!empty($options['refresh_token'])) { + $this->refreshToken = $options['refresh_token']; + } + + // We need to know when the token expires. Show preference to + // 'expires_in' since it is defined in RFC6749 Section 5.1. + // Defer to 'expires' if it is provided instead. + if (isset($options['expires_in'])) { + if (!is_numeric($options['expires_in'])) { + throw new \InvalidArgumentException('expires_in value must be an integer'); + } + + $this->expires = $options['expires_in'] != 0 ? $this->getTimeNow() + $options['expires_in'] : 0; + } elseif (!empty($options['expires'])) { + // Some providers supply the seconds until expiration rather than + // the exact timestamp. Take a best guess at which we received. + $expires = $options['expires']; + + if (!$this->isExpirationTimestamp($expires)) { + $expires += $this->getTimeNow(); + } + + $this->expires = $expires; + } + + // Capture any additional values that might exist in the token but are + // not part of the standard response. Vendors will sometimes pass + // additional user data this way. + $this->values = array_diff_key($options, array_flip([ + 'access_token', + 'resource_owner_id', + 'refresh_token', + 'expires_in', + 'expires', + ])); + } + + /** + * Check if a value is an expiration timestamp or second value. + * + * @param integer $value + * @return bool + */ + protected function isExpirationTimestamp($value) + { + // If the given value is larger than the original OAuth 2 draft date, + // assume that it is meant to be a (possible expired) timestamp. + $oauth2InceptionDate = 1349067600; // 2012-10-01 + return ($value > $oauth2InceptionDate); + } + + /** + * @inheritdoc + */ + public function getToken() + { + return $this->accessToken; + } + + /** + * @inheritdoc + */ + public function getRefreshToken() + { + return $this->refreshToken; + } + + /** + * @inheritdoc + */ + public function getExpires() + { + return $this->expires; + } + + /** + * @inheritdoc + */ + public function getResourceOwnerId() + { + return $this->resourceOwnerId; + } + + /** + * @inheritdoc + */ + public function hasExpired() + { + $expires = $this->getExpires(); + + if (empty($expires)) { + throw new RuntimeException('"expires" is not set on the token'); + } + + return $expires < time(); + } + + /** + * @inheritdoc + */ + public function getValues() + { + return $this->values; + } + + /** + * @inheritdoc + */ + public function __toString() + { + return (string) $this->getToken(); + } + + /** + * @inheritdoc + */ + public function jsonSerialize() + { + $parameters = $this->values; + + if ($this->accessToken) { + $parameters['access_token'] = $this->accessToken; + } + + if ($this->refreshToken) { + $parameters['refresh_token'] = $this->refreshToken; + } + + if ($this->expires) { + $parameters['expires'] = $this->expires; + } + + if ($this->resourceOwnerId) { + $parameters['resource_owner_id'] = $this->resourceOwnerId; + } + + return $parameters; + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Token/AccessTokenInterface.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Token/AccessTokenInterface.php new file mode 100644 index 0000000000..5fd219ffca --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Token/AccessTokenInterface.php @@ -0,0 +1,74 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Token; + +use JsonSerializable; +use ReturnTypeWillChange; +use RuntimeException; + +interface AccessTokenInterface extends JsonSerializable +{ + /** + * Returns the access token string of this instance. + * + * @return string + */ + public function getToken(); + + /** + * Returns the refresh token, if defined. + * + * @return string|null + */ + public function getRefreshToken(); + + /** + * Returns the expiration timestamp in seconds, if defined. + * + * @return integer|null + */ + public function getExpires(); + + /** + * Checks if this token has expired. + * + * @return boolean true if the token has expired, false otherwise. + * @throws RuntimeException if 'expires' is not set on the token. + */ + public function hasExpired(); + + /** + * Returns additional vendor values stored in the token. + * + * @return array + */ + public function getValues(); + + /** + * Returns a string representation of the access token + * + * @return string + */ + public function __toString(); + + /** + * Returns an array of parameters to serialize when this is serialized with + * json_encode(). + * + * @return array + */ + #[ReturnTypeWillChange] + public function jsonSerialize(); +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php new file mode 100644 index 0000000000..51e4ce4139 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php @@ -0,0 +1,25 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Token; + +interface ResourceOwnerAccessTokenInterface extends AccessTokenInterface +{ + /** + * Returns the resource owner identifier, if defined. + * + * @return string|null + */ + public function getResourceOwnerId(); +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/ArrayAccessorTrait.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/ArrayAccessorTrait.php new file mode 100644 index 0000000000..a18198cf30 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/ArrayAccessorTrait.php @@ -0,0 +1,52 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +/** + * Provides generic array navigation tools. + */ +trait ArrayAccessorTrait +{ + /** + * Returns a value by key using dot notation. + * + * @param array $data + * @param string $key + * @param mixed|null $default + * @return mixed + */ + private function getValueByKey(array $data, $key, $default = null) + { + if (!is_string($key) || empty($key) || !count($data)) { + return $default; + } + + if (strpos($key, '.') !== false) { + $keys = explode('.', $key); + + foreach ($keys as $innerKey) { + if (!is_array($data) || !array_key_exists($innerKey, $data)) { + return $default; + } + + $data = $data[$innerKey]; + } + + return $data; + } + + return array_key_exists($key, $data) ? $data[$key] : $default; + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php new file mode 100644 index 0000000000..081c7c861e --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php @@ -0,0 +1,36 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use League\OAuth2\Client\Token\AccessTokenInterface; + +/** + * Enables `Bearer` header authorization for providers. + * + * @link http://tools.ietf.org/html/rfc6750 Bearer Token Usage (RFC 6750) + */ +trait BearerAuthorizationTrait +{ + /** + * Returns authorization headers for the 'bearer' grant. + * + * @param AccessTokenInterface|string|null $token Either a string or an access token instance + * @return array + */ + protected function getAuthorizationHeaders($token = null) + { + return ['Authorization' => 'Bearer ' . $token]; + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/GuardedPropertyTrait.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/GuardedPropertyTrait.php new file mode 100644 index 0000000000..02c9ba5fb6 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/GuardedPropertyTrait.php @@ -0,0 +1,70 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +/** + * Provides support for blacklisting explicit properties from the + * mass assignment behavior. + */ +trait GuardedPropertyTrait +{ + /** + * The properties that aren't mass assignable. + * + * @var array + */ + protected $guarded = []; + + /** + * Attempts to mass assign the given options to explicitly defined properties, + * skipping over any properties that are defined in the guarded array. + * + * @param array $options + * @return mixed + */ + protected function fillProperties(array $options = []) + { + if (isset($options['guarded'])) { + unset($options['guarded']); + } + + foreach ($options as $option => $value) { + if (property_exists($this, $option) && !$this->isGuarded($option)) { + $this->{$option} = $value; + } + } + } + + /** + * Returns current guarded properties. + * + * @return array + */ + public function getGuarded() + { + return $this->guarded; + } + + /** + * Determines if the given property is guarded. + * + * @param string $property + * @return bool + */ + public function isGuarded($property) + { + return in_array($property, $this->getGuarded()); + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/MacAuthorizationTrait.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/MacAuthorizationTrait.php new file mode 100644 index 0000000000..f8dcd77c54 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/MacAuthorizationTrait.php @@ -0,0 +1,83 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use League\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Token\AccessTokenInterface; + +/** + * Enables `MAC` header authorization for providers. + * + * @link http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-05 Message Authentication Code (MAC) Tokens + */ +trait MacAuthorizationTrait +{ + /** + * Returns the id of this token for MAC generation. + * + * @param AccessToken $token + * @return string + */ + abstract protected function getTokenId(AccessToken $token); + + /** + * Returns the MAC signature for the current request. + * + * @param string $id + * @param integer $ts + * @param string $nonce + * @return string + */ + abstract protected function getMacSignature($id, $ts, $nonce); + + /** + * Returns a new random string to use as the state parameter in an + * authorization flow. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + abstract protected function getRandomState($length = 32); + + /** + * Returns the authorization headers for the 'mac' grant. + * + * @param AccessTokenInterface|string|null $token Either a string or an access token instance + * @return array + * @codeCoverageIgnore + * + * @todo This is currently untested and provided only as an example. If you + * complete the implementation, please create a pull request for + * https://github.com/thephpleague/oauth2-client + */ + protected function getAuthorizationHeaders($token = null) + { + if ($token === null) { + return []; + } + + $ts = time(); + $id = $this->getTokenId($token); + $nonce = $this->getRandomState(16); + $mac = $this->getMacSignature($id, $ts, $nonce); + + $parts = []; + foreach (compact('id', 'ts', 'nonce', 'mac') as $key => $value) { + $parts[] = sprintf('%s="%s"', $key, $value); + } + + return ['Authorization' => 'MAC ' . implode(', ', $parts)]; + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/ProviderRedirectTrait.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/ProviderRedirectTrait.php new file mode 100644 index 0000000000..f81b511f9e --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/ProviderRedirectTrait.php @@ -0,0 +1,122 @@ +redirectLimit) { + $attempts++; + $response = $this->getHttpClient()->send($request, [ + 'allow_redirects' => false + ]); + + if ($this->isRedirect($response)) { + $redirectUrl = new Uri($response->getHeader('Location')[0]); + $request = $request->withUri($redirectUrl); + } else { + break; + } + } + + return $response; + } + + /** + * Returns the HTTP client instance. + * + * @return GuzzleHttp\ClientInterface + */ + abstract public function getHttpClient(); + + /** + * Retrieves current redirect limit. + * + * @return integer + */ + public function getRedirectLimit() + { + return $this->redirectLimit; + } + + /** + * Determines if a given response is a redirect. + * + * @param ResponseInterface $response + * + * @return boolean + */ + protected function isRedirect(ResponseInterface $response) + { + $statusCode = $response->getStatusCode(); + + return $statusCode > 300 && $statusCode < 400 && $response->hasHeader('Location'); + } + + /** + * Sends a request instance and returns a response instance. + * + * WARNING: This method does not attempt to catch exceptions caused by HTTP + * errors! It is recommended to wrap this method in a try/catch block. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + public function getResponse(RequestInterface $request) + { + try { + $response = $this->followRequestRedirects($request); + } catch (BadResponseException $e) { + $response = $e->getResponse(); + } + + return $response; + } + + /** + * Updates the redirect limit. + * + * @param integer $limit + * @return League\OAuth2\Client\Provider\AbstractProvider + * @throws InvalidArgumentException + */ + public function setRedirectLimit($limit) + { + if (!is_int($limit)) { + throw new InvalidArgumentException('redirectLimit must be an integer.'); + } + + if ($limit < 1) { + throw new InvalidArgumentException('redirectLimit must be greater than or equal to one.'); + } + + $this->redirectLimit = $limit; + + return $this; + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/QueryBuilderTrait.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/QueryBuilderTrait.php new file mode 100644 index 0000000000..bdda3e79ef --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/QueryBuilderTrait.php @@ -0,0 +1,33 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +/** + * Provides a standard way to generate query strings. + */ +trait QueryBuilderTrait +{ + /** + * Build a query string from an array. + * + * @param array $params + * + * @return string + */ + protected function buildQueryString(array $params) + { + return http_build_query($params, '', '&', \PHP_QUERY_RFC3986); + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/RequestFactory.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/RequestFactory.php new file mode 100644 index 0000000000..1af434297f --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/RequestFactory.php @@ -0,0 +1,87 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use GuzzleHttp\Psr7\Request; + +/** + * Used to produce PSR-7 Request instances. + * + * @link https://github.com/guzzle/guzzle/pull/1101 + */ +class RequestFactory +{ + /** + * Creates a PSR-7 Request instance. + * + * @param null|string $method HTTP method for the request. + * @param null|string $uri URI for the request. + * @param array $headers Headers for the message. + * @param string|resource|StreamInterface $body Message body. + * @param string $version HTTP protocol version. + * + * @return Request + */ + public function getRequest( + $method, + $uri, + array $headers = [], + $body = null, + $version = '1.1' + ) { + return new Request($method, $uri, $headers, $body, $version); + } + + /** + * Parses simplified options. + * + * @param array $options Simplified options. + * + * @return array Extended options for use with getRequest. + */ + protected function parseOptions(array $options) + { + // Should match default values for getRequest + $defaults = [ + 'headers' => [], + 'body' => null, + 'version' => '1.1', + ]; + + return array_merge($defaults, $options); + } + + /** + * Creates a request using a simplified array of options. + * + * @param null|string $method + * @param null|string $uri + * @param array $options + * + * @return Request + */ + public function getRequestWithOptions($method, $uri, array $options = []) + { + $options = $this->parseOptions($options); + + return $this->getRequest( + $method, + $uri, + $options['headers'], + $options['body'], + $options['version'] + ); + } +} diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/RequiredParameterTrait.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/RequiredParameterTrait.php new file mode 100644 index 0000000000..47da977177 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Tool/RequiredParameterTrait.php @@ -0,0 +1,56 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use BadMethodCallException; + +/** + * Provides functionality to check for required parameters. + */ +trait RequiredParameterTrait +{ + /** + * Checks for a required parameter in a hash. + * + * @throws BadMethodCallException + * @param string $name + * @param array $params + * @return void + */ + private function checkRequiredParameter($name, array $params) + { + if (!isset($params[$name])) { + throw new BadMethodCallException(sprintf( + 'Required parameter not passed: "%s"', + $name + )); + } + } + + /** + * Checks for multiple required parameters in a hash. + * + * @throws InvalidArgumentException + * @param array $names + * @param array $params + * @return void + */ + private function checkRequiredParameters(array $names, array $params) + { + foreach ($names as $name) { + $this->checkRequiredParameter($name, $params); + } + } +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/.github/FUNDING.yml b/data/web/inc/lib/vendor/matthiasmullie/minify/.github/FUNDING.yml new file mode 100644 index 0000000000..9b9868875f --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [matthiasmullie] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/CONTRIBUTING.md b/data/web/inc/lib/vendor/matthiasmullie/minify/CONTRIBUTING.md new file mode 100644 index 0000000000..226cf976b5 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# How to contribute + + +## Issues + +When [filing bugs](https://github.com/matthiasmullie/minify/issues/new), +try to be as thorough as possible: +* What version did you use? +* What did you try to do? ***Please post the relevant parts of your code.*** +* What went wrong? ***Please include error messages, if any.*** +* What was the expected result? + + +## Pull requests + +Bug fixes and general improvements to the existing codebase are always welcome. +New features are also welcome, but will be judged on an individual basis. If +you'd rather not risk wasting your time implementing a new feature only to see +it turned down, please start the discussion by +[opening an issue](https://github.com/matthiasmullie/minify/issues/new). + +Don't forget to add your changes to the [changelog](CHANGELOG.md). + + +### Testing + +Please include tests for every change or addition to the code. +To run the complete test suite: + +```sh +vendor/bin/phpunit +``` + +When submitting a new pull request, please make sure that that the test suite +passes (Travis CI will run it & report back on your pull request.) + +To run the tests on Windows, run `tests/convert_symlinks_to_windows_style.sh` +from the command line in order to convert Linux-style test symlinks to +Windows-style. + + +### Coding standards + +All code must follow [PSR-2](http://www.php-fig.org/psr/psr-2/). Just make sure +to run php-cs-fixer before submitting the code, it'll take care of the +formatting for you: + +```sh +vendor/bin/php-cs-fixer fix src +vendor/bin/php-cs-fixer fix tests +``` + +Document the code thoroughly! + + +## License + +Note that minify is MIT-licensed, which basically allows anyone to do +anything they like with it, without restriction. diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/Dockerfile b/data/web/inc/lib/vendor/matthiasmullie/minify/Dockerfile new file mode 100644 index 0000000000..5917e05f42 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/Dockerfile @@ -0,0 +1,13 @@ +ARG version=cli +FROM php:$version + +COPY . /var/www +WORKDIR /var/www + +RUN apt-get update +RUN apt-get install -y zip unzip libzip-dev git +RUN docker-php-ext-install zip +RUN docker-php-ext-install pcntl +RUN curl -sS https://getcomposer.org/installer | php +RUN mv composer.phar /usr/local/bin/composer +RUN composer install diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/LICENSE b/data/web/inc/lib/vendor/matthiasmullie/minify/LICENSE new file mode 100644 index 0000000000..0c0d08a79b --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2012 Matthias Mullie + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/bin/minifycss b/data/web/inc/lib/vendor/matthiasmullie/minify/bin/minifycss new file mode 100755 index 0000000000..6a681a850f --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/bin/minifycss @@ -0,0 +1,45 @@ +#!/usr/bin/env php +minify(); +} catch (Exception $e) { + fwrite(STDERR, $e->getMessage(), PHP_EOL); + exit(1); +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/bin/minifyjs b/data/web/inc/lib/vendor/matthiasmullie/minify/bin/minifyjs new file mode 100755 index 0000000000..4cbe63ff87 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/bin/minifyjs @@ -0,0 +1,45 @@ +#!/usr/bin/env php +minify(); +} catch (Exception $e) { + fwrite(STDERR, $e->getMessage(), PHP_EOL); + exit(1); +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/composer.json b/data/web/inc/lib/vendor/matthiasmullie/minify/composer.json new file mode 100644 index 0000000000..a6dd4ab6ac --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/composer.json @@ -0,0 +1,38 @@ +{ + "name": "matthiasmullie/minify", + "type": "library", + "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", + "keywords": ["minify", "minifier", "css", "js", "javascript"], + "homepage": "http://www.minifier.org", + "license": "MIT", + "authors": [ + { + "name": "Matthias Mullie", + "homepage": "http://www.mullie.eu", + "email": "minify@mullie.eu", + "role": "Developer" + } + ], + "require": { + "php": ">=5.3.0", + "ext-pcre": "*", + "matthiasmullie/path-converter": "~1.1" + }, + "require-dev": { + "matthiasmullie/scrapbook": "dev-master", + "phpunit/phpunit": ">=4.8", + "friendsofphp/php-cs-fixer": "~2.0" + }, + "suggest": { + "psr/cache-implementation": "Cache implementation to use with Minify::cache" + }, + "autoload": { + "psr-4": { + "MatthiasMullie\\Minify\\": "src/" + } + }, + "bin": [ + "bin/minifycss", + "bin/minifyjs" + ] +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_after.txt b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_after.txt new file mode 100644 index 0000000000..5c8cba7f35 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_after.txt @@ -0,0 +1,7 @@ +in +public +extends +private +protected +implements +instanceof \ No newline at end of file diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_before.txt b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_before.txt new file mode 100644 index 0000000000..5abf35799b --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_before.txt @@ -0,0 +1,26 @@ +do +in +let +new +var +case +else +enum +void +with +class +const +yield +delete +export +import +public +static +typeof +extends +package +private +function +protected +implements +instanceof \ No newline at end of file diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_reserved.txt b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_reserved.txt new file mode 100644 index 0000000000..2a3ad3c0eb --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/keywords_reserved.txt @@ -0,0 +1,63 @@ +do +if +in +for +let +new +try +var +case +else +enum +eval +null +this +true +void +with +break +catch +class +const +false +super +throw +while +yield +delete +export +import +public +return +static +switch +typeof +default +extends +finally +package +private +continue +debugger +function +arguments +interface +protected +implements +instanceof +abstract +boolean +byte +char +double +final +float +goto +int +long +native +short +synchronized +throws +transient +volatile \ No newline at end of file diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators.txt b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators.txt new file mode 100644 index 0000000000..e66229ae1c --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators.txt @@ -0,0 +1,46 @@ ++ +- +* +/ +% += ++= +-= +*= +/= +%= +<<= +>>= +>>>= +&= +^= +|= +& +| +^ +~ +<< +>> +>>> +== +=== +!= +!== +> +< +>= +<= +&& +|| +! +. +[ +] +? +: +, +; +( +) +{ +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators_after.txt b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators_after.txt new file mode 100644 index 0000000000..71a9b7091e --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators_after.txt @@ -0,0 +1,43 @@ ++ +- +* +/ +% += ++= +-= +*= +/= +%= +<<= +>>= +>>>= +&= +^= +|= +& +| +^ +<< +>> +>>> +== +=== +!= +!== +> +< +>= +<= +&& +|| +. +[ +] +? +: +, +; +( +) +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators_before.txt b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators_before.txt new file mode 100644 index 0000000000..ff50d87033 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/data/js/operators_before.txt @@ -0,0 +1,43 @@ ++ +- +* +/ +% += ++= +-= +*= +/= +%= +<<= +>>= +>>>= +&= +^= +|= +& +| +^ +~ +<< +>> +>>> +== +=== +!= +!== +> +< +>= +<= +&& +|| +! +. +[ +? +: +, +; +( +{ diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/docker-compose.yml b/data/web/inc/lib/vendor/matthiasmullie/minify/docker-compose.yml new file mode 100644 index 0000000000..cb7ace1026 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/docker-compose.yml @@ -0,0 +1,46 @@ +version: '2.1' +services: + php: + build: + context: . + dockerfile: Dockerfile + volumes: + - ./src:/var/www/src + - ./data:/var/www/data + - ./tests:/var/www/tests + - ./phpunit.xml.dist:/var/www/phpunit.xml.dist + '8.0': + extends: php + build: + args: + version: 8.0-cli + '7.4': + extends: php + build: + args: + version: 7.4-cli + '7.3': + extends: php + build: + args: + version: 7.3-cli + '7.2': + extends: php + build: + args: + version: 7.2-cli + '7.1': + extends: php + build: + args: + version: 7.1-cli + '7.0': + extends: php + build: + args: + version: 7.0-cli + '5.6': + extends: php + build: + args: + version: 5.6-cli diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/CSS.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/CSS.php new file mode 100644 index 0000000000..eb98e52ed3 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/CSS.php @@ -0,0 +1,786 @@ + + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ + +namespace MatthiasMullie\Minify; + +use MatthiasMullie\Minify\Exceptions\FileImportException; +use MatthiasMullie\PathConverter\ConverterInterface; +use MatthiasMullie\PathConverter\Converter; + +/** + * CSS minifier + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @package Minify + * @author Matthias Mullie + * @author Tijs Verkoyen + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +class CSS extends Minify +{ + /** + * @var int maximum inport size in kB + */ + protected $maxImportSize = 5; + + /** + * @var string[] valid import extensions + */ + protected $importExtensions = array( + 'gif' => 'data:image/gif', + 'png' => 'data:image/png', + 'jpe' => 'data:image/jpeg', + 'jpg' => 'data:image/jpeg', + 'jpeg' => 'data:image/jpeg', + 'svg' => 'data:image/svg+xml', + 'woff' => 'data:application/x-font-woff', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'xbm' => 'image/x-xbitmap', + ); + + /** + * Set the maximum size if files to be imported. + * + * Files larger than this size (in kB) will not be imported into the CSS. + * Importing files into the CSS as data-uri will save you some connections, + * but we should only import relatively small decorative images so that our + * CSS file doesn't get too bulky. + * + * @param int $size Size in kB + */ + public function setMaxImportSize($size) + { + $this->maxImportSize = $size; + } + + /** + * Set the type of extensions to be imported into the CSS (to save network + * connections). + * Keys of the array should be the file extensions & respective values + * should be the data type. + * + * @param string[] $extensions Array of file extensions + */ + public function setImportExtensions(array $extensions) + { + $this->importExtensions = $extensions; + } + + /** + * Move any import statements to the top. + * + * @param string $content Nearly finished CSS content + * + * @return string + */ + protected function moveImportsToTop($content) + { + if (preg_match_all('/(;?)(@import (?url\()?(?P["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) { + // remove from content + foreach ($matches[0] as $import) { + $content = str_replace($import, '', $content); + } + + // add to top + $content = implode(';', $matches[2]).';'.trim($content, ';'); + } + + return $content; + } + + /** + * Combine CSS from import statements. + * + * @import's will be loaded and their content merged into the original file, + * to save HTTP requests. + * + * @param string $source The file to combine imports for + * @param string $content The CSS content to combine imports for + * @param string[] $parents Parent paths, for circular reference checks + * + * @return string + * + * @throws FileImportException + */ + protected function combineImports($source, $content, $parents) + { + $importRegexes = array( + // @import url(xxx) + '/ + # import statement + @import + + # whitespace + \s+ + + # open url() + url\( + + # (optional) open path enclosure + (?P["\']?) + + # fetch path + (?P.+?) + + # (optional) close path enclosure + (?P=quotes) + + # close url() + \) + + # (optional) trailing whitespace + \s* + + # (optional) media statement(s) + (?P[^;]*) + + # (optional) trailing whitespace + \s* + + # (optional) closing semi-colon + ;? + + /ix', + + // @import 'xxx' + '/ + + # import statement + @import + + # whitespace + \s+ + + # open path enclosure + (?P["\']) + + # fetch path + (?P.+?) + + # close path enclosure + (?P=quotes) + + # (optional) trailing whitespace + \s* + + # (optional) media statement(s) + (?P[^;]*) + + # (optional) trailing whitespace + \s* + + # (optional) closing semi-colon + ;? + + /ix', + ); + + // find all relative imports in css + $matches = array(); + foreach ($importRegexes as $importRegex) { + if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) { + $matches = array_merge($matches, $regexMatches); + } + } + + $search = array(); + $replace = array(); + + // loop the matches + foreach ($matches as $match) { + // get the path for the file that will be imported + $importPath = dirname($source).'/'.$match['path']; + + // only replace the import with the content if we can grab the + // content of the file + if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) { + continue; + } + + // check if current file was not imported previously in the same + // import chain. + if (in_array($importPath, $parents)) { + throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.'); + } + + // grab referenced file & minify it (which may include importing + // yet other @import statements recursively) + $minifier = new self($importPath); + $minifier->setMaxImportSize($this->maxImportSize); + $minifier->setImportExtensions($this->importExtensions); + $importContent = $minifier->execute($source, $parents); + + // check if this is only valid for certain media + if (!empty($match['media'])) { + $importContent = '@media '.$match['media'].'{'.$importContent.'}'; + } + + // add to replacement array + $search[] = $match[0]; + $replace[] = $importContent; + } + + // replace the import statements + return str_replace($search, $replace, $content); + } + + /** + * Import files into the CSS, base64-ized. + * + * @url(image.jpg) images will be loaded and their content merged into the + * original file, to save HTTP requests. + * + * @param string $source The file to import files for + * @param string $content The CSS content to import files for + * + * @return string + */ + protected function importFiles($source, $content) + { + $regex = '/url\((["\']?)(.+?)\\1\)/i'; + if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + $search = array(); + $replace = array(); + + // loop the matches + foreach ($matches as $match) { + $extension = substr(strrchr($match[2], '.'), 1); + if ($extension && !array_key_exists($extension, $this->importExtensions)) { + continue; + } + + // get the path for the file that will be imported + $path = $match[2]; + $path = dirname($source).'/'.$path; + + // only replace the import with the content if we're able to get + // the content of the file, and it's relatively small + if ($this->canImportFile($path) && $this->canImportBySize($path)) { + // grab content && base64-ize + $importContent = $this->load($path); + $importContent = base64_encode($importContent); + + // build replacement + $search[] = $match[0]; + $replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')'; + } + } + + // replace the import statements + $content = str_replace($search, $replace, $content); + } + + return $content; + } + + /** + * Minify the data. + * Perform CSS optimizations. + * + * @param string[optional] $path Path to write the data to + * @param string[] $parents Parent paths, for circular reference checks + * + * @return string The minified data + */ + public function execute($path = null, $parents = array()) + { + $content = ''; + + // loop CSS data (raw data and files) + foreach ($this->data as $source => $css) { + /* + * Let's first take out strings & comments, since we can't just + * remove whitespace anywhere. If whitespace occurs inside a string, + * we should leave it alone. E.g.: + * p { content: "a test" } + */ + $this->extractStrings(); + $this->stripComments(); + $this->extractMath(); + $this->extractCustomProperties(); + $css = $this->replace($css); + + $css = $this->stripWhitespace($css); + $css = $this->shortenColors($css); + $css = $this->shortenZeroes($css); + $css = $this->shortenFontWeights($css); + $css = $this->stripEmptyTags($css); + + // restore the string we've extracted earlier + $css = $this->restoreExtractedData($css); + + $source = is_int($source) ? '' : $source; + $parents = $source ? array_merge($parents, array($source)) : $parents; + $css = $this->combineImports($source, $css, $parents); + $css = $this->importFiles($source, $css); + + /* + * If we'll save to a new path, we'll have to fix the relative paths + * to be relative no longer to the source file, but to the new path. + * If we don't write to a file, fall back to same path so no + * conversion happens (because we still want it to go through most + * of the move code, which also addresses url() & @import syntax...) + */ + $converter = $this->getPathConverter($source, $path ?: $source); + $css = $this->move($converter, $css); + + // combine css + $content .= $css; + } + + $content = $this->moveImportsToTop($content); + + return $content; + } + + /** + * Moving a css file should update all relative urls. + * Relative references (e.g. ../images/image.gif) in a certain css file, + * will have to be updated when a file is being saved at another location + * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper). + * + * @param ConverterInterface $converter Relative path converter + * @param string $content The CSS content to update relative urls for + * + * @return string + */ + protected function move(ConverterInterface $converter, $content) + { + /* + * Relative path references will usually be enclosed by url(). @import + * is an exception, where url() is not necessary around the path (but is + * allowed). + * This *could* be 1 regular expression, where both regular expressions + * in this array are on different sides of a |. But we're using named + * patterns in both regexes, the same name on both regexes. This is only + * possible with a (?J) modifier, but that only works after a fairly + * recent PCRE version. That's why I'm doing 2 separate regular + * expressions & combining the matches after executing of both. + */ + $relativeRegexes = array( + // url(xxx) + '/ + # open url() + url\( + + \s* + + # open path enclosure + (?P["\'])? + + # fetch path + (?P.+?) + + # close path enclosure + (?(quotes)(?P=quotes)) + + \s* + + # close url() + \) + + /ix', + + // @import "xxx" + '/ + # import statement + @import + + # whitespace + \s+ + + # we don\'t have to check for @import url(), because the + # condition above will already catch these + + # open path enclosure + (?P["\']) + + # fetch path + (?P.+?) + + # close path enclosure + (?P=quotes) + + /ix', + ); + + // find all relative urls in css + $matches = array(); + foreach ($relativeRegexes as $relativeRegex) { + if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) { + $matches = array_merge($matches, $regexMatches); + } + } + + $search = array(); + $replace = array(); + + // loop all urls + foreach ($matches as $match) { + // determine if it's a url() or an @import match + $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url'); + + $url = $match['path']; + if ($this->canImportByPath($url)) { + // attempting to interpret GET-params makes no sense, so let's discard them for awhile + $params = strrchr($url, '?'); + $url = $params ? substr($url, 0, -strlen($params)) : $url; + + // fix relative url + $url = $converter->convert($url); + + // now that the path has been converted, re-apply GET-params + $url .= $params; + } + + /* + * Urls with control characters above 0x7e should be quoted. + * According to Mozilla's parser, whitespace is only allowed at the + * end of unquoted urls. + * Urls with `)` (as could happen with data: uris) should also be + * quoted to avoid being confused for the url() closing parentheses. + * And urls with a # have also been reported to cause issues. + * Urls with quotes inside should also remain escaped. + * + * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation + * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378 + * @see https://github.com/matthiasmullie/minify/issues/193 + */ + $url = trim($url); + if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) { + $url = $match['quotes'] . $url . $match['quotes']; + } + + // build replacement + $search[] = $match[0]; + if ($type === 'url') { + $replace[] = 'url('.$url.')'; + } elseif ($type === 'import') { + $replace[] = '@import "'.$url.'"'; + } + } + + // replace urls + return str_replace($search, $replace, $content); + } + + /** + * Shorthand hex color codes. + * #FF0000 -> #F00. + * + * @param string $content The CSS content to shorten the hex color codes for + * + * @return string + */ + protected function shortenColors($content) + { + $content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?:([0-9a-z])\\4)?(?=[; }])/i', '#$1$2$3$4', $content); + + // remove alpha channel if it's pointless... + $content = preg_replace('/(?<=[: ])#([0-9a-z]{6})ff?(?=[; }])/i', '#$1', $content); + $content = preg_replace('/(?<=[: ])#([0-9a-z]{3})f?(?=[; }])/i', '#$1', $content); + + $colors = array( + // we can shorten some even more by replacing them with their color name + '#F0FFFF' => 'azure', + '#F5F5DC' => 'beige', + '#A52A2A' => 'brown', + '#FF7F50' => 'coral', + '#FFD700' => 'gold', + '#808080' => 'gray', + '#008000' => 'green', + '#4B0082' => 'indigo', + '#FFFFF0' => 'ivory', + '#F0E68C' => 'khaki', + '#FAF0E6' => 'linen', + '#800000' => 'maroon', + '#000080' => 'navy', + '#808000' => 'olive', + '#CD853F' => 'peru', + '#FFC0CB' => 'pink', + '#DDA0DD' => 'plum', + '#800080' => 'purple', + '#F00' => 'red', + '#FA8072' => 'salmon', + '#A0522D' => 'sienna', + '#C0C0C0' => 'silver', + '#FFFAFA' => 'snow', + '#D2B48C' => 'tan', + '#FF6347' => 'tomato', + '#EE82EE' => 'violet', + '#F5DEB3' => 'wheat', + // or the other way around + 'WHITE' => '#fff', + 'BLACK' => '#000', + ); + + return preg_replace_callback( + '/(?<=[: ])('.implode('|', array_keys($colors)).')(?=[; }])/i', + function ($match) use ($colors) { + return $colors[strtoupper($match[0])]; + }, + $content + ); + } + + /** + * Shorten CSS font weights. + * + * @param string $content The CSS content to shorten the font weights for + * + * @return string + */ + protected function shortenFontWeights($content) + { + $weights = array( + 'normal' => 400, + 'bold' => 700, + ); + + $callback = function ($match) use ($weights) { + return $match[1].$weights[$match[2]]; + }; + + return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content); + } + + /** + * Shorthand 0 values to plain 0, instead of e.g. -0em. + * + * @param string $content The CSS content to shorten the zero values for + * + * @return string + */ + protected function shortenZeroes($content) + { + // we don't want to strip units in `calc()` expressions: + // `5px - 0px` is valid, but `5px - 0` is not + // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but + // `10 * 0` is invalid + // we've extracted calcs earlier, so we don't need to worry about this + + // reusable bits of code throughout these regexes: + // before & after are used to make sure we don't match lose unintended + // 0-like values (e.g. in #000, or in http://url/1.0) + // units can be stripped from 0 values, or used to recognize non 0 + // values (where wa may be able to strip a .0 suffix) + $before = '(?<=[:(, ])'; + $after = '(?=[ ,);}])'; + $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)'; + + // strip units after zeroes (0px -> 0) + // NOTE: it should be safe to remove all units for a 0 value, but in + // practice, Webkit (especially Safari) seems to stumble over at least + // 0%, potentially other units as well. Only stripping 'px' for now. + // @see https://github.com/matthiasmullie/minify/issues/60 + $content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content); + + // strip 0-digits (.0 -> 0) + $content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content); + // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px + $content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content); + // strip trailing 0: 50.00 -> 50, 50.00px -> 50px + $content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content); + // strip leading 0: 0.1 -> .1, 01.1 -> 1.1 + $content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content); + + // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0) + $content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content); + + // IE doesn't seem to understand a unitless flex-basis value (correct - + // it goes against the spec), so let's add it in again (make it `%`, + // which is only 1 char: 0%, 0px, 0 anything, it's all just the same) + // @see https://developer.mozilla.org/nl/docs/Web/CSS/flex + $content = preg_replace('/flex:([0-9]+\s[0-9]+\s)0([;\}])/', 'flex:${1}0%${2}', $content); + $content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content); + + return $content; + } + + /** + * Strip empty tags from source code. + * + * @param string $content + * + * @return string + */ + protected function stripEmptyTags($content) + { + $content = preg_replace('/(?<=^)[^\{\};]+\{\s*\}/', '', $content); + $content = preg_replace('/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content); + + return $content; + } + + /** + * Strip comments from source code. + */ + protected function stripComments() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier) { + $count = count($minifier->extracted); + $placeholder = '/*'.$count.'*/'; + $minifier->extracted[$placeholder] = $match[0]; + + return $placeholder; + }; + $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback); + + $this->registerPattern('/\/\*.*?\*\//s', ''); + } + + /** + * Strip whitespace. + * + * @param string $content The CSS content to strip the whitespace for + * + * @return string + */ + protected function stripWhitespace($content) + { + // remove leading & trailing whitespace + $content = preg_replace('/^\s*/m', '', $content); + $content = preg_replace('/\s*$/m', '', $content); + + // replace newlines with a single space + $content = preg_replace('/\s+/', ' ', $content); + + // remove whitespace around meta characters + // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex + $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content); + $content = preg_replace('/([\[(:>\+])\s+/', '$1', $content); + $content = preg_replace('/\s+([\]\)>\+])/', '$1', $content); + $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content); + + // whitespace around + and - can only be stripped inside some pseudo- + // classes, like `:nth-child(3+2n)` + // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or + // selectors like `div.weird- p` + $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type'); + $content = preg_replace('/:('.implode('|', $pseudos).')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content); + + // remove semicolon/whitespace followed by closing bracket + $content = str_replace(';}', '}', $content); + + return trim($content); + } + + /** + * Replace all occurrences of functions that may contain math, where + * whitespace around operators needs to be preserved (e.g. calc, clamp) + */ + protected function extractMath() + { + $functions = array('calc', 'clamp', 'min', 'max'); + $pattern = '/\b('. implode('|', $functions) .')(\(.+?)(?=$|;|})/m'; + + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier, $pattern, &$callback) { + $function = $match[1]; + $length = strlen($match[2]); + $expr = ''; + $opened = 0; + + // the regular expression for extracting math has 1 significant problem: + // it can't determine the correct closing parenthesis... + // instead, it'll match a larger portion of code to where it's certain that + // the calc() musts have ended, and we'll figure out which is the correct + // closing parenthesis here, by counting how many have opened + for ($i = 0; $i < $length; $i++) { + $char = $match[2][$i]; + $expr .= $char; + if ($char === '(') { + $opened++; + } elseif ($char === ')' && --$opened === 0) { + break; + } + } + + // now that we've figured out where the calc() starts and ends, extract it + $count = count($minifier->extracted); + $placeholder = 'math('.$count.')'; + $minifier->extracted[$placeholder] = $function.'('.trim(substr($expr, 1, -1)).')'; + + // and since we've captured more code than required, we may have some leftover + // calc() in here too - go recursive on the remaining but of code to go figure + // that out and extract what is needed + $rest = str_replace($function.$expr, '', $match[0]); + $rest = preg_replace_callback($pattern, $callback, $rest); + + return $placeholder.$rest; + }; + + $this->registerPattern($pattern, $callback); + } + + /** + * Replace custom properties, whose values may be used in scenarios where + * we wouldn't want them to be minified (e.g. inside calc) + */ + protected function extractCustomProperties() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $this->registerPattern( + '/(?<=^|[;}])(--[^:;{}"\'\s]+)\s*:([^;{}]+)/m', + function ($match) use ($minifier) { + $placeholder = '--custom-'. count($minifier->extracted) . ':0'; + $minifier->extracted[$placeholder] = $match[1] .':'. trim($match[2]); + return $placeholder; + + } + ); + } + + /** + * Check if file is small enough to be imported. + * + * @param string $path The path to the file + * + * @return bool + */ + protected function canImportBySize($path) + { + return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024; + } + + /** + * Check if file a file can be imported, going by the path. + * + * @param string $path + * + * @return bool + */ + protected function canImportByPath($path) + { + return preg_match('/^(data:|https?:|\\/)/', $path) === 0; + } + + /** + * Return a converter to update relative paths to be relative to the new + * destination. + * + * @param string $source + * @param string $target + * + * @return ConverterInterface + */ + protected function getPathConverter($source, $target) + { + return new Converter($source, $target); + } +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exception.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exception.php new file mode 100644 index 0000000000..d03898f043 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exception.php @@ -0,0 +1,20 @@ + + */ +namespace MatthiasMullie\Minify; + +/** + * Base Exception Class + * @deprecated Use Exceptions\BasicException instead + * + * @package Minify + * @author Matthias Mullie + */ +abstract class Exception extends \Exception +{ +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php new file mode 100644 index 0000000000..af5e81bc08 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php @@ -0,0 +1,23 @@ + + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify\Exceptions; + +use MatthiasMullie\Minify\Exception; + +/** + * Basic Exception Class + * + * @package Minify\Exception + * @author Matthias Mullie + */ +abstract class BasicException extends Exception +{ +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php new file mode 100644 index 0000000000..912a2c908d --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php @@ -0,0 +1,21 @@ + + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify\Exceptions; + +/** + * File Import Exception Class + * + * @package Minify\Exception + * @author Matthias Mullie + */ +class FileImportException extends BasicException +{ +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/IOException.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/IOException.php new file mode 100644 index 0000000000..b172eb48f3 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/IOException.php @@ -0,0 +1,21 @@ + + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify\Exceptions; + +/** + * IO Exception Class + * + * @package Minify\Exception + * @author Matthias Mullie + */ +class IOException extends BasicException +{ +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/JS.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/JS.php new file mode 100644 index 0000000000..a0fa649d3a --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/JS.php @@ -0,0 +1,612 @@ + + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify; + +/** + * JavaScript Minifier Class + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @package Minify + * @author Matthias Mullie + * @author Tijs Verkoyen + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +class JS extends Minify +{ + /** + * Var-matching regex based on http://stackoverflow.com/a/9337047/802993. + * + * Note that regular expressions using that bit must have the PCRE_UTF8 + * pattern modifier (/u) set. + * + * @var string + */ + const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b'; + + /** + * Full list of JavaScript reserved words. + * Will be loaded from /data/js/keywords_reserved.txt. + * + * @see https://mathiasbynens.be/notes/reserved-keywords + * + * @var string[] + */ + protected $keywordsReserved = array(); + + /** + * List of JavaScript reserved words that accept a + * after them. Some end of lines are not the end of a statement, like with + * these keywords. + * + * E.g.: we shouldn't insert a ; after this else + * else + * console.log('this is quite fine') + * + * Will be loaded from /data/js/keywords_before.txt + * + * @var string[] + */ + protected $keywordsBefore = array(); + + /** + * List of JavaScript reserved words that accept a + * before them. Some end of lines are not the end of a statement, like when + * continued by one of these keywords on the newline. + * + * E.g.: we shouldn't insert a ; before this instanceof + * variable + * instanceof String + * + * Will be loaded from /data/js/keywords_after.txt + * + * @var string[] + */ + protected $keywordsAfter = array(); + + /** + * List of all JavaScript operators. + * + * Will be loaded from /data/js/operators.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operators = array(); + + /** + * List of JavaScript operators that accept a after + * them. Some end of lines are not the end of a statement, like with these + * operators. + * + * Note: Most operators are fine, we've only removed ++ and --. + * ++ & -- have to be joined with the value they're in-/decrementing. + * + * Will be loaded from /data/js/operators_before.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operatorsBefore = array(); + + /** + * List of JavaScript operators that accept a before + * them. Some end of lines are not the end of a statement, like when + * continued by one of these operators on the newline. + * + * Note: Most operators are fine, we've only removed ), ], ++, --, ! and ~. + * There can't be a newline separating ! or ~ and whatever it is negating. + * ++ & -- have to be joined with the value they're in-/decrementing. + * ) & ] are "special" in that they have lots or usecases. () for example + * is used for function calls, for grouping, in if () and for (), ... + * + * Will be loaded from /data/js/operators_after.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operatorsAfter = array(); + + /** + * {@inheritdoc} + */ + public function __construct() + { + call_user_func_array(array('parent', '__construct'), func_get_args()); + + $dataDir = __DIR__.'/../data/js/'; + $options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES; + $this->keywordsReserved = file($dataDir.'keywords_reserved.txt', $options); + $this->keywordsBefore = file($dataDir.'keywords_before.txt', $options); + $this->keywordsAfter = file($dataDir.'keywords_after.txt', $options); + $this->operators = file($dataDir.'operators.txt', $options); + $this->operatorsBefore = file($dataDir.'operators_before.txt', $options); + $this->operatorsAfter = file($dataDir.'operators_after.txt', $options); + } + + /** + * Minify the data. + * Perform JS optimizations. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + public function execute($path = null) + { + $content = ''; + + /* + * Let's first take out strings, comments and regular expressions. + * All of these can contain JS code-like characters, and we should make + * sure any further magic ignores anything inside of these. + * + * Consider this example, where we should not strip any whitespace: + * var str = "a test"; + * + * Comments will be removed altogether, strings and regular expressions + * will be replaced by placeholder text, which we'll restore later. + */ + $this->extractStrings('\'"`'); + $this->stripComments(); + $this->extractRegex(); + + // loop files + foreach ($this->data as $source => $js) { + // take out strings, comments & regex (for which we've registered + // the regexes just a few lines earlier) + $js = $this->replace($js); + + $js = $this->propertyNotation($js); + $js = $this->shortenBools($js); + $js = $this->stripWhitespace($js); + + // combine js: separating the scripts by a ; + $content .= $js.";"; + } + + // clean up leftover `;`s from the combination of multiple scripts + $content = ltrim($content, ';'); + $content = (string) substr($content, 0, -1); + + /* + * Earlier, we extracted strings & regular expressions and replaced them + * with placeholder text. This will restore them. + */ + $content = $this->restoreExtractedData($content); + + return $content; + } + + /** + * Strip comments from source code. + */ + protected function stripComments() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier) { + $count = count($minifier->extracted); + $placeholder = '/*'.$count.'*/'; + $minifier->extracted[$placeholder] = $match[0]; + + return $placeholder; + }; + // multi-line comments + $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback); + $this->registerPattern('/\/\*.*?\*\//s', ''); + + // single-line comments + $this->registerPattern('/\/\/.*$/m', ''); + } + + /** + * JS can have /-delimited regular expressions, like: /ab+c/.match(string). + * + * The content inside the regex can contain characters that may be confused + * for JS code: e.g. it could contain whitespace it needs to match & we + * don't want to strip whitespace in there. + * + * The regex can be pretty simple: we don't have to care about comments, + * (which also use slashes) because stripComments() will have stripped those + * already. + * + * This method will replace all string content with simple REGEX# + * placeholder text, so we've rid all regular expressions from characters + * that may be misinterpreted. Original regex content will be saved in + * $this->extracted and after doing all other minifying, we can restore the + * original content via restoreRegex() + */ + protected function extractRegex() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier) { + $count = count($minifier->extracted); + $placeholder = '"'.$count.'"'; + $minifier->extracted[$placeholder] = $match[0]; + + return $placeholder; + }; + + // match all chars except `/` and `\` + // `\` is allowed though, along with whatever char follows (which is the + // one being escaped) + // this should allow all chars, except for an unescaped `/` (= the one + // closing the regex) + // then also ignore bare `/` inside `[]`, where they don't need to be + // escaped: anything inside `[]` can be ignored safely + $pattern = '\\/(?!\*)(?:[^\\[\\/\\\\\n\r]++|(?:\\\\.)++|(?:\\[(?:[^\\]\\\\\n\r]++|(?:\\\\.)++)++\\])++)++\\/[gimuy]*'; + + // a regular expression can only be followed by a few operators or some + // of the RegExp methods (a `\` followed by a variable or value is + // likely part of a division, not a regex) + $keywords = array('do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof'); + $before = '(^|[=:,;\+\-\*\/\}\(\{\[&\|!]|'.implode('|', $keywords).')\s*'; + $propertiesAndMethods = array( + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2 + 'constructor', + 'flags', + 'global', + 'ignoreCase', + 'multiline', + 'source', + 'sticky', + 'unicode', + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2 + 'compile(', + 'exec(', + 'test(', + 'toSource(', + 'toString(', + ); + $delimiters = array_fill(0, count($propertiesAndMethods), '/'); + $propertiesAndMethods = array_map('preg_quote', $propertiesAndMethods, $delimiters); + $after = '(?=\s*([\.,;\)\}&\|+]|\/\/|$|\.('.implode('|', $propertiesAndMethods).')))'; + $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback); + + // regular expressions following a `)` are rather annoying to detect... + // quite often, `/` after `)` is a division operator & if it happens to + // be followed by another one (or a comment), it is likely to be + // confused for a regular expression + // however, it's perfectly possible for a regex to follow a `)`: after + // a single-line `if()`, `while()`, ... statement, for example + // since, when they occur like that, they're always the start of a + // statement, there's only a limited amount of ways they can be useful: + // by calling the regex methods directly + // if a regex following `)` is not followed by `.`, + // it's quite likely not a regex + $before = '\)\s*'; + $after = '(?=\s*\.('.implode('|', $propertiesAndMethods).'))'; + $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback); + + // 1 more edge case: a regex can be followed by a lot more operators or + // keywords if there's a newline (ASI) in between, where the operator + // actually starts a new statement + // (https://github.com/matthiasmullie/minify/issues/56) + $operators = $this->getOperatorsForRegex($this->operatorsBefore, '/'); + $operators += $this->getOperatorsForRegex($this->keywordsReserved, '/'); + $after = '(?=\s*\n\s*('.implode('|', $operators).'))'; + $this->registerPattern('/'.$pattern.$after.'/', $callback); + } + + /** + * Strip whitespace. + * + * We won't strip *all* whitespace, but as much as possible. The thing that + * we'll preserve are newlines we're unsure about. + * JavaScript doesn't require statements to be terminated with a semicolon. + * It will automatically fix missing semicolons with ASI (automatic semi- + * colon insertion) at the end of line causing errors (without semicolon.) + * + * Because it's sometimes hard to tell if a newline is part of a statement + * that should be terminated or not, we'll just leave some of them alone. + * + * @param string $content The content to strip the whitespace for + * + * @return string + */ + protected function stripWhitespace($content) + { + // uniform line endings, make them all line feed + $content = str_replace(array("\r\n", "\r"), "\n", $content); + + // collapse all non-line feed whitespace into a single space + $content = preg_replace('/[^\S\n]+/', ' ', $content); + + // strip leading & trailing whitespace + $content = str_replace(array(" \n", "\n "), "\n", $content); + + // collapse consecutive line feeds into just 1 + $content = preg_replace('/\n+/', "\n", $content); + + $operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/'); + $operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/'); + $operators = $this->getOperatorsForRegex($this->operators, '/'); + $keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/'); + $keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/'); + + // strip whitespace that ends in (or next line begin with) an operator + // that allows statements to be broken up over multiple lines + unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']); + $content = preg_replace( + array( + '/('.implode('|', $operatorsBefore).')\s+/', + '/\s+('.implode('|', $operatorsAfter).')/', + ), + '\\1', + $content + ); + + // make sure + and - can't be mistaken for, or joined into ++ and -- + $content = preg_replace( + array( + '/(?%&|', $delimiter); + $operators['='] = '(?keywordsReserved; + $callback = function ($match) use ($minifier, $keywords) { + $property = trim($minifier->extracted[$match[1]], '\'"'); + + /* + * Check if the property is a reserved keyword. In this context (as + * property of an object literal/array) it shouldn't matter, but IE8 + * freaks out with "Expected identifier". + */ + if (in_array($property, $keywords)) { + return $match[0]; + } + + /* + * See if the property is in a variable-like format (e.g. + * array['key-here'] can't be replaced by array.key-here since '-' + * is not a valid character there. + */ + if (!preg_match('/^'.$minifier::REGEX_VARIABLE.'$/u', $property)) { + return $match[0]; + } + + return '.'.$property; + }; + + /* + * Figure out if previous character is a variable name (of the array + * we want to use property notation on) - this is to make sure + * standalone ['value'] arrays aren't confused for keys-of-an-array. + * We can (and only have to) check the last character, because PHP's + * regex implementation doesn't allow unfixed-length look-behind + * assertions. + */ + preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar); + $previousChar = $previousChar[1]; + + /* + * Make sure word preceding the ['value'] is not a keyword, e.g. + * return['x']. Because -again- PHP's regex implementation doesn't allow + * unfixed-length look-behind assertions, I'm just going to do a lot of + * separate look-behind assertions, one for each keyword. + */ + $keywords = $this->getKeywordsForRegex($keywords); + $keywords = '(? + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify; + +use MatthiasMullie\Minify\Exceptions\IOException; +use Psr\Cache\CacheItemInterface; + +/** + * Abstract minifier class. + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @package Minify + * @author Matthias Mullie + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +abstract class Minify +{ + /** + * The data to be minified. + * + * @var string[] + */ + protected $data = array(); + + /** + * Array of patterns to match. + * + * @var string[] + */ + protected $patterns = array(); + + /** + * This array will hold content of strings and regular expressions that have + * been extracted from the JS source code, so we can reliably match "code", + * without having to worry about potential "code-like" characters inside. + * + * @var string[] + */ + public $extracted = array(); + + /** + * Init the minify class - optionally, code may be passed along already. + */ + public function __construct(/* $data = null, ... */) + { + // it's possible to add the source through the constructor as well ;) + if (func_num_args()) { + call_user_func_array(array($this, 'add'), func_get_args()); + } + } + + /** + * Add a file or straight-up code to be minified. + * + * @param string|string[] $data + * + * @return static + */ + public function add($data /* $data = null, ... */) + { + // bogus "usage" of parameter $data: scrutinizer warns this variable is + // not used (we're using func_get_args instead to support overloading), + // but it still needs to be defined because it makes no sense to have + // this function without argument :) + $args = array($data) + func_get_args(); + + // this method can be overloaded + foreach ($args as $data) { + if (is_array($data)) { + call_user_func_array(array($this, 'add'), $data); + continue; + } + + // redefine var + $data = (string) $data; + + // load data + $value = $this->load($data); + $key = ($data != $value) ? $data : count($this->data); + + // replace CR linefeeds etc. + // @see https://github.com/matthiasmullie/minify/pull/139 + $value = str_replace(array("\r\n", "\r"), "\n", $value); + + // store data + $this->data[$key] = $value; + } + + return $this; + } + + /** + * Add a file to be minified. + * + * @param string|string[] $data + * + * @return static + * + * @throws IOException + */ + public function addFile($data /* $data = null, ... */) + { + // bogus "usage" of parameter $data: scrutinizer warns this variable is + // not used (we're using func_get_args instead to support overloading), + // but it still needs to be defined because it makes no sense to have + // this function without argument :) + $args = array($data) + func_get_args(); + + // this method can be overloaded + foreach ($args as $path) { + if (is_array($path)) { + call_user_func_array(array($this, 'addFile'), $path); + continue; + } + + // redefine var + $path = (string) $path; + + // check if we can read the file + if (!$this->canImportFile($path)) { + throw new IOException('The file "'.$path.'" could not be opened for reading. Check if PHP has enough permissions.'); + } + + $this->add($path); + } + + return $this; + } + + /** + * Minify the data & (optionally) saves it to a file. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + public function minify($path = null) + { + $content = $this->execute($path); + + // save to path + if ($path !== null) { + $this->save($content, $path); + } + + return $content; + } + + /** + * Minify & gzip the data & (optionally) saves it to a file. + * + * @param string[optional] $path Path to write the data to + * @param int[optional] $level Compression level, from 0 to 9 + * + * @return string The minified & gzipped data + */ + public function gzip($path = null, $level = 9) + { + $content = $this->execute($path); + $content = gzencode($content, $level, FORCE_GZIP); + + // save to path + if ($path !== null) { + $this->save($content, $path); + } + + return $content; + } + + /** + * Minify the data & write it to a CacheItemInterface object. + * + * @param CacheItemInterface $item Cache item to write the data to + * + * @return CacheItemInterface Cache item with the minifier data + */ + public function cache(CacheItemInterface $item) + { + $content = $this->execute(); + $item->set($content); + + return $item; + } + + /** + * Minify the data. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + abstract public function execute($path = null); + + /** + * Load data. + * + * @param string $data Either a path to a file or the content itself + * + * @return string + */ + protected function load($data) + { + // check if the data is a file + if ($this->canImportFile($data)) { + $data = file_get_contents($data); + + // strip BOM, if any + if (substr($data, 0, 3) == "\xef\xbb\xbf") { + $data = substr($data, 3); + } + } + + return $data; + } + + /** + * Save to file. + * + * @param string $content The minified data + * @param string $path The path to save the minified data to + * + * @throws IOException + */ + protected function save($content, $path) + { + $handler = $this->openFileForWriting($path); + + $this->writeToFile($handler, $content); + + @fclose($handler); + } + + /** + * Register a pattern to execute against the source content. + * + * @param string $pattern PCRE pattern + * @param string|callable $replacement Replacement value for matched pattern + */ + protected function registerPattern($pattern, $replacement = '') + { + // study the pattern, we'll execute it more than once + $pattern .= 'S'; + + $this->patterns[] = array($pattern, $replacement); + } + + /** + * We can't "just" run some regular expressions against JavaScript: it's a + * complex language. E.g. having an occurrence of // xyz would be a comment, + * unless it's used within a string. Of you could have something that looks + * like a 'string', but inside a comment. + * The only way to accurately replace these pieces is to traverse the JS one + * character at a time and try to find whatever starts first. + * + * @param string $content The content to replace patterns in + * + * @return string The (manipulated) content + */ + protected function replace($content) + { + $processed = ''; + $positions = array_fill(0, count($this->patterns), -1); + $matches = array(); + + while ($content) { + // find first match for all patterns + foreach ($this->patterns as $i => $pattern) { + list($pattern, $replacement) = $pattern; + + // we can safely ignore patterns for positions we've unset earlier, + // because we know these won't show up anymore + if (array_key_exists($i, $positions) == false) { + continue; + } + + // no need to re-run matches that are still in the part of the + // content that hasn't been processed + if ($positions[$i] >= 0) { + continue; + } + + $match = null; + if (preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE)) { + $matches[$i] = $match; + + // we'll store the match position as well; that way, we + // don't have to redo all preg_matches after changing only + // the first (we'll still know where those others are) + $positions[$i] = $match[0][1]; + } else { + // if the pattern couldn't be matched, there's no point in + // executing it again in later runs on this same content; + // ignore this one until we reach end of content + unset($matches[$i], $positions[$i]); + } + } + + // no more matches to find: everything's been processed, break out + if (!$matches) { + $processed .= $content; + break; + } + + // see which of the patterns actually found the first thing (we'll + // only want to execute that one, since we're unsure if what the + // other found was not inside what the first found) + $discardLength = min($positions); + $firstPattern = array_search($discardLength, $positions); + $match = $matches[$firstPattern][0][0]; + + // execute the pattern that matches earliest in the content string + list($pattern, $replacement) = $this->patterns[$firstPattern]; + $replacement = $this->replacePattern($pattern, $replacement, $content); + + // figure out which part of the string was unmatched; that's the + // part we'll execute the patterns on again next + $content = (string) substr($content, $discardLength); + $unmatched = (string) substr($content, strpos($content, $match) + strlen($match)); + + // move the replaced part to $processed and prepare $content to + // again match batch of patterns against + $processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched)); + $content = $unmatched; + + // first match has been replaced & that content is to be left alone, + // the next matches will start after this replacement, so we should + // fix their offsets + foreach ($positions as $i => $position) { + $positions[$i] -= $discardLength + strlen($match); + } + } + + return $processed; + } + + /** + * This is where a pattern is matched against $content and the matches + * are replaced by their respective value. + * This function will be called plenty of times, where $content will always + * move up 1 character. + * + * @param string $pattern Pattern to match + * @param string|callable $replacement Replacement value + * @param string $content Content to match pattern against + * + * @return string + */ + protected function replacePattern($pattern, $replacement, $content) + { + if (is_callable($replacement)) { + return preg_replace_callback($pattern, $replacement, $content, 1, $count); + } else { + return preg_replace($pattern, $replacement, $content, 1, $count); + } + } + + /** + * Strings are a pattern we need to match, in order to ignore potential + * code-like content inside them, but we just want all of the string + * content to remain untouched. + * + * This method will replace all string content with simple STRING# + * placeholder text, so we've rid all strings from characters that may be + * misinterpreted. Original string content will be saved in $this->extracted + * and after doing all other minifying, we can restore the original content + * via restoreStrings(). + * + * @param string[optional] $chars + * @param string[optional] $placeholderPrefix + */ + protected function extractStrings($chars = '\'"', $placeholderPrefix = '') + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier, $placeholderPrefix) { + // check the second index here, because the first always contains a quote + if ($match[2] === '') { + /* + * Empty strings need no placeholder; they can't be confused for + * anything else anyway. + * But we still needed to match them, for the extraction routine + * to skip over this particular string. + */ + return $match[0]; + } + + $count = count($minifier->extracted); + $placeholder = $match[1].$placeholderPrefix.$count.$match[1]; + $minifier->extracted[$placeholder] = $match[1].$match[2].$match[1]; + + return $placeholder; + }; + + /* + * The \\ messiness explained: + * * Don't count ' or " as end-of-string if it's escaped (has backslash + * in front of it) + * * Unless... that backslash itself is escaped (another leading slash), + * in which case it's no longer escaping the ' or " + * * So there can be either no backslash, or an even number + * * multiply all of that times 4, to account for the escaping that has + * to be done to pass the backslash into the PHP string without it being + * considered as escape-char (times 2) and to get it in the regex, + * escaped (times 2) + */ + $this->registerPattern('/(['.$chars.'])(.*?(?extracted. + * + * @param string $content + * + * @return string + */ + protected function restoreExtractedData($content) + { + if (!$this->extracted) { + // nothing was extracted, nothing to restore + return $content; + } + + $content = strtr($content, $this->extracted); + + $this->extracted = array(); + + return $content; + } + + /** + * Check if the path is a regular file and can be read. + * + * @param string $path + * + * @return bool + */ + protected function canImportFile($path) + { + $parsed = parse_url($path); + if ( + // file is elsewhere + isset($parsed['host']) || + // file responds to queries (may change, or need to bypass cache) + isset($parsed['query']) + ) { + return false; + } + + return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path); + } + + /** + * Attempts to open file specified by $path for writing. + * + * @param string $path The path to the file + * + * @return resource Specifier for the target file + * + * @throws IOException + */ + protected function openFileForWriting($path) + { + if ($path === '' || ($handler = @fopen($path, 'w')) === false) { + throw new IOException('The file "'.$path.'" could not be opened for writing. Check if PHP has enough permissions.'); + } + + return $handler; + } + + /** + * Attempts to write $content to the file specified by $handler. $path is used for printing exceptions. + * + * @param resource $handler The resource to write to + * @param string $content The content to write + * @param string $path The path to the file (for exception printing only) + * + * @throws IOException + */ + protected function writeToFile($handler, $content, $path = '') + { + if ( + !is_resource($handler) || + ($result = @fwrite($handler, $content)) === false || + ($result < strlen($content)) + ) { + throw new IOException('The file "'.$path.'" could not be written to. Check your disk space and file permissions.'); + } + } +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/path-converter/LICENSE b/data/web/inc/lib/vendor/matthiasmullie/path-converter/LICENSE new file mode 100644 index 0000000000..491295ad30 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/path-converter/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2015 Matthias Mullie + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/matthiasmullie/path-converter/composer.json b/data/web/inc/lib/vendor/matthiasmullie/path-converter/composer.json new file mode 100644 index 0000000000..1cb6a4c597 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/path-converter/composer.json @@ -0,0 +1,28 @@ +{ + "name": "matthiasmullie/path-converter", + "type": "library", + "description": "Relative path converter", + "keywords": ["relative", "path", "converter", "paths"], + "homepage": "http://github.com/matthiasmullie/path-converter", + "license": "MIT", + "authors": [ + { + "name": "Matthias Mullie", + "homepage": "http://www.mullie.eu", + "email": "pathconverter@mullie.eu", + "role": "Developer" + } + ], + "require": { + "php": ">=5.3.0", + "ext-pcre": "*" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "autoload": { + "psr-4": { + "MatthiasMullie\\PathConverter\\": "src/" + } + } +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/Converter.php b/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/Converter.php new file mode 100644 index 0000000000..80cf269b94 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/Converter.php @@ -0,0 +1,204 @@ + + * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved + * @license MIT License + */ +class Converter implements ConverterInterface +{ + /** + * @var string + */ + protected $from; + + /** + * @var string + */ + protected $to; + + /** + * @param string $from The original base path (directory, not file!) + * @param string $to The new base path (directory, not file!) + * @param string $root Root directory (defaults to `getcwd`) + */ + public function __construct($from, $to, $root = '') + { + $shared = $this->shared($from, $to); + if ($shared === '') { + // when both paths have nothing in common, one of them is probably + // absolute while the other is relative + $root = $root ?: getcwd(); + $from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root.'/'.$from); + $to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root.'/'.$to); + + // or traveling the tree via `..` + // attempt to resolve path, or assume it's fine if it doesn't exist + $from = @realpath($from) ?: $from; + $to = @realpath($to) ?: $to; + } + + $from = $this->dirname($from); + $to = $this->dirname($to); + + $from = $this->normalize($from); + $to = $this->normalize($to); + + $this->from = $from; + $this->to = $to; + } + + /** + * Normalize path. + * + * @param string $path + * + * @return string + */ + protected function normalize($path) + { + // deal with different operating systems' directory structure + $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/'); + + // remove leading current directory. + if (substr($path, 0, 2) === './') { + $path = substr($path, 2); + } + + // remove references to current directory in the path. + $path = str_replace('/./', '/', $path); + + /* + * Example: + * /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif + * to + * /home/forkcms/frontend/core/layout/images/img.gif + */ + do { + $path = preg_replace('/[^\/]+(? $chunk) { + if (isset($path2[$i]) && $path1[$i] == $path2[$i]) { + $shared[] = $chunk; + } else { + break; + } + } + + return implode('/', $shared); + } + + /** + * Convert paths relative from 1 file to another. + * + * E.g. + * ../images/img.gif relative to /home/forkcms/frontend/core/layout/css + * should become: + * ../../core/layout/images/img.gif relative to + * /home/forkcms/frontend/cache/minified_css + * + * @param string $path The relative path that needs to be converted + * + * @return string The new relative path + */ + public function convert($path) + { + // quit early if conversion makes no sense + if ($this->from === $this->to) { + return $path; + } + + $path = $this->normalize($path); + // if we're not dealing with a relative path, just return absolute + if (strpos($path, '/') === 0) { + return $path; + } + + // normalize paths + $path = $this->normalize($this->from.'/'.$path); + + // strip shared ancestor paths + $shared = $this->shared($path, $this->to); + $path = mb_substr($path, mb_strlen($shared)); + $to = mb_substr($this->to, mb_strlen($shared)); + + // add .. for every directory that needs to be traversed to new path + $to = str_repeat('../', count(array_filter(explode('/', $to)))); + + return $to.ltrim($path, '/'); + } + + /** + * Attempt to get the directory name from a path. + * + * @param string $path + * + * @return string + */ + protected function dirname($path) + { + if (@is_file($path)) { + return dirname($path); + } + + if (@is_dir($path)) { + return rtrim($path, '/'); + } + + // no known file/dir, start making assumptions + + // ends in / = dir + if (mb_substr($path, -1) === '/') { + return rtrim($path, '/'); + } + + // has a dot in the name, likely a file + if (preg_match('/.*\..*$/', basename($path)) !== 0) { + return dirname($path); + } + + // you're on your own here! + return $path; + } +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/ConverterInterface.php b/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/ConverterInterface.php new file mode 100644 index 0000000000..dc1b765737 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/ConverterInterface.php @@ -0,0 +1,24 @@ + + * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved + * @license MIT License + */ +interface ConverterInterface +{ + /** + * Convert file paths. + * + * @param string $path The path to be converted + * + * @return string The new path + */ + public function convert($path); +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/NoConverter.php b/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/NoConverter.php new file mode 100644 index 0000000000..2fcfd0f204 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/path-converter/src/NoConverter.php @@ -0,0 +1,23 @@ + + * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved + * @license MIT License + */ +class NoConverter implements ConverterInterface +{ + /** + * {@inheritdoc} + */ + public function convert($path) + { + return $path; + } +} diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/.gitignore b/data/web/inc/lib/vendor/mustangostang/spyc/.gitignore new file mode 100644 index 0000000000..e5294f448e --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/.gitignore @@ -0,0 +1,3 @@ +vendor +composer.lock +.DS_Store diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/COPYING b/data/web/inc/lib/vendor/mustangostang/spyc/COPYING new file mode 100644 index 0000000000..8e7ddbcf6b --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/COPYING @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2011 Vladimir Andersen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/README.md b/data/web/inc/lib/vendor/mustangostang/spyc/README.md new file mode 100644 index 0000000000..1ebbdc556a --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/README.md @@ -0,0 +1,30 @@ +**Spyc** is a YAML loader/dumper written in pure PHP. Given a YAML document, Spyc will return an array that +you can use however you see fit. Given an array, Spyc will return a string which contains a YAML document +built from your data. + +**YAML** is an amazingly human friendly and strikingly versatile data serialization language which can be used +for log files, config files, custom protocols, the works. For more information, see http://www.yaml.org. + +Spyc supports YAML 1.0 specification. + +## Using Spyc + +Using Spyc is trivial: + +```php + + * @author Chris Wanstrath + * @link https://github.com/mustangostang/spyc/ + * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @package Spyc + */ + +if (!function_exists('spyc_load')) { + /** + * Parses YAML to array. + * @param string $string YAML string. + * @return array + */ + function spyc_load ($string) { + return Spyc::YAMLLoadString($string); + } +} + +if (!function_exists('spyc_load_file')) { + /** + * Parses YAML to array. + * @param string $file Path to YAML file. + * @return array + */ + function spyc_load_file ($file) { + return Spyc::YAMLLoad($file); + } +} + +if (!function_exists('spyc_dump')) { + /** + * Dumps array to YAML. + * @param array $data Array. + * @return string + */ + function spyc_dump ($data) { + return Spyc::YAMLDump($data, false, false, true); + } +} + +if (!class_exists('Spyc')) { + +/** + * The Simple PHP YAML Class. + * + * This class can be used to read a YAML file and convert its contents + * into a PHP array. It currently supports a very limited subsection of + * the YAML spec. + * + * Usage: + * + * $Spyc = new Spyc; + * $array = $Spyc->load($file); + * + * or: + * + * $array = Spyc::YAMLLoad($file); + * + * or: + * + * $array = spyc_load_file($file); + * + * @package Spyc + */ +class Spyc { + + // SETTINGS + + const REMPTY = "\0\0\0\0\0"; + + /** + * Setting this to true will force YAMLDump to enclose any string value in + * quotes. False by default. + * + * @var bool + */ + public $setting_dump_force_quotes = false; + + /** + * Setting this to true will forse YAMLLoad to use syck_load function when + * possible. False by default. + * @var bool + */ + public $setting_use_syck_is_possible = false; + + /** + * Setting this to true will forse YAMLLoad to use syck_load function when + * possible. False by default. + * @var bool + */ + public $setting_empty_hash_as_object = false; + + + /**#@+ + * @access private + * @var mixed + */ + private $_dumpIndent; + private $_dumpWordWrap; + private $_containsGroupAnchor = false; + private $_containsGroupAlias = false; + private $path; + private $result; + private $LiteralPlaceHolder = '___YAML_Literal_Block___'; + private $SavedGroups = array(); + private $indent; + /** + * Path modifier that should be applied after adding current element. + * @var array + */ + private $delayedPath = array(); + + /**#@+ + * @access public + * @var mixed + */ + public $_nodeId; + +/** + * Load a valid YAML string to Spyc. + * @param string $input + * @return array + */ + public function load ($input) { + return $this->_loadString($input); + } + + /** + * Load a valid YAML file to Spyc. + * @param string $file + * @return array + */ + public function loadFile ($file) { + return $this->_load($file); + } + + /** + * Load YAML into a PHP array statically + * + * The load method, when supplied with a YAML stream (string or file), + * will do its best to convert YAML in a file into a PHP array. Pretty + * simple. + * Usage: + * + * $array = Spyc::YAMLLoad('lucky.yaml'); + * print_r($array); + * + * @access public + * @return array + * @param string $input Path of YAML file or string containing YAML + * @param array set options + */ + public static function YAMLLoad($input, $options = []) { + $Spyc = new Spyc; + foreach ($options as $key => $value) { + if (property_exists($Spyc, $key)) { + $Spyc->$key = $value; + } + } + return $Spyc->_load($input); + } + + /** + * Load a string of YAML into a PHP array statically + * + * The load method, when supplied with a YAML string, will do its best + * to convert YAML in a string into a PHP array. Pretty simple. + * + * Note: use this function if you don't want files from the file system + * loaded and processed as YAML. This is of interest to people concerned + * about security whose input is from a string. + * + * Usage: + * + * $array = Spyc::YAMLLoadString("---\n0: hello world\n"); + * print_r($array); + * + * @access public + * @return array + * @param string $input String containing YAML + * @param array set options + */ + public static function YAMLLoadString($input, $options = []) { + $Spyc = new Spyc; + foreach ($options as $key => $value) { + if (property_exists($Spyc, $key)) { + $Spyc->$key = $value; + } + } + return $Spyc->_loadString($input); + } + + /** + * Dump YAML from PHP array statically + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as nothing.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array|\stdClass $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + * @param bool $no_opening_dashes Do not start YAML file with "---\n" + */ + public static function YAMLDump($array, $indent = false, $wordwrap = false, $no_opening_dashes = false) { + $spyc = new Spyc; + return $spyc->dump($array, $indent, $wordwrap, $no_opening_dashes); + } + + + /** + * Dump PHP array to YAML + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as tasteful.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + */ + public function dump($array,$indent = false,$wordwrap = false, $no_opening_dashes = false) { + // Dumps to some very clean YAML. We'll have to add some more features + // and options soon. And better support for folding. + + // New features and options. + if ($indent === false or !is_numeric($indent)) { + $this->_dumpIndent = 2; + } else { + $this->_dumpIndent = $indent; + } + + if ($wordwrap === false or !is_numeric($wordwrap)) { + $this->_dumpWordWrap = 40; + } else { + $this->_dumpWordWrap = $wordwrap; + } + + // New YAML document + $string = ""; + if (!$no_opening_dashes) $string = "---\n"; + + // Start at the base of the array and move through it. + if ($array) { + $array = (array)$array; + $previous_key = -1; + foreach ($array as $key => $value) { + if (!isset($first_key)) $first_key = $key; + $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key, $array); + $previous_key = $key; + } + } + return $string; + } + + /** + * Attempts to convert a key / value array item to YAML + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + private function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0, $source_array = null) { + if(is_object($value)) $value = (array)$value; + if (is_array($value)) { + if (empty ($value)) + return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key, $source_array); + // It has children. What to do? + // Make it the right kind of item + $string = $this->_dumpNode($key, self::REMPTY, $indent, $previous_key, $first_key, $source_array); + // Add the indent + $indent += $this->_dumpIndent; + // Yamlize the array + $string .= $this->_yamlizeArray($value,$indent); + } elseif (!is_array($value)) { + // It doesn't have children. Yip. + $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key, $source_array); + } + return $string; + } + + /** + * Attempts to convert an array to YAML + * @access private + * @return string + * @param $array The array you want to convert + * @param $indent The indent of the current level + */ + private function _yamlizeArray($array,$indent) { + if (is_array($array)) { + $string = ''; + $previous_key = -1; + foreach ($array as $key => $value) { + if (!isset($first_key)) $first_key = $key; + $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key, $array); + $previous_key = $key; + } + return $string; + } else { + return false; + } + } + + /** + * Returns YAML from a key and a value + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + private function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0, $source_array = null) { + // do some folding here, for blocks + if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false || + strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value, '%') !== false || strpos ($value, ' ') !== false || + strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value, "'") !== false || strpos($value, "!") === 0 || + substr ($value, -1, 1) == ':') + ) { + $value = $this->_doLiteralBlock($value,$indent); + } else { + $value = $this->_doFolding($value,$indent); + } + + if ($value === array()) $value = '[ ]'; + if ($value === "") $value = '""'; + if (self::isTranslationWord($value)) { + $value = $this->_doLiteralBlock($value, $indent); + } + if (trim ($value) != $value) + $value = $this->_doLiteralBlock($value,$indent); + + if (is_bool($value)) { + $value = $value ? "true" : "false"; + } + + if ($value === null) $value = 'null'; + if ($value === "'" . self::REMPTY . "'") $value = null; + + $spaces = str_repeat(' ',$indent); + + //if (is_int($key) && $key - 1 == $previous_key && $first_key===0) { + if (is_array ($source_array) && array_keys($source_array) === range(0, count($source_array) - 1)) { + // It's a sequence + $string = $spaces.'- '.$value."\n"; + } else { + // if ($first_key===0) throw new Exception('Keys are all screwy. The first one was zero, now it\'s "'. $key .'"'); + // It's mapped + if (strpos($key, ":") !== false || strpos($key, "#") !== false) { $key = '"' . $key . '"'; } + $string = rtrim ($spaces.$key.': '.$value)."\n"; + } + return $string; + } + + /** + * Creates a literal block for dumping + * @access private + * @return string + * @param $value + * @param $indent int The value of the indent + */ + private function _doLiteralBlock($value,$indent) { + if ($value === "\n") return '\n'; + if (strpos($value, "\n") === false && strpos($value, "'") === false) { + return sprintf ("'%s'", $value); + } + if (strpos($value, "\n") === false && strpos($value, '"') === false) { + return sprintf ('"%s"', $value); + } + $exploded = explode("\n",$value); + $newValue = '|'; + if (isset($exploded[0]) && ($exploded[0] == "|" || $exploded[0] == "|-" || $exploded[0] == ">")) { + $newValue = $exploded[0]; + unset($exploded[0]); + } + $indent += $this->_dumpIndent; + $spaces = str_repeat(' ',$indent); + foreach ($exploded as $line) { + $line = trim($line); + if (strpos($line, '"') === 0 && strrpos($line, '"') == (strlen($line)-1) || strpos($line, "'") === 0 && strrpos($line, "'") == (strlen($line)-1)) { + $line = substr($line, 1, -1); + } + $newValue .= "\n" . $spaces . ($line); + } + return $newValue; + } + + /** + * Folds a string of text, if necessary + * @access private + * @return string + * @param $value The string you wish to fold + */ + private function _doFolding($value,$indent) { + // Don't do anything if wordwrap is set to 0 + + if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) { + $indent += $this->_dumpIndent; + $indent = str_repeat(' ',$indent); + $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent"); + $value = ">\n".$indent.$wrapped; + } else { + if ($this->setting_dump_force_quotes && is_string ($value) && $value !== self::REMPTY) + $value = '"' . $value . '"'; + if (is_numeric($value) && is_string($value)) + $value = '"' . $value . '"'; + } + + + return $value; + } + + private function isTrueWord($value) { + $words = self::getTranslations(array('true', 'on', 'yes', 'y')); + return in_array($value, $words, true); + } + + private function isFalseWord($value) { + $words = self::getTranslations(array('false', 'off', 'no', 'n')); + return in_array($value, $words, true); + } + + private function isNullWord($value) { + $words = self::getTranslations(array('null', '~')); + return in_array($value, $words, true); + } + + private function isTranslationWord($value) { + return ( + self::isTrueWord($value) || + self::isFalseWord($value) || + self::isNullWord($value) + ); + } + + /** + * Coerce a string into a native type + * Reference: http://yaml.org/type/bool.html + * TODO: Use only words from the YAML spec. + * @access private + * @param $value The value to coerce + */ + private function coerceValue(&$value) { + if (self::isTrueWord($value)) { + $value = true; + } else if (self::isFalseWord($value)) { + $value = false; + } else if (self::isNullWord($value)) { + $value = null; + } + } + + /** + * Given a set of words, perform the appropriate translations on them to + * match the YAML 1.1 specification for type coercing. + * @param $words The words to translate + * @access private + */ + private static function getTranslations(array $words) { + $result = array(); + foreach ($words as $i) { + $result = array_merge($result, array(ucfirst($i), strtoupper($i), strtolower($i))); + } + return $result; + } + +// LOADING FUNCTIONS + + private function _load($input) { + $Source = $this->loadFromSource($input); + return $this->loadWithSource($Source); + } + + private function _loadString($input) { + $Source = $this->loadFromString($input); + return $this->loadWithSource($Source); + } + + private function loadWithSource($Source) { + if (empty ($Source)) return array(); + if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) { + $array = syck_load (implode ("\n", $Source)); + return is_array($array) ? $array : array(); + } + + $this->path = array(); + $this->result = array(); + + $cnt = count($Source); + for ($i = 0; $i < $cnt; $i++) { + $line = $Source[$i]; + + $this->indent = strlen($line) - strlen(ltrim($line)); + $tempPath = $this->getParentPathByIndent($this->indent); + $line = self::stripIndent($line, $this->indent); + if (self::isComment($line)) continue; + if (self::isEmpty($line)) continue; + $this->path = $tempPath; + + $literalBlockStyle = self::startsLiteralBlock($line); + if ($literalBlockStyle) { + $line = rtrim ($line, $literalBlockStyle . " \n"); + $literalBlock = ''; + $line .= ' '.$this->LiteralPlaceHolder; + $literal_block_indent = strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1])); + while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) { + $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle, $literal_block_indent); + } + $i--; + } + + // Strip out comments + if (strpos ($line, '#')) { + $line = preg_replace('/\s*#([^"\']+)$/','',$line); + } + + while (++$i < $cnt && self::greedilyNeedNextLine($line)) { + $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t"); + } + $i--; + + $lineArray = $this->_parseLine($line); + + if ($literalBlockStyle) + $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock); + + $this->addArray($lineArray, $this->indent); + + foreach ($this->delayedPath as $indent => $delayedPath) + $this->path[$indent] = $delayedPath; + + $this->delayedPath = array(); + + } + return $this->result; + } + + private function loadFromSource ($input) { + if (!empty($input) && strpos($input, "\n") === false && file_exists($input)) + $input = file_get_contents($input); + + return $this->loadFromString($input); + } + + private function loadFromString ($input) { + $lines = explode("\n",$input); + foreach ($lines as $k => $_) { + $lines[$k] = rtrim ($_, "\r"); + } + return $lines; + } + + /** + * Parses YAML code and returns an array for a node + * @access private + * @return array + * @param string $line A line from the YAML file + */ + private function _parseLine($line) { + if (!$line) return array(); + $line = trim($line); + if (!$line) return array(); + + $array = array(); + + $group = $this->nodeContainsGroup($line); + if ($group) { + $this->addGroup($line, $group); + $line = $this->stripGroup ($line, $group); + } + + if ($this->startsMappedSequence($line)) { + return $this->returnMappedSequence($line); + } + + if ($this->startsMappedValue($line)) { + return $this->returnMappedValue($line); + } + + if ($this->isArrayElement($line)) + return $this->returnArrayElement($line); + + if ($this->isPlainArray($line)) + return $this->returnPlainArray($line); + + return $this->returnKeyValuePair($line); + + } + + /** + * Finds the type of the passed value, returns the value as the new type. + * @access private + * @param string $value + * @return mixed + */ + private function _toType($value) { + if ($value === '') return ""; + + if ($this->setting_empty_hash_as_object && $value === '{}') { + return new stdClass(); + } + + $first_character = $value[0]; + $last_character = substr($value, -1, 1); + + $is_quoted = false; + do { + if (!$value) break; + if ($first_character != '"' && $first_character != "'") break; + if ($last_character != '"' && $last_character != "'") break; + $is_quoted = true; + } while (0); + + if ($is_quoted) { + $value = str_replace('\n', "\n", $value); + if ($first_character == "'") + return strtr(substr ($value, 1, -1), array ('\'\'' => '\'', '\\\''=> '\'')); + return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\\\''=> '\'')); + } + + if (strpos($value, ' #') !== false && !$is_quoted) + $value = preg_replace('/\s+#(.+)$/','',$value); + + if ($first_character == '[' && $last_character == ']') { + // Take out strings sequences and mappings + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $value = array(); + foreach ($explode as $v) { + $value[] = $this->_toType($v); + } + return $value; + } + + if (strpos($value,': ')!==false && $first_character != '{') { + $array = explode(': ',$value); + $key = trim($array[0]); + array_shift($array); + $value = trim(implode(': ',$array)); + $value = $this->_toType($value); + return array($key => $value); + } + + if ($first_character == '{' && $last_character == '}') { + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + // Inline Mapping + // Take out strings sequences and mappings + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $array = array(); + foreach ($explode as $v) { + $SubArr = $this->_toType($v); + if (empty($SubArr)) continue; + if (is_array ($SubArr)) { + $array[key($SubArr)] = $SubArr[key($SubArr)]; continue; + } + $array[] = $SubArr; + } + return $array; + } + + if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') { + return null; + } + + if ( is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/', $value) ){ + $intvalue = (int)$value; + if ($intvalue != PHP_INT_MAX && $intvalue != ~PHP_INT_MAX) + $value = $intvalue; + return $value; + } + + if ( is_string($value) && preg_match('/^0[xX][0-9a-fA-F]+$/', $value)) { + // Hexadecimal value. + return hexdec($value); + } + + $this->coerceValue($value); + + if (is_numeric($value)) { + if ($value === '0') return 0; + if (rtrim ($value, 0) === $value) + $value = (float)$value; + return $value; + } + + return $value; + } + + /** + * Used in inlines to check for more inlines or quoted strings + * @access private + * @return array + */ + private function _inlineEscape($inline) { + // There's gotta be a cleaner way to do this... + // While pure sequences seem to be nesting just fine, + // pure mappings and mappings with sequences inside can't go very + // deep. This needs to be fixed. + + $seqs = array(); + $maps = array(); + $saved_strings = array(); + $saved_empties = array(); + + // Check for empty strings + $regex = '/("")|(\'\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_empties = $strings[0]; + $inline = preg_replace($regex,'YAMLEmpty',$inline); + } + unset($regex); + + // Check for strings + $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_strings = $strings[0]; + $inline = preg_replace($regex,'YAMLString',$inline); + } + unset($regex); + + // echo $inline; + + $i = 0; + do { + + // Check for sequences + while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) { + $seqs[] = $matchseqs[0]; + $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1); + } + + // Check for mappings + while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) { + $maps[] = $matchmaps[0]; + $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1); + } + + if ($i++ >= 10) break; + + } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false); + + $explode = explode(',',$inline); + $explode = array_map('trim', $explode); + $stringi = 0; $i = 0; + + while (1) { + + // Re-add the sequences + if (!empty($seqs)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + foreach ($seqs as $seqk => $seq) { + $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value); + $value = $explode[$key]; + } + } + } + } + + // Re-add the mappings + if (!empty($maps)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLMap') !== false) { + foreach ($maps as $mapk => $map) { + $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value); + $value = $explode[$key]; + } + } + } + } + + + // Re-add the strings + if (!empty($saved_strings)) { + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLString') !== false) { + $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1); + unset($saved_strings[$stringi]); + ++$stringi; + $value = $explode[$key]; + } + } + } + + + // Re-add the empties + if (!empty($saved_empties)) { + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLEmpty') !== false) { + $explode[$key] = preg_replace('/YAMLEmpty/', '', $value, 1); + $value = $explode[$key]; + } + } + } + + $finished = true; + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLMap') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLString') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLEmpty') !== false) { + $finished = false; break; + } + } + if ($finished) break; + + $i++; + if ($i > 10) + break; // Prevent infinite loops. + } + + + return $explode; + } + + private function literalBlockContinues ($line, $lineIndent) { + if (!trim($line)) return true; + if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true; + return false; + } + + private function referenceContentsByAlias ($alias) { + do { + if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; } + $groupPath = $this->SavedGroups[$alias]; + $value = $this->result; + foreach ($groupPath as $k) { + $value = $value[$k]; + } + } while (false); + return $value; + } + + private function addArrayInline ($array, $indent) { + $CommonGroupPath = $this->path; + if (empty ($array)) return false; + + foreach ($array as $k => $_) { + $this->addArray(array($k => $_), $indent); + $this->path = $CommonGroupPath; + } + return true; + } + + private function addArray ($incoming_data, $incoming_indent) { + + // print_r ($incoming_data); + + if (count ($incoming_data) > 1) + return $this->addArrayInline ($incoming_data, $incoming_indent); + + $key = key ($incoming_data); + $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null; + if ($key === '__!YAMLZero') $key = '0'; + + if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values. + if ($key || $key === '' || $key === '0') { + $this->result[$key] = $value; + } else { + $this->result[] = $value; end ($this->result); $key = key ($this->result); + } + $this->path[$incoming_indent] = $key; + return; + } + + + + $history = array(); + // Unfolding inner array tree. + $history[] = $_arr = $this->result; + foreach ($this->path as $k) { + $history[] = $_arr = $_arr[$k]; + } + + if ($this->_containsGroupAlias) { + $value = $this->referenceContentsByAlias($this->_containsGroupAlias); + $this->_containsGroupAlias = false; + } + + + // Adding string or numeric key to the innermost level or $this->arr. + if (is_string($key) && $key == '<<') { + if (!is_array ($_arr)) { $_arr = array (); } + + $_arr = array_merge ($_arr, $value); + } else if ($key || $key === '' || $key === '0') { + if (!is_array ($_arr)) + $_arr = array ($key=>$value); + else + $_arr[$key] = $value; + } else { + if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; } + else { $_arr[] = $value; end ($_arr); $key = key ($_arr); } + } + + $reverse_path = array_reverse($this->path); + $reverse_history = array_reverse ($history); + $reverse_history[0] = $_arr; + $cnt = count($reverse_history) - 1; + for ($i = 0; $i < $cnt; $i++) { + $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i]; + } + $this->result = $reverse_history[$cnt]; + + $this->path[$incoming_indent] = $key; + + if ($this->_containsGroupAnchor) { + $this->SavedGroups[$this->_containsGroupAnchor] = $this->path; + if (is_array ($value)) { + $k = key ($value); + if (!is_int ($k)) { + $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k; + } + } + $this->_containsGroupAnchor = false; + } + + } + + private static function startsLiteralBlock ($line) { + $lastChar = substr (trim($line), -1); + if ($lastChar != '>' && $lastChar != '|') return false; + if ($lastChar == '|') return $lastChar; + // HTML tags should not be counted as literal blocks. + if (preg_match ('#<.*?>$#', $line)) return false; + return $lastChar; + } + + private static function greedilyNeedNextLine($line) { + $line = trim ($line); + if (!strlen($line)) return false; + if (substr ($line, -1, 1) == ']') return false; + if ($line[0] == '[') return true; + if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true; + return false; + } + + private function addLiteralLine ($literalBlock, $line, $literalBlockStyle, $indent = -1) { + $line = self::stripIndent($line, $indent); + if ($literalBlockStyle !== '|') { + $line = self::stripIndent($line); + } + $line = rtrim ($line, "\r\n\t ") . "\n"; + if ($literalBlockStyle == '|') { + return $literalBlock . $line; + } + if (strlen($line) == 0) + return rtrim($literalBlock, ' ') . "\n"; + if ($line == "\n" && $literalBlockStyle == '>') { + return rtrim ($literalBlock, " \t") . "\n"; + } + if ($line != "\n") + $line = trim ($line, "\r\n ") . " "; + return $literalBlock . $line; + } + + function revertLiteralPlaceHolder ($lineArray, $literalBlock) { + foreach ($lineArray as $k => $_) { + if (is_array($_)) + $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock); + else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder) + $lineArray[$k] = rtrim ($literalBlock, " \r\n"); + } + return $lineArray; + } + + private static function stripIndent ($line, $indent = -1) { + if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line)); + return substr ($line, $indent); + } + + private function getParentPathByIndent ($indent) { + if ($indent == 0) return array(); + $linePath = $this->path; + do { + end($linePath); $lastIndentInParentPath = key($linePath); + if ($indent <= $lastIndentInParentPath) array_pop ($linePath); + } while ($indent <= $lastIndentInParentPath); + return $linePath; + } + + + private function clearBiggerPathValues ($indent) { + + + if ($indent == 0) $this->path = array(); + if (empty ($this->path)) return true; + + foreach ($this->path as $k => $_) { + if ($k > $indent) unset ($this->path[$k]); + } + + return true; + } + + + private static function isComment ($line) { + if (!$line) return false; + if ($line[0] == '#') return true; + if (trim($line, " \r\n\t") == '---') return true; + return false; + } + + private static function isEmpty ($line) { + return (trim ($line) === ''); + } + + + private function isArrayElement ($line) { + if (!$line || !is_scalar($line)) return false; + if (substr($line, 0, 2) != '- ') return false; + if (strlen ($line) > 3) + if (substr($line,0,3) == '---') return false; + + return true; + } + + private function isHashElement ($line) { + return strpos($line, ':'); + } + + private function isLiteral ($line) { + if ($this->isArrayElement($line)) return false; + if ($this->isHashElement($line)) return false; + return true; + } + + + private static function unquote ($value) { + if (!$value) return $value; + if (!is_string($value)) return $value; + if ($value[0] == '\'') return trim ($value, '\''); + if ($value[0] == '"') return trim ($value, '"'); + return $value; + } + + private function startsMappedSequence ($line) { + return (substr($line, 0, 2) == '- ' && substr ($line, -1, 1) == ':'); + } + + private function returnMappedSequence ($line) { + $array = array(); + $key = self::unquote(trim(substr($line,1,-1))); + $array[$key] = array(); + $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key); + return array($array); + } + + private function checkKeysInValue($value) { + if (strchr('[{"\'', $value[0]) === false) { + if (strchr($value, ': ') !== false) { + throw new Exception('Too many keys: '.$value); + } + } + } + + private function returnMappedValue ($line) { + $this->checkKeysInValue($line); + $array = array(); + $key = self::unquote (trim(substr($line,0,-1))); + $array[$key] = ''; + return $array; + } + + private function startsMappedValue ($line) { + return (substr ($line, -1, 1) == ':'); + } + + private function isPlainArray ($line) { + return ($line[0] == '[' && substr ($line, -1, 1) == ']'); + } + + private function returnPlainArray ($line) { + return $this->_toType($line); + } + + private function returnKeyValuePair ($line) { + $array = array(); + $key = ''; + if (strpos ($line, ': ')) { + // It's a key/value pair most likely + // If the key is in double quotes pull it out + if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) { + $value = trim(str_replace($matches[1],'',$line)); + $key = $matches[2]; + } else { + // Do some guesswork as to the key and the value + $explode = explode(': ', $line); + $key = trim(array_shift($explode)); + $value = trim(implode(': ', $explode)); + $this->checkKeysInValue($value); + } + // Set the type of the value. Int, string, etc + $value = $this->_toType($value); + + if ($key === '0') $key = '__!YAMLZero'; + $array[$key] = $value; + } else { + $array = array ($line); + } + return $array; + + } + + + private function returnArrayElement ($line) { + if (strlen($line) <= 1) return array(array()); // Weird %) + $array = array(); + $value = trim(substr($line,1)); + $value = $this->_toType($value); + if ($this->isArrayElement($value)) { + $value = $this->returnArrayElement($value); + } + $array[] = $value; + return $array; + } + + + private function nodeContainsGroup ($line) { + $symbolsForReference = 'A-z0-9_\-'; + if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-) + if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1]; + if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1]; + if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1]; + return false; + + } + + private function addGroup ($line, $group) { + if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1); + if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1); + //print_r ($this->path); + } + + private function stripGroup ($line, $group) { + $line = trim(str_replace($group, '', $line)); + return $line; + } +} +} + +// Enable use of Spyc from command line +// The syntax is the following: php Spyc.php spyc.yaml + +do { + if (PHP_SAPI != 'cli') break; + if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break; + if (empty ($_SERVER['PHP_SELF']) || FALSE === strpos ($_SERVER['PHP_SELF'], 'Spyc.php') ) break; + $file = $argv[1]; + echo json_encode (spyc_load_file ($file)); +} while (0); diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/composer.json b/data/web/inc/lib/vendor/mustangostang/spyc/composer.json new file mode 100644 index 0000000000..e5ab776848 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/composer.json @@ -0,0 +1,30 @@ +{ + "name": "mustangostang/spyc", + "description": "A simple YAML loader/dumper class for PHP", + "type": "library", + "keywords": [ + "spyc", + "yaml", + "yml" + ], + "homepage": "https://github.com/mustangostang/spyc/", + "authors" : [{ + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + }], + "license": "MIT", + "require": { + "php": ">=5.3.1" + }, + "autoload": { + "files": [ "Spyc.php" ] + }, + "require-dev": { + "phpunit/phpunit": "4.3.*@dev" + }, + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + } +} diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/examples/yaml-dump.php b/data/web/inc/lib/vendor/mustangostang/spyc/examples/yaml-dump.php new file mode 100644 index 0000000000..9d2160aaf8 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/examples/yaml-dump.php @@ -0,0 +1,25 @@ + 'A sequence','second' => 'of mapped values'); +$array['Mapped'] = array('A sequence','which is mapped'); +$array['A Note'] = 'What if your text is too long?'; +$array['Another Note'] = 'If that is the case, the dumper will probably fold your text by using a block. Kinda like this.'; +$array['The trick?'] = 'The trick is that we overrode the default indent, 2, to 4 and the default wordwrap, 40, to 60.'; +$array['Old Dog'] = "And if you want\n to preserve line breaks, \ngo ahead!"; +$array['key:withcolon'] = "Should support this to"; + +$yaml = Spyc::YAMLDump($array,4,60); diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/examples/yaml-load.php b/data/web/inc/lib/vendor/mustangostang/spyc/examples/yaml-load.php new file mode 100644 index 0000000000..9e457e1e15 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/examples/yaml-load.php @@ -0,0 +1,21 @@ +spyc.yaml loaded into PHP:
    '; +print_r($array); +echo ''; + + +echo '
    YAML Data dumped back:
    '; +echo Spyc::YAMLDump($array); +echo '
    '; diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/php4/5to4.php b/data/web/inc/lib/vendor/mustangostang/spyc/php4/5to4.php new file mode 100644 index 0000000000..5a48694f01 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/php4/5to4.php @@ -0,0 +1,17 @@ +', $code); + $f = fopen ($dest, 'w'); + fwrite($f, $code); + fclose ($f); + print "Written to $dest.\n"; +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/php4/spyc.php4 b/data/web/inc/lib/vendor/mustangostang/spyc/php4/spyc.php4 new file mode 100644 index 0000000000..73f08cc079 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/php4/spyc.php4 @@ -0,0 +1,1023 @@ + + * @author Chris Wanstrath + * @link http://code.google.com/p/spyc/ + * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2009 Vlad Andersen + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @package Spyc + */ + +if (!function_exists('spyc_load')) { + /** + * Parses YAML to array. + * @param string $string YAML string. + * @return array + */ + function spyc_load ($string) { + return Spyc::YAMLLoadString($string); + } +} + +if (!function_exists('spyc_load_file')) { + /** + * Parses YAML to array. + * @param string $file Path to YAML file. + * @return array + */ + function spyc_load_file ($file) { + return Spyc::YAMLLoad($file); + } +} + +/** + * The Simple PHP YAML Class. + * + * This class can be used to read a YAML file and convert its contents + * into a PHP array. It currently supports a very limited subsection of + * the YAML spec. + * + * Usage: + * + * $Spyc = new Spyc; + * $array = $Spyc->load($file); + * + * or: + * + * $array = Spyc::YAMLLoad($file); + * + * or: + * + * $array = spyc_load_file($file); + * + * @package Spyc + */ +class Spyc { + + // SETTINGS + + /** + * Setting this to true will force YAMLDump to enclose any string value in + * quotes. False by default. + * + * @var bool + */ + var $setting_dump_force_quotes = false; + + /** + * Setting this to true will forse YAMLLoad to use syck_load function when + * possible. False by default. + * @var bool + */ + var $setting_use_syck_is_possible = false; + + + + /**#@+ + * @access private + * @var mixed + */ + var $_dumpIndent; + var $_dumpWordWrap; + var $_containsGroupAnchor = false; + var $_containsGroupAlias = false; + var $path; + var $result; + var $LiteralPlaceHolder = '___YAML_Literal_Block___'; + var $SavedGroups = array(); + var $indent; + /** + * Path modifier that should be applied after adding current element. + * @var array + */ + var $delayedPath = array(); + + /**#@+ + * @access public + * @var mixed + */ + var $_nodeId; + +/** + * Load a valid YAML string to Spyc. + * @param string $input + * @return array + */ + function load ($input) { + return $this->__loadString($input); + } + + /** + * Load a valid YAML file to Spyc. + * @param string $file + * @return array + */ + function loadFile ($file) { + return $this->__load($file); + } + + /** + * Load YAML into a PHP array statically + * + * The load method, when supplied with a YAML stream (string or file), + * will do its best to convert YAML in a file into a PHP array. Pretty + * simple. + * Usage: + * + * $array = Spyc::YAMLLoad('lucky.yaml'); + * print_r($array); + * + * @access public + * @return array + * @param string $input Path of YAML file or string containing YAML + */ + function YAMLLoad($input) { + $Spyc = new Spyc; + return $Spyc->__load($input); + } + + /** + * Load a string of YAML into a PHP array statically + * + * The load method, when supplied with a YAML string, will do its best + * to convert YAML in a string into a PHP array. Pretty simple. + * + * Note: use this function if you don't want files from the file system + * loaded and processed as YAML. This is of interest to people concerned + * about security whose input is from a string. + * + * Usage: + * + * $array = Spyc::YAMLLoadString("---\n0: hello world\n"); + * print_r($array); + * + * @access public + * @return array + * @param string $input String containing YAML + */ + function YAMLLoadString($input) { + $Spyc = new Spyc; + return $Spyc->__loadString($input); + } + + /** + * Dump YAML from PHP array statically + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as nothing.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + */ + function YAMLDump($array,$indent = false,$wordwrap = false) { + $spyc = new Spyc; + return $spyc->dump($array,$indent,$wordwrap); + } + + + /** + * Dump PHP array to YAML + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as tasteful.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + */ + function dump($array,$indent = false,$wordwrap = false) { + // Dumps to some very clean YAML. We'll have to add some more features + // and options soon. And better support for folding. + + // New features and options. + if ($indent === false or !is_numeric($indent)) { + $this->_dumpIndent = 2; + } else { + $this->_dumpIndent = $indent; + } + + if ($wordwrap === false or !is_numeric($wordwrap)) { + $this->_dumpWordWrap = 40; + } else { + $this->_dumpWordWrap = $wordwrap; + } + + // New YAML document + $string = "---\n"; + + // Start at the base of the array and move through it. + if ($array) { + $array = (array)$array; + $first_key = key($array); + + $previous_key = -1; + foreach ($array as $key => $value) { + $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key); + $previous_key = $key; + } + } + return $string; + } + + /** + * Attempts to convert a key / value array item to YAML + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0) { + if (is_array($value)) { + if (empty ($value)) + return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key); + // It has children. What to do? + // Make it the right kind of item + $string = $this->_dumpNode($key, NULL, $indent, $previous_key, $first_key); + // Add the indent + $indent += $this->_dumpIndent; + // Yamlize the array + $string .= $this->_yamlizeArray($value,$indent); + } elseif (!is_array($value)) { + // It doesn't have children. Yip. + $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key); + } + return $string; + } + + /** + * Attempts to convert an array to YAML + * @access private + * @return string + * @param $array The array you want to convert + * @param $indent The indent of the current level + */ + function _yamlizeArray($array,$indent) { + if (is_array($array)) { + $string = ''; + $previous_key = -1; + $first_key = key($array); + foreach ($array as $key => $value) { + $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key); + $previous_key = $key; + } + return $string; + } else { + return false; + } + } + + /** + * Returns YAML from a key and a value + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0) { + // do some folding here, for blocks + if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false || + strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || + strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || substr ($value, -1, 1) == ':')) { + $value = $this->_doLiteralBlock($value,$indent); + } else { + $value = $this->_doFolding($value,$indent); + if (is_bool($value)) { + $value = ($value) ? "true" : "false"; + } + } + + if ($value === array()) $value = '[ ]'; + + $spaces = str_repeat(' ',$indent); + + if (is_int($key) && $key - 1 == $previous_key && $first_key===0) { + // It's a sequence + $string = $spaces.'- '.$value."\n"; + } else { + if ($first_key===0) throw new Exception('Keys are all screwy. The first one was zero, now it\'s "'. $key .'"'); + // It's mapped + if (strpos($key, ":") !== false) { $key = '"' . $key . '"'; } + $string = $spaces.$key.': '.$value."\n"; + } + return $string; + } + + /** + * Creates a literal block for dumping + * @access private + * @return string + * @param $value + * @param $indent int The value of the indent + */ + function _doLiteralBlock($value,$indent) { + if (strpos($value, "\n") === false && strpos($value, "'") === false) { + return sprintf ("'%s'", $value); + } + if (strpos($value, "\n") === false && strpos($value, '"') === false) { + return sprintf ('"%s"', $value); + } + $exploded = explode("\n",$value); + $newValue = '|'; + $indent += $this->_dumpIndent; + $spaces = str_repeat(' ',$indent); + foreach ($exploded as $line) { + $newValue .= "\n" . $spaces . trim($line); + } + return $newValue; + } + + /** + * Folds a string of text, if necessary + * @access private + * @return string + * @param $value The string you wish to fold + */ + function _doFolding($value,$indent) { + // Don't do anything if wordwrap is set to 0 + + if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) { + $indent += $this->_dumpIndent; + $indent = str_repeat(' ',$indent); + $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent"); + $value = ">\n".$indent.$wrapped; + } else { + if ($this->setting_dump_force_quotes && is_string ($value)) + $value = '"' . $value . '"'; + } + + + return $value; + } + +// LOADING FUNCTIONS + + function __load($input) { + $Source = $this->loadFromSource($input); + return $this->loadWithSource($Source); + } + + function __loadString($input) { + $Source = $this->loadFromString($input); + return $this->loadWithSource($Source); + } + + function loadWithSource($Source) { + if (empty ($Source)) return array(); + if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) { + $array = syck_load (implode ('', $Source)); + return is_array($array) ? $array : array(); + } + + $this->path = array(); + $this->result = array(); + + $cnt = count($Source); + for ($i = 0; $i < $cnt; $i++) { + $line = $Source[$i]; + + $this->indent = strlen($line) - strlen(ltrim($line)); + $tempPath = $this->getParentPathByIndent($this->indent); + $line = $this->stripIndent($line, $this->indent); + if ($this->isComment($line)) continue; + if ($this->isEmpty($line)) continue; + $this->path = $tempPath; + + $literalBlockStyle = $this->startsLiteralBlock($line); + if ($literalBlockStyle) { + $line = rtrim ($line, $literalBlockStyle . " \n"); + $literalBlock = ''; + $line .= $this->LiteralPlaceHolder; + + while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) { + $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle); + } + $i--; + } + + while (++$i < $cnt && $this->greedilyNeedNextLine($line)) { + $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t"); + } + $i--; + + + + if (strpos ($line, '#')) { + if (strpos ($line, '"') === false && strpos ($line, "'") === false) + $line = preg_replace('/\s+#(.+)$/','',$line); + } + + $lineArray = $this->_parseLine($line); + + if ($literalBlockStyle) + $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock); + + $this->addArray($lineArray, $this->indent); + + foreach ($this->delayedPath as $indent => $delayedPath) + $this->path[$indent] = $delayedPath; + + $this->delayedPath = array(); + + } + return $this->result; + } + + function loadFromSource ($input) { + if (!empty($input) && strpos($input, "\n") === false && file_exists($input)) + return file($input); + + return $this->loadFromString($input); + } + + function loadFromString ($input) { + $lines = explode("\n",$input); + foreach ($lines as $k => $_) { + $lines[$k] = rtrim ($_, "\r"); + } + return $lines; + } + + /** + * Parses YAML code and returns an array for a node + * @access private + * @return array + * @param string $line A line from the YAML file + */ + function _parseLine($line) { + if (!$line) return array(); + $line = trim($line); + + if (!$line) return array(); + $array = array(); + + $group = $this->nodeContainsGroup($line); + if ($group) { + $this->addGroup($line, $group); + $line = $this->stripGroup ($line, $group); + } + + if ($this->startsMappedSequence($line)) + return $this->returnMappedSequence($line); + + if ($this->startsMappedValue($line)) + return $this->returnMappedValue($line); + + if ($this->isArrayElement($line)) + return $this->returnArrayElement($line); + + if ($this->isPlainArray($line)) + return $this->returnPlainArray($line); + + + return $this->returnKeyValuePair($line); + + } + + /** + * Finds the type of the passed value, returns the value as the new type. + * @access private + * @param string $value + * @return mixed + */ + function _toType($value) { + if ($value === '') return null; + $first_character = $value[0]; + $last_character = substr($value, -1, 1); + + $is_quoted = false; + do { + if (!$value) break; + if ($first_character != '"' && $first_character != "'") break; + if ($last_character != '"' && $last_character != "'") break; + $is_quoted = true; + } while (0); + + if ($is_quoted) + return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\'\'' => '\'', '\\\'' => '\'')); + + if (strpos($value, ' #') !== false) + $value = preg_replace('/\s+#(.+)$/','',$value); + + if ($first_character == '[' && $last_character == ']') { + // Take out strings sequences and mappings + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $value = array(); + foreach ($explode as $v) { + $value[] = $this->_toType($v); + } + return $value; + } + + if (strpos($value,': ')!==false && $first_character != '{') { + $array = explode(': ',$value); + $key = trim($array[0]); + array_shift($array); + $value = trim(implode(': ',$array)); + $value = $this->_toType($value); + return array($key => $value); + } + + if ($first_character == '{' && $last_character == '}') { + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + // Inline Mapping + // Take out strings sequences and mappings + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $array = array(); + foreach ($explode as $v) { + $SubArr = $this->_toType($v); + if (empty($SubArr)) continue; + if (is_array ($SubArr)) { + $array[key($SubArr)] = $SubArr[key($SubArr)]; continue; + } + $array[] = $SubArr; + } + return $array; + } + + if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') { + return null; + } + + if (intval($first_character) > 0 && preg_match ('/^[1-9]+[0-9]*$/', $value)) { + $intvalue = (int)$value; + if ($intvalue != PHP_INT_MAX) + $value = $intvalue; + return $value; + } + + if (in_array($value, + array('true', 'on', '+', 'yes', 'y', 'True', 'TRUE', 'On', 'ON', 'YES', 'Yes', 'Y'))) { + return true; + } + + if (in_array(strtolower($value), + array('false', 'off', '-', 'no', 'n'))) { + return false; + } + + if (is_numeric($value)) { + if ($value === '0') return 0; + if (trim ($value, 0) === $value) + $value = (float)$value; + return $value; + } + + return $value; + } + + /** + * Used in inlines to check for more inlines or quoted strings + * @access private + * @return array + */ + function _inlineEscape($inline) { + // There's gotta be a cleaner way to do this... + // While pure sequences seem to be nesting just fine, + // pure mappings and mappings with sequences inside can't go very + // deep. This needs to be fixed. + + $seqs = array(); + $maps = array(); + $saved_strings = array(); + + // Check for strings + $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_strings = $strings[0]; + $inline = preg_replace($regex,'YAMLString',$inline); + } + unset($regex); + + $i = 0; + do { + + // Check for sequences + while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) { + $seqs[] = $matchseqs[0]; + $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1); + } + + // Check for mappings + while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) { + $maps[] = $matchmaps[0]; + $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1); + } + + if ($i++ >= 10) break; + + } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false); + + $explode = explode(', ',$inline); + $stringi = 0; $i = 0; + + while (1) { + + // Re-add the sequences + if (!empty($seqs)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + foreach ($seqs as $seqk => $seq) { + $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value); + $value = $explode[$key]; + } + } + } + } + + // Re-add the mappings + if (!empty($maps)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLMap') !== false) { + foreach ($maps as $mapk => $map) { + $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value); + $value = $explode[$key]; + } + } + } + } + + + // Re-add the strings + if (!empty($saved_strings)) { + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLString') !== false) { + $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1); + unset($saved_strings[$stringi]); + ++$stringi; + $value = $explode[$key]; + } + } + } + + $finished = true; + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLMap') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLString') !== false) { + $finished = false; break; + } + } + if ($finished) break; + + $i++; + if ($i > 10) + break; // Prevent infinite loops. + } + + return $explode; + } + + function literalBlockContinues ($line, $lineIndent) { + if (!trim($line)) return true; + if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true; + return false; + } + + function referenceContentsByAlias ($alias) { + do { + if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; } + $groupPath = $this->SavedGroups[$alias]; + $value = $this->result; + foreach ($groupPath as $k) { + $value = $value[$k]; + } + } while (false); + return $value; + } + + function addArrayInline ($array, $indent) { + $CommonGroupPath = $this->path; + if (empty ($array)) return false; + + foreach ($array as $k => $_) { + $this->addArray(array($k => $_), $indent); + $this->path = $CommonGroupPath; + } + return true; + } + + function addArray ($incoming_data, $incoming_indent) { + + // print_r ($incoming_data); + + if (count ($incoming_data) > 1) + return $this->addArrayInline ($incoming_data, $incoming_indent); + + $key = key ($incoming_data); + $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null; + if ($key === '__!YAMLZero') $key = '0'; + + if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values. + if ($key || $key === '' || $key === '0') { + $this->result[$key] = $value; + } else { + $this->result[] = $value; end ($this->result); $key = key ($this->result); + } + $this->path[$incoming_indent] = $key; + return; + } + + + + $history = array(); + // Unfolding inner array tree. + $history[] = $_arr = $this->result; + foreach ($this->path as $k) { + $history[] = $_arr = $_arr[$k]; + } + + if ($this->_containsGroupAlias) { + $value = $this->referenceContentsByAlias($this->_containsGroupAlias); + $this->_containsGroupAlias = false; + } + + + // Adding string or numeric key to the innermost level or $this->arr. + if (is_string($key) && $key == '<<') { + if (!is_array ($_arr)) { $_arr = array (); } + $_arr = array_merge ($_arr, $value); + } else if ($key || $key === '' || $key === '0') { + $_arr[$key] = $value; + } else { + if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; } + else { $_arr[] = $value; end ($_arr); $key = key ($_arr); } + } + + $reverse_path = array_reverse($this->path); + $reverse_history = array_reverse ($history); + $reverse_history[0] = $_arr; + $cnt = count($reverse_history) - 1; + for ($i = 0; $i < $cnt; $i++) { + $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i]; + } + $this->result = $reverse_history[$cnt]; + + $this->path[$incoming_indent] = $key; + + if ($this->_containsGroupAnchor) { + $this->SavedGroups[$this->_containsGroupAnchor] = $this->path; + if (is_array ($value)) { + $k = key ($value); + if (!is_int ($k)) { + $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k; + } + } + $this->_containsGroupAnchor = false; + } + + } + + function startsLiteralBlock ($line) { + $lastChar = substr (trim($line), -1); + if ($lastChar != '>' && $lastChar != '|') return false; + if ($lastChar == '|') return $lastChar; + // HTML tags should not be counted as literal blocks. + if (preg_match ('#<.*?>$#', $line)) return false; + return $lastChar; + } + + function greedilyNeedNextLine($line) { + $line = trim ($line); + if (!strlen($line)) return false; + if (substr ($line, -1, 1) == ']') return false; + if ($line[0] == '[') return true; + if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true; + return false; + } + + function addLiteralLine ($literalBlock, $line, $literalBlockStyle) { + $line = $this->stripIndent($line); + $line = rtrim ($line, "\r\n\t ") . "\n"; + if ($literalBlockStyle == '|') { + return $literalBlock . $line; + } + if (strlen($line) == 0) + return rtrim($literalBlock, ' ') . "\n"; + if ($line == "\n" && $literalBlockStyle == '>') { + return rtrim ($literalBlock, " \t") . "\n"; + } + if ($line != "\n") + $line = trim ($line, "\r\n ") . " "; + return $literalBlock . $line; + } + + function revertLiteralPlaceHolder ($lineArray, $literalBlock) { + foreach ($lineArray as $k => $_) { + if (is_array($_)) + $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock); + else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder) + $lineArray[$k] = rtrim ($literalBlock, " \r\n"); + } + return $lineArray; + } + + function stripIndent ($line, $indent = -1) { + if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line)); + return substr ($line, $indent); + } + + function getParentPathByIndent ($indent) { + if ($indent == 0) return array(); + $linePath = $this->path; + do { + end($linePath); $lastIndentInParentPath = key($linePath); + if ($indent <= $lastIndentInParentPath) array_pop ($linePath); + } while ($indent <= $lastIndentInParentPath); + return $linePath; + } + + + function clearBiggerPathValues ($indent) { + + + if ($indent == 0) $this->path = array(); + if (empty ($this->path)) return true; + + foreach ($this->path as $k => $_) { + if ($k > $indent) unset ($this->path[$k]); + } + + return true; + } + + + function isComment ($line) { + if (!$line) return false; + if ($line[0] == '#') return true; + if (trim($line, " \r\n\t") == '---') return true; + return false; + } + + function isEmpty ($line) { + return (trim ($line) === ''); + } + + + function isArrayElement ($line) { + if (!$line) return false; + if ($line[0] != '-') return false; + if (strlen ($line) > 3) + if (substr($line,0,3) == '---') return false; + + return true; + } + + function isHashElement ($line) { + return strpos($line, ':'); + } + + function isLiteral ($line) { + if ($this->isArrayElement($line)) return false; + if ($this->isHashElement($line)) return false; + return true; + } + + + function unquote ($value) { + if (!$value) return $value; + if (!is_string($value)) return $value; + if ($value[0] == '\'') return trim ($value, '\''); + if ($value[0] == '"') return trim ($value, '"'); + return $value; + } + + function startsMappedSequence ($line) { + return ($line[0] == '-' && substr ($line, -1, 1) == ':'); + } + + function returnMappedSequence ($line) { + $array = array(); + $key = $this->unquote(trim(substr($line,1,-1))); + $array[$key] = array(); + $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key); + return array($array); + } + + function returnMappedValue ($line) { + $array = array(); + $key = $this->unquote (trim(substr($line,0,-1))); + $array[$key] = ''; + return $array; + } + + function startsMappedValue ($line) { + return (substr ($line, -1, 1) == ':'); + } + + function isPlainArray ($line) { + return ($line[0] == '[' && substr ($line, -1, 1) == ']'); + } + + function returnPlainArray ($line) { + return $this->_toType($line); + } + + function returnKeyValuePair ($line) { + $array = array(); + $key = ''; + if (strpos ($line, ':')) { + // It's a key/value pair most likely + // If the key is in double quotes pull it out + if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) { + $value = trim(str_replace($matches[1],'',$line)); + $key = $matches[2]; + } else { + // Do some guesswork as to the key and the value + $explode = explode(':',$line); + $key = trim($explode[0]); + array_shift($explode); + $value = trim(implode(':',$explode)); + } + // Set the type of the value. Int, string, etc + $value = $this->_toType($value); + if ($key === '0') $key = '__!YAMLZero'; + $array[$key] = $value; + } else { + $array = array ($line); + } + return $array; + + } + + + function returnArrayElement ($line) { + if (strlen($line) <= 1) return array(array()); // Weird %) + $array = array(); + $value = trim(substr($line,1)); + $value = $this->_toType($value); + $array[] = $value; + return $array; + } + + + function nodeContainsGroup ($line) { + $symbolsForReference = 'A-z0-9_\-'; + if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-) + if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1]; + if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1]; + if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1]; + return false; + + } + + function addGroup ($line, $group) { + if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1); + if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1); + //print_r ($this->path); + } + + function stripGroup ($line, $group) { + $line = trim(str_replace($group, '', $line)); + return $line; + } +} + +// Enable use of Spyc from command line +// The syntax is the following: php spyc.php spyc.yaml + +define ('SPYC_FROM_COMMAND_LINE', false); + +do { + if (!SPYC_FROM_COMMAND_LINE) break; + if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break; + if (empty ($_SERVER['PHP_SELF']) || $_SERVER['PHP_SELF'] != 'spyc.php') break; + $file = $argv[1]; + printf ("Spyc loading file: %s\n", $file); + print_r (spyc_load_file ($file)); +} while (0); \ No newline at end of file diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/php4/test.php4 b/data/web/inc/lib/vendor/mustangostang/spyc/php4/test.php4 new file mode 100644 index 0000000000..315f5019d8 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/php4/test.php4 @@ -0,0 +1,162 @@ + "1.5ghz", "ram" => "1 gig", + "os" => "os x 10.4.1")) + die('Sequence 4 failed'); + +# Mapped sequence +if ($yaml['domains'] != array("yaml.org", "php.net")) + die("Key: 'domains' failed"); + +# A sequence like this. +if ($yaml[5] != array("program" => "Adium", "platform" => "OS X", + "type" => "Chat Client")) + die('Sequence 5 failed'); + +# A folded block as a mapped value +if ($yaml['no time'] != "There isn't any time for your tricks!\nDo you understand?") + die("Key: 'no time' failed"); + +# A literal block as a mapped value +if ($yaml['some time'] != "There is nothing but time\nfor your tricks.") + die("Key: 'some time' failed"); + +# Crazy combinations +if ($yaml['databases'] != array( array("name" => "spartan", "notes" => + array( "Needs to be backed up", + "Needs to be normalized" ), + "type" => "mysql" ))) + die("Key: 'databases' failed"); + +# You can be a bit tricky +if ($yaml["if: you'd"] != "like") + die("Key: 'if: you\'d' failed"); + +# Inline sequences +if ($yaml[6] != array("One", "Two", "Three", "Four")) + die("Sequence 6 failed"); + +# Nested Inline Sequences +if ($yaml[7] != array("One", array("Two", "And", "Three"), "Four", "Five")) + die("Sequence 7 failed"); + +# Nested Nested Inline Sequences +if ($yaml[8] != array( "This", array("Is", "Getting", array("Ridiculous", "Guys")), + "Seriously", array("Show", "Mercy"))) + die("Sequence 8 failed"); + +# Inline mappings +if ($yaml[9] != array("name" => "chris", "age" => "young", "brand" => "lucky strike")) + die("Sequence 9 failed"); + +# Nested inline mappings +if ($yaml[10] != array("name" => "mark", "age" => "older than chris", + "brand" => array("marlboro", "lucky strike"))) + die("Sequence 10 failed"); + +# References -- they're shaky, but functional +if ($yaml['dynamic languages'] != array('Perl', 'Python', 'PHP', 'Ruby')) + die("Key: 'dynamic languages' failed"); + +if ($yaml['compiled languages'] != array('C/C++', 'Java')) + die("Key: 'compiled languages' failed"); + +if ($yaml['all languages'] != array( + array('Perl', 'Python', 'PHP', 'Ruby'), + array('C/C++', 'Java') + )) + die("Key: 'all languages' failed"); + +# Added in .2.2: Escaped quotes +if ($yaml[11] != "you know, this shouldn't work. but it does.") + die("Sequence 11 failed."); + +if ($yaml[12] != "that's my value.") + die("Sequence 12 failed."); + +if ($yaml[13] != "again, that's my value.") + die("Sequence 13 failed."); + +if ($yaml[14] != "here's to \"quotes\", boss.") + die("Sequence 14 failed."); + +if ($yaml[15] != array( 'name' => "Foo, Bar's", 'age' => 20)) + die("Sequence 15 failed."); + +if ($yaml[16] != array( 0 => "a", 1 => array (0 => 1, 1 => 2), 2 => "b")) + die("Sequence 16 failed."); + +if ($yaml['endloop'] != "Does this line in the end indeed make Spyc go to an infinite loop?") + die("[endloop] failed."); + + +print "spyc.yaml parsed correctly\n"; + +?> \ No newline at end of file diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/phpunit.xml b/data/web/inc/lib/vendor/mustangostang/spyc/phpunit.xml new file mode 100644 index 0000000000..a6151af8e8 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/phpunit.xml @@ -0,0 +1,8 @@ + + + + + tests/ + + + diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/spyc.yaml b/data/web/inc/lib/vendor/mustangostang/spyc/spyc.yaml new file mode 100644 index 0000000000..489f28c9fb --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/spyc.yaml @@ -0,0 +1,219 @@ +# +# S P Y C +# a simple php yaml class +# +# authors: [vlad andersen (vlad.andersen@gmail.com), chris wanstrath (chris@ozmm.org)] +# websites: [http://www.yaml.org, http://spyc.sourceforge.net/] +# license: [MIT License, http://www.opensource.org/licenses/mit-license.php] +# copyright: (c) 2005-2006 Chris Wanstrath, 2006-2014 Vlad Andersen +# +# spyc.yaml - A file containing the YAML that Spyc understands. + +--- + +# Mappings - with proper types +String: Anyone's name, really. +Int: 13 +BadHex: f0xf3 +Hex: 0xf3 +True: true +False: false +Zero: 0 +Null: NULL +NotNull: 'null' +NotTrue: 'y' +NotBoolTrue: 'true' +NotInt: '5' +Float: 5.34 +Negative: -90 +SmallFloat: 0.7 +NewLine: \n +QuotedNewLine: "\n" + +# A sequence +- PHP Class +- Basic YAML Loader +- Very Basic YAML Dumper + +# A sequence of a sequence +- + - YAML is so easy to learn. + - Your config files will never be the same. + +# Sequence of mappings +- + cpu: 1.5ghz + ram: 1 gig + os : os x 10.4.1 + +# Mapped sequence +domains: + - yaml.org + - php.net + +# A sequence like this. +- program: Adium + platform: OS X + type: Chat Client + +# A folded block as a mapped value +no time: > + There isn't any time + for your tricks! + + Do you understand? + +# A literal block as a mapped value +some time: | + There is nothing but time + for your tricks. + +# Crazy combinations +databases: + - name: spartan + notes: + - Needs to be backed up + - Needs to be normalized + type: mysql + +# You can be a bit tricky +"if: you'd": like + +# Inline sequences +- [One, Two, Three, Four] + +# Nested Inline Sequences +- [One, [Two, And, Three], Four, Five] + +# Nested Nested Inline Sequences +- [This, [Is, Getting, [Ridiculous, Guys]], Seriously, [Show, Mercy]] + +# Inline mappings +- {name: chris, age: young, brand: lucky strike} + +# Nested inline mappings +- {name: mark, age: older than chris, brand: [marlboro, lucky strike]} + +# References -- they're shaky, but functional +dynamic languages: &DLANGS + - Perl + - Python + - PHP + - Ruby +compiled languages: &CLANGS + - C/C++ + - Java +all languages: + - *DLANGS + - *CLANGS + +# Added in .2.2: Escaped quotes +- you know, this shouldn't work. but it does. +- 'that''s my value.' +- 'again, that\'s my value.' +- "here's to \"quotes\", boss." + +# added in .2.3 +- {name: "Foo, Bar's", age: 20} + +# Added in .2.4: bug [ 1418193 ] Quote Values in Nested Arrays +- [a, ['1', "2"], b] + +# Add in .5.2: Quoted new line values. +- "First line\nSecond line\nThird line" + +# Added in .2.4: malformed YAML +all + javascripts: [dom1.js, dom.js] + +# Added in .2 +1040: Ooo, a numeric key! # And working comments? Wow! Colons in comments: a menace (0.3). + +hash_1: Hash #and a comment +hash_2: "Hash #and a comment" +"hash#3": "Hash (#) can appear in key too" + +float_test: 1.0 +float_test_with_quotes: '1.0' +float_inverse_test: 001 + +a_really_large_number: 115792089237316195423570985008687907853269984665640564039457584007913129639936 # 2^256 + +int array: [ 1, 2, 3 ] + +array on several lines: + [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ] + +morelesskey: "" + +array_of_zero: [0] +sophisticated_array_of_zero: {rx: {tx: [0]} } + +switches: + - { row: 0, col: 0, func: {tx: [0, 1]} } + +empty_sequence: [ ] +empty_hash: { } + +special_characters: "[{]]{{]]" + +asterisks: "*" + +empty_key: + : + key: value + +trailing_colon: "foo:" + +multiline_items: + - type: SomeItem + values: [blah, blah, blah, + blah] + ints: [2, 54, 12, + 2143] + +many_lines: | + A quick + fox + + + jumped + over + + + + + + a lazy + + + + dog + + +werte: + 1: nummer 1 + 0: Stunde 0 + +noindent_records: +- record1: value1 +- record2: value2 + +"a:1": [1000] +"a:2": + - 2000 +a:3: [3000] + +complex_unquoted_key: + a:b:''test': value + +array with commas: + ["0","1"] + +invoice: ["Something", "", '', "Something else"] +quotes: ['Something', "Nothing", 'Anything', "Thing"] + +# [Endloop] +endloop: | + Does this line in the end indeed make Spyc go to an infinite loop? diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/tests/DumpTest.php b/data/web/inc/lib/vendor/mustangostang/spyc/tests/DumpTest.php new file mode 100644 index 0000000000..ceb5c8e5b1 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/tests/DumpTest.php @@ -0,0 +1,194 @@ +files_to_test = array (__DIR__.'/../spyc.yaml', 'failing1.yaml', 'indent_1.yaml', 'quotes.yaml'); + } + + public function testShortSyntax() { + $dump = spyc_dump(array ('item1', 'item2', 'item3')); + $awaiting = "- item1\n- item2\n- item3\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testDump() { + foreach ($this->files_to_test as $file) { + $yaml = spyc_load(file_get_contents($file)); + $dump = Spyc::YAMLDump ($yaml); + $yaml_after_dump = Spyc::YAMLLoad ($dump); + $this->assertEquals ($yaml, $yaml_after_dump); + } + } + + public function testDumpWithQuotes() { + $Spyc = new Spyc(); + $Spyc->setting_dump_force_quotes = true; + foreach ($this->files_to_test as $file) { + $yaml = $Spyc->load(file_get_contents($file)); + $dump = $Spyc->dump ($yaml); + $yaml_after_dump = Spyc::YAMLLoad ($dump); + $this->assertEquals ($yaml, $yaml_after_dump); + } + } + + public function testDumpArrays() { + $dump = Spyc::YAMLDump(array ('item1', 'item2', 'item3')); + $awaiting = "---\n- item1\n- item2\n- item3\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testNull() { + $dump = Spyc::YAMLDump(array('a' => 1, 'b' => null, 'c' => 3)); + $awaiting = "---\na: 1\nb: null\nc: 3\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testNext() { + $array = array("aaa", "bbb", "ccc"); + #set arrays internal pointer to next element + next($array); + $dump = Spyc::YAMLDump($array); + $awaiting = "---\n- aaa\n- bbb\n- ccc\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testDumpingMixedArrays() { + $array = array(); + $array[] = 'Sequence item'; + $array['The Key'] = 'Mapped value'; + $array[] = array('A sequence','of a sequence'); + $array[] = array('first' => 'A sequence','second' => 'of mapped values'); + $array['Mapped'] = array('A sequence','which is mapped'); + $array['A Note'] = 'What if your text is too long?'; + $array['Another Note'] = 'If that is the case, the dumper will probably fold your text by using a block. Kinda like this.'; + $array['The trick?'] = 'The trick is that we overrode the default indent, 2, to 4 and the default wordwrap, 40, to 60.'; + $array['Old Dog'] = "And if you want\n to preserve line breaks, \ngo ahead!"; + $array['key:withcolon'] = "Should support this to"; + + $yaml = Spyc::YAMLDump($array,4,60); + } + + public function testMixed() { + $dump = Spyc::YAMLDump(array(0 => 1, 'b' => 2, 1 => 3)); + $awaiting = "---\n0: 1\nb: 2\n1: 3\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testDumpNumerics() { + $dump = Spyc::YAMLDump(array ('404', '405', '500')); + $awaiting = "---\n- \"404\"\n- \"405\"\n- \"500\"\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testDumpAsterisks() { + $dump = Spyc::YAMLDump(array ('*')); + $awaiting = "---\n- '*'\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testDumpAmpersands() { + $dump = Spyc::YAMLDump(array ('some' => '&foo')); + $awaiting = "---\nsome: '&foo'\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testDumpExclamations() { + $dump = Spyc::YAMLDump(array ('some' => '!foo')); + $awaiting = "---\nsome: '!foo'\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testDumpExclamations2() { + $dump = Spyc::YAMLDump(array ('some' => 'foo!')); + $awaiting = "---\nsome: foo!\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testDumpApostrophes() { + $dump = Spyc::YAMLDump(array ('some' => "'Biz' pimpt bedrijventerreinen")); + $awaiting = "---\nsome: \"'Biz' pimpt bedrijventerreinen\"\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testDumpNumericHashes() { + $dump = Spyc::YAMLDump(array ("titel"=> array("0" => "", 1 => "Dr.", 5 => "Prof.", 6 => "Prof. Dr."))); + $awaiting = "---\ntitel:\n 0: \"\"\n 1: Dr.\n 5: Prof.\n 6: Prof. Dr.\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testEmpty() { + $dump = Spyc::YAMLDump(array("foo" => array())); + $awaiting = "---\nfoo: [ ]\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testHashesInKeys() { + $dump = Spyc::YAMLDump(array ('#color' => '#ffffff')); + $awaiting = "---\n\"#color\": '#ffffff'\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testParagraph() { + $dump = Spyc::YAMLDump(array ('key' => "|\n value")); + $awaiting = "---\nkey: |\n value\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testParagraphTwo() { + $dump = Spyc::YAMLDump(array ('key' => 'Congrats, pimpt bedrijventerreinen pimpt bedrijventerreinen pimpt bedrijventerreinen!')); + $awaiting = "---\nkey: >\n Congrats, pimpt bedrijventerreinen pimpt\n bedrijventerreinen pimpt\n bedrijventerreinen!\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testString() { + $dump = Spyc::YAMLDump(array ('key' => array('key_one' => 'Congrats, pimpt bedrijventerreinen!'))); + $awaiting = "---\nkey:\n key_one: Congrats, pimpt bedrijventerreinen!\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testStringLong() { + $dump = Spyc::YAMLDump(array ('key' => array('key_one' => 'Congrats, pimpt bedrijventerreinen pimpt bedrijventerreinen pimpt bedrijventerreinen!'))); + $awaiting = "---\nkey:\n key_one: >\n Congrats, pimpt bedrijventerreinen pimpt\n bedrijventerreinen pimpt\n bedrijventerreinen!\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testStringDoubleQuote() { + $dump = Spyc::YAMLDump(array ('key' => array('key_one' => array('key_two' => '"Systรจme d\'e-rรฉservation"')))); + $awaiting = "---\nkey:\n key_one:\n key_two: |\n Systรจme d'e-rรฉservation\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testLongStringDoubleQuote() { + $dump = Spyc::YAMLDump(array ('key' => array('key_one' => array('key_two' => '"Systรจme d\'e-rรฉservation bedrijventerreinen pimpt" bedrijventerreinen!')))); + $awaiting = "---\nkey:\n key_one:\n key_two: |\n \"Systรจme d'e-rรฉservation bedrijventerreinen pimpt\" bedrijventerreinen!\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testStringStartingWithSpace() { + $dump = Spyc::YAMLDump(array ('key' => array('key_one' => " Congrats, pimpt bedrijventerreinen \n pimpt bedrijventerreinen pimpt bedrijventerreinen!"))); + $awaiting = "---\nkey:\n key_one: |\n Congrats, pimpt bedrijventerreinen\n pimpt bedrijventerreinen pimpt bedrijventerreinen!\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testPerCentOne() { + $dump = Spyc::YAMLDump(array ('key' => "%name%, pimpts bedrijventerreinen!")); + $awaiting = "---\nkey: '%name%, pimpts bedrijventerreinen!'\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testPerCentAndSimpleQuote() { + $dump = Spyc::YAMLDump(array ('key' => "%name%, pimpt's bedrijventerreinen!")); + $awaiting = "---\nkey: \"%name%, pimpt's bedrijventerreinen!\"\n"; + $this->assertEquals ($awaiting, $dump); + } + + public function testPerCentAndDoubleQuote() { + $dump = Spyc::YAMLDump(array ('key' => '%name%, pimpt\'s "bed"rijventerreinen!')); + $awaiting = "---\nkey: |\n %name%, pimpt's \"bed\"rijventerreinen!\n"; + $this->assertEquals ($awaiting, $dump); + } + +} diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/tests/IndentTest.php b/data/web/inc/lib/vendor/mustangostang/spyc/tests/IndentTest.php new file mode 100644 index 0000000000..b7cdcab7d3 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/tests/IndentTest.php @@ -0,0 +1,68 @@ +Y = Spyc::YAMLLoad(__DIR__."/indent_1.yaml"); + } + + public function testIndent_1() { + $this->assertEquals (array ('child_1' => 2, 'child_2' => 0, 'child_3' => 1), $this->Y['root']); + } + + public function testIndent_2() { + $this->assertEquals (array ('child_1' => 1, 'child_2' => 2), $this->Y['root2']); + } + + public function testIndent_3() { + $this->assertEquals (array (array ('resolutions' => array (1024 => 768, 1920 => 1200), 'producer' => 'Nec')), $this->Y['display']); + } + + public function testIndent_4() { + $this->assertEquals (array ( + array ('resolutions' => array (1024 => 768)), + array ('resolutions' => array (1920 => 1200)), + ), $this->Y['displays']); + } + + public function testIndent_5() { + $this->assertEquals (array (array ( + 'row' => 0, + 'col' => 0, + 'headsets_affected' => array ( + array ( + 'ports' => array (0), + 'side' => 'left', + ) + ), + 'switch_function' => array ( + 'ics_ptt' => true + ) + )), $this->Y['nested_hashes_and_seqs']); + } + + public function testIndent_6() { + $this->assertEquals (array ( + 'h' => array ( + array ('a' => 'b', 'a1' => 'b1'), + array ('c' => 'd') + ) + ), $this->Y['easier_nest']); + } + + public function testIndent_space() { + $this->assertEquals ("By four\n spaces", $this->Y['one_space']); + } + + public function testListAndComment() { + $this->assertEquals (array ('one', 'two', 'three'), $this->Y['list_and_comment']); + } + + public function testAnchorAndAlias() { + $this->assertEquals (array ('database' => 'rails_dev', 'adapter' => 'mysql', 'host' => 'localhost'), $this->Y['development']); + $this->assertEquals (array (1 => 'abc'), $this->Y['zzz']); + } + +} diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/tests/LoadTest.php b/data/web/inc/lib/vendor/mustangostang/spyc/tests/LoadTest.php new file mode 100644 index 0000000000..a25d36fe86 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/tests/LoadTest.php @@ -0,0 +1,17 @@ +assertEquals ($yaml, $yaml_loaded); + } + } +} diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/tests/ParseTest.php b/data/web/inc/lib/vendor/mustangostang/spyc/tests/ParseTest.php new file mode 100644 index 0000000000..2ce77ca852 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/tests/ParseTest.php @@ -0,0 +1,413 @@ +yaml = spyc_load_file(__DIR__.'/../spyc.yaml'); + } + + public function testMergeHashKeys() { + $Expected = array ( + array ('step' => array('instrument' => 'Lasik 2000', 'pulseEnergy' => 5.4, 'pulseDuration' => 12, 'repetition' => 1000, 'spotSize' => '1mm')), + array ('step' => array('instrument' => 'Lasik 2000', 'pulseEnergy' => 5.4, 'pulseDuration' => 12, 'repetition' => 1000, 'spotSize' => '2mm')), + ); + $Actual = spyc_load_file (__DIR__.'/indent_1.yaml'); + $this->assertEquals ($Expected, $Actual['steps']); + } + + public function testDeathMasks() { + $Expected = array ('sad' => 2, 'magnificent' => 4); + $Actual = spyc_load_file (__DIR__.'/indent_1.yaml'); + $this->assertEquals ($Expected, $Actual['death masks are']); + } + + public function testDevDb() { + $Expected = array ('adapter' => 'mysql', 'host' => 'localhost', 'database' => 'rails_dev'); + $Actual = spyc_load_file (__DIR__.'/indent_1.yaml'); + $this->assertEquals ($Expected, $Actual['development']); + } + + public function testNumericKey() { + $this->assertEquals ("Ooo, a numeric key!", $this->yaml[1040]); + } + + public function testMappingsString() { + $this->assertEquals ("Anyone's name, really.", $this->yaml['String']); + } + + public function testMappingsInt() { + $this->assertSame (13, $this->yaml['Int']); + } + + public function testMappingsHex() { + $this->assertSame (243, $this->yaml['Hex']); + $this->assertSame ('f0xf3', $this->yaml['BadHex']); + } + + public function testMappingsBooleanTrue() { + $this->assertSame (true, $this->yaml['True']); + } + + public function testMappingsBooleanFalse() { + $this->assertSame (false, $this->yaml['False']); + } + + public function testMappingsZero() { + $this->assertSame (0, $this->yaml['Zero']); + } + + public function testMappingsNull() { + $this->assertSame (null, $this->yaml['Null']); + } + + public function testMappingsNotNull() { + $this->assertSame ('null', $this->yaml['NotNull']); + } + + public function testMappingsFloat() { + $this->assertSame (5.34, $this->yaml['Float']); + } + + public function testMappingsNegative() { + $this->assertSame (-90, $this->yaml['Negative']); + } + + public function testMappingsSmallFloat() { + $this->assertSame (0.7, $this->yaml['SmallFloat']); + } + + public function testNewline() { + $this->assertSame ('\n', $this->yaml['NewLine']); + } + + public function testQuotedNewline() { + $this->assertSame ("\n", $this->yaml['QuotedNewLine']); + } + + public function testSeq0() { + $this->assertEquals ("PHP Class", $this->yaml[0]); + } + + public function testSeq1() { + $this->assertEquals ("Basic YAML Loader", $this->yaml[1]); + } + + public function testSeq2() { + $this->assertEquals ("Very Basic YAML Dumper", $this->yaml[2]); + } + + public function testSeq3() { + $this->assertEquals (array("YAML is so easy to learn.", + "Your config files will never be the same."), $this->yaml[3]); + } + + public function testSeqMap() { + $this->assertEquals (array("cpu" => "1.5ghz", "ram" => "1 gig", + "os" => "os x 10.4.1"), $this->yaml[4]); + } + + public function testMappedSequence() { + $this->assertEquals (array("yaml.org", "php.net"), $this->yaml['domains']); + } + + public function testAnotherSequence() { + $this->assertEquals (array("program" => "Adium", "platform" => "OS X", + "type" => "Chat Client"), $this->yaml[5]); + } + + public function testFoldedBlock() { + $this->assertEquals ("There isn't any time for your tricks!\nDo you understand?", $this->yaml['no time']); + } + + public function testLiteralAsMapped() { + $this->assertEquals ("There is nothing but time\nfor your tricks.", $this->yaml['some time']); + } + + public function testCrazy() { + $this->assertEquals (array( array("name" => "spartan", "notes" => + array( "Needs to be backed up", + "Needs to be normalized" ), + "type" => "mysql" )), $this->yaml['databases']); + } + + public function testColons() { + $this->assertEquals ("like", $this->yaml["if: you'd"]); + } + + public function testInline() { + $this->assertEquals (array("One", "Two", "Three", "Four"), $this->yaml[6]); + } + + public function testNestedInline() { + $this->assertEquals (array("One", array("Two", "And", "Three"), "Four", "Five"), $this->yaml[7]); + } + + public function testNestedNestedInline() { + $this->assertEquals (array( "This", array("Is", "Getting", array("Ridiculous", "Guys")), + "Seriously", array("Show", "Mercy")), $this->yaml[8]); + } + + public function testInlineMappings() { + $this->assertEquals (array("name" => "chris", "age" => "young", "brand" => "lucky strike"), $this->yaml[9]); + } + + public function testNestedInlineMappings() { + $this->assertEquals (array("name" => "mark", "age" => "older than chris", + "brand" => array("marlboro", "lucky strike")), $this->yaml[10]); + } + + public function testReferences() { + $this->assertEquals (array('Perl', 'Python', 'PHP', 'Ruby'), $this->yaml['dynamic languages']); + } + + public function testReferences2() { + $this->assertEquals (array('C/C++', 'Java'), $this->yaml['compiled languages']); + } + + public function testReferences3() { + $this->assertEquals (array( + array('Perl', 'Python', 'PHP', 'Ruby'), + array('C/C++', 'Java') + ), $this->yaml['all languages']); + } + + public function testEscapedQuotes() { + $this->assertEquals ("you know, this shouldn't work. but it does.", $this->yaml[11]); + } + + public function testEscapedQuotes_2() { + $this->assertEquals ( "that's my value.", $this->yaml[12]); + } + + public function testEscapedQuotes_3() { + $this->assertEquals ("again, that's my value.", $this->yaml[13]); + } + + public function testQuotes() { + $this->assertEquals ("here's to \"quotes\", boss.", $this->yaml[14]); + } + + public function testQuoteSequence() { + $this->assertEquals ( array( 'name' => "Foo, Bar's", 'age' => 20), $this->yaml[15]); + } + + public function testShortSequence() { + $this->assertEquals (array( 0 => "a", 1 => array (0 => 1, 1 => 2), 2 => "b"), $this->yaml[16]); + } + + public function testQuotedNewlines() { + $this->assertEquals ("First line\nSecond line\nThird line", $this->yaml[17]); + } + + public function testHash_1() { + $this->assertEquals ("Hash", $this->yaml['hash_1']); + } + + public function testHash_2() { + $this->assertEquals ('Hash #and a comment', $this->yaml['hash_2']); + } + + public function testHash_3() { + $this->assertEquals ('Hash (#) can appear in key too', $this->yaml['hash#3']); + } + + public function testEndloop() { + $this->assertEquals ("Does this line in the end indeed make Spyc go to an infinite loop?", $this->yaml['endloop']); + } + + public function testReallyLargeNumber() { + $this->assertEquals ('115792089237316195423570985008687907853269984665640564039457584007913129639936', $this->yaml['a_really_large_number']); + } + + public function testFloatWithZeros() { + $this->assertSame ('1.0', $this->yaml['float_test']); + } + + public function testFloatWithQuotes() { + $this->assertSame ('1.0', $this->yaml['float_test_with_quotes']); + } + + public function testFloatInverse() { + $this->assertEquals ('001', $this->yaml['float_inverse_test']); + } + + public function testIntArray() { + $this->assertEquals (array (1, 2, 3), $this->yaml['int array']); + } + + public function testArrayOnSeveralLines() { + $this->assertEquals (array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19), $this->yaml['array on several lines']); + } + + public function testArrayWithCommas() { + $this->assertEquals(array (0, 1), $this->yaml['array with commas']); + } + + public function testmoreLessKey() { + $this->assertEquals ('', $this->yaml['morelesskey']); + } + + public function testArrayOfZero() { + $this->assertSame (array(0), $this->yaml['array_of_zero']); + } + + public function testSophisticatedArrayOfZero() { + $this->assertSame (array('rx' => array ('tx' => array (0))), $this->yaml['sophisticated_array_of_zero']); + } + + public function testSwitches() { + $this->assertEquals (array (array ('row' => 0, 'col' => 0, 'func' => array ('tx' => array(0, 1)))), $this->yaml['switches']); + } + + public function testEmptySequence() { + $this->assertSame (array(), $this->yaml['empty_sequence']); + } + + public function testEmptyHash() { + $this->assertSame (array(), $this->yaml['empty_hash']); + } + + public function testEmptykey() { + $this->assertSame (array('' => array ('key' => 'value')), $this->yaml['empty_key']); + } + + public function testMultilines() { + $this->assertSame (array(array('type' => 'SomeItem', 'values' => array ('blah', 'blah', 'blah', 'blah'), 'ints' => array(2, 54, 12, 2143))), $this->yaml['multiline_items']); + } + + public function testManyNewlines() { + $this->assertSame ('A quick +fox + + +jumped +over + + + + + +a lazy + + + +dog', $this->yaml['many_lines']); + } + + public function testWerte() { + $this->assertSame (array ('1' => 'nummer 1', '0' => 'Stunde 0'), $this->yaml['werte']); + } + + /* public function testNoIndent() { + $this->assertSame (array( + array ('record1'=>'value1'), + array ('record2'=>'value2') + ) + , $this->yaml['noindent_records']); + } */ + + public function testColonsInKeys() { + $this->assertSame (array (1000), $this->yaml['a:1']); + } + + public function testColonsInKeys2() { + $this->assertSame (array (2000), $this->yaml['a:2']); + } + + public function testUnquotedColonsInKeys() { + $this->assertSame (array (3000), $this->yaml['a:3']); + } + + public function testComplicatedKeyWithColon() { + $this->assertSame(array("a:b:''test'" => 'value'), $this->yaml['complex_unquoted_key']); + } + + public function testKeysInMappedValueException() { + $this->setExpectedException('Exception'); + Spyc::YAMLLoad('x: y: z:'); + } + + public function testKeysInValueException() { + $this->setExpectedException('Exception'); + Spyc::YAMLLoad('x: y: z'); + } + + public function testSpecialCharacters() { + $this->assertSame ('[{]]{{]]', $this->yaml['special_characters']); + } + + public function testAngleQuotes() { + $Quotes = Spyc::YAMLLoad(__DIR__.'/quotes.yaml'); + $this->assertEquals (array ('html_tags' => array ('
    ', '

    '), 'html_content' => array ('

    hello world

    ', 'hello
    world'), 'text_content' => array ('hello world')), + $Quotes); + } + + public function testFailingColons() { + $Failing = Spyc::YAMLLoad(__DIR__.'/failing1.yaml'); + $this->assertSame (array ('MyObject' => array ('Prop1' => array ('key1:val1'))), + $Failing); + } + + public function testQuotesWithComments() { + $Expected = 'bar'; + $Actual = spyc_load_file (__DIR__.'/comments.yaml'); + $this->assertEquals ($Expected, $Actual['foo']); + } + + public function testArrayWithComments() { + $Expected = array ('x', 'y', 'z'); + $Actual = spyc_load_file (__DIR__.'/comments.yaml'); + $this->assertEquals ($Expected, $Actual['arr']); + } + + public function testAfterArrayWithKittens() { + $Expected = 'kittens'; + $Actual = spyc_load_file (__DIR__.'/comments.yaml'); + $this->assertEquals ($Expected, $Actual['bar']); + } + + // Plain characters http://www.yaml.org/spec/1.2/spec.html#id2789510 + public function testKai() { + $Expected = array('-example' => 'value'); + $Actual = spyc_load_file (__DIR__.'/indent_1.yaml'); + $this->assertEquals ($Expected, $Actual['kai']); + } + + public function testKaiList() { + $Expected = array ('-item', '-item', '-item'); + $Actual = spyc_load_file (__DIR__.'/indent_1.yaml'); + $this->assertEquals ($Expected, $Actual['kai_list_of_items']); + } + + public function testDifferentQuoteTypes() { + $expected = array ('Something', "", "", "Something else"); + $this->assertSame ($expected, $this->yaml['invoice']); + } + + public function testDifferentQuoteTypes2() { + $expected = array ('Something', "Nothing", "Anything", "Thing"); + $this->assertSame ($expected, $this->yaml['quotes']); + } + + // Separation spaces http://www.yaml.org/spec/1.2/spec.html#id2778394 + public function testMultipleArrays() { + $expected = array(array(array('x'))); + $this->assertSame($expected, Spyc::YAMLLoad("- - - x")); + } + + public function testElementWithEmptyHash() + { + $element = "hash: {}\narray: []"; + $yaml = Spyc::YAMLLoadString($element); + $this->assertEquals($yaml['hash'], []); + $this->assertEquals($yaml['array'], []); + + $yaml = Spyc::YAMLLoadString($element, [ + 'setting_empty_hash_as_object' => true + ]); + $this->assertInstanceOf(stdClass::class, $yaml['hash']); + $this->assertEquals($yaml['array'], []); + } +} diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/tests/RoundTripTest.php b/data/web/inc/lib/vendor/mustangostang/spyc/tests/RoundTripTest.php new file mode 100644 index 0000000000..3e707dfe87 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/tests/RoundTripTest.php @@ -0,0 +1,76 @@ + $a))); } + + +class RoundTripTest extends PHPUnit_Framework_TestCase { + + protected function setUp() { + } + + public function testNull() { + $this->assertEquals (array ('x' => null), roundTrip (null)); + } + + public function testY() { + $this->assertEquals (array ('x' => 'y'), roundTrip ('y')); + } + + public function testExclam() { + $this->assertEquals (array ('x' => '!yeah'), roundTrip ('!yeah')); + } + + public function test5() { + $this->assertEquals (array ('x' => '5'), roundTrip ('5')); + } + + public function testSpaces() { + $this->assertEquals (array ('x' => 'x '), roundTrip ('x ')); + } + + public function testApostrophes() { + $this->assertEquals (array ('x' => "'biz'"), roundTrip ("'biz'")); + } + + public function testNewLines() { + $this->assertEquals (array ('x' => "\n"), roundTrip ("\n")); + } + + public function testHashes() { + $this->assertEquals (array ('x' => array ("#color" => '#fff')), roundTrip (array ("#color" => '#fff'))); + } + + public function testPreserveString() { + $result1 = roundTrip ('0'); + $result2 = roundTrip ('true'); + $this->assertTrue (is_string ($result1['x'])); + $this->assertTrue (is_string ($result2['x'])); + } + + public function testPreserveBool() { + $result = roundTrip (true); + $this->assertTrue (is_bool ($result['x'])); + } + + public function testPreserveInteger() { + $result = roundTrip (0); + $this->assertTrue (is_int ($result['x'])); + } + + public function testWordWrap() { + $this->assertEquals (array ('x' => "aaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), roundTrip ("aaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")); + } + + public function testABCD() { + $this->assertEquals (array ('a', 'b', 'c', 'd'), Spyc::YAMLLoad(Spyc::YAMLDump(array('a', 'b', 'c', 'd')))); + } + + public function testABCD2() { + $a = array('a', 'b', 'c', 'd'); // Create a simple list + $b = Spyc::YAMLDump($a); // Dump the list as YAML + $c = Spyc::YAMLLoad($b); // Load the dumped YAML + $d = Spyc::YAMLDump($c); // Re-dump the data + $this->assertSame($b, $d); + } + +} diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/tests/comments.yaml b/data/web/inc/lib/vendor/mustangostang/spyc/tests/comments.yaml new file mode 100644 index 0000000000..c05012f3d1 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/tests/comments.yaml @@ -0,0 +1,3 @@ +foo: 'bar' #Comment +arr: ['x', 'y', 'z'] # Comment here +bar: kittens \ No newline at end of file diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/tests/failing1.yaml b/data/web/inc/lib/vendor/mustangostang/spyc/tests/failing1.yaml new file mode 100644 index 0000000000..6906a51c16 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/tests/failing1.yaml @@ -0,0 +1,2 @@ +MyObject: + Prop1: {key1:val1} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/tests/indent_1.yaml b/data/web/inc/lib/vendor/mustangostang/spyc/tests/indent_1.yaml new file mode 100644 index 0000000000..62d6ece939 --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/tests/indent_1.yaml @@ -0,0 +1,70 @@ +root: + child_1: 2 + + child_2: 0 + child_3: 1 + +root2: + child_1: 1 +# A comment + child_2: 2 + +displays: + - resolutions: + 1024: 768 + - resolutions: + 1920: 1200 + +display: + - resolutions: + 1024: 768 + 1920: 1200 + producer: "Nec" + +nested_hashes_and_seqs: + - { row: 0, col: 0, headsets_affected: [{ports: [0], side: left}], switch_function: {ics_ptt: true} } + +easier_nest: { h: [{a: b, a1: b1}, {c: d}] } + +one_space: | + By four + spaces + +steps: + - step: &id001 + instrument: Lasik 2000 + pulseEnergy: 5.4 + pulseDuration: 12 + repetition: 1000 + spotSize: 1mm + - step: + <<: *id001 + spotSize: 2mm + +death masks are: + sad: 2 + <<: {magnificent: 4} + +login: &login + adapter: mysql + host: localhost + +development: + database: rails_dev + <<: *login + +"key": "value:" +colon_only: ":" + +list_and_comment: [one, two, three] # comment +kai: + -example: value +kai_list_of_items: + - -item + - '-item' + -item + +&foo bar: + 1: "abc" + +zzz: *foo diff --git a/data/web/inc/lib/vendor/mustangostang/spyc/tests/quotes.yaml b/data/web/inc/lib/vendor/mustangostang/spyc/tests/quotes.yaml new file mode 100644 index 0000000000..2ceea86aae --- /dev/null +++ b/data/web/inc/lib/vendor/mustangostang/spyc/tests/quotes.yaml @@ -0,0 +1,8 @@ +html_tags: + -
    + -

    +html_content: + -

    hello world

    + - hello
    world +text_content: + - hello world \ No newline at end of file diff --git a/data/web/inc/lib/vendor/nesbot/carbon/.phpstorm.meta.php b/data/web/inc/lib/vendor/nesbot/carbon/.phpstorm.meta.php new file mode 100644 index 0000000000..bd7c7e0e7a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/.phpstorm.meta.php @@ -0,0 +1,10 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\MessageFormatter; + +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; + +if (!class_exists(LazyMessageFormatter::class, false)) { + abstract class LazyMessageFormatter implements MessageFormatterInterface + { + public function format(string $message, string $locale, array $parameters = []): string + { + return $this->formatter->format( + $message, + $this->transformLocale($locale), + $parameters + ); + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/MessageFormatter/MessageFormatterMapperWeakType.php b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/MessageFormatter/MessageFormatterMapperWeakType.php new file mode 100644 index 0000000000..cbd890d5b6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/MessageFormatter/MessageFormatterMapperWeakType.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\MessageFormatter; + +use Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; + +if (!class_exists(LazyMessageFormatter::class, false)) { + abstract class LazyMessageFormatter implements MessageFormatterInterface, ChoiceMessageFormatterInterface + { + abstract protected function transformLocale(?string $locale): ?string; + + public function format($message, $locale, array $parameters = []) + { + return $this->formatter->format( + $message, + $this->transformLocale($locale), + $parameters + ); + } + + public function choiceFormat($message, $number, $locale, array $parameters = []) + { + return $this->formatter->choiceFormat($message, $number, $locale, $parameters); + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroBuiltin.php b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroBuiltin.php new file mode 100644 index 0000000000..ba7cf63201 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroBuiltin.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use PHPStan\BetterReflection\Reflection; +use ReflectionMethod; + +if (!class_exists(AbstractReflectionMacro::class, false)) { + abstract class AbstractReflectionMacro extends AbstractMacro + { + /** + * {@inheritdoc} + */ + public function getReflection(): ?ReflectionMethod + { + if ($this->reflectionFunction instanceof Reflection\ReflectionMethod) { + return new Reflection\Adapter\ReflectionMethod($this->reflectionFunction); + } + + return $this->reflectionFunction instanceof ReflectionMethod + ? $this->reflectionFunction + : null; + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroStatic.php b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroStatic.php new file mode 100644 index 0000000000..bd4c8e804c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroStatic.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use PHPStan\BetterReflection\Reflection; +use ReflectionMethod; + +if (!class_exists(AbstractReflectionMacro::class, false)) { + abstract class AbstractReflectionMacro extends AbstractMacro + { + /** + * {@inheritdoc} + */ + public function getReflection(): ?Reflection\Adapter\ReflectionMethod + { + if ($this->reflectionFunction instanceof Reflection\Adapter\ReflectionMethod) { + return $this->reflectionFunction; + } + + if ($this->reflectionFunction instanceof Reflection\ReflectionMethod) { + return new Reflection\Adapter\ReflectionMethod($this->reflectionFunction); + } + + return $this->reflectionFunction instanceof ReflectionMethod + ? new Reflection\Adapter\ReflectionMethod( + Reflection\ReflectionMethod::createFromName( + $this->reflectionFunction->getDeclaringClass()->getName(), + $this->reflectionFunction->getName() + ) + ) + : null; + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroStrongType.php b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroStrongType.php new file mode 100644 index 0000000000..f615b3a641 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroStrongType.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +if (!class_exists(LazyMacro::class, false)) { + abstract class LazyMacro extends AbstractReflectionMacro + { + /** + * {@inheritdoc} + */ + public function getFileName(): ?string + { + $file = $this->reflectionFunction->getFileName(); + + return (($file ? realpath($file) : null) ?: $file) ?: null; + } + + /** + * {@inheritdoc} + */ + public function getStartLine(): ?int + { + return $this->reflectionFunction->getStartLine(); + } + + /** + * {@inheritdoc} + */ + public function getEndLine(): ?int + { + return $this->reflectionFunction->getEndLine(); + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroWeakType.php b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroWeakType.php new file mode 100644 index 0000000000..bf64c1dd94 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroWeakType.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +if (!class_exists(LazyMacro::class, false)) { + abstract class LazyMacro extends AbstractReflectionMacro + { + /** + * {@inheritdoc} + * + * @return string|false + */ + public function getFileName() + { + $file = $this->reflectionFunction->getFileName(); + + return (($file ? realpath($file) : null) ?: $file) ?: null; + } + + /** + * {@inheritdoc} + * + * @return int|false + */ + public function getStartLine() + { + return $this->reflectionFunction->getStartLine(); + } + + /** + * {@inheritdoc} + * + * @return int|false + */ + public function getEndLine() + { + return $this->reflectionFunction->getEndLine(); + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/TranslatorStrongType.php b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/TranslatorStrongType.php new file mode 100644 index 0000000000..d35308a66c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/TranslatorStrongType.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +if (!class_exists(LazyTranslator::class, false)) { + class LazyTranslator extends AbstractTranslator implements TranslatorStrongTypeInterface + { + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + return $this->translate($id, $parameters, $domain, $locale); + } + + public function getFromCatalogue(MessageCatalogueInterface $catalogue, string $id, string $domain = 'messages') + { + $messages = $this->getPrivateProperty($catalogue, 'messages'); + + if (isset($messages[$domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX][$id])) { + return $messages[$domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX][$id]; + } + + if (isset($messages[$domain][$id])) { + return $messages[$domain][$id]; + } + + $fallbackCatalogue = $this->getPrivateProperty($catalogue, 'fallbackCatalogue'); + + if ($fallbackCatalogue !== null) { + return $this->getFromCatalogue($fallbackCatalogue, $id, $domain); + } + + return $id; + } + + private function getPrivateProperty($instance, string $field) + { + return (function (string $field) { + return $this->$field; + })->call($instance, $field); + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/TranslatorWeakType.php b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/TranslatorWeakType.php new file mode 100644 index 0000000000..94dbdc30ae --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/TranslatorWeakType.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +if (!class_exists(LazyTranslator::class, false)) { + class LazyTranslator extends AbstractTranslator + { + /** + * Returns the translation. + * + * @param string|null $id + * @param array $parameters + * @param string|null $domain + * @param string|null $locale + * + * @return string + */ + public function trans($id, array $parameters = [], $domain = null, $locale = null) + { + return $this->translate($id, $parameters, $domain, $locale); + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/readme.md b/data/web/inc/lib/vendor/nesbot/carbon/readme.md new file mode 100644 index 0000000000..3f41177068 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/readme.md @@ -0,0 +1,175 @@ +# Carbon + +[![Latest Stable Version](https://img.shields.io/packagist/v/nesbot/carbon.svg?style=flat-square)](https://packagist.org/packages/nesbot/carbon) +[![Total Downloads](https://img.shields.io/packagist/dt/nesbot/carbon.svg?style=flat-square)](https://packagist.org/packages/nesbot/carbon) +[![GitHub Actions](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fbriannesbitt%2FCarbon%2Fbadge&style=flat-square&label=Build&logo=none)](https://github.com/briannesbitt/Carbon/actions) +[![codecov.io](https://img.shields.io/codecov/c/github/briannesbitt/Carbon.svg?style=flat-square)](https://codecov.io/github/briannesbitt/Carbon?branch=master) +[![Tidelift](https://tidelift.com/badges/github/briannesbitt/Carbon)](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme) + +An international PHP extension for DateTime. [https://carbon.nesbot.com](https://carbon.nesbot.com) + +```php +toDateTimeString()); +printf("Right now in Vancouver is %s", Carbon::now('America/Vancouver')); //implicit __toString() +$tomorrow = Carbon::now()->addDay(); +$lastWeek = Carbon::now()->subWeek(); +$nextSummerOlympics = Carbon::createFromDate(2016)->addYears(4); + +$officialDate = Carbon::now()->toRfc2822String(); + +$howOldAmI = Carbon::createFromDate(1975, 5, 21)->age; + +$noonTodayLondonTime = Carbon::createFromTime(12, 0, 0, 'Europe/London'); + +$internetWillBlowUpOn = Carbon::create(2038, 01, 19, 3, 14, 7, 'GMT'); + +// Don't really want this to happen so mock now +Carbon::setTestNow(Carbon::createFromDate(2000, 1, 1)); + +// comparisons are always done in UTC +if (Carbon::now()->gte($internetWillBlowUpOn)) { + die(); +} + +// Phew! Return to normal behaviour +Carbon::setTestNow(); + +if (Carbon::now()->isWeekend()) { + echo 'Party!'; +} +// Over 200 languages (and over 500 regional variants) supported: +echo Carbon::now()->subMinutes(2)->diffForHumans(); // '2 minutes ago' +echo Carbon::now()->subMinutes(2)->locale('zh_CN')->diffForHumans(); // '2ๅˆ†้’Ÿๅ‰' +echo Carbon::parse('2019-07-23 14:51')->isoFormat('LLLL'); // 'Tuesday, July 23, 2019 2:51 PM' +echo Carbon::parse('2019-07-23 14:51')->locale('fr_FR')->isoFormat('LLLL'); // 'mardi 23 juillet 2019 14:51' + +// ... but also does 'from now', 'after' and 'before' +// rolling up to seconds, minutes, hours, days, months, years + +$daysSinceEpoch = Carbon::createFromTimestamp(0)->diffInDays(); +``` + +[Get supported nesbot/carbon with the Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme) + +## Installation + +### With Composer + +``` +$ composer require nesbot/carbon +``` + +```json +{ + "require": { + "nesbot/carbon": "^2.16" + } +} +``` + +```php + + +### Translators + +[Thanks to people helping us to translate Carbon in so many languages](https://carbon.nesbot.com/contribute/translators/) + +### Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. + + +ะžะฝะปะฐะนะฝ ะบะฐะทะธะฝะพ +CasinoHex Canada +Probukmacher +ะ˜ะณั€ะพะฒั‹ะต ะฐะฒั‚ะพะผะฐั‚ั‹ +Casino-portugal.pt +Slots City +inkedin +ะžะฝะปะฐะนะฝ ะบะฐะทะธะฝะพ ะฃะบั€ะฐั—ะฝะธ +OnlineCasinosSpelen +Best non Gamstop sites in the UK +Real Money Pokies +Non GamStop Bookies UK +ะžะฝะปะฐะนะฝ ะšะฐะทะธะฝะพ ะฃะบั€ะฐะธะฝั‹ +SSSTwitter +Non-GamStop Bets UK +Chudovo +UK Casino Gap +NZ Casino Deps +NonStopCasino.org +Migliori Siti Non AAMS +UK NonGamStopCasinos +SnapTik +IG Downloader +Proxidize +Blastup +AzuraCast +Triplebyte +GitHub Sponsors +Salesforce + + +[[Become a sponsor via OpenCollective](https://opencollective.com/Carbon#sponsor)] + + + + + + +[[Become a sponsor via GitHub](https://github.com/sponsors/kylekatarnls)] + +### Backers + +Thank you to all our backers! ๐Ÿ™ + + + +[[Become a backer](https://opencollective.com/Carbon#backer)] + +## Carbon for enterprise + +Available as part of the Tidelift Subscription. + +The maintainers of ``Carbon`` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/data/web/inc/lib/vendor/nesbot/carbon/sponsors.php b/data/web/inc/lib/vendor/nesbot/carbon/sponsors.php new file mode 100644 index 0000000000..67b217161a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/sponsors.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\CarbonImmutable; + +require_once __DIR__.'/vendor/autoload.php'; + +function getMaxHistoryMonthsByAmount($amount): int +{ + if ($amount >= 50) { + return 6; + } + + if ($amount >= 20) { + return 4; + } + + return 2; +} + +function getHtmlAttribute($rawValue): string +{ + return str_replace( + ['โ€‹', "\r"], + '', + trim(htmlspecialchars((string) $rawValue), "ย  \n\r\t\v\0"), + ); +} + +function getOpenCollectiveSponsors(): string +{ + $customSponsorImages = [ + // For consistency and equity among sponsors, as of now, we kindly ask our sponsors + // to provide an image having a width/height ratio between 1/1 and 2/1. + // By default, we'll show the member picture from OpenCollective, and will resize it if bigger + // int(OpenCollective.MemberId) => ImageURL + ]; + + $members = json_decode(file_get_contents('https://opencollective.com/carbon/members/all.json'), true); + + $list = array_filter($members, static function ($member): bool { + return ($member['lastTransactionAmount'] > 3 || $member['isActive']) && + $member['role'] === 'BACKER' && + $member['type'] !== 'USER' && + ( + $member['totalAmountDonated'] > 100 || + $member['lastTransactionAt'] > CarbonImmutable::now() + ->subMonthsNoOverflow(getMaxHistoryMonthsByAmount($member['lastTransactionAmount'])) + ->format('Y-m-d h:i') || + $member['isActive'] && $member['lastTransactionAmount'] >= 30 + ); + }); + + $list = array_map(static function (array $member): array { + $createdAt = CarbonImmutable::parse($member['createdAt']); + $lastTransactionAt = CarbonImmutable::parse($member['lastTransactionAt']); + + if ($createdAt->format('d H:i:s.u') > $lastTransactionAt->format('d H:i:s.u')) { + $createdAt = $createdAt + ->setDay($lastTransactionAt->day) + ->modify($lastTransactionAt->format('H:i:s.u')); + } + + $monthlyContribution = (float) ($member['totalAmountDonated'] / ceil($createdAt->floatDiffInMonths())); + + if ( + $lastTransactionAt->isAfter('last month') && + $member['lastTransactionAmount'] > $monthlyContribution + ) { + $monthlyContribution = (float) $member['lastTransactionAmount']; + } + + $yearlyContribution = (float) ($member['totalAmountDonated'] / max(1, $createdAt->floatDiffInYears())); + $status = null; + + if ($monthlyContribution > 29) { + $status = 'sponsor'; + } elseif ($monthlyContribution > 4.5 || $yearlyContribution > 29) { + $status = 'backer'; + } elseif ($member['totalAmountDonated'] > 0) { + $status = 'helper'; + } + + return array_merge($member, [ + 'star' => ($monthlyContribution > 98 || $yearlyContribution > 500), + 'status' => $status, + 'monthlyContribution' => $monthlyContribution, + 'yearlyContribution' => $yearlyContribution, + ]); + }, $list); + + usort($list, static function (array $a, array $b): int { + return ($b['monthlyContribution'] <=> $a['monthlyContribution']) + ?: ($b['totalAmountDonated'] <=> $a['totalAmountDonated']); + }); + + return implode('', array_map(static function (array $member) use ($customSponsorImages): string { + $href = htmlspecialchars($member['website'] ?? $member['profile']); + $src = $customSponsorImages[$member['MemberId'] ?? ''] ?? $member['image'] ?? (strtr($member['profile'], ['https://opencollective.com/' => 'https://images.opencollective.com/']).'/avatar/256.png'); + [$x, $y] = @getimagesize($src) ?: [0, 0]; + $validImage = ($x && $y); + $src = $validImage ? htmlspecialchars($src) : 'https://opencollective.com/static/images/default-guest-logo.svg'; + $height = $member['status'] === 'sponsor' ? 64 : 42; + $width = min($height * 2, $validImage ? round($x * $height / $y) : $height); + $href .= (strpos($href, '?') === false ? '?' : '&').'utm_source=opencollective&utm_medium=github&utm_campaign=Carbon'; + $title = getHtmlAttribute(($member['description'] ?? null) ?: $member['name']); + $alt = getHtmlAttribute($member['name']); + + return "\n".''. + ''.$alt.''. + ''; + }, $list))."\n"; +} + +file_put_contents('readme.md', preg_replace_callback( + '/()[\s\S]+()/', + static function (array $match): string { + return $match[1].getOpenCollectiveSponsors().$match[2]; + }, + file_get_contents('readme.md') +)); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/AbstractTranslator.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/AbstractTranslator.php new file mode 100644 index 0000000000..8b8fe089ee --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/AbstractTranslator.php @@ -0,0 +1,398 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\MessageFormatter\MessageFormatterMapper; +use Closure; +use ReflectionException; +use ReflectionFunction; +use Symfony\Component\Translation; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; +use Symfony\Component\Translation\Loader\ArrayLoader; + +abstract class AbstractTranslator extends Translation\Translator +{ + /** + * Translator singletons for each language. + * + * @var array + */ + protected static $singletons = []; + + /** + * List of custom localized messages. + * + * @var array + */ + protected $messages = []; + + /** + * List of custom directories that contain translation files. + * + * @var string[] + */ + protected $directories = []; + + /** + * Set to true while constructing. + * + * @var bool + */ + protected $initializing = false; + + /** + * List of locales aliases. + * + * @var array + */ + protected $aliases = [ + 'me' => 'sr_Latn_ME', + 'scr' => 'sh', + ]; + + /** + * Return a singleton instance of Translator. + * + * @param string|null $locale optional initial locale ("en" - english by default) + * + * @return static + */ + public static function get($locale = null) + { + $locale = $locale ?: 'en'; + $key = static::class === Translator::class ? $locale : static::class.'|'.$locale; + + if (!isset(static::$singletons[$key])) { + static::$singletons[$key] = new static($locale); + } + + return static::$singletons[$key]; + } + + public function __construct($locale, MessageFormatterInterface $formatter = null, $cacheDir = null, $debug = false) + { + parent::setLocale($locale); + $this->initializing = true; + $this->directories = [__DIR__.'/Lang']; + $this->addLoader('array', new ArrayLoader()); + parent::__construct($locale, new MessageFormatterMapper($formatter), $cacheDir, $debug); + $this->initializing = false; + } + + /** + * Returns the list of directories translation files are searched in. + * + * @return array + */ + public function getDirectories(): array + { + return $this->directories; + } + + /** + * Set list of directories translation files are searched in. + * + * @param array $directories new directories list + * + * @return $this + */ + public function setDirectories(array $directories) + { + $this->directories = $directories; + + return $this; + } + + /** + * Add a directory to the list translation files are searched in. + * + * @param string $directory new directory + * + * @return $this + */ + public function addDirectory(string $directory) + { + $this->directories[] = $directory; + + return $this; + } + + /** + * Remove a directory from the list translation files are searched in. + * + * @param string $directory directory path + * + * @return $this + */ + public function removeDirectory(string $directory) + { + $search = rtrim(strtr($directory, '\\', '/'), '/'); + + return $this->setDirectories(array_filter($this->getDirectories(), function ($item) use ($search) { + return rtrim(strtr($item, '\\', '/'), '/') !== $search; + })); + } + + /** + * Reset messages of a locale (all locale if no locale passed). + * Remove custom messages and reload initial messages from matching + * file in Lang directory. + * + * @param string|null $locale + * + * @return bool + */ + public function resetMessages($locale = null) + { + if ($locale === null) { + $this->messages = []; + + return true; + } + + foreach ($this->getDirectories() as $directory) { + $data = @include sprintf('%s/%s.php', rtrim($directory, '\\/'), $locale); + + if ($data !== false) { + $this->messages[$locale] = $data; + $this->addResource('array', $this->messages[$locale], $locale); + + return true; + } + } + + return false; + } + + /** + * Returns the list of files matching a given locale prefix (or all if empty). + * + * @param string $prefix prefix required to filter result + * + * @return array + */ + public function getLocalesFiles($prefix = '') + { + $files = []; + + foreach ($this->getDirectories() as $directory) { + $directory = rtrim($directory, '\\/'); + + foreach (glob("$directory/$prefix*.php") as $file) { + $files[] = $file; + } + } + + return array_unique($files); + } + + /** + * Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * + * @param string $prefix prefix required to filter result + * + * @return array + */ + public function getAvailableLocales($prefix = '') + { + $locales = []; + foreach ($this->getLocalesFiles($prefix) as $file) { + $locales[] = substr($file, strrpos($file, '/') + 1, -4); + } + + return array_unique(array_merge($locales, array_keys($this->messages))); + } + + protected function translate(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + if ($domain === null) { + $domain = 'messages'; + } + + $catalogue = $this->getCatalogue($locale); + $format = $this instanceof TranslatorStrongTypeInterface + ? $this->getFromCatalogue($catalogue, (string) $id, $domain) + : $this->getCatalogue($locale)->get((string) $id, $domain); // @codeCoverageIgnore + + if ($format instanceof Closure) { + // @codeCoverageIgnoreStart + try { + $count = (new ReflectionFunction($format))->getNumberOfRequiredParameters(); + } catch (ReflectionException $exception) { + $count = 0; + } + // @codeCoverageIgnoreEnd + + return $format( + ...array_values($parameters), + ...array_fill(0, max(0, $count - \count($parameters)), null) + ); + } + + return parent::trans($id, $parameters, $domain, $locale); + } + + /** + * Init messages language from matching file in Lang directory. + * + * @param string $locale + * + * @return bool + */ + protected function loadMessagesFromFile($locale) + { + return isset($this->messages[$locale]) || $this->resetMessages($locale); + } + + /** + * Set messages of a locale and take file first if present. + * + * @param string $locale + * @param array $messages + * + * @return $this + */ + public function setMessages($locale, $messages) + { + $this->loadMessagesFromFile($locale); + $this->addResource('array', $messages, $locale); + $this->messages[$locale] = array_merge( + $this->messages[$locale] ?? [], + $messages + ); + + return $this; + } + + /** + * Set messages of the current locale and take file first if present. + * + * @param array $messages + * + * @return $this + */ + public function setTranslations($messages) + { + return $this->setMessages($this->getLocale(), $messages); + } + + /** + * Get messages of a locale, if none given, return all the + * languages. + * + * @param string|null $locale + * + * @return array + */ + public function getMessages($locale = null) + { + return $locale === null ? $this->messages : $this->messages[$locale]; + } + + /** + * Set the current translator locale and indicate if the source locale file exists + * + * @param string $locale locale ex. en + * + * @return bool + */ + public function setLocale($locale) + { + $locale = preg_replace_callback('/[-_]([a-z]{2,}|\d{2,})/', function ($matches) { + // _2-letters or YUE is a region, _3+-letters is a variant + $upper = strtoupper($matches[1]); + + if ($upper === 'YUE' || $upper === 'ISO' || \strlen($upper) < 3) { + return "_$upper"; + } + + return '_'.ucfirst($matches[1]); + }, strtolower($locale)); + + $previousLocale = $this->getLocale(); + + if ($previousLocale === $locale && isset($this->messages[$locale])) { + return true; + } + + unset(static::$singletons[$previousLocale]); + + if ($locale === 'auto') { + $completeLocale = setlocale(LC_TIME, '0'); + $locale = preg_replace('/^([^_.-]+).*$/', '$1', $completeLocale); + $locales = $this->getAvailableLocales($locale); + + $completeLocaleChunks = preg_split('/[_.-]+/', $completeLocale); + + $getScore = function ($language) use ($completeLocaleChunks) { + return self::compareChunkLists($completeLocaleChunks, preg_split('/[_.-]+/', $language)); + }; + + usort($locales, function ($first, $second) use ($getScore) { + return $getScore($second) <=> $getScore($first); + }); + + $locale = $locales[0]; + } + + if (isset($this->aliases[$locale])) { + $locale = $this->aliases[$locale]; + } + + // If subtag (ex: en_CA) first load the macro (ex: en) to have a fallback + if (str_contains($locale, '_') && + $this->loadMessagesFromFile($macroLocale = preg_replace('/^([^_]+).*$/', '$1', $locale)) + ) { + parent::setLocale($macroLocale); + } + + if (!$this->loadMessagesFromFile($locale) && !$this->initializing) { + return false; + } + + parent::setLocale($locale); + + return true; + } + + /** + * Show locale on var_dump(). + * + * @return array + */ + public function __debugInfo() + { + return [ + 'locale' => $this->getLocale(), + ]; + } + + private static function compareChunkLists($referenceChunks, $chunks) + { + $score = 0; + + foreach ($referenceChunks as $index => $chunk) { + if (!isset($chunks[$index])) { + $score++; + + continue; + } + + if (strtolower($chunks[$index]) === strtolower($chunk)) { + $score += 10; + } + } + + return $score; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Carbon.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Carbon.php new file mode 100644 index 0000000000..e32569ae30 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Carbon.php @@ -0,0 +1,523 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Traits\Date; +use Carbon\Traits\DeprecatedProperties; +use DateTime; +use DateTimeInterface; +use DateTimeZone; + +/** + * A simple API extension for DateTime. + * + * @mixin DeprecatedProperties + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method $this years(int $value) Set current instance year to the given value. + * @method $this year(int $value) Set current instance year to the given value. + * @method $this setYears(int $value) Set current instance year to the given value. + * @method $this setYear(int $value) Set current instance year to the given value. + * @method $this months(int $value) Set current instance month to the given value. + * @method $this month(int $value) Set current instance month to the given value. + * @method $this setMonths(int $value) Set current instance month to the given value. + * @method $this setMonth(int $value) Set current instance month to the given value. + * @method $this days(int $value) Set current instance day to the given value. + * @method $this day(int $value) Set current instance day to the given value. + * @method $this setDays(int $value) Set current instance day to the given value. + * @method $this setDay(int $value) Set current instance day to the given value. + * @method $this hours(int $value) Set current instance hour to the given value. + * @method $this hour(int $value) Set current instance hour to the given value. + * @method $this setHours(int $value) Set current instance hour to the given value. + * @method $this setHour(int $value) Set current instance hour to the given value. + * @method $this minutes(int $value) Set current instance minute to the given value. + * @method $this minute(int $value) Set current instance minute to the given value. + * @method $this setMinutes(int $value) Set current instance minute to the given value. + * @method $this setMinute(int $value) Set current instance minute to the given value. + * @method $this seconds(int $value) Set current instance second to the given value. + * @method $this second(int $value) Set current instance second to the given value. + * @method $this setSeconds(int $value) Set current instance second to the given value. + * @method $this setSecond(int $value) Set current instance second to the given value. + * @method $this millis(int $value) Set current instance millisecond to the given value. + * @method $this milli(int $value) Set current instance millisecond to the given value. + * @method $this setMillis(int $value) Set current instance millisecond to the given value. + * @method $this setMilli(int $value) Set current instance millisecond to the given value. + * @method $this milliseconds(int $value) Set current instance millisecond to the given value. + * @method $this millisecond(int $value) Set current instance millisecond to the given value. + * @method $this setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method $this setMillisecond(int $value) Set current instance millisecond to the given value. + * @method $this micros(int $value) Set current instance microsecond to the given value. + * @method $this micro(int $value) Set current instance microsecond to the given value. + * @method $this setMicros(int $value) Set current instance microsecond to the given value. + * @method $this setMicro(int $value) Set current instance microsecond to the given value. + * @method $this microseconds(int $value) Set current instance microsecond to the given value. + * @method $this microsecond(int $value) Set current instance microsecond to the given value. + * @method $this setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method $this setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method $this addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method $this addYear() Add one year to the instance (using date interval). + * @method $this subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method $this subYear() Sub one year to the instance (using date interval). + * @method $this addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method $this subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method $this addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method $this addMonth() Add one month to the instance (using date interval). + * @method $this subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method $this subMonth() Sub one month to the instance (using date interval). + * @method $this addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method $this addDay() Add one day to the instance (using date interval). + * @method $this subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method $this subDay() Sub one day to the instance (using date interval). + * @method $this addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method $this addHour() Add one hour to the instance (using date interval). + * @method $this subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method $this subHour() Sub one hour to the instance (using date interval). + * @method $this addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method $this addMinute() Add one minute to the instance (using date interval). + * @method $this subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method $this subMinute() Sub one minute to the instance (using date interval). + * @method $this addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method $this addSecond() Add one second to the instance (using date interval). + * @method $this subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method $this subSecond() Sub one second to the instance (using date interval). + * @method $this addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMilli() Add one millisecond to the instance (using date interval). + * @method $this subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMilli() Sub one millisecond to the instance (using date interval). + * @method $this addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMillisecond() Add one millisecond to the instance (using date interval). + * @method $this subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMillisecond() Sub one millisecond to the instance (using date interval). + * @method $this addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMicro() Add one microsecond to the instance (using date interval). + * @method $this subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMicro() Sub one microsecond to the instance (using date interval). + * @method $this addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMicrosecond() Add one microsecond to the instance (using date interval). + * @method $this subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method $this addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method $this addMillennium() Add one millennium to the instance (using date interval). + * @method $this subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method $this subMillennium() Sub one millennium to the instance (using date interval). + * @method $this addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method $this addCentury() Add one century to the instance (using date interval). + * @method $this subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method $this subCentury() Sub one century to the instance (using date interval). + * @method $this addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method $this subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method $this addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method $this addDecade() Add one decade to the instance (using date interval). + * @method $this subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method $this subDecade() Sub one decade to the instance (using date interval). + * @method $this addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method $this subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method $this addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method $this addQuarter() Add one quarter to the instance (using date interval). + * @method $this subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method $this subQuarter() Sub one quarter to the instance (using date interval). + * @method $this addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method $this subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method $this addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method $this addWeek() Add one week to the instance (using date interval). + * @method $this subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method $this subWeek() Sub one week to the instance (using date interval). + * @method $this addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method $this addWeekday() Add one weekday to the instance (using date interval). + * @method $this subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method $this subWeekday() Sub one weekday to the instance (using date interval). + * @method $this addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMicro() Add one microsecond to the instance (using timestamp). + * @method $this subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method $this addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method $this subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method $this addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMilli() Add one millisecond to the instance (using timestamp). + * @method $this subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method $this addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method $this subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method $this addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealSecond() Add one second to the instance (using timestamp). + * @method $this subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method $this addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMinute() Add one minute to the instance (using timestamp). + * @method $this subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method $this addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method $this addRealHour() Add one hour to the instance (using timestamp). + * @method $this subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method $this subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method $this addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method $this addRealDay() Add one day to the instance (using timestamp). + * @method $this subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method $this subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method $this addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method $this addRealWeek() Add one week to the instance (using timestamp). + * @method $this subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method $this subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method $this addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMonth() Add one month to the instance (using timestamp). + * @method $this subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method $this addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method $this addRealQuarter() Add one quarter to the instance (using timestamp). + * @method $this subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method $this subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method $this addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method $this addRealYear() Add one year to the instance (using timestamp). + * @method $this subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method $this subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method $this addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method $this addRealDecade() Add one decade to the instance (using timestamp). + * @method $this subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method $this subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method $this addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method $this addRealCentury() Add one century to the instance (using timestamp). + * @method $this subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method $this subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method $this addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMillennium() Add one millennium to the instance (using timestamp). + * @method $this subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method $this roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method $this floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method $this ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method $this ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method $this roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method $this floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method $this ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method $this ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method $this roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method $this floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method $this ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method $this ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method $this roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method $this floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method $this ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method $this ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method $this roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method $this ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method static static|false createFromFormat(string $format, string $time, DateTimeZone|string|false|null $timezone = null) Parse a string into a new Carbon object according to the specified format. + * @method static static __set_state(array $array) https://php.net/manual/en/datetime.set-state.php + * + * + */ +class Carbon extends DateTime implements CarbonInterface +{ + use Date; + + /** + * Returns true if the current class/instance is mutable. + * + * @return bool + */ + public static function isMutable() + { + return true; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonConverterInterface.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonConverterInterface.php new file mode 100644 index 0000000000..1ce967b255 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonConverterInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use DateTimeInterface; + +interface CarbonConverterInterface +{ + public function convertDate(DateTimeInterface $dateTime, bool $negated = false): CarbonInterface; +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonImmutable.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonImmutable.php new file mode 100644 index 0000000000..4c9c1cfef5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonImmutable.php @@ -0,0 +1,582 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Traits\Date; +use Carbon\Traits\DeprecatedProperties; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; + +/** + * A simple API extension for DateTimeImmutable. + * + * @mixin DeprecatedProperties + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method CarbonImmutable years(int $value) Set current instance year to the given value. + * @method CarbonImmutable year(int $value) Set current instance year to the given value. + * @method CarbonImmutable setYears(int $value) Set current instance year to the given value. + * @method CarbonImmutable setYear(int $value) Set current instance year to the given value. + * @method CarbonImmutable months(int $value) Set current instance month to the given value. + * @method CarbonImmutable month(int $value) Set current instance month to the given value. + * @method CarbonImmutable setMonths(int $value) Set current instance month to the given value. + * @method CarbonImmutable setMonth(int $value) Set current instance month to the given value. + * @method CarbonImmutable days(int $value) Set current instance day to the given value. + * @method CarbonImmutable day(int $value) Set current instance day to the given value. + * @method CarbonImmutable setDays(int $value) Set current instance day to the given value. + * @method CarbonImmutable setDay(int $value) Set current instance day to the given value. + * @method CarbonImmutable hours(int $value) Set current instance hour to the given value. + * @method CarbonImmutable hour(int $value) Set current instance hour to the given value. + * @method CarbonImmutable setHours(int $value) Set current instance hour to the given value. + * @method CarbonImmutable setHour(int $value) Set current instance hour to the given value. + * @method CarbonImmutable minutes(int $value) Set current instance minute to the given value. + * @method CarbonImmutable minute(int $value) Set current instance minute to the given value. + * @method CarbonImmutable setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonImmutable setMinute(int $value) Set current instance minute to the given value. + * @method CarbonImmutable seconds(int $value) Set current instance second to the given value. + * @method CarbonImmutable second(int $value) Set current instance second to the given value. + * @method CarbonImmutable setSeconds(int $value) Set current instance second to the given value. + * @method CarbonImmutable setSecond(int $value) Set current instance second to the given value. + * @method CarbonImmutable millis(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable milli(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable micros(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable micro(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addYear() Add one year to the instance (using date interval). + * @method CarbonImmutable subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subYear() Sub one year to the instance (using date interval). + * @method CarbonImmutable addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMonth() Add one month to the instance (using date interval). + * @method CarbonImmutable subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMonth() Sub one month to the instance (using date interval). + * @method CarbonImmutable addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addDay() Add one day to the instance (using date interval). + * @method CarbonImmutable subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subDay() Sub one day to the instance (using date interval). + * @method CarbonImmutable addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addHour() Add one hour to the instance (using date interval). + * @method CarbonImmutable subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subHour() Sub one hour to the instance (using date interval). + * @method CarbonImmutable addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMinute() Add one minute to the instance (using date interval). + * @method CarbonImmutable subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMinute() Sub one minute to the instance (using date interval). + * @method CarbonImmutable addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addSecond() Add one second to the instance (using date interval). + * @method CarbonImmutable subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subSecond() Sub one second to the instance (using date interval). + * @method CarbonImmutable addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonImmutable subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonImmutable addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonImmutable subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonImmutable addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonImmutable subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonImmutable addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonImmutable subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonImmutable addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonImmutable subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonImmutable addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addCentury() Add one century to the instance (using date interval). + * @method CarbonImmutable subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subCentury() Sub one century to the instance (using date interval). + * @method CarbonImmutable addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addDecade() Add one decade to the instance (using date interval). + * @method CarbonImmutable subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subDecade() Sub one decade to the instance (using date interval). + * @method CarbonImmutable addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonImmutable subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonImmutable addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addWeek() Add one week to the instance (using date interval). + * @method CarbonImmutable subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subWeek() Sub one week to the instance (using date interval). + * @method CarbonImmutable addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonImmutable subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonImmutable addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonImmutable subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonImmutable addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonImmutable subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonImmutable addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonImmutable subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonImmutable addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonImmutable subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonImmutable addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealSecond() Add one second to the instance (using timestamp). + * @method CarbonImmutable subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method CarbonImmutable addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMinute() Add one minute to the instance (using timestamp). + * @method CarbonImmutable subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method CarbonImmutable addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealHour() Add one hour to the instance (using timestamp). + * @method CarbonImmutable subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method CarbonImmutable addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealDay() Add one day to the instance (using timestamp). + * @method CarbonImmutable subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method CarbonImmutable addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealWeek() Add one week to the instance (using timestamp). + * @method CarbonImmutable subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method CarbonImmutable addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMonth() Add one month to the instance (using timestamp). + * @method CarbonImmutable subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method CarbonImmutable addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonImmutable subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method CarbonImmutable addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealYear() Add one year to the instance (using timestamp). + * @method CarbonImmutable subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method CarbonImmutable addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealDecade() Add one decade to the instance (using timestamp). + * @method CarbonImmutable subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method CarbonImmutable addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealCentury() Add one century to the instance (using timestamp). + * @method CarbonImmutable subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method CarbonImmutable addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonImmutable subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method CarbonImmutable roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonImmutable roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonImmutable floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonImmutable floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonImmutable ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonImmutable ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonImmutable roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonImmutable roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonImmutable floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonImmutable floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonImmutable ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonImmutable ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonImmutable roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonImmutable roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonImmutable floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonImmutable floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonImmutable ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonImmutable ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonImmutable roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonImmutable roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonImmutable floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonImmutable floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonImmutable ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonImmutable ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonImmutable roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonImmutable roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonImmutable floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonImmutable floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonImmutable ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonImmutable ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonImmutable roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonImmutable roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonImmutable floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonImmutable floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonImmutable ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonImmutable ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonImmutable roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonImmutable roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonImmutable floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonImmutable floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonImmutable ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonImmutable ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonImmutable roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonImmutable roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonImmutable floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonImmutable floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonImmutable ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonImmutable ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonImmutable roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonImmutable roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonImmutable floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonImmutable floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonImmutable ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonImmutable ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonImmutable roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonImmutable roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonImmutable floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonImmutable floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonImmutable ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonImmutable ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonImmutable roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonImmutable roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonImmutable floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonImmutable floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonImmutable ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonImmutable ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonImmutable roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonImmutable roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonImmutable floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonImmutable floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonImmutable ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonImmutable ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method static static|false createFromFormat(string $format, string $time, DateTimeZone|string|false|null $timezone = null) Parse a string into a new CarbonImmutable object according to the specified format. + * @method static static __set_state(array $array) https://php.net/manual/en/datetime.set-state.php + * + * + */ +class CarbonImmutable extends DateTimeImmutable implements CarbonInterface +{ + use Date { + __clone as dateTraitClone; + } + + public function __clone() + { + $this->dateTraitClone(); + $this->endOfTime = false; + $this->startOfTime = false; + } + + /** + * Create a very old date representing start of time. + * + * @return static + */ + public static function startOfTime(): self + { + $date = static::parse('0001-01-01')->years(self::getStartOfTimeYear()); + $date->startOfTime = true; + + return $date; + } + + /** + * Create a very far date representing end of time. + * + * @return static + */ + public static function endOfTime(): self + { + $date = static::parse('9999-12-31 23:59:59.999999')->years(self::getEndOfTimeYear()); + $date->endOfTime = true; + + return $date; + } + + /** + * @codeCoverageIgnore + */ + private static function getEndOfTimeYear(): int + { + if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { + return 145261681241552; + } + + // Remove if https://bugs.php.net/bug.php?id=81107 is fixed + if (version_compare(PHP_VERSION, '8.1.0-dev', '>=')) { + return 1118290769066902787; + } + + return PHP_INT_MAX; + } + + /** + * @codeCoverageIgnore + */ + private static function getStartOfTimeYear(): int + { + if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { + return -135908816449551; + } + + // Remove if https://bugs.php.net/bug.php?id=81107 is fixed + if (version_compare(PHP_VERSION, '8.1.0-dev', '>=')) { + return -1118290769066898816; + } + + return max(PHP_INT_MIN, -9223372036854773760); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonInterface.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonInterface.php new file mode 100644 index 0000000000..b90e298172 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonInterface.php @@ -0,0 +1,5142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use BadMethodCallException; +use Carbon\Exceptions\BadComparisonUnitException; +use Carbon\Exceptions\ImmutableException; +use Carbon\Exceptions\InvalidDateException; +use Carbon\Exceptions\InvalidFormatException; +use Carbon\Exceptions\UnknownGetterException; +use Carbon\Exceptions\UnknownMethodException; +use Carbon\Exceptions\UnknownSetterException; +use Closure; +use DateInterval; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use JsonSerializable; +use ReflectionException; +use ReturnTypeWillChange; +use Symfony\Component\Translation\TranslatorInterface; +use Throwable; + +/** + * Common interface for Carbon and CarbonImmutable. + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method CarbonInterface years(int $value) Set current instance year to the given value. + * @method CarbonInterface year(int $value) Set current instance year to the given value. + * @method CarbonInterface setYears(int $value) Set current instance year to the given value. + * @method CarbonInterface setYear(int $value) Set current instance year to the given value. + * @method CarbonInterface months(int $value) Set current instance month to the given value. + * @method CarbonInterface month(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonths(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonth(int $value) Set current instance month to the given value. + * @method CarbonInterface days(int $value) Set current instance day to the given value. + * @method CarbonInterface day(int $value) Set current instance day to the given value. + * @method CarbonInterface setDays(int $value) Set current instance day to the given value. + * @method CarbonInterface setDay(int $value) Set current instance day to the given value. + * @method CarbonInterface hours(int $value) Set current instance hour to the given value. + * @method CarbonInterface hour(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHours(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHour(int $value) Set current instance hour to the given value. + * @method CarbonInterface minutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface minute(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinute(int $value) Set current instance minute to the given value. + * @method CarbonInterface seconds(int $value) Set current instance second to the given value. + * @method CarbonInterface second(int $value) Set current instance second to the given value. + * @method CarbonInterface setSeconds(int $value) Set current instance second to the given value. + * @method CarbonInterface setSecond(int $value) Set current instance second to the given value. + * @method CarbonInterface millis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface micros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface micro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addYear() Add one year to the instance (using date interval). + * @method CarbonInterface subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subYear() Sub one year to the instance (using date interval). + * @method CarbonInterface addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMonth() Add one month to the instance (using date interval). + * @method CarbonInterface subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMonth() Sub one month to the instance (using date interval). + * @method CarbonInterface addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDay() Add one day to the instance (using date interval). + * @method CarbonInterface subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDay() Sub one day to the instance (using date interval). + * @method CarbonInterface addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addHour() Add one hour to the instance (using date interval). + * @method CarbonInterface subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subHour() Sub one hour to the instance (using date interval). + * @method CarbonInterface addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMinute() Add one minute to the instance (using date interval). + * @method CarbonInterface subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMinute() Sub one minute to the instance (using date interval). + * @method CarbonInterface addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addSecond() Add one second to the instance (using date interval). + * @method CarbonInterface subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subSecond() Sub one second to the instance (using date interval). + * @method CarbonInterface addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonInterface subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonInterface addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addCentury() Add one century to the instance (using date interval). + * @method CarbonInterface subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subCentury() Sub one century to the instance (using date interval). + * @method CarbonInterface addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDecade() Add one decade to the instance (using date interval). + * @method CarbonInterface subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDecade() Sub one decade to the instance (using date interval). + * @method CarbonInterface addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonInterface subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonInterface addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeek() Add one week to the instance (using date interval). + * @method CarbonInterface subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeek() Sub one week to the instance (using date interval). + * @method CarbonInterface addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonInterface subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonInterface addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealSecond() Add one second to the instance (using timestamp). + * @method CarbonInterface subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method CarbonInterface addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMinute() Add one minute to the instance (using timestamp). + * @method CarbonInterface subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method CarbonInterface addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealHour() Add one hour to the instance (using timestamp). + * @method CarbonInterface subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method CarbonInterface addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDay() Add one day to the instance (using timestamp). + * @method CarbonInterface subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method CarbonInterface addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealWeek() Add one week to the instance (using timestamp). + * @method CarbonInterface subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method CarbonInterface addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMonth() Add one month to the instance (using timestamp). + * @method CarbonInterface subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method CarbonInterface addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonInterface subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method CarbonInterface addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealYear() Add one year to the instance (using timestamp). + * @method CarbonInterface subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method CarbonInterface addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDecade() Add one decade to the instance (using timestamp). + * @method CarbonInterface subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method CarbonInterface addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealCentury() Add one century to the instance (using timestamp). + * @method CarbonInterface subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method CarbonInterface addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonInterface subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method CarbonInterface roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonInterface ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * + * + */ +interface CarbonInterface extends DateTimeInterface, JsonSerializable +{ + /** + * Diff wording options(expressed in octal). + */ + public const NO_ZERO_DIFF = 01; + public const JUST_NOW = 02; + public const ONE_DAY_WORDS = 04; + public const TWO_DAY_WORDS = 010; + public const SEQUENTIAL_PARTS_ONLY = 020; + public const ROUND = 040; + public const FLOOR = 0100; + public const CEIL = 0200; + + /** + * Diff syntax options. + */ + public const DIFF_ABSOLUTE = 1; // backward compatibility with true + public const DIFF_RELATIVE_AUTO = 0; // backward compatibility with false + public const DIFF_RELATIVE_TO_NOW = 2; + public const DIFF_RELATIVE_TO_OTHER = 3; + + /** + * Translate string options. + */ + public const TRANSLATE_MONTHS = 1; + public const TRANSLATE_DAYS = 2; + public const TRANSLATE_UNITS = 4; + public const TRANSLATE_MERIDIEM = 8; + public const TRANSLATE_DIFF = 0x10; + public const TRANSLATE_ALL = self::TRANSLATE_MONTHS | self::TRANSLATE_DAYS | self::TRANSLATE_UNITS | self::TRANSLATE_MERIDIEM | self::TRANSLATE_DIFF; + + /** + * The day constants. + */ + public const SUNDAY = 0; + public const MONDAY = 1; + public const TUESDAY = 2; + public const WEDNESDAY = 3; + public const THURSDAY = 4; + public const FRIDAY = 5; + public const SATURDAY = 6; + + /** + * The month constants. + * These aren't used by Carbon itself but exist for + * convenience sake alone. + */ + public const JANUARY = 1; + public const FEBRUARY = 2; + public const MARCH = 3; + public const APRIL = 4; + public const MAY = 5; + public const JUNE = 6; + public const JULY = 7; + public const AUGUST = 8; + public const SEPTEMBER = 9; + public const OCTOBER = 10; + public const NOVEMBER = 11; + public const DECEMBER = 12; + + /** + * Number of X in Y. + */ + public const YEARS_PER_MILLENNIUM = 1000; + public const YEARS_PER_CENTURY = 100; + public const YEARS_PER_DECADE = 10; + public const MONTHS_PER_YEAR = 12; + public const MONTHS_PER_QUARTER = 3; + public const QUARTERS_PER_YEAR = 4; + public const WEEKS_PER_YEAR = 52; + public const WEEKS_PER_MONTH = 4; + public const DAYS_PER_YEAR = 365; + public const DAYS_PER_WEEK = 7; + public const HOURS_PER_DAY = 24; + public const MINUTES_PER_HOUR = 60; + public const SECONDS_PER_MINUTE = 60; + public const MILLISECONDS_PER_SECOND = 1000; + public const MICROSECONDS_PER_MILLISECOND = 1000; + public const MICROSECONDS_PER_SECOND = 1000000; + + /** + * Special settings to get the start of week from current locale culture. + */ + public const WEEK_DAY_AUTO = 'auto'; + + /** + * RFC7231 DateTime format. + * + * @var string + */ + public const RFC7231_FORMAT = 'D, d M Y H:i:s \G\M\T'; + + /** + * Default format to use for __toString method when type juggling occurs. + * + * @var string + */ + public const DEFAULT_TO_STRING_FORMAT = 'Y-m-d H:i:s'; + + /** + * Format for converting mocked time, includes microseconds. + * + * @var string + */ + public const MOCK_DATETIME_FORMAT = 'Y-m-d H:i:s.u'; + + /** + * Pattern detection for ->isoFormat and ::createFromIsoFormat. + * + * @var string + */ + public const ISO_FORMAT_REGEXP = '(O[YMDHhms]|[Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY?|g{1,5}|G{1,5}|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?)'; + + // + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws UnknownMethodException|BadMethodCallException|ReflectionException|Throwable + * + * @return mixed + */ + public function __call($method, $parameters); + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws BadMethodCallException + * + * @return mixed + */ + public static function __callStatic($method, $parameters); + + /** + * Update constructedObjectId on cloned. + */ + public function __clone(); + + /** + * Create a new Carbon instance. + * + * Please see the testing aids section (specifically static::setTestNow()) + * for more on the possibility of this constructor returning a test instance. + * + * @param DateTimeInterface|string|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + */ + public function __construct($time = null, $tz = null); + + /** + * Show truthy properties on var_dump(). + * + * @return array + */ + public function __debugInfo(); + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function __get($name); + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset($name); + + /** + * Set a part of the Carbon object + * + * @param string $name + * @param string|int|DateTimeZone $value + * + * @throws UnknownSetterException|ReflectionException + * + * @return void + */ + public function __set($name, $value); + + /** + * The __set_state handler. + * + * @param string|array $dump + * + * @return static + */ + #[ReturnTypeWillChange] + public static function __set_state($dump); + + /** + * Returns the list of properties to dump on serialize() called on. + * + * Only used by PHP < 7.4. + * + * @return array + */ + public function __sleep(); + + /** + * Format the instance as a string using the set format + * + * @example + * ``` + * echo Carbon::now(); // Carbon instances can be cast to string + * ``` + * + * @return string + */ + public function __toString(); + + /** + * Add given units or interval to the current instance. + * + * @example $date->add('hour', 3) + * @example $date->add(15, 'days') + * @example $date->add(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function add($unit, $value = 1, $overflow = null); + + /** + * Add seconds to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param string $unit + * @param int $value + * + * @return static + */ + public function addRealUnit($unit, $value = 1); + + /** + * Add given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function addUnit($unit, $value = 1, $overflow = null); + + /** + * Add any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to add to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function addUnitNoOverflow($valueUnit, $value, $overflowUnit); + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function ago($syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Modify the current instance to the average of a given instance (default now) and the current instance + * (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date + * + * @return static + */ + public function average($date = null); + + /** + * Clone the current instance if it's mutable. + * + * This method is convenient to ensure you don't mutate the initial object + * but avoid to make a useless copy of it if it's already immutable. + * + * @return static + */ + public function avoidMutation(); + + /** + * Determines if the instance is between two others. + * + * The third argument allow you to specify if bounds are included or not (true by default) + * but for when you including/excluding bounds may produce different results in your application, + * we recommend to use the explicit methods ->betweenIncluded() or ->betweenExcluded() instead. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->between('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function between($date1, $date2, $equal = true): bool; + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenExcluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-25', '2018-08-01'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenExcluded($date1, $date2): bool; + + /** + * Determines if the instance is between two others, bounds included. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenIncluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-25', '2018-08-01'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenIncluded($date1, $date2): bool; + + /** + * Returns either day of week + time (e.g. "Last Friday at 3:30 PM") if reference time is within 7 days, + * or a calendar date (e.g. "10/29/2017") otherwise. + * + * Language, date and time formats will change according to the current locale. + * + * @param Carbon|\DateTimeInterface|string|null $referenceTime + * @param array $formats + * + * @return string + */ + public function calendar($referenceTime = null, array $formats = []); + + /** + * Checks if the (date)time string is in a given format and valid to create a + * new instance. + * + * @example + * ``` + * Carbon::canBeCreatedFromFormat('11:12:45', 'h:i:s'); // true + * Carbon::canBeCreatedFromFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function canBeCreatedFromFormat($date, $format); + + /** + * Return the Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|DateTimeInterface|string|null $date + * + * @return static + */ + public function carbonize($date = null); + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateTimeInterface + */ + public function cast(string $className); + + /** + * Ceil the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function ceil($precision = 1); + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function ceilUnit($unit, $precision = 1); + + /** + * Ceil the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function ceilWeek($weekStartsAt = null); + + /** + * Similar to native modify() method of DateTime but can handle more grammars. + * + * @example + * ``` + * echo Carbon::now()->change('next 2pm'); + * ``` + * + * @link https://php.net/manual/en/datetime.modify.php + * + * @param string $modifier + * + * @return static|false + */ + public function change($modifier); + + /** + * Cleanup properties attached to the public scope of DateTime when a dump of the date is requested. + * foreach ($date as $_) {} + * serializer($date) + * var_export($date) + * get_object_vars($date) + */ + public function cleanupDumpProperties(); + + /** + * @alias copy + * + * Get a copy of the instance. + * + * @return static + */ + public function clone(); + + /** + * Get the closest date from the instance (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function closest($date1, $date2); + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy(); + + /** + * Create a new Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * @param DateTimeInterface|int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null); + + /** + * Create a Carbon instance from just a date. The time portion is set to now. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromDate($year = null, $month = null, $day = null, $tz = null); + + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + #[ReturnTypeWillChange] + public static function createFromFormat($format, $time, $tz = null); + + /** + * Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz optional timezone + * @param string|null $locale locale to be used for LTS, LT, LL, LLL, etc. macro-formats (en by fault, unneeded if no such macro-format in use) + * @param \Symfony\Component\Translation\TranslatorInterface $translator optional custom translator to use for macro-formats + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null); + + /** + * Create a Carbon instance from a specific format and a string in a given language. + * + * @param string $format Datetime format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleFormat($format, $locale, $time, $tz = null); + + /** + * Create a Carbon instance from a specific ISO format and a string in a given language. + * + * @param string $format Datetime ISO format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleIsoFormat($format, $locale, $time, $tz = null); + + /** + * Create a Carbon instance from just a time. The date portion is set to today. + * + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null); + + /** + * Create a Carbon instance from a time string. The date portion is set to today. + * + * @param string $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromTimeString($time, $tz = null); + + /** + * Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestamp($timestamp, $tz = null); + + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestampMs($timestamp, $tz = null); + + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampMsUTC($timestamp); + + /** + * Create a Carbon instance from an timestamp keeping the timezone to UTC. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampUTC($timestamp); + + /** + * Create a Carbon instance from just a date. The time portion is set to midnight. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createMidnightDate($year = null, $month = null, $day = null, $tz = null); + + /** + * Create a new safe Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * If one of the set values is not valid, an InvalidDateException + * will be thrown. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidDateException + * + * @return static|false + */ + public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null); + + /** + * Create a new Carbon instance from a specific date and time using strict validation. + * + * @see create() + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null); + + /** + * Get/set the day of year. + * + * @param int|null $value new value for day of year if using as setter. + * + * @return static|int + */ + public function dayOfYear($value = null); + + /** + * Get the difference as a CarbonInterval instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return CarbonInterval + */ + public function diffAsCarbonInterval($date = null, $absolute = true, array $skip = []); + + /** + * Get the difference by the given interval using a filter closure. + * + * @param CarbonInterval $ci An interval to traverse by + * @param Closure $callback + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffFiltered(CarbonInterval $ci, Closure $callback, $date = null, $absolute = true); + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @example + * ``` + * echo Carbon::tomorrow()->diffForHumans() . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 2]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 3, 'join' => true]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday()) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday(), ['short' => true]) . "\n"; + * ``` + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'skip' entry, list of units to skip (array of strings or a single string, + * ` it can be the unit name (singular or plural) or its shortcut + * ` (y, m, w, d, h, min, s, ms, ยตs). + * - 'aUnit' entry, prefer "an hour" over "1 hour" if true + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * - 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ` short form of the units, e.g. 'hour' or 'h' (default value: s) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function diffForHumans($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get the difference in days rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDays($date = null, $absolute = true); + + /** + * Get the difference in days using a filter closure rounded down. + * + * @param Closure $callback + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDaysFiltered(Closure $callback, $date = null, $absolute = true); + + /** + * Get the difference in hours rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHours($date = null, $absolute = true); + + /** + * Get the difference in hours using a filter closure rounded down. + * + * @param Closure $callback + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHoursFiltered(Closure $callback, $date = null, $absolute = true); + + /** + * Get the difference in microseconds. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMicroseconds($date = null, $absolute = true); + + /** + * Get the difference in milliseconds rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMilliseconds($date = null, $absolute = true); + + /** + * Get the difference in minutes rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMinutes($date = null, $absolute = true); + + /** + * Get the difference in months rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMonths($date = null, $absolute = true); + + /** + * Get the difference in quarters rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInQuarters($date = null, $absolute = true); + + /** + * Get the difference in hours rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealHours($date = null, $absolute = true); + + /** + * Get the difference in microseconds using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMicroseconds($date = null, $absolute = true); + + /** + * Get the difference in milliseconds rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMilliseconds($date = null, $absolute = true); + + /** + * Get the difference in minutes rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMinutes($date = null, $absolute = true); + + /** + * Get the difference in seconds using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealSeconds($date = null, $absolute = true); + + /** + * Get the difference in seconds rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInSeconds($date = null, $absolute = true); + + /** + * Get the difference in weekdays rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekdays($date = null, $absolute = true); + + /** + * Get the difference in weekend days using a filter rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekendDays($date = null, $absolute = true); + + /** + * Get the difference in weeks rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeeks($date = null, $absolute = true); + + /** + * Get the difference in years + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInYears($date = null, $absolute = true); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function disableHumanDiffOption($humanDiffOption); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function enableHumanDiffOption($humanDiffOption); + + /** + * Modify to end of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function endOf($unit, ...$params); + + /** + * Resets the date to end of the century and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfCentury(); + * ``` + * + * @return static + */ + public function endOfCentury(); + + /** + * Resets the time to 23:59:59.999999 end of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDay(); + * ``` + * + * @return static + */ + public function endOfDay(); + + /** + * Resets the date to end of the decade and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDecade(); + * ``` + * + * @return static + */ + public function endOfDecade(); + + /** + * Modify to end of current hour, minutes and seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfHour(); + * ``` + * + * @return static + */ + public function endOfHour(); + + /** + * Resets the date to end of the millennium and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMillennium(); + * ``` + * + * @return static + */ + public function endOfMillennium(); + + /** + * Modify to end of current minute, seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMinute(); + * ``` + * + * @return static + */ + public function endOfMinute(); + + /** + * Resets the date to end of the month and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMonth(); + * ``` + * + * @return static + */ + public function endOfMonth(); + + /** + * Resets the date to end of the quarter and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfQuarter(); + * ``` + * + * @return static + */ + public function endOfQuarter(); + + /** + * Modify to end of current second, microseconds become 999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->endOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function endOfSecond(); + + /** + * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek(Carbon::SATURDAY) . "\n"; + * ``` + * + * @param int $weekEndsAt optional start allow you to specify the day of week to use to end the week + * + * @return static + */ + public function endOfWeek($weekEndsAt = null); + + /** + * Resets the date to end of the year and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfYear(); + * ``` + * + * @return static + */ + public function endOfYear(); + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->eq(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see equalTo() + * + * @return bool + */ + public function eq($date): bool; + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function equalTo($date): bool; + + /** + * Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * + * @param string $locale locale ex. en + * @param callable $func + * + * @return mixed + */ + public static function executeWithLocale($locale, $func); + + /** + * Get the farthest date from the instance (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function farthest($date1, $date2); + + /** + * Modify to the first occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * first day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function firstOfMonth($dayOfWeek = null); + + /** + * Modify to the first occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * first day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfQuarter($dayOfWeek = null); + + /** + * Modify to the first occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * first day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfYear($dayOfWeek = null); + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInDays($date = null, $absolute = true); + + /** + * Get the difference in hours as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInHours($date = null, $absolute = true); + + /** + * Get the difference in minutes as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMinutes($date = null, $absolute = true); + + /** + * Get the difference in months as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMonths($date = null, $absolute = true); + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealDays($date = null, $absolute = true); + + /** + * Get the difference in hours as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealHours($date = null, $absolute = true); + + /** + * Get the difference in minutes as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMinutes($date = null, $absolute = true); + + /** + * Get the difference in months as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMonths($date = null, $absolute = true); + + /** + * Get the difference in seconds as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealSeconds($date = null, $absolute = true); + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealWeeks($date = null, $absolute = true); + + /** + * Get the difference in year as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealYears($date = null, $absolute = true); + + /** + * Get the difference in seconds as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInSeconds($date = null, $absolute = true); + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInWeeks($date = null, $absolute = true); + + /** + * Get the difference in year as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInYears($date = null, $absolute = true); + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function floor($precision = 1); + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function floorUnit($unit, $precision = 1); + + /** + * Truncate the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function floorWeek($weekStartsAt = null); + + /** + * Format the instance with the current locale. You can set the current + * locale using setlocale() https://php.net/setlocale. + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat() instead. + * Deprecated since 2.55.0 + * + * @param string $format + * + * @return string + */ + public function formatLocalized($format); + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function from($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get the difference in a human readable format in the current locale from current + * instance to now. + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function fromNow($syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Create an instance from a serialized string. + * + * @param string $value + * + * @throws InvalidFormatException + * + * @return static + */ + public static function fromSerialized($value); + + /** + * Register a custom macro. + * + * @param object|callable $macro + * @param int $priority marco with higher priority is tried first + * + * @return void + */ + public static function genericMacro($macro, $priority = 0); + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function get($name); + + /** + * Returns the alternative number for a given date property if available in the current locale. + * + * @param string $key date property + * + * @return string + */ + public function getAltNumber(string $key): string; + + /** + * Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * + * @return array + */ + public static function getAvailableLocales(); + + /** + * Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * + * @return Language[] + */ + public static function getAvailableLocalesInfo(); + + /** + * Returns list of calendar formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getCalendarFormats($locale = null); + + /** + * Get the days of the week + * + * @return array + */ + public static function getDays(); + + /** + * Return the number of days since the start of the week (using the current locale or the first parameter + * if explicitly given). + * + * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) + * + * @return int + */ + public function getDaysFromStartOfWeek(?int $weekStartsAt = null): int; + + /** + * Get the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @return string|null + */ + public static function getFallbackLocale(); + + /** + * List of replacements from date() format to isoFormat(). + * + * @return array + */ + public static function getFormatsToIsoReplacements(); + + /** + * Return default humanDiff() options (merged flags as integer). + * + * @return int + */ + public static function getHumanDiffOptions(); + + /** + * Returns list of locale formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getIsoFormats($locale = null); + + /** + * Returns list of locale units for ISO formatting. + * + * @return array + */ + public static function getIsoUnits(); + + /** + * {@inheritdoc} + * + * @return array + */ + #[ReturnTypeWillChange] + public static function getLastErrors(); + + /** + * Get the raw callable macro registered globally or locally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public function getLocalMacro($name); + + /** + * Get the translator of the current instance or the default if none set. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public function getLocalTranslator(); + + /** + * Get the current translator locale. + * + * @return string + */ + public static function getLocale(); + + /** + * Get the raw callable macro registered globally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public static function getMacro($name); + + /** + * get midday/noon hour + * + * @return int + */ + public static function getMidDayAt(); + + /** + * Returns the offset hour and minute formatted with +/- and a given separator (":" by default). + * For example, if the time zone is 9 hours 30 minutes, you'll get "+09:30", with "@@" as first + * argument, "+09@@30", with "" as first argument, "+0930". Negative offset will return something + * like "-12:00". + * + * @param string $separator string to place between hours and minutes (":" by default) + * + * @return string + */ + public function getOffsetString($separator = ':'); + + /** + * Returns a unit of the instance padded with 0 by default or any other string if specified. + * + * @param string $unit Carbon unit name + * @param int $length Length of the output (2 by default) + * @param string $padString String to use for padding ("0" by default) + * @param int $padType Side(s) to pad (STR_PAD_LEFT by default) + * + * @return string + */ + public function getPaddedUnit($unit, $length = 2, $padString = '0', $padType = 0); + + /** + * Returns a timestamp rounded with the given precision (6 by default). + * + * @example getPreciseTimestamp() 1532087464437474 (microsecond maximum precision) + * @example getPreciseTimestamp(6) 1532087464437474 + * @example getPreciseTimestamp(5) 153208746443747 (1/100000 second precision) + * @example getPreciseTimestamp(4) 15320874644375 (1/10000 second precision) + * @example getPreciseTimestamp(3) 1532087464437 (millisecond precision) + * @example getPreciseTimestamp(2) 153208746444 (1/100 second precision) + * @example getPreciseTimestamp(1) 15320874644 (1/10 second precision) + * @example getPreciseTimestamp(0) 1532087464 (second precision) + * @example getPreciseTimestamp(-1) 153208746 (10 second precision) + * @example getPreciseTimestamp(-2) 15320875 (100 second precision) + * + * @param int $precision + * + * @return float + */ + public function getPreciseTimestamp($precision = 6); + + /** + * Returns current local settings. + * + * @return array + */ + public function getSettings(); + + /** + * Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * + * @return Closure|static the current instance used for testing + */ + public static function getTestNow(); + + /** + * Return a format from H:i to H:i:s.u according to given unit precision. + * + * @param string $unitPrecision "minute", "second", "millisecond" or "microsecond" + * + * @return string + */ + public static function getTimeFormatByPrecision($unitPrecision); + + /** + * Returns the timestamp with millisecond precision. + * + * @return int + */ + public function getTimestampMs(); + + /** + * Get the translation of the current week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "", "_short" or "_min" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedDayName($context = null, $keySuffix = '', $defaultValue = null); + + /** + * Get the translation of the current abbreviated week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedMinDayName($context = null); + + /** + * Get the translation of the current month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "" or "_short" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedMonthName($context = null, $keySuffix = '', $defaultValue = null); + + /** + * Get the translation of the current short week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortDayName($context = null); + + /** + * Get the translation of the current short month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortMonthName($context = null); + + /** + * Returns raw translation message for a given key. + * + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * @param \Symfony\Component\Translation\TranslatorInterface $translator an optional translator to use + * + * @return string + */ + public function getTranslationMessage(string $key, ?string $locale = null, ?string $default = null, $translator = null); + + /** + * Returns raw translation message for a given key. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator the translator to use + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * + * @return string + */ + public static function getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null); + + /** + * Get the default translator instance in use. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public static function getTranslator(); + + /** + * Get the last day of week + * + * @return int + */ + public static function getWeekEndsAt(); + + /** + * Get the first day of week + * + * @return int + */ + public static function getWeekStartsAt(); + + /** + * Get weekend days + * + * @return array + */ + public static function getWeekendDays(); + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThan($date): bool; + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThanOrEqualTo($date): bool; + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function gt($date): bool; + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThanOrEqualTo() + * + * @return bool + */ + public function gte($date): bool; + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormat('11:12:45', 'h:i:s'); // true + * Carbon::hasFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormat($date, $format); + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormatWithModifiers('31/08/2015', 'd#m#Y'); // true + * Carbon::hasFormatWithModifiers('31/08/2015', 'm#d#Y'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormatWithModifiers($date, $format): bool; + + /** + * Checks if macro is registered globally or locally. + * + * @param string $name + * + * @return bool + */ + public function hasLocalMacro($name); + + /** + * Return true if the current instance has its own translator. + * + * @return bool + */ + public function hasLocalTranslator(); + + /** + * Checks if macro is registered globally. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name); + + /** + * Determine if a time string will produce a relative date. + * + * @param string $time + * + * @return bool true if time match a relative date, false if absolute or invalid time string + */ + public static function hasRelativeKeywords($time); + + /** + * Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * + * @return bool true if there is a test instance, otherwise false + */ + public static function hasTestNow(); + + /** + * Create a Carbon instance from a DateTime one. + * + * @param DateTimeInterface $date + * + * @return static + */ + public static function instance($date); + + /** + * Returns true if the current date matches the given string. + * + * @example + * ``` + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2018')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('Sunday')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('June')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:45')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:00')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12h')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3pm')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3am')); // false + * ``` + * + * @param string $tester day name, month name, hour, date, etc. as string + * + * @return bool + */ + public function is(string $tester); + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function isAfter($date): bool; + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function isBefore($date): bool; + + /** + * Determines if the instance is between two others + * + * @example + * ``` + * Carbon::parse('2018-07-25')->isBetween('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function isBetween($date1, $date2, $equal = true): bool; + + /** + * Check if its the birthday. Compares the date/month values of the two dates. + * + * @example + * ``` + * Carbon::now()->subYears(5)->isBirthday(); // true + * Carbon::now()->subYears(5)->subDay()->isBirthday(); // false + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-05')); // true + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-06')); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @return bool + */ + public function isBirthday($date = null); + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::now()->isCurrentUnit('hour'); // true + * Carbon::now()->subHours(2)->isCurrentUnit('hour'); // false + * ``` + * + * @param string $unit The unit to test. + * + * @throws BadMethodCallException + * + * @return bool + */ + public function isCurrentUnit($unit); + + /** + * Checks if this day is a specific day of the week. + * + * @example + * ``` + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::WEDNESDAY); // true + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::FRIDAY); // false + * Carbon::parse('2019-07-17')->isDayOfWeek('Wednesday'); // true + * Carbon::parse('2019-07-17')->isDayOfWeek('Friday'); // false + * ``` + * + * @param int $dayOfWeek + * + * @return bool + */ + public function isDayOfWeek($dayOfWeek); + + /** + * Check if the instance is end of day. + * + * @example + * ``` + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:58.999999')->isEndOfDay(); // false + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(true); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(true); // false + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isEndOfDay($checkMicroseconds = false); + + /** + * Returns true if the date was created using CarbonImmutable::endOfTime() + * + * @return bool + */ + public function isEndOfTime(): bool; + + /** + * Determines if the instance is in the future, ie. greater (after) than now. + * + * @example + * ``` + * Carbon::now()->addHours(5)->isFuture(); // true + * Carbon::now()->subHours(5)->isFuture(); // false + * ``` + * + * @return bool + */ + public function isFuture(); + + /** + * Returns true if the current class/instance is immutable. + * + * @return bool + */ + public static function isImmutable(); + + /** + * Check if today is the last day of the Month + * + * @example + * ``` + * Carbon::parse('2019-02-28')->isLastOfMonth(); // true + * Carbon::parse('2019-03-28')->isLastOfMonth(); // false + * Carbon::parse('2019-03-30')->isLastOfMonth(); // false + * Carbon::parse('2019-03-31')->isLastOfMonth(); // true + * Carbon::parse('2019-04-30')->isLastOfMonth(); // true + * ``` + * + * @return bool + */ + public function isLastOfMonth(); + + /** + * Determines if the instance is a leap year. + * + * @example + * ``` + * Carbon::parse('2020-01-01')->isLeapYear(); // true + * Carbon::parse('2019-01-01')->isLeapYear(); // false + * ``` + * + * @return bool + */ + public function isLeapYear(); + + /** + * Determines if the instance is a long year (using ISO 8601 year). + * + * @example + * ``` + * Carbon::parse('2015-01-01')->isLongIsoYear(); // true + * Carbon::parse('2016-01-01')->isLongIsoYear(); // true + * Carbon::parse('2016-01-03')->isLongIsoYear(); // false + * Carbon::parse('2019-12-29')->isLongIsoYear(); // false + * Carbon::parse('2019-12-30')->isLongIsoYear(); // true + * ``` + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongIsoYear(); + + /** + * Determines if the instance is a long year (using calendar year). + * + * โš ๏ธ This method completely ignores month and day to use the numeric year number, + * it's not correct if the exact date matters. For instance as `2019-12-30` is already + * in the first week of the 2020 year, if you want to know from this date if ISO week + * year 2020 is a long year, use `isLongIsoYear` instead. + * + * @example + * ``` + * Carbon::create(2015)->isLongYear(); // true + * Carbon::create(2016)->isLongYear(); // false + * ``` + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongYear(); + + /** + * Check if the instance is midday. + * + * @example + * ``` + * Carbon::parse('2019-02-28 11:59:59.999999')->isMidday(); // false + * Carbon::parse('2019-02-28 12:00:00')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:00.999999')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:01')->isMidday(); // false + * ``` + * + * @return bool + */ + public function isMidday(); + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:01')->isMidnight(); // false + * ``` + * + * @return bool + */ + public function isMidnight(); + + /** + * Returns true if a property can be changed via setter. + * + * @param string $unit + * + * @return bool + */ + public static function isModifiableUnit($unit); + + /** + * Returns true if the current class/instance is mutable. + * + * @return bool + */ + public static function isMutable(); + + /** + * Determines if the instance is in the past, ie. less (before) than now. + * + * @example + * ``` + * Carbon::now()->subHours(5)->isPast(); // true + * Carbon::now()->addHours(5)->isPast(); // false + * ``` + * + * @return bool + */ + public function isPast(); + + /** + * Compares the formatted values of the two dates. + * + * @example + * ``` + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-12-13')); // true + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-06-14')); // false + * ``` + * + * @param string $format date formats to compare. + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date instance to compare with or null to use current day. + * + * @return bool + */ + public function isSameAs($format, $date = null); + + /** + * Checks if the passed in date is in the same month as the instanceยดs month. + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-01-01')); // true + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-02-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01'), false); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameMonth($date = null, $ofSameYear = true); + + /** + * Checks if the passed in date is in the same quarter as the instance quarter (and year if needed). + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-03-01')); // true + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-04-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01'), false); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date The instance to compare with or null to use current day. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameQuarter($date = null, $ofSameYear = true); + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::parse('2019-01-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // true + * Carbon::parse('2018-12-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // false + * ``` + * + * @param string $unit singular unit string + * @param \Carbon\Carbon|\DateTimeInterface|null $date instance to compare with or null to use current day. + * + * @throws BadComparisonUnitException + * + * @return bool + */ + public function isSameUnit($unit, $date = null); + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:01')->isStartOfDay(); // false + * Carbon::parse('2019-02-28 00:00:00.000000')->isStartOfDay(true); // true + * Carbon::parse('2019-02-28 00:00:00.000012')->isStartOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isStartOfDay($checkMicroseconds = false); + + /** + * Returns true if the date was created using CarbonImmutable::startOfTime() + * + * @return bool + */ + public function isStartOfTime(): bool; + + /** + * Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * + * @return bool + */ + public static function isStrictModeEnabled(); + + /** + * Determines if the instance is today. + * + * @example + * ``` + * Carbon::today()->isToday(); // true + * Carbon::tomorrow()->isToday(); // false + * ``` + * + * @return bool + */ + public function isToday(); + + /** + * Determines if the instance is tomorrow. + * + * @example + * ``` + * Carbon::tomorrow()->isTomorrow(); // true + * Carbon::yesterday()->isTomorrow(); // false + * ``` + * + * @return bool + */ + public function isTomorrow(); + + /** + * Determines if the instance is a weekday. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekday(); // false + * Carbon::parse('2019-07-15')->isWeekday(); // true + * ``` + * + * @return bool + */ + public function isWeekday(); + + /** + * Determines if the instance is a weekend day. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekend(); // true + * Carbon::parse('2019-07-15')->isWeekend(); // false + * ``` + * + * @return bool + */ + public function isWeekend(); + + /** + * Determines if the instance is yesterday. + * + * @example + * ``` + * Carbon::yesterday()->isYesterday(); // true + * Carbon::tomorrow()->isYesterday(); // false + * ``` + * + * @return bool + */ + public function isYesterday(); + + /** + * Format in the current language using ISO replacement patterns. + * + * @param string $format + * @param string|null $originalFormat provide context if a chunk has been passed alone + * + * @return string + */ + public function isoFormat(string $format, ?string $originalFormat = null): string; + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function isoWeek($week = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function isoWeekYear($year = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Get/set the ISO weekday from 1 (Monday) to 7 (Sunday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function isoWeekday($value = null); + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function isoWeeksInYear($dayOfWeek = null, $dayOfYear = null); + + /** + * Prepare the object for JSON serialization. + * + * @return array|string + */ + #[ReturnTypeWillChange] + public function jsonSerialize(); + + /** + * Modify to the last occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * last day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function lastOfMonth($dayOfWeek = null); + + /** + * Modify to the last occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * last day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfQuarter($dayOfWeek = null); + + /** + * Modify to the last occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * last day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfYear($dayOfWeek = null); + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThan($date): bool; + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThanOrEqualTo($date): bool; + + /** + * Get/set the locale for the current instance. + * + * @param string|null $locale + * @param string ...$fallbackLocales + * + * @return $this|string + */ + public function locale(?string $locale = null, ...$fallbackLocales); + + /** + * Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffOneDayWords($locale); + + /** + * Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffSyntax($locale); + + /** + * Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffTwoDayWords($locale); + + /** + * Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasPeriodSyntax($locale); + + /** + * Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasShortUnits($locale); + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function lt($date): bool; + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThanOrEqualTo() + * + * @return bool + */ + public function lte($date): bool; + + /** + * Register a custom macro. + * + * @example + * ``` + * $userSettings = [ + * 'locale' => 'pt', + * 'timezone' => 'America/Sao_Paulo', + * ]; + * Carbon::macro('userFormat', function () use ($userSettings) { + * return $this->copy()->locale($userSettings['locale'])->tz($userSettings['timezone'])->calendar(); + * }); + * echo Carbon::yesterday()->hours(11)->userFormat(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro); + + /** + * Make a Carbon instance from given variable if possible. + * + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * + * @param mixed $var + * + * @throws InvalidFormatException + * + * @return static|null + */ + public static function make($var); + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return static + */ + public function max($date = null); + + /** + * Create a Carbon instance for the greatest supported date. + * + * @return static + */ + public static function maxValue(); + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see max() + * + * @return static + */ + public function maximum($date = null); + + /** + * Return the meridiem of the current time in the current locale. + * + * @param bool $isLower if true, returns lowercase variant if available in the current locale. + * + * @return string + */ + public function meridiem(bool $isLower = false): string; + + /** + * Modify to midday, default to self::$midDayAt + * + * @return static + */ + public function midDay(); + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return static + */ + public function min($date = null); + + /** + * Create a Carbon instance for the lowest supported date. + * + * @return static + */ + public static function minValue(); + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see min() + * + * @return static + */ + public function minimum($date = null); + + /** + * Mix another object into the class. + * + * @example + * ``` + * Carbon::mixin(new class { + * public function addMoon() { + * return function () { + * return $this->addDays(30); + * }; + * } + * public function subMoon() { + * return function () { + * return $this->subDays(30); + * }; + * } + * }); + * $fullMoon = Carbon::create('2018-12-22'); + * $nextFullMoon = $fullMoon->addMoon(); + * $blackMoon = Carbon::create('2019-01-06'); + * $previousBlackMoon = $blackMoon->subMoon(); + * echo "$nextFullMoon\n"; + * echo "$previousBlackMoon\n"; + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin); + + /** + * Calls \DateTime::modify if mutable or \DateTimeImmutable::modify else. + * + * @see https://php.net/manual/en/datetime.modify.php + * + * @return static|false + */ + #[ReturnTypeWillChange] + public function modify($modify); + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->ne(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($date): bool; + + /** + * Modify to the next occurrence of a given modifier such as a day of + * the week. If no modifier is provided, modify to the next occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param string|int|null $modifier + * + * @return static|false + */ + public function next($modifier = null); + + /** + * Go forward to the next weekday. + * + * @return static + */ + public function nextWeekday(); + + /** + * Go forward to the next weekend day. + * + * @return static + */ + public function nextWeekendDay(); + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function notEqualTo($date): bool; + + /** + * Get a Carbon instance for the current date and time. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function now($tz = null); + + /** + * Returns a present instance in the same timezone. + * + * @return static + */ + public function nowWithSameTz(); + + /** + * Modify to the given occurrence of a given day of the week + * in the current month. If the calculated occurrence is outside the scope + * of the current month, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfMonth($nth, $dayOfWeek); + + /** + * Modify to the given occurrence of a given day of the week + * in the current quarter. If the calculated occurrence is outside the scope + * of the current quarter, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfQuarter($nth, $dayOfWeek); + + /** + * Modify to the given occurrence of a given day of the week + * in the current year. If the calculated occurrence is outside the scope + * of the current year, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfYear($nth, $dayOfWeek); + + /** + * Return a property with its ordinal. + * + * @param string $key + * @param string|null $period + * + * @return string + */ + public function ordinal(string $key, ?string $period = null): string; + + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parse($time = null, $tz = null); + + /** + * Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * + * @param string $time date/time string in the given language (may also contain English). + * @param string|null $locale if locale is null or not specified, current global locale will be + * used instead. + * @param DateTimeZone|string|null $tz optional timezone for the new instance. + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parseFromLocale($time, $locale = null, $tz = null); + + /** + * Returns standardized plural of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function pluralUnit(string $unit): string; + + /** + * Modify to the previous occurrence of a given modifier such as a day of + * the week. If no dayOfWeek is provided, modify to the previous occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param string|int|null $modifier + * + * @return static|false + */ + public function previous($modifier = null); + + /** + * Go backward to the previous weekday. + * + * @return static + */ + public function previousWeekday(); + + /** + * Go backward to the previous weekend day. + * + * @return static + */ + public function previousWeekendDay(); + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|Carbon|CarbonImmutable|null $end period end date + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function range($end = null, $interval = null, $unit = null); + + /** + * Call native PHP DateTime/DateTimeImmutable add() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawAdd(DateInterval $interval); + + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function rawCreateFromFormat($format, $time, $tz = null); + + /** + * @see https://php.net/manual/en/datetime.format.php + * + * @param string $format + * + * @return string + */ + public function rawFormat($format); + + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function rawParse($time = null, $tz = null); + + /** + * Call native PHP DateTime/DateTimeImmutable sub() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawSub(DateInterval $interval); + + /** + * Remove all macros and generic macros. + */ + public static function resetMacros(); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetMonthsOverflow(); + + /** + * Reset the format used to the default when type juggling a Carbon instance to a string + * + * @return void + */ + public static function resetToStringFormat(); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetYearsOverflow(); + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return CarbonInterface + */ + public function round($precision = 1, $function = 'round'); + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int $precision + * @param string $function + * + * @return CarbonInterface + */ + public function roundUnit($unit, $precision = 1, $function = 'round'); + + /** + * Round the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function roundWeek($weekStartsAt = null); + + /** + * The number of seconds since midnight. + * + * @return int + */ + public function secondsSinceMidnight(); + + /** + * The number of seconds until 23:59:59. + * + * @return int + */ + public function secondsUntilEndOfDay(); + + /** + * Return a serialized string of the instance. + * + * @return string + */ + public function serialize(); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform Carbon object before the serialization. + * + * JSON serialize all Carbon instances using the given callback. + * + * @param callable $callback + * + * @return void + */ + public static function serializeUsing($callback); + + /** + * Set a part of the Carbon object + * + * @param string|array $name + * @param string|int|DateTimeZone $value + * + * @throws ImmutableException|UnknownSetterException + * + * @return $this + */ + public function set($name, $value = null); + + /** + * Set the date with gregorian year, month and day numbers. + * + * @see https://php.net/manual/en/datetime.setdate.php + * + * @param int $year + * @param int $month + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setDate($year, $month, $day); + + /** + * Set the year, month, and date for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setDateFrom($date = null); + + /** + * Set the date and time all together. + * + * @param int $year + * @param int $month + * @param int $day + * @param int $hour + * @param int $minute + * @param int $second + * @param int $microseconds + * + * @return static + */ + public function setDateTime($year, $month, $day, $hour, $minute, $second = 0, $microseconds = 0); + + /** + * Set the date and time for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date + * + * @return static + */ + public function setDateTimeFrom($date = null); + + /** + * Set the day (keeping the current time) to the start of the week + the number of days passed as the first + * parameter. First day of week is driven by the locale unless explicitly set with the second parameter. + * + * @param int $numberOfDays number of days to add after the start of the current week + * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) + * + * @return static + */ + public function setDaysFromStartOfWeek(int $numberOfDays, ?int $weekStartsAt = null); + + /** + * Set the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @param string $locale + */ + public static function setFallbackLocale($locale); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOptions + */ + public static function setHumanDiffOptions($humanDiffOptions); + + /** + * Set a date according to the ISO 8601 standard - using weeks and day offsets rather than specific dates. + * + * @see https://php.net/manual/en/datetime.setisodate.php + * + * @param int $year + * @param int $week + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setISODate($year, $week, $day = 1); + + /** + * Set the translator for the current instance. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return $this + */ + public function setLocalTranslator(TranslatorInterface $translator); + + /** + * Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function setLocale($locale); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * + * Set midday/noon hour + * + * @param int $hour midday hour + * + * @return void + */ + public static function setMidDayAt($hour); + + /** + * Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + */ + public static function setTestNow($testNow = null); + + /** + * Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + */ + public static function setTestNowAndTimezone($testNow = null, $tz = null); + + /** + * Resets the current time of the DateTime object to a different time. + * + * @see https://php.net/manual/en/datetime.settime.php + * + * @param int $hour + * @param int $minute + * @param int $second + * @param int $microseconds + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTime($hour, $minute, $second = 0, $microseconds = 0); + + /** + * Set the hour, minute, second and microseconds for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setTimeFrom($date = null); + + /** + * Set the time by time string. + * + * @param string $time + * + * @return static + */ + public function setTimeFromTimeString($time); + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimestamp($unixTimestamp); + + /** + * Set the instance's timezone from a string or object. + * + * @param DateTimeZone|string $value + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimezone($value); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump another string + * format. + * + * Set the default format used when type juggling a Carbon instance to a string. + * + * @param string|Closure|null $format + * + * @return void + */ + public static function setToStringFormat($format); + + /** + * Set the default translator instance to use. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return void + */ + public static function setTranslator(TranslatorInterface $translator); + + /** + * Set specified unit to new given value. + * + * @param string $unit year, month, day, hour, minute, second or microsecond + * @param int $value new value for given unit + * + * @return static + */ + public function setUnit($unit, $value = null); + + /** + * Set any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value new value for the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function setUnitNoOverflow($valueUnit, $value, $overflowUnit); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * + * Set if UTF8 will be used for localized date/time. + * + * @param bool $utf8 + */ + public static function setUtf8($utf8); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * + * Set the last day of week + * + * @param int|string $day week end day (or 'auto' to get the day before the first day of week + * from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekEndsAt($day); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * + * Set the first day of week + * + * @param int|string $day week start day (or 'auto' to get the first day of week from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekStartsAt($day); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * + * Set weekend days + * + * @param array $days + * + * @return void + */ + public static function setWeekendDays($days); + + /** + * Set specific options. + * - strictMode: true|false|null + * - monthOverflow: true|false|null + * - yearOverflow: true|false|null + * - humanDiffOptions: int|null + * - toStringFormat: string|Closure|null + * - toJsonFormat: string|Closure|null + * - locale: string|null + * - timezone: \DateTimeZone|string|int|null + * - macros: array|null + * - genericMacros: array|null + * + * @param array $settings + * + * @return $this|static + */ + public function settings(array $settings); + + /** + * Set the instance's timezone from a string or object and add/subtract the offset difference. + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function shiftTimezone($value); + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowMonths(); + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowYears(); + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + */ + public function since($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Returns standardized singular of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function singularUnit(string $unit): string; + + /** + * Modify to start of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function startOf($unit, ...$params); + + /** + * Resets the date to the first day of the century and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfCentury(); + * ``` + * + * @return static + */ + public function startOfCentury(); + + /** + * Resets the time to 00:00:00 start of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDay(); + * ``` + * + * @return static + */ + public function startOfDay(); + + /** + * Resets the date to the first day of the decade and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDecade(); + * ``` + * + * @return static + */ + public function startOfDecade(); + + /** + * Modify to start of current hour, minutes and seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfHour(); + * ``` + * + * @return static + */ + public function startOfHour(); + + /** + * Resets the date to the first day of the millennium and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMillennium(); + * ``` + * + * @return static + */ + public function startOfMillennium(); + + /** + * Modify to start of current minute, seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMinute(); + * ``` + * + * @return static + */ + public function startOfMinute(); + + /** + * Resets the date to the first day of the month and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMonth(); + * ``` + * + * @return static + */ + public function startOfMonth(); + + /** + * Resets the date to the first day of the quarter and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfQuarter(); + * ``` + * + * @return static + */ + public function startOfQuarter(); + + /** + * Modify to start of current second, microseconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function startOfSecond(); + + /** + * Resets the date to the first day of week (defined in $weekStartsAt) and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek(Carbon::SUNDAY) . "\n"; + * ``` + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return static + */ + public function startOfWeek($weekStartsAt = null); + + /** + * Resets the date to the first day of the year and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfYear(); + * ``` + * + * @return static + */ + public function startOfYear(); + + /** + * Subtract given units or interval to the current instance. + * + * @example $date->sub('hour', 3) + * @example $date->sub(15, 'days') + * @example $date->sub(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function sub($unit, $value = 1, $overflow = null); + + public function subRealUnit($unit, $value = 1); + + /** + * Subtract given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subUnit($unit, $value = 1, $overflow = null); + + /** + * Subtract any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to subtract to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function subUnitNoOverflow($valueUnit, $value, $overflowUnit); + + /** + * Subtract given units or interval to the current instance. + * + * @see sub() + * + * @param string|DateInterval $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subtract($unit, $value = 1, $overflow = null); + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @return string + */ + public function timespan($other = null, $timezone = null); + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + public function timestamp($unixTimestamp); + + /** + * @alias setTimezone + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function timezone($value); + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * When comparing a value in the past to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the future to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the past to another value: + * 1 hour after + * 5 months after + * + * When comparing a value in the future to another value: + * 1 hour before + * 5 months before + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function to($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get default array representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toArray()); + * ``` + * + * @return array + */ + public function toArray(); + + /** + * Format the instance as ATOM + * + * @example + * ``` + * echo Carbon::now()->toAtomString(); + * ``` + * + * @return string + */ + public function toAtomString(); + + /** + * Format the instance as COOKIE + * + * @example + * ``` + * echo Carbon::now()->toCookieString(); + * ``` + * + * @return string + */ + public function toCookieString(); + + /** + * @alias toDateTime + * + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDate()); + * ``` + * + * @return DateTime + */ + public function toDate(); + + /** + * Format the instance as date + * + * @example + * ``` + * echo Carbon::now()->toDateString(); + * ``` + * + * @return string + */ + public function toDateString(); + + /** + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTime()); + * ``` + * + * @return DateTime + */ + public function toDateTime(); + + /** + * Return native toDateTimeImmutable PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTimeImmutable()); + * ``` + * + * @return DateTimeImmutable + */ + public function toDateTimeImmutable(); + + /** + * Format the instance as date and time T-separated with no timezone + * + * @example + * ``` + * echo Carbon::now()->toDateTimeLocalString(); + * echo "\n"; + * echo Carbon::now()->toDateTimeLocalString('minute'); // You can specify precision among: minute, second, millisecond and microsecond + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeLocalString($unitPrecision = 'second'); + + /** + * Format the instance as date and time + * + * @example + * ``` + * echo Carbon::now()->toDateTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeString($unitPrecision = 'second'); + + /** + * Format the instance with day, date and time + * + * @example + * ``` + * echo Carbon::now()->toDayDateTimeString(); + * ``` + * + * @return string + */ + public function toDayDateTimeString(); + + /** + * Format the instance as a readable date + * + * @example + * ``` + * echo Carbon::now()->toFormattedDateString(); + * ``` + * + * @return string + */ + public function toFormattedDateString(); + + /** + * Format the instance with the day, and a readable date + * + * @example + * ``` + * echo Carbon::now()->toFormattedDayDateString(); + * ``` + * + * @return string + */ + public function toFormattedDayDateString(): string; + + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z, if $keepOffset truthy, offset will be kept: + * 1977-04-22T01:00:00-05:00). + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toISOString() . "\n"; + * echo Carbon::now('America/Toronto')->toISOString(true) . "\n"; + * ``` + * + * @param bool $keepOffset Pass true to keep the date offset. Else forced to UTC. + * + * @return null|string + */ + public function toISOString($keepOffset = false); + + /** + * Return a immutable copy of the instance. + * + * @return CarbonImmutable + */ + public function toImmutable(); + + /** + * Format the instance as ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601String(); + * ``` + * + * @return string + */ + public function toIso8601String(); + + /** + * Convert the instance to UTC and return as Zulu ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601ZuluString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toIso8601ZuluString($unitPrecision = 'second'); + + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z) with UTC timezone. + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toJSON(); + * ``` + * + * @return null|string + */ + public function toJSON(); + + /** + * Return a mutable copy of the instance. + * + * @return Carbon + */ + public function toMutable(); + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function toNow($syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get default object representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toObject()); + * ``` + * + * @return object + */ + public function toObject(); + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|Carbon|CarbonImmutable|int|null $end period end date or recurrences count if int + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function toPeriod($end = null, $interval = null, $unit = null); + + /** + * Format the instance as RFC1036 + * + * @example + * ``` + * echo Carbon::now()->toRfc1036String(); + * ``` + * + * @return string + */ + public function toRfc1036String(); + + /** + * Format the instance as RFC1123 + * + * @example + * ``` + * echo Carbon::now()->toRfc1123String(); + * ``` + * + * @return string + */ + public function toRfc1123String(); + + /** + * Format the instance as RFC2822 + * + * @example + * ``` + * echo Carbon::now()->toRfc2822String(); + * ``` + * + * @return string + */ + public function toRfc2822String(); + + /** + * Format the instance as RFC3339 + * + * @param bool $extended + * + * @example + * ``` + * echo Carbon::now()->toRfc3339String() . "\n"; + * echo Carbon::now()->toRfc3339String(true) . "\n"; + * ``` + * + * @return string + */ + public function toRfc3339String($extended = false); + + /** + * Format the instance as RFC7231 + * + * @example + * ``` + * echo Carbon::now()->toRfc7231String(); + * ``` + * + * @return string + */ + public function toRfc7231String(); + + /** + * Format the instance as RFC822 + * + * @example + * ``` + * echo Carbon::now()->toRfc822String(); + * ``` + * + * @return string + */ + public function toRfc822String(); + + /** + * Format the instance as RFC850 + * + * @example + * ``` + * echo Carbon::now()->toRfc850String(); + * ``` + * + * @return string + */ + public function toRfc850String(); + + /** + * Format the instance as RSS + * + * @example + * ``` + * echo Carbon::now()->toRssString(); + * ``` + * + * @return string + */ + public function toRssString(); + + /** + * Returns english human readable complete date string. + * + * @example + * ``` + * echo Carbon::now()->toString(); + * ``` + * + * @return string + */ + public function toString(); + + /** + * Format the instance as time + * + * @example + * ``` + * echo Carbon::now()->toTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toTimeString($unitPrecision = 'second'); + + /** + * Format the instance as W3C + * + * @example + * ``` + * echo Carbon::now()->toW3cString(); + * ``` + * + * @return string + */ + public function toW3cString(); + + /** + * Create a Carbon instance for today. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function today($tz = null); + + /** + * Create a Carbon instance for tomorrow. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function tomorrow($tz = null); + + /** + * Translate using translation string or callback available. + * + * @param string $key + * @param array $parameters + * @param string|int|float|null $number + * @param \Symfony\Component\Translation\TranslatorInterface|null $translator + * @param bool $altNumbers + * + * @return string + */ + public function translate(string $key, array $parameters = [], $number = null, ?TranslatorInterface $translator = null, bool $altNumbers = false): string; + + /** + * Returns the alternative number for a given integer if available in the current locale. + * + * @param int $number + * + * @return string + */ + public function translateNumber(int $number): string; + + /** + * Translate a time string from a locale to an other. + * + * @param string $timeString date/time/duration string to translate (may also contain English) + * @param string|null $from input locale of the $timeString parameter (`Carbon::getLocale()` by default) + * @param string|null $to output locale of the result returned (`"en"` by default) + * @param int $mode specify what to translate with options: + * - self::TRANSLATE_ALL (default) + * - CarbonInterface::TRANSLATE_MONTHS + * - CarbonInterface::TRANSLATE_DAYS + * - CarbonInterface::TRANSLATE_UNITS + * - CarbonInterface::TRANSLATE_MERIDIEM + * You can use pipe to group: CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS + * + * @return string + */ + public static function translateTimeString($timeString, $from = null, $to = null, $mode = self::TRANSLATE_ALL); + + /** + * Translate a time string from the current locale (`$date->locale()`) to an other. + * + * @param string $timeString time string to translate + * @param string|null $to output locale of the result returned ("en" by default) + * + * @return string + */ + public function translateTimeStringTo($timeString, $to = null); + + /** + * Translate using translation string or callback available. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * @param string $key + * @param array $parameters + * @param null $number + * + * @return string + */ + public static function translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null): string; + + /** + * Format as ->format() do (using date replacements patterns from https://php.net/manual/en/function.date.php) + * but translate words whenever possible (months, day names, etc.) using the current locale. + * + * @param string $format + * + * @return string + */ + public function translatedFormat(string $format): string; + + /** + * Set the timezone or returns the timezone name if no arguments passed. + * + * @param DateTimeZone|string $value + * + * @return static|string + */ + public function tz($value = null); + + /** + * @alias getTimestamp + * + * Returns the UNIX timestamp for the current date. + * + * @return int + */ + public function unix(); + + /** + * @alias to + * + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function until($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if months should be calculated with overflow. + * + * @param bool $monthsOverflow + * + * @return void + */ + public static function useMonthsOverflow($monthsOverflow = true); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * Enable the strict mode (or disable with passing false). + * + * @param bool $strictModeEnabled + */ + public static function useStrictMode($strictModeEnabled = true); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if years should be calculated with overflow. + * + * @param bool $yearsOverflow + * + * @return void + */ + public static function useYearsOverflow($yearsOverflow = true); + + /** + * Set the instance's timezone to UTC. + * + * @return static + */ + public function utc(); + + /** + * Returns the minutes offset to UTC if no arguments passed, else set the timezone with given minutes shift passed. + * + * @param int|null $minuteOffset + * + * @return int|static + */ + public function utcOffset(?int $minuteOffset = null); + + /** + * Returns the milliseconds timestamps used amongst other by Date javascript objects. + * + * @return float + */ + public function valueOf(); + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function week($week = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function weekYear($year = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Get/set the weekday from 0 (Sunday) to 6 (Saturday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function weekday($value = null); + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function weeksInYear($dayOfWeek = null, $dayOfYear = null); + + /** + * Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * + * /!\ Use this method for unit tests only. + * + * @template T + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + * @param Closure(): T $callback + * + * @return T + */ + public static function withTestNow($testNow, $callback); + + /** + * Create a Carbon instance for yesterday. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function yesterday($tz = null); + + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php new file mode 100644 index 0000000000..8437c545e1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php @@ -0,0 +1,3054 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\BadFluentConstructorException; +use Carbon\Exceptions\BadFluentSetterException; +use Carbon\Exceptions\InvalidCastException; +use Carbon\Exceptions\InvalidIntervalException; +use Carbon\Exceptions\OutOfRangeException; +use Carbon\Exceptions\ParseErrorException; +use Carbon\Exceptions\UnitNotConfiguredException; +use Carbon\Exceptions\UnknownGetterException; +use Carbon\Exceptions\UnknownSetterException; +use Carbon\Exceptions\UnknownUnitException; +use Carbon\Traits\IntervalRounding; +use Carbon\Traits\IntervalStep; +use Carbon\Traits\MagicParameter; +use Carbon\Traits\Mixin; +use Carbon\Traits\Options; +use Carbon\Traits\ToStringFormat; +use Closure; +use DateInterval; +use DateMalformedIntervalStringException; +use DateTimeInterface; +use DateTimeZone; +use Exception; +use InvalidArgumentException; +use ReflectionException; +use ReturnTypeWillChange; +use RuntimeException; +use Throwable; + +/** + * A simple API extension for DateInterval. + * The implementation provides helpers to handle weeks but only days are saved. + * Weeks are calculated based on the total days of the current instance. + * + * @property int $years Total years of the current interval. + * @property int $months Total months of the current interval. + * @property int $weeks Total weeks of the current interval calculated from the days. + * @property int $dayz Total days of the current interval (weeks * 7 + days). + * @property int $hours Total hours of the current interval. + * @property int $minutes Total minutes of the current interval. + * @property int $seconds Total seconds of the current interval. + * @property int $microseconds Total microseconds of the current interval. + * @property int $milliseconds Total milliseconds of the current interval. + * @property int $microExcludeMilli Remaining microseconds without the milliseconds. + * @property int $dayzExcludeWeeks Total days remaining in the final week of the current instance (days % 7). + * @property int $daysExcludeWeeks alias of dayzExcludeWeeks + * @property-read float $totalYears Number of years equivalent to the interval. + * @property-read float $totalMonths Number of months equivalent to the interval. + * @property-read float $totalWeeks Number of weeks equivalent to the interval. + * @property-read float $totalDays Number of days equivalent to the interval. + * @property-read float $totalDayz Alias for totalDays. + * @property-read float $totalHours Number of hours equivalent to the interval. + * @property-read float $totalMinutes Number of minutes equivalent to the interval. + * @property-read float $totalSeconds Number of seconds equivalent to the interval. + * @property-read float $totalMilliseconds Number of milliseconds equivalent to the interval. + * @property-read float $totalMicroseconds Number of microseconds equivalent to the interval. + * @property-read string $locale locale of the current instance + * + * @method static CarbonInterval years($years = 1) Create instance specifying a number of years or modify the number of years if called on an instance. + * @method static CarbonInterval year($years = 1) Alias for years() + * @method static CarbonInterval months($months = 1) Create instance specifying a number of months or modify the number of months if called on an instance. + * @method static CarbonInterval month($months = 1) Alias for months() + * @method static CarbonInterval weeks($weeks = 1) Create instance specifying a number of weeks or modify the number of weeks if called on an instance. + * @method static CarbonInterval week($weeks = 1) Alias for weeks() + * @method static CarbonInterval days($days = 1) Create instance specifying a number of days or modify the number of days if called on an instance. + * @method static CarbonInterval dayz($days = 1) Alias for days() + * @method static CarbonInterval daysExcludeWeeks($days = 1) Create instance specifying a number of days or modify the number of days (keeping the current number of weeks) if called on an instance. + * @method static CarbonInterval dayzExcludeWeeks($days = 1) Alias for daysExcludeWeeks() + * @method static CarbonInterval day($days = 1) Alias for days() + * @method static CarbonInterval hours($hours = 1) Create instance specifying a number of hours or modify the number of hours if called on an instance. + * @method static CarbonInterval hour($hours = 1) Alias for hours() + * @method static CarbonInterval minutes($minutes = 1) Create instance specifying a number of minutes or modify the number of minutes if called on an instance. + * @method static CarbonInterval minute($minutes = 1) Alias for minutes() + * @method static CarbonInterval seconds($seconds = 1) Create instance specifying a number of seconds or modify the number of seconds if called on an instance. + * @method static CarbonInterval second($seconds = 1) Alias for seconds() + * @method static CarbonInterval milliseconds($milliseconds = 1) Create instance specifying a number of milliseconds or modify the number of milliseconds if called on an instance. + * @method static CarbonInterval millisecond($milliseconds = 1) Alias for milliseconds() + * @method static CarbonInterval microseconds($microseconds = 1) Create instance specifying a number of microseconds or modify the number of microseconds if called on an instance. + * @method static CarbonInterval microsecond($microseconds = 1) Alias for microseconds() + * @method $this addYears(int $years) Add given number of years to the current interval + * @method $this subYears(int $years) Subtract given number of years to the current interval + * @method $this addMonths(int $months) Add given number of months to the current interval + * @method $this subMonths(int $months) Subtract given number of months to the current interval + * @method $this addWeeks(int|float $weeks) Add given number of weeks to the current interval + * @method $this subWeeks(int|float $weeks) Subtract given number of weeks to the current interval + * @method $this addDays(int|float $days) Add given number of days to the current interval + * @method $this subDays(int|float $days) Subtract given number of days to the current interval + * @method $this addHours(int|float $hours) Add given number of hours to the current interval + * @method $this subHours(int|float $hours) Subtract given number of hours to the current interval + * @method $this addMinutes(int|float $minutes) Add given number of minutes to the current interval + * @method $this subMinutes(int|float $minutes) Subtract given number of minutes to the current interval + * @method $this addSeconds(int|float $seconds) Add given number of seconds to the current interval + * @method $this subSeconds(int|float $seconds) Subtract given number of seconds to the current interval + * @method $this addMilliseconds(int|float $milliseconds) Add given number of milliseconds to the current interval + * @method $this subMilliseconds(int|float $milliseconds) Subtract given number of milliseconds to the current interval + * @method $this addMicroseconds(int|float $microseconds) Add given number of microseconds to the current interval + * @method $this subMicroseconds(int|float $microseconds) Subtract given number of microseconds to the current interval + * @method $this roundYear(int|float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this roundYears(int|float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this floorYear(int|float $precision = 1) Truncate the current instance year with given precision. + * @method $this floorYears(int|float $precision = 1) Truncate the current instance year with given precision. + * @method $this ceilYear(int|float $precision = 1) Ceil the current instance year with given precision. + * @method $this ceilYears(int|float $precision = 1) Ceil the current instance year with given precision. + * @method $this roundMonth(int|float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this roundMonths(int|float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this floorMonth(int|float $precision = 1) Truncate the current instance month with given precision. + * @method $this floorMonths(int|float $precision = 1) Truncate the current instance month with given precision. + * @method $this ceilMonth(int|float $precision = 1) Ceil the current instance month with given precision. + * @method $this ceilMonths(int|float $precision = 1) Ceil the current instance month with given precision. + * @method $this roundWeek(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundWeeks(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorWeek(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorWeeks(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilWeek(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilWeeks(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundDay(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundDays(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorDay(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorDays(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilDay(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilDays(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundHour(int|float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this roundHours(int|float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this floorHour(int|float $precision = 1) Truncate the current instance hour with given precision. + * @method $this floorHours(int|float $precision = 1) Truncate the current instance hour with given precision. + * @method $this ceilHour(int|float $precision = 1) Ceil the current instance hour with given precision. + * @method $this ceilHours(int|float $precision = 1) Ceil the current instance hour with given precision. + * @method $this roundMinute(int|float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this roundMinutes(int|float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this floorMinute(int|float $precision = 1) Truncate the current instance minute with given precision. + * @method $this floorMinutes(int|float $precision = 1) Truncate the current instance minute with given precision. + * @method $this ceilMinute(int|float $precision = 1) Ceil the current instance minute with given precision. + * @method $this ceilMinutes(int|float $precision = 1) Ceil the current instance minute with given precision. + * @method $this roundSecond(int|float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this roundSeconds(int|float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this floorSecond(int|float $precision = 1) Truncate the current instance second with given precision. + * @method $this floorSeconds(int|float $precision = 1) Truncate the current instance second with given precision. + * @method $this ceilSecond(int|float $precision = 1) Ceil the current instance second with given precision. + * @method $this ceilSeconds(int|float $precision = 1) Ceil the current instance second with given precision. + * @method $this roundMillennium(int|float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this roundMillennia(int|float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this floorMillennium(int|float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this floorMillennia(int|float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this ceilMillennium(int|float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this ceilMillennia(int|float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this roundCentury(int|float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this roundCenturies(int|float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this floorCentury(int|float $precision = 1) Truncate the current instance century with given precision. + * @method $this floorCenturies(int|float $precision = 1) Truncate the current instance century with given precision. + * @method $this ceilCentury(int|float $precision = 1) Ceil the current instance century with given precision. + * @method $this ceilCenturies(int|float $precision = 1) Ceil the current instance century with given precision. + * @method $this roundDecade(int|float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this roundDecades(int|float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this floorDecade(int|float $precision = 1) Truncate the current instance decade with given precision. + * @method $this floorDecades(int|float $precision = 1) Truncate the current instance decade with given precision. + * @method $this ceilDecade(int|float $precision = 1) Ceil the current instance decade with given precision. + * @method $this ceilDecades(int|float $precision = 1) Ceil the current instance decade with given precision. + * @method $this roundQuarter(int|float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this roundQuarters(int|float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this floorQuarter(int|float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this floorQuarters(int|float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this ceilQuarter(int|float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this ceilQuarters(int|float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this roundMillisecond(int|float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this roundMilliseconds(int|float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this floorMillisecond(int|float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this floorMilliseconds(int|float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this ceilMillisecond(int|float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this ceilMilliseconds(int|float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this roundMicrosecond(int|float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this roundMicroseconds(int|float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this floorMicrosecond(int|float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this floorMicroseconds(int|float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this ceilMicrosecond(int|float $precision = 1) Ceil the current instance microsecond with given precision. + * @method $this ceilMicroseconds(int|float $precision = 1) Ceil the current instance microsecond with given precision. + */ +class CarbonInterval extends DateInterval implements CarbonConverterInterface +{ + use IntervalRounding; + use IntervalStep; + use MagicParameter; + use Mixin { + Mixin::mixin as baseMixin; + } + use Options; + use ToStringFormat; + + /** + * Interval spec period designators + */ + public const PERIOD_PREFIX = 'P'; + public const PERIOD_YEARS = 'Y'; + public const PERIOD_MONTHS = 'M'; + public const PERIOD_DAYS = 'D'; + public const PERIOD_TIME_PREFIX = 'T'; + public const PERIOD_HOURS = 'H'; + public const PERIOD_MINUTES = 'M'; + public const PERIOD_SECONDS = 'S'; + + /** + * A translator to ... er ... translate stuff + * + * @var \Symfony\Component\Translation\TranslatorInterface + */ + protected static $translator; + + /** + * @var array|null + */ + protected static $cascadeFactors; + + /** + * @var array + */ + protected static $formats = [ + 'y' => 'y', + 'Y' => 'y', + 'o' => 'y', + 'm' => 'm', + 'n' => 'm', + 'W' => 'weeks', + 'd' => 'd', + 'j' => 'd', + 'z' => 'd', + 'h' => 'h', + 'g' => 'h', + 'H' => 'h', + 'G' => 'h', + 'i' => 'i', + 's' => 's', + 'u' => 'micro', + 'v' => 'milli', + ]; + + /** + * @var array|null + */ + private static $flipCascadeFactors; + + /** + * @var bool + */ + private static $floatSettersEnabled = false; + + /** + * The registered macros. + * + * @var array + */ + protected static $macros = []; + + /** + * Timezone handler for settings() method. + * + * @var mixed + */ + protected $tzName; + + /** + * Set the instance's timezone from a string or object. + * + * @param \DateTimeZone|string $tzName + * + * @return static + */ + public function setTimezone($tzName) + { + $this->tzName = $tzName; + + return $this; + } + + /** + * @internal + * + * Set the instance's timezone from a string or object and add/subtract the offset difference. + * + * @param \DateTimeZone|string $tzName + * + * @return static + */ + public function shiftTimezone($tzName) + { + $this->tzName = $tzName; + + return $this; + } + + /** + * Mapping of units and factors for cascading. + * + * Should only be modified by changing the factors or referenced constants. + * + * @return array + */ + public static function getCascadeFactors() + { + return static::$cascadeFactors ?: static::getDefaultCascadeFactors(); + } + + protected static function getDefaultCascadeFactors(): array + { + return [ + 'milliseconds' => [Carbon::MICROSECONDS_PER_MILLISECOND, 'microseconds'], + 'seconds' => [Carbon::MILLISECONDS_PER_SECOND, 'milliseconds'], + 'minutes' => [Carbon::SECONDS_PER_MINUTE, 'seconds'], + 'hours' => [Carbon::MINUTES_PER_HOUR, 'minutes'], + 'dayz' => [Carbon::HOURS_PER_DAY, 'hours'], + 'weeks' => [Carbon::DAYS_PER_WEEK, 'dayz'], + 'months' => [Carbon::WEEKS_PER_MONTH, 'weeks'], + 'years' => [Carbon::MONTHS_PER_YEAR, 'months'], + ]; + } + + private static function standardizeUnit($unit) + { + $unit = rtrim($unit, 'sz').'s'; + + return $unit === 'days' ? 'dayz' : $unit; + } + + private static function getFlipCascadeFactors() + { + if (!self::$flipCascadeFactors) { + self::$flipCascadeFactors = []; + + foreach (static::getCascadeFactors() as $to => [$factor, $from]) { + self::$flipCascadeFactors[self::standardizeUnit($from)] = [self::standardizeUnit($to), $factor]; + } + } + + return self::$flipCascadeFactors; + } + + /** + * Set default cascading factors for ->cascade() method. + * + * @param array $cascadeFactors + */ + public static function setCascadeFactors(array $cascadeFactors) + { + self::$flipCascadeFactors = null; + static::$cascadeFactors = $cascadeFactors; + } + + /** + * This option allow you to opt-in for the Carbon 3 behavior where float + * values will no longer be cast to integer (so truncated). + * + * โš ๏ธ This settings will be applied globally, which mean your whole application + * code including the third-party dependencies that also may use Carbon will + * adopt the new behavior. + */ + public static function enableFloatSetters(bool $floatSettersEnabled = true): void + { + self::$floatSettersEnabled = $floatSettersEnabled; + } + + /////////////////////////////////////////////////////////////////// + //////////////////////////// CONSTRUCTORS ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Create a new CarbonInterval instance. + * + * @param Closure|DateInterval|string|int|null $years + * @param int|float|null $months + * @param int|float|null $weeks + * @param int|float|null $days + * @param int|float|null $hours + * @param int|float|null $minutes + * @param int|float|null $seconds + * @param int|float|null $microseconds + * + * @throws Exception when the interval_spec (passed as $years) cannot be parsed as an interval. + */ + public function __construct($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null, $microseconds = null) + { + if ($years instanceof Closure) { + $this->step = $years; + $years = null; + } + + if ($years instanceof DateInterval) { + parent::__construct(static::getDateIntervalSpec($years)); + $this->f = $years->f; + self::copyNegativeUnits($years, $this); + + return; + } + + $spec = $years; + $isStringSpec = (\is_string($spec) && !preg_match('/^[\d.]/', $spec)); + + if (!$isStringSpec || (float) $years) { + $spec = static::PERIOD_PREFIX; + + $spec .= $years > 0 ? $years.static::PERIOD_YEARS : ''; + $spec .= $months > 0 ? $months.static::PERIOD_MONTHS : ''; + + $specDays = 0; + $specDays += $weeks > 0 ? $weeks * static::getDaysPerWeek() : 0; + $specDays += $days > 0 ? $days : 0; + + $spec .= $specDays > 0 ? $specDays.static::PERIOD_DAYS : ''; + + if ($hours > 0 || $minutes > 0 || $seconds > 0) { + $spec .= static::PERIOD_TIME_PREFIX; + $spec .= $hours > 0 ? $hours.static::PERIOD_HOURS : ''; + $spec .= $minutes > 0 ? $minutes.static::PERIOD_MINUTES : ''; + $spec .= $seconds > 0 ? $seconds.static::PERIOD_SECONDS : ''; + } + + if ($spec === static::PERIOD_PREFIX) { + // Allow the zero interval. + $spec .= '0'.static::PERIOD_YEARS; + } + } + + try { + parent::__construct($spec); + } catch (Throwable $exception) { + try { + parent::__construct('PT0S'); + + if ($isStringSpec) { + if (!preg_match('/^P + (?:(?[+-]?\d*(?:\.\d+)?)Y)? + (?:(?[+-]?\d*(?:\.\d+)?)M)? + (?:(?[+-]?\d*(?:\.\d+)?)W)? + (?:(?[+-]?\d*(?:\.\d+)?)D)? + (?:T + (?:(?[+-]?\d*(?:\.\d+)?)H)? + (?:(?[+-]?\d*(?:\.\d+)?)M)? + (?:(?[+-]?\d*(?:\.\d+)?)S)? + )? + $/x', $spec, $match)) { + throw new InvalidArgumentException("Invalid duration: $spec"); + } + + $years = (float) ($match['year'] ?? 0); + $this->assertSafeForInteger('year', $years); + $months = (float) ($match['month'] ?? 0); + $this->assertSafeForInteger('month', $months); + $weeks = (float) ($match['week'] ?? 0); + $this->assertSafeForInteger('week', $weeks); + $days = (float) ($match['day'] ?? 0); + $this->assertSafeForInteger('day', $days); + $hours = (float) ($match['hour'] ?? 0); + $this->assertSafeForInteger('hour', $hours); + $minutes = (float) ($match['minute'] ?? 0); + $this->assertSafeForInteger('minute', $minutes); + $seconds = (float) ($match['second'] ?? 0); + $this->assertSafeForInteger('second', $seconds); + } + + $totalDays = (($weeks * static::getDaysPerWeek()) + $days); + $this->assertSafeForInteger('days total (including weeks)', $totalDays); + + $this->y = (int) $years; + $this->m = (int) $months; + $this->d = (int) $totalDays; + $this->h = (int) $hours; + $this->i = (int) $minutes; + $this->s = (int) $seconds; + + if ( + ((float) $this->y) !== $years || + ((float) $this->m) !== $months || + ((float) $this->d) !== $totalDays || + ((float) $this->h) !== $hours || + ((float) $this->i) !== $minutes || + ((float) $this->s) !== $seconds + ) { + $this->add(static::fromString( + ($years - $this->y).' years '. + ($months - $this->m).' months '. + ($totalDays - $this->d).' days '. + ($hours - $this->h).' hours '. + ($minutes - $this->i).' minutes '. + ($seconds - $this->s).' seconds ' + )); + } + } catch (Throwable $secondException) { + throw $secondException instanceof OutOfRangeException ? $secondException : $exception; + } + } + + if ($microseconds !== null) { + $this->f = $microseconds / Carbon::MICROSECONDS_PER_SECOND; + } + } + + /** + * Returns the factor for a given source-to-target couple. + * + * @param string $source + * @param string $target + * + * @return int|float|null + */ + public static function getFactor($source, $target) + { + $source = self::standardizeUnit($source); + $target = self::standardizeUnit($target); + $factors = self::getFlipCascadeFactors(); + + if (isset($factors[$source])) { + [$to, $factor] = $factors[$source]; + + if ($to === $target) { + return $factor; + } + + return $factor * static::getFactor($to, $target); + } + + return null; + } + + /** + * Returns the factor for a given source-to-target couple if set, + * else try to find the appropriate constant as the factor, such as Carbon::DAYS_PER_WEEK. + * + * @param string $source + * @param string $target + * + * @return int|float|null + */ + public static function getFactorWithDefault($source, $target) + { + $factor = self::getFactor($source, $target); + + if ($factor) { + return $factor; + } + + static $defaults = [ + 'month' => ['year' => Carbon::MONTHS_PER_YEAR], + 'week' => ['month' => Carbon::WEEKS_PER_MONTH], + 'day' => ['week' => Carbon::DAYS_PER_WEEK], + 'hour' => ['day' => Carbon::HOURS_PER_DAY], + 'minute' => ['hour' => Carbon::MINUTES_PER_HOUR], + 'second' => ['minute' => Carbon::SECONDS_PER_MINUTE], + 'millisecond' => ['second' => Carbon::MILLISECONDS_PER_SECOND], + 'microsecond' => ['millisecond' => Carbon::MICROSECONDS_PER_MILLISECOND], + ]; + + return $defaults[$source][$target] ?? null; + } + + /** + * Returns current config for days per week. + * + * @return int|float + */ + public static function getDaysPerWeek() + { + return static::getFactor('dayz', 'weeks') ?: Carbon::DAYS_PER_WEEK; + } + + /** + * Returns current config for hours per day. + * + * @return int|float + */ + public static function getHoursPerDay() + { + return static::getFactor('hours', 'dayz') ?: Carbon::HOURS_PER_DAY; + } + + /** + * Returns current config for minutes per hour. + * + * @return int|float + */ + public static function getMinutesPerHour() + { + return static::getFactor('minutes', 'hours') ?: Carbon::MINUTES_PER_HOUR; + } + + /** + * Returns current config for seconds per minute. + * + * @return int|float + */ + public static function getSecondsPerMinute() + { + return static::getFactor('seconds', 'minutes') ?: Carbon::SECONDS_PER_MINUTE; + } + + /** + * Returns current config for microseconds per second. + * + * @return int|float + */ + public static function getMillisecondsPerSecond() + { + return static::getFactor('milliseconds', 'seconds') ?: Carbon::MILLISECONDS_PER_SECOND; + } + + /** + * Returns current config for microseconds per second. + * + * @return int|float + */ + public static function getMicrosecondsPerMillisecond() + { + return static::getFactor('microseconds', 'milliseconds') ?: Carbon::MICROSECONDS_PER_MILLISECOND; + } + + /** + * Create a new CarbonInterval instance from specific values. + * This is an alias for the constructor that allows better fluent + * syntax as it allows you to do CarbonInterval::create(1)->fn() rather than + * (new CarbonInterval(1))->fn(). + * + * @param int $years + * @param int $months + * @param int $weeks + * @param int $days + * @param int $hours + * @param int $minutes + * @param int $seconds + * @param int $microseconds + * + * @throws Exception when the interval_spec (passed as $years) cannot be parsed as an interval. + * + * @return static + */ + public static function create($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null, $microseconds = null) + { + return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds, $microseconds); + } + + /** + * Parse a string into a new CarbonInterval object according to the specified format. + * + * @example + * ``` + * echo Carboninterval::createFromFormat('H:i', '1:30'); + * ``` + * + * @param string $format Format of the $interval input string + * @param string|null $interval Input string to convert into an interval + * + * @throws \Carbon\Exceptions\ParseErrorException when the $interval cannot be parsed as an interval. + * + * @return static + */ + public static function createFromFormat(string $format, ?string $interval) + { + $instance = new static(0); + $length = mb_strlen($format); + + if (preg_match('/s([,.])([uv])$/', $format, $match)) { + $interval = explode($match[1], $interval); + $index = \count($interval) - 1; + $interval[$index] = str_pad($interval[$index], $match[2] === 'v' ? 3 : 6, '0'); + $interval = implode($match[1], $interval); + } + + $interval = $interval ?? ''; + + for ($index = 0; $index < $length; $index++) { + $expected = mb_substr($format, $index, 1); + $nextCharacter = mb_substr($interval, 0, 1); + $unit = static::$formats[$expected] ?? null; + + if ($unit) { + if (!preg_match('/^-?\d+/', $interval, $match)) { + throw new ParseErrorException('number', $nextCharacter); + } + + $interval = mb_substr($interval, mb_strlen($match[0])); + $instance->$unit += (int) ($match[0]); + + continue; + } + + if ($nextCharacter !== $expected) { + throw new ParseErrorException( + "'$expected'", + $nextCharacter, + 'Allowed substitutes for interval formats are '.implode(', ', array_keys(static::$formats))."\n". + 'See https://php.net/manual/en/function.date.php for their meaning' + ); + } + + $interval = mb_substr($interval, 1); + } + + if ($interval !== '') { + throw new ParseErrorException( + 'end of string', + $interval + ); + } + + return $instance; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + $date = new static(0); + $date->copyProperties($this); + $date->step = $this->step; + + return $date; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function clone() + { + return $this->copy(); + } + + /** + * Provide static helpers to create instances. Allows CarbonInterval::years(3). + * + * Note: This is done using the magic method to allow static and instance methods to + * have the same names. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @return static|null + */ + public static function __callStatic($method, $parameters) + { + try { + $interval = new static(0); + $localStrictModeEnabled = $interval->localStrictModeEnabled; + $interval->localStrictModeEnabled = true; + + $result = static::hasMacro($method) + ? static::bindMacroContext(null, function () use (&$method, &$parameters, &$interval) { + return $interval->callMacro($method, $parameters); + }) + : $interval->$method(...$parameters); + + $interval->localStrictModeEnabled = $localStrictModeEnabled; + + return $result; + } catch (BadFluentSetterException $exception) { + if (Carbon::isStrictModeEnabled()) { + throw new BadFluentConstructorException($method, 0, $exception); + } + + return null; + } + } + + /** + * Evaluate the PHP generated by var_export() and recreate the exported CarbonInterval instance. + * + * @param array $dump data as exported by var_export() + * + * @return static + */ + #[ReturnTypeWillChange] + public static function __set_state($dump) + { + /** @noinspection PhpVoidFunctionResultUsedInspection */ + /** @var DateInterval $dateInterval */ + $dateInterval = parent::__set_state($dump); + + return static::instance($dateInterval); + } + + /** + * Return the current context from inside a macro callee or a new one if static. + * + * @return static + */ + protected static function this() + { + return end(static::$macroContextStack) ?: new static(0); + } + + /** + * Creates a CarbonInterval from string. + * + * Format: + * + * Suffix | Unit | Example | DateInterval expression + * -------|---------|---------|------------------------ + * y | years | 1y | P1Y + * mo | months | 3mo | P3M + * w | weeks | 2w | P2W + * d | days | 28d | P28D + * h | hours | 4h | PT4H + * m | minutes | 12m | PT12M + * s | seconds | 59s | PT59S + * + * e. g. `1w 3d 4h 32m 23s` is converted to 10 days 4 hours 32 minutes and 23 seconds. + * + * Special cases: + * - An empty string will return a zero interval + * - Fractions are allowed for weeks, days, hours and minutes and will be converted + * and rounded to the next smaller value (caution: 0.5w = 4d) + * + * @param string $intervalDefinition + * + * @return static + */ + public static function fromString($intervalDefinition) + { + if (empty($intervalDefinition)) { + return new static(0); + } + + $years = 0; + $months = 0; + $weeks = 0; + $days = 0; + $hours = 0; + $minutes = 0; + $seconds = 0; + $milliseconds = 0; + $microseconds = 0; + + $pattern = '/(\d+(?:\.\d+)?)\h*([^\d\h]*)/i'; + preg_match_all($pattern, $intervalDefinition, $parts, PREG_SET_ORDER); + + while ([$part, $value, $unit] = array_shift($parts)) { + $intValue = (int) $value; + $fraction = (float) $value - $intValue; + + // Fix calculation precision + switch (round($fraction, 6)) { + case 1: + $fraction = 0; + $intValue++; + + break; + case 0: + $fraction = 0; + + break; + } + + switch ($unit === 'ยตs' ? 'ยตs' : strtolower($unit)) { + case 'millennia': + case 'millennium': + $years += $intValue * CarbonInterface::YEARS_PER_MILLENNIUM; + + break; + + case 'century': + case 'centuries': + $years += $intValue * CarbonInterface::YEARS_PER_CENTURY; + + break; + + case 'decade': + case 'decades': + $years += $intValue * CarbonInterface::YEARS_PER_DECADE; + + break; + + case 'year': + case 'years': + case 'y': + case 'yr': + case 'yrs': + $years += $intValue; + + break; + + case 'quarter': + case 'quarters': + $months += $intValue * CarbonInterface::MONTHS_PER_QUARTER; + + break; + + case 'month': + case 'months': + case 'mo': + case 'mos': + $months += $intValue; + + break; + + case 'week': + case 'weeks': + case 'w': + $weeks += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getDaysPerWeek(), 'd']; + } + + break; + + case 'day': + case 'days': + case 'd': + $days += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getHoursPerDay(), 'h']; + } + + break; + + case 'hour': + case 'hours': + case 'h': + $hours += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getMinutesPerHour(), 'm']; + } + + break; + + case 'minute': + case 'minutes': + case 'm': + $minutes += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getSecondsPerMinute(), 's']; + } + + break; + + case 'second': + case 'seconds': + case 's': + $seconds += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getMillisecondsPerSecond(), 'ms']; + } + + break; + + case 'millisecond': + case 'milliseconds': + case 'milli': + case 'ms': + $milliseconds += $intValue; + + if ($fraction) { + $microseconds += round($fraction * static::getMicrosecondsPerMillisecond()); + } + + break; + + case 'microsecond': + case 'microseconds': + case 'micro': + case 'ยตs': + $microseconds += $intValue; + + break; + + default: + throw new InvalidIntervalException( + sprintf('Invalid part %s in definition %s', $part, $intervalDefinition) + ); + } + } + + return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds, $milliseconds * Carbon::MICROSECONDS_PER_MILLISECOND + $microseconds); + } + + /** + * Creates a CarbonInterval from string using a different locale. + * + * @param string $interval interval string in the given language (may also contain English). + * @param string|null $locale if locale is null or not specified, current global locale will be used instead. + * + * @return static + */ + public static function parseFromLocale($interval, $locale = null) + { + return static::fromString(Carbon::translateTimeString($interval, $locale ?: static::getLocale(), 'en')); + } + + private static function castIntervalToClass(DateInterval $interval, string $className, array $skip = []) + { + $mainClass = DateInterval::class; + + if (!is_a($className, $mainClass, true)) { + throw new InvalidCastException("$className is not a sub-class of $mainClass."); + } + + $microseconds = $interval->f; + $instance = new $className(static::getDateIntervalSpec($interval, false, $skip)); + + if ($microseconds) { + $instance->f = $microseconds; + } + + if ($interval instanceof self && is_a($className, self::class, true)) { + self::copyStep($interval, $instance); + } + + self::copyNegativeUnits($interval, $instance); + + return $instance; + } + + private static function copyNegativeUnits(DateInterval $from, DateInterval $to): void + { + $to->invert = $from->invert; + + foreach (['y', 'm', 'd', 'h', 'i', 's'] as $unit) { + if ($from->$unit < 0) { + $to->$unit *= -1; + } + } + } + + private static function copyStep(self $from, self $to): void + { + $to->setStep($from->getStep()); + } + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateInterval + */ + public function cast(string $className) + { + return self::castIntervalToClass($this, $className); + } + + /** + * Create a CarbonInterval instance from a DateInterval one. Can not instance + * DateInterval objects created from DateTime::diff() as you can't externally + * set the $days field. + * + * @param DateInterval $interval + * @param bool $skipCopy set to true to return the passed object + * (without copying it) if it's already of the + * current class + * + * @return static + */ + public static function instance(DateInterval $interval, array $skip = [], bool $skipCopy = false) + { + if ($skipCopy && $interval instanceof static) { + return $interval; + } + + return self::castIntervalToClass($interval, static::class, $skip); + } + + /** + * Make a CarbonInterval instance from given variable if possible. + * + * Always return a new instance. Parse only strings and only these likely to be intervals (skip dates + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * + * @param mixed|int|DateInterval|string|Closure|null $interval interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * @param bool $skipCopy set to true to return the passed object + * (without copying it) if it's already of the + * current class + * + * @return static|null + */ + public static function make($interval, $unit = null, bool $skipCopy = false) + { + if ($unit) { + $interval = "$interval ".Carbon::pluralUnit($unit); + } + + if ($interval instanceof DateInterval) { + return static::instance($interval, [], $skipCopy); + } + + if ($interval instanceof Closure) { + return new static($interval); + } + + if (!\is_string($interval)) { + return null; + } + + return static::makeFromString($interval); + } + + protected static function makeFromString(string $interval) + { + $interval = preg_replace('/\s+/', ' ', trim($interval)); + + if (preg_match('/^P[T\d]/', $interval)) { + return new static($interval); + } + + if (preg_match('/^(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+$/i', $interval)) { + return static::fromString($interval); + } + + // @codeCoverageIgnoreStart + try { + /** @var static $interval */ + $interval = static::createFromDateString($interval); + } catch (DateMalformedIntervalStringException $e) { + return null; + } + // @codeCoverageIgnoreEnd + + return !$interval || $interval->isEmpty() ? null : $interval; + } + + protected function resolveInterval($interval) + { + if (!($interval instanceof self)) { + return self::make($interval); + } + + return $interval; + } + + /** + * Sets up a DateInterval from the relative parts of the string. + * + * @param string $time + * + * @return static + * + * @link https://php.net/manual/en/dateinterval.createfromdatestring.php + */ + #[ReturnTypeWillChange] + public static function createFromDateString($time) + { + $interval = @parent::createFromDateString(strtr($time, [ + ',' => ' ', + ' and ' => ' ', + ])); + + if ($interval instanceof DateInterval) { + $interval = static::instance($interval); + } + + return $interval; + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// GETTERS AND SETTERS ///////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get a part of the CarbonInterval object. + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return int|float|string + */ + public function get($name) + { + if (str_starts_with($name, 'total')) { + return $this->total(substr($name, 5)); + } + + switch ($name) { + case 'years': + return $this->y; + + case 'months': + return $this->m; + + case 'dayz': + return $this->d; + + case 'hours': + return $this->h; + + case 'minutes': + return $this->i; + + case 'seconds': + return $this->s; + + case 'milli': + case 'milliseconds': + return (int) (round($this->f * Carbon::MICROSECONDS_PER_SECOND) / Carbon::MICROSECONDS_PER_MILLISECOND); + + case 'micro': + case 'microseconds': + return (int) round($this->f * Carbon::MICROSECONDS_PER_SECOND); + + case 'microExcludeMilli': + return (int) round($this->f * Carbon::MICROSECONDS_PER_SECOND) % Carbon::MICROSECONDS_PER_MILLISECOND; + + case 'weeks': + return (int) ($this->d / (int) static::getDaysPerWeek()); + + case 'daysExcludeWeeks': + case 'dayzExcludeWeeks': + return $this->d % (int) static::getDaysPerWeek(); + + case 'locale': + return $this->getTranslatorLocale(); + + default: + throw new UnknownGetterException($name); + } + } + + /** + * Get a part of the CarbonInterval object. + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return int|float|string + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * Set a part of the CarbonInterval object. + * + * @param string|array $name + * @param int $value + * + * @throws UnknownSetterException + * + * @return $this + */ + public function set($name, $value = null) + { + $properties = \is_array($name) ? $name : [$name => $value]; + + foreach ($properties as $key => $value) { + switch (Carbon::singularUnit(rtrim($key, 'z'))) { + case 'year': + $this->checkIntegerValue($key, $value); + $this->y = $value; + $this->handleDecimalPart('year', $value, $this->y); + + break; + + case 'month': + $this->checkIntegerValue($key, $value); + $this->m = $value; + $this->handleDecimalPart('month', $value, $this->m); + + break; + + case 'week': + $this->checkIntegerValue($key, $value); + $days = $value * (int) static::getDaysPerWeek(); + $this->assertSafeForInteger('days total (including weeks)', $days); + $this->d = $days; + $this->handleDecimalPart('day', $days, $this->d); + + break; + + case 'day': + $this->checkIntegerValue($key, $value); + $this->d = $value; + $this->handleDecimalPart('day', $value, $this->d); + + break; + + case 'daysexcludeweek': + case 'dayzexcludeweek': + $this->checkIntegerValue($key, $value); + $days = $this->weeks * (int) static::getDaysPerWeek() + $value; + $this->assertSafeForInteger('days total (including weeks)', $days); + $this->d = $days; + $this->handleDecimalPart('day', $days, $this->d); + + break; + + case 'hour': + $this->checkIntegerValue($key, $value); + $this->h = $value; + $this->handleDecimalPart('hour', $value, $this->h); + + break; + + case 'minute': + $this->checkIntegerValue($key, $value); + $this->i = $value; + $this->handleDecimalPart('minute', $value, $this->i); + + break; + + case 'second': + $this->checkIntegerValue($key, $value); + $this->s = $value; + $this->handleDecimalPart('second', $value, $this->s); + + break; + + case 'milli': + case 'millisecond': + $this->microseconds = $value * Carbon::MICROSECONDS_PER_MILLISECOND + $this->microseconds % Carbon::MICROSECONDS_PER_MILLISECOND; + + break; + + case 'micro': + case 'microsecond': + $this->f = $value / Carbon::MICROSECONDS_PER_SECOND; + + break; + + default: + if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) { + throw new UnknownSetterException($key); + } + + $this->$key = $value; + } + } + + return $this; + } + + /** + * Set a part of the CarbonInterval object. + * + * @param string $name + * @param int $value + * + * @throws UnknownSetterException + */ + public function __set($name, $value) + { + $this->set($name, $value); + } + + /** + * Allow setting of weeks and days to be cumulative. + * + * @param int $weeks Number of weeks to set + * @param int $days Number of days to set + * + * @return static + */ + public function weeksAndDays($weeks, $days) + { + $this->dayz = ($weeks * static::getDaysPerWeek()) + $days; + + return $this; + } + + /** + * Returns true if the interval is empty for each unit. + * + * @return bool + */ + public function isEmpty() + { + return $this->years === 0 && + $this->months === 0 && + $this->dayz === 0 && + !$this->days && + $this->hours === 0 && + $this->minutes === 0 && + $this->seconds === 0 && + $this->microseconds === 0; + } + + /** + * Register a custom macro. + * + * @example + * ``` + * CarbonInterval::macro('twice', function () { + * return $this->times(2); + * }); + * echo CarbonInterval::hours(2)->twice(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$macros[$name] = $macro; + } + + /** + * Register macros from a mixin object. + * + * @example + * ``` + * CarbonInterval::mixin(new class { + * public function daysToHours() { + * return function () { + * $this->hours += $this->days; + * $this->days = 0; + * + * return $this; + * }; + * } + * public function hoursToDays() { + * return function () { + * $this->days += $this->hours; + * $this->hours = 0; + * + * return $this; + * }; + * } + * }); + * echo CarbonInterval::hours(5)->hoursToDays() . "\n"; + * echo CarbonInterval::days(5)->daysToHours() . "\n"; + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + static::baseMixin($mixin); + } + + /** + * Check if macro is registered. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$macros[$name]); + } + + /** + * Call given macro. + * + * @param string $name + * @param array $parameters + * + * @return mixed + */ + protected function callMacro($name, $parameters) + { + $macro = static::$macros[$name]; + + if ($macro instanceof Closure) { + $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + } + + /** + * Allow fluent calls on the setters... CarbonInterval::years(3)->months(5)->day(). + * + * Note: This is done using the magic method to allow static and instance methods to + * have the same names. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws BadFluentSetterException|Throwable + * + * @return static + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return static::bindMacroContext($this, function () use (&$method, &$parameters) { + return $this->callMacro($method, $parameters); + }); + } + + $roundedValue = $this->callRoundMethod($method, $parameters); + + if ($roundedValue !== null) { + return $roundedValue; + } + + if (preg_match('/^(?add|sub)(?[A-Z].*)$/', $method, $match)) { + $value = $this->getMagicParameter($parameters, 0, Carbon::pluralUnit($match['unit']), 0); + + return $this->{$match['method']}($value, $match['unit']); + } + + $value = $this->getMagicParameter($parameters, 0, Carbon::pluralUnit($method), 1); + + try { + $this->set($method, $value); + } catch (UnknownSetterException $exception) { + if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) { + throw new BadFluentSetterException($method, 0, $exception); + } + } + + return $this; + } + + protected function getForHumansInitialVariables($syntax, $short) + { + if (\is_array($syntax)) { + return $syntax; + } + + if (\is_int($short)) { + return [ + 'parts' => $short, + 'short' => false, + ]; + } + + if (\is_bool($syntax)) { + return [ + 'short' => $syntax, + 'syntax' => CarbonInterface::DIFF_ABSOLUTE, + ]; + } + + return []; + } + + /** + * @param mixed $syntax + * @param mixed $short + * @param mixed $parts + * @param mixed $options + * + * @return array + */ + protected function getForHumansParameters($syntax = null, $short = false, $parts = -1, $options = null) + { + $optionalSpace = ' '; + $default = $this->getTranslationMessage('list.0') ?? $this->getTranslationMessage('list') ?? ' '; + $join = $default === '' ? '' : ' '; + $altNumbers = false; + $aUnit = false; + $minimumUnit = 's'; + $skip = []; + extract($this->getForHumansInitialVariables($syntax, $short)); + $skip = array_map('strtolower', array_filter((array) $skip, static function ($value) { + return \is_string($value) && $value !== ''; + })); + + if ($syntax === null) { + $syntax = CarbonInterface::DIFF_ABSOLUTE; + } + + if ($parts === -1) { + $parts = INF; + } + + if ($options === null) { + $options = static::getHumanDiffOptions(); + } + + if ($join === false) { + $join = ' '; + } elseif ($join === true) { + $join = [ + $default, + $this->getTranslationMessage('list.1') ?? $default, + ]; + } + + if ($altNumbers && $altNumbers !== true) { + $language = new Language($this->locale); + $altNumbers = \in_array($language->getCode(), (array) $altNumbers, true); + } + + if (\is_array($join)) { + [$default, $last] = $join; + + if ($default !== ' ') { + $optionalSpace = ''; + } + + $join = function ($list) use ($default, $last) { + if (\count($list) < 2) { + return implode('', $list); + } + + $end = array_pop($list); + + return implode($default, $list).$last.$end; + }; + } + + if (\is_string($join)) { + if ($join !== ' ') { + $optionalSpace = ''; + } + + $glue = $join; + $join = function ($list) use ($glue) { + return implode($glue, $list); + }; + } + + $interpolations = [ + ':optional-space' => $optionalSpace, + ]; + + return [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit, $skip]; + } + + protected static function getRoundingMethodFromOptions(int $options): ?string + { + if ($options & CarbonInterface::ROUND) { + return 'round'; + } + + if ($options & CarbonInterface::CEIL) { + return 'ceil'; + } + + if ($options & CarbonInterface::FLOOR) { + return 'floor'; + } + + return null; + } + + /** + * Returns interval values as an array where key are the unit names and values the counts. + * + * @return int[] + */ + public function toArray() + { + return [ + 'years' => $this->years, + 'months' => $this->months, + 'weeks' => $this->weeks, + 'days' => $this->daysExcludeWeeks, + 'hours' => $this->hours, + 'minutes' => $this->minutes, + 'seconds' => $this->seconds, + 'microseconds' => $this->microseconds, + ]; + } + + /** + * Returns interval non-zero values as an array where key are the unit names and values the counts. + * + * @return int[] + */ + public function getNonZeroValues() + { + return array_filter($this->toArray(), 'intval'); + } + + /** + * Returns interval values as an array where key are the unit names and values the counts + * from the biggest non-zero one the the smallest non-zero one. + * + * @return int[] + */ + public function getValuesSequence() + { + $nonZeroValues = $this->getNonZeroValues(); + + if ($nonZeroValues === []) { + return []; + } + + $keys = array_keys($nonZeroValues); + $firstKey = $keys[0]; + $lastKey = $keys[\count($keys) - 1]; + $values = []; + $record = false; + + foreach ($this->toArray() as $unit => $count) { + if ($unit === $firstKey) { + $record = true; + } + + if ($record) { + $values[$unit] = $count; + } + + if ($unit === $lastKey) { + $record = false; + } + } + + return $values; + } + + /** + * Get the current interval in a human readable format in the current locale. + * + * @example + * ``` + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans() . "\n"; + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['parts' => 2]) . "\n"; + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['parts' => 3, 'join' => true]) . "\n"; + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['short' => true]) . "\n"; + * echo CarbonInterval::fromString('1d 24h')->forHumans(['join' => ' or ']) . "\n"; + * echo CarbonInterval::fromString('1d 24h')->forHumans(['minimumUnit' => 'hour']) . "\n"; + * ``` + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'skip' entry, list of units to skip (array of strings or a single string, + * ` it can be the unit name (singular or plural) or its shortcut + * ` (y, m, w, d, h, min, s, ms, ยตs). + * - 'aUnit' entry, prefer "an hour" over "1 hour" if true + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ` short form of the units, e.g. 'hour' or 'h' (default value: s) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: -1: no limits) + * @param int $options human diff options + * + * @throws Exception + * + * @return string + */ + public function forHumans($syntax = null, $short = false, $parts = -1, $options = null) + { + [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit, $skip] = $this + ->getForHumansParameters($syntax, $short, $parts, $options); + + $interval = []; + + $syntax = (int) ($syntax ?? CarbonInterface::DIFF_ABSOLUTE); + $absolute = $syntax === CarbonInterface::DIFF_ABSOLUTE; + $relativeToNow = $syntax === CarbonInterface::DIFF_RELATIVE_TO_NOW; + $count = 1; + $unit = $short ? 's' : 'second'; + $isFuture = $this->invert === 1; + $transId = $relativeToNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before'); + $declensionMode = null; + + /** @var \Symfony\Component\Translation\Translator $translator */ + $translator = $this->getLocalTranslator(); + + $handleDeclensions = function ($unit, $count, $index = 0, $parts = 1) use ($interpolations, $transId, $translator, $altNumbers, $absolute, &$declensionMode) { + if (!$absolute) { + $declensionMode = $declensionMode ?? $this->translate($transId.'_mode'); + + if ($this->needsDeclension($declensionMode, $index, $parts)) { + // Some languages have special pluralization for past and future tense. + $key = $unit.'_'.$transId; + $result = $this->translate($key, $interpolations, $count, $translator, $altNumbers); + + if ($result !== $key) { + return $result; + } + } + } + + $result = $this->translate($unit, $interpolations, $count, $translator, $altNumbers); + + if ($result !== $unit) { + return $result; + } + + return null; + }; + + $intervalValues = $this; + $method = static::getRoundingMethodFromOptions($options); + + if ($method) { + $previousCount = INF; + + while ( + \count($intervalValues->getNonZeroValues()) > $parts && + ($count = \count($keys = array_keys($intervalValues->getValuesSequence()))) > 1 + ) { + $index = min($count, $previousCount - 1) - 2; + + if ($index < 0) { + break; + } + + $intervalValues = $this->copy()->roundUnit( + $keys[$index], + 1, + $method + ); + $previousCount = $count; + } + } + + $diffIntervalArray = [ + ['value' => $intervalValues->years, 'unit' => 'year', 'unitShort' => 'y'], + ['value' => $intervalValues->months, 'unit' => 'month', 'unitShort' => 'm'], + ['value' => $intervalValues->weeks, 'unit' => 'week', 'unitShort' => 'w'], + ['value' => $intervalValues->daysExcludeWeeks, 'unit' => 'day', 'unitShort' => 'd'], + ['value' => $intervalValues->hours, 'unit' => 'hour', 'unitShort' => 'h'], + ['value' => $intervalValues->minutes, 'unit' => 'minute', 'unitShort' => 'min'], + ['value' => $intervalValues->seconds, 'unit' => 'second', 'unitShort' => 's'], + ['value' => $intervalValues->milliseconds, 'unit' => 'millisecond', 'unitShort' => 'ms'], + ['value' => $intervalValues->microExcludeMilli, 'unit' => 'microsecond', 'unitShort' => 'ยตs'], + ]; + + if (!empty($skip)) { + foreach ($diffIntervalArray as $index => &$unitData) { + $nextIndex = $index + 1; + + if ($unitData['value'] && + isset($diffIntervalArray[$nextIndex]) && + \count(array_intersect([$unitData['unit'], $unitData['unit'].'s', $unitData['unitShort']], $skip)) + ) { + $diffIntervalArray[$nextIndex]['value'] += $unitData['value'] * + self::getFactorWithDefault($diffIntervalArray[$nextIndex]['unit'], $unitData['unit']); + $unitData['value'] = 0; + } + } + } + + $transChoice = function ($short, $unitData, $index, $parts) use ($absolute, $handleDeclensions, $translator, $aUnit, $altNumbers, $interpolations) { + $count = $unitData['value']; + + if ($short) { + $result = $handleDeclensions($unitData['unitShort'], $count, $index, $parts); + + if ($result !== null) { + return $result; + } + } elseif ($aUnit) { + $result = $handleDeclensions('a_'.$unitData['unit'], $count, $index, $parts); + + if ($result !== null) { + return $result; + } + } + + if (!$absolute) { + return $handleDeclensions($unitData['unit'], $count, $index, $parts); + } + + return $this->translate($unitData['unit'], $interpolations, $count, $translator, $altNumbers); + }; + + $fallbackUnit = ['second', 's']; + + foreach ($diffIntervalArray as $diffIntervalData) { + if ($diffIntervalData['value'] > 0) { + $unit = $short ? $diffIntervalData['unitShort'] : $diffIntervalData['unit']; + $count = $diffIntervalData['value']; + $interval[] = [$short, $diffIntervalData]; + } elseif ($options & CarbonInterface::SEQUENTIAL_PARTS_ONLY && \count($interval) > 0) { + break; + } + + // break the loop after we get the required number of parts in array + if (\count($interval) >= $parts) { + break; + } + + // break the loop after we have reached the minimum unit + if (\in_array($minimumUnit, [$diffIntervalData['unit'], $diffIntervalData['unitShort']], true)) { + $fallbackUnit = [$diffIntervalData['unit'], $diffIntervalData['unitShort']]; + + break; + } + } + + $actualParts = \count($interval); + + foreach ($interval as $index => &$item) { + $item = $transChoice($item[0], $item[1], $index, $actualParts); + } + + if (\count($interval) === 0) { + if ($relativeToNow && $options & CarbonInterface::JUST_NOW) { + $key = 'diff_now'; + $translation = $this->translate($key, $interpolations, null, $translator); + + if ($translation !== $key) { + return $translation; + } + } + + $count = $options & CarbonInterface::NO_ZERO_DIFF ? 1 : 0; + $unit = $fallbackUnit[$short ? 1 : 0]; + $interval[] = $this->translate($unit, $interpolations, $count, $translator, $altNumbers); + } + + // join the interval parts by a space + $time = $join($interval); + + unset($diffIntervalArray, $interval); + + if ($absolute) { + return $time; + } + + $isFuture = $this->invert === 1; + + $transId = $relativeToNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before'); + + if ($parts === 1) { + if ($relativeToNow && $unit === 'day') { + if ($count === 1 && $options & CarbonInterface::ONE_DAY_WORDS) { + $key = $isFuture ? 'diff_tomorrow' : 'diff_yesterday'; + $translation = $this->translate($key, $interpolations, null, $translator); + + if ($translation !== $key) { + return $translation; + } + } + + if ($count === 2 && $options & CarbonInterface::TWO_DAY_WORDS) { + $key = $isFuture ? 'diff_after_tomorrow' : 'diff_before_yesterday'; + $translation = $this->translate($key, $interpolations, null, $translator); + + if ($translation !== $key) { + return $translation; + } + } + } + + $aTime = $aUnit ? $handleDeclensions('a_'.$unit, $count) : null; + + $time = $aTime ?: $handleDeclensions($unit, $count) ?: $time; + } + + $time = [':time' => $time]; + + return $this->translate($transId, array_merge($time, $interpolations, $time), null, $translator); + } + + /** + * Format the instance as a string using the forHumans() function. + * + * @throws Exception + * + * @return string + */ + public function __toString() + { + $format = $this->localToStringFormat ?? static::$toStringFormat; + + if (!$format) { + return $this->forHumans(); + } + + if ($format instanceof Closure) { + return $format($this); + } + + return $this->format($format); + } + + /** + * Return native DateInterval PHP object matching the current instance. + * + * @example + * ``` + * var_dump(CarbonInterval::hours(2)->toDateInterval()); + * ``` + * + * @return DateInterval + */ + public function toDateInterval() + { + return self::castIntervalToClass($this, DateInterval::class); + } + + /** + * Convert the interval to a CarbonPeriod. + * + * @param DateTimeInterface|string|int ...$params Start date, [end date or recurrences] and optional settings. + * + * @return CarbonPeriod + */ + public function toPeriod(...$params) + { + if ($this->tzName) { + $tz = \is_string($this->tzName) ? new DateTimeZone($this->tzName) : $this->tzName; + + if ($tz instanceof DateTimeZone) { + array_unshift($params, $tz); + } + } + + return CarbonPeriod::create($this, ...$params); + } + + /** + * Invert the interval. + * + * @param bool|int $inverted if a parameter is passed, the passed value cast as 1 or 0 is used + * as the new value of the ->invert property. + * + * @return $this + */ + public function invert($inverted = null) + { + $this->invert = (\func_num_args() === 0 ? !$this->invert : $inverted) ? 1 : 0; + + return $this; + } + + protected function solveNegativeInterval() + { + if (!$this->isEmpty() && $this->years <= 0 && $this->months <= 0 && $this->dayz <= 0 && $this->hours <= 0 && $this->minutes <= 0 && $this->seconds <= 0 && $this->microseconds <= 0) { + $this->years *= -1; + $this->months *= -1; + $this->dayz *= -1; + $this->hours *= -1; + $this->minutes *= -1; + $this->seconds *= -1; + $this->microseconds *= -1; + $this->invert(); + } + + return $this; + } + + /** + * Add the passed interval to the current instance. + * + * @param string|DateInterval $unit + * @param int|float $value + * + * @return $this + */ + public function add($unit, $value = 1) + { + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + if (\is_string($unit) && !preg_match('/^\s*\d/', $unit)) { + $unit = "$value $unit"; + $value = 1; + } + + $interval = static::make($unit); + + if (!$interval) { + throw new InvalidIntervalException('This type of data cannot be added/subtracted.'); + } + + if ($value !== 1) { + $interval->times($value); + } + + $sign = ($this->invert === 1) !== ($interval->invert === 1) ? -1 : 1; + $this->years += $interval->y * $sign; + $this->months += $interval->m * $sign; + $this->dayz += ($interval->days === false ? $interval->d : $interval->days) * $sign; + $this->hours += $interval->h * $sign; + $this->minutes += $interval->i * $sign; + $this->seconds += $interval->s * $sign; + $this->microseconds += $interval->microseconds * $sign; + + $this->solveNegativeInterval(); + + return $this; + } + + /** + * Subtract the passed interval to the current instance. + * + * @param string|DateInterval $unit + * @param int|float $value + * + * @return $this + */ + public function sub($unit, $value = 1) + { + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + return $this->add($unit, -(float) $value); + } + + /** + * Subtract the passed interval to the current instance. + * + * @param string|DateInterval $unit + * @param int|float $value + * + * @return $this + */ + public function subtract($unit, $value = 1) + { + return $this->sub($unit, $value); + } + + /** + * Add given parameters to the current interval. + * + * @param int $years + * @param int $months + * @param int|float $weeks + * @param int|float $days + * @param int|float $hours + * @param int|float $minutes + * @param int|float $seconds + * @param int|float $microseconds + * + * @return $this + */ + public function plus( + $years = 0, + $months = 0, + $weeks = 0, + $days = 0, + $hours = 0, + $minutes = 0, + $seconds = 0, + $microseconds = 0 + ): self { + return $this->add(" + $years years $months months $weeks weeks $days days + $hours hours $minutes minutes $seconds seconds $microseconds microseconds + "); + } + + /** + * Add given parameters to the current interval. + * + * @param int $years + * @param int $months + * @param int|float $weeks + * @param int|float $days + * @param int|float $hours + * @param int|float $minutes + * @param int|float $seconds + * @param int|float $microseconds + * + * @return $this + */ + public function minus( + $years = 0, + $months = 0, + $weeks = 0, + $days = 0, + $hours = 0, + $minutes = 0, + $seconds = 0, + $microseconds = 0 + ): self { + return $this->sub(" + $years years $months months $weeks weeks $days days + $hours hours $minutes minutes $seconds seconds $microseconds microseconds + "); + } + + /** + * Multiply current instance given number of times. times() is naive, it multiplies each unit + * (so day can be greater than 31, hour can be greater than 23, etc.) and the result is rounded + * separately for each unit. + * + * Use times() when you want a fast and approximated calculation that does not cascade units. + * + * For a precise and cascaded calculation, + * + * @see multiply() + * + * @param float|int $factor + * + * @return $this + */ + public function times($factor) + { + if ($factor < 0) { + $this->invert = $this->invert ? 0 : 1; + $factor = -$factor; + } + + $this->years = (int) round($this->years * $factor); + $this->months = (int) round($this->months * $factor); + $this->dayz = (int) round($this->dayz * $factor); + $this->hours = (int) round($this->hours * $factor); + $this->minutes = (int) round($this->minutes * $factor); + $this->seconds = (int) round($this->seconds * $factor); + $this->microseconds = (int) round($this->microseconds * $factor); + + return $this; + } + + /** + * Divide current instance by a given divider. shares() is naive, it divides each unit separately + * and the result is rounded for each unit. So 5 hours and 20 minutes shared by 3 becomes 2 hours + * and 7 minutes. + * + * Use shares() when you want a fast and approximated calculation that does not cascade units. + * + * For a precise and cascaded calculation, + * + * @see divide() + * + * @param float|int $divider + * + * @return $this + */ + public function shares($divider) + { + return $this->times(1 / $divider); + } + + protected function copyProperties(self $interval, $ignoreSign = false) + { + $this->years = $interval->years; + $this->months = $interval->months; + $this->dayz = $interval->dayz; + $this->hours = $interval->hours; + $this->minutes = $interval->minutes; + $this->seconds = $interval->seconds; + $this->microseconds = $interval->microseconds; + + if (!$ignoreSign) { + $this->invert = $interval->invert; + } + + return $this; + } + + /** + * Multiply and cascade current instance by a given factor. + * + * @param float|int $factor + * + * @return $this + */ + public function multiply($factor) + { + if ($factor < 0) { + $this->invert = $this->invert ? 0 : 1; + $factor = -$factor; + } + + $yearPart = (int) floor($this->years * $factor); // Split calculation to prevent imprecision + + if ($yearPart) { + $this->years -= $yearPart / $factor; + } + + return $this->copyProperties( + static::create($yearPart) + ->microseconds(abs($this->totalMicroseconds) * $factor) + ->cascade(), + true + ); + } + + /** + * Divide and cascade current instance by a given divider. + * + * @param float|int $divider + * + * @return $this + */ + public function divide($divider) + { + return $this->multiply(1 / $divider); + } + + /** + * Get the interval_spec string of a date interval. + * + * @param DateInterval $interval + * + * @return string + */ + public static function getDateIntervalSpec(DateInterval $interval, bool $microseconds = false, array $skip = []) + { + $date = array_filter([ + static::PERIOD_YEARS => abs($interval->y), + static::PERIOD_MONTHS => abs($interval->m), + static::PERIOD_DAYS => abs($interval->d), + ]); + + if ( + $interval->days >= CarbonInterface::DAYS_PER_WEEK * CarbonInterface::WEEKS_PER_MONTH && + (!isset($date[static::PERIOD_YEARS]) || \count(array_intersect(['y', 'year', 'years'], $skip))) && + (!isset($date[static::PERIOD_MONTHS]) || \count(array_intersect(['m', 'month', 'months'], $skip))) + ) { + $date = [ + static::PERIOD_DAYS => abs($interval->days), + ]; + } + + $seconds = abs($interval->s); + if ($microseconds && $interval->f > 0) { + $seconds = sprintf('%d.%06d', $seconds, abs($interval->f) * 1000000); + } + + $time = array_filter([ + static::PERIOD_HOURS => abs($interval->h), + static::PERIOD_MINUTES => abs($interval->i), + static::PERIOD_SECONDS => $seconds, + ]); + + $specString = static::PERIOD_PREFIX; + + foreach ($date as $key => $value) { + $specString .= $value.$key; + } + + if (\count($time) > 0) { + $specString .= static::PERIOD_TIME_PREFIX; + foreach ($time as $key => $value) { + $specString .= $value.$key; + } + } + + return $specString === static::PERIOD_PREFIX ? 'PT0S' : $specString; + } + + /** + * Get the interval_spec string. + * + * @return string + */ + public function spec(bool $microseconds = false) + { + return static::getDateIntervalSpec($this, $microseconds); + } + + /** + * Comparing 2 date intervals. + * + * @param DateInterval $first + * @param DateInterval $second + * + * @return int + */ + public static function compareDateIntervals(DateInterval $first, DateInterval $second) + { + $current = Carbon::now(); + $passed = $current->avoidMutation()->add($second); + $current->add($first); + + if ($current < $passed) { + return -1; + } + if ($current > $passed) { + return 1; + } + + return 0; + } + + /** + * Comparing with passed interval. + * + * @param DateInterval $interval + * + * @return int + */ + public function compare(DateInterval $interval) + { + return static::compareDateIntervals($this, $interval); + } + + private function invertCascade(array $values) + { + return $this->set(array_map(function ($value) { + return -$value; + }, $values))->doCascade(true)->invert(); + } + + private function doCascade(bool $deep) + { + $originalData = $this->toArray(); + $originalData['milliseconds'] = (int) ($originalData['microseconds'] / static::getMicrosecondsPerMillisecond()); + $originalData['microseconds'] = $originalData['microseconds'] % static::getMicrosecondsPerMillisecond(); + $originalData['weeks'] = (int) ($this->d / static::getDaysPerWeek()); + $originalData['daysExcludeWeeks'] = fmod($this->d, static::getDaysPerWeek()); + unset($originalData['days']); + $newData = $originalData; + $previous = []; + + foreach (self::getFlipCascadeFactors() as $source => [$target, $factor]) { + foreach (['source', 'target'] as $key) { + if ($$key === 'dayz') { + $$key = 'daysExcludeWeeks'; + } + } + + $value = $newData[$source]; + $modulo = fmod($factor + fmod($value, $factor), $factor); + $newData[$source] = $modulo; + $newData[$target] += ($value - $modulo) / $factor; + + $decimalPart = fmod($newData[$source], 1); + + if ($decimalPart !== 0.0) { + $unit = $source; + + foreach ($previous as [$subUnit, $subFactor]) { + $newData[$unit] -= $decimalPart; + $newData[$subUnit] += $decimalPart * $subFactor; + $decimalPart = fmod($newData[$subUnit], 1); + + if ($decimalPart === 0.0) { + break; + } + + $unit = $subUnit; + } + } + + array_unshift($previous, [$source, $factor]); + } + + $positive = null; + + if (!$deep) { + foreach ($newData as $value) { + if ($value) { + if ($positive === null) { + $positive = ($value > 0); + + continue; + } + + if (($value > 0) !== $positive) { + return $this->invertCascade($originalData) + ->solveNegativeInterval(); + } + } + } + } + + return $this->set($newData) + ->solveNegativeInterval(); + } + + /** + * Convert overflowed values into bigger units. + * + * @return $this + */ + public function cascade() + { + return $this->doCascade(false); + } + + public function hasNegativeValues(): bool + { + foreach ($this->toArray() as $value) { + if ($value < 0) { + return true; + } + } + + return false; + } + + public function hasPositiveValues(): bool + { + foreach ($this->toArray() as $value) { + if ($value > 0) { + return true; + } + } + + return false; + } + + /** + * Get amount of given unit equivalent to the interval. + * + * @param string $unit + * + * @throws UnknownUnitException|UnitNotConfiguredException + * + * @return float + */ + public function total($unit) + { + $realUnit = $unit = strtolower($unit); + + if (\in_array($unit, ['days', 'weeks'])) { + $realUnit = 'dayz'; + } elseif (!\in_array($unit, ['microseconds', 'milliseconds', 'seconds', 'minutes', 'hours', 'dayz', 'months', 'years'])) { + throw new UnknownUnitException($unit); + } + + $result = 0; + $cumulativeFactor = 0; + $unitFound = false; + $factors = self::getFlipCascadeFactors(); + $daysPerWeek = (int) static::getDaysPerWeek(); + + $values = [ + 'years' => $this->years, + 'months' => $this->months, + 'weeks' => (int) ($this->d / $daysPerWeek), + 'dayz' => fmod($this->d, $daysPerWeek), + 'hours' => $this->hours, + 'minutes' => $this->minutes, + 'seconds' => $this->seconds, + 'milliseconds' => (int) ($this->microseconds / Carbon::MICROSECONDS_PER_MILLISECOND), + 'microseconds' => $this->microseconds % Carbon::MICROSECONDS_PER_MILLISECOND, + ]; + + if (isset($factors['dayz']) && $factors['dayz'][0] !== 'weeks') { + $values['dayz'] += $values['weeks'] * $daysPerWeek; + $values['weeks'] = 0; + } + + foreach ($factors as $source => [$target, $factor]) { + if ($source === $realUnit) { + $unitFound = true; + $value = $values[$source]; + $result += $value; + $cumulativeFactor = 1; + } + + if ($factor === false) { + if ($unitFound) { + break; + } + + $result = 0; + $cumulativeFactor = 0; + + continue; + } + + if ($target === $realUnit) { + $unitFound = true; + } + + if ($cumulativeFactor) { + $cumulativeFactor *= $factor; + $result += $values[$target] * $cumulativeFactor; + + continue; + } + + $value = $values[$source]; + + $result = ($result + $value) / $factor; + } + + if (isset($target) && !$cumulativeFactor) { + $result += $values[$target]; + } + + if (!$unitFound) { + throw new UnitNotConfiguredException($unit); + } + + if ($this->invert) { + $result *= -1; + } + + if ($unit === 'weeks') { + $result /= $daysPerWeek; + } + + // Cast as int numbers with no decimal part + return fmod($result, 1) === 0.0 ? (int) $result : $result; + } + + /** + * Determines if the instance is equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see equalTo() + * + * @return bool + */ + public function eq($interval): bool + { + return $this->equalTo($interval); + } + + /** + * Determines if the instance is equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function equalTo($interval): bool + { + $interval = $this->resolveInterval($interval); + + return $interval !== null && $this->totalMicroseconds === $interval->totalMicroseconds; + } + + /** + * Determines if the instance is not equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($interval): bool + { + return $this->notEqualTo($interval); + } + + /** + * Determines if the instance is not equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function notEqualTo($interval): bool + { + return !$this->eq($interval); + } + + /** + * Determines if the instance is greater (longer) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see greaterThan() + * + * @return bool + */ + public function gt($interval): bool + { + return $this->greaterThan($interval); + } + + /** + * Determines if the instance is greater (longer) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function greaterThan($interval): bool + { + $interval = $this->resolveInterval($interval); + + return $interval === null || $this->totalMicroseconds > $interval->totalMicroseconds; + } + + /** + * Determines if the instance is greater (longer) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see greaterThanOrEqualTo() + * + * @return bool + */ + public function gte($interval): bool + { + return $this->greaterThanOrEqualTo($interval); + } + + /** + * Determines if the instance is greater (longer) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function greaterThanOrEqualTo($interval): bool + { + return $this->greaterThan($interval) || $this->equalTo($interval); + } + + /** + * Determines if the instance is less (shorter) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see lessThan() + * + * @return bool + */ + public function lt($interval): bool + { + return $this->lessThan($interval); + } + + /** + * Determines if the instance is less (shorter) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function lessThan($interval): bool + { + $interval = $this->resolveInterval($interval); + + return $interval !== null && $this->totalMicroseconds < $interval->totalMicroseconds; + } + + /** + * Determines if the instance is less (shorter) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see lessThanOrEqualTo() + * + * @return bool + */ + public function lte($interval): bool + { + return $this->lessThanOrEqualTo($interval); + } + + /** + * Determines if the instance is less (shorter) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function lessThanOrEqualTo($interval): bool + { + return $this->lessThan($interval) || $this->equalTo($interval); + } + + /** + * Determines if the instance is between two others. + * + * The third argument allow you to specify if bounds are included or not (true by default) + * but for when you including/excluding bounds may produce different results in your application, + * we recommend to use the explicit methods ->betweenIncluded() or ->betweenExcluded() instead. + * + * @example + * ``` + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(2)); // true + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(2), false); // false + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function between($interval1, $interval2, $equal = true): bool + { + return $equal + ? $this->greaterThanOrEqualTo($interval1) && $this->lessThanOrEqualTo($interval2) + : $this->greaterThan($interval1) && $this->lessThan($interval2); + } + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(2)); // true + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * + * @return bool + */ + public function betweenIncluded($interval1, $interval2): bool + { + return $this->between($interval1, $interval2, true); + } + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(2)); // false + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * + * @return bool + */ + public function betweenExcluded($interval1, $interval2): bool + { + return $this->between($interval1, $interval2, false); + } + + /** + * Determines if the instance is between two others + * + * @example + * ``` + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(2)); // true + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(2), false); // false + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function isBetween($interval1, $interval2, $equal = true): bool + { + return $this->between($interval1, $interval2, $equal); + } + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int|string|DateInterval|null $precision + * @param string $function + * + * @throws Exception + * + * @return $this + */ + public function roundUnit($unit, $precision = 1, $function = 'round') + { + if (static::getCascadeFactors() !== static::getDefaultCascadeFactors()) { + $value = $function($this->total($unit) / $precision) * $precision; + $inverted = $value < 0; + + return $this->copyProperties(self::fromString( + number_format(abs($value), 12, '.', '').' '.$unit + )->invert($inverted)->cascade()); + } + + $base = CarbonImmutable::parse('2000-01-01 00:00:00', 'UTC') + ->roundUnit($unit, $precision, $function); + $next = $base->add($this); + $inverted = $next < $base; + + if ($inverted) { + $next = $base->sub($this); + } + + $this->copyProperties( + $next + ->roundUnit($unit, $precision, $function) + ->diffAsCarbonInterval($base) + ); + + return $this->invert($inverted); + } + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function floorUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'floor'); + } + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function ceilUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'ceil'); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|DateInterval|null $precision + * @param string $function + * + * @throws Exception + * + * @return $this + */ + public function round($precision = 1, $function = 'round') + { + return $this->roundWith($precision, $function); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function floor($precision = 1) + { + return $this->round($precision, 'floor'); + } + + /** + * Ceil the current instance second with given precision if specified. + * + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function ceil($precision = 1) + { + return $this->round($precision, 'ceil'); + } + + private function needsDeclension(string $mode, int $index, int $parts): bool + { + switch ($mode) { + case 'last': + return $index === $parts - 1; + default: + return true; + } + } + + private function checkIntegerValue(string $name, $value) + { + if (\is_int($value)) { + return; + } + + $this->assertSafeForInteger($name, $value); + + if (\is_float($value) && (((float) (int) $value) === $value)) { + return; + } + + if (!self::$floatSettersEnabled) { + $type = \gettype($value); + @trigger_error( + "Since 2.70.0, it's deprecated to pass $type value for $name.\n". + "It's truncated when stored as an integer interval unit.\n". + "From 3.0.0, decimal part will no longer be truncated and will be cascaded to smaller units.\n". + "- To maintain the current behavior, use explicit cast: $name((int) \$value)\n". + "- To adopt the new behavior globally, call CarbonInterval::enableFloatSetters()\n", + \E_USER_DEPRECATED + ); + } + } + + /** + * Throw an exception if precision loss when storing the given value as an integer would be >= 1.0. + */ + private function assertSafeForInteger(string $name, $value) + { + if ($value && !\is_int($value) && ($value >= 0x7fffffffffffffff || $value <= -0x7fffffffffffffff)) { + throw new OutOfRangeException($name, -0x7fffffffffffffff, 0x7fffffffffffffff, $value); + } + } + + private function handleDecimalPart(string $unit, $value, $integerValue) + { + if (self::$floatSettersEnabled) { + $floatValue = (float) $value; + $base = (float) $integerValue; + + if ($floatValue === $base) { + return; + } + + $units = [ + 'y' => 'year', + 'm' => 'month', + 'd' => 'day', + 'h' => 'hour', + 'i' => 'minute', + 's' => 'second', + ]; + $upper = true; + + foreach ($units as $property => $name) { + if ($name === $unit) { + $upper = false; + + continue; + } + + if (!$upper && $this->$property !== 0) { + throw new RuntimeException( + "You cannot set $unit to a float value as $name would be overridden, ". + 'set it first to 0 explicitly if you really want to erase its value' + ); + } + } + + $this->add($unit, $floatValue - $base); + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php new file mode 100644 index 0000000000..d12a986971 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php @@ -0,0 +1,2742 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\EndLessPeriodException; +use Carbon\Exceptions\InvalidCastException; +use Carbon\Exceptions\InvalidIntervalException; +use Carbon\Exceptions\InvalidPeriodDateException; +use Carbon\Exceptions\InvalidPeriodParameterException; +use Carbon\Exceptions\NotACarbonClassException; +use Carbon\Exceptions\NotAPeriodException; +use Carbon\Exceptions\UnknownGetterException; +use Carbon\Exceptions\UnknownMethodException; +use Carbon\Exceptions\UnreachableException; +use Carbon\Traits\IntervalRounding; +use Carbon\Traits\Mixin; +use Carbon\Traits\Options; +use Carbon\Traits\ToStringFormat; +use Closure; +use Countable; +use DateInterval; +use DatePeriod; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use InvalidArgumentException; +use Iterator; +use JsonSerializable; +use ReflectionException; +use ReturnTypeWillChange; +use RuntimeException; + +/** + * Substitution of DatePeriod with some modifications and many more features. + * + * @property-read int|float $recurrences number of recurrences (if end not set). + * @property-read bool $include_start_date rather the start date is included in the iteration. + * @property-read bool $include_end_date rather the end date is included in the iteration (if recurrences not set). + * @property-read CarbonInterface $start Period start date. + * @property-read CarbonInterface $current Current date from the iteration. + * @property-read CarbonInterface $end Period end date. + * @property-read CarbonInterval $interval Underlying date interval instance. Always present, one day by default. + * + * @method static static start($date, $inclusive = null) Create instance specifying start date or modify the start date if called on an instance. + * @method static static since($date, $inclusive = null) Alias for start(). + * @method static static sinceNow($inclusive = null) Create instance with start date set to now or set the start date to now if called on an instance. + * @method static static end($date = null, $inclusive = null) Create instance specifying end date or modify the end date if called on an instance. + * @method static static until($date = null, $inclusive = null) Alias for end(). + * @method static static untilNow($inclusive = null) Create instance with end date set to now or set the end date to now if called on an instance. + * @method static static dates($start, $end = null) Create instance with start and end dates or modify the start and end dates if called on an instance. + * @method static static between($start, $end = null) Create instance with start and end dates or modify the start and end dates if called on an instance. + * @method static static recurrences($recurrences = null) Create instance with maximum number of recurrences or modify the number of recurrences if called on an instance. + * @method static static times($recurrences = null) Alias for recurrences(). + * @method static static options($options = null) Create instance with options or modify the options if called on an instance. + * @method static static toggle($options, $state = null) Create instance with options toggled on or off, or toggle options if called on an instance. + * @method static static filter($callback, $name = null) Create instance with filter added to the stack or append a filter if called on an instance. + * @method static static push($callback, $name = null) Alias for filter(). + * @method static static prepend($callback, $name = null) Create instance with filter prepended to the stack or prepend a filter if called on an instance. + * @method static static filters(array $filters = []) Create instance with filters stack or replace the whole filters stack if called on an instance. + * @method static static interval($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static static each($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static static every($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static static step($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static static stepBy($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static static invert() Create instance with inverted date interval or invert the interval if called on an instance. + * @method static static years($years = 1) Create instance specifying a number of years for date interval or replace the interval by the given a number of years if called on an instance. + * @method static static year($years = 1) Alias for years(). + * @method static static months($months = 1) Create instance specifying a number of months for date interval or replace the interval by the given a number of months if called on an instance. + * @method static static month($months = 1) Alias for months(). + * @method static static weeks($weeks = 1) Create instance specifying a number of weeks for date interval or replace the interval by the given a number of weeks if called on an instance. + * @method static static week($weeks = 1) Alias for weeks(). + * @method static static days($days = 1) Create instance specifying a number of days for date interval or replace the interval by the given a number of days if called on an instance. + * @method static static dayz($days = 1) Alias for days(). + * @method static static day($days = 1) Alias for days(). + * @method static static hours($hours = 1) Create instance specifying a number of hours for date interval or replace the interval by the given a number of hours if called on an instance. + * @method static static hour($hours = 1) Alias for hours(). + * @method static static minutes($minutes = 1) Create instance specifying a number of minutes for date interval or replace the interval by the given a number of minutes if called on an instance. + * @method static static minute($minutes = 1) Alias for minutes(). + * @method static static seconds($seconds = 1) Create instance specifying a number of seconds for date interval or replace the interval by the given a number of seconds if called on an instance. + * @method static static second($seconds = 1) Alias for seconds(). + * @method static static milliseconds($milliseconds = 1) Create instance specifying a number of milliseconds for date interval or replace the interval by the given a number of milliseconds if called on an instance. + * @method static static millisecond($milliseconds = 1) Alias for milliseconds(). + * @method static static microseconds($microseconds = 1) Create instance specifying a number of microseconds for date interval or replace the interval by the given a number of microseconds if called on an instance. + * @method static static microsecond($microseconds = 1) Alias for microseconds(). + * @method $this roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method $this floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method $this ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method $this ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method $this roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method $this floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method $this ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method $this ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method $this roundWeek(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundWeeks(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorWeek(float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorWeeks(float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilWeek(float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilWeeks(float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method $this floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method $this ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method $this ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method $this roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method $this floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method $this ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method $this ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method $this roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method $this ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CarbonPeriod implements Iterator, Countable, JsonSerializable +{ + use IntervalRounding; + use Mixin { + Mixin::mixin as baseMixin; + } + use Options; + use ToStringFormat; + + /** + * Built-in filter for limit by recurrences. + * + * @var callable + */ + public const RECURRENCES_FILTER = [self::class, 'filterRecurrences']; + + /** + * Built-in filter for limit to an end. + * + * @var callable + */ + public const END_DATE_FILTER = [self::class, 'filterEndDate']; + + /** + * Special value which can be returned by filters to end iteration. Also a filter. + * + * @var callable + */ + public const END_ITERATION = [self::class, 'endIteration']; + + /** + * Exclude start date from iteration. + * + * @var int + */ + public const EXCLUDE_START_DATE = 1; + + /** + * Exclude end date from iteration. + * + * @var int + */ + public const EXCLUDE_END_DATE = 2; + + /** + * Yield CarbonImmutable instances. + * + * @var int + */ + public const IMMUTABLE = 4; + + /** + * Number of maximum attempts before giving up on finding next valid date. + * + * @var int + */ + public const NEXT_MAX_ATTEMPTS = 1000; + + /** + * Number of maximum attempts before giving up on finding end date. + * + * @var int + */ + public const END_MAX_ATTEMPTS = 10000; + + /** + * Default date class of iteration items. + * + * @var string + */ + protected const DEFAULT_DATE_CLASS = Carbon::class; + + /** + * The registered macros. + * + * @var array + */ + protected static $macros = []; + + /** + * Date class of iteration items. + * + * @var string + */ + protected $dateClass = Carbon::class; + + /** + * Underlying date interval instance. Always present, one day by default. + * + * @var CarbonInterval + */ + protected $dateInterval; + + /** + * True once __construct is finished. + * + * @var bool + */ + protected $constructed = false; + + /** + * Whether current date interval was set by default. + * + * @var bool + */ + protected $isDefaultInterval; + + /** + * The filters stack. + * + * @var array + */ + protected $filters = []; + + /** + * Period start date. Applied on rewind. Always present, now by default. + * + * @var CarbonInterface + */ + protected $startDate; + + /** + * Period end date. For inverted interval should be before the start date. Applied via a filter. + * + * @var CarbonInterface|null + */ + protected $endDate; + + /** + * Limit for number of recurrences. Applied via a filter. + * + * @var int|null + */ + protected $recurrences; + + /** + * Iteration options. + * + * @var int + */ + protected $options; + + /** + * Index of current date. Always sequential, even if some dates are skipped by filters. + * Equal to null only before the first iteration. + * + * @var int + */ + protected $key; + + /** + * Current date. May temporarily hold unaccepted value when looking for a next valid date. + * Equal to null only before the first iteration. + * + * @var CarbonInterface + */ + protected $current; + + /** + * Timezone of current date. Taken from the start date. + * + * @var \DateTimeZone|null + */ + protected $timezone; + + /** + * The cached validation result for current date. + * + * @var bool|string|null + */ + protected $validationResult; + + /** + * Timezone handler for settings() method. + * + * @var mixed + */ + protected $tzName; + + /** + * Make a CarbonPeriod instance from given variable if possible. + * + * @param mixed $var + * + * @return static|null + */ + public static function make($var) + { + try { + return static::instance($var); + } catch (NotAPeriodException $e) { + return static::create($var); + } + } + + /** + * Create a new instance from a DatePeriod or CarbonPeriod object. + * + * @param CarbonPeriod|DatePeriod $period + * + * @return static + */ + public static function instance($period) + { + if ($period instanceof static) { + return $period->copy(); + } + + if ($period instanceof self) { + return new static( + $period->getStartDate(), + $period->getEndDate() ?: $period->getRecurrences(), + $period->getDateInterval(), + $period->getOptions() + ); + } + + if ($period instanceof DatePeriod) { + return new static( + $period->start, + $period->end ?: ($period->recurrences - 1), + $period->interval, + $period->include_start_date ? 0 : static::EXCLUDE_START_DATE + ); + } + + $class = static::class; + $type = \gettype($period); + + throw new NotAPeriodException( + 'Argument 1 passed to '.$class.'::'.__METHOD__.'() '. + 'must be an instance of DatePeriod or '.$class.', '. + ($type === 'object' ? 'instance of '.\get_class($period) : $type).' given.' + ); + } + + /** + * Create a new instance. + * + * @return static + */ + public static function create(...$params) + { + return static::createFromArray($params); + } + + /** + * Create a new instance from an array of parameters. + * + * @param array $params + * + * @return static + */ + public static function createFromArray(array $params) + { + return new static(...$params); + } + + /** + * Create CarbonPeriod from ISO 8601 string. + * + * @param string $iso + * @param int|null $options + * + * @return static + */ + public static function createFromIso($iso, $options = null) + { + $params = static::parseIso8601($iso); + + $instance = static::createFromArray($params); + + if ($options !== null) { + $instance->setOptions($options); + } + + return $instance; + } + + /** + * Return whether given interval contains non zero value of any time unit. + * + * @param \DateInterval $interval + * + * @return bool + */ + protected static function intervalHasTime(DateInterval $interval) + { + return $interval->h || $interval->i || $interval->s || $interval->f; + } + + /** + * Return whether given variable is an ISO 8601 specification. + * + * Note: Check is very basic, as actual validation will be done later when parsing. + * We just want to ensure that variable is not any other type of a valid parameter. + * + * @param mixed $var + * + * @return bool + */ + protected static function isIso8601($var) + { + if (!\is_string($var)) { + return false; + } + + // Match slash but not within a timezone name. + $part = '[a-z]+(?:[_-][a-z]+)*'; + + preg_match("#\b$part/$part\b|(/)#i", $var, $match); + + return isset($match[1]); + } + + /** + * Parse given ISO 8601 string into an array of arguments. + * + * @SuppressWarnings(PHPMD.ElseExpression) + * + * @param string $iso + * + * @return array + */ + protected static function parseIso8601($iso) + { + $result = []; + + $interval = null; + $start = null; + $end = null; + $dateClass = static::DEFAULT_DATE_CLASS; + + foreach (explode('/', $iso) as $key => $part) { + if ($key === 0 && preg_match('/^R(\d*|INF)$/', $part, $match)) { + $parsed = \strlen($match[1]) ? (($match[1] !== 'INF') ? (int) $match[1] : INF) : null; + } elseif ($interval === null && $parsed = CarbonInterval::make($part)) { + $interval = $part; + } elseif ($start === null && $parsed = $dateClass::make($part)) { + $start = $part; + } elseif ($end === null && $parsed = $dateClass::make(static::addMissingParts($start ?? '', $part))) { + $end = $part; + } else { + throw new InvalidPeriodParameterException("Invalid ISO 8601 specification: $iso."); + } + + $result[] = $parsed; + } + + return $result; + } + + /** + * Add missing parts of the target date from the source date. + * + * @param string $source + * @param string $target + * + * @return string + */ + protected static function addMissingParts($source, $target) + { + $pattern = '/'.preg_replace('/\d+/', '[0-9]+', preg_quote($target, '/')).'$/'; + + $result = preg_replace($pattern, $target, $source, 1, $count); + + return $count ? $result : $target; + } + + /** + * Register a custom macro. + * + * @example + * ``` + * CarbonPeriod::macro('middle', function () { + * return $this->getStartDate()->average($this->getEndDate()); + * }); + * echo CarbonPeriod::since('2011-05-12')->until('2011-06-03')->middle(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$macros[$name] = $macro; + } + + /** + * Register macros from a mixin object. + * + * @example + * ``` + * CarbonPeriod::mixin(new class { + * public function addDays() { + * return function ($count = 1) { + * return $this->setStartDate( + * $this->getStartDate()->addDays($count) + * )->setEndDate( + * $this->getEndDate()->addDays($count) + * ); + * }; + * } + * public function subDays() { + * return function ($count = 1) { + * return $this->setStartDate( + * $this->getStartDate()->subDays($count) + * )->setEndDate( + * $this->getEndDate()->subDays($count) + * ); + * }; + * } + * }); + * echo CarbonPeriod::create('2000-01-01', '2000-02-01')->addDays(5)->subDays(3); + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + static::baseMixin($mixin); + } + + /** + * Check if macro is registered. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$macros[$name]); + } + + /** + * Provide static proxy for instance aliases. + * + * @param string $method + * @param array $parameters + * + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + $date = new static(); + + if (static::hasMacro($method)) { + return static::bindMacroContext(null, function () use (&$method, &$parameters, &$date) { + return $date->callMacro($method, $parameters); + }); + } + + return $date->$method(...$parameters); + } + + /** + * CarbonPeriod constructor. + * + * @SuppressWarnings(PHPMD.ElseExpression) + * + * @throws InvalidArgumentException + */ + public function __construct(...$arguments) + { + if (is_a($this->dateClass, DateTimeImmutable::class, true)) { + $this->options = static::IMMUTABLE; + } + + // Parse and assign arguments one by one. First argument may be an ISO 8601 spec, + // which will be first parsed into parts and then processed the same way. + + $argumentsCount = \count($arguments); + + if ($argumentsCount && static::isIso8601($iso = $arguments[0])) { + array_splice($arguments, 0, 1, static::parseIso8601($iso)); + } + + if ($argumentsCount === 1) { + if ($arguments[0] instanceof DatePeriod) { + $arguments = [ + $arguments[0]->start, + $arguments[0]->end ?: ($arguments[0]->recurrences - 1), + $arguments[0]->interval, + $arguments[0]->include_start_date ? 0 : static::EXCLUDE_START_DATE, + ]; + } elseif ($arguments[0] instanceof self) { + $arguments = [ + $arguments[0]->getStartDate(), + $arguments[0]->getEndDate() ?: $arguments[0]->getRecurrences(), + $arguments[0]->getDateInterval(), + $arguments[0]->getOptions(), + ]; + } + } + + $optionsSet = false; + + foreach ($arguments as $argument) { + $parsedDate = null; + + if ($argument instanceof DateTimeZone) { + $this->setTimezone($argument); + } elseif ($this->dateInterval === null && + ( + (\is_string($argument) && preg_match( + '/^(-?\d(\d(?![\/-])|[^\d\/-]([\/-])?)*|P[T\d].*|(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+)$/i', + $argument + )) || + $argument instanceof DateInterval || + $argument instanceof Closure + ) && + $parsedInterval = @CarbonInterval::make($argument) + ) { + $this->setDateInterval($parsedInterval); + } elseif ($this->startDate === null && $parsedDate = $this->makeDateTime($argument)) { + $this->setStartDate($parsedDate); + } elseif ($this->endDate === null && ($parsedDate = $parsedDate ?? $this->makeDateTime($argument))) { + $this->setEndDate($parsedDate); + } elseif ($this->recurrences === null && $this->endDate === null && is_numeric($argument)) { + $this->setRecurrences($argument); + } elseif (!$optionsSet && (\is_int($argument) || $argument === null)) { + $optionsSet = true; + $this->setOptions(((int) $this->options) | ((int) $argument)); + } else { + throw new InvalidPeriodParameterException('Invalid constructor parameters.'); + } + } + + if ($this->startDate === null) { + $dateClass = $this->dateClass; + $this->setStartDate($dateClass::now()); + } + + if ($this->dateInterval === null) { + $this->setDateInterval(CarbonInterval::day()); + + $this->isDefaultInterval = true; + } + + if ($this->options === null) { + $this->setOptions(0); + } + + $this->constructed = true; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + return clone $this; + } + + /** + * Prepare the instance to be set (self if mutable to be mutated, + * copy if immutable to generate a new instance). + * + * @return static + */ + protected function copyIfImmutable() + { + return $this; + } + + /** + * Get the getter for a property allowing both `DatePeriod` snakeCase and camelCase names. + * + * @param string $name + * + * @return callable|null + */ + protected function getGetter(string $name) + { + switch (strtolower(preg_replace('/[A-Z]/', '_$0', $name))) { + case 'start': + case 'start_date': + return [$this, 'getStartDate']; + case 'end': + case 'end_date': + return [$this, 'getEndDate']; + case 'interval': + case 'date_interval': + return [$this, 'getDateInterval']; + case 'recurrences': + return [$this, 'getRecurrences']; + case 'include_start_date': + return [$this, 'isStartIncluded']; + case 'include_end_date': + return [$this, 'isEndIncluded']; + case 'current': + return [$this, 'current']; + default: + return null; + } + } + + /** + * Get a property allowing both `DatePeriod` snakeCase and camelCase names. + * + * @param string $name + * + * @return bool|CarbonInterface|CarbonInterval|int|null + */ + public function get(string $name) + { + $getter = $this->getGetter($name); + + if ($getter) { + return $getter(); + } + + throw new UnknownGetterException($name); + } + + /** + * Get a property allowing both `DatePeriod` snakeCase and camelCase names. + * + * @param string $name + * + * @return bool|CarbonInterface|CarbonInterval|int|null + */ + public function __get(string $name) + { + return $this->get($name); + } + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset(string $name): bool + { + return $this->getGetter($name) !== null; + } + + /** + * @alias copy + * + * Get a copy of the instance. + * + * @return static + */ + public function clone() + { + return clone $this; + } + + /** + * Set the iteration item class. + * + * @param string $dateClass + * + * @return static + */ + public function setDateClass(string $dateClass) + { + if (!is_a($dateClass, CarbonInterface::class, true)) { + throw new NotACarbonClassException($dateClass); + } + + $self = $this->copyIfImmutable(); + $self->dateClass = $dateClass; + + if (is_a($dateClass, Carbon::class, true)) { + $self->options = $self->options & ~static::IMMUTABLE; + } elseif (is_a($dateClass, CarbonImmutable::class, true)) { + $self->options = $self->options | static::IMMUTABLE; + } + + return $self; + } + + /** + * Returns iteration item date class. + * + * @return string + */ + public function getDateClass(): string + { + return $this->dateClass; + } + + /** + * Change the period date interval. + * + * @param DateInterval|string $interval + * + * @throws InvalidIntervalException + * + * @return static + */ + public function setDateInterval($interval) + { + if (!$interval = CarbonInterval::make($interval)) { + throw new InvalidIntervalException('Invalid interval.'); + } + + if ($interval->spec() === 'PT0S' && !$interval->f && !$interval->getStep()) { + throw new InvalidIntervalException('Empty interval is not accepted.'); + } + + $self = $this->copyIfImmutable(); + $self->dateInterval = $interval; + + $self->isDefaultInterval = false; + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Invert the period date interval. + * + * @return static + */ + public function invertDateInterval() + { + return $this->setDateInterval($this->dateInterval->invert()); + } + + /** + * Set start and end date. + * + * @param DateTime|DateTimeInterface|string $start + * @param DateTime|DateTimeInterface|string|null $end + * + * @return static + */ + public function setDates($start, $end) + { + return $this->setStartDate($start)->setEndDate($end); + } + + /** + * Change the period options. + * + * @param int|null $options + * + * @throws InvalidArgumentException + * + * @return static + */ + public function setOptions($options) + { + if (!\is_int($options) && $options !== null) { + throw new InvalidPeriodParameterException('Invalid options.'); + } + + $self = $this->copyIfImmutable(); + $self->options = $options ?: 0; + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Get the period options. + * + * @return int + */ + public function getOptions() + { + return $this->options; + } + + /** + * Toggle given options on or off. + * + * @param int $options + * @param bool|null $state + * + * @throws \InvalidArgumentException + * + * @return static + */ + public function toggleOptions($options, $state = null) + { + if ($state === null) { + $state = ($this->options & $options) !== $options; + } + + return $this->setOptions( + $state ? + $this->options | $options : + $this->options & ~$options + ); + } + + /** + * Toggle EXCLUDE_START_DATE option. + * + * @param bool $state + * + * @return static + */ + public function excludeStartDate($state = true) + { + return $this->toggleOptions(static::EXCLUDE_START_DATE, $state); + } + + /** + * Toggle EXCLUDE_END_DATE option. + * + * @param bool $state + * + * @return static + */ + public function excludeEndDate($state = true) + { + return $this->toggleOptions(static::EXCLUDE_END_DATE, $state); + } + + /** + * Get the underlying date interval. + * + * @return CarbonInterval + */ + public function getDateInterval() + { + return $this->dateInterval->copy(); + } + + /** + * Get start date of the period. + * + * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. + * + * @return CarbonInterface + */ + public function getStartDate(string $rounding = null) + { + $date = $this->startDate->avoidMutation(); + + return $rounding ? $date->round($this->getDateInterval(), $rounding) : $date; + } + + /** + * Get end date of the period. + * + * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. + * + * @return CarbonInterface|null + */ + public function getEndDate(string $rounding = null) + { + if (!$this->endDate) { + return null; + } + + $date = $this->endDate->avoidMutation(); + + return $rounding ? $date->round($this->getDateInterval(), $rounding) : $date; + } + + /** + * Get number of recurrences. + * + * @return int|float|null + */ + public function getRecurrences() + { + return $this->recurrences; + } + + /** + * Returns true if the start date should be excluded. + * + * @return bool + */ + public function isStartExcluded() + { + return ($this->options & static::EXCLUDE_START_DATE) !== 0; + } + + /** + * Returns true if the end date should be excluded. + * + * @return bool + */ + public function isEndExcluded() + { + return ($this->options & static::EXCLUDE_END_DATE) !== 0; + } + + /** + * Returns true if the start date should be included. + * + * @return bool + */ + public function isStartIncluded() + { + return !$this->isStartExcluded(); + } + + /** + * Returns true if the end date should be included. + * + * @return bool + */ + public function isEndIncluded() + { + return !$this->isEndExcluded(); + } + + /** + * Return the start if it's included by option, else return the start + 1 period interval. + * + * @return CarbonInterface + */ + public function getIncludedStartDate() + { + $start = $this->getStartDate(); + + if ($this->isStartExcluded()) { + return $start->add($this->getDateInterval()); + } + + return $start; + } + + /** + * Return the end if it's included by option, else return the end - 1 period interval. + * Warning: if the period has no fixed end, this method will iterate the period to calculate it. + * + * @return CarbonInterface + */ + public function getIncludedEndDate() + { + $end = $this->getEndDate(); + + if (!$end) { + return $this->calculateEnd(); + } + + if ($this->isEndExcluded()) { + return $end->sub($this->getDateInterval()); + } + + return $end; + } + + /** + * Add a filter to the stack. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param callable $callback + * @param string $name + * + * @return static + */ + public function addFilter($callback, $name = null) + { + $self = $this->copyIfImmutable(); + $tuple = $self->createFilterTuple(\func_get_args()); + + $self->filters[] = $tuple; + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Prepend a filter to the stack. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param callable $callback + * @param string $name + * + * @return static + */ + public function prependFilter($callback, $name = null) + { + $self = $this->copyIfImmutable(); + $tuple = $self->createFilterTuple(\func_get_args()); + + array_unshift($self->filters, $tuple); + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Remove a filter by instance or name. + * + * @param callable|string $filter + * + * @return static + */ + public function removeFilter($filter) + { + $self = $this->copyIfImmutable(); + $key = \is_callable($filter) ? 0 : 1; + + $self->filters = array_values(array_filter( + $this->filters, + function ($tuple) use ($key, $filter) { + return $tuple[$key] !== $filter; + } + )); + + $self->updateInternalState(); + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Return whether given instance or name is in the filter stack. + * + * @param callable|string $filter + * + * @return bool + */ + public function hasFilter($filter) + { + $key = \is_callable($filter) ? 0 : 1; + + foreach ($this->filters as $tuple) { + if ($tuple[$key] === $filter) { + return true; + } + } + + return false; + } + + /** + * Get filters stack. + * + * @return array + */ + public function getFilters() + { + return $this->filters; + } + + /** + * Set filters stack. + * + * @param array $filters + * + * @return static + */ + public function setFilters(array $filters) + { + $self = $this->copyIfImmutable(); + $self->filters = $filters; + + $self->updateInternalState(); + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Reset filters stack. + * + * @return static + */ + public function resetFilters() + { + $self = $this->copyIfImmutable(); + $self->filters = []; + + if ($self->endDate !== null) { + $self->filters[] = [static::END_DATE_FILTER, null]; + } + + if ($self->recurrences !== null) { + $self->filters[] = [static::RECURRENCES_FILTER, null]; + } + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Add a recurrences filter (set maximum number of recurrences). + * + * @param int|float|null $recurrences + * + * @throws InvalidArgumentException + * + * @return static + */ + public function setRecurrences($recurrences) + { + if ((!is_numeric($recurrences) && $recurrences !== null) || $recurrences < 0) { + throw new InvalidPeriodParameterException('Invalid number of recurrences.'); + } + + if ($recurrences === null) { + return $this->removeFilter(static::RECURRENCES_FILTER); + } + + /** @var self $self */ + $self = $this->copyIfImmutable(); + $self->recurrences = $recurrences === INF ? INF : (int) $recurrences; + + if (!$self->hasFilter(static::RECURRENCES_FILTER)) { + return $self->addFilter(static::RECURRENCES_FILTER); + } + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Change the period start date. + * + * @param DateTime|DateTimeInterface|string $date + * @param bool|null $inclusive + * + * @throws InvalidPeriodDateException + * + * @return static + */ + public function setStartDate($date, $inclusive = null) + { + if (!$this->isInfiniteDate($date) && !($date = ([$this->dateClass, 'make'])($date))) { + throw new InvalidPeriodDateException('Invalid start date.'); + } + + $self = $this->copyIfImmutable(); + $self->startDate = $date; + + if ($inclusive !== null) { + $self = $self->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive); + } + + return $self; + } + + /** + * Change the period end date. + * + * @param DateTime|DateTimeInterface|string|null $date + * @param bool|null $inclusive + * + * @throws \InvalidArgumentException + * + * @return static + */ + public function setEndDate($date, $inclusive = null) + { + if ($date !== null && !$this->isInfiniteDate($date) && !$date = ([$this->dateClass, 'make'])($date)) { + throw new InvalidPeriodDateException('Invalid end date.'); + } + + if (!$date) { + return $this->removeFilter(static::END_DATE_FILTER); + } + + $self = $this->copyIfImmutable(); + $self->endDate = $date; + + if ($inclusive !== null) { + $self = $self->toggleOptions(static::EXCLUDE_END_DATE, !$inclusive); + } + + if (!$self->hasFilter(static::END_DATE_FILTER)) { + return $self->addFilter(static::END_DATE_FILTER); + } + + $self->handleChangedParameters(); + + return $self; + } + + /** + * Check if the current position is valid. + * + * @return bool + */ + #[ReturnTypeWillChange] + public function valid() + { + return $this->validateCurrentDate() === true; + } + + /** + * Return the current key. + * + * @return int|null + */ + #[ReturnTypeWillChange] + public function key() + { + return $this->valid() + ? $this->key + : null; + } + + /** + * Return the current date. + * + * @return CarbonInterface|null + */ + #[ReturnTypeWillChange] + public function current() + { + return $this->valid() + ? $this->prepareForReturn($this->current) + : null; + } + + /** + * Move forward to the next date. + * + * @throws RuntimeException + * + * @return void + */ + #[ReturnTypeWillChange] + public function next() + { + if ($this->current === null) { + $this->rewind(); + } + + if ($this->validationResult !== static::END_ITERATION) { + $this->key++; + + $this->incrementCurrentDateUntilValid(); + } + } + + /** + * Rewind to the start date. + * + * Iterating over a date in the UTC timezone avoids bug during backward DST change. + * + * @see https://bugs.php.net/bug.php?id=72255 + * @see https://bugs.php.net/bug.php?id=74274 + * @see https://wiki.php.net/rfc/datetime_and_daylight_saving_time + * + * @throws RuntimeException + * + * @return void + */ + #[ReturnTypeWillChange] + public function rewind() + { + $this->key = 0; + $this->current = ([$this->dateClass, 'make'])($this->startDate); + $settings = $this->getSettings(); + + if ($this->hasLocalTranslator()) { + $settings['locale'] = $this->getTranslatorLocale(); + } + + $this->current->settings($settings); + $this->timezone = static::intervalHasTime($this->dateInterval) ? $this->current->getTimezone() : null; + + if ($this->timezone) { + $this->current = $this->current->utc(); + } + + $this->validationResult = null; + + if ($this->isStartExcluded() || $this->validateCurrentDate() === false) { + $this->incrementCurrentDateUntilValid(); + } + } + + /** + * Skip iterations and returns iteration state (false if ended, true if still valid). + * + * @param int $count steps number to skip (1 by default) + * + * @return bool + */ + public function skip($count = 1) + { + for ($i = $count; $this->valid() && $i > 0; $i--) { + $this->next(); + } + + return $this->valid(); + } + + /** + * Format the date period as ISO 8601. + * + * @return string + */ + public function toIso8601String() + { + $parts = []; + + if ($this->recurrences !== null) { + $parts[] = 'R'.$this->recurrences; + } + + $parts[] = $this->startDate->toIso8601String(); + + $parts[] = $this->dateInterval->spec(); + + if ($this->endDate !== null) { + $parts[] = $this->endDate->toIso8601String(); + } + + return implode('/', $parts); + } + + /** + * Convert the date period into a string. + * + * @return string + */ + public function toString() + { + $format = $this->localToStringFormat ?? static::$toStringFormat; + + if ($format instanceof Closure) { + return $format($this); + } + + $translator = ([$this->dateClass, 'getTranslator'])(); + + $parts = []; + + $format = $format ?? ( + !$this->startDate->isStartOfDay() || ($this->endDate && !$this->endDate->isStartOfDay()) + ? 'Y-m-d H:i:s' + : 'Y-m-d' + ); + + if ($this->recurrences !== null) { + $parts[] = $this->translate('period_recurrences', [], $this->recurrences, $translator); + } + + $parts[] = $this->translate('period_interval', [':interval' => $this->dateInterval->forHumans([ + 'join' => true, + ])], null, $translator); + + $parts[] = $this->translate('period_start_date', [':date' => $this->startDate->rawFormat($format)], null, $translator); + + if ($this->endDate !== null) { + $parts[] = $this->translate('period_end_date', [':date' => $this->endDate->rawFormat($format)], null, $translator); + } + + $result = implode(' ', $parts); + + return mb_strtoupper(mb_substr($result, 0, 1)).mb_substr($result, 1); + } + + /** + * Format the date period as ISO 8601. + * + * @return string + */ + public function spec() + { + return $this->toIso8601String(); + } + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DatePeriod + */ + public function cast(string $className) + { + if (!method_exists($className, 'instance')) { + if (is_a($className, DatePeriod::class, true)) { + return new $className( + $this->rawDate($this->getStartDate()), + $this->getDateInterval(), + $this->getEndDate() ? $this->rawDate($this->getIncludedEndDate()) : $this->getRecurrences(), + $this->isStartExcluded() ? DatePeriod::EXCLUDE_START_DATE : 0 + ); + } + + throw new InvalidCastException("$className has not the instance() method needed to cast the date."); + } + + return $className::instance($this); + } + + /** + * Return native DatePeriod PHP object matching the current instance. + * + * @example + * ``` + * var_dump(CarbonPeriod::create('2021-01-05', '2021-02-15')->toDatePeriod()); + * ``` + * + * @return DatePeriod + */ + public function toDatePeriod() + { + return $this->cast(DatePeriod::class); + } + + /** + * Return `true` if the period has no custom filter and is guaranteed to be endless. + * + * Note that we can't check if a period is endless as soon as it has custom filters + * because filters can emit `CarbonPeriod::END_ITERATION` to stop the iteration in + * a way we can't predict without actually iterating the period. + */ + public function isUnfilteredAndEndLess(): bool + { + foreach ($this->filters as $filter) { + switch ($filter) { + case [static::RECURRENCES_FILTER, null]: + if ($this->recurrences !== null && is_finite($this->recurrences)) { + return false; + } + + break; + + case [static::END_DATE_FILTER, null]: + if ($this->endDate !== null && !$this->endDate->isEndOfTime()) { + return false; + } + + break; + + default: + return false; + } + } + + return true; + } + + /** + * Convert the date period into an array without changing current iteration state. + * + * @return CarbonInterface[] + */ + public function toArray() + { + if ($this->isUnfilteredAndEndLess()) { + throw new EndLessPeriodException("Endless period can't be converted to array nor counted."); + } + + $state = [ + $this->key, + $this->current ? $this->current->avoidMutation() : null, + $this->validationResult, + ]; + + $result = iterator_to_array($this); + + [$this->key, $this->current, $this->validationResult] = $state; + + return $result; + } + + /** + * Count dates in the date period. + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + return \count($this->toArray()); + } + + /** + * Return the first date in the date period. + * + * @return CarbonInterface|null + */ + public function first() + { + if ($this->isUnfilteredAndEndLess()) { + foreach ($this as $date) { + $this->rewind(); + + return $date; + } + + return null; + } + + return ($this->toArray() ?: [])[0] ?? null; + } + + /** + * Return the last date in the date period. + * + * @return CarbonInterface|null + */ + public function last() + { + $array = $this->toArray(); + + return $array ? $array[\count($array) - 1] : null; + } + + /** + * Convert the date period into a string. + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Add aliases for setters. + * + * CarbonPeriod::days(3)->hours(5)->invert() + * ->sinceNow()->until('2010-01-10') + * ->filter(...) + * ->count() + * + * Note: We use magic method to let static and instance aliases with the same names. + * + * @param string $method + * @param array $parameters + * + * @return mixed + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return static::bindMacroContext($this, function () use (&$method, &$parameters) { + return $this->callMacro($method, $parameters); + }); + } + + $roundedValue = $this->callRoundMethod($method, $parameters); + + if ($roundedValue !== null) { + return $roundedValue; + } + + switch ($method) { + case 'start': + case 'since': + self::setDefaultParameters($parameters, [ + [0, 'date', null], + ]); + + return $this->setStartDate(...$parameters); + + case 'sinceNow': + return $this->setStartDate(new Carbon(), ...$parameters); + + case 'end': + case 'until': + self::setDefaultParameters($parameters, [ + [0, 'date', null], + ]); + + return $this->setEndDate(...$parameters); + + case 'untilNow': + return $this->setEndDate(new Carbon(), ...$parameters); + + case 'dates': + case 'between': + self::setDefaultParameters($parameters, [ + [0, 'start', null], + [1, 'end', null], + ]); + + return $this->setDates(...$parameters); + + case 'recurrences': + case 'times': + self::setDefaultParameters($parameters, [ + [0, 'recurrences', null], + ]); + + return $this->setRecurrences(...$parameters); + + case 'options': + self::setDefaultParameters($parameters, [ + [0, 'options', null], + ]); + + return $this->setOptions(...$parameters); + + case 'toggle': + self::setDefaultParameters($parameters, [ + [0, 'options', null], + ]); + + return $this->toggleOptions(...$parameters); + + case 'filter': + case 'push': + return $this->addFilter(...$parameters); + + case 'prepend': + return $this->prependFilter(...$parameters); + + case 'filters': + self::setDefaultParameters($parameters, [ + [0, 'filters', []], + ]); + + return $this->setFilters(...$parameters); + + case 'interval': + case 'each': + case 'every': + case 'step': + case 'stepBy': + return $this->setDateInterval(...$parameters); + + case 'invert': + return $this->invertDateInterval(); + + case 'years': + case 'year': + case 'months': + case 'month': + case 'weeks': + case 'week': + case 'days': + case 'dayz': + case 'day': + case 'hours': + case 'hour': + case 'minutes': + case 'minute': + case 'seconds': + case 'second': + case 'milliseconds': + case 'millisecond': + case 'microseconds': + case 'microsecond': + return $this->setDateInterval(( + // Override default P1D when instantiating via fluent setters. + [$this->isDefaultInterval ? new CarbonInterval('PT0S') : $this->dateInterval, $method] + )(...$parameters)); + } + + $dateClass = $this->dateClass; + + if ($this->localStrictModeEnabled ?? $dateClass::isStrictModeEnabled()) { + throw new UnknownMethodException($method); + } + + return $this; + } + + /** + * Set the instance's timezone from a string or object and apply it to start/end. + * + * @param \DateTimeZone|string $timezone + * + * @return static + */ + public function setTimezone($timezone) + { + $self = $this->copyIfImmutable(); + $self->tzName = $timezone; + $self->timezone = $timezone; + + if ($self->startDate) { + $self = $self->setStartDate($self->startDate->setTimezone($timezone)); + } + + if ($self->endDate) { + $self = $self->setEndDate($self->endDate->setTimezone($timezone)); + } + + return $self; + } + + /** + * Set the instance's timezone from a string or object and add/subtract the offset difference to start/end. + * + * @param \DateTimeZone|string $timezone + * + * @return static + */ + public function shiftTimezone($timezone) + { + $self = $this->copyIfImmutable(); + $self->tzName = $timezone; + $self->timezone = $timezone; + + if ($self->startDate) { + $self = $self->setStartDate($self->startDate->shiftTimezone($timezone)); + } + + if ($self->endDate) { + $self = $self->setEndDate($self->endDate->shiftTimezone($timezone)); + } + + return $self; + } + + /** + * Returns the end is set, else calculated from start an recurrences. + * + * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. + * + * @return CarbonInterface + */ + public function calculateEnd(string $rounding = null) + { + if ($end = $this->getEndDate($rounding)) { + return $end; + } + + if ($this->dateInterval->isEmpty()) { + return $this->getStartDate($rounding); + } + + $date = $this->getEndFromRecurrences() ?? $this->iterateUntilEnd(); + + if ($date && $rounding) { + $date = $date->avoidMutation()->round($this->getDateInterval(), $rounding); + } + + return $date; + } + + /** + * @return CarbonInterface|null + */ + private function getEndFromRecurrences() + { + if ($this->recurrences === null) { + throw new UnreachableException( + "Could not calculate period end without either explicit end or recurrences.\n". + "If you're looking for a forever-period, use ->setRecurrences(INF)." + ); + } + + if ($this->recurrences === INF) { + $start = $this->getStartDate(); + + return $start < $start->avoidMutation()->add($this->getDateInterval()) + ? CarbonImmutable::endOfTime() + : CarbonImmutable::startOfTime(); + } + + if ($this->filters === [[static::RECURRENCES_FILTER, null]]) { + return $this->getStartDate()->avoidMutation()->add( + $this->getDateInterval()->times( + $this->recurrences - ($this->isStartExcluded() ? 0 : 1) + ) + ); + } + + return null; + } + + /** + * @return CarbonInterface|null + */ + private function iterateUntilEnd() + { + $attempts = 0; + $date = null; + + foreach ($this as $date) { + if (++$attempts > static::END_MAX_ATTEMPTS) { + throw new UnreachableException( + 'Could not calculate period end after iterating '.static::END_MAX_ATTEMPTS.' times.' + ); + } + } + + return $date; + } + + /** + * Returns true if the current period overlaps the given one (if 1 parameter passed) + * or the period between 2 dates (if 2 parameters passed). + * + * @param CarbonPeriod|\DateTimeInterface|Carbon|CarbonImmutable|string $rangeOrRangeStart + * @param \DateTimeInterface|Carbon|CarbonImmutable|string|null $rangeEnd + * + * @return bool + */ + public function overlaps($rangeOrRangeStart, $rangeEnd = null) + { + $range = $rangeEnd ? static::create($rangeOrRangeStart, $rangeEnd) : $rangeOrRangeStart; + + if (!($range instanceof self)) { + $range = static::create($range); + } + + [$start, $end] = $this->orderCouple($this->getStartDate(), $this->calculateEnd()); + [$rangeStart, $rangeEnd] = $this->orderCouple($range->getStartDate(), $range->calculateEnd()); + + return $end > $rangeStart && $rangeEnd > $start; + } + + /** + * Execute a given function on each date of the period. + * + * @example + * ``` + * Carbon::create('2020-11-29')->daysUntil('2020-12-24')->forEach(function (Carbon $date) { + * echo $date->diffInDays('2020-12-25')." days before Christmas!\n"; + * }); + * ``` + * + * @param callable $callback + */ + public function forEach(callable $callback) + { + foreach ($this as $date) { + $callback($date); + } + } + + /** + * Execute a given function on each date of the period and yield the result of this function. + * + * @example + * ``` + * $period = Carbon::create('2020-11-29')->daysUntil('2020-12-24'); + * echo implode("\n", iterator_to_array($period->map(function (Carbon $date) { + * return $date->diffInDays('2020-12-25').' days before Christmas!'; + * }))); + * ``` + * + * @param callable $callback + * + * @return \Generator + */ + public function map(callable $callback) + { + foreach ($this as $date) { + yield $callback($date); + } + } + + /** + * Determines if the instance is equal to another. + * Warning: if options differ, instances will never be equal. + * + * @param mixed $period + * + * @see equalTo() + * + * @return bool + */ + public function eq($period): bool + { + return $this->equalTo($period); + } + + /** + * Determines if the instance is equal to another. + * Warning: if options differ, instances will never be equal. + * + * @param mixed $period + * + * @return bool + */ + public function equalTo($period): bool + { + if (!($period instanceof self)) { + $period = self::make($period); + } + + $end = $this->getEndDate(); + + return $period !== null + && $this->getDateInterval()->eq($period->getDateInterval()) + && $this->getStartDate()->eq($period->getStartDate()) + && ($end ? $end->eq($period->getEndDate()) : $this->getRecurrences() === $period->getRecurrences()) + && ($this->getOptions() & (~static::IMMUTABLE)) === ($period->getOptions() & (~static::IMMUTABLE)); + } + + /** + * Determines if the instance is not equal to another. + * Warning: if options differ, instances will never be equal. + * + * @param mixed $period + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($period): bool + { + return $this->notEqualTo($period); + } + + /** + * Determines if the instance is not equal to another. + * Warning: if options differ, instances will never be equal. + * + * @param mixed $period + * + * @return bool + */ + public function notEqualTo($period): bool + { + return !$this->eq($period); + } + + /** + * Determines if the start date is before an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsBefore($date = null): bool + { + return $this->getStartDate()->lessThan($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is before or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsBeforeOrAt($date = null): bool + { + return $this->getStartDate()->lessThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is after an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsAfter($date = null): bool + { + return $this->getStartDate()->greaterThan($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is after or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsAfterOrAt($date = null): bool + { + return $this->getStartDate()->greaterThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsAt($date = null): bool + { + return $this->getStartDate()->equalTo($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is before an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsBefore($date = null): bool + { + return $this->calculateEnd()->lessThan($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is before or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsBeforeOrAt($date = null): bool + { + return $this->calculateEnd()->lessThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is after an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsAfter($date = null): bool + { + return $this->calculateEnd()->greaterThan($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is after or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsAfterOrAt($date = null): bool + { + return $this->calculateEnd()->greaterThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsAt($date = null): bool + { + return $this->calculateEnd()->equalTo($this->resolveCarbon($date)); + } + + /** + * Return true if start date is now or later. + * (Rather start/end are included by options is ignored.) + * + * @return bool + */ + public function isStarted(): bool + { + return $this->startsBeforeOrAt(); + } + + /** + * Return true if end date is now or later. + * (Rather start/end are included by options is ignored.) + * + * @return bool + */ + public function isEnded(): bool + { + return $this->endsBeforeOrAt(); + } + + /** + * Return true if now is between start date (included) and end date (excluded). + * (Rather start/end are included by options is ignored.) + * + * @return bool + */ + public function isInProgress(): bool + { + return $this->isStarted() && !$this->isEnded(); + } + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return static + */ + public function roundUnit($unit, $precision = 1, $function = 'round') + { + $self = $this->copyIfImmutable(); + $self = $self->setStartDate($self->getStartDate()->roundUnit($unit, $precision, $function)); + + if ($self->endDate) { + $self = $self->setEndDate($self->getEndDate()->roundUnit($unit, $precision, $function)); + } + + return $self->setDateInterval($self->getDateInterval()->roundUnit($unit, $precision, $function)); + } + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|\DateInterval|null $precision + * + * @return static + */ + public function floorUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'floor'); + } + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|\DateInterval|null $precision + * + * @return static + */ + public function ceilUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'ceil'); + } + + /** + * Round the current instance second with given precision if specified (else period interval is used). + * + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return static + */ + public function round($precision = null, $function = 'round') + { + return $this->roundWith( + $precision ?? $this->getDateInterval()->setLocalTranslator(TranslatorImmutable::get('en'))->forHumans(), + $function + ); + } + + /** + * Round the current instance second with given precision if specified (else period interval is used). + * + * @param float|int|string|\DateInterval|null $precision + * + * @return static + */ + public function floor($precision = null) + { + return $this->round($precision, 'floor'); + } + + /** + * Ceil the current instance second with given precision if specified (else period interval is used). + * + * @param float|int|string|\DateInterval|null $precision + * + * @return static + */ + public function ceil($precision = null) + { + return $this->round($precision, 'ceil'); + } + + /** + * Specify data which should be serialized to JSON. + * + * @link https://php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return CarbonInterface[] + */ + #[ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Return true if the given date is between start and end. + * + * @param \Carbon\Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|\DateTimeInterface|string|null $date + * + * @return bool + */ + public function contains($date = null): bool + { + $startMethod = 'startsBefore'.($this->isStartIncluded() ? 'OrAt' : ''); + $endMethod = 'endsAfter'.($this->isEndIncluded() ? 'OrAt' : ''); + + return $this->$startMethod($date) && $this->$endMethod($date); + } + + /** + * Return true if the current period follows a given other period (with no overlap). + * For instance, [2019-08-01 -> 2019-08-12] follows [2019-07-29 -> 2019-07-31] + * Note than in this example, follows() would be false if 2019-08-01 or 2019-07-31 was excluded by options. + * + * @param \Carbon\CarbonPeriod|\DatePeriod|string $period + * + * @return bool + */ + public function follows($period, ...$arguments): bool + { + $period = $this->resolveCarbonPeriod($period, ...$arguments); + + return $this->getIncludedStartDate()->equalTo($period->getIncludedEndDate()->add($period->getDateInterval())); + } + + /** + * Return true if the given other period follows the current one (with no overlap). + * For instance, [2019-07-29 -> 2019-07-31] is followed by [2019-08-01 -> 2019-08-12] + * Note than in this example, isFollowedBy() would be false if 2019-08-01 or 2019-07-31 was excluded by options. + * + * @param \Carbon\CarbonPeriod|\DatePeriod|string $period + * + * @return bool + */ + public function isFollowedBy($period, ...$arguments): bool + { + $period = $this->resolveCarbonPeriod($period, ...$arguments); + + return $period->follows($this); + } + + /** + * Return true if the given period either follows or is followed by the current one. + * + * @see follows() + * @see isFollowedBy() + * + * @param \Carbon\CarbonPeriod|\DatePeriod|string $period + * + * @return bool + */ + public function isConsecutiveWith($period, ...$arguments): bool + { + return $this->follows($period, ...$arguments) || $this->isFollowedBy($period, ...$arguments); + } + + /** + * Update properties after removing built-in filters. + * + * @return void + */ + protected function updateInternalState() + { + if (!$this->hasFilter(static::END_DATE_FILTER)) { + $this->endDate = null; + } + + if (!$this->hasFilter(static::RECURRENCES_FILTER)) { + $this->recurrences = null; + } + } + + /** + * Create a filter tuple from raw parameters. + * + * Will create an automatic filter callback for one of Carbon's is* methods. + * + * @param array $parameters + * + * @return array + */ + protected function createFilterTuple(array $parameters) + { + $method = array_shift($parameters); + + if (!$this->isCarbonPredicateMethod($method)) { + return [$method, array_shift($parameters)]; + } + + return [function ($date) use ($method, $parameters) { + return ([$date, $method])(...$parameters); + }, $method]; + } + + /** + * Return whether given callable is a string pointing to one of Carbon's is* methods + * and should be automatically converted to a filter callback. + * + * @param callable $callable + * + * @return bool + */ + protected function isCarbonPredicateMethod($callable) + { + return \is_string($callable) && str_starts_with($callable, 'is') && + (method_exists($this->dateClass, $callable) || ([$this->dateClass, 'hasMacro'])($callable)); + } + + /** + * Recurrences filter callback (limits number of recurrences). + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param \Carbon\Carbon $current + * @param int $key + * + * @return bool|string + */ + protected function filterRecurrences($current, $key) + { + if ($key < $this->recurrences) { + return true; + } + + return static::END_ITERATION; + } + + /** + * End date filter callback. + * + * @param \Carbon\Carbon $current + * + * @return bool|string + */ + protected function filterEndDate($current) + { + if (!$this->isEndExcluded() && $current == $this->endDate) { + return true; + } + + if ($this->dateInterval->invert ? $current > $this->endDate : $current < $this->endDate) { + return true; + } + + return static::END_ITERATION; + } + + /** + * End iteration filter callback. + * + * @return string + */ + protected function endIteration() + { + return static::END_ITERATION; + } + + /** + * Handle change of the parameters. + */ + protected function handleChangedParameters() + { + if (($this->getOptions() & static::IMMUTABLE) && $this->dateClass === Carbon::class) { + $this->dateClass = CarbonImmutable::class; + } elseif (!($this->getOptions() & static::IMMUTABLE) && $this->dateClass === CarbonImmutable::class) { + $this->dateClass = Carbon::class; + } + + $this->validationResult = null; + } + + /** + * Validate current date and stop iteration when necessary. + * + * Returns true when current date is valid, false if it is not, or static::END_ITERATION + * when iteration should be stopped. + * + * @return bool|string + */ + protected function validateCurrentDate() + { + if ($this->current === null) { + $this->rewind(); + } + + // Check after the first rewind to avoid repeating the initial validation. + return $this->validationResult ?? ($this->validationResult = $this->checkFilters()); + } + + /** + * Check whether current value and key pass all the filters. + * + * @return bool|string + */ + protected function checkFilters() + { + $current = $this->prepareForReturn($this->current); + + foreach ($this->filters as $tuple) { + $result = \call_user_func( + $tuple[0], + $current->avoidMutation(), + $this->key, + $this + ); + + if ($result === static::END_ITERATION) { + return static::END_ITERATION; + } + + if (!$result) { + return false; + } + } + + return true; + } + + /** + * Prepare given date to be returned to the external logic. + * + * @param CarbonInterface $date + * + * @return CarbonInterface + */ + protected function prepareForReturn(CarbonInterface $date) + { + $date = ([$this->dateClass, 'make'])($date); + + if ($this->timezone) { + $date = $date->setTimezone($this->timezone); + } + + return $date; + } + + /** + * Keep incrementing the current date until a valid date is found or the iteration is ended. + * + * @throws RuntimeException + * + * @return void + */ + protected function incrementCurrentDateUntilValid() + { + $attempts = 0; + + do { + $this->current = $this->current->add($this->dateInterval); + + $this->validationResult = null; + + if (++$attempts > static::NEXT_MAX_ATTEMPTS) { + throw new UnreachableException('Could not find next valid date.'); + } + } while ($this->validateCurrentDate() === false); + } + + /** + * Call given macro. + * + * @param string $name + * @param array $parameters + * + * @return mixed + */ + protected function callMacro($name, $parameters) + { + $macro = static::$macros[$name]; + + if ($macro instanceof Closure) { + $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + } + + /** + * Return the Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param \Carbon\Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|\DateTimeInterface|string|null $date + * + * @return \Carbon\CarbonInterface + */ + protected function resolveCarbon($date = null) + { + return $this->getStartDate()->nowWithSameTz()->carbonize($date); + } + + /** + * Resolve passed arguments or DatePeriod to a CarbonPeriod object. + * + * @param mixed $period + * @param mixed ...$arguments + * + * @return static + */ + protected function resolveCarbonPeriod($period, ...$arguments) + { + if ($period instanceof self) { + return $period; + } + + return $period instanceof DatePeriod + ? static::instance($period) + : static::create($period, ...$arguments); + } + + private function orderCouple($first, $second): array + { + return $first > $second ? [$second, $first] : [$first, $second]; + } + + private function makeDateTime($value): ?DateTimeInterface + { + if ($value instanceof DateTimeInterface) { + return $value; + } + + if (\is_string($value)) { + $value = trim($value); + + if (!preg_match('/^P[\dT]/', $value) && + !preg_match('/^R\d/', $value) && + preg_match('/[a-z\d]/i', $value) + ) { + $dateClass = $this->dateClass; + + return $dateClass::parse($value, $this->tzName); + } + } + + return null; + } + + private function isInfiniteDate($date): bool + { + return $date instanceof CarbonInterface && ($date->isEndOfTime() || $date->isStartOfTime()); + } + + private function rawDate($date): ?DateTimeInterface + { + if ($date === false || $date === null) { + return null; + } + + if ($date instanceof CarbonInterface) { + return $date->isMutable() + ? $date->toDateTime() + : $date->toDateTimeImmutable(); + } + + if (\in_array(\get_class($date), [DateTime::class, DateTimeImmutable::class], true)) { + return $date; + } + + $class = $date instanceof DateTime ? DateTime::class : DateTimeImmutable::class; + + return new $class($date->format('Y-m-d H:i:s.u'), $date->getTimezone()); + } + + private static function setDefaultParameters(array &$parameters, array $defaults): void + { + foreach ($defaults as [$index, $name, $value]) { + if (!\array_key_exists($index, $parameters) && !\array_key_exists($name, $parameters)) { + $parameters[$index] = $value; + } + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonPeriodImmutable.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonPeriodImmutable.php new file mode 100644 index 0000000000..f0d0ee2811 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonPeriodImmutable.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +class CarbonPeriodImmutable extends CarbonPeriod +{ + /** + * Default date class of iteration items. + * + * @var string + */ + protected const DEFAULT_DATE_CLASS = CarbonImmutable::class; + + /** + * Date class of iteration items. + * + * @var string + */ + protected $dateClass = CarbonImmutable::class; + + /** + * Prepare the instance to be set (self if mutable to be mutated, + * copy if immutable to generate a new instance). + * + * @return static + */ + protected function copyIfImmutable() + { + return $this->constructed ? clone $this : $this; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonTimeZone.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonTimeZone.php new file mode 100644 index 0000000000..c81899f1ea --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonTimeZone.php @@ -0,0 +1,320 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\InvalidCastException; +use Carbon\Exceptions\InvalidTimeZoneException; +use DateTimeInterface; +use DateTimeZone; +use Throwable; + +class CarbonTimeZone extends DateTimeZone +{ + public function __construct($timezone = null) + { + parent::__construct(static::getDateTimeZoneNameFromMixed($timezone)); + } + + protected static function parseNumericTimezone($timezone) + { + if ($timezone <= -100 || $timezone >= 100) { + throw new InvalidTimeZoneException('Absolute timezone offset cannot be greater than 100.'); + } + + return ($timezone >= 0 ? '+' : '').ltrim($timezone, '+').':00'; + } + + protected static function getDateTimeZoneNameFromMixed($timezone) + { + if ($timezone === null) { + return date_default_timezone_get(); + } + + if (\is_string($timezone)) { + $timezone = preg_replace('/^\s*([+-]\d+)(\d{2})\s*$/', '$1:$2', $timezone); + } + + if (is_numeric($timezone)) { + return static::parseNumericTimezone($timezone); + } + + return $timezone; + } + + protected static function getDateTimeZoneFromName(&$name) + { + return @timezone_open($name = (string) static::getDateTimeZoneNameFromMixed($name)); + } + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateTimeZone + */ + public function cast(string $className) + { + if (!method_exists($className, 'instance')) { + if (is_a($className, DateTimeZone::class, true)) { + return new $className($this->getName()); + } + + throw new InvalidCastException("$className has not the instance() method needed to cast the date."); + } + + return $className::instance($this); + } + + /** + * Create a CarbonTimeZone from mixed input. + * + * @param DateTimeZone|string|int|null $object original value to get CarbonTimeZone from it. + * @param DateTimeZone|string|int|null $objectDump dump of the object for error messages. + * + * @throws InvalidTimeZoneException + * + * @return false|static + */ + public static function instance($object = null, $objectDump = null) + { + $tz = $object; + + if ($tz instanceof static) { + return $tz; + } + + if ($tz === null) { + return new static(); + } + + if (!$tz instanceof DateTimeZone) { + $tz = static::getDateTimeZoneFromName($object); + } + + if ($tz !== false) { + return new static($tz->getName()); + } + + if (Carbon::isStrictModeEnabled()) { + throw new InvalidTimeZoneException('Unknown or bad timezone ('.($objectDump ?: $object).')'); + } + + return false; + } + + /** + * Returns abbreviated name of the current timezone according to DST setting. + * + * @param bool $dst + * + * @return string + */ + public function getAbbreviatedName($dst = false) + { + $name = $this->getName(); + + foreach ($this->listAbbreviations() as $abbreviation => $zones) { + foreach ($zones as $zone) { + if ($zone['timezone_id'] === $name && $zone['dst'] == $dst) { + return $abbreviation; + } + } + } + + return 'unknown'; + } + + /** + * @alias getAbbreviatedName + * + * Returns abbreviated name of the current timezone according to DST setting. + * + * @param bool $dst + * + * @return string + */ + public function getAbbr($dst = false) + { + return $this->getAbbreviatedName($dst); + } + + /** + * Get the offset as string "sHH:MM" (such as "+00:00" or "-12:30"). + * + * @param DateTimeInterface|null $date + * + * @return string + */ + public function toOffsetName(DateTimeInterface $date = null) + { + return static::getOffsetNameFromMinuteOffset( + $this->getOffset($date ?: Carbon::now($this)) / 60 + ); + } + + /** + * Returns a new CarbonTimeZone object using the offset string instead of region string. + * + * @param DateTimeInterface|null $date + * + * @return CarbonTimeZone + */ + public function toOffsetTimeZone(DateTimeInterface $date = null) + { + return new static($this->toOffsetName($date)); + } + + /** + * Returns the first region string (such as "America/Toronto") that matches the current timezone or + * false if no match is found. + * + * @see timezone_name_from_abbr native PHP function. + * + * @param DateTimeInterface|null $date + * @param int $isDst + * + * @return string|false + */ + public function toRegionName(DateTimeInterface $date = null, $isDst = 1) + { + $name = $this->getName(); + $firstChar = substr($name, 0, 1); + + if ($firstChar !== '+' && $firstChar !== '-') { + return $name; + } + + $date = $date ?: Carbon::now($this); + + // Integer construction no longer supported since PHP 8 + // @codeCoverageIgnoreStart + try { + $offset = @$this->getOffset($date) ?: 0; + } catch (Throwable $e) { + $offset = 0; + } + // @codeCoverageIgnoreEnd + + $name = @timezone_name_from_abbr('', $offset, $isDst); + + if ($name) { + return $name; + } + + foreach (timezone_identifiers_list() as $timezone) { + if (Carbon::instance($date)->tz($timezone)->getOffset() === $offset) { + return $timezone; + } + } + + return false; + } + + /** + * Returns a new CarbonTimeZone object using the region string instead of offset string. + * + * @param DateTimeInterface|null $date + * + * @return CarbonTimeZone|false + */ + public function toRegionTimeZone(DateTimeInterface $date = null) + { + $tz = $this->toRegionName($date); + + if ($tz !== false) { + return new static($tz); + } + + if (Carbon::isStrictModeEnabled()) { + throw new InvalidTimeZoneException('Unknown timezone for offset '.$this->getOffset($date ?: Carbon::now($this)).' seconds.'); + } + + return false; + } + + /** + * Cast to string (get timezone name). + * + * @return string + */ + public function __toString() + { + return $this->getName(); + } + + /** + * Return the type number: + * + * Type 1; A UTC offset, such as -0300 + * Type 2; A timezone abbreviation, such as GMT + * Type 3: A timezone identifier, such as Europe/London + */ + public function getType(): int + { + return preg_match('/"timezone_type";i:(\d)/', serialize($this), $match) ? (int) $match[1] : 3; + } + + /** + * Create a CarbonTimeZone from mixed input. + * + * @param DateTimeZone|string|int|null $object + * + * @return false|static + */ + public static function create($object = null) + { + return static::instance($object); + } + + /** + * Create a CarbonTimeZone from int/float hour offset. + * + * @param float $hourOffset number of hour of the timezone shift (can be decimal). + * + * @return false|static + */ + public static function createFromHourOffset(float $hourOffset) + { + return static::createFromMinuteOffset($hourOffset * Carbon::MINUTES_PER_HOUR); + } + + /** + * Create a CarbonTimeZone from int/float minute offset. + * + * @param float $minuteOffset number of total minutes of the timezone shift. + * + * @return false|static + */ + public static function createFromMinuteOffset(float $minuteOffset) + { + return static::instance(static::getOffsetNameFromMinuteOffset($minuteOffset)); + } + + /** + * Convert a total minutes offset into a standardized timezone offset string. + * + * @param float $minutes number of total minutes of the timezone shift. + * + * @return string + */ + public static function getOffsetNameFromMinuteOffset(float $minutes): string + { + $minutes = round($minutes); + $unsignedMinutes = abs($minutes); + + return ($minutes < 0 ? '-' : '+'). + str_pad((string) floor($unsignedMinutes / 60), 2, '0', STR_PAD_LEFT). + ':'. + str_pad((string) ($unsignedMinutes % 60), 2, '0', STR_PAD_LEFT); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Cli/Invoker.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Cli/Invoker.php new file mode 100644 index 0000000000..4f35d6c618 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Cli/Invoker.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Cli; + +class Invoker +{ + public const CLI_CLASS_NAME = 'Carbon\\Cli'; + + protected function runWithCli(string $className, array $parameters): bool + { + $cli = new $className(); + + return $cli(...$parameters); + } + + public function __invoke(...$parameters): bool + { + if (class_exists(self::CLI_CLASS_NAME)) { + return $this->runWithCli(self::CLI_CLASS_NAME, $parameters); + } + + $function = (($parameters[1] ?? '') === 'install' ? ($parameters[2] ?? null) : null) ?: 'shell_exec'; + $function('composer require carbon-cli/carbon-cli --no-interaction'); + + echo 'Installation succeeded.'; + + return true; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadComparisonUnitException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadComparisonUnitException.php new file mode 100644 index 0000000000..3ca8837d18 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadComparisonUnitException.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Throwable; + +class BadComparisonUnitException extends UnitException +{ + /** + * The unit. + * + * @var string + */ + protected $unit; + + /** + * Constructor. + * + * @param string $unit + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($unit, $code = 0, Throwable $previous = null) + { + $this->unit = $unit; + + parent::__construct("Bad comparison unit: '$unit'", $code, $previous); + } + + /** + * Get the unit. + * + * @return string + */ + public function getUnit(): string + { + return $this->unit; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentConstructorException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentConstructorException.php new file mode 100644 index 0000000000..2e222e54e9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentConstructorException.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use BadMethodCallException as BaseBadMethodCallException; +use Throwable; + +class BadFluentConstructorException extends BaseBadMethodCallException implements BadMethodCallException +{ + /** + * The method. + * + * @var string + */ + protected $method; + + /** + * Constructor. + * + * @param string $method + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($method, $code = 0, Throwable $previous = null) + { + $this->method = $method; + + parent::__construct(sprintf("Unknown fluent constructor '%s'.", $method), $code, $previous); + } + + /** + * Get the method. + * + * @return string + */ + public function getMethod(): string + { + return $this->method; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentSetterException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentSetterException.php new file mode 100644 index 0000000000..4ceaa2ef06 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentSetterException.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use BadMethodCallException as BaseBadMethodCallException; +use Throwable; + +class BadFluentSetterException extends BaseBadMethodCallException implements BadMethodCallException +{ + /** + * The setter. + * + * @var string + */ + protected $setter; + + /** + * Constructor. + * + * @param string $setter + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($setter, $code = 0, Throwable $previous = null) + { + $this->setter = $setter; + + parent::__construct(sprintf("Unknown fluent setter '%s'", $setter), $code, $previous); + } + + /** + * Get the setter. + * + * @return string + */ + public function getSetter(): string + { + return $this->setter; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadMethodCallException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadMethodCallException.php new file mode 100644 index 0000000000..108206d3eb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadMethodCallException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +interface BadMethodCallException extends Exception +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/EndLessPeriodException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/EndLessPeriodException.php new file mode 100644 index 0000000000..e10492693c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/EndLessPeriodException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use RuntimeException as BaseRuntimeException; + +final class EndLessPeriodException extends BaseRuntimeException implements RuntimeException +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/Exception.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/Exception.php new file mode 100644 index 0000000000..8ad747e75f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/Exception.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +interface Exception +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/ImmutableException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/ImmutableException.php new file mode 100644 index 0000000000..db334c6c9c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/ImmutableException.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use RuntimeException as BaseRuntimeException; +use Throwable; + +class ImmutableException extends BaseRuntimeException implements RuntimeException +{ + /** + * The value. + * + * @var string + */ + protected $value; + + /** + * Constructor. + * + * @param string $value the immutable type/value + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($value, $code = 0, Throwable $previous = null) + { + $this->value = $value; + parent::__construct("$value is immutable.", $code, $previous); + } + + /** + * Get the value. + * + * @return string + */ + public function getValue(): string + { + return $this->value; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidArgumentException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidArgumentException.php new file mode 100644 index 0000000000..5b013cd507 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidArgumentException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +interface InvalidArgumentException extends Exception +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidCastException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidCastException.php new file mode 100644 index 0000000000..a421401f6d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidCastException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidCastException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php new file mode 100644 index 0000000000..c9ecb6b060 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class InvalidDateException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The invalid field. + * + * @var string + */ + private $field; + + /** + * The invalid value. + * + * @var mixed + */ + private $value; + + /** + * Constructor. + * + * @param string $field + * @param mixed $value + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($field, $value, $code = 0, Throwable $previous = null) + { + $this->field = $field; + $this->value = $value; + parent::__construct($field.' : '.$value.' is not a valid value.', $code, $previous); + } + + /** + * Get the invalid field. + * + * @return string + */ + public function getField() + { + return $this->field; + } + + /** + * Get the invalid value. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidFormatException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidFormatException.php new file mode 100644 index 0000000000..92d55fe353 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidFormatException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidFormatException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidIntervalException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidIntervalException.php new file mode 100644 index 0000000000..69cf4128a9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidIntervalException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidIntervalException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodDateException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodDateException.php new file mode 100644 index 0000000000..9bd84a96d4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodDateException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidPeriodDateException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodParameterException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodParameterException.php new file mode 100644 index 0000000000..cf2c902409 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodParameterException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidPeriodParameterException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTimeZoneException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTimeZoneException.php new file mode 100644 index 0000000000..f72595583c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTimeZoneException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidTimeZoneException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTypeException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTypeException.php new file mode 100644 index 0000000000..2c8ec9ba04 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTypeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidTypeException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/NotACarbonClassException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/NotACarbonClassException.php new file mode 100644 index 0000000000..7a87632c41 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/NotACarbonClassException.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Carbon\CarbonInterface; +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class NotACarbonClassException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The className. + * + * @var string + */ + protected $className; + + /** + * Constructor. + * + * @param string $className + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($className, $code = 0, Throwable $previous = null) + { + $this->className = $className; + + parent::__construct(sprintf('Given class does not implement %s: %s', CarbonInterface::class, $className), $code, $previous); + } + + /** + * Get the className. + * + * @return string + */ + public function getClassName(): string + { + return $this->className; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/NotAPeriodException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/NotAPeriodException.php new file mode 100644 index 0000000000..4edd7a4841 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/NotAPeriodException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class NotAPeriodException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/NotLocaleAwareException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/NotLocaleAwareException.php new file mode 100644 index 0000000000..f2c546843b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/NotLocaleAwareException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class NotLocaleAwareException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param mixed $object + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($object, $code = 0, Throwable $previous = null) + { + $dump = \is_object($object) ? \get_class($object) : \gettype($object); + + parent::__construct("$dump does neither implements Symfony\Contracts\Translation\LocaleAwareInterface nor getLocale() method.", $code, $previous); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/OutOfRangeException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/OutOfRangeException.php new file mode 100644 index 0000000000..2c586d0b76 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/OutOfRangeException.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +// This will extends OutOfRangeException instead of InvalidArgumentException since 3.0.0 +// use OutOfRangeException as BaseOutOfRangeException; + +class OutOfRangeException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The unit or name of the value. + * + * @var string + */ + private $unit; + + /** + * The range minimum. + * + * @var mixed + */ + private $min; + + /** + * The range maximum. + * + * @var mixed + */ + private $max; + + /** + * The invalid value. + * + * @var mixed + */ + private $value; + + /** + * Constructor. + * + * @param string $unit + * @param mixed $min + * @param mixed $max + * @param mixed $value + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($unit, $min, $max, $value, $code = 0, Throwable $previous = null) + { + $this->unit = $unit; + $this->min = $min; + $this->max = $max; + $this->value = $value; + + parent::__construct("$unit must be between $min and $max, $value given", $code, $previous); + } + + /** + * @return mixed + */ + public function getMax() + { + return $this->max; + } + + /** + * @return mixed + */ + public function getMin() + { + return $this->min; + } + + /** + * @return mixed + */ + public function getUnit() + { + return $this->unit; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/ParseErrorException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/ParseErrorException.php new file mode 100644 index 0000000000..5416fd1491 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/ParseErrorException.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class ParseErrorException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The expected. + * + * @var string + */ + protected $expected; + + /** + * The actual. + * + * @var string + */ + protected $actual; + + /** + * The help message. + * + * @var string + */ + protected $help; + + /** + * Constructor. + * + * @param string $expected + * @param string $actual + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($expected, $actual, $help = '', $code = 0, Throwable $previous = null) + { + $this->expected = $expected; + $this->actual = $actual; + $this->help = $help; + + $actual = $actual === '' ? 'data is missing' : "get '$actual'"; + + parent::__construct(trim("Format expected $expected but $actual\n$help"), $code, $previous); + } + + /** + * Get the expected. + * + * @return string + */ + public function getExpected(): string + { + return $this->expected; + } + + /** + * Get the actual. + * + * @return string + */ + public function getActual(): string + { + return $this->actual; + } + + /** + * Get the help message. + * + * @return string + */ + public function getHelp(): string + { + return $this->help; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/RuntimeException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/RuntimeException.php new file mode 100644 index 0000000000..ad196f79d5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/RuntimeException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +interface RuntimeException extends Exception +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitException.php new file mode 100644 index 0000000000..ee99953b4d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; + +class UnitException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitNotConfiguredException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitNotConfiguredException.php new file mode 100644 index 0000000000..0e7230563b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitNotConfiguredException.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Throwable; + +class UnitNotConfiguredException extends UnitException +{ + /** + * The unit. + * + * @var string + */ + protected $unit; + + /** + * Constructor. + * + * @param string $unit + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($unit, $code = 0, Throwable $previous = null) + { + $this->unit = $unit; + + parent::__construct("Unit $unit have no configuration to get total from other units.", $code, $previous); + } + + /** + * Get the unit. + * + * @return string + */ + public function getUnit(): string + { + return $this->unit; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownGetterException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownGetterException.php new file mode 100644 index 0000000000..5c504975aa --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownGetterException.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class UnknownGetterException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The getter. + * + * @var string + */ + protected $getter; + + /** + * Constructor. + * + * @param string $getter getter name + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($getter, $code = 0, Throwable $previous = null) + { + $this->getter = $getter; + + parent::__construct("Unknown getter '$getter'", $code, $previous); + } + + /** + * Get the getter. + * + * @return string + */ + public function getGetter(): string + { + return $this->getter; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownMethodException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownMethodException.php new file mode 100644 index 0000000000..75273a706e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownMethodException.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use BadMethodCallException as BaseBadMethodCallException; +use Throwable; + +class UnknownMethodException extends BaseBadMethodCallException implements BadMethodCallException +{ + /** + * The method. + * + * @var string + */ + protected $method; + + /** + * Constructor. + * + * @param string $method + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($method, $code = 0, Throwable $previous = null) + { + $this->method = $method; + + parent::__construct("Method $method does not exist.", $code, $previous); + } + + /** + * Get the method. + * + * @return string + */ + public function getMethod(): string + { + return $this->method; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownSetterException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownSetterException.php new file mode 100644 index 0000000000..a795f5d725 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownSetterException.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use InvalidArgumentException as BaseInvalidArgumentException; +use Throwable; + +class UnknownSetterException extends BaseInvalidArgumentException implements BadMethodCallException +{ + /** + * The setter. + * + * @var string + */ + protected $setter; + + /** + * Constructor. + * + * @param string $setter setter name + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($setter, $code = 0, Throwable $previous = null) + { + $this->setter = $setter; + + parent::__construct("Unknown setter '$setter'", $code, $previous); + } + + /** + * Get the setter. + * + * @return string + */ + public function getSetter(): string + { + return $this->setter; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownUnitException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownUnitException.php new file mode 100644 index 0000000000..ecd7f7a592 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownUnitException.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Throwable; + +class UnknownUnitException extends UnitException +{ + /** + * The unit. + * + * @var string + */ + protected $unit; + + /** + * Constructor. + * + * @param string $unit + * @param int $code + * @param Throwable|null $previous + */ + public function __construct($unit, $code = 0, Throwable $previous = null) + { + $this->unit = $unit; + + parent::__construct("Unknown unit '$unit'.", $code, $previous); + } + + /** + * Get the unit. + * + * @return string + */ + public function getUnit(): string + { + return $this->unit; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnreachableException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnreachableException.php new file mode 100644 index 0000000000..1654ab11b5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnreachableException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use RuntimeException as BaseRuntimeException; + +class UnreachableException extends BaseRuntimeException implements RuntimeException +{ + // +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Factory.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Factory.php new file mode 100644 index 0000000000..d497535f77 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Factory.php @@ -0,0 +1,326 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Closure; +use DateTimeInterface; +use ReflectionMethod; + +/** + * A factory to generate Carbon instances with common settings. + * + * + * + * @method bool canBeCreatedFromFormat($date, $format) Checks if the (date)time string is in a given format and valid to create a + * new instance. + * @method Carbon|false create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) Create a new Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * @method Carbon createFromDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to now. + * @method Carbon|false createFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. + * @method Carbon|false createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * @method Carbon|false createFromLocaleFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific format and a string in a given language. + * @method Carbon|false createFromLocaleIsoFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific ISO format and a string in a given language. + * @method Carbon createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) Create a Carbon instance from just a time. The date portion is set to today. + * @method Carbon createFromTimeString($time, $tz = null) Create a Carbon instance from a time string. The date portion is set to today. + * @method Carbon createFromTimestamp($timestamp, $tz = null) Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createFromTimestampMs($timestamp, $tz = null) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createFromTimestampMsUTC($timestamp) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createFromTimestampUTC($timestamp) Create a Carbon instance from an timestamp keeping the timezone to UTC. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createMidnightDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to midnight. + * @method Carbon|false createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) Create a new safe Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * If one of the set values is not valid, an InvalidDateException + * will be thrown. + * @method CarbonInterface createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null) Create a new Carbon instance from a specific date and time using strict validation. + * @method Carbon disableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method Carbon enableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method mixed executeWithLocale($locale, $func) Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * @method Carbon fromSerialized($value) Create an instance from a serialized string. + * @method void genericMacro($macro, $priority = 0) Register a custom macro. + * @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * @method Language[] getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * @method array getDays() Get the days of the week + * @method string|null getFallbackLocale() Get the fallback locale. + * @method array getFormatsToIsoReplacements() List of replacements from date() format to isoFormat(). + * @method int getHumanDiffOptions() Return default humanDiff() options (merged flags as integer). + * @method array getIsoUnits() Returns list of locale units for ISO formatting. + * @method array getLastErrors() {@inheritdoc} + * @method string getLocale() Get the current translator locale. + * @method callable|null getMacro($name) Get the raw callable macro registered globally for a given name. + * @method int getMidDayAt() get midday/noon hour + * @method Closure|Carbon getTestNow() Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * @method string getTimeFormatByPrecision($unitPrecision) Return a format from H:i to H:i:s.u according to given unit precision. + * @method string getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) Returns raw translation message for a given key. + * @method \Symfony\Component\Translation\TranslatorInterface getTranslator() Get the default translator instance in use. + * @method int getWeekEndsAt() Get the last day of week + * @method int getWeekStartsAt() Get the first day of week + * @method array getWeekendDays() Get weekend days + * @method bool hasFormat($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasFormatWithModifiers($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasMacro($name) Checks if macro is registered globally. + * @method bool hasRelativeKeywords($time) Determine if a time string will produce a relative date. + * @method bool hasTestNow() Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * @method Carbon instance($date) Create a Carbon instance from a DateTime one. + * @method bool isImmutable() Returns true if the current class/instance is immutable. + * @method bool isModifiableUnit($unit) Returns true if a property can be changed via setter. + * @method bool isMutable() Returns true if the current class/instance is mutable. + * @method bool isStrictModeEnabled() Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * @method bool localeHasDiffOneDayWords($locale) Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * @method bool localeHasDiffSyntax($locale) Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasDiffTwoDayWords($locale) Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * @method bool localeHasPeriodSyntax($locale) Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasShortUnits($locale) Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * @method void macro($name, $macro) Register a custom macro. + * @method Carbon|null make($var) Make a Carbon instance from given variable if possible. + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * @method Carbon maxValue() Create a Carbon instance for the greatest supported date. + * @method Carbon minValue() Create a Carbon instance for the lowest supported date. + * @method void mixin($mixin) Mix another object into the class. + * @method Carbon now($tz = null) Get a Carbon instance for the current date and time. + * @method Carbon parse($time = null, $tz = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method Carbon parseFromLocale($time, $locale = null, $tz = null) Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * @method string pluralUnit(string $unit) Returns standardized plural of a given singular/plural unit name (in English). + * @method Carbon|false rawCreateFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. + * @method Carbon rawParse($time = null, $tz = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method Carbon resetMacros() Remove all macros and generic macros. + * @method void resetMonthsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void resetToStringFormat() Reset the format used to the default when type juggling a Carbon instance to a string + * @method void resetYearsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void serializeUsing($callback) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform Carbon object before the serialization. + * JSON serialize all Carbon instances using the given callback. + * @method Carbon setFallbackLocale($locale) Set the fallback locale. + * @method Carbon setHumanDiffOptions($humanDiffOptions) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method bool setLocale($locale) Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * @method void setMidDayAt($hour) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * Set midday/noon hour + * @method Carbon setTestNow($testNow = null) Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method Carbon setTestNowAndTimezone($testNow = null, $tz = null) Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method void setToStringFormat($format) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump another string + * format. + * Set the default format used when type juggling a Carbon instance to a string. + * @method void setTranslator(TranslatorInterface $translator) Set the default translator instance to use. + * @method Carbon setUtf8($utf8) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * Set if UTF8 will be used for localized date/time. + * @method void setWeekEndsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * Set the last day of week + * @method void setWeekStartsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * Set the first day of week + * @method void setWeekendDays($days) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * Set weekend days + * @method bool shouldOverflowMonths() Get the month overflow global behavior (can be overridden in specific instances). + * @method bool shouldOverflowYears() Get the month overflow global behavior (can be overridden in specific instances). + * @method string singularUnit(string $unit) Returns standardized singular of a given singular/plural unit name (in English). + * @method Carbon today($tz = null) Create a Carbon instance for today. + * @method Carbon tomorrow($tz = null) Create a Carbon instance for tomorrow. + * @method string translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) Translate a time string from a locale to an other. + * @method string translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null) Translate using translation string or callback available. + * @method void useMonthsOverflow($monthsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method Carbon useStrictMode($strictModeEnabled = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method void useYearsOverflow($yearsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method mixed withTestNow($testNow, $callback) Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * /!\ Use this method for unit tests only. + * @method Carbon yesterday($tz = null) Create a Carbon instance for yesterday. + * + * + */ +class Factory +{ + protected $className = Carbon::class; + + protected $settings = []; + + public function __construct(array $settings = [], ?string $className = null) + { + if ($className) { + $this->className = $className; + } + + $this->settings = $settings; + } + + public function getClassName() + { + return $this->className; + } + + public function setClassName(string $className) + { + $this->className = $className; + + return $this; + } + + public function className(string $className = null) + { + return $className === null ? $this->getClassName() : $this->setClassName($className); + } + + public function getSettings() + { + return $this->settings; + } + + public function setSettings(array $settings) + { + $this->settings = $settings; + + return $this; + } + + public function settings(array $settings = null) + { + return $settings === null ? $this->getSettings() : $this->setSettings($settings); + } + + public function mergeSettings(array $settings) + { + $this->settings = array_merge($this->settings, $settings); + + return $this; + } + + public function __call($name, $arguments) + { + $method = new ReflectionMethod($this->className, $name); + $settings = $this->settings; + + if ($settings && isset($settings['timezone'])) { + $tzParameters = array_filter($method->getParameters(), function ($parameter) { + return \in_array($parameter->getName(), ['tz', 'timezone'], true); + }); + + if (isset($arguments[0]) && \in_array($name, ['instance', 'make', 'create', 'parse'], true)) { + if ($arguments[0] instanceof DateTimeInterface) { + $settings['innerTimezone'] = $settings['timezone']; + } elseif (\is_string($arguments[0]) && date_parse($arguments[0])['is_localtime']) { + unset($settings['timezone'], $settings['innerTimezone']); + } + } elseif (\count($tzParameters)) { + array_splice($arguments, key($tzParameters), 0, [$settings['timezone']]); + unset($settings['timezone']); + } + } + + $result = $this->className::$name(...$arguments); + + return $result instanceof CarbonInterface && !empty($settings) + ? $result->settings($settings) + : $result; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/FactoryImmutable.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/FactoryImmutable.php new file mode 100644 index 0000000000..d88a1cf67f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/FactoryImmutable.php @@ -0,0 +1,259 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Closure; +use DateTimeImmutable; +use DateTimeZone; +use Psr\Clock\ClockInterface; + +/** + * A factory to generate CarbonImmutable instances with common settings. + * + * + * + * @method bool canBeCreatedFromFormat($date, $format) Checks if the (date)time string is in a given format and valid to create a + * new instance. + * @method CarbonImmutable|false create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) Create a new Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * @method CarbonImmutable createFromDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to now. + * @method CarbonImmutable|false createFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. + * @method CarbonImmutable|false createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * @method CarbonImmutable|false createFromLocaleFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific format and a string in a given language. + * @method CarbonImmutable|false createFromLocaleIsoFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific ISO format and a string in a given language. + * @method CarbonImmutable createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) Create a Carbon instance from just a time. The date portion is set to today. + * @method CarbonImmutable createFromTimeString($time, $tz = null) Create a Carbon instance from a time string. The date portion is set to today. + * @method CarbonImmutable createFromTimestamp($timestamp, $tz = null) Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampMs($timestamp, $tz = null) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampMsUTC($timestamp) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampUTC($timestamp) Create a Carbon instance from an timestamp keeping the timezone to UTC. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createMidnightDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to midnight. + * @method CarbonImmutable|false createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) Create a new safe Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * If one of the set values is not valid, an InvalidDateException + * will be thrown. + * @method CarbonInterface createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null) Create a new Carbon instance from a specific date and time using strict validation. + * @method CarbonImmutable disableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method CarbonImmutable enableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method mixed executeWithLocale($locale, $func) Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * @method CarbonImmutable fromSerialized($value) Create an instance from a serialized string. + * @method void genericMacro($macro, $priority = 0) Register a custom macro. + * @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * @method Language[] getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * @method array getDays() Get the days of the week + * @method string|null getFallbackLocale() Get the fallback locale. + * @method array getFormatsToIsoReplacements() List of replacements from date() format to isoFormat(). + * @method int getHumanDiffOptions() Return default humanDiff() options (merged flags as integer). + * @method array getIsoUnits() Returns list of locale units for ISO formatting. + * @method array getLastErrors() {@inheritdoc} + * @method string getLocale() Get the current translator locale. + * @method callable|null getMacro($name) Get the raw callable macro registered globally for a given name. + * @method int getMidDayAt() get midday/noon hour + * @method Closure|CarbonImmutable getTestNow() Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * @method string getTimeFormatByPrecision($unitPrecision) Return a format from H:i to H:i:s.u according to given unit precision. + * @method string getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) Returns raw translation message for a given key. + * @method \Symfony\Component\Translation\TranslatorInterface getTranslator() Get the default translator instance in use. + * @method int getWeekEndsAt() Get the last day of week + * @method int getWeekStartsAt() Get the first day of week + * @method array getWeekendDays() Get weekend days + * @method bool hasFormat($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasFormatWithModifiers($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasMacro($name) Checks if macro is registered globally. + * @method bool hasRelativeKeywords($time) Determine if a time string will produce a relative date. + * @method bool hasTestNow() Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * @method CarbonImmutable instance($date) Create a Carbon instance from a DateTime one. + * @method bool isImmutable() Returns true if the current class/instance is immutable. + * @method bool isModifiableUnit($unit) Returns true if a property can be changed via setter. + * @method bool isMutable() Returns true if the current class/instance is mutable. + * @method bool isStrictModeEnabled() Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * @method bool localeHasDiffOneDayWords($locale) Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * @method bool localeHasDiffSyntax($locale) Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasDiffTwoDayWords($locale) Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * @method bool localeHasPeriodSyntax($locale) Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasShortUnits($locale) Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * @method void macro($name, $macro) Register a custom macro. + * @method CarbonImmutable|null make($var) Make a Carbon instance from given variable if possible. + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * @method CarbonImmutable maxValue() Create a Carbon instance for the greatest supported date. + * @method CarbonImmutable minValue() Create a Carbon instance for the lowest supported date. + * @method void mixin($mixin) Mix another object into the class. + * @method CarbonImmutable parse($time = null, $tz = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method CarbonImmutable parseFromLocale($time, $locale = null, $tz = null) Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * @method string pluralUnit(string $unit) Returns standardized plural of a given singular/plural unit name (in English). + * @method CarbonImmutable|false rawCreateFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. + * @method CarbonImmutable rawParse($time = null, $tz = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method CarbonImmutable resetMacros() Remove all macros and generic macros. + * @method void resetMonthsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void resetToStringFormat() Reset the format used to the default when type juggling a Carbon instance to a string + * @method void resetYearsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void serializeUsing($callback) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform Carbon object before the serialization. + * JSON serialize all Carbon instances using the given callback. + * @method CarbonImmutable setFallbackLocale($locale) Set the fallback locale. + * @method CarbonImmutable setHumanDiffOptions($humanDiffOptions) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method bool setLocale($locale) Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * @method void setMidDayAt($hour) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * Set midday/noon hour + * @method CarbonImmutable setTestNow($testNow = null) Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method CarbonImmutable setTestNowAndTimezone($testNow = null, $tz = null) Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method void setToStringFormat($format) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump another string + * format. + * Set the default format used when type juggling a Carbon instance to a string. + * @method void setTranslator(TranslatorInterface $translator) Set the default translator instance to use. + * @method CarbonImmutable setUtf8($utf8) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * Set if UTF8 will be used for localized date/time. + * @method void setWeekEndsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * Set the last day of week + * @method void setWeekStartsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * Set the first day of week + * @method void setWeekendDays($days) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * Set weekend days + * @method bool shouldOverflowMonths() Get the month overflow global behavior (can be overridden in specific instances). + * @method bool shouldOverflowYears() Get the month overflow global behavior (can be overridden in specific instances). + * @method string singularUnit(string $unit) Returns standardized singular of a given singular/plural unit name (in English). + * @method CarbonImmutable today($tz = null) Create a Carbon instance for today. + * @method CarbonImmutable tomorrow($tz = null) Create a Carbon instance for tomorrow. + * @method string translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) Translate a time string from a locale to an other. + * @method string translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null) Translate using translation string or callback available. + * @method void useMonthsOverflow($monthsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method CarbonImmutable useStrictMode($strictModeEnabled = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method void useYearsOverflow($yearsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method mixed withTestNow($testNow, $callback) Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * /!\ Use this method for unit tests only. + * @method CarbonImmutable yesterday($tz = null) Create a Carbon instance for yesterday. + * + * + */ +class FactoryImmutable extends Factory implements ClockInterface +{ + protected $className = CarbonImmutable::class; + + /** + * Get a Carbon instance for the current date and time. + * + * @param DateTimeZone|string|int|null $tz + * + * @return CarbonImmutable + */ + public function now($tz = null): DateTimeImmutable + { + $className = $this->className; + + return new $className(null, $tz); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa.php new file mode 100644 index 0000000000..f3431e4bc2 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/aa_DJ.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_DJ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_DJ.php new file mode 100644 index 0000000000..c6e23c0df3 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_DJ.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Qunxa Garablu', 'Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Liiqen', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['qun', 'nah', 'cig', 'agd', 'cax', 'qas', 'qad', 'leq', 'way', 'dit', 'xim', 'kax'], + 'weekdays' => ['Acaada', 'Etleeni', 'Talaata', 'Arbaqa', 'Kamiisi', 'Gumqata', 'Sabti'], + 'weekdays_short' => ['aca', 'etl', 'tal', 'arb', 'kam', 'gum', 'sab'], + 'weekdays_min' => ['aca', 'etl', 'tal', 'arb', 'kam', 'gum', 'sab'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], + + 'year' => ':count gaqambo', // less reliable + 'y' => ':count gaqambo', // less reliable + 'a_year' => ':count gaqambo', // less reliable + + 'month' => ':count ร lsa', + 'm' => ':count ร lsa', + 'a_month' => ':count ร lsa', + + 'day' => ':count saaku', // less reliable + 'd' => ':count saaku', // less reliable + 'a_day' => ':count saaku', // less reliable + + 'hour' => ':count ayti', // less reliable + 'h' => ':count ayti', // less reliable + 'a_hour' => ':count ayti', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER.php new file mode 100644 index 0000000000..f8f395b760 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Qunxa Garablu', 'Naharsi Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Leqeeni', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['Qun', 'Nah', 'Cig', 'Agd', 'Cax', 'Qas', 'Qad', 'Leq', 'Way', 'Dit', 'Xim', 'Kax'], + 'weekdays' => ['Acaada', 'Etleeni', 'Talaata', 'Arbaqa', 'Kamiisi', 'Gumqata', 'Sabti'], + 'weekdays_short' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'weekdays_min' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER@saaho.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER@saaho.php new file mode 100644 index 0000000000..646122533f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER@saaho.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Qunxa Garablu', 'Naharsi Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Leqeeni', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['Qun', 'Nah', 'Cig', 'Agd', 'Cax', 'Qas', 'Qad', 'Leq', 'Way', 'Dit', 'Xim', 'Kax'], + 'weekdays' => ['Naba Sambat', 'Sani', 'Salus', 'Rabuq', 'Camus', 'Jumqata', 'Qunxa Sambat'], + 'weekdays_short' => ['Nab', 'San', 'Sal', 'Rab', 'Cam', 'Jum', 'Qun'], + 'weekdays_min' => ['Nab', 'San', 'Sal', 'Rab', 'Cam', 'Jum', 'Qun'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_ET.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_ET.php new file mode 100644 index 0000000000..e55e591b54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Qunxa Garablu', 'Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Liiqen', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['Qun', 'Kud', 'Cig', 'Agd', 'Cax', 'Qas', 'Qad', 'Leq', 'Way', 'Dit', 'Xim', 'Kax'], + 'weekdays' => ['Acaada', 'Etleeni', 'Talaata', 'Arbaqa', 'Kamiisi', 'Gumqata', 'Sabti'], + 'weekdays_short' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'weekdays_min' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/af.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/af.php new file mode 100644 index 0000000000..27771d7aac --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/af.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - JD Isaacks + * - Pierre du Plessis + */ +return [ + 'year' => ':count jaar', + 'a_year' => '\'n jaar|:count jaar', + 'y' => ':count j.', + 'month' => ':count maand|:count maande', + 'a_month' => '\'n maand|:count maande', + 'm' => ':count maa.', + 'week' => ':count week|:count weke', + 'a_week' => '\'n week|:count weke', + 'w' => ':count w.', + 'day' => ':count dag|:count dae', + 'a_day' => '\'n dag|:count dae', + 'd' => ':count d.', + 'hour' => ':count uur', + 'a_hour' => '\'n uur|:count uur', + 'h' => ':count u.', + 'minute' => ':count minuut|:count minute', + 'a_minute' => '\'n minuut|:count minute', + 'min' => ':count min.', + 'second' => ':count sekond|:count sekondes', + 'a_second' => '\'n paar sekondes|:count sekondes', + 's' => ':count s.', + 'ago' => ':time gelede', + 'from_now' => 'oor :time', + 'after' => ':time na', + 'before' => ':time voor', + 'diff_now' => 'Nou', + 'diff_today' => 'Vandag', + 'diff_today_regexp' => 'Vandag(?:\\s+om)?', + 'diff_yesterday' => 'Gister', + 'diff_yesterday_regexp' => 'Gister(?:\\s+om)?', + 'diff_tomorrow' => 'Mรดre', + 'diff_tomorrow_regexp' => 'Mรดre(?:\\s+om)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Vandag om] LT', + 'nextDay' => '[Mรดre om] LT', + 'nextWeek' => 'dddd [om] LT', + 'lastDay' => '[Gister om] LT', + 'lastWeek' => '[Laas] dddd [om] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); + }, + 'meridiem' => ['VM', 'NM'], + 'months' => ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'], + 'months_short' => ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'], + 'weekdays_short' => ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'], + 'weekdays_min' => ['So', 'Ma', 'Di', 'Wo', 'Do', 'Vr', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' en '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/af_NA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/af_NA.php new file mode 100644 index 0000000000..f2fcf05324 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/af_NA.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/af.php', [ + 'meridiem' => ['v', 'n'], + 'weekdays' => ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'], + 'weekdays_short' => ['So.', 'Ma.', 'Di.', 'Wo.', 'Do.', 'Vr.', 'Sa.'], + 'weekdays_min' => ['So.', 'Ma.', 'Di.', 'Wo.', 'Do.', 'Vr.', 'Sa.'], + 'months' => ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'], + 'months_short' => ['Jan.', 'Feb.', 'Mrt.', 'Apr.', 'Mei', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Okt.', 'Nov.', 'Des.'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'DD MMM YYYY', + 'LLL' => 'DD MMMM YYYY HH:mm', + 'LLLL' => 'dddd, DD MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/af_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/af_ZA.php new file mode 100644 index 0000000000..27896bd0f6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/af_ZA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/af.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/agq.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/agq.php new file mode 100644 index 0000000000..70114649ba --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/agq.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['a.g', 'a.k'], + 'weekdays' => ['tsuส”ntsษจ', 'tsuส”ukpร ', 'tsuส”ughษ”e', 'tsuส”utษ”ฬ€mlรฒ', 'tsuส”umรจ', 'tsuส”ughษจฬ‚m', 'tsuส”ndzษจkษ”ส”ษ”'], + 'weekdays_short' => ['nts', 'kpa', 'ghษ”', 'tษ”m', 'ume', 'ghษจ', 'dzk'], + 'weekdays_min' => ['nts', 'kpa', 'ghษ”', 'tษ”m', 'ume', 'ghษจ', 'dzk'], + 'months' => ['ndzษ”ฬ€ล‹ษ”ฬ€nรนm', 'ndzษ”ฬ€ล‹ษ”ฬ€kฦ—ฬ€zรนส”', 'ndzษ”ฬ€ล‹ษ”ฬ€tฦ—ฬ€dส‰ฬ€ghร ', 'ndzษ”ฬ€ล‹ษ”ฬ€tวŽafส‰ฬ„ghฤ', 'ndzษ”ฬ€ล‹รจsรจe', 'ndzษ”ฬ€ล‹ษ”ฬ€nzรนghรฒ', 'ndzษ”ฬ€ล‹ษ”ฬ€dรนmlo', 'ndzษ”ฬ€ล‹ษ”ฬ€kwรฎfษ”ฬ€e', 'ndzษ”ฬ€ล‹ษ”ฬ€tฦ—ฬ€fส‰ฬ€ghร dzughรน', 'ndzษ”ฬ€ล‹ษ”ฬ€ghว”uwelษ”ฬ€m', 'ndzษ”ฬ€ล‹ษ”ฬ€chwaส”ร kaa wo', 'ndzษ”ฬ€ล‹รจfwรฒo'], + 'months_short' => ['nรนm', 'kษจz', 'tษจd', 'taa', 'see', 'nzu', 'dum', 'fษ”e', 'dzu', 'lษ”m', 'kaa', 'fwo'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/agr.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/agr.php new file mode 100644 index 0000000000..8f036ae8c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/agr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/agr_PE.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/agr_PE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/agr_PE.php new file mode 100644 index 0000000000..54a326af0f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/agr_PE.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - somosazucar.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Petsatin', 'Kupitin', 'Uyaitin', 'Tayutin', 'Kegketin', 'Tegmatin', 'Kuntutin', 'Yagkujutin', 'Daiktatin', 'Ipamtatin', 'Shinutin', 'Sakamtin'], + 'months_short' => ['Pet', 'Kup', 'Uya', 'Tay', 'Keg', 'Teg', 'Kun', 'Yag', 'Dait', 'Ipam', 'Shin', 'Sak'], + 'weekdays' => ['Tuntuamtin', 'Achutin', 'Kugkuktin', 'Saketin', 'Shimpitin', 'Imaptin', 'Bataetin'], + 'weekdays_short' => ['Tun', 'Ach', 'Kug', 'Sak', 'Shim', 'Im', 'Bat'], + 'weekdays_min' => ['Tun', 'Ach', 'Kug', 'Sak', 'Shim', 'Im', 'Bat'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 7, + 'meridiem' => ['VM', 'NM'], + + 'year' => ':count yaya', // less reliable + 'y' => ':count yaya', // less reliable + 'a_year' => ':count yaya', // less reliable + + 'month' => ':count nantu', // less reliable + 'm' => ':count nantu', // less reliable + 'a_month' => ':count nantu', // less reliable + + 'day' => ':count nayaim', // less reliable + 'd' => ':count nayaim', // less reliable + 'a_day' => ':count nayaim', // less reliable + + 'hour' => ':count kuwiลก', // less reliable + 'h' => ':count kuwiลก', // less reliable + 'a_hour' => ':count kuwiลก', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ak.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ak.php new file mode 100644 index 0000000000..5a64be3738 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ak.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ak_GH.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ak_GH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ak_GH.php new file mode 100644 index 0000000000..138194675f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ak_GH.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sugar Labs // OLPC sugarlabs.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY/MM/DD', + ], + 'months' => ['Sanda-ฦ†pษ›pษ”n', 'Kwakwar-ฦ†gyefuo', 'Ebษ”w-ฦ†benem', 'Ebษ”bira-Oforisuo', 'Esusow Aketseaba-Kษ”tษ”nimba', 'Obirade-Ayษ›wohomumu', 'Ayษ›woho-Kitawonsa', 'Difuu-ฦ†sandaa', 'Fankwa-ฦbษ”', 'ฦ†bษ›sษ›-Ahinime', 'ฦ†berษ›fษ›w-Obubuo', 'Mumu-ฦ†pษ›nimba'], + 'months_short' => ['S-ฦ†', 'K-ฦ†', 'E-ฦ†', 'E-O', 'E-K', 'O-A', 'A-K', 'D-ฦ†', 'F-ฦ', 'ฦ†-A', 'ฦ†-O', 'M-ฦ†'], + 'weekdays' => ['Kwesida', 'Dwowda', 'Benada', 'Wukuda', 'Yawda', 'Fida', 'Memeneda'], + 'weekdays_short' => ['Kwe', 'Dwo', 'Ben', 'Wuk', 'Yaw', 'Fia', 'Mem'], + 'weekdays_min' => ['Kwe', 'Dwo', 'Ben', 'Wuk', 'Yaw', 'Fia', 'Mem'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['AN', 'EW'], + + 'year' => ':count afe', + 'y' => ':count afe', + 'a_year' => ':count afe', + + 'month' => ':count bosume', + 'm' => ':count bosume', + 'a_month' => ':count bosume', + + 'day' => ':count ษ›da', + 'd' => ':count ษ›da', + 'a_day' => ':count ษ›da', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/am.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/am.php new file mode 100644 index 0000000000..63bf72d2db --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/am.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/am_ET.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/am_ET.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/am_ET.php new file mode 100644 index 0000000000..ece80621a8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/am_ET.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['แŒƒแŠ•แ‹ฉแ‹ˆแˆช', 'แŒแ‰ฅแˆฉแ‹ˆแˆช', 'แˆ›แˆญแ‰ฝ', 'แŠคแ•แˆชแˆ', 'แˆœแ‹ญ', 'แŒแŠ•', 'แŒแˆ‹แ‹ญ', 'แŠฆแŒˆแˆตแ‰ต', 'แˆดแ•แ‰ดแˆแ‰ แˆญ', 'แŠฆแŠญแ‰ถแ‰ แˆญ', 'แŠ–แ‰ฌแˆแ‰ แˆญ', 'แ‹ฒแˆดแˆแ‰ แˆญ'], + 'months_short' => ['แŒƒแŠ•แ‹ฉ', 'แŒแ‰ฅแˆฉ', 'แˆ›แˆญแ‰ฝ', 'แŠคแ•แˆจ', 'แˆœแ‹ญ ', 'แŒแŠ• ', 'แŒแˆ‹แ‹ญ', 'แŠฆแŒˆแˆต', 'แˆดแ•แ‰ด', 'แŠฆแŠญแ‰ฐ', 'แŠ–แ‰ฌแˆ', 'แ‹ฒแˆดแˆ'], + 'weekdays' => ['แŠฅแˆ‘แ‹ต', 'แˆฐแŠž', 'แˆ›แŠญแˆฐแŠž', 'แˆจแ‰กแ‹•', 'แˆแˆ™แˆต', 'แ‹“แˆญแ‰ฅ', 'แ‰…แ‹ณแˆœ'], + 'weekdays_short' => ['แŠฅแˆ‘แ‹ต', 'แˆฐแŠž ', 'แˆ›แŠญแˆฐ', 'แˆจแ‰กแ‹•', 'แˆแˆ™แˆต', 'แ‹“แˆญแ‰ฅ', 'แ‰…แ‹ณแˆœ'], + 'weekdays_min' => ['แŠฅแˆ‘แ‹ต', 'แˆฐแŠž ', 'แˆ›แŠญแˆฐ', 'แˆจแ‰กแ‹•', 'แˆแˆ™แˆต', 'แ‹“แˆญแ‰ฅ', 'แ‰…แ‹ณแˆœ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['แŒกแ‹‹แ‰ต', 'แŠจแˆฐแ‹“แ‰ต'], + + 'year' => ':count แŠ แˆ˜แ‰ต', + 'y' => ':count แŠ แˆ˜แ‰ต', + 'a_year' => ':count แŠ แˆ˜แ‰ต', + + 'month' => ':count แ‹ˆแˆญ', + 'm' => ':count แ‹ˆแˆญ', + 'a_month' => ':count แ‹ˆแˆญ', + + 'week' => ':count แˆณแˆแŠ•แ‰ต', + 'w' => ':count แˆณแˆแŠ•แ‰ต', + 'a_week' => ':count แˆณแˆแŠ•แ‰ต', + + 'day' => ':count แ‰€แŠ•', + 'd' => ':count แ‰€แŠ•', + 'a_day' => ':count แ‰€แŠ•', + + 'hour' => ':count แˆฐแ‹“แ‰ต', + 'h' => ':count แˆฐแ‹“แ‰ต', + 'a_hour' => ':count แˆฐแ‹“แ‰ต', + + 'minute' => ':count แ‹ฐแ‰‚แ‰ƒ', + 'min' => ':count แ‹ฐแ‰‚แ‰ƒ', + 'a_minute' => ':count แ‹ฐแ‰‚แ‰ƒ', + + 'second' => ':count แˆดแŠฎแŠ•แ‹ต', + 's' => ':count แˆดแŠฎแŠ•แ‹ต', + 'a_second' => ':count แˆดแŠฎแŠ•แ‹ต', + + 'ago' => 'แŠจ:time แ‰ แŠแ‰ต', + 'from_now' => 'แ‰ :time แ‹แˆตแŒฅ', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/an.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/an.php new file mode 100644 index 0000000000..565abf2699 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/an.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/an_ES.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/an_ES.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/an_ES.php new file mode 100644 index 0000000000..faf8ae072e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/an_ES.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Softaragones Jordi Mallach Pรฉrez, Juan Pablo Martรญnez bug-glibc-locales@gnu.org, softaragones@softaragones.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['chinero', 'febrero', 'marzo', 'abril', 'mayo', 'chunyo', 'chuliol', 'agosto', 'setiembre', 'octubre', 'noviembre', 'aviento'], + 'months_short' => ['chi', 'feb', 'mar', 'abr', 'may', 'chn', 'chl', 'ago', 'set', 'oct', 'nov', 'avi'], + 'weekdays' => ['domingo', 'luns', 'martes', 'mierques', 'chueves', 'viernes', 'sabado'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mie', 'chu', 'vie', 'sab'], + 'weekdays_min' => ['dom', 'lun', 'mar', 'mie', 'chu', 'vie', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count aรฑo', + 'y' => ':count aรฑo', + 'a_year' => ':count aรฑo', + + 'month' => ':count mes', + 'm' => ':count mes', + 'a_month' => ':count mes', + + 'week' => ':count semana', + 'w' => ':count semana', + 'a_week' => ':count semana', + + 'day' => ':count dรญa', + 'd' => ':count dรญa', + 'a_day' => ':count dรญa', + + 'hour' => ':count reloch', // less reliable + 'h' => ':count reloch', // less reliable + 'a_hour' => ':count reloch', // less reliable + + 'minute' => ':count minuto', + 'min' => ':count minuto', + 'a_minute' => ':count minuto', + + 'second' => ':count segundo', + 's' => ':count segundo', + 'a_second' => ':count segundo', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/anp.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/anp.php new file mode 100644 index 0000000000..b56c67bbba --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/anp.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/anp_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/anp_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/anp_IN.php new file mode 100644 index 0000000000..11069be3f3 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/anp_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bhashaghar@googlegroups.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅˆเคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเค‚เคฌเคฐ', 'เค…เค•เฅเคŸเฅ‚เคฌเคฐ', 'เคจเคตเค‚เคฌเคฐ', 'เคฆเคฟเคธเค‚เคฌเคฐ"'], + 'months_short' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅˆเคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเค‚เคฌเคฐ', 'เค…เค•เฅเคŸเฅ‚เคฌเคฐ', 'เคจเคตเค‚เคฌเคฐ', 'เคฆเคฟเคธเค‚เคฌเคฐ'], + 'weekdays' => ['เคฐเคตเคฟเคตเคพเคฐ', 'เคธเฅ‹เคฎเคตเคพเคฐ', 'เคฎเค‚เค—เคฒเคตเคพเคฐ', 'เคฌเฅเคงเคตเคพเคฐ', 'เคฌเฅƒเคนเคธเฅเคชเคคเคฟเคตเคพเคฐ', 'เคถเฅเค•เฅเคฐเคตเคพเคฐ', 'เคถเคจเคฟเคตเคพเคฐ'], + 'weekdays_short' => ['เคฐเคตเคฟ', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคง', 'เคฌเฅƒเคนเคธเฅเคชเคคเคฟ', 'เคถเฅเค•เฅเคฐ', 'เคถเคจเคฟ'], + 'weekdays_min' => ['เคฐเคตเคฟ', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคง', 'เคฌเฅƒเคนเคธเฅเคชเคคเคฟ', 'เคถเฅเค•เฅเคฐ', 'เคถเคจเคฟ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เคชเฅ‚เคฐเฅเคตเคพเคนเฅเคจ', 'เค…เคชเคฐเคพเคนเฅเคจ'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar.php new file mode 100644 index 0000000000..5f73f63952 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Atef Ben Ali (atefBB) + * - Ibrahim AshShohail + * - MLTDev + * - Mohamed Sabil (mohamedsabil83) + * - Yazan Alnugnugh (yazan-alnugnugh) + */ +$months = [ + 'ูŠู†ุงูŠุฑ', + 'ูุจุฑุงูŠุฑ', + 'ู…ุงุฑุณ', + 'ุฃุจุฑูŠู„', + 'ู…ุงูŠูˆ', + 'ูŠูˆู†ูŠูˆ', + 'ูŠูˆู„ูŠูˆ', + 'ุฃุบุณุทุณ', + 'ุณุจุชู…ุจุฑ', + 'ุฃูƒุชูˆุจุฑ', + 'ู†ูˆูู…ุจุฑ', + 'ุฏูŠุณู…ุจุฑ', +]; + +return [ + 'year' => implode('|', ['{0}:count ุณู†ุฉ', '{1}ุณู†ุฉ', '{2}ุณู†ุชูŠู†', ']2,11[:count ุณู†ูˆุงุช', ']10,Inf[:count ุณู†ุฉ']), + 'a_year' => implode('|', ['{0}:count ุณู†ุฉ', '{1}ุณู†ุฉ', '{2}ุณู†ุชูŠู†', ']2,11[:count ุณู†ูˆุงุช', ']10,Inf[:count ุณู†ุฉ']), + 'month' => implode('|', ['{0}:count ุดู‡ุฑ', '{1}ุดู‡ุฑ', '{2}ุดู‡ุฑูŠู†', ']2,11[:count ุฃุดู‡ุฑ', ']10,Inf[:count ุดู‡ุฑ']), + 'a_month' => implode('|', ['{0}:count ุดู‡ุฑ', '{1}ุดู‡ุฑ', '{2}ุดู‡ุฑูŠู†', ']2,11[:count ุฃุดู‡ุฑ', ']10,Inf[:count ุดู‡ุฑ']), + 'week' => implode('|', ['{0}:count ุฃุณุจูˆุน', '{1}ุฃุณุจูˆุน', '{2}ุฃุณุจูˆุนูŠู†', ']2,11[:count ุฃุณุงุจูŠุน', ']10,Inf[:count ุฃุณุจูˆุน']), + 'a_week' => implode('|', ['{0}:count ุฃุณุจูˆุน', '{1}ุฃุณุจูˆุน', '{2}ุฃุณุจูˆุนูŠู†', ']2,11[:count ุฃุณุงุจูŠุน', ']10,Inf[:count ุฃุณุจูˆุน']), + 'day' => implode('|', ['{0}:count ูŠูˆู…', '{1}ูŠูˆู…', '{2}ูŠูˆู…ูŠู†', ']2,11[:count ุฃูŠุงู…', ']10,Inf[:count ูŠูˆู…']), + 'a_day' => implode('|', ['{0}:count ูŠูˆู…', '{1}ูŠูˆู…', '{2}ูŠูˆู…ูŠู†', ']2,11[:count ุฃูŠุงู…', ']10,Inf[:count ูŠูˆู…']), + 'hour' => implode('|', ['{0}:count ุณุงุนุฉ', '{1}ุณุงุนุฉ', '{2}ุณุงุนุชูŠู†', ']2,11[:count ุณุงุนุงุช', ']10,Inf[:count ุณุงุนุฉ']), + 'a_hour' => implode('|', ['{0}:count ุณุงุนุฉ', '{1}ุณุงุนุฉ', '{2}ุณุงุนุชูŠู†', ']2,11[:count ุณุงุนุงุช', ']10,Inf[:count ุณุงุนุฉ']), + 'minute' => implode('|', ['{0}:count ุฏู‚ูŠู‚ุฉ', '{1}ุฏู‚ูŠู‚ุฉ', '{2}ุฏู‚ูŠู‚ุชูŠู†', ']2,11[:count ุฏู‚ุงุฆู‚', ']10,Inf[:count ุฏู‚ูŠู‚ุฉ']), + 'a_minute' => implode('|', ['{0}:count ุฏู‚ูŠู‚ุฉ', '{1}ุฏู‚ูŠู‚ุฉ', '{2}ุฏู‚ูŠู‚ุชูŠู†', ']2,11[:count ุฏู‚ุงุฆู‚', ']10,Inf[:count ุฏู‚ูŠู‚ุฉ']), + 'second' => implode('|', ['{0}:count ุซุงู†ูŠุฉ', '{1}ุซุงู†ูŠุฉ', '{2}ุซุงู†ูŠุชูŠู†', ']2,11[:count ุซูˆุงู†ูŠ', ']10,Inf[:count ุซุงู†ูŠุฉ']), + 'a_second' => implode('|', ['{0}:count ุซุงู†ูŠุฉ', '{1}ุซุงู†ูŠุฉ', '{2}ุซุงู†ูŠุชูŠู†', ']2,11[:count ุซูˆุงู†ูŠ', ']10,Inf[:count ุซุงู†ูŠุฉ']), + 'ago' => 'ู…ู†ุฐ :time', + 'from_now' => ':time ู…ู† ุงู„ุขู†', + 'after' => 'ุจุนุฏ :time', + 'before' => 'ู‚ุจู„ :time', + 'diff_now' => 'ุงู„ุขู†', + 'diff_today' => 'ุงู„ูŠูˆู…', + 'diff_today_regexp' => 'ุงู„ูŠูˆู…(?:\\s+ุนู†ุฏ)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_yesterday' => 'ุฃู…ุณ', + 'diff_yesterday_regexp' => 'ุฃู…ุณ(?:\\s+ุนู†ุฏ)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_tomorrow' => 'ุบุฏุงู‹', + 'diff_tomorrow_regexp' => 'ุบุฏู‹ุง(?:\\s+ุนู†ุฏ)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_before_yesterday' => 'ู‚ุจู„ ุงู„ุฃู…ุณ', + 'diff_after_tomorrow' => 'ุจุนุฏ ุบุฏ', + 'period_recurrences' => implode('|', ['{0}ู…ุฑุฉ', '{1}ู…ุฑุฉ', '{2}:count ู…ุฑุชูŠู†', ']2,11[:count ู…ุฑุงุช', ']10,Inf[:count ู…ุฑุฉ']), + 'period_interval' => 'ูƒู„ :interval', + 'period_start_date' => 'ู…ู† :date', + 'period_end_date' => 'ุฅู„ู‰ :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุฃุญุฏ', 'ุงุซู†ูŠู†', 'ุซู„ุงุซุงุก', 'ุฃุฑุจุนุงุก', 'ุฎู…ูŠุณ', 'ุฌู…ุนุฉ', 'ุณุจุช'], + 'weekdays_min' => ['ุญ', 'ุงุซ', 'ุซู„', 'ุฃุฑ', 'ุฎู…', 'ุฌ', 'ุณ'], + 'list' => ['ุŒ ', ' ูˆ '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ุงู„ูŠูˆู… ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'nextDay' => '[ุบุฏู‹ุง ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'nextWeek' => 'dddd [ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'lastDay' => '[ุฃู…ุณ ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'lastWeek' => 'dddd [ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ุต', 'ู…'], + 'weekend' => [5, 6], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_AE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_AE.php new file mode 100644 index 0000000000..35a22b1d20 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_AE.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['ูŠู†ุงูŠุฑ', 'ูุจุฑุงูŠุฑ', 'ู…ุงุฑุณ', 'ุฃุจุฑูŠู„', 'ู…ุงูŠูˆ', 'ูŠูˆู†ูŠูˆ', 'ูŠูˆู„ูŠูˆ', 'ุฃุบุณุทุณ', 'ุณุจุชู…ุจุฑ', 'ุฃูƒุชูˆุจุฑ', 'ู†ูˆูู…ุจุฑ', 'ุฏูŠุณู…ุจุฑ'], + 'months_short' => ['ูŠู†ุง', 'ูุจุฑ', 'ู…ุงุฑ', 'ุฃุจุฑ', 'ู…ุงูŠ', 'ูŠูˆู†', 'ูŠูˆู„', 'ุฃุบุณ', 'ุณุจุช', 'ุฃูƒุช', 'ู†ูˆู', 'ุฏูŠุณ'], + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช '], + 'weekdays_short' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐูค', 'ฐูฅ', 'ฐูฆ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑูค', 'ฑูฅ', 'ฑูฆ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒูค', 'ฒูฅ', 'ฒูฆ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณูค', 'ณูฅ', 'ณูฆ', 'ณท', 'ณธ', 'ณน', 'ูคฐ', 'ูคฑ', 'ูคฒ', 'ูคณ', 'ูคูค', 'ูคูฅ', 'ูคูฆ', 'ูคท', 'ูคธ', 'ูคน', 'ูฅฐ', 'ูฅฑ', 'ูฅฒ', 'ูฅณ', 'ูฅูค', 'ูฅูฅ', 'ูฅูฆ', 'ูฅท', 'ูฅธ', 'ูฅน', 'ูฆฐ', 'ูฆฑ', 'ูฆฒ', 'ูฆณ', 'ูฆูค', 'ูฆูฅ', 'ูฆูฆ', 'ูฆท', 'ูฆธ', 'ูฆน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทูค', 'ทูฅ', 'ทูฆ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธูค', 'ธูฅ', 'ธูฆ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นูค', 'นูฅ', 'นูฆ', 'นท', 'นธ', 'นน'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_BH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_BH.php new file mode 100644 index 0000000000..35180965a4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_BH.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['ูŠู†ุงูŠุฑ', 'ูุจุฑุงูŠุฑ', 'ู…ุงุฑุณ', 'ุฃุจุฑูŠู„', 'ู…ุงูŠูˆ', 'ูŠูˆู†ูŠูˆ', 'ูŠูˆู„ูŠูˆ', 'ุฃุบุณุทุณ', 'ุณุจุชู…ุจุฑ', 'ุฃูƒุชูˆุจุฑ', 'ู†ูˆูู…ุจุฑ', 'ุฏูŠุณู…ุจุฑ'], + 'months_short' => ['ูŠู†ุง', 'ูุจุฑ', 'ู…ุงุฑ', 'ุฃุจุฑ', 'ู…ุงูŠ', 'ูŠูˆู†', 'ูŠูˆู„', 'ุฃุบุณ', 'ุณุจุช', 'ุฃูƒุช', 'ู†ูˆู', 'ุฏูŠุณ'], + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐูค', 'ฐูฅ', 'ฐูฆ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑูค', 'ฑูฅ', 'ฑูฆ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒูค', 'ฒูฅ', 'ฒูฆ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณูค', 'ณูฅ', 'ณูฆ', 'ณท', 'ณธ', 'ณน', 'ูคฐ', 'ูคฑ', 'ูคฒ', 'ูคณ', 'ูคูค', 'ูคูฅ', 'ูคูฆ', 'ูคท', 'ูคธ', 'ูคน', 'ูฅฐ', 'ูฅฑ', 'ูฅฒ', 'ูฅณ', 'ูฅูค', 'ูฅูฅ', 'ูฅูฆ', 'ูฅท', 'ูฅธ', 'ูฅน', 'ูฆฐ', 'ูฆฑ', 'ูฆฒ', 'ูฆณ', 'ูฆูค', 'ูฆูฅ', 'ูฆูฆ', 'ูฆท', 'ูฆธ', 'ูฆน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทูค', 'ทูฅ', 'ทูฆ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธูค', 'ธูฅ', 'ธูฆ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นูค', 'นูฅ', 'นูฆ', 'นท', 'นธ', 'นน'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_DJ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_DJ.php new file mode 100644 index 0000000000..e790b99e63 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_DJ.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_DZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_DZ.php new file mode 100644 index 0000000000..aea4eeecc3 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_DZ.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - Noureddine LOUAHEDJ + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +$months = [ + 'ุฌุงู†ููŠ', + 'ููŠูุฑูŠ', + 'ู…ุงุฑุณ', + 'ุฃูุฑูŠู„', + 'ู…ุงูŠ', + 'ุฌูˆุงู†', + 'ุฌูˆูŠู„ูŠุฉ', + 'ุฃูˆุช', + 'ุณุจุชู…ุจุฑ', + 'ุฃูƒุชูˆุจุฑ', + 'ู†ูˆูู…ุจุฑ', + 'ุฏูŠุณู…ุจุฑ', +]; + +return [ + 'year' => implode('|', ['{0}:count ุณู†ุฉ', '{1}ุณู†ุฉ', '{2}ุณู†ุชูŠู†', ']2,11[:count ุณู†ูˆุงุช', ']10,Inf[:count ุณู†ุฉ']), + 'a_year' => implode('|', ['{0}:count ุณู†ุฉ', '{1}ุณู†ุฉ', '{2}ุณู†ุชูŠู†', ']2,11[:count ุณู†ูˆุงุช', ']10,Inf[:count ุณู†ุฉ']), + 'month' => implode('|', ['{0}:count ุดู‡ุฑ', '{1}ุดู‡ุฑ', '{2}ุดู‡ุฑูŠู†', ']2,11[:count ุฃุดู‡ุฑ', ']10,Inf[:count ุดู‡ุฑ']), + 'a_month' => implode('|', ['{0}:count ุดู‡ุฑ', '{1}ุดู‡ุฑ', '{2}ุดู‡ุฑูŠู†', ']2,11[:count ุฃุดู‡ุฑ', ']10,Inf[:count ุดู‡ุฑ']), + 'week' => implode('|', ['{0}:count ุฃุณุจูˆุน', '{1}ุฃุณุจูˆุน', '{2}ุฃุณุจูˆุนูŠู†', ']2,11[:count ุฃุณุงุจูŠุน', ']10,Inf[:count ุฃุณุจูˆุน']), + 'a_week' => implode('|', ['{0}:count ุฃุณุจูˆุน', '{1}ุฃุณุจูˆุน', '{2}ุฃุณุจูˆุนูŠู†', ']2,11[:count ุฃุณุงุจูŠุน', ']10,Inf[:count ุฃุณุจูˆุน']), + 'day' => implode('|', ['{0}:count ูŠูˆู…', '{1}ูŠูˆู…', '{2}ูŠูˆู…ูŠู†', ']2,11[:count ุฃูŠุงู…', ']10,Inf[:count ูŠูˆู…']), + 'a_day' => implode('|', ['{0}:count ูŠูˆู…', '{1}ูŠูˆู…', '{2}ูŠูˆู…ูŠู†', ']2,11[:count ุฃูŠุงู…', ']10,Inf[:count ูŠูˆู…']), + 'hour' => implode('|', ['{0}:count ุณุงุนุฉ', '{1}ุณุงุนุฉ', '{2}ุณุงุนุชูŠู†', ']2,11[:count ุณุงุนุงุช', ']10,Inf[:count ุณุงุนุฉ']), + 'a_hour' => implode('|', ['{0}:count ุณุงุนุฉ', '{1}ุณุงุนุฉ', '{2}ุณุงุนุชูŠู†', ']2,11[:count ุณุงุนุงุช', ']10,Inf[:count ุณุงุนุฉ']), + 'minute' => implode('|', ['{0}:count ุฏู‚ูŠู‚ุฉ', '{1}ุฏู‚ูŠู‚ุฉ', '{2}ุฏู‚ูŠู‚ุชูŠู†', ']2,11[:count ุฏู‚ุงุฆู‚', ']10,Inf[:count ุฏู‚ูŠู‚ุฉ']), + 'a_minute' => implode('|', ['{0}:count ุฏู‚ูŠู‚ุฉ', '{1}ุฏู‚ูŠู‚ุฉ', '{2}ุฏู‚ูŠู‚ุชูŠู†', ']2,11[:count ุฏู‚ุงุฆู‚', ']10,Inf[:count ุฏู‚ูŠู‚ุฉ']), + 'second' => implode('|', ['{0}:count ุซุงู†ูŠุฉ', '{1}ุซุงู†ูŠุฉ', '{2}ุซุงู†ูŠุชูŠู†', ']2,11[:count ุซูˆุงู†ูŠ', ']10,Inf[:count ุซุงู†ูŠุฉ']), + 'a_second' => implode('|', ['{0}:count ุซุงู†ูŠุฉ', '{1}ุซุงู†ูŠุฉ', '{2}ุซุงู†ูŠุชูŠู†', ']2,11[:count ุซูˆุงู†ูŠ', ']10,Inf[:count ุซุงู†ูŠุฉ']), + 'ago' => 'ู…ู†ุฐ :time', + 'from_now' => 'ููŠ :time', + 'after' => 'ุจุนุฏ :time', + 'before' => 'ู‚ุจู„ :time', + 'diff_now' => 'ุงู„ุขู†', + 'diff_today' => 'ุงู„ูŠูˆู…', + 'diff_today_regexp' => 'ุงู„ูŠูˆู…(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_yesterday' => 'ุฃู…ุณ', + 'diff_yesterday_regexp' => 'ุฃู…ุณ(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_tomorrow' => 'ุบุฏุงู‹', + 'diff_tomorrow_regexp' => 'ุบุฏุง(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_before_yesterday' => 'ู‚ุจู„ ุงู„ุฃู…ุณ', + 'diff_after_tomorrow' => 'ุจุนุฏ ุบุฏ', + 'period_recurrences' => implode('|', ['{0}ู…ุฑุฉ', '{1}ู…ุฑุฉ', '{2}:count ู…ุฑุชูŠู†', ']2,11[:count ู…ุฑุงุช', ']10,Inf[:count ู…ุฑุฉ']), + 'period_interval' => 'ูƒู„ :interval', + 'period_start_date' => 'ู…ู† :date', + 'period_end_date' => 'ุฅู„ู‰ :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุฃุญุฏ', 'ุงุซู†ูŠู†', 'ุซู„ุงุซุงุก', 'ุฃุฑุจุนุงุก', 'ุฎู…ูŠุณ', 'ุฌู…ุนุฉ', 'ุณุจุช'], + 'weekdays_min' => ['ุฃุญ', 'ุฅุซ', 'ุซู„ุง', 'ุฃุฑ', 'ุฎู…', 'ุฌู…', 'ุณุจ'], + 'list' => ['ุŒ ', ' ูˆ '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 4, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ุงู„ูŠูˆู… ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'nextDay' => '[ุบุฏุง ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'nextWeek' => 'dddd [ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'lastDay' => '[ุฃู…ุณ ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'lastWeek' => 'dddd [ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ุต', 'ู…'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_EG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_EG.php new file mode 100644 index 0000000000..35180965a4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_EG.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['ูŠู†ุงูŠุฑ', 'ูุจุฑุงูŠุฑ', 'ู…ุงุฑุณ', 'ุฃุจุฑูŠู„', 'ู…ุงูŠูˆ', 'ูŠูˆู†ูŠูˆ', 'ูŠูˆู„ูŠูˆ', 'ุฃุบุณุทุณ', 'ุณุจุชู…ุจุฑ', 'ุฃูƒุชูˆุจุฑ', 'ู†ูˆูู…ุจุฑ', 'ุฏูŠุณู…ุจุฑ'], + 'months_short' => ['ูŠู†ุง', 'ูุจุฑ', 'ู…ุงุฑ', 'ุฃุจุฑ', 'ู…ุงูŠ', 'ูŠูˆู†', 'ูŠูˆู„', 'ุฃุบุณ', 'ุณุจุช', 'ุฃูƒุช', 'ู†ูˆู', 'ุฏูŠุณ'], + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐูค', 'ฐูฅ', 'ฐูฆ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑูค', 'ฑูฅ', 'ฑูฆ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒูค', 'ฒูฅ', 'ฒูฆ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณูค', 'ณูฅ', 'ณูฆ', 'ณท', 'ณธ', 'ณน', 'ูคฐ', 'ูคฑ', 'ูคฒ', 'ูคณ', 'ูคูค', 'ูคูฅ', 'ูคูฆ', 'ูคท', 'ูคธ', 'ูคน', 'ูฅฐ', 'ูฅฑ', 'ูฅฒ', 'ูฅณ', 'ูฅูค', 'ูฅูฅ', 'ูฅูฆ', 'ูฅท', 'ูฅธ', 'ูฅน', 'ูฆฐ', 'ูฆฑ', 'ูฆฒ', 'ูฆณ', 'ูฆูค', 'ูฆูฅ', 'ูฆูฆ', 'ูฆท', 'ูฆธ', 'ูฆน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทูค', 'ทูฅ', 'ทูฆ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธูค', 'ธูฅ', 'ธูฆ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นูค', 'นูฅ', 'นูฆ', 'นท', 'นธ', 'นน'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_EH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_EH.php new file mode 100644 index 0000000000..e790b99e63 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_EH.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_ER.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_ER.php new file mode 100644 index 0000000000..e790b99e63 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_ER.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_IL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_IL.php new file mode 100644 index 0000000000..e790b99e63 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_IL.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_IN.php new file mode 100644 index 0000000000..5fecf70f61 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['ูŠู†ุงูŠุฑ', 'ูุจุฑุงูŠุฑ', 'ู…ุงุฑุณ', 'ุฃุจุฑูŠู„', 'ู…ุงูŠูˆ', 'ูŠูˆู†ูŠูˆ', 'ูŠูˆู„ูŠูˆ', 'ุฃุบุณุทุณ', 'ุณุจุชู…ุจุฑ', 'ุฃูƒุชูˆุจุฑ', 'ู†ูˆูู…ุจุฑ', 'ุฏูŠุณู…ุจุฑ'], + 'months_short' => ['ูŠู†ุง', 'ูุจุฑ', 'ู…ุงุฑ', 'ุฃุจุฑ', 'ู…ุงูŠ', 'ูŠูˆู†', 'ูŠูˆู„', 'ุฃุบุณ', 'ุณุจุช', 'ุฃูƒุช', 'ู†ูˆู', 'ุฏูŠุณ'], + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_IQ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_IQ.php new file mode 100644 index 0000000000..2d4200845f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_IQ.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['ูƒุงู†ูˆู† ุงู„ุซุงู†ูŠ', 'ุดุจุงุท', 'ุขุฐุงุฑ', 'ู†ูŠุณุงู†', 'ุฃูŠุงุฑ', 'ุญุฒูŠุฑุงู†', 'ุชู…ูˆุฒ', 'ุขุจ', 'ุฃูŠู„ูˆู„', 'ุชุดุฑูŠู† ุงู„ุฃูˆู„', 'ุชุดุฑูŠู† ุงู„ุซุงู†ูŠ', 'ูƒุงู†ูˆู† ุงู„ุฃูˆู„'], + 'months_short' => ['ูƒุงู†ูˆู† ุงู„ุซุงู†ูŠ', 'ุดุจุงุท', 'ุขุฐุงุฑ', 'ู†ูŠุณุงู†', 'ุฃูŠุงุฑ', 'ุญุฒูŠุฑุงู†', 'ุชู…ูˆุฒ', 'ุขุจ', 'ุฃูŠู„ูˆู„', 'ุชุดุฑูŠู† ุงู„ุฃูˆู„', 'ุชุดุฑูŠู† ุงู„ุซุงู†ูŠ', 'ูƒุงู†ูˆู† ุงู„ุฃูˆู„'], + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐูค', 'ฐูฅ', 'ฐูฆ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑูค', 'ฑูฅ', 'ฑูฆ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒูค', 'ฒูฅ', 'ฒูฆ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณูค', 'ณูฅ', 'ณูฆ', 'ณท', 'ณธ', 'ณน', 'ูคฐ', 'ูคฑ', 'ูคฒ', 'ูคณ', 'ูคูค', 'ูคูฅ', 'ูคูฆ', 'ูคท', 'ูคธ', 'ูคน', 'ูฅฐ', 'ูฅฑ', 'ูฅฒ', 'ูฅณ', 'ูฅูค', 'ูฅูฅ', 'ูฅูฆ', 'ูฅท', 'ูฅธ', 'ูฅน', 'ูฆฐ', 'ูฆฑ', 'ูฆฒ', 'ูฆณ', 'ูฆูค', 'ูฆูฅ', 'ูฆูฆ', 'ูฆท', 'ูฆธ', 'ูฆน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทูค', 'ทูฅ', 'ทูฆ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธูค', 'ธูฅ', 'ธูฆ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นูค', 'นูฅ', 'นูฆ', 'นท', 'นธ', 'นน'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_JO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_JO.php new file mode 100644 index 0000000000..2d4200845f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_JO.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['ูƒุงู†ูˆู† ุงู„ุซุงู†ูŠ', 'ุดุจุงุท', 'ุขุฐุงุฑ', 'ู†ูŠุณุงู†', 'ุฃูŠุงุฑ', 'ุญุฒูŠุฑุงู†', 'ุชู…ูˆุฒ', 'ุขุจ', 'ุฃูŠู„ูˆู„', 'ุชุดุฑูŠู† ุงู„ุฃูˆู„', 'ุชุดุฑูŠู† ุงู„ุซุงู†ูŠ', 'ูƒุงู†ูˆู† ุงู„ุฃูˆู„'], + 'months_short' => ['ูƒุงู†ูˆู† ุงู„ุซุงู†ูŠ', 'ุดุจุงุท', 'ุขุฐุงุฑ', 'ู†ูŠุณุงู†', 'ุฃูŠุงุฑ', 'ุญุฒูŠุฑุงู†', 'ุชู…ูˆุฒ', 'ุขุจ', 'ุฃูŠู„ูˆู„', 'ุชุดุฑูŠู† ุงู„ุฃูˆู„', 'ุชุดุฑูŠู† ุงู„ุซุงู†ูŠ', 'ูƒุงู†ูˆู† ุงู„ุฃูˆู„'], + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐูค', 'ฐูฅ', 'ฐูฆ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑูค', 'ฑูฅ', 'ฑูฆ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒูค', 'ฒูฅ', 'ฒูฆ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณูค', 'ณูฅ', 'ณูฆ', 'ณท', 'ณธ', 'ณน', 'ูคฐ', 'ูคฑ', 'ูคฒ', 'ูคณ', 'ูคูค', 'ูคูฅ', 'ูคูฆ', 'ูคท', 'ูคธ', 'ูคน', 'ูฅฐ', 'ูฅฑ', 'ูฅฒ', 'ูฅณ', 'ูฅูค', 'ูฅูฅ', 'ูฅูฆ', 'ูฅท', 'ูฅธ', 'ูฅน', 'ูฆฐ', 'ูฆฑ', 'ูฆฒ', 'ูฆณ', 'ูฆูค', 'ูฆูฅ', 'ูฆูฆ', 'ูฆท', 'ูฆธ', 'ูฆน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทูค', 'ทูฅ', 'ทูฆ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธูค', 'ธูฅ', 'ธูฆ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นูค', 'นูฅ', 'นูฆ', 'นท', 'นธ', 'นน'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_KM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_KM.php new file mode 100644 index 0000000000..e790b99e63 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_KM.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_KW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_KW.php new file mode 100644 index 0000000000..b3fb1cfec8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_KW.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - Nusret Parlak + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + * - Abdullah-Alhariri + */ +$months = [ + 'ูŠู†ุงูŠุฑ', + 'ูุจุฑุงูŠุฑ', + 'ู…ุงุฑุณ', + 'ุฃุจุฑูŠู„', + 'ู…ุงูŠ', + 'ูŠูˆู†ูŠูˆ', + 'ูŠูˆู„ูŠูˆุฒ', + 'ุบุดุช', + 'ุดุชู†ุจุฑ', + 'ุฃูƒุชูˆุจุฑ', + 'ู†ูˆู†ุจุฑ', + 'ุฏุฌู†ุจุฑ', +]; + +return [ + 'year' => implode('|', ['{0}:count ุณู†ุฉ', '{1}ุณู†ุฉ', '{2}ุณู†ุชูŠู†', ']2,11[:count ุณู†ูˆุงุช', ']10,Inf[:count ุณู†ุฉ']), + 'a_year' => implode('|', ['{0}:count ุณู†ุฉ', '{1}ุณู†ุฉ', '{2}ุณู†ุชูŠู†', ']2,11[:count ุณู†ูˆุงุช', ']10,Inf[:count ุณู†ุฉ']), + 'month' => implode('|', ['{0}:count ุดู‡ุฑ', '{1}ุดู‡ุฑ', '{2}ุดู‡ุฑูŠู†', ']2,11[:count ุฃุดู‡ุฑ', ']10,Inf[:count ุดู‡ุฑ']), + 'a_month' => implode('|', ['{0}:count ุดู‡ุฑ', '{1}ุดู‡ุฑ', '{2}ุดู‡ุฑูŠู†', ']2,11[:count ุฃุดู‡ุฑ', ']10,Inf[:count ุดู‡ุฑ']), + 'week' => implode('|', ['{0}:count ุฃุณุจูˆุน', '{1}ุฃุณุจูˆุน', '{2}ุฃุณุจูˆุนูŠู†', ']2,11[:count ุฃุณุงุจูŠุน', ']10,Inf[:count ุฃุณุจูˆุน']), + 'a_week' => implode('|', ['{0}:count ุฃุณุจูˆุน', '{1}ุฃุณุจูˆุน', '{2}ุฃุณุจูˆุนูŠู†', ']2,11[:count ุฃุณุงุจูŠุน', ']10,Inf[:count ุฃุณุจูˆุน']), + 'day' => implode('|', ['{0}:count ูŠูˆู…', '{1}ูŠูˆู…', '{2}ูŠูˆู…ูŠู†', ']2,11[:count ุฃูŠุงู…', ']10,Inf[:count ูŠูˆู…']), + 'a_day' => implode('|', ['{0}:count ูŠูˆู…', '{1}ูŠูˆู…', '{2}ูŠูˆู…ูŠู†', ']2,11[:count ุฃูŠุงู…', ']10,Inf[:count ูŠูˆู…']), + 'hour' => implode('|', ['{0}:count ุณุงุนุฉ', '{1}ุณุงุนุฉ', '{2}ุณุงุนุชูŠู†', ']2,11[:count ุณุงุนุงุช', ']10,Inf[:count ุณุงุนุฉ']), + 'a_hour' => implode('|', ['{0}:count ุณุงุนุฉ', '{1}ุณุงุนุฉ', '{2}ุณุงุนุชูŠู†', ']2,11[:count ุณุงุนุงุช', ']10,Inf[:count ุณุงุนุฉ']), + 'minute' => implode('|', ['{0}:count ุฏู‚ูŠู‚ุฉ', '{1}ุฏู‚ูŠู‚ุฉ', '{2}ุฏู‚ูŠู‚ุชูŠู†', ']2,11[:count ุฏู‚ุงุฆู‚', ']10,Inf[:count ุฏู‚ูŠู‚ุฉ']), + 'a_minute' => implode('|', ['{0}:count ุฏู‚ูŠู‚ุฉ', '{1}ุฏู‚ูŠู‚ุฉ', '{2}ุฏู‚ูŠู‚ุชูŠู†', ']2,11[:count ุฏู‚ุงุฆู‚', ']10,Inf[:count ุฏู‚ูŠู‚ุฉ']), + 'second' => implode('|', ['{0}:count ุซุงู†ูŠุฉ', '{1}ุซุงู†ูŠุฉ', '{2}ุซุงู†ูŠุชูŠู†', ']2,11[:count ุซูˆุงู†ูŠ', ']10,Inf[:count ุซุงู†ูŠุฉ']), + 'a_second' => implode('|', ['{0}:count ุซุงู†ูŠุฉ', '{1}ุซุงู†ูŠุฉ', '{2}ุซุงู†ูŠุชูŠู†', ']2,11[:count ุซูˆุงู†ูŠ', ']10,Inf[:count ุซุงู†ูŠุฉ']), + 'ago' => 'ู…ู†ุฐ :time', + 'from_now' => 'ููŠ :time', + 'after' => 'ุจุนุฏ :time', + 'before' => 'ู‚ุจู„ :time', + 'diff_now' => 'ุงู„ุขู†', + 'diff_today' => 'ุงู„ูŠูˆู…', + 'diff_today_regexp' => 'ุงู„ูŠูˆู…(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_yesterday' => 'ุฃู…ุณ', + 'diff_yesterday_regexp' => 'ุฃู…ุณ(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_tomorrow' => 'ุบุฏุงู‹', + 'diff_tomorrow_regexp' => 'ุบุฏุง(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_before_yesterday' => 'ู‚ุจู„ ุงู„ุฃู…ุณ', + 'diff_after_tomorrow' => 'ุจุนุฏ ุบุฏ', + 'period_recurrences' => implode('|', ['{0}ู…ุฑุฉ', '{1}ู…ุฑุฉ', '{2}:count ู…ุฑุชูŠู†', ']2,11[:count ู…ุฑุงุช', ']10,Inf[:count ู…ุฑุฉ']), + 'period_interval' => 'ูƒู„ :interval', + 'period_start_date' => 'ู…ู† :date', + 'period_end_date' => 'ุฅู„ู‰ :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุฃุญุฏ', 'ุงุซู†ูŠู†', 'ุซู„ุงุซุงุก', 'ุฃุฑุจุนุงุก', 'ุฎู…ูŠุณ', 'ุฌู…ุนุฉ', 'ุณุจุช'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'list' => ['ุŒ ', ' ูˆ '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ุงู„ูŠูˆู… ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'nextDay' => '[ุบุฏุง ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'nextWeek' => 'dddd [ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'lastDay' => '[ุฃู…ุณ ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'lastWeek' => 'dddd [ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ุต', 'ู…'], + 'weekend' => [5, 6], + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐูค', 'ฐูฅ', 'ฐูฆ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑูค', 'ฑูฅ', 'ฑูฆ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒูค', 'ฒูฅ', 'ฒูฆ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณูค', 'ณูฅ', 'ณูฆ', 'ณท', 'ณธ', 'ณน', 'ูคฐ', 'ูคฑ', 'ูคฒ', 'ูคณ', 'ูคูค', 'ูคูฅ', 'ูคูฆ', 'ูคท', 'ูคธ', 'ูคน', 'ูฅฐ', 'ูฅฑ', 'ูฅฒ', 'ูฅณ', 'ูฅูค', 'ูฅูฅ', 'ูฅูฆ', 'ูฅท', 'ูฅธ', 'ูฅน', 'ูฆฐ', 'ูฆฑ', 'ูฆฒ', 'ูฆณ', 'ูฆูค', 'ูฆูฅ', 'ูฆูฆ', 'ูฆท', 'ูฆธ', 'ูฆน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทูค', 'ทูฅ', 'ทูฆ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธูค', 'ธูฅ', 'ธูฆ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นูค', 'นูฅ', 'นูฆ', 'นท', 'นธ', 'นน'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_LB.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_LB.php new file mode 100644 index 0000000000..2792745c6c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_LB.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['ูƒุงู†ูˆู† ุงู„ุซุงู†ูŠ', 'ุดุจุงุท', 'ุขุฐุงุฑ', 'ู†ูŠุณุงู†', 'ุฃูŠุงุฑ', 'ุญุฒูŠุฑุงู†', 'ุชู…ูˆุฒ', 'ุขุจ', 'ุฃูŠู„ูˆู„', 'ุชุดุฑูŠู† ุงู„ุฃูˆู„', 'ุชุดุฑูŠู† ุงู„ุซุงู†ูŠ', 'ูƒุงู†ูˆู† ุงู„ุฃูˆู„'], + 'months_short' => ['ูƒุงู†ูˆู† ุงู„ุซุงู†ูŠ', 'ุดุจุงุท', 'ุขุฐุงุฑ', 'ู†ูŠุณุงู†', 'ุฃูŠุงุฑ', 'ุญุฒูŠุฑุงู†', 'ุชู…ูˆุฒ', 'ุขุจ', 'ุฃูŠู„ูˆู„', 'ุชุดุฑูŠู† ุงู„ุฃูˆู„', 'ุชุดุฑูŠู† ุงู„ุซุงู†ูŠ', 'ูƒุงู†ูˆู† ุงู„ุฃูˆู„'], + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐูค', 'ฐูฅ', 'ฐูฆ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑูค', 'ฑูฅ', 'ฑูฆ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒูค', 'ฒูฅ', 'ฒูฆ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณูค', 'ณูฅ', 'ณูฆ', 'ณท', 'ณธ', 'ณน', 'ูคฐ', 'ูคฑ', 'ูคฒ', 'ูคณ', 'ูคูค', 'ูคูฅ', 'ูคูฆ', 'ูคท', 'ูคธ', 'ูคน', 'ูฅฐ', 'ูฅฑ', 'ูฅฒ', 'ูฅณ', 'ูฅูค', 'ูฅูฅ', 'ูฅูฆ', 'ูฅท', 'ูฅธ', 'ูฅน', 'ูฆฐ', 'ูฆฑ', 'ูฆฒ', 'ูฆณ', 'ูฆูค', 'ูฆูฅ', 'ูฆูฆ', 'ูฆท', 'ูฆธ', 'ูฆน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทูค', 'ทูฅ', 'ทูฆ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธูค', 'ธูฅ', 'ธูฆ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นูค', 'นูฅ', 'นูฆ', 'นท', 'นธ', 'นน'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_LY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_LY.php new file mode 100644 index 0000000000..1f0af49d64 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_LY.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Atef Ben Ali (atefBB) + * - Ibrahim AshShohail + * - MLTDev + */ + +$months = [ + 'ูŠู†ุงูŠุฑ', + 'ูุจุฑุงูŠุฑ', + 'ู…ุงุฑุณ', + 'ุฃุจุฑูŠู„', + 'ู…ุงูŠูˆ', + 'ูŠูˆู†ูŠูˆ', + 'ูŠูˆู„ูŠูˆ', + 'ุฃุบุณุทุณ', + 'ุณุจุชู…ุจุฑ', + 'ุฃูƒุชูˆุจุฑ', + 'ู†ูˆูู…ุจุฑ', + 'ุฏูŠุณู…ุจุฑ', +]; + +return [ + 'year' => implode('|', [':count ุณู†ุฉ', 'ุณู†ุฉ', 'ุณู†ุชูŠู†', ':count ุณู†ูˆุงุช', ':count ุณู†ุฉ']), + 'a_year' => implode('|', [':count ุณู†ุฉ', 'ุณู†ุฉ', 'ุณู†ุชูŠู†', ':count ุณู†ูˆุงุช', ':count ุณู†ุฉ']), + 'month' => implode('|', [':count ุดู‡ุฑ', 'ุดู‡ุฑ', 'ุดู‡ุฑูŠู†', ':count ุฃุดู‡ุฑ', ':count ุดู‡ุฑ']), + 'a_month' => implode('|', [':count ุดู‡ุฑ', 'ุดู‡ุฑ', 'ุดู‡ุฑูŠู†', ':count ุฃุดู‡ุฑ', ':count ุดู‡ุฑ']), + 'week' => implode('|', [':count ุฃุณุจูˆุน', 'ุฃุณุจูˆุน', 'ุฃุณุจูˆุนูŠู†', ':count ุฃุณุงุจูŠุน', ':count ุฃุณุจูˆุน']), + 'a_week' => implode('|', [':count ุฃุณุจูˆุน', 'ุฃุณุจูˆุน', 'ุฃุณุจูˆุนูŠู†', ':count ุฃุณุงุจูŠุน', ':count ุฃุณุจูˆุน']), + 'day' => implode('|', [':count ูŠูˆู…', 'ูŠูˆู…', 'ูŠูˆู…ูŠู†', ':count ุฃูŠุงู…', ':count ูŠูˆู…']), + 'a_day' => implode('|', [':count ูŠูˆู…', 'ูŠูˆู…', 'ูŠูˆู…ูŠู†', ':count ุฃูŠุงู…', ':count ูŠูˆู…']), + 'hour' => implode('|', [':count ุณุงุนุฉ', 'ุณุงุนุฉ', 'ุณุงุนุชูŠู†', ':count ุณุงุนุงุช', ':count ุณุงุนุฉ']), + 'a_hour' => implode('|', [':count ุณุงุนุฉ', 'ุณุงุนุฉ', 'ุณุงุนุชูŠู†', ':count ุณุงุนุงุช', ':count ุณุงุนุฉ']), + 'minute' => implode('|', [':count ุฏู‚ูŠู‚ุฉ', 'ุฏู‚ูŠู‚ุฉ', 'ุฏู‚ูŠู‚ุชูŠู†', ':count ุฏู‚ุงุฆู‚', ':count ุฏู‚ูŠู‚ุฉ']), + 'a_minute' => implode('|', [':count ุฏู‚ูŠู‚ุฉ', 'ุฏู‚ูŠู‚ุฉ', 'ุฏู‚ูŠู‚ุชูŠู†', ':count ุฏู‚ุงุฆู‚', ':count ุฏู‚ูŠู‚ุฉ']), + 'second' => implode('|', [':count ุซุงู†ูŠุฉ', 'ุซุงู†ูŠุฉ', 'ุซุงู†ูŠุชูŠู†', ':count ุซูˆุงู†ูŠ', ':count ุซุงู†ูŠุฉ']), + 'a_second' => implode('|', [':count ุซุงู†ูŠุฉ', 'ุซุงู†ูŠุฉ', 'ุซุงู†ูŠุชูŠู†', ':count ุซูˆุงู†ูŠ', ':count ุซุงู†ูŠุฉ']), + 'ago' => 'ู…ู†ุฐ :time', + 'from_now' => ':time ู…ู† ุงู„ุขู†', + 'after' => 'ุจุนุฏ :time', + 'before' => 'ู‚ุจู„ :time', + 'diff_now' => 'ุงู„ุขู†', + 'diff_today' => 'ุงู„ูŠูˆู…', + 'diff_today_regexp' => 'ุงู„ูŠูˆู…(?:\\s+ุนู†ุฏ)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_yesterday' => 'ุฃู…ุณ', + 'diff_yesterday_regexp' => 'ุฃู…ุณ(?:\\s+ุนู†ุฏ)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_tomorrow' => 'ุบุฏุงู‹', + 'diff_tomorrow_regexp' => 'ุบุฏู‹ุง(?:\\s+ุนู†ุฏ)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_before_yesterday' => 'ู‚ุจู„ ุงู„ุฃู…ุณ', + 'diff_after_tomorrow' => 'ุจุนุฏ ุบุฏ', + 'period_recurrences' => implode('|', ['ู…ุฑุฉ', 'ู…ุฑุฉ', ':count ู…ุฑุชูŠู†', ':count ู…ุฑุงุช', ':count ู…ุฑุฉ']), + 'period_interval' => 'ูƒู„ :interval', + 'period_start_date' => 'ู…ู† :date', + 'period_end_date' => 'ุฅู„ู‰ :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุฃุญุฏ', 'ุงุซู†ูŠู†', 'ุซู„ุงุซุงุก', 'ุฃุฑุจุนุงุก', 'ุฎู…ูŠุณ', 'ุฌู…ุนุฉ', 'ุณุจุช'], + 'weekdays_min' => ['ุญ', 'ุงุซ', 'ุซู„', 'ุฃุฑ', 'ุฎู…', 'ุฌ', 'ุณ'], + 'list' => ['ุŒ ', ' ูˆ '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ุงู„ูŠูˆู… ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'nextDay' => '[ุบุฏู‹ุง ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'nextWeek' => 'dddd [ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'lastDay' => '[ุฃู…ุณ ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'lastWeek' => 'dddd [ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ุต', 'ู…'], + 'weekend' => [5, 6], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_MA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_MA.php new file mode 100644 index 0000000000..047ae05af3 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_MA.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +$months = [ + 'ูŠู†ุงูŠุฑ', + 'ูุจุฑุงูŠุฑ', + 'ู…ุงุฑุณ', + 'ุฃุจุฑูŠู„', + 'ู…ุงูŠ', + 'ูŠูˆู†ูŠูˆ', + 'ูŠูˆู„ูŠูˆุฒ', + 'ุบุดุช', + 'ุดุชู†ุจุฑ', + 'ุฃูƒุชูˆุจุฑ', + 'ู†ูˆู†ุจุฑ', + 'ุฏุฌู†ุจุฑ', +]; + +return [ + 'year' => implode('|', ['{0}:count ุณู†ุฉ', '{1}ุณู†ุฉ', '{2}ุณู†ุชูŠู†', ']2,11[:count ุณู†ูˆุงุช', ']10,Inf[:count ุณู†ุฉ']), + 'a_year' => implode('|', ['{0}:count ุณู†ุฉ', '{1}ุณู†ุฉ', '{2}ุณู†ุชูŠู†', ']2,11[:count ุณู†ูˆุงุช', ']10,Inf[:count ุณู†ุฉ']), + 'month' => implode('|', ['{0}:count ุดู‡ุฑ', '{1}ุดู‡ุฑ', '{2}ุดู‡ุฑูŠู†', ']2,11[:count ุฃุดู‡ุฑ', ']10,Inf[:count ุดู‡ุฑ']), + 'a_month' => implode('|', ['{0}:count ุดู‡ุฑ', '{1}ุดู‡ุฑ', '{2}ุดู‡ุฑูŠู†', ']2,11[:count ุฃุดู‡ุฑ', ']10,Inf[:count ุดู‡ุฑ']), + 'week' => implode('|', ['{0}:count ุฃุณุจูˆุน', '{1}ุฃุณุจูˆุน', '{2}ุฃุณุจูˆุนูŠู†', ']2,11[:count ุฃุณุงุจูŠุน', ']10,Inf[:count ุฃุณุจูˆุน']), + 'a_week' => implode('|', ['{0}:count ุฃุณุจูˆุน', '{1}ุฃุณุจูˆุน', '{2}ุฃุณุจูˆุนูŠู†', ']2,11[:count ุฃุณุงุจูŠุน', ']10,Inf[:count ุฃุณุจูˆุน']), + 'day' => implode('|', ['{0}:count ูŠูˆู…', '{1}ูŠูˆู…', '{2}ูŠูˆู…ูŠู†', ']2,11[:count ุฃูŠุงู…', ']10,Inf[:count ูŠูˆู…']), + 'a_day' => implode('|', ['{0}:count ูŠูˆู…', '{1}ูŠูˆู…', '{2}ูŠูˆู…ูŠู†', ']2,11[:count ุฃูŠุงู…', ']10,Inf[:count ูŠูˆู…']), + 'hour' => implode('|', ['{0}:count ุณุงุนุฉ', '{1}ุณุงุนุฉ', '{2}ุณุงุนุชูŠู†', ']2,11[:count ุณุงุนุงุช', ']10,Inf[:count ุณุงุนุฉ']), + 'a_hour' => implode('|', ['{0}:count ุณุงุนุฉ', '{1}ุณุงุนุฉ', '{2}ุณุงุนุชูŠู†', ']2,11[:count ุณุงุนุงุช', ']10,Inf[:count ุณุงุนุฉ']), + 'minute' => implode('|', ['{0}:count ุฏู‚ูŠู‚ุฉ', '{1}ุฏู‚ูŠู‚ุฉ', '{2}ุฏู‚ูŠู‚ุชูŠู†', ']2,11[:count ุฏู‚ุงุฆู‚', ']10,Inf[:count ุฏู‚ูŠู‚ุฉ']), + 'a_minute' => implode('|', ['{0}:count ุฏู‚ูŠู‚ุฉ', '{1}ุฏู‚ูŠู‚ุฉ', '{2}ุฏู‚ูŠู‚ุชูŠู†', ']2,11[:count ุฏู‚ุงุฆู‚', ']10,Inf[:count ุฏู‚ูŠู‚ุฉ']), + 'second' => implode('|', ['{0}:count ุซุงู†ูŠุฉ', '{1}ุซุงู†ูŠุฉ', '{2}ุซุงู†ูŠุชูŠู†', ']2,11[:count ุซูˆุงู†ูŠ', ']10,Inf[:count ุซุงู†ูŠุฉ']), + 'a_second' => implode('|', ['{0}:count ุซุงู†ูŠุฉ', '{1}ุซุงู†ูŠุฉ', '{2}ุซุงู†ูŠุชูŠู†', ']2,11[:count ุซูˆุงู†ูŠ', ']10,Inf[:count ุซุงู†ูŠุฉ']), + 'ago' => 'ู…ู†ุฐ :time', + 'from_now' => 'ููŠ :time', + 'after' => 'ุจุนุฏ :time', + 'before' => 'ู‚ุจู„ :time', + 'diff_now' => 'ุงู„ุขู†', + 'diff_today' => 'ุงู„ูŠูˆู…', + 'diff_today_regexp' => 'ุงู„ูŠูˆู…(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_yesterday' => 'ุฃู…ุณ', + 'diff_yesterday_regexp' => 'ุฃู…ุณ(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_tomorrow' => 'ุบุฏุงู‹', + 'diff_tomorrow_regexp' => 'ุบุฏุง(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_before_yesterday' => 'ู‚ุจู„ ุงู„ุฃู…ุณ', + 'diff_after_tomorrow' => 'ุจุนุฏ ุบุฏ', + 'period_recurrences' => implode('|', ['{0}ู…ุฑุฉ', '{1}ู…ุฑุฉ', '{2}:count ู…ุฑุชูŠู†', ']2,11[:count ู…ุฑุงุช', ']10,Inf[:count ู…ุฑุฉ']), + 'period_interval' => 'ูƒู„ :interval', + 'period_start_date' => 'ู…ู† :date', + 'period_end_date' => 'ุฅู„ู‰ :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุฃุญุฏ', 'ุงุซู†ูŠู†', 'ุซู„ุงุซุงุก', 'ุฃุฑุจุนุงุก', 'ุฎู…ูŠุณ', 'ุฌู…ุนุฉ', 'ุณุจุช'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'list' => ['ุŒ ', ' ูˆ '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ุงู„ูŠูˆู… ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'nextDay' => '[ุบุฏุง ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'nextWeek' => 'dddd [ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'lastDay' => '[ุฃู…ุณ ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'lastWeek' => 'dddd [ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ุต', 'ู…'], + 'weekend' => [5, 6], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_MR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_MR.php new file mode 100644 index 0000000000..e790b99e63 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_MR.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_OM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_OM.php new file mode 100644 index 0000000000..35180965a4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_OM.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['ูŠู†ุงูŠุฑ', 'ูุจุฑุงูŠุฑ', 'ู…ุงุฑุณ', 'ุฃุจุฑูŠู„', 'ู…ุงูŠูˆ', 'ูŠูˆู†ูŠูˆ', 'ูŠูˆู„ูŠูˆ', 'ุฃุบุณุทุณ', 'ุณุจุชู…ุจุฑ', 'ุฃูƒุชูˆุจุฑ', 'ู†ูˆูู…ุจุฑ', 'ุฏูŠุณู…ุจุฑ'], + 'months_short' => ['ูŠู†ุง', 'ูุจุฑ', 'ู…ุงุฑ', 'ุฃุจุฑ', 'ู…ุงูŠ', 'ูŠูˆู†', 'ูŠูˆู„', 'ุฃุบุณ', 'ุณุจุช', 'ุฃูƒุช', 'ู†ูˆู', 'ุฏูŠุณ'], + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐูค', 'ฐูฅ', 'ฐูฆ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑูค', 'ฑูฅ', 'ฑูฆ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒูค', 'ฒูฅ', 'ฒูฆ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณูค', 'ณูฅ', 'ณูฆ', 'ณท', 'ณธ', 'ณน', 'ูคฐ', 'ูคฑ', 'ูคฒ', 'ูคณ', 'ูคูค', 'ูคูฅ', 'ูคูฆ', 'ูคท', 'ูคธ', 'ูคน', 'ูฅฐ', 'ูฅฑ', 'ูฅฒ', 'ูฅณ', 'ูฅูค', 'ูฅูฅ', 'ูฅูฆ', 'ูฅท', 'ูฅธ', 'ูฅน', 'ูฆฐ', 'ูฆฑ', 'ูฆฒ', 'ูฆณ', 'ูฆูค', 'ูฆูฅ', 'ูฆูฆ', 'ูฆท', 'ูฆธ', 'ูฆน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทูค', 'ทูฅ', 'ทูฆ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธูค', 'ธูฅ', 'ธูฆ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นูค', 'นูฅ', 'นูฆ', 'นท', 'นธ', 'นน'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_PS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_PS.php new file mode 100644 index 0000000000..503c60d2ed --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_PS.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐูค', 'ฐูฅ', 'ฐูฆ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑูค', 'ฑูฅ', 'ฑูฆ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒูค', 'ฒูฅ', 'ฒูฆ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณูค', 'ณูฅ', 'ณูฆ', 'ณท', 'ณธ', 'ณน', 'ูคฐ', 'ูคฑ', 'ูคฒ', 'ูคณ', 'ูคูค', 'ูคูฅ', 'ูคูฆ', 'ูคท', 'ูคธ', 'ูคน', 'ูฅฐ', 'ูฅฑ', 'ูฅฒ', 'ูฅณ', 'ูฅูค', 'ูฅูฅ', 'ูฅูฆ', 'ูฅท', 'ูฅธ', 'ูฅน', 'ูฆฐ', 'ูฆฑ', 'ูฆฒ', 'ูฆณ', 'ูฆูค', 'ูฆูฅ', 'ูฆูฆ', 'ูฆท', 'ูฆธ', 'ูฆน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทูค', 'ทูฅ', 'ทูฆ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธูค', 'ธูฅ', 'ธูฆ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นูค', 'นูฅ', 'นูฆ', 'นท', 'นธ', 'นน'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_QA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_QA.php new file mode 100644 index 0000000000..35180965a4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_QA.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['ูŠู†ุงูŠุฑ', 'ูุจุฑุงูŠุฑ', 'ู…ุงุฑุณ', 'ุฃุจุฑูŠู„', 'ู…ุงูŠูˆ', 'ูŠูˆู†ูŠูˆ', 'ูŠูˆู„ูŠูˆ', 'ุฃุบุณุทุณ', 'ุณุจุชู…ุจุฑ', 'ุฃูƒุชูˆุจุฑ', 'ู†ูˆูู…ุจุฑ', 'ุฏูŠุณู…ุจุฑ'], + 'months_short' => ['ูŠู†ุง', 'ูุจุฑ', 'ู…ุงุฑ', 'ุฃุจุฑ', 'ู…ุงูŠ', 'ูŠูˆู†', 'ูŠูˆู„', 'ุฃุบุณ', 'ุณุจุช', 'ุฃูƒุช', 'ู†ูˆู', 'ุฏูŠุณ'], + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐูค', 'ฐูฅ', 'ฐูฆ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑูค', 'ฑูฅ', 'ฑูฆ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒูค', 'ฒูฅ', 'ฒูฆ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณูค', 'ณูฅ', 'ณูฆ', 'ณท', 'ณธ', 'ณน', 'ูคฐ', 'ูคฑ', 'ูคฒ', 'ูคณ', 'ูคูค', 'ูคูฅ', 'ูคูฆ', 'ูคท', 'ูคธ', 'ูคน', 'ูฅฐ', 'ูฅฑ', 'ูฅฒ', 'ูฅณ', 'ูฅูค', 'ูฅูฅ', 'ูฅูฆ', 'ูฅท', 'ูฅธ', 'ูฅน', 'ูฆฐ', 'ูฆฑ', 'ูฆฒ', 'ูฆณ', 'ูฆูค', 'ูฆูฅ', 'ูฆูฆ', 'ูฆท', 'ูฆธ', 'ูฆน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทูค', 'ทูฅ', 'ทูฆ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธูค', 'ธูฅ', 'ธูฆ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นูค', 'นูฅ', 'นูฆ', 'นท', 'นธ', 'นน'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SA.php new file mode 100644 index 0000000000..550b0c73a4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SA.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + * - Abdullah-Alhariri + */ +$months = [ + 'ูŠู†ุงูŠุฑ', + 'ูุจุฑุงูŠุฑ', + 'ู…ุงุฑุณ', + 'ุฃุจุฑูŠู„', + 'ู…ุงูŠูˆ', + 'ูŠูˆู†ูŠูˆ', + 'ูŠูˆู„ูŠูˆ', + 'ุฃุบุณุทุณ', + 'ุณุจุชู…ุจุฑ', + 'ุฃูƒุชูˆุจุฑ', + 'ู†ูˆูู…ุจุฑ', + 'ุฏูŠุณู…ุจุฑ', +]; + +return [ + 'year' => implode('|', ['{0}:count ุณู†ุฉ', '{1}ุณู†ุฉ', '{2}ุณู†ุชูŠู†', ']2,11[:count ุณู†ูˆุงุช', ']10,Inf[:count ุณู†ุฉ']), + 'a_year' => implode('|', ['{0}:count ุณู†ุฉ', '{1}ุณู†ุฉ', '{2}ุณู†ุชูŠู†', ']2,11[:count ุณู†ูˆุงุช', ']10,Inf[:count ุณู†ุฉ']), + 'month' => implode('|', ['{0}:count ุดู‡ุฑ', '{1}ุดู‡ุฑ', '{2}ุดู‡ุฑูŠู†', ']2,11[:count ุฃุดู‡ุฑ', ']10,Inf[:count ุดู‡ุฑ']), + 'a_month' => implode('|', ['{0}:count ุดู‡ุฑ', '{1}ุดู‡ุฑ', '{2}ุดู‡ุฑูŠู†', ']2,11[:count ุฃุดู‡ุฑ', ']10,Inf[:count ุดู‡ุฑ']), + 'week' => implode('|', ['{0}:count ุฃุณุจูˆุน', '{1}ุฃุณุจูˆุน', '{2}ุฃุณุจูˆุนูŠู†', ']2,11[:count ุฃุณุงุจูŠุน', ']10,Inf[:count ุฃุณุจูˆุน']), + 'a_week' => implode('|', ['{0}:count ุฃุณุจูˆุน', '{1}ุฃุณุจูˆุน', '{2}ุฃุณุจูˆุนูŠู†', ']2,11[:count ุฃุณุงุจูŠุน', ']10,Inf[:count ุฃุณุจูˆุน']), + 'day' => implode('|', ['{0}:count ูŠูˆู…', '{1}ูŠูˆู…', '{2}ูŠูˆู…ูŠู†', ']2,11[:count ุฃูŠุงู…', ']10,Inf[:count ูŠูˆู…']), + 'a_day' => implode('|', ['{0}:count ูŠูˆู…', '{1}ูŠูˆู…', '{2}ูŠูˆู…ูŠู†', ']2,11[:count ุฃูŠุงู…', ']10,Inf[:count ูŠูˆู…']), + 'hour' => implode('|', ['{0}:count ุณุงุนุฉ', '{1}ุณุงุนุฉ', '{2}ุณุงุนุชูŠู†', ']2,11[:count ุณุงุนุงุช', ']10,Inf[:count ุณุงุนุฉ']), + 'a_hour' => implode('|', ['{0}:count ุณุงุนุฉ', '{1}ุณุงุนุฉ', '{2}ุณุงุนุชูŠู†', ']2,11[:count ุณุงุนุงุช', ']10,Inf[:count ุณุงุนุฉ']), + 'minute' => implode('|', ['{0}:count ุฏู‚ูŠู‚ุฉ', '{1}ุฏู‚ูŠู‚ุฉ', '{2}ุฏู‚ูŠู‚ุชูŠู†', ']2,11[:count ุฏู‚ุงุฆู‚', ']10,Inf[:count ุฏู‚ูŠู‚ุฉ']), + 'a_minute' => implode('|', ['{0}:count ุฏู‚ูŠู‚ุฉ', '{1}ุฏู‚ูŠู‚ุฉ', '{2}ุฏู‚ูŠู‚ุชูŠู†', ']2,11[:count ุฏู‚ุงุฆู‚', ']10,Inf[:count ุฏู‚ูŠู‚ุฉ']), + 'second' => implode('|', ['{0}:count ุซุงู†ูŠุฉ', '{1}ุซุงู†ูŠุฉ', '{2}ุซุงู†ูŠุชูŠู†', ']2,11[:count ุซูˆุงู†ูŠ', ']10,Inf[:count ุซุงู†ูŠุฉ']), + 'a_second' => implode('|', ['{0}:count ุซุงู†ูŠุฉ', '{1}ุซุงู†ูŠุฉ', '{2}ุซุงู†ูŠุชูŠู†', ']2,11[:count ุซูˆุงู†ูŠ', ']10,Inf[:count ุซุงู†ูŠุฉ']), + 'ago' => 'ู…ู†ุฐ :time', + 'from_now' => 'ููŠ :time', + 'after' => 'ุจุนุฏ :time', + 'before' => 'ู‚ุจู„ :time', + 'diff_now' => 'ุงู„ุขู†', + 'diff_today' => 'ุงู„ูŠูˆู…', + 'diff_today_regexp' => 'ุงู„ูŠูˆู…(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_yesterday' => 'ุฃู…ุณ', + 'diff_yesterday_regexp' => 'ุฃู…ุณ(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_tomorrow' => 'ุบุฏุงู‹', + 'diff_tomorrow_regexp' => 'ุบุฏุง(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_before_yesterday' => 'ู‚ุจู„ ุงู„ุฃู…ุณ', + 'diff_after_tomorrow' => 'ุจุนุฏ ุบุฏ', + 'period_recurrences' => implode('|', ['{0}ู…ุฑุฉ', '{1}ู…ุฑุฉ', '{2}:count ู…ุฑุชูŠู†', ']2,11[:count ู…ุฑุงุช', ']10,Inf[:count ู…ุฑุฉ']), + 'period_interval' => 'ูƒู„ :interval', + 'period_start_date' => 'ู…ู† :date', + 'period_end_date' => 'ุฅู„ู‰ :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุฃุญุฏ', 'ุงุซู†ูŠู†', 'ุซู„ุงุซุงุก', 'ุฃุฑุจุนุงุก', 'ุฎู…ูŠุณ', 'ุฌู…ุนุฉ', 'ุณุจุช'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'list' => ['ุŒ ', ' ูˆ '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ุงู„ูŠูˆู… ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'nextDay' => '[ุบุฏุง ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'nextWeek' => 'dddd [ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'lastDay' => '[ุฃู…ุณ ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'lastWeek' => 'dddd [ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ุต', 'ู…'], + 'weekend' => [5, 6], + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐูค', 'ฐูฅ', 'ฐูฆ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑูค', 'ฑูฅ', 'ฑูฆ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒูค', 'ฒูฅ', 'ฒูฆ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณูค', 'ณูฅ', 'ณูฆ', 'ณท', 'ณธ', 'ณน', 'ูคฐ', 'ูคฑ', 'ูคฒ', 'ูคณ', 'ูคูค', 'ูคูฅ', 'ูคูฆ', 'ูคท', 'ูคธ', 'ูคน', 'ูฅฐ', 'ูฅฑ', 'ูฅฒ', 'ูฅณ', 'ูฅูค', 'ูฅูฅ', 'ูฅูฆ', 'ูฅท', 'ูฅธ', 'ูฅน', 'ูฆฐ', 'ูฆฑ', 'ูฆฒ', 'ูฆณ', 'ูฆูค', 'ูฆูฅ', 'ูฆูฆ', 'ูฆท', 'ูฆธ', 'ูฆน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทูค', 'ทูฅ', 'ทูฆ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธูค', 'ธูฅ', 'ธูฆ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นูค', 'นูฅ', 'นูฆ', 'นท', 'นธ', 'นน'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SD.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SD.php new file mode 100644 index 0000000000..35180965a4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SD.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['ูŠู†ุงูŠุฑ', 'ูุจุฑุงูŠุฑ', 'ู…ุงุฑุณ', 'ุฃุจุฑูŠู„', 'ู…ุงูŠูˆ', 'ูŠูˆู†ูŠูˆ', 'ูŠูˆู„ูŠูˆ', 'ุฃุบุณุทุณ', 'ุณุจุชู…ุจุฑ', 'ุฃูƒุชูˆุจุฑ', 'ู†ูˆูู…ุจุฑ', 'ุฏูŠุณู…ุจุฑ'], + 'months_short' => ['ูŠู†ุง', 'ูุจุฑ', 'ู…ุงุฑ', 'ุฃุจุฑ', 'ู…ุงูŠ', 'ูŠูˆู†', 'ูŠูˆู„', 'ุฃุบุณ', 'ุณุจุช', 'ุฃูƒุช', 'ู†ูˆู', 'ุฏูŠุณ'], + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐูค', 'ฐูฅ', 'ฐูฆ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑูค', 'ฑูฅ', 'ฑูฆ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒูค', 'ฒูฅ', 'ฒูฆ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณูค', 'ณูฅ', 'ณูฆ', 'ณท', 'ณธ', 'ณน', 'ูคฐ', 'ูคฑ', 'ูคฒ', 'ูคณ', 'ูคูค', 'ูคูฅ', 'ูคูฆ', 'ูคท', 'ูคธ', 'ูคน', 'ูฅฐ', 'ูฅฑ', 'ูฅฒ', 'ูฅณ', 'ูฅูค', 'ูฅูฅ', 'ูฅูฆ', 'ูฅท', 'ูฅธ', 'ูฅน', 'ูฆฐ', 'ูฆฑ', 'ูฆฒ', 'ูฆณ', 'ูฆูค', 'ูฆูฅ', 'ูฆูฆ', 'ูฆท', 'ูฆธ', 'ูฆน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทูค', 'ทูฅ', 'ทูฆ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธูค', 'ธูฅ', 'ธูฆ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นูค', 'นูฅ', 'นูฆ', 'นท', 'นธ', 'นน'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SO.php new file mode 100644 index 0000000000..e790b99e63 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SO.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SS.php new file mode 100644 index 0000000000..32f32825c0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SS.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['ูŠู†ุงูŠุฑ', 'ูุจุฑุงูŠุฑ', 'ู…ุงุฑุณ', 'ุฃุจุฑูŠู„', 'ู…ุงูŠูˆ', 'ูŠูˆู†ูŠูˆ', 'ูŠูˆู„ูŠูˆ', 'ุฃุบุณุทุณ', 'ุณุจุชู…ุจุฑ', 'ุฃูƒุชูˆุจุฑ', 'ู†ูˆูู…ุจุฑ', 'ุฏูŠุณู…ุจุฑ'], + 'months_short' => ['ูŠู†ุง', 'ูุจุฑ', 'ู…ุงุฑ', 'ุฃุจุฑ', 'ู…ุงูŠ', 'ูŠูˆู†', 'ูŠูˆู„', 'ุฃุบุณ', 'ุณุจุช', 'ุฃูƒุช', 'ู†ูˆู', 'ุฏูŠุณ'], + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SY.php new file mode 100644 index 0000000000..2d4200845f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SY.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['ูƒุงู†ูˆู† ุงู„ุซุงู†ูŠ', 'ุดุจุงุท', 'ุขุฐุงุฑ', 'ู†ูŠุณุงู†', 'ุฃูŠุงุฑ', 'ุญุฒูŠุฑุงู†', 'ุชู…ูˆุฒ', 'ุขุจ', 'ุฃูŠู„ูˆู„', 'ุชุดุฑูŠู† ุงู„ุฃูˆู„', 'ุชุดุฑูŠู† ุงู„ุซุงู†ูŠ', 'ูƒุงู†ูˆู† ุงู„ุฃูˆู„'], + 'months_short' => ['ูƒุงู†ูˆู† ุงู„ุซุงู†ูŠ', 'ุดุจุงุท', 'ุขุฐุงุฑ', 'ู†ูŠุณุงู†', 'ุฃูŠุงุฑ', 'ุญุฒูŠุฑุงู†', 'ุชู…ูˆุฒ', 'ุขุจ', 'ุฃูŠู„ูˆู„', 'ุชุดุฑูŠู† ุงู„ุฃูˆู„', 'ุชุดุฑูŠู† ุงู„ุซุงู†ูŠ', 'ูƒุงู†ูˆู† ุงู„ุฃูˆู„'], + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐูค', 'ฐูฅ', 'ฐูฆ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑูค', 'ฑูฅ', 'ฑูฆ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒูค', 'ฒูฅ', 'ฒูฆ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณูค', 'ณูฅ', 'ณูฆ', 'ณท', 'ณธ', 'ณน', 'ูคฐ', 'ูคฑ', 'ูคฒ', 'ูคณ', 'ูคูค', 'ูคูฅ', 'ูคูฆ', 'ูคท', 'ูคธ', 'ูคน', 'ูฅฐ', 'ูฅฑ', 'ูฅฒ', 'ูฅณ', 'ูฅูค', 'ูฅูฅ', 'ูฅูฆ', 'ูฅท', 'ูฅธ', 'ูฅน', 'ูฆฐ', 'ูฆฑ', 'ูฆฒ', 'ูฆณ', 'ูฆูค', 'ูฆูฅ', 'ูฆูฆ', 'ูฆท', 'ูฆธ', 'ูฆน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทูค', 'ทูฅ', 'ทูฆ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธูค', 'ธูฅ', 'ธูฆ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นูค', 'นูฅ', 'นูฆ', 'นท', 'นธ', 'นน'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php new file mode 100644 index 0000000000..c2d4b43d0a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Abdellah Chadidi + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +// Same for long and short +$months = [ + // @TODO add shakl to months + 'ูŠู†ุงูŠุฑ', + 'ูุจุฑุงูŠุฑ', + 'ู…ุงุฑุณ', + 'ุฃุจุฑูŠู„', + 'ู…ุงูŠูˆ', + 'ูŠูˆู†ูŠูˆ', + 'ูŠูˆู„ูŠูˆ', + 'ุฃุบุณุทุณ', + 'ุณุจุชู…ุจุฑ', + 'ุฃูƒุชูˆุจุฑ', + 'ู†ูˆูู…ุจุฑ', + 'ุฏูŠุณู…ุจุฑ', +]; + +return [ + 'year' => implode('|', ['{0}:count ุณูŽู†ูŽุฉ', '{1}ุณูŽู†ูŽุฉ', '{2}ุณูŽู†ูŽุชูŽูŠู’ู†', ']2,11[:count ุณูŽู†ูŽูˆูŽุงุช', ']10,Inf[:count ุณูŽู†ูŽุฉ']), + 'a_year' => implode('|', ['{0}:count ุณูŽู†ูŽุฉ', '{1}ุณูŽู†ูŽุฉ', '{2}ุณูŽู†ูŽุชูŽูŠู’ู†', ']2,11[:count ุณูŽู†ูŽูˆูŽุงุช', ']10,Inf[:count ุณูŽู†ูŽุฉ']), + 'month' => implode('|', ['{0}:count ุดูŽู‡ู’ุฑูŽ', '{1}ุดูŽู‡ู’ุฑูŽ', '{2}ุดูŽู‡ู’ุฑูŽูŠู’ู†', ']2,11[:count ุฃูŽุดู’ู‡ูุฑ', ']10,Inf[:count ุดูŽู‡ู’ุฑูŽ']), + 'a_month' => implode('|', ['{0}:count ุดูŽู‡ู’ุฑูŽ', '{1}ุดูŽู‡ู’ุฑูŽ', '{2}ุดูŽู‡ู’ุฑูŽูŠู’ู†', ']2,11[:count ุฃูŽุดู’ู‡ูุฑ', ']10,Inf[:count ุดูŽู‡ู’ุฑูŽ']), + 'week' => implode('|', ['{0}:count ุฃูุณู’ุจููˆุน', '{1}ุฃูุณู’ุจููˆุน', '{2}ุฃูุณู’ุจููˆุนูŽูŠู’ู†', ']2,11[:count ุฃูŽุณูŽุงุจููŠุน', ']10,Inf[:count ุฃูุณู’ุจููˆุน']), + 'a_week' => implode('|', ['{0}:count ุฃูุณู’ุจููˆุน', '{1}ุฃูุณู’ุจููˆุน', '{2}ุฃูุณู’ุจููˆุนูŽูŠู’ู†', ']2,11[:count ุฃูŽุณูŽุงุจููŠุน', ']10,Inf[:count ุฃูุณู’ุจููˆุน']), + 'day' => implode('|', ['{0}:count ูŠูŽูˆู’ู…', '{1}ูŠูŽูˆู’ู…', '{2}ูŠูŽูˆู’ู…ูŽูŠู’ู†', ']2,11[:count ุฃูŽูŠู‘ูŽุงู…', ']10,Inf[:count ูŠูŽูˆู’ู…']), + 'a_day' => implode('|', ['{0}:count ูŠูŽูˆู’ู…', '{1}ูŠูŽูˆู’ู…', '{2}ูŠูŽูˆู’ู…ูŽูŠู’ู†', ']2,11[:count ุฃูŽูŠู‘ูŽุงู…', ']10,Inf[:count ูŠูŽูˆู’ู…']), + 'hour' => implode('|', ['{0}:count ุณูŽุงุนูŽุฉ', '{1}ุณูŽุงุนูŽุฉ', '{2}ุณูŽุงุนูŽุชูŽูŠู’ู†', ']2,11[:count ุณูŽุงุนูŽุงุช', ']10,Inf[:count ุณูŽุงุนูŽุฉ']), + 'a_hour' => implode('|', ['{0}:count ุณูŽุงุนูŽุฉ', '{1}ุณูŽุงุนูŽุฉ', '{2}ุณูŽุงุนูŽุชูŽูŠู’ู†', ']2,11[:count ุณูŽุงุนูŽุงุช', ']10,Inf[:count ุณูŽุงุนูŽุฉ']), + 'minute' => implode('|', ['{0}:count ุฏูŽู‚ููŠู‚ูŽุฉ', '{1}ุฏูŽู‚ููŠู‚ูŽุฉ', '{2}ุฏูŽู‚ููŠู‚ูŽุชูŽูŠู’ู†', ']2,11[:count ุฏูŽู‚ูŽุงุฆูู‚', ']10,Inf[:count ุฏูŽู‚ููŠู‚ูŽุฉ']), + 'a_minute' => implode('|', ['{0}:count ุฏูŽู‚ููŠู‚ูŽุฉ', '{1}ุฏูŽู‚ููŠู‚ูŽุฉ', '{2}ุฏูŽู‚ููŠู‚ูŽุชูŽูŠู’ู†', ']2,11[:count ุฏูŽู‚ูŽุงุฆูู‚', ']10,Inf[:count ุฏูŽู‚ููŠู‚ูŽุฉ']), + 'second' => implode('|', ['{0}:count ุซูŽุงู†ููŠูŽุฉ', '{1}ุซูŽุงู†ููŠูŽุฉ', '{2}ุซูŽุงู†ููŠูŽุชูŽูŠู’ู†', ']2,11[:count ุซูŽูˆูŽุงู†', ']10,Inf[:count ุซูŽุงู†ููŠูŽุฉ']), + 'a_second' => implode('|', ['{0}:count ุซูŽุงู†ููŠูŽุฉ', '{1}ุซูŽุงู†ููŠูŽุฉ', '{2}ุซูŽุงู†ููŠูŽุชูŽูŠู’ู†', ']2,11[:count ุซูŽูˆูŽุงู†', ']10,Inf[:count ุซูŽุงู†ููŠูŽุฉ']), + 'ago' => 'ู…ูู†ู’ุฐู :time', + 'from_now' => 'ู…ูู†ูŽ ุงู„ู’ุขู† :time', + 'after' => 'ุจูŽุนู’ุฏูŽ :time', + 'before' => 'ู‚ูŽุจู’ู„ูŽ :time', + + // @TODO add shakl to translations below + 'diff_now' => 'ุงู„ุขู†', + 'diff_today' => 'ุงู„ูŠูˆู…', + 'diff_today_regexp' => 'ุงู„ูŠูˆู…(?:\\s+ุนู†ุฏ)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_yesterday' => 'ุฃู…ุณ', + 'diff_yesterday_regexp' => 'ุฃู…ุณ(?:\\s+ุนู†ุฏ)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_tomorrow' => 'ุบุฏุงู‹', + 'diff_tomorrow_regexp' => 'ุบุฏู‹ุง(?:\\s+ุนู†ุฏ)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_before_yesterday' => 'ู‚ุจู„ ุงู„ุฃู…ุณ', + 'diff_after_tomorrow' => 'ุจุนุฏ ุบุฏ', + 'period_recurrences' => implode('|', ['{0}ู…ุฑุฉ', '{1}ู…ุฑุฉ', '{2}:count ู…ุฑุชูŠู†', ']2,11[:count ู…ุฑุงุช', ']10,Inf[:count ู…ุฑุฉ']), + 'period_interval' => 'ูƒู„ :interval', + 'period_start_date' => 'ู…ู† :date', + 'period_end_date' => 'ุฅู„ู‰ :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุฃุญุฏ', 'ุงุซู†ูŠู†', 'ุซู„ุงุซุงุก', 'ุฃุฑุจุนุงุก', 'ุฎู…ูŠุณ', 'ุฌู…ุนุฉ', 'ุณุจุช'], + 'weekdays_min' => ['ุญ', 'ุงุซ', 'ุซู„', 'ุฃุฑ', 'ุฎู…', 'ุฌ', 'ุณ'], + 'list' => ['ุŒ ', ' ูˆ '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ุงู„ูŠูˆู… ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'nextDay' => '[ุบุฏู‹ุง ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'nextWeek' => 'dddd [ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'lastDay' => '[ุฃู…ุณ ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'lastWeek' => 'dddd [ุนู†ุฏ ุงู„ุณุงุนุฉ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ุต', 'ู…'], + 'weekend' => [5, 6], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_TD.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_TD.php new file mode 100644 index 0000000000..e790b99e63 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_TD.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_TN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_TN.php new file mode 100644 index 0000000000..f096678f4a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_TN.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +$months = [ + 'ุฌุงู†ููŠ', + 'ููŠูุฑูŠ', + 'ู…ุงุฑุณ', + 'ุฃูุฑูŠู„', + 'ู…ุงูŠ', + 'ุฌูˆุงู†', + 'ุฌูˆูŠู„ูŠุฉ', + 'ุฃูˆุช', + 'ุณุจุชู…ุจุฑ', + 'ุฃูƒุชูˆุจุฑ', + 'ู†ูˆูู…ุจุฑ', + 'ุฏูŠุณู…ุจุฑ', +]; + +return [ + 'year' => implode('|', ['{0}:count ุณู†ุฉ', '{1}ุณู†ุฉ', '{2}ุณู†ุชูŠู†', ']2,11[:count ุณู†ูˆุงุช', ']10,Inf[:count ุณู†ุฉ']), + 'a_year' => implode('|', ['{0}:count ุณู†ุฉ', '{1}ุณู†ุฉ', '{2}ุณู†ุชูŠู†', ']2,11[:count ุณู†ูˆุงุช', ']10,Inf[:count ุณู†ุฉ']), + 'month' => implode('|', ['{0}:count ุดู‡ุฑ', '{1}ุดู‡ุฑ', '{2}ุดู‡ุฑูŠู†', ']2,11[:count ุฃุดู‡ุฑ', ']10,Inf[:count ุดู‡ุฑ']), + 'a_month' => implode('|', ['{0}:count ุดู‡ุฑ', '{1}ุดู‡ุฑ', '{2}ุดู‡ุฑูŠู†', ']2,11[:count ุฃุดู‡ุฑ', ']10,Inf[:count ุดู‡ุฑ']), + 'week' => implode('|', ['{0}:count ุฃุณุจูˆุน', '{1}ุฃุณุจูˆุน', '{2}ุฃุณุจูˆุนูŠู†', ']2,11[:count ุฃุณุงุจูŠุน', ']10,Inf[:count ุฃุณุจูˆุน']), + 'a_week' => implode('|', ['{0}:count ุฃุณุจูˆุน', '{1}ุฃุณุจูˆุน', '{2}ุฃุณุจูˆุนูŠู†', ']2,11[:count ุฃุณุงุจูŠุน', ']10,Inf[:count ุฃุณุจูˆุน']), + 'day' => implode('|', ['{0}:count ูŠูˆู…', '{1}ูŠูˆู…', '{2}ูŠูˆู…ูŠู†', ']2,11[:count ุฃูŠุงู…', ']10,Inf[:count ูŠูˆู…']), + 'a_day' => implode('|', ['{0}:count ูŠูˆู…', '{1}ูŠูˆู…', '{2}ูŠูˆู…ูŠู†', ']2,11[:count ุฃูŠุงู…', ']10,Inf[:count ูŠูˆู…']), + 'hour' => implode('|', ['{0}:count ุณุงุนุฉ', '{1}ุณุงุนุฉ', '{2}ุณุงุนุชูŠู†', ']2,11[:count ุณุงุนุงุช', ']10,Inf[:count ุณุงุนุฉ']), + 'a_hour' => implode('|', ['{0}:count ุณุงุนุฉ', '{1}ุณุงุนุฉ', '{2}ุณุงุนุชูŠู†', ']2,11[:count ุณุงุนุงุช', ']10,Inf[:count ุณุงุนุฉ']), + 'minute' => implode('|', ['{0}:count ุฏู‚ูŠู‚ุฉ', '{1}ุฏู‚ูŠู‚ุฉ', '{2}ุฏู‚ูŠู‚ุชูŠู†', ']2,11[:count ุฏู‚ุงุฆู‚', ']10,Inf[:count ุฏู‚ูŠู‚ุฉ']), + 'a_minute' => implode('|', ['{0}:count ุฏู‚ูŠู‚ุฉ', '{1}ุฏู‚ูŠู‚ุฉ', '{2}ุฏู‚ูŠู‚ุชูŠู†', ']2,11[:count ุฏู‚ุงุฆู‚', ']10,Inf[:count ุฏู‚ูŠู‚ุฉ']), + 'second' => implode('|', ['{0}:count ุซุงู†ูŠุฉ', '{1}ุซุงู†ูŠุฉ', '{2}ุซุงู†ูŠุชูŠู†', ']2,11[:count ุซูˆุงู†ูŠ', ']10,Inf[:count ุซุงู†ูŠุฉ']), + 'a_second' => implode('|', ['{0}:count ุซุงู†ูŠุฉ', '{1}ุซุงู†ูŠุฉ', '{2}ุซุงู†ูŠุชูŠู†', ']2,11[:count ุซูˆุงู†ูŠ', ']10,Inf[:count ุซุงู†ูŠุฉ']), + 'ago' => 'ู…ู†ุฐ :time', + 'from_now' => 'ููŠ :time', + 'after' => 'ุจุนุฏ :time', + 'before' => 'ู‚ุจู„ :time', + 'diff_now' => 'ุงู„ุขู†', + 'diff_today' => 'ุงู„ูŠูˆู…', + 'diff_today_regexp' => 'ุงู„ูŠูˆู…(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_yesterday' => 'ุฃู…ุณ', + 'diff_yesterday_regexp' => 'ุฃู…ุณ(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_tomorrow' => 'ุบุฏุงู‹', + 'diff_tomorrow_regexp' => 'ุบุฏุง(?:\\s+ุนู„ู‰)?(?:\\s+ุงู„ุณุงุนุฉ)?', + 'diff_before_yesterday' => 'ู‚ุจู„ ุงู„ุฃู…ุณ', + 'diff_after_tomorrow' => 'ุจุนุฏ ุบุฏ', + 'period_recurrences' => implode('|', ['{0}ู…ุฑุฉ', '{1}ู…ุฑุฉ', '{2}:count ู…ุฑุชูŠู†', ']2,11[:count ู…ุฑุงุช', ']10,Inf[:count ู…ุฑุฉ']), + 'period_interval' => 'ูƒู„ :interval', + 'period_start_date' => 'ู…ู† :date', + 'period_end_date' => 'ุฅู„ู‰ :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุฃุญุฏ', 'ุงุซู†ูŠู†', 'ุซู„ุงุซุงุก', 'ุฃุฑุจุนุงุก', 'ุฎู…ูŠุณ', 'ุฌู…ุนุฉ', 'ุณุจุช'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'list' => ['ุŒ ', ' ูˆ '], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ุงู„ูŠูˆู… ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'nextDay' => '[ุบุฏุง ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'nextWeek' => 'dddd [ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'lastDay' => '[ุฃู…ุณ ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'lastWeek' => 'dddd [ุนู„ู‰ ุงู„ุณุงุนุฉ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ุต', 'ู…'], + 'weekend' => [5, 6], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_YE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_YE.php new file mode 100644 index 0000000000..169fe88a95 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_YE.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + * - Abdullah-Alhariri + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['ูŠู†ุงูŠุฑ', 'ูุจุฑุงูŠุฑ', 'ู…ุงุฑุณ', 'ุฃุจุฑูŠู„', 'ู…ุงูŠูˆ', 'ูŠูˆู†ูŠูˆ', 'ูŠูˆู„ูŠูˆ', 'ุฃุบุณุทุณ', 'ุณุจุชู…ุจุฑ', 'ุฃูƒุชูˆุจุฑ', 'ู†ูˆูู…ุจุฑ', 'ุฏูŠุณู…ุจุฑ'], + 'months_short' => ['ูŠู†ุง', 'ูุจุฑ', 'ู…ุงุฑ', 'ุฃุจุฑ', 'ู…ุงูŠ', 'ูŠูˆู†', 'ูŠูˆู„', 'ุฃุบุณ', 'ุณุจุช', 'ุฃูƒุช', 'ู†ูˆู', 'ุฏูŠุณ'], + 'weekdays' => ['ุงู„ุฃุญุฏ', 'ุงู„ุงุซู†ูŠู†', 'ุงู„ุซู„ุงุซุงุก', 'ุงู„ุฃุฑุจุนุงุก', 'ุงู„ุฎู…ูŠุณ', 'ุงู„ุฌู…ุนุฉ', 'ุงู„ุณุจุช'], + 'weekdays_short' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'weekdays_min' => ['ุญ', 'ู†', 'ุซ', 'ุฑ', 'ุฎ', 'ุฌ', 'ุณ'], + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐูค', 'ฐูฅ', 'ฐูฆ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑูค', 'ฑูฅ', 'ฑูฆ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒูค', 'ฒูฅ', 'ฒูฆ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณูค', 'ณูฅ', 'ณูฆ', 'ณท', 'ณธ', 'ณน', 'ูคฐ', 'ูคฑ', 'ูคฒ', 'ูคณ', 'ูคูค', 'ูคูฅ', 'ูคูฆ', 'ูคท', 'ูคธ', 'ูคน', 'ูฅฐ', 'ูฅฑ', 'ูฅฒ', 'ูฅณ', 'ูฅูค', 'ูฅูฅ', 'ูฅูฆ', 'ูฅท', 'ูฅธ', 'ูฅน', 'ูฆฐ', 'ูฆฑ', 'ูฆฒ', 'ูฆณ', 'ูฆูค', 'ูฆูฅ', 'ูฆูฆ', 'ูฆท', 'ูฆธ', 'ูฆน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทูค', 'ทูฅ', 'ทูฆ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธูค', 'ธูฅ', 'ธูฆ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นูค', 'นูฅ', 'นูฆ', 'นท', 'นธ', 'นน'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/as.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/as.php new file mode 100644 index 0000000000..04bc3dfd1e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/as.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/as_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/as_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/as_IN.php new file mode 100644 index 0000000000..5fbc3dba35 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/as_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Amitakhya Phukan, Red Hat bug-glibc@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D-MM-YYYY', + ], + 'months' => ['เฆœเฆพเฆจเงเงฑเฆพเงฐเง€', 'เฆซเง‡เฆฌเงเงฐเงเงฑเฆพเงฐเง€', 'เฆฎเฆพเงฐเงเฆš', 'เฆเฆชเงเงฐเฆฟเฆฒ', 'เฆฎเง‡', 'เฆœเงเฆจ', 'เฆœเงเฆฒเฆพเฆ‡', 'เฆ†เฆ—เฆทเงเฆŸ', 'เฆ›เง‡เฆชเงเฆคเง‡เฆฎเงเฆฌเงฐ', 'เฆ…เฆ•เงเฆŸเง‹เฆฌเงฐ', 'เฆจเงฑเง‡เฆฎเงเฆฌเงฐ', 'เฆกเฆฟเฆšเง‡เฆฎเงเฆฌเงฐ'], + 'months_short' => ['เฆœเฆพเฆจเง', 'เฆซเง‡เฆฌเงเงฐเง', 'เฆฎเฆพเงฐเงเฆš', 'เฆเฆชเงเงฐเฆฟเฆฒ', 'เฆฎเง‡', 'เฆœเงเฆจ', 'เฆœเงเฆฒเฆพเฆ‡', 'เฆ†เฆ—', 'เฆธเง‡เฆชเงเฆŸ', 'เฆ…เฆ•เงเฆŸเง‹', 'เฆจเฆญเง‡', 'เฆกเฆฟเฆธเง‡'], + 'weekdays' => ['เฆฆเง‡เฆ“เฆฌเฆพเงฐ', 'เฆธเง‹เฆฎเฆฌเฆพเงฐ', 'เฆฎเฆ™เงเฆ—เฆฒเฆฌเฆพเงฐ', 'เฆฌเงเฆงเฆฌเฆพเงฐ', 'เฆฌเงƒเฆนเฆทเงเฆชเฆคเฆฟเฆฌเฆพเงฐ', 'เฆถเงเฆ•เงเงฐเฆฌเฆพเงฐ', 'เฆถเฆจเฆฟเฆฌเฆพเงฐ'], + 'weekdays_short' => ['เฆฆเง‡เฆ“', 'เฆธเง‹เฆฎ', 'เฆฎเฆ™เงเฆ—เฆฒ', 'เฆฌเงเฆง', 'เฆฌเงƒเฆนเฆทเงเฆชเฆคเฆฟ', 'เฆถเงเฆ•เงเงฐ', 'เฆถเฆจเฆฟ'], + 'weekdays_min' => ['เฆฆเง‡เฆ“', 'เฆธเง‹เฆฎ', 'เฆฎเฆ™เงเฆ—เฆฒ', 'เฆฌเงเฆง', 'เฆฌเงƒเฆนเฆทเงเฆชเฆคเฆฟ', 'เฆถเงเฆ•เงเงฐ', 'เฆถเฆจเฆฟ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เฆชเง‚เงฐเงเฆฌเงเฆฌเฆพเฆนเงเฆจ', 'เฆ…เฆชเงฐเฆพเฆนเงเฆจ'], + + 'year' => ':count เฆฌเฆ›เงฐ', + 'y' => ':count เฆฌเฆ›เงฐ', + 'a_year' => ':count เฆฌเฆ›เงฐ', + + 'month' => ':count เฆฎเฆพเฆน', + 'm' => ':count เฆฎเฆพเฆน', + 'a_month' => ':count เฆฎเฆพเฆน', + + 'week' => ':count เฆธเฆชเงเฆคเฆพเฆน', + 'w' => ':count เฆธเฆชเงเฆคเฆพเฆน', + 'a_week' => ':count เฆธเฆชเงเฆคเฆพเฆน', + + 'day' => ':count เฆฌเฆพเงฐ', + 'd' => ':count เฆฌเฆพเงฐ', + 'a_day' => ':count เฆฌเฆพเงฐ', + + 'hour' => ':count เฆ˜เฆฃเงเฆŸเฆพ', + 'h' => ':count เฆ˜เฆฃเงเฆŸเฆพ', + 'a_hour' => ':count เฆ˜เฆฃเงเฆŸเฆพ', + + 'minute' => ':count เฆฎเฆฟเฆจเฆฟเฆŸ', + 'min' => ':count เฆฎเฆฟเฆจเฆฟเฆŸ', + 'a_minute' => ':count เฆฎเฆฟเฆจเฆฟเฆŸ', + + 'second' => ':count เฆฆเงเฆฌเฆฟเฆคเง€เฆฏเฆผ', + 's' => ':count เฆฆเงเฆฌเฆฟเฆคเง€เฆฏเฆผ', + 'a_second' => ':count เฆฆเงเฆฌเฆฟเฆคเง€เฆฏเฆผ', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/asa.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/asa.php new file mode 100644 index 0000000000..03bb48391d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/asa.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['icheheavo', 'ichamthi'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Ijm', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Ijm', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Dec'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ast.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ast.php new file mode 100644 index 0000000000..d9bdebe581 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ast.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Jordi Mallach jordi@gnu.org + * - Adolfo Jayme-Barrientos (fitojb) + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['de xineru', 'de febreru', 'de marzu', 'dโ€™abril', 'de mayu', 'de xunu', 'de xunetu', 'dโ€™agostu', 'de setiembre', 'dโ€™ochobre', 'de payares', 'dโ€™avientu'], + 'months_short' => ['xin', 'feb', 'mar', 'abr', 'may', 'xun', 'xnt', 'ago', 'set', 'och', 'pay', 'avi'], + 'weekdays' => ['domingu', 'llunes', 'martes', 'miรฉrcoles', 'xueves', 'vienres', 'sรกbadu'], + 'weekdays_short' => ['dom', 'llu', 'mar', 'miรฉ', 'xue', 'vie', 'sรกb'], + 'weekdays_min' => ['dom', 'llu', 'mar', 'miรฉ', 'xue', 'vie', 'sรกb'], + + 'year' => ':count aรฑu|:count aรฑos', + 'y' => ':count aรฑu|:count aรฑos', + 'a_year' => 'un aรฑu|:count aรฑos', + + 'month' => ':count mes', + 'm' => ':count mes', + 'a_month' => 'un mes|:count mes', + + 'week' => ':count selmana|:count selmanes', + 'w' => ':count selmana|:count selmanes', + 'a_week' => 'una selmana|:count selmanes', + + 'day' => ':count dรญa|:count dรญes', + 'd' => ':count dรญa|:count dรญes', + 'a_day' => 'un dรญa|:count dรญes', + + 'hour' => ':count hora|:count hores', + 'h' => ':count hora|:count hores', + 'a_hour' => 'una hora|:count hores', + + 'minute' => ':count minutu|:count minutos', + 'min' => ':count minutu|:count minutos', + 'a_minute' => 'un minutu|:count minutos', + + 'second' => ':count segundu|:count segundos', + 's' => ':count segundu|:count segundos', + 'a_second' => 'un segundu|:count segundos', + + 'ago' => 'hai :time', + 'from_now' => 'en :time', + 'after' => ':time dempuรฉs', + 'before' => ':time enantes', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ast_ES.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ast_ES.php new file mode 100644 index 0000000000..04d7562190 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ast_ES.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ast.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ayc.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ayc.php new file mode 100644 index 0000000000..d6a6f638a7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ayc.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ayc_PE.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ayc_PE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ayc_PE.php new file mode 100644 index 0000000000..ff18504f0a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ayc_PE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - runasimipi.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['inรฏru', 'phiwriru', 'marsu', 'awrila', 'mayu', 'junyu', 'julyu', 'awustu', 'sitimri', 'uktuwri', 'nuwimri', 'risimri'], + 'months_short' => ['ini', 'phi', 'mar', 'awr', 'may', 'jun', 'jul', 'awu', 'sit', 'ukt', 'nuw', 'ris'], + 'weekdays' => ['tuminku', 'lunisa', 'martisa', 'mirkulisa', 'juywisa', 'wirnisa', 'sawรคru'], + 'weekdays_short' => ['tum', 'lun', 'mar', 'mir', 'juy', 'wir', 'saw'], + 'weekdays_min' => ['tum', 'lun', 'mar', 'mir', 'juy', 'wir', 'saw'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['VM', 'NM'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az.php new file mode 100644 index 0000000000..1e92106df2 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Kunal Marwaha + * - Franรงois B + * - JD Isaacks + * - Orxan + * - ลžษ™hriyar ฤฐmanov + * - Baran ลžengรผl + */ +return [ + 'year' => ':count il', + 'a_year' => '{1}bir il|]1,Inf[:count il', + 'y' => ':count il', + 'month' => ':count ay', + 'a_month' => '{1}bir ay|]1,Inf[:count ay', + 'm' => ':count ay', + 'week' => ':count hษ™ftษ™', + 'a_week' => '{1}bir hษ™ftษ™|]1,Inf[:count hษ™ftษ™', + 'w' => ':count h.', + 'day' => ':count gรผn', + 'a_day' => '{1}bir gรผn|]1,Inf[:count gรผn', + 'd' => ':count g.', + 'hour' => ':count saat', + 'a_hour' => '{1}bir saat|]1,Inf[:count saat', + 'h' => ':count saat', + 'minute' => ':count d.', + 'a_minute' => '{1}bir dษ™qiqษ™|]1,Inf[:count dษ™qiqษ™', + 'min' => ':count dษ™qiqษ™', + 'second' => ':count san.', + 'a_second' => '{1}birneรงษ™ saniyษ™|]1,Inf[:count saniyษ™', + 's' => ':count saniyษ™', + 'ago' => ':time ษ™vvษ™l', + 'from_now' => ':time sonra', + 'after' => ':time sonra', + 'before' => ':time ษ™vvษ™l', + 'diff_now' => 'indi', + 'diff_today' => 'bugรผn', + 'diff_today_regexp' => 'bugรผn(?:\\s+saat)?', + 'diff_yesterday' => 'dรผnษ™n', + 'diff_tomorrow' => 'sabah', + 'diff_tomorrow_regexp' => 'sabah(?:\\s+saat)?', + 'diff_before_yesterday' => 'sraฤŸagรผn', + 'diff_after_tomorrow' => 'birisi gรผn', + 'period_recurrences' => ':count dษ™fษ™dษ™n bir', + 'period_interval' => 'hษ™r :interval', + 'period_start_date' => ':date tarixindษ™n baลŸlayaraq', + 'period_end_date' => ':date tarixinษ™dษ™k', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[bugรผn saat] LT', + 'nextDay' => '[sabah saat] LT', + 'nextWeek' => '[gษ™lษ™n hษ™ftษ™] dddd [saat] LT', + 'lastDay' => '[dรผnษ™n] LT', + 'lastWeek' => '[keรงษ™n hษ™ftษ™] dddd [saat] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + if ($number === 0) { // special case for zero + return "$number-ฤฑncฤฑ"; + } + + static $suffixes = [ + 1 => '-inci', + 5 => '-inci', + 8 => '-inci', + 70 => '-inci', + 80 => '-inci', + 2 => '-nci', + 7 => '-nci', + 20 => '-nci', + 50 => '-nci', + 3 => '-รผncรผ', + 4 => '-รผncรผ', + 100 => '-รผncรผ', + 6 => '-ncฤฑ', + 9 => '-uncu', + 10 => '-uncu', + 30 => '-uncu', + 60 => '-ฤฑncฤฑ', + 90 => '-ฤฑncฤฑ', + ]; + + $lastDigit = $number % 10; + + return $number.($suffixes[$lastDigit] ?? $suffixes[$number % 100 - $lastDigit] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'gecษ™'; + } + if ($hour < 12) { + return 'sษ™hษ™r'; + } + if ($hour < 17) { + return 'gรผndรผz'; + } + + return 'axลŸam'; + }, + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['yan', 'fev', 'mar', 'apr', 'may', 'iyn', 'iyl', 'avq', 'sen', 'okt', 'noy', 'dek'], + 'months_standalone' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'ฤฐyun', 'ฤฐyul', 'Avqust', 'Sentyabr', 'Oktyabr', 'Noyabr', 'Dekabr'], + 'weekdays' => ['bazar', 'bazar ertษ™si', 'รงษ™rลŸษ™nbษ™ axลŸamฤฑ', 'รงษ™rลŸษ™nbษ™', 'cรผmษ™ axลŸamฤฑ', 'cรผmษ™', 'ลŸษ™nbษ™'], + 'weekdays_short' => ['baz', 'bze', 'รงax', 'รงษ™r', 'cax', 'cรผm', 'ลŸษ™n'], + 'weekdays_min' => ['bz', 'be', 'รงa', 'รงษ™', 'ca', 'cรผ', 'ลŸษ™'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' vษ™ '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_AZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_AZ.php new file mode 100644 index 0000000000..2acf881a97 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_AZ.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/az.php', [ + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'ฤฐyn', 'ฤฐyl', 'Avq', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['bazar gรผnรผ', 'bazar ertษ™si', 'รงษ™rลŸษ™nbษ™ axลŸamฤฑ', 'รงษ™rลŸษ™nbษ™', 'cรผmษ™ axลŸamฤฑ', 'cรผmษ™', 'ลŸษ™nbษ™'], + 'weekdays_short' => ['baz', 'ber', 'รงax', 'รงษ™r', 'cax', 'cรผm', 'ลŸnb'], + 'weekdays_min' => ['baz', 'ber', 'รงax', 'รงษ™r', 'cax', 'cรผm', 'ลŸnb'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Cyrl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Cyrl.php new file mode 100644 index 0000000000..28fc62fe2c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Cyrl.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/az.php', [ + 'weekdays' => ['ะฑะฐะทะฐั€', 'ะฑะฐะทะฐั€ ะตั€ั‚ำ™ัะธ', 'ั‡ำ™ั€ัˆำ™ะฝะฑำ™ ะฐั…ัˆะฐะผั‹', 'ั‡ำ™ั€ัˆำ™ะฝะฑำ™', 'านาฏะผำ™ ะฐั…ัˆะฐะผั‹', 'านาฏะผำ™', 'ัˆำ™ะฝะฑำ™'], + 'weekdays_short' => ['ะ‘.', 'ะ‘.ะ•.', 'ะง.ะ.', 'ะง.', 'าธ.ะ.', 'าธ.', 'ะจ.'], + 'weekdays_min' => ['ะ‘.', 'ะ‘.ะ•.', 'ะง.ะ.', 'ะง.', 'าธ.ะ.', 'าธ.', 'ะจ.'], + 'months' => ['ั˜ะฐะฝะฒะฐั€', 'ั„ะตะฒั€ะฐะป', 'ะผะฐั€ั‚', 'ะฐะฟั€ะตะป', 'ะผะฐะน', 'ะธั˜ัƒะฝ', 'ะธั˜ัƒะป', 'ะฐะฒะณัƒัั‚', 'ัะตะฝั‚ั˜ะฐะฑั€', 'ะพะบั‚ั˜ะฐะฑั€', 'ะฝะพั˜ะฐะฑั€', 'ะดะตะบะฐะฑั€'], + 'months_short' => ['ั˜ะฐะฝ', 'ั„ะตะฒ', 'ะผะฐั€', 'ะฐะฟั€', 'ะผะฐะน', 'ะธั˜ะฝ', 'ะธั˜ะป', 'ะฐะฒะณ', 'ัะตะฝ', 'ะพะบั‚', 'ะฝะพั˜', 'ะดะตะบ'], + 'months_standalone' => ['ะˆะฐะฝะฒะฐั€', 'ะคะตะฒั€ะฐะป', 'ะœะฐั€ั‚', 'ะะฟั€ะตะป', 'ะœะฐะน', 'ะ˜ั˜ัƒะฝ', 'ะ˜ั˜ัƒะป', 'ะะฒะณัƒัั‚', 'ะกะตะฝั‚ั˜ะฐะฑั€', 'ะžะบั‚ั˜ะฐะฑั€', 'ะะพั˜ะฐะฑั€', 'ะ”ะตะบะฐะฑั€'], + 'meridiem' => ['ะฐ', 'ะฟ'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_IR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_IR.php new file mode 100644 index 0000000000..991a0efb2d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_IR.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Mousa Moradi mousamk@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'OY/OM/OD', + ], + 'months' => ['ฺ˜ุงู†ูˆŒู‡', 'ููˆุฑŒู‡', 'ู…ุงุฑุณ', 'ุขูˆุฑŒู„', 'ู…ุฆŒ', 'ฺ˜ูˆุฆู†', 'ุฌูˆู„ุงŒ', 'ุขู‚‡ุณุช', 'ุณูพุชุงู…ุจุฑ', 'ุงูˆู’ฺฉุชูˆู’ุจุฑ', 'ู†ูˆู’ูˆุงู…ุจุฑ', 'ุฏุณุงู…ุจุฑ'], + 'months_short' => ['ฺ˜ุงู†ูˆŒู‡', 'ููˆุฑŒู‡', 'ู…ุงุฑุณ', 'ุขูˆุฑŒู„', 'ู…ุฆŒ', 'ฺ˜ูˆุฆู†', 'ุฌูˆู„ุงŒ', 'ุขู‚‡ุณุช', 'ุณูพุชุงู…ุจุฑ', 'ุงูˆู’ฺฉุชูˆู’ุจุฑ', 'ู†ูˆู’ูˆุงู…ุจุฑ', 'ุฏุณุงู…ุจุฑ'], + 'weekdays' => ['Œฺฉุดู†ุจู‡', 'ุฏูˆุดู†ุจู‡', 'ุณู‡โ€Œุดู†ุจู‡', 'ฺ†ุงุฑุดู†ุจู‡', 'ุฌ†ู…ุนู‡ ุขุฎุดุงู…Œ', 'ุฌ†ู…ุนู‡', 'ุดู†ุจู‡'], + 'weekdays_short' => ['Œฺฉุดู†ุจู‡', 'ุฏูˆุดู†ุจู‡', 'ุณู‡โ€Œุดู†ุจู‡', 'ฺ†ุงุฑุดู†ุจู‡', 'ุฌ†ู…ุนู‡ ุขุฎุดุงู…Œ', 'ุฌ†ู…ุนู‡', 'ุดู†ุจู‡'], + 'weekdays_min' => ['Œฺฉุดู†ุจู‡', 'ุฏูˆุดู†ุจู‡', 'ุณู‡โ€Œุดู†ุจู‡', 'ฺ†ุงุฑุดู†ุจู‡', 'ุฌ†ู…ุนู‡ ุขุฎุดุงู…Œ', 'ุฌ†ู…ุนู‡', 'ุดู†ุจู‡'], + 'first_day_of_week' => 6, + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐด', 'ฐต', 'ฐถ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑด', 'ฑต', 'ฑถ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒด', 'ฒต', 'ฒถ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณด', 'ณต', 'ณถ', 'ณท', 'ณธ', 'ณน', 'ดฐ', 'ดฑ', 'ดฒ', 'ดณ', 'ดด', 'ดต', 'ดถ', 'ดท', 'ดธ', 'ดน', 'ตฐ', 'ตฑ', 'ตฒ', 'ตณ', 'ตด', 'ตต', 'ตถ', 'ตท', 'ตธ', 'ตน', 'ถฐ', 'ถฑ', 'ถฒ', 'ถณ', 'ถด', 'ถต', 'ถถ', 'ถท', 'ถธ', 'ถน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทด', 'ทต', 'ทถ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธด', 'ธต', 'ธถ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นด', 'นต', 'นถ', 'นท', 'นธ', 'นน'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Latn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Latn.php new file mode 100644 index 0000000000..0be33914f7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Latn.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/az.php', [ + 'meridiem' => ['a', 'p'], + 'weekdays' => ['bazar', 'bazar ertษ™si', 'รงษ™rลŸษ™nbษ™ axลŸamฤฑ', 'รงษ™rลŸษ™nbษ™', 'cรผmษ™ axลŸamฤฑ', 'cรผmษ™', 'ลŸษ™nbษ™'], + 'weekdays_short' => ['B.', 'B.E.', 'ร‡.A.', 'ร‡.', 'C.A.', 'C.', 'ลž.'], + 'weekdays_min' => ['B.', 'B.E.', 'ร‡.A.', 'ร‡.', 'C.A.', 'C.', 'ลž.'], + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['yan', 'fev', 'mar', 'apr', 'may', 'iyn', 'iyl', 'avq', 'sen', 'okt', 'noy', 'dek'], + 'months_standalone' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'ฤฐyun', 'ฤฐyul', 'Avqust', 'Sentyabr', 'Oktyabr', 'Noyabr', 'Dekabr'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'D MMMM YYYY, dddd HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bas.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bas.php new file mode 100644 index 0000000000..41bfa1d818 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bas.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['I bikษ›ฬ‚glร ', 'I ษ“ugajษ”p'], + 'weekdays' => ['ล‹gwร  nษ”ฬ‚y', 'ล‹gwร  njaล‹gumba', 'ล‹gwร  รปm', 'ล‹gwร  ล‹gรช', 'ล‹gwร  mbษ”k', 'ล‹gwร  kษ”ษ”', 'ล‹gwร  jรดn'], + 'weekdays_short' => ['nษ”y', 'nja', 'uum', 'ล‹ge', 'mbษ”', 'kษ”ษ”', 'jon'], + 'weekdays_min' => ['nษ”y', 'nja', 'uum', 'ล‹ge', 'mbษ”', 'kษ”ษ”', 'jon'], + 'months' => ['Kษ”ndษ”ล‹', 'Mร cษ›ฬ‚l', 'Mร tรนmb', 'Mร top', 'Mฬ€puyษ›', 'Hรฌlรฒndษ›ฬ€', 'Njรจbร ', 'Hรฌkaล‹', 'Dรฌpษ”ฬ€s', 'Bรฌรฒรดm', 'Mร yษ›sรจp', 'Lรฌbuy li ล„yรจe'], + 'months_short' => ['kษ”n', 'mac', 'mat', 'mto', 'mpu', 'hil', 'nje', 'hik', 'dip', 'bio', 'may', 'liษ“'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'second' => ':count mรณndรฎ', // less reliable + 's' => ':count mรณndรฎ', // less reliable + 'a_second' => ':count mรณndรฎ', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/be.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/be.php new file mode 100644 index 0000000000..ee736365a1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/be.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\CarbonInterface; +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + }, 'be'); +} +// @codeCoverageIgnoreEnd + +/* + * Authors: + * - Josh Soref + * - SobakaSlava + * - Franรงois B + * - Serhan Apaydฤฑn + * - JD Isaacks + * - AbadonnaAbbys + * - Siomkin Alexander + */ +return [ + 'year' => ':count ะณะพะด|:count ะณะฐะดั‹|:count ะณะฐะดะพัž', + 'a_year' => '{1}ะณะพะด|:count ะณะพะด|:count ะณะฐะดั‹|:count ะณะฐะดะพัž', + 'y' => ':count ะณะพะด|:count ะณะฐะดั‹|:count ะณะฐะดะพัž', + 'month' => ':count ะผะตััั†|:count ะผะตััั†ั‹|:count ะผะตััั†ะฐัž', + 'a_month' => '{1}ะผะตััั†|:count ะผะตััั†|:count ะผะตััั†ั‹|:count ะผะตััั†ะฐัž', + 'm' => ':count ะผะตััั†|:count ะผะตััั†ั‹|:count ะผะตััั†ะฐัž', + 'week' => ':count ั‚ั‹ะดะทะตะฝัŒ|:count ั‚ั‹ะดะฝั–|:count ั‚ั‹ะดะฝััž', + 'a_week' => '{1}ั‚ั‹ะดะทะตะฝัŒ|:count ั‚ั‹ะดะทะตะฝัŒ|:count ั‚ั‹ะดะฝั–|:count ั‚ั‹ะดะฝััž', + 'w' => ':count ั‚ั‹ะดะทะตะฝัŒ|:count ั‚ั‹ะดะฝั–|:count ั‚ั‹ะดะฝััž', + 'day' => ':count ะดะทะตะฝัŒ|:count ะดะฝั–|:count ะดะทั‘ะฝ', + 'a_day' => '{1}ะดะทะตะฝัŒ|:count ะดะทะตะฝัŒ|:count ะดะฝั–|:count ะดะทั‘ะฝ', + 'd' => ':count ะดะฝ', + 'hour' => ':count ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'a_hour' => '{1}ะณะฐะดะทั–ะฝะฐ|:count ะณะฐะดะทั–ะฝะฐ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'h' => ':count ะณะฐะดะทั–ะฝะฐ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'minute' => ':count ั…ะฒั–ะปั–ะฝะฐ|:count ั…ะฒั–ะปั–ะฝั‹|:count ั…ะฒั–ะปั–ะฝ', + 'a_minute' => '{1}ั…ะฒั–ะปั–ะฝะฐ|:count ั…ะฒั–ะปั–ะฝะฐ|:count ั…ะฒั–ะปั–ะฝั‹|:count ั…ะฒั–ะปั–ะฝ', + 'min' => ':count ั…ะฒ', + 'second' => ':count ัะตะบัƒะฝะดะฐ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + 'a_second' => '{1}ะฝะตะบะฐะปัŒะบั– ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะดะฐ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + 's' => ':count ัะตะบ', + + 'hour_ago' => ':count ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'a_hour_ago' => '{1}ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'h_ago' => ':count ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'minute_ago' => ':count ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝั‹|:count ั…ะฒั–ะปั–ะฝ', + 'a_minute_ago' => '{1}ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝั‹|:count ั…ะฒั–ะปั–ะฝ', + 'min_ago' => ':count ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝั‹|:count ั…ะฒั–ะปั–ะฝ', + 'second_ago' => ':count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + 'a_second_ago' => '{1}ะฝะตะบะฐะปัŒะบั– ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + 's_ago' => ':count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + + 'hour_from_now' => ':count ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'a_hour_from_now' => '{1}ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'h_from_now' => ':count ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'minute_from_now' => ':count ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝั‹|:count ั…ะฒั–ะปั–ะฝ', + 'a_minute_from_now' => '{1}ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝั‹|:count ั…ะฒั–ะปั–ะฝ', + 'min_from_now' => ':count ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝั‹|:count ั…ะฒั–ะปั–ะฝ', + 'second_from_now' => ':count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + 'a_second_from_now' => '{1}ะฝะตะบะฐะปัŒะบั– ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + 's_from_now' => ':count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + + 'hour_after' => ':count ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'a_hour_after' => '{1}ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'h_after' => ':count ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'minute_after' => ':count ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝั‹|:count ั…ะฒั–ะปั–ะฝ', + 'a_minute_after' => '{1}ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝั‹|:count ั…ะฒั–ะปั–ะฝ', + 'min_after' => ':count ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝั‹|:count ั…ะฒั–ะปั–ะฝ', + 'second_after' => ':count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + 'a_second_after' => '{1}ะฝะตะบะฐะปัŒะบั– ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + 's_after' => ':count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + + 'hour_before' => ':count ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'a_hour_before' => '{1}ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'h_before' => ':count ะณะฐะดะทั–ะฝัƒ|:count ะณะฐะดะทั–ะฝั‹|:count ะณะฐะดะทั–ะฝ', + 'minute_before' => ':count ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝั‹|:count ั…ะฒั–ะปั–ะฝ', + 'a_minute_before' => '{1}ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝั‹|:count ั…ะฒั–ะปั–ะฝ', + 'min_before' => ':count ั…ะฒั–ะปั–ะฝัƒ|:count ั…ะฒั–ะปั–ะฝั‹|:count ั…ะฒั–ะปั–ะฝ', + 'second_before' => ':count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + 'a_second_before' => '{1}ะฝะตะบะฐะปัŒะบั– ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + 's_before' => ':count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + + 'ago' => ':time ั‚ะฐะผัƒ', + 'from_now' => 'ะฟั€ะฐะท :time', + 'after' => ':time ะฟะฐัะปั', + 'before' => ':time ะดะฐ', + 'diff_now' => 'ั†ัะฟะตั€', + 'diff_today' => 'ะกั‘ะฝะฝั', + 'diff_today_regexp' => 'ะกั‘ะฝะฝั(?:\\s+ัž)?', + 'diff_yesterday' => 'ัƒั‡ะพั€ะฐ', + 'diff_yesterday_regexp' => 'ะฃั‡ะพั€ะฐ(?:\\s+ัž)?', + 'diff_tomorrow' => 'ะทะฐัžั‚ั€ะฐ', + 'diff_tomorrow_regexp' => 'ะ—ะฐัžั‚ั€ะฐ(?:\\s+ัž)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY ะณ.', + 'LLL' => 'D MMMM YYYY ะณ., HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY ะณ., HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ะกั‘ะฝะฝั ัž] LT', + 'nextDay' => '[ะ—ะฐัžั‚ั€ะฐ ัž] LT', + 'nextWeek' => '[ะฃ] dddd [ัž] LT', + 'lastDay' => '[ะฃั‡ะพั€ะฐ ัž] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 1: + case 2: + case 4: + return '[ะฃ ะผั–ะฝัƒะปั‹] dddd [ัž] LT'; + default: + return '[ะฃ ะผั–ะฝัƒะปัƒัŽ] dddd [ัž] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'M': + case 'd': + case 'DDD': + case 'w': + case 'W': + return ($number % 10 === 2 || $number % 10 === 3) && ($number % 100 !== 12 && $number % 100 !== 13) ? $number.'-ั–' : $number.'-ั‹'; + case 'D': + return $number.'-ะณะฐ'; + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ะฝะพั‡ั‹'; + } + if ($hour < 12) { + return 'ั€ะฐะฝั–ั†ั‹'; + } + if ($hour < 17) { + return 'ะดะฝั'; + } + + return 'ะฒะตั‡ะฐั€ะฐ'; + }, + 'months' => ['ัั‚ัƒะดะทะตะฝั', 'ะปัŽั‚ะฐะณะฐ', 'ัะฐะบะฐะฒั–ะบะฐ', 'ะบั€ะฐัะฐะฒั–ะบะฐ', 'ั‚ั€ะฐัžะฝั', 'ั‡ัั€ะฒะตะฝั', 'ะปั–ะฟะตะฝั', 'ะถะฝั–ัžะฝั', 'ะฒะตั€ะฐัะฝั', 'ะบะฐัั‚ั€ั‹ั‡ะฝั–ะบะฐ', 'ะปั–ัั‚ะฐะฟะฐะดะฐ', 'ัะฝะตะถะฝั'], + 'months_standalone' => ['ัั‚ัƒะดะทะตะฝัŒ', 'ะปัŽั‚ั‹', 'ัะฐะบะฐะฒั–ะบ', 'ะบั€ะฐัะฐะฒั–ะบ', 'ั‚ั€ะฐะฒะตะฝัŒ', 'ั‡ัั€ะฒะตะฝัŒ', 'ะปั–ะฟะตะฝัŒ', 'ะถะฝั–ะฒะตะฝัŒ', 'ะฒะตั€ะฐัะตะฝัŒ', 'ะบะฐัั‚ั€ั‹ั‡ะฝั–ะบ', 'ะปั–ัั‚ะฐะฟะฐะด', 'ัะฝะตะถะฐะฝัŒ'], + 'months_short' => ['ัั‚ัƒะด', 'ะปัŽั‚', 'ัะฐะบ', 'ะบั€ะฐั', 'ั‚ั€ะฐะฒ', 'ั‡ัั€ะฒ', 'ะปั–ะฟ', 'ะถะฝั–ะฒ', 'ะฒะตั€', 'ะบะฐัั‚', 'ะปั–ัั‚', 'ัะฝะตะถ'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['ะฝัะดะทะตะปัŽ', 'ะฟะฐะฝัะดะทะตะปะฐะบ', 'ะฐัžั‚ะพั€ะฐะบ', 'ัะตั€ะฐะดัƒ', 'ั‡ะฐั†ะฒะตั€', 'ะฟัั‚ะฝั–ั†ัƒ', 'ััƒะฑะพั‚ัƒ'], + 'weekdays_standalone' => ['ะฝัะดะทะตะปั', 'ะฟะฐะฝัะดะทะตะปะฐะบ', 'ะฐัžั‚ะพั€ะฐะบ', 'ัะตั€ะฐะดะฐ', 'ั‡ะฐั†ะฒะตั€', 'ะฟัั‚ะฝั–ั†ะฐ', 'ััƒะฑะพั‚ะฐ'], + 'weekdays_short' => ['ะฝะด', 'ะฟะฝ', 'ะฐั‚', 'ัั€', 'ั‡ั†', 'ะฟั‚', 'ัะฑ'], + 'weekdays_min' => ['ะฝะด', 'ะฟะฝ', 'ะฐั‚', 'ัั€', 'ั‡ั†', 'ะฟั‚', 'ัะฑ'], + 'weekdays_regexp' => '/\[ ?[ะฃัƒัž] ?(?:ะผั–ะฝัƒะปัƒัŽ|ะฝะฐัั‚ัƒะฟะฝัƒัŽ)? ?\] ?dddd/', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ั– '], + 'months_short_standalone' => ['ัั‚ัƒ', 'ะปัŽั‚', 'ัะฐะบ', 'ะบั€ะฐ', 'ะผะฐะน', 'ั‡ัั€', 'ะปั–ะฟ', 'ะถะฝั–', 'ะฒะตั€', 'ะบะฐั', 'ะปั–ั', 'ัะฝะต'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/be_BY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/be_BY.php new file mode 100644 index 0000000000..26684b40ee --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/be_BY.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/be.php', [ + 'months' => ['ัั‚ัƒะดะทะตะฝั', 'ะปัŽั‚ะฐะณะฐ', 'ัะฐะบะฐะฒั–ะบะฐ', 'ะบั€ะฐัะฐะฒั–ะบะฐ', 'ะผะฐั', 'ั‡ัั€ะฒะตะฝั', 'ะปั–ะฟะตะฝั', 'ะถะฝั–ัžะฝั', 'ะฒะตั€ะฐัะฝั', 'ะบะฐัั‚ั€ั‹ั‡ะฝั–ะบะฐ', 'ะปั–ัั‚ะฐะฟะฐะดะฐ', 'ัะฝะตะถะฝั'], + 'months_short' => ['ัั‚ัƒ', 'ะปัŽั‚', 'ัะฐะบ', 'ะบั€ะฐ', 'ะผะฐั', 'ั‡ัั€', 'ะปั–ะฟ', 'ะถะฝั–', 'ะฒะตั€', 'ะบะฐั', 'ะปั–ั', 'ัะฝะต'], + 'weekdays' => ['ะัะดะทะตะปั', 'ะŸะฐะฝัะดะทะตะปะฐะบ', 'ะัžั‚ะพั€ะฐะบ', 'ะกะตั€ะฐะดะฐ', 'ะงะฐั†ะฒะตั€', 'ะŸัั‚ะฝั–ั†ะฐ', 'ะกัƒะฑะพั‚ะฐ'], + 'weekdays_short' => ['ะัะด', 'ะŸะฐะฝ', 'ะัžั‚', 'ะกั€ะด', 'ะงั†ะฒ', 'ะŸัั‚', 'ะกัƒะฑ'], + 'weekdays_min' => ['ะัะด', 'ะŸะฐะฝ', 'ะัžั‚', 'ะกั€ะด', 'ะงั†ะฒ', 'ะŸัั‚', 'ะกัƒะฑ'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/be_BY@latin.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/be_BY@latin.php new file mode 100644 index 0000000000..517ce83af5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/be_BY@latin.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['studzienia', 'lutaha', 'sakavika', 'krasavika', 'maja', 'ฤervienia', 'lipienia', 'ลพniลญnia', 'vieraล›nia', 'kastryฤnika', 'listapada', 'ล›nieลพnia'], + 'months_short' => ['Stu', 'Lut', 'Sak', 'Kra', 'Maj', 'ฤŒer', 'Lip', 'ลฝni', 'Vie', 'Kas', 'Lis', 'ลšni'], + 'weekdays' => ['Niadziela', 'Paniadzieล‚ak', 'Aลญtorak', 'Sierada', 'ฤŒaฤ‡vier', 'Piatnica', 'Subota'], + 'weekdays_short' => ['Nia', 'Pan', 'Aลญt', 'Sie', 'ฤŒaฤ‡', 'Pia', 'Sub'], + 'weekdays_min' => ['Nia', 'Pan', 'Aลญt', 'Sie', 'ฤŒaฤ‡', 'Pia', 'Sub'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bem.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bem.php new file mode 100644 index 0000000000..1c3ef03967 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bem.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bem_ZM.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bem_ZM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bem_ZM.php new file mode 100644 index 0000000000..620b579542 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bem_ZM.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ANLoc Martin Benjamin locales@africanlocalization.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YYYY', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Epreo', 'Mei', 'Juni', 'Julai', 'Ogasti', 'Septemba', 'Oktoba', 'Novemba', 'Disemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Epr', 'Mei', 'Jun', 'Jul', 'Oga', 'Sep', 'Okt', 'Nov', 'Dis'], + 'weekdays' => ['Pa Mulungu', 'Palichimo', 'Palichibuli', 'Palichitatu', 'Palichine', 'Palichisano', 'Pachibelushi'], + 'weekdays_short' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'weekdays_min' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['uluchelo', 'akasuba'], + + 'year' => 'myaka :count', + 'y' => 'myaka :count', + 'a_year' => 'myaka :count', + + 'month' => 'myeshi :count', + 'm' => 'myeshi :count', + 'a_month' => 'myeshi :count', + + 'week' => 'umulungu :count', + 'w' => 'umulungu :count', + 'a_week' => 'umulungu :count', + + 'day' => 'inshiku :count', + 'd' => 'inshiku :count', + 'a_day' => 'inshiku :count', + + 'hour' => 'awala :count', + 'h' => 'awala :count', + 'a_hour' => 'awala :count', + + 'minute' => 'miniti :count', + 'min' => 'miniti :count', + 'a_minute' => 'miniti :count', + + 'second' => 'sekondi :count', + 's' => 'sekondi :count', + 'a_second' => 'sekondi :count', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ber.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ber.php new file mode 100644 index 0000000000..685603c00c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ber.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ber_DZ.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ber_DZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ber_DZ.php new file mode 100644 index 0000000000..38de10ab02 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ber_DZ.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'ฤฐyn', 'ฤฐyl', 'Avq', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['bazar gรผnรผ', 'birinci gรผn', 'ikinci gรผn', 'รผรงรผncรผ gรผn', 'dรถrdรผncรผ gรผn', 'beลŸinci gรผn', 'altฤฑncฤฑ gรผn'], + 'weekdays_short' => ['baz', 'bir', 'iki', 'รผรงรผ', 'dรถr', 'beลŸ', 'alt'], + 'weekdays_min' => ['baz', 'bir', 'iki', 'รผรงรผ', 'dรถr', 'beลŸ', 'alt'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ber_MA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ber_MA.php new file mode 100644 index 0000000000..38de10ab02 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ber_MA.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'ฤฐyn', 'ฤฐyl', 'Avq', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['bazar gรผnรผ', 'birinci gรผn', 'ikinci gรผn', 'รผรงรผncรผ gรผn', 'dรถrdรผncรผ gรผn', 'beลŸinci gรผn', 'altฤฑncฤฑ gรผn'], + 'weekdays_short' => ['baz', 'bir', 'iki', 'รผรงรผ', 'dรถr', 'beลŸ', 'alt'], + 'weekdays_min' => ['baz', 'bir', 'iki', 'รผรงรผ', 'dรถr', 'beลŸ', 'alt'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bez.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bez.php new file mode 100644 index 0000000000..d59c5ef525 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bez.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['pamilau', 'pamunyi'], + 'weekdays' => ['pa mulungu', 'pa shahuviluha', 'pa hivili', 'pa hidatu', 'pa hitayi', 'pa hihanu', 'pa shahulembela'], + 'weekdays_short' => ['Mul', 'Vil', 'Hiv', 'Hid', 'Hit', 'Hih', 'Lem'], + 'weekdays_min' => ['Mul', 'Vil', 'Hiv', 'Hid', 'Hit', 'Hih', 'Lem'], + 'months' => ['pa mwedzi gwa hutala', 'pa mwedzi gwa wuvili', 'pa mwedzi gwa wudatu', 'pa mwedzi gwa wutai', 'pa mwedzi gwa wuhanu', 'pa mwedzi gwa sita', 'pa mwedzi gwa saba', 'pa mwedzi gwa nane', 'pa mwedzi gwa tisa', 'pa mwedzi gwa kumi', 'pa mwedzi gwa kumi na moja', 'pa mwedzi gwa kumi na mbili'], + 'months_short' => ['Hut', 'Vil', 'Dat', 'Tai', 'Han', 'Sit', 'Sab', 'Nan', 'Tis', 'Kum', 'Kmj', 'Kmb'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bg.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bg.php new file mode 100644 index 0000000000..f768074032 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bg.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Franรงois B + * - Serhan Apaydฤฑn + * - JD Isaacks + * - Glaviฤ‡ + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count ะณะพะดะธะฝะฐ|:count ะณะพะดะธะฝะธ', + 'a_year' => 'ะณะพะดะธะฝะฐ|:count ะณะพะดะธะฝะธ', + 'y' => ':count ะณะพะดะธะฝะฐ|:count ะณะพะดะธะฝะธ', + 'month' => ':count ะผะตัะตั†|:count ะผะตัะตั†ะฐ', + 'a_month' => 'ะผะตัะตั†|:count ะผะตัะตั†ะฐ', + 'm' => ':count ะผะตัะตั†|:count ะผะตัะตั†ะฐ', + 'week' => ':count ัะตะดะผะธั†ะฐ|:count ัะตะดะผะธั†ะธ', + 'a_week' => 'ัะตะดะผะธั†ะฐ|:count ัะตะดะผะธั†ะธ', + 'w' => ':count ัะตะดะผะธั†ะฐ|:count ัะตะดะผะธั†ะธ', + 'day' => ':count ะดะตะฝ|:count ะดะฝะธ', + 'a_day' => 'ะดะตะฝ|:count ะดะฝะธ', + 'd' => ':count ะดะตะฝ|:count ะดะฝะธ', + 'hour' => ':count ั‡ะฐั|:count ั‡ะฐัะฐ', + 'a_hour' => 'ั‡ะฐั|:count ั‡ะฐัะฐ', + 'h' => ':count ั‡ะฐั|:count ั‡ะฐัะฐ', + 'minute' => ':count ะผะธะฝัƒั‚ะฐ|:count ะผะธะฝัƒั‚ะธ', + 'a_minute' => 'ะผะธะฝัƒั‚ะฐ|:count ะผะธะฝัƒั‚ะธ', + 'min' => ':count ะผะธะฝัƒั‚ะฐ|:count ะผะธะฝัƒั‚ะธ', + 'second' => ':count ัะตะบัƒะฝะดะฐ|:count ัะตะบัƒะฝะดะธ', + 'a_second' => 'ะฝัะบะพะปะบะพ ัะตะบัƒะฝะดะธ|:count ัะตะบัƒะฝะดะธ', + 's' => ':count ัะตะบัƒะฝะดะฐ|:count ัะตะบัƒะฝะดะธ', + 'ago' => 'ะฟั€ะตะดะธ :time', + 'from_now' => 'ัะปะตะด :time', + 'after' => 'ัะปะตะด :time', + 'before' => 'ะฟั€ะตะดะธ :time', + 'diff_now' => 'ัะตะณะฐ', + 'diff_today' => 'ะ”ะฝะตั', + 'diff_today_regexp' => 'ะ”ะฝะตั(?:\\s+ะฒ)?', + 'diff_yesterday' => 'ะฒั‡ะตั€ะฐ', + 'diff_yesterday_regexp' => 'ะ’ั‡ะตั€ะฐ(?:\\s+ะฒ)?', + 'diff_tomorrow' => 'ัƒั‚ั€ะต', + 'diff_tomorrow_regexp' => 'ะฃั‚ั€ะต(?:\\s+ะฒ)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY H:mm', + 'LLLL' => 'dddd, D MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[ะ”ะฝะตั ะฒ] LT', + 'nextDay' => '[ะฃั‚ั€ะต ะฒ] LT', + 'nextWeek' => 'dddd [ะฒ] LT', + 'lastDay' => '[ะ’ั‡ะตั€ะฐ ะฒ] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 0: + case 3: + case 6: + return '[ะ’ ะธะทะผะธะฝะฐะปะฐั‚ะฐ] dddd [ะฒ] LT'; + default: + return '[ะ’ ะธะทะผะธะฝะฐะปะธั] dddd [ะฒ] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + $last2Digits = $number % 100; + if ($number === 0) { + return "$number-ะตะฒ"; + } + if ($last2Digits === 0) { + return "$number-ะตะฝ"; + } + if ($last2Digits > 10 && $last2Digits < 20) { + return "$number-ั‚ะธ"; + } + if ($lastDigit === 1) { + return "$number-ะฒะธ"; + } + if ($lastDigit === 2) { + return "$number-ั€ะธ"; + } + if ($lastDigit === 7 || $lastDigit === 8) { + return "$number-ะผะธ"; + } + + return "$number-ั‚ะธ"; + }, + 'months' => ['ัะฝัƒะฐั€ะธ', 'ั„ะตะฒั€ัƒะฐั€ะธ', 'ะผะฐั€ั‚', 'ะฐะฟั€ะธะป', 'ะผะฐะน', 'ัŽะฝะธ', 'ัŽะปะธ', 'ะฐะฒะณัƒัั‚', 'ัะตะฟั‚ะตะผะฒั€ะธ', 'ะพะบั‚ะพะผะฒั€ะธ', 'ะฝะพะตะผะฒั€ะธ', 'ะดะตะบะตะผะฒั€ะธ'], + 'months_short' => ['ัะฝัƒ', 'ั„ะตะฒ', 'ะผะฐั€', 'ะฐะฟั€', 'ะผะฐะน', 'ัŽะฝะธ', 'ัŽะปะธ', 'ะฐะฒะณ', 'ัะตะฟ', 'ะพะบั‚', 'ะฝะพะต', 'ะดะตะบ'], + 'weekdays' => ['ะฝะตะดะตะปั', 'ะฟะพะฝะตะดะตะปะฝะธะบ', 'ะฒั‚ะพั€ะฝะธะบ', 'ัั€ัะดะฐ', 'ั‡ะตั‚ะฒัŠั€ั‚ัŠะบ', 'ะฟะตั‚ัŠะบ', 'ััŠะฑะพั‚ะฐ'], + 'weekdays_short' => ['ะฝะตะด', 'ะฟะพะฝ', 'ะฒั‚ะพ', 'ัั€ั', 'ั‡ะตั‚', 'ะฟะตั‚', 'ััŠะฑ'], + 'weekdays_min' => ['ะฝะด', 'ะฟะฝ', 'ะฒั‚', 'ัั€', 'ั‡ั‚', 'ะฟั‚', 'ัะฑ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ะธ '], + 'meridiem' => ['ะฟั€ะตะดะธ ะพะฑัะด', 'ัะปะตะดะพะฑะตะด'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bg_BG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bg_BG.php new file mode 100644 index 0000000000..b53874d301 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bg_BG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/bg.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bhb.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bhb.php new file mode 100644 index 0000000000..49f08032e8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bhb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bhb_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bhb_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bhb_IN.php new file mode 100644 index 0000000000..ab557cbf3a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bhb_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. alexey.merzlyakov@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + 'weekdays_short' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'weekdays_min' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bho.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bho.php new file mode 100644 index 0000000000..e9ed0b6873 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bho.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bho_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bho_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bho_IN.php new file mode 100644 index 0000000000..bc54f36313 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bho_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bhashaghar@googlegroups.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅˆเคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเคฎเฅเคฌเคฐ', 'เค…เค•เฅเคŸเฅ‚เคฌเคฐ', 'เคจเคตเคฎเฅเคฌเคฐ', 'เคฆเคฟเคธเคฎเฅเคฌเคฐ"'], + 'months_short' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅˆเคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเคฎเฅเคฌเคฐ', 'เค…เค•เฅเคŸเฅ‚เคฌเคฐ', 'เคจเคตเคฎเฅเคฌเคฐ', 'เคฆเคฟเคธเคฎเฅเคฌเคฐ"'], + 'weekdays' => ['เคฐเคตเคฟเคตเคพเคฐ', 'เคธเฅ‹เคฎเคตเคพเคฐ', 'เคฎเค‚เค—เคฒเคตเคพเคฐ', 'เคฌเฅเคงเคตเคพเคฐ', 'เค—เฅเคฐเฅเคตเคพเคฐ', 'เคถเฅเค•เฅเคฐเคตเคพเคฐ', 'เคถเคจเคฟเคตเคพเคฐ'], + 'weekdays_short' => ['เคฐเคตเคฟ', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคง', 'เค—เฅเคฐเฅ', 'เคถเฅเค•เฅเคฐ', 'เคถเคจเคฟ'], + 'weekdays_min' => ['เคฐเคตเคฟ', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคง', 'เค—เฅเคฐเฅ', 'เคถเฅเค•เฅเคฐ', 'เคถเคจเคฟ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เคชเฅ‚เคฐเฅเคตเคพเคนเฅเคจ', 'เค…เคชเคฐเคพเคนเฅเคจ'], + + 'hour' => ':count เคฎเฅŒเคธเคฎ', + 'h' => ':count เคฎเฅŒเคธเคฎ', + 'a_hour' => ':count เคฎเฅŒเคธเคฎ', + + 'minute' => ':count เค•เคฒเคพ', + 'min' => ':count เค•เคฒเคพ', + 'a_minute' => ':count เค•เคฒเคพ', + + 'second' => ':count เคธเฅ‹เคฎเคพเคฐ', + 's' => ':count เคธเฅ‹เคฎเคพเคฐ', + 'a_second' => ':count เคธเฅ‹เคฎเคพเคฐ', + + 'year' => ':count เคธเคพเคฒ', + 'y' => ':count เคธเคพเคฒ', + 'a_year' => ':count เคธเคพเคฒ', + + 'month' => ':count เคฎเคนเคฟเคจเคพ', + 'm' => ':count เคฎเคนเคฟเคจเคพ', + 'a_month' => ':count เคฎเคนเคฟเคจเคพ', + + 'week' => ':count เคธเคชเฅเคคเคพเคน', + 'w' => ':count เคธเคชเฅเคคเคพเคน', + 'a_week' => ':count เคธเคชเฅเคคเคพเคน', + + 'day' => ':count เคฆเคฟเคจ', + 'd' => ':count เคฆเคฟเคจ', + 'a_day' => ':count เคฆเคฟเคจ', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bi.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bi.php new file mode 100644 index 0000000000..dd08128e80 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bi_VU.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bi_VU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bi_VU.php new file mode 100644 index 0000000000..1fe7770564 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bi_VU.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com & maninder1.s@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['jenuware', 'febwari', 'maj', 'epril', 'mei', 'jun', 'julae', 'ogis', 'septemba', 'oktoba', 'novemba', 'disemba'], + 'months_short' => ['jen', 'feb', 'maj', 'epr', 'mei', 'jun', 'jul', 'ogi', 'sep', 'okt', 'nov', 'dis'], + 'weekdays' => ['sande', 'mande', 'maj', 'wota', 'fraede', 'sarede'], + 'weekdays_short' => ['san', 'man', 'maj', 'wot', 'fra', 'sar'], + 'weekdays_min' => ['san', 'man', 'maj', 'wot', 'fra', 'sar'], + + 'year' => ':count seven', // less reliable + 'y' => ':count seven', // less reliable + 'a_year' => ':count seven', // less reliable + + 'month' => ':count mi', // less reliable + 'm' => ':count mi', // less reliable + 'a_month' => ':count mi', // less reliable + + 'week' => ':count sarede', // less reliable + 'w' => ':count sarede', // less reliable + 'a_week' => ':count sarede', // less reliable + + 'day' => ':count betde', // less reliable + 'd' => ':count betde', // less reliable + 'a_day' => ':count betde', // less reliable + + 'hour' => ':count klok', // less reliable + 'h' => ':count klok', // less reliable + 'a_hour' => ':count klok', // less reliable + + 'minute' => ':count smol', // less reliable + 'min' => ':count smol', // less reliable + 'a_minute' => ':count smol', // less reliable + + 'second' => ':count tu', // less reliable + 's' => ':count tu', // less reliable + 'a_second' => ':count tu', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bm.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bm.php new file mode 100644 index 0000000000..92822d2994 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bm.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Estelle Comment + */ +return [ + 'year' => 'san :count', + 'a_year' => '{1}san kelen|san :count', + 'y' => 'san :count', + 'month' => 'kalo :count', + 'a_month' => '{1}kalo kelen|kalo :count', + 'm' => 'k. :count', + 'week' => 'dษ”gษ”kun :count', + 'a_week' => 'dษ”gษ”kun kelen', + 'w' => 'd. :count', + 'day' => 'tile :count', + 'd' => 't. :count', + 'a_day' => '{1}tile kelen|tile :count', + 'hour' => 'lษ›rษ› :count', + 'a_hour' => '{1}lษ›rษ› kelen|lษ›rษ› :count', + 'h' => 'l. :count', + 'minute' => 'miniti :count', + 'a_minute' => '{1}miniti kelen|miniti :count', + 'min' => 'm. :count', + 'second' => 'sekondi :count', + 'a_second' => '{1}sanga dama dama|sekondi :count', + 's' => 'sek. :count', + 'ago' => 'a bษ› :time bษ”', + 'from_now' => ':time kษ”nษ”', + 'diff_today' => 'Bi', + 'diff_yesterday' => 'Kunu', + 'diff_yesterday_regexp' => 'Kunu(?:\\s+lษ›rษ›)?', + 'diff_tomorrow' => 'Sini', + 'diff_tomorrow_regexp' => 'Sini(?:\\s+lษ›rษ›)?', + 'diff_today_regexp' => 'Bi(?:\\s+lษ›rษ›)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'MMMM [tile] D [san] YYYY', + 'LLL' => 'MMMM [tile] D [san] YYYY [lษ›rษ›] HH:mm', + 'LLLL' => 'dddd MMMM [tile] D [san] YYYY [lษ›rษ›] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Bi lษ›rษ›] LT', + 'nextDay' => '[Sini lษ›rษ›] LT', + 'nextWeek' => 'dddd [don lษ›rษ›] LT', + 'lastDay' => '[Kunu lษ›rษ›] LT', + 'lastWeek' => 'dddd [tษ›mษ›nen lษ›rษ›] LT', + 'sameElse' => 'L', + ], + 'months' => ['Zanwuyekalo', 'Fewuruyekalo', 'Marisikalo', 'Awirilikalo', 'Mษ›kalo', 'Zuwษ›nkalo', 'Zuluyekalo', 'Utikalo', 'Sษ›tanburukalo', 'ษ”kutษ”burukalo', 'Nowanburukalo', 'Desanburukalo'], + 'months_short' => ['Zan', 'Few', 'Mar', 'Awi', 'Mษ›', 'Zuw', 'Zul', 'Uti', 'Sษ›t', 'ษ”ku', 'Now', 'Des'], + 'weekdays' => ['Kari', 'Ntษ›nษ›n', 'Tarata', 'Araba', 'Alamisa', 'Juma', 'Sibiri'], + 'weekdays_short' => ['Kar', 'Ntษ›', 'Tar', 'Ara', 'Ala', 'Jum', 'Sib'], + 'weekdays_min' => ['Ka', 'Nt', 'Ta', 'Ar', 'Al', 'Ju', 'Si'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ni '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bn.php new file mode 100644 index 0000000000..8e147899bb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bn.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Shakib Hossain + * - Raju + * - Aniruddha Adhikary + * - JD Isaacks + * - Saiful Islam + * - Faisal Islam + */ +return [ + 'year' => ':count เฆฌเฆ›เฆฐ', + 'a_year' => 'เฆเฆ• เฆฌเฆ›เฆฐ|:count เฆฌเฆ›เฆฐ', + 'y' => 'เงง เฆฌเฆ›เฆฐ|:count เฆฌเฆ›เฆฐ', + 'month' => ':count เฆฎเฆพเฆธ', + 'a_month' => 'เฆเฆ• เฆฎเฆพเฆธ|:count เฆฎเฆพเฆธ', + 'm' => 'เงง เฆฎเฆพเฆธ|:count เฆฎเฆพเฆธ', + 'week' => ':count เฆธเฆชเงเฆคเฆพเฆน', + 'a_week' => 'เงง เฆธเฆชเงเฆคเฆพเฆน|:count เฆธเฆชเงเฆคเฆพเฆน', + 'w' => 'เงง เฆธเฆชเงเฆคเฆพเฆน|:count เฆธเฆชเงเฆคเฆพเฆน', + 'day' => ':count เฆฆเฆฟเฆจ', + 'a_day' => 'เฆเฆ• เฆฆเฆฟเฆจ|:count เฆฆเฆฟเฆจ', + 'd' => 'เงง เฆฆเฆฟเฆจ|:count เฆฆเฆฟเฆจ', + 'hour' => ':count เฆ˜เฆจเงเฆŸเฆพ', + 'a_hour' => 'เฆเฆ• เฆ˜เฆจเงเฆŸเฆพ|:count เฆ˜เฆจเงเฆŸเฆพ', + 'h' => 'เงง เฆ˜เฆจเงเฆŸเฆพ|:count เฆ˜เฆจเงเฆŸเฆพ', + 'minute' => ':count เฆฎเฆฟเฆจเฆฟเฆŸ', + 'a_minute' => 'เฆเฆ• เฆฎเฆฟเฆจเฆฟเฆŸ|:count เฆฎเฆฟเฆจเฆฟเฆŸ', + 'min' => 'เงง เฆฎเฆฟเฆจเฆฟเฆŸ|:count เฆฎเฆฟเฆจเฆฟเฆŸ', + 'second' => ':count เฆธเง‡เฆ•เง‡เฆจเงเฆก', + 'a_second' => 'เฆ•เงŸเง‡เฆ• เฆธเง‡เฆ•เง‡เฆจเงเฆก|:count เฆธเง‡เฆ•เง‡เฆจเงเฆก', + 's' => 'เงง เฆธเง‡เฆ•เง‡เฆจเงเฆก|:count เฆธเง‡เฆ•เง‡เฆจเงเฆก', + 'ago' => ':time เฆ†เฆ—เง‡', + 'from_now' => ':time เฆชเฆฐเง‡', + 'after' => ':time เฆชเฆฐเง‡', + 'before' => ':time เฆ†เฆ—เง‡', + 'diff_now' => 'เฆเฆ–เฆจ', + 'diff_today' => 'เฆ†เฆœ', + 'diff_yesterday' => 'เฆ—เฆคเฆ•เฆพเฆฒ', + 'diff_tomorrow' => 'เฆ†เฆ—เฆพเฆฎเง€เฆ•เฆพเฆฒ', + 'period_recurrences' => ':count เฆฌเฆพเฆฐ|:count เฆฌเฆพเฆฐ', + 'period_interval' => 'เฆชเงเฆฐเฆคเฆฟ :interval', + 'period_start_date' => ':date เฆฅเง‡เฆ•เง‡', + 'period_end_date' => ':date เฆชเฆฐเงเฆฏเฆจเงเฆค', + 'formats' => [ + 'LT' => 'A Oh:Om เฆธเฆฎเงŸ', + 'LTS' => 'A Oh:Om:Os เฆธเฆฎเงŸ', + 'L' => 'OD/OM/OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY, A Oh:Om เฆธเฆฎเงŸ', + 'LLLL' => 'dddd, OD MMMM OY, A Oh:Om เฆธเฆฎเงŸ', + ], + 'calendar' => [ + 'sameDay' => '[เฆ†เฆœ] LT', + 'nextDay' => '[เฆ†เฆ—เฆพเฆฎเง€เฆ•เฆพเฆฒ] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[เฆ—เฆคเฆ•เฆพเฆฒ] LT', + 'lastWeek' => '[เฆ—เฆค] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'เฆฐเฆพเฆค'; + } + if ($hour < 10) { + return 'เฆธเฆ•เฆพเฆฒ'; + } + if ($hour < 17) { + return 'เฆฆเงเฆชเงเฆฐ'; + } + if ($hour < 20) { + return 'เฆฌเฆฟเฆ•เฆพเฆฒ'; + } + + return 'เฆฐเฆพเฆค'; + }, + 'months' => ['เฆœเฆพเฆจเงเงŸเฆพเฆฐเง€', 'เฆซเง‡เฆฌเงเฆฐเงเงŸเฆพเฆฐเฆฟ', 'เฆฎเฆพเฆฐเงเฆš', 'เฆเฆชเงเฆฐเฆฟเฆฒ', 'เฆฎเง‡', 'เฆœเงเฆจ', 'เฆœเงเฆฒเฆพเฆ‡', 'เฆ†เฆ—เฆธเงเฆŸ', 'เฆธเง‡เฆชเงเฆŸเง‡เฆฎเงเฆฌเฆฐ', 'เฆ…เฆ•เงเฆŸเง‹เฆฌเฆฐ', 'เฆจเฆญเง‡เฆฎเงเฆฌเฆฐ', 'เฆกเฆฟเฆธเง‡เฆฎเงเฆฌเฆฐ'], + 'months_short' => ['เฆœเฆพเฆจเง', 'เฆซเง‡เฆฌ', 'เฆฎเฆพเฆฐเงเฆš', 'เฆเฆชเงเฆฐ', 'เฆฎเง‡', 'เฆœเงเฆจ', 'เฆœเงเฆฒ', 'เฆ†เฆ—', 'เฆธเง‡เฆชเงเฆŸ', 'เฆ…เฆ•เงเฆŸเง‹', 'เฆจเฆญเง‡', 'เฆกเฆฟเฆธเง‡'], + 'weekdays' => ['เฆฐเฆฌเฆฟเฆฌเฆพเฆฐ', 'เฆธเง‹เฆฎเฆฌเฆพเฆฐ', 'เฆฎเฆ™เงเฆ—เฆฒเฆฌเฆพเฆฐ', 'เฆฌเงเฆงเฆฌเฆพเฆฐ', 'เฆฌเงƒเฆนเฆธเงเฆชเฆคเฆฟเฆฌเฆพเฆฐ', 'เฆถเงเฆ•เงเฆฐเฆฌเฆพเฆฐ', 'เฆถเฆจเฆฟเฆฌเฆพเฆฐ'], + 'weekdays_short' => ['เฆฐเฆฌเฆฟ', 'เฆธเง‹เฆฎ', 'เฆฎเฆ™เงเฆ—เฆฒ', 'เฆฌเงเฆง', 'เฆฌเงƒเฆนเฆธเงเฆชเฆคเฆฟ', 'เฆถเงเฆ•เงเฆฐ', 'เฆถเฆจเฆฟ'], + 'weekdays_min' => ['เฆฐเฆฌเฆฟ', 'เฆธเง‹เฆฎ', 'เฆฎเฆ™เงเฆ—', 'เฆฌเงเฆง', 'เฆฌเงƒเฆนเฆƒ', 'เฆถเงเฆ•เงเฆฐ', 'เฆถเฆจเฆฟ'], + 'list' => [', ', ' เฆเฆฌเฆ‚ '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekdays_standalone' => ['เฆฐเฆฌเฆฟเฆฌเฆพเฆฐ', 'เฆธเง‹เฆฎเฆฌเฆพเฆฐ', 'เฆฎเฆ™เงเฆ—เฆฒเฆฌเฆพเฆฐ', 'เฆฌเงเฆงเฆฌเฆพเฆฐ', 'เฆฌเงƒเฆนเฆทเงเฆชเฆคเฆฟเฆฌเฆพเฆฐ', 'เฆถเงเฆ•เงเฆฐเฆฌเฆพเฆฐ', 'เฆถเฆจเฆฟเฆฌเฆพเฆฐ'], + 'weekdays_min_standalone' => ['เฆฐเฆƒ', 'เฆธเง‹เฆƒ', 'เฆฎเฆƒ', 'เฆฌเงเฆƒ', 'เฆฌเงƒเฆƒ', 'เฆถเงเฆƒ', 'เฆถเฆจเฆฟ'], + 'months_short_standalone' => ['เฆœเฆพเฆจเงเฆฏเฆผเฆพเฆฐเง€', 'เฆซเง‡เฆฌเงเฆฐเงเฆฏเฆผเฆพเฆฐเง€', 'เฆฎเฆพเฆฐเงเฆš', 'เฆเฆชเงเฆฐเฆฟเฆฒ', 'เฆฎเง‡', 'เฆœเงเฆจ', 'เฆœเงเฆฒเฆพเฆ‡', 'เฆ†เฆ—เฆธเงเฆŸ', 'เฆธเง‡เฆชเงเฆŸเง‡เฆฎเงเฆฌเฆฐ', 'เฆ…เฆ•เงเฆŸเง‹เฆฌเฆฐ', 'เฆจเฆญเง‡เฆฎเงเฆฌเฆฐ', 'เฆกเฆฟเฆธเง‡เฆฎเงเฆฌเฆฐ'], + 'alt_numbers' => ['เงฆ', 'เงง', 'เงจ', 'เงฉ', 'เงช', 'เงซ', 'เงฌ', 'เงญ', 'เงฎ', 'เงฏ'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bn_BD.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bn_BD.php new file mode 100644 index 0000000000..b5b28dd1a0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bn_BD.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ankur Group, Taneem Ahmed, Jamil Ahmed + */ +return array_replace_recursive(require __DIR__.'/bn.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เฆœเฆพเฆจเงเฆฏเฆผเฆพเฆฐเง€', 'เฆซเง‡เฆฌเงเฆฐเงเฆฏเฆผเฆพเฆฐเง€', 'เฆฎเฆพเฆฐเงเฆš', 'เฆเฆชเงเฆฐเฆฟเฆฒ', 'เฆฎเง‡', 'เฆœเงเฆจ', 'เฆœเงเฆฒเฆพเฆ‡', 'เฆ†เฆ—เฆธเงเฆŸ', 'เฆธเง‡เฆชเงเฆŸเง‡เฆฎเงเฆฌเฆฐ', 'เฆ…เฆ•เงเฆŸเง‹เฆฌเฆฐ', 'เฆจเฆญเง‡เฆฎเงเฆฌเฆฐ', 'เฆกเฆฟเฆธเง‡เฆฎเงเฆฌเฆฐ'], + 'months_short' => ['เฆœเฆพเฆจเง', 'เฆซเง‡เฆฌ', 'เฆฎเฆพเฆฐเงเฆš', 'เฆเฆชเงเฆฐเฆฟเฆฒ', 'เฆฎเง‡', 'เฆœเงเฆจ', 'เฆœเงเฆฒเฆพเฆ‡', 'เฆ†เฆ—เฆธเงเฆŸ', 'เฆธเง‡เฆชเงเฆŸเง‡เฆฎเงเฆฌเฆฐ', 'เฆ…เฆ•เงเฆŸเง‹เฆฌเฆฐ', 'เฆจเฆญเง‡เฆฎเงเฆฌเฆฐ', 'เฆกเฆฟเฆธเง‡เฆฎเงเฆฌเฆฐ'], + 'weekdays' => ['เฆฐเฆฌเฆฟเฆฌเฆพเฆฐ', 'เฆธเง‹เฆฎเฆฌเฆพเฆฐ', 'เฆฎเฆ™เงเฆ—เฆฒเฆฌเฆพเฆฐ', 'เฆฌเงเฆงเฆฌเฆพเฆฐ', 'เฆฌเงƒเฆนเฆธเงเฆชเฆคเฆฟเฆฌเฆพเฆฐ', 'เฆถเงเฆ•เงเฆฐเฆฌเฆพเฆฐ', 'เฆถเฆจเฆฟเฆฌเฆพเฆฐ'], + 'weekdays_short' => ['เฆฐเฆฌเฆฟ', 'เฆธเง‹เฆฎ', 'เฆฎเฆ™เงเฆ—เฆฒ', 'เฆฌเงเฆง', 'เฆฌเงƒเฆนเฆƒ', 'เฆถเงเฆ•เงเฆฐ', 'เฆถเฆจเฆฟ'], + 'weekdays_min' => ['เฆฐเฆฌเฆฟ', 'เฆธเง‹เฆฎ', 'เฆฎเฆ™เงเฆ—เฆฒ', 'เฆฌเงเฆง', 'เฆฌเงƒเฆนเฆƒ', 'เฆถเงเฆ•เงเฆฐ', 'เฆถเฆจเฆฟ'], + 'first_day_of_week' => 5, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bn_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bn_IN.php new file mode 100644 index 0000000000..8b3a50e565 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bn_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/bn.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เฆœเฆพเฆจเงเฆฏเฆผเฆพเฆฐเง€', 'เฆซเง‡เฆฌเงเฆฐเงเฆฏเฆผเฆพเฆฐเง€', 'เฆฎเฆพเฆฐเงเฆš', 'เฆเฆชเงเฆฐเฆฟเฆฒ', 'เฆฎเง‡', 'เฆœเงเฆจ', 'เฆœเงเฆฒเฆพเฆ‡', 'เฆ†เฆ—เฆธเงเฆŸ', 'เฆธเง‡เฆชเงเฆŸเง‡เฆฎเงเฆฌเฆฐ', 'เฆ…เฆ•เงเฆŸเง‹เฆฌเฆฐ', 'เฆจเฆญเง‡เฆฎเงเฆฌเฆฐ', 'เฆกเฆฟเฆธเง‡เฆฎเงเฆฌเฆฐ'], + 'months_short' => ['เฆœเฆพเฆจเง', 'เฆซเง‡เฆฌ', 'เฆฎเฆพเฆฐเงเฆš', 'เฆเฆชเงเฆฐเฆฟเฆฒ', 'เฆฎเง‡', 'เฆœเงเฆจ', 'เฆœเงเฆฒเฆพเฆ‡', 'เฆ†เฆ—เฆธเงเฆŸ', 'เฆธเง‡เฆชเงเฆŸเง‡เฆฎเงเฆฌเฆฐ', 'เฆ…เฆ•เงเฆŸเง‹เฆฌเฆฐ', 'เฆจเฆญเง‡เฆฎเงเฆฌเฆฐ', 'เฆกเฆฟเฆธเง‡เฆฎเงเฆฌเฆฐ'], + 'weekdays' => ['เฆฐเฆฌเฆฟเฆฌเฆพเฆฐ', 'เฆธเง‹เฆฎเฆฌเฆพเฆฐ', 'เฆฎเฆ™เงเฆ—เฆฒเฆฌเฆพเฆฐ', 'เฆฌเงเฆงเฆฌเฆพเฆฐ', 'เฆฌเงƒเฆนเฆธเงเฆชเฆคเฆฟเฆฌเฆพเฆฐ', 'เฆถเงเฆ•เงเฆฐเฆฌเฆพเฆฐ', 'เฆถเฆจเฆฟเฆฌเฆพเฆฐ'], + 'weekdays_short' => ['เฆฐเฆฌเฆฟ', 'เฆธเง‹เฆฎ', 'เฆฎเฆ™เงเฆ—เฆฒ', 'เฆฌเงเฆง', 'เฆฌเงƒเฆนเฆธเงเฆชเฆคเฆฟ', 'เฆถเงเฆ•เงเฆฐ', 'เฆถเฆจเฆฟ'], + 'weekdays_min' => ['เฆฐเฆฌเฆฟ', 'เฆธเง‹เฆฎ', 'เฆฎเฆ™เงเฆ—เฆฒ', 'เฆฌเงเฆง', 'เฆฌเงƒเฆนเฆธเงเฆชเฆคเฆฟ', 'เฆถเงเฆ•เงเฆฐ', 'เฆถเฆจเฆฟ'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bo.php new file mode 100644 index 0000000000..99e1bf4c90 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bo.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => '{1}เฝฃเฝผเผ‹เฝ‚เฝ…เฝฒเฝ‚|]1,Inf[:count เฝฃเฝผ', + 'month' => '{1}เฝŸเพณเผ‹เฝ–เผ‹เฝ‚เฝ…เฝฒเฝ‚|]1,Inf[:count เฝŸเพณเผ‹เฝ–', + 'week' => ':count เฝ–เฝ‘เฝดเฝ“เผ‹เฝ•เพฒเฝ‚', + 'day' => '{1}เฝ‰เฝฒเฝ“เผ‹เฝ‚เฝ…เฝฒเฝ‚|]1,Inf[:count เฝ‰เฝฒเฝ“เผ‹', + 'hour' => '{1}เฝ†เฝดเผ‹เฝšเฝผเฝ‘เผ‹เฝ‚เฝ…เฝฒเฝ‚|]1,Inf[:count เฝ†เฝดเผ‹เฝšเฝผเฝ‘', + 'minute' => '{1}เฝฆเพเฝขเผ‹เฝ˜เผ‹เฝ‚เฝ…เฝฒเฝ‚|]1,Inf[:count เฝฆเพเฝขเผ‹เฝ˜', + 'second' => '{1}เฝฃเฝ˜เผ‹เฝฆเฝ„|]1,Inf[:count เฝฆเพเฝขเผ‹เฝ†เผ', + 'ago' => ':time เฝฆเพ”เฝ“เผ‹เฝฃ', + 'from_now' => ':time เฝฃเผ‹', + 'diff_yesterday' => 'เฝเผ‹เฝฆเฝ„', + 'diff_today' => 'เฝ‘เฝฒเผ‹เฝขเฝฒเฝ„', + 'diff_tomorrow' => 'เฝฆเฝ„เผ‹เฝ‰เฝฒเฝ“', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm', + ], + 'calendar' => [ + 'sameDay' => '[เฝ‘เฝฒเผ‹เฝขเฝฒเฝ„] LT', + 'nextDay' => '[เฝฆเฝ„เผ‹เฝ‰เฝฒเฝ“] LT', + 'nextWeek' => '[เฝ–เฝ‘เฝดเฝ“เผ‹เฝ•เพฒเฝ‚เผ‹เฝขเพ—เฝบเฝฆเผ‹เฝ˜], LT', + 'lastDay' => '[เฝเผ‹เฝฆเฝ„] LT', + 'lastWeek' => '[เฝ–เฝ‘เฝดเฝ“เผ‹เฝ•เพฒเฝ‚เผ‹เฝ˜เฝเฝ เผ‹เฝ˜] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'เฝ˜เฝšเฝ“เผ‹เฝ˜เฝผ'; + } + if ($hour < 10) { + return 'เฝžเฝผเฝ‚เฝฆเผ‹เฝ€เฝฆ'; + } + if ($hour < 17) { + return 'เฝ‰เฝฒเฝ“เผ‹เฝ‚เฝดเฝ„'; + } + if ($hour < 20) { + return 'เฝ‘เฝ‚เฝผเฝ„เผ‹เฝ‘เฝ‚'; + } + + return 'เฝ˜เฝšเฝ“เผ‹เฝ˜เฝผ'; + }, + 'months' => ['เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เฝ„เผ‹เฝ”เฝผ', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‚เฝ‰เฝฒเฝฆเผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‚เฝฆเฝดเฝ˜เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝžเฝฒเผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝฃเพ”เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เพฒเฝดเฝ‚เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ‘เฝดเฝ“เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝขเพ’เพฑเฝ‘เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เฝ‚เฝดเผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ‚เฝ…เฝฒเฝ‚เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ‚เฝ‰เฝฒเฝฆเผ‹เฝ”'], + 'months_short' => ['เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เฝ„เผ‹เฝ”เฝผ', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‚เฝ‰เฝฒเฝฆเผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‚เฝฆเฝดเฝ˜เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝžเฝฒเผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝฃเพ”เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เพฒเฝดเฝ‚เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ‘เฝดเฝ“เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝขเพ’เพฑเฝ‘เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เฝ‚เฝดเผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ‚เฝ…เฝฒเฝ‚เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ‚เฝ‰เฝฒเฝฆเผ‹เฝ”'], + 'weekdays' => ['เฝ‚เฝŸเฝ เผ‹เฝ‰เฝฒเผ‹เฝ˜เผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝŸเพณเผ‹เฝ–เผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝ˜เฝฒเฝ‚เผ‹เฝ‘เฝ˜เฝขเผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝฃเพทเฝ‚เผ‹เฝ”เผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝ•เฝดเฝขเผ‹เฝ–เฝด', 'เฝ‚เฝŸเฝ เผ‹เฝ”เผ‹เฝฆเฝ„เฝฆเผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝฆเพคเฝบเฝ“เผ‹เฝ”เผ‹'], + 'weekdays_short' => ['เฝ‰เฝฒเผ‹เฝ˜เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹', 'เฝ˜เฝฒเฝ‚เผ‹เฝ‘เฝ˜เฝขเผ‹', 'เฝฃเพทเฝ‚เผ‹เฝ”เผ‹', 'เฝ•เฝดเฝขเผ‹เฝ–เฝด', 'เฝ”เผ‹เฝฆเฝ„เฝฆเผ‹', 'เฝฆเพคเฝบเฝ“เผ‹เฝ”เผ‹'], + 'weekdays_min' => ['เฝ‰เฝฒเผ‹เฝ˜เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹', 'เฝ˜เฝฒเฝ‚เผ‹เฝ‘เฝ˜เฝขเผ‹', 'เฝฃเพทเฝ‚เผ‹เฝ”เผ‹', 'เฝ•เฝดเฝขเผ‹เฝ–เฝด', 'เฝ”เผ‹เฝฆเฝ„เฝฆเผ‹', 'เฝฆเพคเฝบเฝ“เผ‹เฝ”เผ‹'], + 'list' => [', ', ' เฝจเฝ“เฝ‘เผ‹ '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'months_standalone' => ['เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เฝ„เผ‹เฝ”เฝผเผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‚เฝ‰เฝฒเฝฆเผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‚เฝฆเฝดเฝ˜เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝžเฝฒเผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝฃเพ”เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เพฒเฝดเฝ‚เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ‘เฝดเฝ“เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝขเพ’เพฑเฝ‘เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เฝ‚เฝดเผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ‚เฝ…เฝฒเฝ‚เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ‚เฝ‰เฝฒเฝฆเผ‹เฝ”เผ‹'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bo_CN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bo_CN.php new file mode 100644 index 0000000000..380abb1e72 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bo_CN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/bo.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bo_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bo_IN.php new file mode 100644 index 0000000000..ca50d04933 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bo_IN.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/bo.php', [ + 'meridiem' => ['เฝฆเพ”เผ‹เฝ‘เพฒเฝผเผ‹', 'เฝ•เพฑเฝฒเผ‹เฝ‘เพฒเฝผเผ‹'], + 'weekdays' => ['เฝ‚เฝŸเฝ เผ‹เฝ‰เฝฒเผ‹เฝ˜เผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝŸเพณเผ‹เฝ–เผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝ˜เฝฒเฝ‚เผ‹เฝ‘เฝ˜เฝขเผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝฃเพทเฝ‚เผ‹เฝ”เผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝ•เฝดเฝขเผ‹เฝ–เฝดเผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝ”เผ‹เฝฆเฝ„เฝฆเผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝฆเพคเฝบเฝ“เผ‹เฝ”เผ‹'], + 'weekdays_short' => ['เฝ‰เฝฒเผ‹เฝ˜เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹', 'เฝ˜เฝฒเฝ‚เผ‹เฝ‘เฝ˜เฝขเผ‹', 'เฝฃเพทเฝ‚เผ‹เฝ”เผ‹', 'เฝ•เฝดเฝขเผ‹เฝ–เฝดเผ‹', 'เฝ”เผ‹เฝฆเฝ„เฝฆเผ‹', 'เฝฆเพคเฝบเฝ“เผ‹เฝ”เผ‹'], + 'weekdays_min' => ['เฝ‰เฝฒเผ‹เฝ˜เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹', 'เฝ˜เฝฒเฝ‚เผ‹เฝ‘เฝ˜เฝขเผ‹', 'เฝฃเพทเฝ‚เผ‹เฝ”เผ‹', 'เฝ•เฝดเฝขเผ‹เฝ–เฝดเผ‹', 'เฝ”เผ‹เฝฆเฝ„เฝฆเผ‹', 'เฝฆเพคเฝบเฝ“เผ‹เฝ”เผ‹'], + 'months' => ['เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เฝ„เผ‹เฝ”เฝผ', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‚เฝ‰เฝฒเฝฆเผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‚เฝฆเฝดเฝ˜เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝžเฝฒเผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝฃเพ”เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เพฒเฝดเฝ‚เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ‘เฝดเฝ“เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝขเพ’เพฑเฝ‘เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เฝ‚เฝดเผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ‚เฝ…เฝฒเฝ‚เผ‹เฝ”', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ‚เฝ‰เฝฒเฝฆเผ‹เฝ”'], + 'months_short' => ['เฝŸเพณเผ‹เผก', 'เฝŸเพณเผ‹เผข', 'เฝŸเพณเผ‹เผฃ', 'เฝŸเพณเผ‹เผค', 'เฝŸเพณเผ‹เผฅ', 'เฝŸเพณเผ‹เผฆ', 'เฝŸเพณเผ‹เผง', 'เฝŸเพณเผ‹เผจ', 'เฝŸเพณเผ‹เผฉ', 'เฝŸเพณเผ‹เผกเผ ', 'เฝŸเพณเผ‹เผกเผก', 'เฝŸเพณเผ‹เผกเผข'], + 'months_standalone' => ['เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เฝ„เผ‹เฝ”เฝผเผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‚เฝ‰เฝฒเฝฆเผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‚เฝฆเฝดเฝ˜เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝžเฝฒเผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝฃเพ”เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เพฒเฝดเฝ‚เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ‘เฝดเฝ“เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝขเพ’เพฑเฝ‘เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เฝ‚เฝดเผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ‚เฝ…เฝฒเฝ‚เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ‚เฝ‰เฝฒเฝฆเผ‹เฝ”เผ‹'], + 'weekend' => [0, 0], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY เฝฃเฝผเฝ เฝฒเผ‹MMMเฝšเฝบเฝฆเผ‹D', + 'LLL' => 'เฝฆเพคเพฑเฝฒเผ‹เฝฃเฝผเผ‹YYYY MMMMเฝ เฝฒเผ‹เฝšเฝบเฝฆเผ‹D h:mm a', + 'LLLL' => 'YYYY MMMMเฝ เฝฒเผ‹เฝšเฝบเฝฆเผ‹D, dddd h:mm a', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/br.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/br.php new file mode 100644 index 0000000000..583472fb00 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/br.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Serhan Apaydฤฑn + * - JD Isaacks + */ +return [ + 'year' => '{1}:count bloaz|{3,4,5,9}:count bloaz|[0,Inf[:count vloaz', + 'a_year' => '{1}ur bloaz|{3,4,5,9}:count bloaz|[0,Inf[:count vloaz', + 'month' => '{1}:count miz|{2}:count viz|[0,Inf[:count miz', + 'a_month' => '{1}ur miz|{2}:count viz|[0,Inf[:count miz', + 'week' => ':count sizhun', + 'a_week' => '{1}ur sizhun|:count sizhun', + 'day' => '{1}:count devezh|{2}:count zevezh|[0,Inf[:count devezh', + 'a_day' => '{1}un devezh|{2}:count zevezh|[0,Inf[:count devezh', + 'hour' => ':count eur', + 'a_hour' => '{1}un eur|:count eur', + 'minute' => '{1}:count vunutenn|{2}:count vunutenn|[0,Inf[:count munutenn', + 'a_minute' => '{1}ur vunutenn|{2}:count vunutenn|[0,Inf[:count munutenn', + 'second' => ':count eilenn', + 'a_second' => '{1}un nebeud segondennoรน|[0,Inf[:count eilenn', + 'ago' => ':time \'zo', + 'from_now' => 'a-benn :time', + 'diff_now' => 'bremaรฑ', + 'diff_today' => 'Hiziv', + 'diff_today_regexp' => 'Hiziv(?:\\s+da)?', + 'diff_yesterday' => 'decสผh', + 'diff_yesterday_regexp' => 'Dec\'h(?:\\s+da)?', + 'diff_tomorrow' => 'warcสผhoazh', + 'diff_tomorrow_regexp' => 'Warc\'hoazh(?:\\s+da)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [a viz] MMMM YYYY', + 'LLL' => 'D [a viz] MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D [a viz] MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hiziv da] LT', + 'nextDay' => '[Warc\'hoazh da] LT', + 'nextWeek' => 'dddd [da] LT', + 'lastDay' => '[Dec\'h da] LT', + 'lastWeek' => 'dddd [paset da] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.($number === 1 ? 'aรฑ' : 'vet'); + }, + 'months' => ['Genver', 'C\'hwevrer', 'Meurzh', 'Ebrel', 'Mae', 'Mezheven', 'Gouere', 'Eost', 'Gwengolo', 'Here', 'Du', 'Kerzu'], + 'months_short' => ['Gen', 'C\'hwe', 'Meu', 'Ebr', 'Mae', 'Eve', 'Gou', 'Eos', 'Gwe', 'Her', 'Du', 'Ker'], + 'weekdays' => ['Sul', 'Lun', 'Meurzh', 'Merc\'her', 'Yaou', 'Gwener', 'Sadorn'], + 'weekdays_short' => ['Sul', 'Lun', 'Meu', 'Mer', 'Yao', 'Gwe', 'Sad'], + 'weekdays_min' => ['Su', 'Lu', 'Me', 'Mer', 'Ya', 'Gw', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' hag '], + 'meridiem' => ['A.M.', 'G.M.'], + + 'y' => ':count bl.', + 'd' => ':count d', + 'h' => ':count e', + 'min' => ':count min', + 's' => ':count s', +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/br_FR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/br_FR.php new file mode 100644 index 0000000000..7f541858d0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/br_FR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/br.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/brx.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/brx.php new file mode 100644 index 0000000000..a0a7bf9bc5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/brx.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/brx_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/brx_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/brx_IN.php new file mode 100644 index 0000000000..2d80ced20e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/brx_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'M/D/YY', + ], + 'months' => ['เคœเคพเคจเฅเคตเคพเคฐเฅ€', 'เคซเฅ‡เคฌเฅเคฐเฅเคตเคพเคฐเฅ€', 'เคฎเคพเคฐเฅเคธ', 'เคเคซเฅเคฐเคฟเคฒ', 'เคฎเฅ‡', 'เคœเฅเคจ', 'เคœเฅเคฒเคพเค‡', 'เค†เค—เคธเฅเคฅ', 'เคธเฅ‡เคฌเคฅเฅ‡เคœเฅเคฌเคผเคฐ', 'เค…เค–เคฅเคฌเคฐ', 'เคจเคฌเฅ‡เคœเฅเคฌเคผเคฐ', 'เคฆเคฟเคธเฅ‡เคœเฅเคฌเคผเคฐ'], + 'months_short' => ['เคœเคพเคจเฅเคตเคพเคฐเฅ€', 'เคซเฅ‡เคฌเฅเคฐเฅเคตเคพเคฐเฅ€', 'เคฎเคพเคฐเฅเคธ', 'เคเคชเฅเคฐเคฟเคฒ', 'เคฎเฅ‡', 'เคœเฅเคจ', 'เคœเฅเคฒเคพเค‡', 'เค†เค—เคธเฅเคฅ', 'เคธเฅ‡เคฌเคฅเฅ‡เคœเฅเคฌเคผเคฐ', 'เค…เค–เคฅเคฌเคฐ', 'เคจเคฌเฅ‡เคœเฅเคฌเคผเคฐ', 'เคฆเคฟเคธเฅ‡เคœเฅเคฌเคผเคฐ'], + 'weekdays' => ['เคฐเคฌเคฟเคฌเคพเคฐ', 'เคธเฅ‹เคฌเคพเคฐ', 'เคฎเค‚เค—เคฒเคฌเคพเคฐ', 'เคฌเฅเคฆเคฌเคพเคฐ', 'เคฌเคฟเคธเคฅเคฟเคฌเคพเคฐ', 'เคธเฅเค–เฅเคฐเคฌเคพเคฐ', 'เคธเฅเคจเคฟเคฌเคพเคฐ'], + 'weekdays_short' => ['เคฐเคฌเคฟ', 'เคธเคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคฆ', 'เคฌเคฟเคธเคฅเคฟ', 'เคธเฅเค–เฅเคฐ', 'เคธเฅเคจเคฟ'], + 'weekdays_min' => ['เคฐเคฌเคฟ', 'เคธเคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคฆ', 'เคฌเคฟเคธเคฅเคฟ', 'เคธเฅเค–เฅเคฐ', 'เคธเฅเคจเคฟ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เคซเฅเค‚.', 'เคฌเฅ‡เคฒเคพเคธเฅ‡.'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs.php new file mode 100644 index 0000000000..e5d68083ed --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bokideckonja + * - Josh Soref + * - Franรงois B + * - shaishavgandhi05 + * - Serhan Apaydฤฑn + * - JD Isaacks + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count godina|:count godine|:count godina', + 'y' => ':count godina|:count godine|:count godina', + 'month' => ':count mjesec|:count mjeseca|:count mjeseci', + 'm' => ':count mjesec|:count mjeseca|:count mjeseci', + 'week' => ':count sedmice|:count sedmicu|:count sedmica', + 'w' => ':count sedmice|:count sedmicu|:count sedmica', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count dan|:count dana|:count dana', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count sat|:count sata|:count sati', + 'minute' => ':count minut|:count minuta|:count minuta', + 'min' => ':count minut|:count minuta|:count minuta', + 'second' => ':count sekund|:count sekunda|:count sekundi', + 's' => ':count sekund|:count sekunda|:count sekundi', + 'ago' => 'prije :time', + 'from_now' => 'za :time', + 'after' => 'nakon :time', + 'before' => ':time ranije', + 'diff_now' => 'sada', + 'diff_today' => 'danas', + 'diff_today_regexp' => 'danas(?:\\s+u)?', + 'diff_yesterday' => 'juฤer', + 'diff_yesterday_regexp' => 'juฤer(?:\\s+u)?', + 'diff_tomorrow' => 'sutra', + 'diff_tomorrow_regexp' => 'sutra(?:\\s+u)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danas u] LT', + 'nextDay' => '[sutra u] LT', + 'nextWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastDay' => '[juฤer u] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 0: + case 3: + return '[proลกlu] dddd [u] LT'; + case 6: + return '[proลกle] [subote] [u] LT'; + default: + return '[proลกli] dddd [u] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mart', 'april', 'maj', 'juni', 'juli', 'august', 'septembar', 'oktobar', 'novembar', 'decembar'], + 'months_short' => ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'ฤetvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sri.', 'ฤet.', 'pet.', 'sub.'], + 'weekdays_min' => ['ne', 'po', 'ut', 'sr', 'ฤe', 'pe', 'su'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], + 'meridiem' => ['prijepodne', 'popodne'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php new file mode 100644 index 0000000000..0a591176db --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/bs.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs_Cyrl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs_Cyrl.php new file mode 100644 index 0000000000..e1a1744765 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs_Cyrl.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/bs.php', [ + 'meridiem' => ['ะฟั€ะต ะฟะพะดะฝะต', 'ะฟะพะฟะพะดะฝะต'], + 'weekdays' => ['ะฝะตะดั˜ะตั™ะฐ', 'ะฟะพะฝะตะดั˜ะตั™ะฐะบ', 'ัƒั‚ะพั€ะฐะบ', 'ัั€ะธั˜ะตะดะฐ', 'ั‡ะตั‚ะฒั€ั‚ะฐะบ', 'ะฟะตั‚ะฐะบ', 'ััƒะฑะพั‚ะฐ'], + 'weekdays_short' => ['ะฝะตะด', 'ะฟะพะฝ', 'ัƒั‚ะพ', 'ัั€ะธ', 'ั‡ะตั‚', 'ะฟะตั‚', 'ััƒะฑ'], + 'weekdays_min' => ['ะฝะตะด', 'ะฟะพะฝ', 'ัƒั‚ะพ', 'ัั€ะธ', 'ั‡ะตั‚', 'ะฟะตั‚', 'ััƒะฑ'], + 'months' => ['ั˜ะฐะฝัƒะฐั€', 'ั„ะตะฑั€ัƒะฐั€', 'ะผะฐั€ั‚', 'ะฐะฟั€ะธะป', 'ะผะฐั˜', 'ั˜ัƒะฝะธ', 'ั˜ัƒะปะธ', 'ะฐัƒะณัƒัั‚', 'ัะตะฟั‚ะตะผะฑะฐั€', 'ะพะบั‚ะพะฑะฐั€', 'ะฝะพะฒะตะผะฑะฐั€', 'ะดะตั†ะตะผะฑะฐั€'], + 'months_short' => ['ั˜ะฐะฝ', 'ั„ะตะฑ', 'ะผะฐั€', 'ะฐะฟั€', 'ะผะฐั˜', 'ั˜ัƒะฝ', 'ั˜ัƒะป', 'ะฐัƒะณ', 'ัะตะฟ', 'ะพะบั‚', 'ะฝะพะฒ', 'ะดะตั†'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D.M.YYYY.', + 'LL' => 'DD.MM.YYYY.', + 'LLL' => 'DD. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, DD. MMMM YYYY. HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs_Latn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs_Latn.php new file mode 100644 index 0000000000..b4e363e752 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs_Latn.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/bs.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/byn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/byn.php new file mode 100644 index 0000000000..7125f3d6b1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/byn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/byn_ER.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/byn_ER.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/byn_ER.php new file mode 100644 index 0000000000..ad6753340a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/byn_ER.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['แˆแ‹ฐแ‰ตแˆช', 'แŠซแ‰ฅแŠฝแ‰ฅแ‰ฒ', 'แŠญแ‰ฅแˆ‹', 'แ‹แŒ…แŠบแˆช', 'แŠญแ‰ขแ‰…แˆช', 'แˆแŠชแŠคแˆ แ‰ตแŒ“ฬ…แŠ’แˆช', 'แŠฐแˆญแŠฉ', 'แˆ›แˆญแ‹ซแˆ แ‰ตแˆช', 'แ‹ซแŠธแŠ’ แˆ˜แˆณแ‰…แˆˆแˆช', 'แˆ˜แ‰ฐแˆ‰', 'แˆแŠชแŠคแˆ แˆ˜แˆฝแ‹ˆแˆช', 'แ‰ฐแˆ•แˆณแˆตแˆช'], + 'months_short' => ['แˆแ‹ฐแ‰ต', 'แŠซแ‰ฅแŠฝ', 'แŠญแ‰ฅแˆ‹', 'แ‹แŒ…แŠบ', 'แŠญแ‰ขแ‰…', 'แˆ/แ‰ต', 'แŠฐแˆญ', 'แˆ›แˆญแ‹ซ', 'แ‹ซแŠธแŠ’', 'แˆ˜แ‰ฐแˆ‰', 'แˆ/แˆ', 'แ‰ฐแˆ•แˆณ'], + 'weekdays' => ['แˆฐแŠ•แ‰ แˆญ แ‰…แ‹ณแ‹…', 'แˆฐแŠ‘', 'แˆฐแˆŠแŒ', 'แˆˆแŒ“ แ‹ˆแˆช แˆˆแ‰ฅแ‹‹', 'แŠฃแˆแ‹ต', 'แŠฃแˆญแ‰ฅ', 'แˆฐแŠ•แ‰ แˆญ แˆฝแŒ“แ‹…'], + 'weekdays_short' => ['แˆฐ/แ‰…', 'แˆฐแŠ‘', 'แˆฐแˆŠแŒ', 'แˆˆแŒ“', 'แŠฃแˆแ‹ต', 'แŠฃแˆญแ‰ฅ', 'แˆฐ/แˆฝ'], + 'weekdays_min' => ['แˆฐ/แ‰…', 'แˆฐแŠ‘', 'แˆฐแˆŠแŒ', 'แˆˆแŒ“', 'แŠฃแˆแ‹ต', 'แŠฃแˆญแ‰ฅ', 'แˆฐ/แˆฝ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['แ‹แ‹ฑแˆต แŒƒแ‰ฅ', 'แ‹แ‹ฑแˆต แ‹ฐแˆแ‰ข'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca.php new file mode 100644 index 0000000000..b8b19946be --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - mestremuten + * - Franรงois B + * - Marc Ordinas i Llopis + * - Pere Orga + * - JD Isaacks + * - Quentรญ + * - Vรญctor Dรญaz + * - Xavi + * - qcardona + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count any|:count anys', + 'a_year' => 'un any|:count anys', + 'y' => ':count any|:count anys', + 'month' => ':count mes|:count mesos', + 'a_month' => 'un mes|:count mesos', + 'm' => ':count mes|:count mesos', + 'week' => ':count setmana|:count setmanes', + 'a_week' => 'una setmana|:count setmanes', + 'w' => ':count setmana|:count setmanes', + 'day' => ':count dia|:count dies', + 'a_day' => 'un dia|:count dies', + 'd' => ':count d', + 'hour' => ':count hora|:count hores', + 'a_hour' => 'una hora|:count hores', + 'h' => ':count h', + 'minute' => ':count minut|:count minuts', + 'a_minute' => 'un minut|:count minuts', + 'min' => ':count min', + 'second' => ':count segon|:count segons', + 'a_second' => 'uns segons|:count segons', + 's' => ':count s', + 'ago' => 'fa :time', + 'from_now' => 'd\'aquรญ a :time', + 'after' => ':time desprรฉs', + 'before' => ':time abans', + 'diff_now' => 'ara mateix', + 'diff_today' => 'avui', + 'diff_today_regexp' => 'avui(?:\\s+a)?(?:\\s+les)?', + 'diff_yesterday' => 'ahir', + 'diff_yesterday_regexp' => 'ahir(?:\\s+a)?(?:\\s+les)?', + 'diff_tomorrow' => 'demร ', + 'diff_tomorrow_regexp' => 'demร (?:\\s+a)?(?:\\s+les)?', + 'diff_before_yesterday' => 'abans d\'ahir', + 'diff_after_tomorrow' => 'demร  passat', + 'period_recurrences' => ':count cop|:count cops', + 'period_interval' => 'cada :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'fins a :date', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM [de] YYYY', + 'LLL' => 'D MMMM [de] YYYY [a les] H:mm', + 'LLLL' => 'dddd D MMMM [de] YYYY [a les] H:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $current) { + return '[avui a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'nextDay' => function (CarbonInterface $current) { + return '[demร  a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'nextWeek' => function (CarbonInterface $current) { + return 'dddd [a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'lastDay' => function (CarbonInterface $current) { + return '[ahir a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'lastWeek' => function (CarbonInterface $current) { + return '[el] dddd [passat a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + return $number.( + ($period === 'w' || $period === 'W') ? 'a' : ( + ($number === 1) ? 'r' : ( + ($number === 2) ? 'n' : ( + ($number === 3) ? 'r' : ( + ($number === 4) ? 't' : 'รจ' + ) + ) + ) + ) + ); + }, + 'months' => ['de gener', 'de febrer', 'de marรง', 'd\'abril', 'de maig', 'de juny', 'de juliol', 'd\'agost', 'de setembre', 'd\'octubre', 'de novembre', 'de desembre'], + 'months_standalone' => ['gener', 'febrer', 'marรง', 'abril', 'maig', 'juny', 'juliol', 'agost', 'setembre', 'octubre', 'novembre', 'desembre'], + 'months_short' => ['de gen.', 'de febr.', 'de marรง', 'd\'abr.', 'de maig', 'de juny', 'de jul.', 'd\'ag.', 'de set.', 'd\'oct.', 'de nov.', 'de des.'], + 'months_short_standalone' => ['gen.', 'febr.', 'marรง', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'], + 'months_regexp' => '/(D[oD]?[\s,]+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['diumenge', 'dilluns', 'dimarts', 'dimecres', 'dijous', 'divendres', 'dissabte'], + 'weekdays_short' => ['dg.', 'dl.', 'dt.', 'dc.', 'dj.', 'dv.', 'ds.'], + 'weekdays_min' => ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' i '], + 'meridiem' => ['a. m.', 'p. m.'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_AD.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_AD.php new file mode 100644 index 0000000000..861acd2a06 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_AD.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES.php new file mode 100644 index 0000000000..50049786c3 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ca.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES_Valencia.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES_Valencia.php new file mode 100644 index 0000000000..1c16421ada --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES_Valencia.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'ca'); + }, 'ca_ES_Valencia'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_FR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_FR.php new file mode 100644 index 0000000000..861acd2a06 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_FR.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_IT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_IT.php new file mode 100644 index 0000000000..861acd2a06 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca_IT.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ccp.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ccp.php new file mode 100644 index 0000000000..99c1dcacd7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ccp.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['๐‘„ข๐‘„ง๐‘„๐‘„จ๐‘„๐‘„ข๐‘„ด', '๐‘„ฅ๐‘„ง๐‘„Ÿ๐‘„ด๐‘„๐‘„ข๐‘„ด', '๐‘„Ÿ๐‘„ง๐‘„๐‘„‰๐‘„ง๐‘„ฃ๐‘„ด๐‘„๐‘„ข๐‘„ด', '๐‘„๐‘„ช๐‘„–๐‘„ด๐‘„๐‘„ข๐‘„ด', '๐‘„๐‘„ณ๐‘„ข๐‘„จ๐‘„ฅ๐‘„ช๐‘„›๐‘„ด๐‘„๐‘„ข๐‘„ด', '๐‘„ฅ๐‘„ช๐‘„‡๐‘„ด๐‘„‡๐‘„ฎ๐‘„ข๐‘„ด๐‘„๐‘„ข๐‘„ด', '๐‘„ฅ๐‘„ง๐‘„š๐‘„จ๐‘„๐‘„ข๐‘„ด'], + 'weekdays_short' => ['๐‘„ข๐‘„ง๐‘„๐‘„จ', '๐‘„ฅ๐‘„ง๐‘„Ÿ๐‘„ด', '๐‘„Ÿ๐‘„ง๐‘„๐‘„‰๐‘„ง๐‘„ฃ๐‘„ด', '๐‘„๐‘„ช๐‘„–๐‘„ด', '๐‘„๐‘„ณ๐‘„ข๐‘„จ๐‘„ฅ๐‘„ช๐‘„›๐‘„ด', '๐‘„ฅ๐‘„ช๐‘„‡๐‘„ด๐‘„‡๐‘„ฎ๐‘„ข๐‘„ด', '๐‘„ฅ๐‘„ง๐‘„š๐‘„จ'], + 'weekdays_min' => ['๐‘„ข๐‘„ง๐‘„๐‘„จ', '๐‘„ฅ๐‘„ง๐‘„Ÿ๐‘„ด', '๐‘„Ÿ๐‘„ง๐‘„๐‘„‰๐‘„ง๐‘„ฃ๐‘„ด', '๐‘„๐‘„ช๐‘„–๐‘„ด', '๐‘„๐‘„ณ๐‘„ข๐‘„จ๐‘„ฅ๐‘„ช๐‘„›๐‘„ด', '๐‘„ฅ๐‘„ช๐‘„‡๐‘„ด๐‘„‡๐‘„ฎ๐‘„ข๐‘„ด', '๐‘„ฅ๐‘„ง๐‘„š๐‘„จ'], + 'months' => ['๐‘„Ž๐‘„š๐‘„ช๐‘„ ๐‘„ข๐‘„จ', '๐‘„œ๐‘„ฌ๐‘„›๐‘„ด๐‘„๐‘„ณ๐‘„ข๐‘„ช๐‘„ ๐‘„ข๐‘„จ', '๐‘„Ÿ๐‘„ข๐‘„ด๐‘„Œ๐‘„ง', '๐‘„ƒ๐‘„ฌ๐‘„›๐‘„ณ๐‘„ข๐‘„จ๐‘„ฃ๐‘„ด', '๐‘„Ÿ๐‘„ฌ', '๐‘„Ž๐‘„ช๐‘„š๐‘„ด', '๐‘„Ž๐‘„ช๐‘„ฃ๐‘„ญ', '๐‘„ƒ๐‘„‰๐‘„ง๐‘„Œ๐‘„ด๐‘„‘๐‘„ด', '๐‘„ฅ๐‘„ฌ๐‘„›๐‘„ด๐‘„‘๐‘„ฌ๐‘„Ÿ๐‘„ด๐‘„๐‘„ง๐‘„ข๐‘„ด', '๐‘„ƒ๐‘„ง๐‘„‡๐‘„ด๐‘„‘๐‘„ฌ๐‘„๐‘„ง๐‘„ข๐‘„ด', '๐‘„š๐‘„ง๐‘„ž๐‘„ฌ๐‘„Ÿ๐‘„ด๐‘„๐‘„ง๐‘„ข๐‘„ด', '๐‘„“๐‘„จ๐‘„ฅ๐‘„ฌ๐‘„Ÿ๐‘„ด๐‘„๐‘„ง๐‘„ข๐‘„ด'], + 'months_short' => ['๐‘„Ž๐‘„š๐‘„ช', '๐‘„œ๐‘„ฌ๐‘„›๐‘„ด', '๐‘„Ÿ๐‘„ข๐‘„ด๐‘„Œ๐‘„ง', '๐‘„ƒ๐‘„ฌ๐‘„›๐‘„ณ๐‘„ข๐‘„จ๐‘„ฃ๐‘„ด', '๐‘„Ÿ๐‘„ฌ', '๐‘„Ž๐‘„ช๐‘„š๐‘„ด', '๐‘„Ž๐‘„ช๐‘„ฃ๐‘„ญ', '๐‘„ƒ๐‘„‰๐‘„ง๐‘„Œ๐‘„ด๐‘„‘๐‘„ด', '๐‘„ฅ๐‘„ฌ๐‘„›๐‘„ด๐‘„‘๐‘„ฌ๐‘„Ÿ๐‘„ด๐‘„๐‘„ง๐‘„ข๐‘„ด', '๐‘„ƒ๐‘„ง๐‘„‡๐‘„ด๐‘„‘๐‘„ฎ๐‘„๐‘„ง๐‘„ข๐‘„ด', '๐‘„š๐‘„ง๐‘„ž๐‘„ฌ๐‘„Ÿ๐‘„ด๐‘„๐‘„ง๐‘„ข๐‘„ด', '๐‘„“๐‘„จ๐‘„ฅ๐‘„ฌ๐‘„Ÿ๐‘„ด๐‘„๐‘„ข๐‘„ด'], + 'months_short_standalone' => ['๐‘„Ž๐‘„š๐‘„ช๐‘„ ๐‘„ข๐‘„จ', '๐‘„œ๐‘„ฌ๐‘„›๐‘„ด๐‘„๐‘„ณ๐‘„ข๐‘„ช๐‘„ ๐‘„ข๐‘„จ', '๐‘„Ÿ๐‘„ข๐‘„ด๐‘„Œ๐‘„ง', '๐‘„ƒ๐‘„ฌ๐‘„›๐‘„ณ๐‘„ข๐‘„จ๐‘„ฃ๐‘„ด', '๐‘„Ÿ๐‘„ฌ', '๐‘„Ž๐‘„ช๐‘„š๐‘„ด', '๐‘„Ž๐‘„ช๐‘„ฃ๐‘„ญ', '๐‘„ƒ๐‘„‰๐‘„ง๐‘„Œ๐‘„ด๐‘„‘๐‘„ด', '๐‘„ฅ๐‘„ฌ๐‘„›๐‘„ด๐‘„‘๐‘„ฌ๐‘„Ÿ๐‘„ด๐‘„๐‘„ง๐‘„ข๐‘„ด', '๐‘„ƒ๐‘„ง๐‘„‡๐‘„ด๐‘„‘๐‘„ฎ๐‘„๐‘„ง๐‘„ข๐‘„ด', '๐‘„š๐‘„ง๐‘„ž๐‘„ฌ๐‘„Ÿ๐‘„ด๐‘„๐‘„ง๐‘„ข๐‘„ด', '๐‘„“๐‘„จ๐‘„ฅ๐‘„ฌ๐‘„Ÿ๐‘„ด๐‘„๐‘„ง๐‘„ข๐‘„ด'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM, YYYY h:mm a', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ccp_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ccp_IN.php new file mode 100644 index 0000000000..c1fa8af0dc --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ccp_IN.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ccp.php', [ + 'weekend' => [0, 0], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ce.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ce.php new file mode 100644 index 0000000000..f99f6ffd56 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ce.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ce_RU.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ce_RU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ce_RU.php new file mode 100644 index 0000000000..f769856284 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ce_RU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ANCHR + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY.DD.MM', + ], + 'months' => ['ะฏะฝะฒะฐั€ัŒ', 'ะคะตะฒั€ะฐะปัŒ', 'ะœะฐั€ั‚', 'ะะฟั€ะตะปัŒ', 'ะœะฐะน', 'ะ˜ัŽะฝัŒ', 'ะ˜ัŽะปัŒ', 'ะะฒะณัƒัั‚', 'ะกะตะฝั‚ัะฑั€ัŒ', 'ะžะบั‚ัะฑั€ัŒ', 'ะะพัะฑั€ัŒ', 'ะ”ะตะบะฐะฑั€ัŒ'], + 'months_short' => ['ัะฝะฒ', 'ั„ะตะฒ', 'ะผะฐั€', 'ะฐะฟั€', 'ะผะฐะน', 'ะธัŽะฝ', 'ะธัŽะป', 'ะฐะฒะณ', 'ัะตะฝ', 'ะพะบั‚', 'ะฝะพั', 'ะดะตะบ'], + 'weekdays' => ['ะšำ€ะธั€ะฐะฝะฐะฝ ะดะต', 'ะžั€ัˆะพั‚ะฐะฝ ะดะต', 'ะจะธะฝะฐั€ะธะฝ ะดะต', 'ะšั…ะฐะฐั€ะธะฝ ะดะต', 'ะ•ะฐั€ะธะฝ ะดะต', 'ะŸำ€ะตั€ะฐัะบะฐะฝ ะดะต', 'ะจะพั‚ ะดะต'], + 'weekdays_short' => ['ะšำ€', 'ะžั€', 'ะจะธ', 'ะšั…', 'ะ•ะฐ', 'ะŸำ€', 'ะจะพ'], + 'weekdays_min' => ['ะšำ€', 'ะžั€', 'ะจะธ', 'ะšั…', 'ะ•ะฐ', 'ะŸำ€', 'ะจะพ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ัˆะพ', + 'y' => ':count ัˆะพ', + 'a_year' => ':count ัˆะพ', + + 'month' => ':count ะฑัƒั‚ั‚', + 'm' => ':count ะฑัƒั‚ั‚', + 'a_month' => ':count ะฑัƒั‚ั‚', + + 'week' => ':count ะบำะธั€ะฐ', + 'w' => ':count ะบำะธั€ะฐ', + 'a_week' => ':count ะบำะธั€ะฐ', + + 'day' => ':count ะดะต', + 'd' => ':count ะดะต', + 'a_day' => ':count ะดะต', + + 'hour' => ':count ัะฐั…ัŒั‚', + 'h' => ':count ัะฐั…ัŒั‚', + 'a_hour' => ':count ัะฐั…ัŒั‚', + + 'minute' => ':count ะผะธะฝะพั‚', + 'min' => ':count ะผะธะฝะพั‚', + 'a_minute' => ':count ะผะธะฝะพั‚', + + 'second' => ':count ัะตะบัƒะฝะด', + 's' => ':count ัะตะบัƒะฝะด', + 'a_second' => ':count ัะตะบัƒะฝะด', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cgg.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cgg.php new file mode 100644 index 0000000000..09bcc1c722 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cgg.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Sande', 'Orwokubanza', 'Orwakabiri', 'Orwakashatu', 'Orwakana', 'Orwakataano', 'Orwamukaaga'], + 'weekdays_short' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'weekdays_min' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'months' => ['Okwokubanza', 'Okwakabiri', 'Okwakashatu', 'Okwakana', 'Okwakataana', 'Okwamukaaga', 'Okwamushanju', 'Okwamunaana', 'Okwamwenda', 'Okwaikumi', 'Okwaikumi na kumwe', 'Okwaikumi na ibiri'], + 'months_short' => ['KBZ', 'KBR', 'KST', 'KKN', 'KTN', 'KMK', 'KMS', 'KMN', 'KMW', 'KKM', 'KNK', 'KNB'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'day' => ':count ruhanga', // less reliable + 'd' => ':count ruhanga', // less reliable + 'a_day' => ':count ruhanga', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/chr.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/chr.php new file mode 100644 index 0000000000..e26190f15a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/chr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/chr_US.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/chr_US.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/chr_US.php new file mode 100644 index 0000000000..371353ef87 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/chr_US.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Cherokee Nation Joseph Erb josepherb7@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YYYY', + ], + 'months' => ['แŽคแƒแŽธแ”แ…', 'แŽงแŽฆแŽต', 'แŽ แ…แฑ', 'แŽงแฌแ‚', 'แŽ แ‚แแŽฌแ˜', 'แ•แŽญแŽทแฑ', 'แŽซแฐแ‰แ‚', 'แŽฆแŽถแ‚', 'แšแŽตแแ—', 'แšแ‚แ…แ—', 'แ…แ“แ•แ†', 'แŽฅแแŽฉแฑ'], + 'months_short' => ['แŽคแƒ', 'แŽงแŽฆ', 'แŽ แ…', 'แŽงแฌ', 'แŽ แ‚', 'แ•แŽญ', 'แŽซแฐ', 'แŽฆแŽถ', 'แšแŽต', 'แšแ‚', 'แ…แ“', 'แŽฅแ'], + 'weekdays' => ['แŽคแŽพแ™แ“แ†แแŽฌ', 'แŽคแŽพแ™แ“แ‰แ…แŽฏ', 'แ”แŽตแแŽขแŽฆ', 'แฆแŽขแแŽขแŽฆ', 'แ…แŽฉแแŽขแŽฆ', 'แงแŽพแŽฉแŽถแแ—', 'แŽคแŽพแ™แ“แˆแ•แŽพ'], + 'weekdays_short' => ['แ†แแŽฌ', 'แ‰แ…แŽฏ', 'แ”แŽตแ', 'แฆแŽขแ', 'แ…แŽฉแ', 'แงแŽพแŽฉ', 'แˆแ•แŽพ'], + 'weekdays_min' => ['แ†แแŽฌ', 'แ‰แ…แŽฏ', 'แ”แŽตแ', 'แฆแŽขแ', 'แ…แŽฉแ', 'แงแŽพแŽฉ', 'แˆแ•แŽพ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['แŒแŽพแŽด', 'แ’แŽฏแฑแŽขแ—แข', '๊ฎœ๊ฎŽ๊ฎ„', '๊ฎข๊ญฟแน๊ญฒ๊ฎง๊ฎฒ'], + + 'second' => ':count แแŽข', // less reliable + 's' => ':count แแŽข', // less reliable + 'a_second' => ':count แแŽข', // less reliable + + 'year' => ':count แ‘แ•แ˜แดแ“', + 'y' => ':count แ‘แ•แ˜แดแ“', + 'a_year' => ':count แ‘แ•แ˜แดแ“', + + 'month' => ':count แแ…แ™', + 'm' => ':count แแ…แ™', + 'a_month' => ':count แแ…แ™', + + 'week' => ':count แ‘แŽพแ™แ“แ†แแ—', + 'w' => ':count แ‘แŽพแ™แ“แ†แแ—', + 'a_week' => ':count แ‘แŽพแ™แ“แ†แแ—', + + 'day' => ':count แŽขแŽฆ', + 'd' => ':count แŽขแŽฆ', + 'a_day' => ':count แŽขแŽฆ', + + 'hour' => ':count แ‘แŸแŽถแ›', + 'h' => ':count แ‘แŸแŽถแ›', + 'a_hour' => ':count แ‘แŸแŽถแ›', + + 'minute' => ':count แŽขแฏแ”แฌแแ”แ…', + 'min' => ':count แŽขแฏแ”แฌแแ”แ…', + 'a_minute' => ':count แŽขแฏแ”แฌแแ”แ…', + + 'ago' => ':time แฅแŽจแ’', + 'from_now' => 'แŽพแŽฟ :time', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ckb.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ckb.php new file mode 100644 index 0000000000..acf4dc2803 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ckb.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Swara Mohammed + */ +$months = [ + 'ฺ•Žุจ•ู†ุฏุงู†', + 'ฺ••ุด•ู…•', + 'ู†•ูˆุฑ†ุฒ', + 'ฺฏูˆฺตุงู†', + 'ุฌ†ุฒ•ุฑุฏุงู†', + 'ูพูˆุดูพ•ฺ•', + 'ฺฏ•ู„ุงูˆŽฺ˜', + 'ุฎ•ุฑู…ุงู†ุงู†', + 'ฺ••ุฒุจ•ุฑ', + 'ฺฏ•ฺตุงฺ•Žุฒุงู†', + 'ุณ•ุฑู…ุงูˆ•ุฑุฒ', + 'ุจ•ูุฑุงู†ุจุงุฑ', +]; + +return [ + 'year' => implode('|', ['{0}:count ุณุงฺตŽฺฉ', '{1}ุณุงฺตŽฺฉ', '{2}ุฏูˆูˆ ุณุงฺต', ']2,11[:count ุณุงฺต', ']10,Inf[:count ุณุงฺต']), + 'a_year' => implode('|', ['{0}:count ุณุงฺตŽฺฉ', '{1}ุณุงฺตŽฺฉ', '{2}ุฏูˆูˆ ุณุงฺต', ']2,11[:count ุณุงฺต', ']10,Inf[:count ุณุงฺต']), + 'month' => implode('|', ['{0}:count ู…ุงู†ฺฏŽฺฉ', '{1}ู…ุงู†ฺฏŽฺฉ', '{2}ุฏูˆูˆ ู…ุงู†ฺฏ', ']2,11[:count ู…ุงู†ฺฏ', ']10,Inf[:count ู…ุงู†ฺฏ']), + 'a_month' => implode('|', ['{0}:count ู…ุงู†ฺฏŽฺฉ', '{1}ู…ุงู†ฺฏŽฺฉ', '{2}ุฏูˆูˆ ู…ุงู†ฺฏ', ']2,11[:count ู…ุงู†ฺฏ', ']10,Inf[:count ู…ุงู†ฺฏ']), + 'week' => implode('|', ['{0}:count ู‡•ูุช•Œ•ฺฉ', '{1}ู‡•ูุช•Œ•ฺฉ', '{2}ุฏูˆูˆ ู‡•ูุช•', ']2,11[:count ู‡•ูุช•', ']10,Inf[:count ู‡•ูุช•']), + 'a_week' => implode('|', ['{0}:count ู‡•ูุช•Œ•ฺฉ', '{1}ู‡•ูุช•Œ•ฺฉ', '{2}ุฏูˆูˆ ู‡•ูุช•', ']2,11[:count ู‡•ูุช•', ']10,Inf[:count ู‡•ูุช•']), + 'day' => implode('|', ['{0}:count ฺ•†ฺ˜Žฺฉ', '{1}ฺ•†ฺ˜Žฺฉ', '{2}ุฏูˆูˆ ฺ•†ฺ˜', ']2,11[:count ฺ•†ฺ˜', ']10,Inf[:count ฺ•†ฺ˜']), + 'a_day' => implode('|', ['{0}:count ฺ•†ฺ˜Žฺฉ', '{1}ฺ•†ฺ˜Žฺฉ', '{2}ุฏูˆูˆ ฺ•†ฺ˜', ']2,11[:count ฺ•†ฺ˜', ']10,Inf[:count ฺ•†ฺ˜']), + 'hour' => implode('|', ['{0}:count ฺฉุงุชฺ˜ู…ŽุฑŽฺฉ', '{1}ฺฉุงุชฺ˜ู…ŽุฑŽฺฉ', '{2}ุฏูˆูˆ ฺฉุงุชฺ˜ู…Žุฑ', ']2,11[:count ฺฉุงุชฺ˜ู…Žุฑ', ']10,Inf[:count ฺฉุงุชฺ˜ู…Žุฑ']), + 'a_hour' => implode('|', ['{0}:count ฺฉุงุชฺ˜ู…ŽุฑŽฺฉ', '{1}ฺฉุงุชฺ˜ู…ŽุฑŽฺฉ', '{2}ุฏูˆูˆ ฺฉุงุชฺ˜ู…Žุฑ', ']2,11[:count ฺฉุงุชฺ˜ู…Žุฑ', ']10,Inf[:count ฺฉุงุชฺ˜ู…Žุฑ']), + 'minute' => implode('|', ['{0}:count ุฎูˆู„•ฺฉŽฺฉ', '{1}ุฎูˆู„•ฺฉŽฺฉ', '{2}ุฏูˆูˆ ุฎูˆู„•ฺฉ', ']2,11[:count ุฎูˆู„•ฺฉ', ']10,Inf[:count ุฎูˆู„•ฺฉ']), + 'a_minute' => implode('|', ['{0}:count ุฎูˆู„•ฺฉŽฺฉ', '{1}ุฎูˆู„•ฺฉŽฺฉ', '{2}ุฏูˆูˆ ุฎูˆู„•ฺฉ', ']2,11[:count ุฎูˆู„•ฺฉ', ']10,Inf[:count ุฎูˆู„•ฺฉ']), + 'second' => implode('|', ['{0}:count ฺ†ุฑฺฉ•Œ•ฺฉ', '{1}ฺ†ุฑฺฉ•Œ•ฺฉ', '{2}ุฏูˆูˆ ฺ†ุฑฺฉ•', ']2,11[:count ฺ†ุฑฺฉ•', ']10,Inf[:count ฺ†ุฑฺฉ•']), + 'a_second' => implode('|', ['{0}:count ฺ†ุฑฺฉ•Œ•ฺฉ', '{1}ฺ†ุฑฺฉ•Œ•ฺฉ', '{2}ุฏูˆูˆ ฺ†ุฑฺฉ•', ']2,11[:count ฺ†ุฑฺฉ•', ']10,Inf[:count ฺ†ุฑฺฉ•']), + 'ago' => 'ูพŽุด :time', + 'from_now' => ':time ู„• ุฆŽุณุชุงูˆ•', + 'after' => 'ุฏูˆุงŒ :time', + 'before' => 'ูพŽุด :time', + 'diff_now' => 'ุฆŽุณุชุง', + 'diff_today' => 'ุฆ•ู…ฺ•†', + 'diff_today_regexp' => 'ฺ•†ฺ˜(?:\\s+ู„•)?(?:\\s+ฺฉุงุชฺ˜ู…Žุฑ)?', + 'diff_yesterday' => 'ุฏูˆŽู†Ž', + 'diff_yesterday_regexp' => 'ุฏูˆŽู†Ž(?:\\s+ู„•)?(?:\\s+ฺฉุงุชฺ˜ู…Žุฑ)?', + 'diff_tomorrow' => 'ุณุจ•Œู†Ž', + 'diff_tomorrow_regexp' => 'ุณุจ•Œู†Ž(?:\\s+ู„•)?(?:\\s+ฺฉุงุชฺ˜ู…Žุฑ)?', + 'diff_before_yesterday' => 'ูพŽุด ุฏูˆŽู†Ž', + 'diff_after_tomorrow' => 'ุฏูˆุงŒ ุณุจ•Œู†Ž', + 'period_recurrences' => implode('|', ['{0}ุฌุงุฑ', '{1}ุฌุงุฑ', '{2}:count ุฏูˆูˆุฌุงุฑ', ']2,11[:count ุฌุงุฑ', ']10,Inf[:count ุฌุงุฑ']), + 'period_interval' => 'ู‡•ู…ูˆูˆ :interval', + 'period_start_date' => 'ู„• :date', + 'period_end_date' => 'ุจ† :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['Œ•ฺฉุด•ู…ู…•', 'ุฏูˆูˆุด•ู…ู…•', 'ุณŽุด•ู…ู…•', 'ฺ†ูˆุงุฑุด•ู…ู…•', 'ูพŽู†ุฌุด•ู…ู…•', 'ู‡•Œู†Œ', 'ุด•ู…ู…•'], + 'weekdays_short' => ['Œ•ฺฉุด•ู…ู…•', 'ุฏูˆูˆุด•ู…ู…•', 'ุณŽุด•ู…ู…•', 'ฺ†ูˆุงุฑุด•ู…ู…•', 'ูพŽู†ุฌุด•ู…ู…•', 'ู‡•Œู†Œ', 'ุด•ู…ู…•'], + 'weekdays_min' => ['Œ•ฺฉุด•ู…ู…•', 'ุฏูˆูˆุด•ู…ู…•', 'ุณŽุด•ู…ู…•', 'ฺ†ูˆุงุฑุด•ู…ู…•', 'ูพŽู†ุฌุด•ู…ู…•', 'ู‡•Œู†Œ', 'ุด•ู…ู…•'], + 'list' => ['ุŒ ', ' ูˆ '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ุฆ•ู…ฺ•† ู„• ฺฉุงุชฺ˜ู…Žุฑ] LT', + 'nextDay' => '[ุณุจ•Œู†Ž ู„• ฺฉุงุชฺ˜ู…Žุฑ] LT', + 'nextWeek' => 'dddd [ู„• ฺฉุงุชฺ˜ู…Žุฑ] LT', + 'lastDay' => '[ุฏูˆŽู†Ž ู„• ฺฉุงุชฺ˜ู…Žุฑ] LT', + 'lastWeek' => 'dddd [ู„• ฺฉุงุชฺ˜ู…Žุฑ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ูพ.ู†', 'ุฏ.ู†'], + 'weekend' => [5, 6], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cmn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cmn.php new file mode 100644 index 0000000000..80b1d694fc --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cmn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/cmn_TW.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cmn_TW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cmn_TW.php new file mode 100644 index 0000000000..7e43f9de31 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cmn_TW.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYYๅนดMMๆœˆDD่™Ÿ', + ], + 'months' => ['ไธ€ๆœˆ', 'ไบŒๆœˆ', 'ไธ‰ๆœˆ', 'ๅ››ๆœˆ', 'ไบ”ๆœˆ', 'ๅ…ญๆœˆ', 'ไธƒๆœˆ', 'ๅ…ซๆœˆ', 'ไนๆœˆ', 'ๅๆœˆ', 'ๅไธ€ๆœˆ', 'ๅไบŒๆœˆ'], + 'months_short' => [' 1ๆœˆ', ' 2ๆœˆ', ' 3ๆœˆ', ' 4ๆœˆ', ' 5ๆœˆ', ' 6ๆœˆ', ' 7ๆœˆ', ' 8ๆœˆ', ' 9ๆœˆ', '10ๆœˆ', '11ๆœˆ', '12ๆœˆ'], + 'weekdays' => ['ๆ˜ŸๆœŸๆ—ฅ', 'ๆ˜ŸๆœŸไธ€', 'ๆ˜ŸๆœŸไบŒ', 'ๆ˜ŸๆœŸไธ‰', 'ๆ˜ŸๆœŸๅ››', 'ๆ˜ŸๆœŸไบ”', 'ๆ˜ŸๆœŸๅ…ญ'], + 'weekdays_short' => ['ๆ—ฅ', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ'], + 'weekdays_min' => ['ๆ—ฅ', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ'], + 'meridiem' => ['ไธŠๅˆ', 'ไธ‹ๅˆ'], + + 'year' => ':count ๅนด', + 'y' => ':count ๅนด', + 'a_year' => ':count ๅนด', + + 'month' => ':count ๆœˆ', + 'm' => ':count ๆœˆ', + 'a_month' => ':count ๆœˆ', + + 'week' => ':count ๅ‘จ', + 'w' => ':count ๅ‘จ', + 'a_week' => ':count ๅ‘จ', + + 'day' => ':count ็™ฝๅคฉ', + 'd' => ':count ็™ฝๅคฉ', + 'a_day' => ':count ็™ฝๅคฉ', + + 'hour' => ':count ๅฐๆ—ถ', + 'h' => ':count ๅฐๆ—ถ', + 'a_hour' => ':count ๅฐๆ—ถ', + + 'minute' => ':count ๅˆ†้’Ÿ', + 'min' => ':count ๅˆ†้’Ÿ', + 'a_minute' => ':count ๅˆ†้’Ÿ', + + 'second' => ':count ็ง’', + 's' => ':count ็ง’', + 'a_second' => ':count ็ง’', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/crh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/crh.php new file mode 100644 index 0000000000..a1d7ce6316 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/crh.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/crh_UA.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/crh_UA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/crh_UA.php new file mode 100644 index 0000000000..05139331e7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/crh_UA.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ReลŸat SABIQ tilde.birlik@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'Mayฤฑs', 'ฤฐyun', 'ฤฐyul', 'Avgust', 'Sentรขbr', 'Oktรขbr', 'Noyabr', 'Dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'ฤฐyn', 'ฤฐyl', 'Avg', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['Bazar', 'Bazarertesi', 'Salฤฑ', 'ร‡arลŸembe', 'CumaaqลŸamฤฑ', 'Cuma', 'Cumaertesi'], + 'weekdays_short' => ['Baz', 'Ber', 'Sal', 'ร‡ar', 'Caq', 'Cum', 'Cer'], + 'weekdays_min' => ['Baz', 'Ber', 'Sal', 'ร‡ar', 'Caq', 'Cum', 'Cer'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['รœE', 'รœS'], + + 'year' => ':count yฤฑl', + 'y' => ':count yฤฑl', + 'a_year' => ':count yฤฑl', + + 'month' => ':count ay', + 'm' => ':count ay', + 'a_month' => ':count ay', + + 'week' => ':count afta', + 'w' => ':count afta', + 'a_week' => ':count afta', + + 'day' => ':count kรผn', + 'd' => ':count kรผn', + 'a_day' => ':count kรผn', + + 'hour' => ':count saat', + 'h' => ':count saat', + 'a_hour' => ':count saat', + + 'minute' => ':count daqqa', + 'min' => ':count daqqa', + 'a_minute' => ':count daqqa', + + 'second' => ':count ekinci', + 's' => ':count ekinci', + 'a_second' => ':count ekinci', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cs.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cs.php new file mode 100644 index 0000000000..c01e3ccc8e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cs.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Jakub Tesinsky + * - Martin Suja + * - Nikos Timiopulos + * - Bohuslav Blรญn + * - Tsutomu Kuroda + * - tjku + * - Lukas Svoboda + * - Max Melentiev + * - Juanito Fatas + * - Akira Matsuda + * - Christopher Dell + * - Vรกclav Pรกvek + * - CodeSkills + * - Tlapi + * - newman101 + * - Petr Kadlec + * - tommaskraus + * - Karel Sommer (calvera) + */ +$za = function ($time) { + return 'za '.strtr($time, [ + 'hodina' => 'hodinu', + 'minuta' => 'minutu', + 'sekunda' => 'sekundu', + ]); +}; + +$pred = function ($time) { + $time = strtr($time, [ + 'hodina' => 'hodinou', + 'minuta' => 'minutou', + 'sekunda' => 'sekundou', + ]); + $time = preg_replace('/hodiny?(?!\w)/', 'hodinami', $time); + $time = preg_replace('/minuty?(?!\w)/', 'minutami', $time); + $time = preg_replace('/sekundy?(?!\w)/', 'sekundami', $time); + + return "pล™ed $time"; +}; + +return [ + 'year' => ':count rok|:count roky|:count let', + 'y' => ':count rok|:count roky|:count let', + 'a_year' => 'rok|:count roky|:count let', + 'month' => ':count mฤ›sรญc|:count mฤ›sรญce|:count mฤ›sรญcลฏ', + 'm' => ':count mฤ›s.', + 'a_month' => 'mฤ›sรญc|:count mฤ›sรญce|:count mฤ›sรญcลฏ', + 'week' => ':count tรฝden|:count tรฝdny|:count tรฝdnลฏ', + 'w' => ':count tรฝd.', + 'a_week' => 'tรฝden|:count tรฝdny|:count tรฝdnลฏ', + 'day' => ':count den|:count dny|:count dnรญ', + 'd' => ':count den|:count dny|:count dnรญ', + 'a_day' => 'den|:count dny|:count dnรญ', + 'hour' => ':count hodina|:count hodiny|:count hodin', + 'h' => ':count hod.', + 'a_hour' => 'hodina|:count hodiny|:count hodin', + 'minute' => ':count minuta|:count minuty|:count minut', + 'min' => ':count min.', + 'a_minute' => 'minuta|:count minuty|:count minut', + 'second' => ':count sekunda|:count sekundy|:count sekund', + 's' => ':count sek.', + 'a_second' => 'pรกr sekund|:count sekundy|:count sekund', + + 'month_ago' => ':count mฤ›sรญcem|:count mฤ›sรญci|:count mฤ›sรญci', + 'a_month_ago' => 'mฤ›sรญcem|:count mฤ›sรญci|:count mฤ›sรญci', + 'day_ago' => ':count dnem|:count dny|:count dny', + 'a_day_ago' => 'dnem|:count dny|:count dny', + 'week_ago' => ':count tรฝdnem|:count tรฝdny|:count tรฝdny', + 'a_week_ago' => 'tรฝdnem|:count tรฝdny|:count tรฝdny', + 'year_ago' => ':count rokem|:count roky|:count lety', + 'y_ago' => ':count rok.|:count rok.|:count let.', + 'a_year_ago' => 'rokem|:count roky|:count lety', + + 'month_before' => ':count mฤ›sรญcem|:count mฤ›sรญci|:count mฤ›sรญci', + 'a_month_before' => 'mฤ›sรญcem|:count mฤ›sรญci|:count mฤ›sรญci', + 'day_before' => ':count dnem|:count dny|:count dny', + 'a_day_before' => 'dnem|:count dny|:count dny', + 'week_before' => ':count tรฝdnem|:count tรฝdny|:count tรฝdny', + 'a_week_before' => 'tรฝdnem|:count tรฝdny|:count tรฝdny', + 'year_before' => ':count rokem|:count roky|:count lety', + 'y_before' => ':count rok.|:count rok.|:count let.', + 'a_year_before' => 'rokem|:count roky|:count lety', + + 'ago' => $pred, + 'from_now' => $za, + 'before' => $pred, + 'after' => $za, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'months' => ['ledna', 'รบnora', 'bล™ezna', 'dubna', 'kvฤ›tna', 'ฤervna', 'ฤervence', 'srpna', 'zรกล™รญ', 'ล™รญjna', 'listopadu', 'prosince'], + 'months_standalone' => ['leden', 'รบnor', 'bล™ezen', 'duben', 'kvฤ›ten', 'ฤerven', 'ฤervenec', 'srpen', 'zรกล™รญ', 'ล™รญjen', 'listopad', 'prosinec'], + 'months_short' => ['led', 'รบno', 'bล™e', 'dub', 'kvฤ›', 'ฤvn', 'ฤvc', 'srp', 'zรกล™', 'ล™รญj', 'lis', 'pro'], + 'weekdays' => ['nedฤ›le', 'pondฤ›lรญ', 'รบterรฝ', 'stล™eda', 'ฤtvrtek', 'pรกtek', 'sobota'], + 'weekdays_short' => ['ned', 'pon', 'รบte', 'stล™', 'ฤtv', 'pรกt', 'sob'], + 'weekdays_min' => ['ne', 'po', 'รบt', 'st', 'ฤt', 'pรก', 'so'], + 'list' => [', ', ' a '], + 'diff_now' => 'nynรญ', + 'diff_yesterday' => 'vฤera', + 'diff_tomorrow' => 'zรญtra', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD. MM. YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY HH:mm', + ], + 'meridiem' => ['dopoledne', 'odpoledne'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cs_CZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cs_CZ.php new file mode 100644 index 0000000000..ea2517e851 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cs_CZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/cs.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/csb.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/csb.php new file mode 100644 index 0000000000..a35d2815f7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/csb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/csb_PL.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/csb_PL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/csb_PL.php new file mode 100644 index 0000000000..25e0ca89b6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/csb_PL.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - csb_PL locale Michal Ostrowski bug-glibc-locales@gnu.org + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'months' => ['stรซcznika', 'gromicznika', 'strรซmiannika', 'ล‚ลผรซkwiata', 'maja', 'czerwiล„ca', 'lรซpiล„ca', 'zรฉlnika', 'sรฉwnika', 'rujana', 'lรซstopadnika', 'gรฒdnika'], + 'months_short' => ['stรซ', 'gro', 'str', 'ล‚ลผรซ', 'maj', 'cze', 'lรซp', 'zรฉl', 'sรฉw', 'ruj', 'lรซs', 'gรฒd'], + 'weekdays' => ['niedzela', 'pรฒniedzรดล‚k', 'wtรณrk', 'strzoda', 'czwiรดrtk', 'piฤ…tk', 'sobรฒta'], + 'weekdays_short' => ['nie', 'pรฒn', 'wtรณ', 'str', 'czw', 'piฤ…', 'sob'], + 'weekdays_min' => ['nie', 'pรฒn', 'wtรณ', 'str', 'czw', 'piฤ…', 'sob'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' a tรฉลผ '], + 'two_words_connector' => ' a tรฉลผ ', + 'year' => ':count rok', + 'month' => ':count miesiฤ…c', + 'week' => ':count tidzรฉล„', + 'day' => ':count dzรฉล„', + 'hour' => ':count gรฒdzรซna', + 'minute' => ':count minuta', + 'second' => ':count sekunda', +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cu.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cu.php new file mode 100644 index 0000000000..d6d1312866 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cu.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'months_short' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => ':count ะปัฃั‚ะพ', + 'y' => ':count ะปัฃั‚ะพ', + 'a_year' => ':count ะปัฃั‚ะพ', + + 'month' => ':count ะผัฃัังั†ัŒ', + 'm' => ':count ะผัฃัังั†ัŒ', + 'a_month' => ':count ะผัฃัังั†ัŒ', + + 'week' => ':count ัั”ะดะผะธั†ะฐ', + 'w' => ':count ัั”ะดะผะธั†ะฐ', + 'a_week' => ':count ัั”ะดะผะธั†ะฐ', + + 'day' => ':count ะดัŒะฝัŒ', + 'd' => ':count ะดัŒะฝัŒ', + 'a_day' => ':count ะดัŒะฝัŒ', + + 'hour' => ':count ะณะพะดะธะฝะฐ', + 'h' => ':count ะณะพะดะธะฝะฐ', + 'a_hour' => ':count ะณะพะดะธะฝะฐ', + + 'minute' => ':count ะผะฐะปัŠ', // less reliable + 'min' => ':count ะผะฐะปัŠ', // less reliable + 'a_minute' => ':count ะผะฐะปัŠ', // less reliable + + 'second' => ':count ะฒัŠั‚ะพั€ัŠ', + 's' => ':count ะฒัŠั‚ะพั€ัŠ', + 'a_second' => ':count ะฒัŠั‚ะพั€ัŠ', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cv.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cv.php new file mode 100644 index 0000000000..8aeb73aa8a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cv.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Franรงois B + * - JD Isaacks + */ +return [ + 'year' => ':count าซัƒะป', + 'a_year' => '{1}ะฟำ—ั€ าซัƒะป|:count าซัƒะป', + 'month' => ':count ัƒะนำ‘ั…', + 'a_month' => '{1}ะฟำ—ั€ ัƒะนำ‘ั…|:count ัƒะนำ‘ั…', + 'week' => ':count ัั€ะฝะต', + 'a_week' => '{1}ะฟำ—ั€ ัั€ะฝะต|:count ัั€ะฝะต', + 'day' => ':count ะบัƒะฝ', + 'a_day' => '{1}ะฟำ—ั€ ะบัƒะฝ|:count ะบัƒะฝ', + 'hour' => ':count ัะตั…ะตั‚', + 'a_hour' => '{1}ะฟำ—ั€ ัะตั…ะตั‚|:count ัะตั…ะตั‚', + 'minute' => ':count ะผะธะฝัƒั‚', + 'a_minute' => '{1}ะฟำ—ั€ ะผะธะฝัƒั‚|:count ะผะธะฝัƒั‚', + 'second' => ':count าซะตะบะบัƒะฝั‚', + 'a_second' => '{1}ะฟำ—ั€-ะธะบ าซะตะบะบัƒะฝั‚|:count าซะตะบะบัƒะฝั‚', + 'ago' => ':time ะบะฐัะปะปะฐ', + 'from_now' => function ($time) { + return $time.(preg_match('/ัะตั…ะตั‚$/u', $time) ? 'ั€ะตะฝ' : (preg_match('/าซัƒะป/u', $time) ? 'ั‚ะฐะฝ' : 'ั€ะฐะฝ')); + }, + 'diff_yesterday' => 'ำ–ะฝะตั€', + 'diff_today' => 'ะŸะฐัะฝ', + 'diff_tomorrow' => 'ะซั€ะฐะฝ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'YYYY [าซัƒะปั…ะธ] MMMM [ัƒะนำ‘ั…ำ—ะฝ] D[-ะผำ—ัˆำ—]', + 'LLL' => 'YYYY [าซัƒะปั…ะธ] MMMM [ัƒะนำ‘ั…ำ—ะฝ] D[-ะผำ—ัˆำ—], HH:mm', + 'LLLL' => 'dddd, YYYY [าซัƒะปั…ะธ] MMMM [ัƒะนำ‘ั…ำ—ะฝ] D[-ะผำ—ัˆำ—], HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ะŸะฐัะฝ] LT [ัะตั…ะตั‚ั€ะต]', + 'nextDay' => '[ะซั€ะฐะฝ] LT [ัะตั…ะตั‚ั€ะต]', + 'nextWeek' => '[าชะธั‚ะตั] dddd LT [ัะตั…ะตั‚ั€ะต]', + 'lastDay' => '[ำ–ะฝะตั€] LT [ัะตั…ะตั‚ั€ะต]', + 'lastWeek' => '[ะ˜ั€ั‚ะฝำ—] dddd LT [ัะตั…ะตั‚ั€ะต]', + 'sameElse' => 'L', + ], + 'ordinal' => ':number-ะผำ—ัˆ', + 'months' => ['ะบำ‘ั€ะปะฐั‡', 'ะฝะฐั€ำ‘ั', 'ะฟัƒัˆ', 'ะฐะบะฐ', 'ะผะฐะน', 'าซำ—ั€ั‚ะผะต', 'ัƒั‚ำ‘', 'าซัƒั€ะปะฐ', 'ะฐะฒำ‘ะฝ', 'ัŽะฟะฐ', 'ั‡ำณะบ', 'ั€ะฐัˆั‚ะฐะฒ'], + 'months_short' => ['ะบำ‘ั€', 'ะฝะฐั€', 'ะฟัƒัˆ', 'ะฐะบะฐ', 'ะผะฐะน', 'าซำ—ั€', 'ัƒั‚ำ‘', 'าซัƒั€', 'ะฐะฒะฝ', 'ัŽะฟะฐ', 'ั‡ำณะบ', 'ั€ะฐัˆ'], + 'weekdays' => ['ะฒั‹ั€ัะฐั€ะฝะธะบัƒะฝ', 'ั‚ัƒะฝั‚ะธะบัƒะฝ', 'ั‹ั‚ะปะฐั€ะธะบัƒะฝ', 'ัŽะฝะบัƒะฝ', 'ะบำ—าซะฝะตั€ะฝะธะบัƒะฝ', 'ัั€ะฝะตะบัƒะฝ', 'ัˆำ‘ะผะฐั‚ะบัƒะฝ'], + 'weekdays_short' => ['ะฒั‹ั€', 'ั‚ัƒะฝ', 'ั‹ั‚ะป', 'ัŽะฝ', 'ะบำ—าซ', 'ัั€ะฝ', 'ัˆำ‘ะผ'], + 'weekdays_min' => ['ะฒั€', 'ั‚ะฝ', 'ั‹ั‚', 'ัŽะฝ', 'ะบาซ', 'ัั€', 'ัˆะผ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ั‚ะฐั‚ะฐ '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cv_RU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cv_RU.php new file mode 100644 index 0000000000..197bd8d3f5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cv_RU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/cv.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cy.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cy.php new file mode 100644 index 0000000000..119274f012 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cy.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - JD Isaacks + * - Daniel Monaghan + */ +return [ + 'year' => '{1}blwyddyn|]1,Inf[:count flynedd', + 'y' => ':countbl', + 'month' => '{1}mis|]1,Inf[:count mis', + 'm' => ':countmi', + 'week' => ':count wythnos', + 'w' => ':countw', + 'day' => '{1}diwrnod|]1,Inf[:count diwrnod', + 'd' => ':countd', + 'hour' => '{1}awr|]1,Inf[:count awr', + 'h' => ':counth', + 'minute' => '{1}munud|]1,Inf[:count munud', + 'min' => ':countm', + 'second' => '{1}ychydig eiliadau|]1,Inf[:count eiliad', + 's' => ':counts', + 'ago' => ':time yn รดl', + 'from_now' => 'mewn :time', + 'after' => ':time ar รดl', + 'before' => ':time o\'r blaen', + 'diff_now' => 'nawr', + 'diff_today' => 'Heddiw', + 'diff_today_regexp' => 'Heddiw(?:\\s+am)?', + 'diff_yesterday' => 'ddoe', + 'diff_yesterday_regexp' => 'Ddoe(?:\\s+am)?', + 'diff_tomorrow' => 'yfory', + 'diff_tomorrow_regexp' => 'Yfory(?:\\s+am)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Heddiw am] LT', + 'nextDay' => '[Yfory am] LT', + 'nextWeek' => 'dddd [am] LT', + 'lastDay' => '[Ddoe am] LT', + 'lastWeek' => 'dddd [diwethaf am] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.( + $number > 20 + ? (\in_array((int) $number, [40, 50, 60, 80, 100], true) ? 'fed' : 'ain') + : ([ + '', 'af', 'il', 'ydd', 'ydd', 'ed', 'ed', 'ed', 'fed', 'fed', 'fed', // 1af to 10fed + 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'fed', // 11eg to 20fed + ])[$number] ?? '' + ); + }, + 'months' => ['Ionawr', 'Chwefror', 'Mawrth', 'Ebrill', 'Mai', 'Mehefin', 'Gorffennaf', 'Awst', 'Medi', 'Hydref', 'Tachwedd', 'Rhagfyr'], + 'months_short' => ['Ion', 'Chwe', 'Maw', 'Ebr', 'Mai', 'Meh', 'Gor', 'Aws', 'Med', 'Hyd', 'Tach', 'Rhag'], + 'weekdays' => ['Dydd Sul', 'Dydd Llun', 'Dydd Mawrth', 'Dydd Mercher', 'Dydd Iau', 'Dydd Gwener', 'Dydd Sadwrn'], + 'weekdays_short' => ['Sul', 'Llun', 'Maw', 'Mer', 'Iau', 'Gwe', 'Sad'], + 'weekdays_min' => ['Su', 'Ll', 'Ma', 'Me', 'Ia', 'Gw', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' a '], + 'meridiem' => ['yb', 'yh'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cy_GB.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cy_GB.php new file mode 100644 index 0000000000..2c8148d0fc --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cy_GB.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/cy.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/da.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/da.php new file mode 100644 index 0000000000..322f91d5b7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/da.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rune Mรธnnike + * - Franรงois B + * - codenhagen + * - JD Isaacks + * - Jens Herlevsen + * - Ulrik McArdle (mcardle) + * - Frederik Sauer (FrittenKeeZ) + * - Janus Bahs Jacquet (kokoshneta) + */ +return [ + 'year' => ':count รฅr|:count รฅr', + 'a_year' => 'et รฅr|:count รฅr', + 'y' => ':count รฅr|:count รฅr', + 'month' => ':count mรฅned|:count mรฅneder', + 'a_month' => 'en mรฅned|:count mรฅneder', + 'm' => ':count mdr.', + 'week' => ':count uge|:count uger', + 'a_week' => 'en uge|:count uger', + 'w' => ':count u.', + 'day' => ':count dag|:count dage', + 'a_day' => ':count dag|:count dage', + 'd' => ':count d.', + 'hour' => ':count time|:count timer', + 'a_hour' => 'en time|:count timer', + 'h' => ':count t.', + 'minute' => ':count minut|:count minutter', + 'a_minute' => 'et minut|:count minutter', + 'min' => ':count min.', + 'second' => ':count sekund|:count sekunder', + 'a_second' => 'fรฅ sekunder|:count sekunder', + 's' => ':count s.', + 'ago' => 'for :time siden', + 'from_now' => 'om :time', + 'after' => ':time efter', + 'before' => ':time fรธr', + 'diff_now' => 'nu', + 'diff_today' => 'i dag', + 'diff_today_regexp' => 'i dag(?:\\s+kl.)?', + 'diff_yesterday' => 'i gรฅr', + 'diff_yesterday_regexp' => 'i gรฅr(?:\\s+kl.)?', + 'diff_tomorrow' => 'i morgen', + 'diff_tomorrow_regexp' => 'i morgen(?:\\s+kl.)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd [d.] D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[i dag kl.] LT', + 'nextDay' => '[i morgen kl.] LT', + 'nextWeek' => 'pรฅ dddd [kl.] LT', + 'lastDay' => '[i gรฅr kl.] LT', + 'lastWeek' => '[i] dddd[s kl.] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'marts', 'april', 'maj', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['sรธndag', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lรธrdag'], + 'weekdays_short' => ['sรธn.', 'man.', 'tir.', 'ons.', 'tor.', 'fre.', 'lรธr.'], + 'weekdays_min' => ['sรธ', 'ma', 'ti', 'on', 'to', 'fr', 'lรธ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/da_DK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/da_DK.php new file mode 100644 index 0000000000..392c484150 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/da_DK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/da.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/da_GL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/da_GL.php new file mode 100644 index 0000000000..ea5698b90c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/da_GL.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/da.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + 'LL' => 'D. MMM YYYY', + 'LLL' => 'D. MMMM YYYY HH.mm', + 'LLLL' => 'dddd [den] D. MMMM YYYY HH.mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dav.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dav.php new file mode 100644 index 0000000000..e95ec4bb25 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dav.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Luma lwa K', 'luma lwa p'], + 'weekdays' => ['Ituku ja jumwa', 'Kuramuka jimweri', 'Kuramuka kawi', 'Kuramuka kadadu', 'Kuramuka kana', 'Kuramuka kasanu', 'Kifula nguwo'], + 'weekdays_short' => ['Jum', 'Jim', 'Kaw', 'Kad', 'Kan', 'Kas', 'Ngu'], + 'weekdays_min' => ['Jum', 'Jim', 'Kaw', 'Kad', 'Kan', 'Kas', 'Ngu'], + 'months' => ['Mori ghwa imbiri', 'Mori ghwa kawi', 'Mori ghwa kadadu', 'Mori ghwa kana', 'Mori ghwa kasanu', 'Mori ghwa karandadu', 'Mori ghwa mfungade', 'Mori ghwa wunyanya', 'Mori ghwa ikenda', 'Mori ghwa ikumi', 'Mori ghwa ikumi na imweri', 'Mori ghwa ikumi na iwi'], + 'months_short' => ['Imb', 'Kaw', 'Kad', 'Kan', 'Kas', 'Kar', 'Mfu', 'Wun', 'Ike', 'Iku', 'Imw', 'Iwi'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de.php new file mode 100644 index 0000000000..3b70750e49 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Michael Hohl + * - sheriffmarley + * - dennisoderwald + * - Timo + * - Karag2006 + * - Pete Scopes (pdscopes) + */ +return [ + 'year' => ':count Jahr|:count Jahre', + 'a_year' => 'ein Jahr|:count Jahre', + 'y' => ':count J.', + 'month' => ':count Monat|:count Monate', + 'a_month' => 'ein Monat|:count Monate', + 'm' => ':count Mon.', + 'week' => ':count Woche|:count Wochen', + 'a_week' => 'eine Woche|:count Wochen', + 'w' => ':count Wo.', + 'day' => ':count Tag|:count Tage', + 'a_day' => 'ein Tag|:count Tage', + 'd' => ':count Tg.', + 'hour' => ':count Stunde|:count Stunden', + 'a_hour' => 'eine Stunde|:count Stunden', + 'h' => ':count Std.', + 'minute' => ':count Minute|:count Minuten', + 'a_minute' => 'eine Minute|:count Minuten', + 'min' => ':count Min.', + 'second' => ':count Sekunde|:count Sekunden', + 'a_second' => 'ein paar Sekunden|:count Sekunden', + 's' => ':count Sek.', + 'millisecond' => ':count Millisekunde|:count Millisekunden', + 'a_millisecond' => 'eine Millisekunde|:count Millisekunden', + 'ms' => ':countms', + 'microsecond' => ':count Mikrosekunde|:count Mikrosekunden', + 'a_microsecond' => 'eine Mikrosekunde|:count Mikrosekunden', + 'ยตs' => ':countยตs', + 'ago' => 'vor :time', + 'from_now' => 'in :time', + 'after' => ':time spรคter', + 'before' => ':time zuvor', + + 'year_from_now' => ':count Jahr|:count Jahren', + 'month_from_now' => ':count Monat|:count Monaten', + 'week_from_now' => ':count Woche|:count Wochen', + 'day_from_now' => ':count Tag|:count Tagen', + 'year_ago' => ':count Jahr|:count Jahren', + 'month_ago' => ':count Monat|:count Monaten', + 'week_ago' => ':count Woche|:count Wochen', + 'day_ago' => ':count Tag|:count Tagen', + 'a_year_from_now' => 'ein Jahr|:count Jahren', + 'a_month_from_now' => 'ein Monat|:count Monaten', + 'a_week_from_now' => 'eine Woche|:count Wochen', + 'a_day_from_now' => 'ein Tag|:count Tagen', + 'a_year_ago' => 'ein Jahr|:count Jahren', + 'a_month_ago' => 'ein Monat|:count Monaten', + 'a_week_ago' => 'eine Woche|:count Wochen', + 'a_day_ago' => 'ein Tag|:count Tagen', + + 'diff_now' => 'Gerade eben', + 'diff_today' => 'heute', + 'diff_today_regexp' => 'heute(?:\\s+um)?', + 'diff_yesterday' => 'Gestern', + 'diff_yesterday_regexp' => 'gestern(?:\\s+um)?', + 'diff_tomorrow' => 'Morgen', + 'diff_tomorrow_regexp' => 'morgen(?:\\s+um)?', + 'diff_before_yesterday' => 'Vorgestern', + 'diff_after_tomorrow' => 'รœbermorgen', + + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY HH:mm', + ], + + 'calendar' => [ + 'sameDay' => '[heute um] LT [Uhr]', + 'nextDay' => '[morgen um] LT [Uhr]', + 'nextWeek' => 'dddd [um] LT [Uhr]', + 'lastDay' => '[gestern um] LT [Uhr]', + 'lastWeek' => '[letzten] dddd [um] LT [Uhr]', + 'sameElse' => 'L', + ], + + 'months' => ['Januar', 'Februar', 'Mรคrz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan', 'Feb', 'Mรคr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + 'weekdays' => ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], + 'weekdays_short' => ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'], + 'weekdays_min' => ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' und '], + 'ordinal_words' => [ + 'of' => 'im', + 'first' => 'erster', + 'second' => 'zweiter', + 'third' => 'dritter', + 'fourth' => 'vierten', + 'fifth' => 'fรผnfter', + 'last' => 'letzten', + ], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_AT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_AT.php new file mode 100644 index 0000000000..a2ea4c0866 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_AT.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - sheriffmarley + * - Timo + * - Michael Hohl + * - Namoshek + * - Bernhard Baumrock (BernhardBaumrock) + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'months' => [ + 0 => 'Jรคnner', + ], + 'months_short' => [ + 0 => 'Jรคn', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_BE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_BE.php new file mode 100644 index 0000000000..8ed8dc62d9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_BE.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_CH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_CH.php new file mode 100644 index 0000000000..a869ab4890 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_CH.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - sheriffmarley + * - Timo + * - Michael Hohl + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'weekdays_short' => ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_DE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_DE.php new file mode 100644 index 0000000000..fb1209d218 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_DE.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return require __DIR__.'/de.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_IT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_IT.php new file mode 100644 index 0000000000..604a85682c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_IT.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Matthias Dieter Wallno:fer libc-locales@sourceware.org + */ +return require __DIR__.'/de.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_LI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_LI.php new file mode 100644 index 0000000000..03e606a6d8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_LI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/de.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_LU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_LU.php new file mode 100644 index 0000000000..8ed8dc62d9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de_LU.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dje.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dje.php new file mode 100644 index 0000000000..74b7ac12ae --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dje.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Subbaahi', 'Zaarikay b'], + 'weekdays' => ['Alhadi', 'Atinni', 'Atalaata', 'Alarba', 'Alhamisi', 'Alzuma', 'Asibti'], + 'weekdays_short' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'weekdays_min' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'months' => ['ลฝanwiye', 'Feewiriye', 'Marsi', 'Awiril', 'Me', 'ลฝuweล‹', 'ลฝuyye', 'Ut', 'Sektanbur', 'Oktoobur', 'Noowanbur', 'Deesanbur'], + 'months_short' => ['ลฝan', 'Fee', 'Mar', 'Awi', 'Me', 'ลฝuw', 'ลฝuy', 'Ut', 'Sek', 'Okt', 'Noo', 'Dee'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count hari', // less reliable + 'y' => ':count hari', // less reliable + 'a_year' => ':count hari', // less reliable + + 'week' => ':count alzuma', // less reliable + 'w' => ':count alzuma', // less reliable + 'a_week' => ':count alzuma', // less reliable + + 'second' => ':count atinni', // less reliable + 's' => ':count atinni', // less reliable + 'a_second' => ':count atinni', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/doi.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/doi.php new file mode 100644 index 0000000000..cb679c5877 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/doi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/doi_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/doi_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/doi_IN.php new file mode 100644 index 0000000000..d35972142b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/doi_IN.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เคเคชเฅเคฐเฅˆเคฒ', 'เคฎเฅ‡เคˆ', 'เคœเฅ‚เคจ', 'เคœเฅ‚เคฒเฅˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเค‚เคฌเคฐ', 'เค…เค•เฅเคคเฅ‚เคฌเคฐ', 'เคจเคตเค‚เคฌเคฐ', 'เคฆเคฟเคธเค‚เคฌเคฐ'], + 'months_short' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เคเคชเฅเคฐเฅˆเคฒ', 'เคฎเฅ‡เคˆ', 'เคœเฅ‚เคจ', 'เคœเฅ‚เคฒเฅˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเค‚เคฌเคฐ', 'เค…เค•เฅเคคเฅ‚เคฌเคฐ', 'เคจเคตเค‚เคฌเคฐ', 'เคฆเคฟเคธเค‚เคฌเคฐ'], + 'weekdays' => ['เคเคคเคฌเคพเคฐ', 'เคธเฅ‹เคฎเคฌเคพเคฐ', 'เคฎเค‚เค—เคฒเคฌเคฐ', 'เคฌเฅเคงเคฌเคพเคฐ', 'เคฌเฅ€เคฐเคฌเคพเคฐ', 'เคถเฅเค•เฅเค•เคฐเคฌเคพเคฐ', 'เคถเฅเคจเฅ€เคšเคฐเคฌเคพเคฐ'], + 'weekdays_short' => ['เคเคค', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคง', 'เคฌเฅ€เคฐ', 'เคถเฅเค•เฅเค•เคฐ', 'เคถเฅเคจเฅ€เคšเคฐ'], + 'weekdays_min' => ['เคเคค', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคง', 'เคฌเฅ€เคฐ', 'เคถเฅเค•เฅเค•เคฐ', 'เคถเฅเคจเฅ€เคšเคฐ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เคธเคžเค‚', 'เคธเคฌเฅ‡เคฐ'], + + 'second' => ':count เคธเค™เคพเคฐ', // less reliable + 's' => ':count เคธเค™เคพเคฐ', // less reliable + 'a_second' => ':count เคธเค™เคพเคฐ', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dsb.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dsb.php new file mode 100644 index 0000000000..1d214d56ef --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dsb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/dsb_DE.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dsb_DE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dsb_DE.php new file mode 100644 index 0000000000..1b941870e7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dsb_DE.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Information from Michael Wolf bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'DD. MMMM YYYY', + 'LLL' => 'DD. MMMM, HH:mm [gรณลบ.]', + 'LLLL' => 'dddd, DD. MMMM YYYY, HH:mm [gรณลบ.]', + ], + 'months' => ['januara', 'februara', 'mฤ›rca', 'apryla', 'maja', 'junija', 'julija', 'awgusta', 'septembra', 'oktobra', 'nowembra', 'decembra'], + 'months_short' => ['Jan', 'Feb', 'Mฤ›r', 'Apr', 'Maj', 'Jun', 'Jul', 'Awg', 'Sep', 'Okt', 'Now', 'Dec'], + 'weekdays' => ['Njeลบela', 'Pรณnjeลบele', 'Waล‚tora', 'Srjoda', 'Stwรณrtk', 'Pฤ›tk', 'Sobota'], + 'weekdays_short' => ['Nj', 'Pรณ', 'Wa', 'Sr', 'St', 'Pฤ›', 'So'], + 'weekdays_min' => ['Nj', 'Pรณ', 'Wa', 'Sr', 'St', 'Pฤ›', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count lฤ›to', + 'y' => ':count lฤ›to', + 'a_year' => ':count lฤ›to', + + 'month' => ':count mjasec', + 'm' => ':count mjasec', + 'a_month' => ':count mjasec', + + 'week' => ':count tyลบeล„', + 'w' => ':count tyลบeล„', + 'a_week' => ':count tyลบeล„', + + 'day' => ':count ลบeล„', + 'd' => ':count ลบeล„', + 'a_day' => ':count ลบeล„', + + 'hour' => ':count gรณลบina', + 'h' => ':count gรณลบina', + 'a_hour' => ':count gรณลบina', + + 'minute' => ':count minuta', + 'min' => ':count minuta', + 'a_minute' => ':count minuta', + + 'second' => ':count drugi', + 's' => ':count drugi', + 'a_second' => ':count drugi', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dua.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dua.php new file mode 100644 index 0000000000..55e5c7c356 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dua.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['idiษ“a', 'ebyรกmu'], + 'weekdays' => ['รฉti', 'mษ”ฬsรบ', 'kwasรบ', 'mukษ”ฬsรบ', 'ล‹gisรบ', 'ษ—รณnษ›sรบ', 'esaษ“asรบ'], + 'weekdays_short' => ['รฉt', 'mษ”ฬs', 'kwa', 'muk', 'ล‹gi', 'ษ—รณn', 'esa'], + 'weekdays_min' => ['รฉt', 'mษ”ฬs', 'kwa', 'muk', 'ล‹gi', 'ษ—รณn', 'esa'], + 'months' => ['dimษ”ฬdi', 'ล‹gษ”ndษ›', 'sษ”ล‹ษ›', 'diษ“รกษ“รก', 'emiasele', 'esษ”pษ›sษ”pษ›', 'madiษ“ษ›ฬdรญษ“ษ›ฬ', 'diล‹gindi', 'nyษ›tษ›ki', 'mayรฉsษ›ฬ', 'tinรญnรญ', 'elรกล‹gษ›ฬ'], + 'months_short' => ['di', 'ล‹gษ”n', 'sษ”ล‹', 'diษ“', 'emi', 'esษ”', 'mad', 'diล‹', 'nyษ›t', 'may', 'tin', 'elรก'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count ma mbu', // less reliable + 'y' => ':count ma mbu', // less reliable + 'a_year' => ':count ma mbu', // less reliable + + 'month' => ':count myoฬฑdi', // less reliable + 'm' => ':count myoฬฑdi', // less reliable + 'a_month' => ':count myoฬฑdi', // less reliable + + 'week' => ':count woki', // less reliable + 'w' => ':count woki', // less reliable + 'a_week' => ':count woki', // less reliable + + 'day' => ':count buล„a', // less reliable + 'd' => ':count buล„a', // less reliable + 'a_day' => ':count buล„a', // less reliable + + 'hour' => ':count ma awa', // less reliable + 'h' => ':count ma awa', // less reliable + 'a_hour' => ':count ma awa', // less reliable + + 'minute' => ':count minuti', // less reliable + 'min' => ':count minuti', // less reliable + 'a_minute' => ':count minuti', // less reliable + + 'second' => ':count maba', // less reliable + 's' => ':count maba', // less reliable + 'a_second' => ':count maba', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dv.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dv.php new file mode 100644 index 0000000000..4b8d7e1a49 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dv.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$months = [ + '–ฌ‚ช‡ฆƒฉ', + 'Šฌ„ฐƒช‡ฆƒฉ', + '‰งƒจ—ช', + '‡ญ•ฐƒฉช', + '‰ญ', + '–ซ‚ฐ', + '–ชฆ‡จ', + '‡ฏŽฆฐ“ช', + 'ฌ•ฐ“ฌ‰ฐ„ฆƒช', + '‡ฎ†ฐ“ฏ„ฆƒช', + '‚ฎˆฌ‰ฐ„ฆƒช', + '‘จฌ‰ฐ„ฆƒช', +]; + +$weekdays = [ + '‡ง‹จ‡ฐŒฆ', + '€ฏ‰ฆ', + '‡ฆ‚ฐŽงƒฆ', + '„ช‹ฆ', + '„ชƒงฐŠฆŒจ', + '€ช†ชƒช', + '€ฎ‚จ€จƒช', +]; + +/* + * Authors: + * - Josh Soref + * - Jawish Hameed + */ +return [ + 'year' => ':count '.'‡ฆ€ฆƒช', + 'a_year' => '{1}'.'‡ฆ€ฆƒฌ‡ฐ'.'|:count '.'‡ฆ€ฆƒช', + 'month' => ':count '.'‰ฆฐ', + 'a_month' => '{1}'.'‰ฆ€ฌ‡ฐ'.'|:count '.'‰ฆฐ', + 'week' => ':count '.'€ฆŠฐŒง', + 'a_week' => '{1}'.'จ†ช‚ฐŒช†ฎ…ฌ‡ฐ'.'|:count '.'€ฆŠฐŒง', + 'day' => ':count '.'‹ชˆฆฐ', + 'a_day' => '{1}'.'‹ชˆฆ€ฌ‡ฐ'.'|:count '.'‹ชˆฆฐ', + 'hour' => ':count '.'Žฆ‘จ‡จƒช', + 'a_hour' => '{1}'.'Žฆ‘จ‡จƒฌ‡ฐ'.'|:count '.'Žฆ‘จ‡จƒช', + 'minute' => ':count '.'‰จ‚จ“ช', + 'a_minute' => '{1}'.'‰จ‚จ“ฌ‡ฐ'.'|:count '.'‰จ‚จ“ช', + 'second' => ':count '.'จ†ช‚ฐŒช', + 'a_second' => '{1}'.'จ†ช‚ฐŒช†ฎ…ฌ‡ฐ'.'|:count '.'จ†ช‚ฐŒช', + 'ago' => '†ชƒจ‚ฐ :time', + 'from_now' => 'ŒฌƒญŽฆ‡จ :time', + 'after' => ':time Šฆ€ช‚ฐ', + 'before' => ':time †ชƒจ', + 'diff_yesterday' => '‡จ‡ฐ”ฌ', + 'diff_today' => '‰จ‡ฆ‹ช', + 'diff_tomorrow' => '‰ง‹ฆ‰ง', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[‰จ‡ฆ‹ช] LT', + 'nextDay' => '[‰ง‹ฆ‰ง] LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '[‡จ‡ฐ”ฌ] LT', + 'lastWeek' => '[Šง‡จŒชˆจ] dddd LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['‰†', '‰Š'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => ['‡ง‹จ', '€ฏ‰ฆ', '‡ฆ‚ฐ', '„ช‹ฆ', '„ชƒง', '€ช†ช', '€ฎ‚จ'], + 'list' => [', ', ' ‡ฆ‹จ '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php new file mode 100644 index 0000000000..2668d5b027 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ahmed Ali + */ + +$months = [ + '–ฌ‚ช‡ฆƒฉ', + 'Šฌ„ฐƒช‡ฆƒฉ', + '‰งƒจ—ช', + '‡ญ•ฐƒฉช', + '‰ญ', + '–ซ‚ฐ', + '–ชฆ‡จ', + '‡ฏŽฆฐ“ช', + 'ฌ•ฐ“ฌ‰ฐ„ฆƒช', + '‡ฎ†ฐ“ฏ„ฆƒช', + '‚ฎˆฌ‰ฐ„ฆƒช', + '‘จฌ‰ฐ„ฆƒช', +]; + +$weekdays = [ + '‡ง‹จ‡ฐŒฆ', + '€ฏ‰ฆ', + '‡ฆ‚ฐŽงƒฆ', + '„ช‹ฆ', + '„ชƒงฐŠฆŒจ', + '€ช†ชƒช', + '€ฎ‚จ€จƒช', +]; + +return [ + 'year' => '{0}‡ฆ€ฆƒฌ‡ฐ|[1,Inf]:count ‡ฆ€ฆƒช', + 'y' => '{0}‡ฆ€ฆƒฌ‡ฐ|[1,Inf]:count ‡ฆ€ฆƒช', + 'month' => '{0}‰ฆ‡ฐฆƒฌ‡ฐ|[1,Inf]:count ‰ฆฐ', + 'm' => '{0}‰ฆ‡ฐฆƒฌ‡ฐ|[1,Inf]:count ‰ฆฐ', + 'week' => '{0}€ฆŠฐŒง‡ฌ‡ฐ|[1,Inf]:count €ฆŠฐŒง', + 'w' => '{0}€ฆŠฐŒง‡ฌ‡ฐ|[1,Inf]:count €ฆŠฐŒง', + 'day' => '{0}‹ชˆฆฐ|[1,Inf]:count ‹ชˆฆฐ', + 'd' => '{0}‹ชˆฆฐ|[1,Inf]:count ‹ชˆฆฐ', + 'hour' => '{0}Žฆ‘จ‡จƒฌ‡ฐ|[1,Inf]:count Žฆ‘จ', + 'h' => '{0}Žฆ‘จ‡จƒฌ‡ฐ|[1,Inf]:count Žฆ‘จ', + 'minute' => '{0}‰จ‚ฌ“ฌ‡ฐ|[1,Inf]:count ‰จ‚ฌ“ฐ', + 'min' => '{0}‰จ‚ฌ“ฌ‡ฐ|[1,Inf]:count ‰จ‚ฌ“ฐ', + 'second' => '{0}จ†ช‚ฐŒฌ‡ฐ|[1,Inf]:count จ†ช‚ฐŒช', + 's' => '{0}จ†ช‚ฐŒฌ‡ฐ|[1,Inf]:count จ†ช‚ฐŒช', + 'ago' => ':time †ชƒจ‚ฐ', + 'from_now' => ':time Šฆ€ช‚ฐ', + 'after' => ':time Šฆ€ช‚ฐ', + 'before' => ':time †ชƒจ', + 'diff_yesterday' => '‡จ‡ฐ”ฌ', + 'diff_today' => '‰จ‡ฆ‹ช', + 'diff_tomorrow' => '‰ง‹ฆ‰ง', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[‰จ‡ฆ‹ช] LT', + 'nextDay' => '[‰ง‹ฆ‰ง] LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '[‡จ‡ฐ”ฌ] LT', + 'lastWeek' => '[Šง‡จŒชˆจ] dddd LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['‰†', '‰Š'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => ['‡ง‹จ', '€ฏ‰ฆ', '‡ฆ‚ฐ', '„ช‹ฆ', '„ชƒง', '€ช†ช', '€ฎ‚จ'], + 'list' => [', ', ' ‡ฆ‹จ '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dyo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dyo.php new file mode 100644 index 0000000000..33082e672e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dyo.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Dimas', 'Teneล‹', 'Talata', 'Alarbay', 'Aramisay', 'Arjuma', 'Sibiti'], + 'weekdays_short' => ['Dim', 'Ten', 'Tal', 'Ala', 'Ara', 'Arj', 'Sib'], + 'weekdays_min' => ['Dim', 'Ten', 'Tal', 'Ala', 'Ara', 'Arj', 'Sib'], + 'months' => ['Sanvie', 'Fรฉbirie', 'Mars', 'Aburil', 'Mee', 'Sueล‹', 'Sรบuyee', 'Ut', 'Settembar', 'Oktobar', 'Novembar', 'Disambar'], + 'months_short' => ['Sa', 'Fe', 'Ma', 'Ab', 'Me', 'Su', 'Sรบ', 'Ut', 'Se', 'Ok', 'No', 'De'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dz.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dz.php new file mode 100644 index 0000000000..cc17e69e88 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dz.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/dz_BT.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dz_BT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dz_BT.php new file mode 100644 index 0000000000..bfbcaf468a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dz_BT.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sherubtse College bug-glibc@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'เฝ”เฝฆเพฑเฝฒเผ‹เฝฃเฝผYYเฝŸเฝฃMMเฝšเฝบเฝฆDD', + ], + 'months' => ['เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เฝ„เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‚เฝ‰เฝฒเฝฆเผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‚เฝฆเฝดเฝ˜เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝžเฝฒเผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝฃเพ”เผ‹เฝ•เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เพฒเฝดเฝ‚เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ‘เฝดเฝ“เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝขเพ’เพฑเฝ‘เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ‘เฝ‚เฝดเผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ‚เฝ…เฝฒเฝ‚เผ‹เฝ”เผ‹', 'เฝŸเพณเผ‹เฝ–เผ‹เฝ–เฝ…เฝดเผ‹เฝ‚เฝ‰เฝฒเฝฆเผ‹เฝ”เผ‹'], + 'months_short' => ['เฝŸเพณเผ‹เผก', 'เฝŸเพณเผ‹เผข', 'เฝŸเพณเผ‹เผฃ', 'เฝŸเพณเผ‹เผค', 'เฝŸเพณเผ‹เผฅ', 'เฝŸเพณเผ‹เผฆ', 'เฝŸเพณเผ‹เผง', 'เฝŸเพณเผ‹เผจ', 'เฝŸเพณเผ‹เผฉ', 'เฝŸเพณเผ‹เผกเผ ', 'เฝŸเพณเผ‹เผกเผก', 'เฝŸเพณเผ‹เผกเผข'], + 'weekdays' => ['เฝ‚เฝŸเฝ เผ‹เฝŸเพณเผ‹เฝ–เผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝ˜เฝฒเฝ‚เผ‹เฝ‘เฝ˜เฝขเผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝฃเพทเฝ‚เผ‹เฝ•เผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝ”เฝดเฝขเผ‹เฝ–เฝดเผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝ”เผ‹เฝฆเฝ„เฝฆเผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝฆเพคเฝบเฝ“เผ‹เฝ•เผ‹', 'เฝ‚เฝŸเฝ เผ‹เฝ‰เฝฒเผ‹เฝ˜เผ‹'], + 'weekdays_short' => ['เฝŸเพณเผ‹', 'เฝ˜เฝฒเฝขเผ‹', 'เฝฃเพทเฝ‚เผ‹', 'เฝ”เฝดเฝขเผ‹', 'เฝฆเฝ„เฝฆเผ‹', 'เฝฆเพคเฝบเฝ“เผ‹', 'เฝ‰เฝฒเผ‹'], + 'weekdays_min' => ['เฝŸเพณเผ‹', 'เฝ˜เฝฒเฝขเผ‹', 'เฝฃเพทเฝ‚เผ‹', 'เฝ”เฝดเฝขเผ‹', 'เฝฆเฝ„เฝฆเผ‹', 'เฝฆเพคเฝบเฝ“เผ‹', 'เฝ‰เฝฒเผ‹'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เฝ„เฝฆเผ‹เฝ†', 'เฝ•เพฑเฝฒเผ‹เฝ†'], + + 'year' => ':count เฝ†เฝขเฝ”', // less reliable + 'y' => ':count เฝ†เฝขเฝ”', // less reliable + 'a_year' => ':count เฝ†เฝขเฝ”', // less reliable + + 'month' => ':count เฝŸเพณเผ‹เฝ–', // less reliable + 'm' => ':count เฝŸเพณเผ‹เฝ–', // less reliable + 'a_month' => ':count เฝŸเพณเผ‹เฝ–', // less reliable + + 'day' => ':count เฝ‰เฝฒ', // less reliable + 'd' => ':count เฝ‰เฝฒ', // less reliable + 'a_day' => ':count เฝ‰เฝฒ', // less reliable + + 'second' => ':count เฝ†', // less reliable + 's' => ':count เฝ†', // less reliable + 'a_second' => ':count เฝ†', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ebu.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ebu.php new file mode 100644 index 0000000000..f60bc6f2f0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ebu.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['KI', 'UT'], + 'weekdays' => ['Kiumia', 'Njumatatu', 'Njumaine', 'Njumatano', 'Aramithi', 'Njumaa', 'NJumamothii'], + 'weekdays_short' => ['Kma', 'Tat', 'Ine', 'Tan', 'Arm', 'Maa', 'NMM'], + 'weekdays_min' => ['Kma', 'Tat', 'Ine', 'Tan', 'Arm', 'Maa', 'NMM'], + 'months' => ['Mweri wa mbere', 'Mweri wa kaฤฉri', 'Mweri wa kathatลฉ', 'Mweri wa kana', 'Mweri wa gatano', 'Mweri wa gatantatลฉ', 'Mweri wa mลฉgwanja', 'Mweri wa kanana', 'Mweri wa kenda', 'Mweri wa ikลฉmi', 'Mweri wa ikลฉmi na ลฉmwe', 'Mweri wa ikลฉmi na Kaฤฉrฤฉ'], + 'months_short' => ['Mbe', 'Kai', 'Kat', 'Kan', 'Gat', 'Gan', 'Mug', 'Knn', 'Ken', 'Iku', 'Imw', 'Igi'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ee.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ee.php new file mode 100644 index 0000000000..f96c5c9dc8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ee.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ล‹', 'ษฃ'], + 'weekdays' => ['kษ”siษ–a', 'dzoษ–a', 'blaษ–a', 'kuษ–a', 'yawoษ–a', 'fiษ–a', 'memleษ–a'], + 'weekdays_short' => ['kษ”s', 'dzo', 'bla', 'kuษ–', 'yaw', 'fiษ–', 'mem'], + 'weekdays_min' => ['kษ”s', 'dzo', 'bla', 'kuษ–', 'yaw', 'fiษ–', 'mem'], + 'months' => ['dzove', 'dzodze', 'tedoxe', 'afษ”fฤฉe', 'dama', 'masa', 'siamlษ”m', 'deasiamime', 'anyษ”nyษ”', 'kele', 'adeษ›mekpษ”xe', 'dzome'], + 'months_short' => ['dzv', 'dzd', 'ted', 'afษ”', 'dam', 'mas', 'sia', 'dea', 'any', 'kel', 'ade', 'dzm'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'a [ga] h:mm', + 'LTS' => 'a [ga] h:mm:ss', + 'L' => 'M/D/YYYY', + 'LL' => 'MMM D [lia], YYYY', + 'LLL' => 'a [ga] h:mm MMMM D [lia] YYYY', + 'LLLL' => 'a [ga] h:mm dddd, MMMM D [lia] YYYY', + ], + + 'year' => 'ฦ’e :count', + 'y' => 'ฦ’e :count', + 'a_year' => 'ฦ’e :count', + + 'month' => 'ษฃleti :count', + 'm' => 'ษฃleti :count', + 'a_month' => 'ษฃleti :count', + + 'week' => 'kwasiษ–a :count', + 'w' => 'kwasiษ–a :count', + 'a_week' => 'kwasiษ–a :count', + + 'day' => 'ล‹keke :count', + 'd' => 'ล‹keke :count', + 'a_day' => 'ล‹keke :count', + + 'hour' => 'gaฦ’oฦ’o :count', + 'h' => 'gaฦ’oฦ’o :count', + 'a_hour' => 'gaฦ’oฦ’o :count', + + 'minute' => 'miniti :count', // less reliable + 'min' => 'miniti :count', // less reliable + 'a_minute' => 'miniti :count', // less reliable + + 'second' => 'sษ›kษ›nd :count', // less reliable + 's' => 'sษ›kษ›nd :count', // less reliable + 'a_second' => 'sษ›kษ›nd :count', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ee_TG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ee_TG.php new file mode 100644 index 0000000000..7a8b36c9b8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ee_TG.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ee.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'LLL' => 'HH:mm MMMM D [lia] YYYY', + 'LLLL' => 'HH:mm dddd, MMMM D [lia] YYYY', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/el.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/el.php new file mode 100644 index 0000000000..7c40f9c12e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/el.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alessandro Di Felice + * - Franรงois B + * - Tim Fish + * - Gabriel Monteagudo + * - JD Isaacks + * - yiannisdesp + * - Ilias Kasmeridis (iliaskasm) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count ฯ‡ฯฯŒฮฝฮฟฯ‚|:count ฯ‡ฯฯŒฮฝฮนฮฑ', + 'a_year' => 'ฮญฮฝฮฑฯ‚ ฯ‡ฯฯŒฮฝฮฟฯ‚|:count ฯ‡ฯฯŒฮฝฮนฮฑ', + 'y' => ':count ฯ‡ฯ.', + 'month' => ':count ฮผฮฎฮฝฮฑฯ‚|:count ฮผฮฎฮฝฮตฯ‚', + 'a_month' => 'ฮญฮฝฮฑฯ‚ ฮผฮฎฮฝฮฑฯ‚|:count ฮผฮฎฮฝฮตฯ‚', + 'm' => ':count ฮผฮฎฮฝ.', + 'week' => ':count ฮตฮฒฮดฮฟฮผฮฌฮดฮฑ|:count ฮตฮฒฮดฮฟฮผฮฌฮดฮตฯ‚', + 'a_week' => 'ฮผฮนฮฑ ฮตฮฒฮดฮฟฮผฮฌฮดฮฑ|:count ฮตฮฒฮดฮฟฮผฮฌฮดฮตฯ‚', + 'w' => ':count ฮตฮฒฮด.', + 'day' => ':count ฮผฮญฯฮฑ|:count ฮผฮญฯฮตฯ‚', + 'a_day' => 'ฮผฮฏฮฑ ฮผฮญฯฮฑ|:count ฮผฮญฯฮตฯ‚', + 'd' => ':count ฮผฮญฯ.', + 'hour' => ':count ฯŽฯฮฑ|:count ฯŽฯฮตฯ‚', + 'a_hour' => 'ฮผฮฏฮฑ ฯŽฯฮฑ|:count ฯŽฯฮตฯ‚', + 'h' => ':count ฯŽฯฮฑ|:count ฯŽฯฮตฯ‚', + 'minute' => ':count ฮปฮตฯ€ฯ„ฯŒ|:count ฮปฮตฯ€ฯ„ฮฌ', + 'a_minute' => 'ฮญฮฝฮฑ ฮปฮตฯ€ฯ„ฯŒ|:count ฮปฮตฯ€ฯ„ฮฌ', + 'min' => ':count ฮปฮตฯ€.', + 'second' => ':count ฮดฮตฯ…ฯ„ฮตฯฯŒฮปฮตฯ€ฯ„ฮฟ|:count ฮดฮตฯ…ฯ„ฮตฯฯŒฮปฮตฯ€ฯ„ฮฑ', + 'a_second' => 'ฮปฮฏฮณฮฑ ฮดฮตฯ…ฯ„ฮตฯฯŒฮปฮตฯ€ฯ„ฮฑ|:count ฮดฮตฯ…ฯ„ฮตฯฯŒฮปฮตฯ€ฯ„ฮฑ', + 's' => ':count ฮดฮตฯ….', + 'ago' => 'ฯ€ฯฮนฮฝ :time', + 'from_now' => 'ฯƒฮต :time', + 'after' => ':time ฮผฮตฯ„ฮฌ', + 'before' => ':time ฯ€ฯฮนฮฝ', + 'diff_now' => 'ฯ„ฯŽฯฮฑ', + 'diff_today' => 'ฮฃฮฎฮผฮตฯฮฑ', + 'diff_today_regexp' => 'ฮฃฮฎฮผฮตฯฮฑ(?:\\s+{})?', + 'diff_yesterday' => 'ฯ‡ฮธฮตฯ‚', + 'diff_yesterday_regexp' => 'ฮงฮธฮตฯ‚(?:\\s+{})?', + 'diff_tomorrow' => 'ฮฑฯฯฮนฮฟ', + 'diff_tomorrow_regexp' => 'ฮ‘ฯฯฮนฮฟ(?:\\s+{})?', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'calendar' => [ + 'sameDay' => '[ฮฃฮฎฮผฮตฯฮฑ {}] LT', + 'nextDay' => '[ฮ‘ฯฯฮนฮฟ {}] LT', + 'nextWeek' => 'dddd [{}] LT', + 'lastDay' => '[ฮงฮธฮตฯ‚ {}] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 6: + return '[ฯ„ฮฟ ฯ€ฯฮฟฮทฮณฮฟฯฮผฮตฮฝฮฟ] dddd [{}] LT'; + default: + return '[ฯ„ฮทฮฝ ฯ€ฯฮฟฮทฮณฮฟฯฮผฮตฮฝฮท] dddd [{}] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberฮท', + 'meridiem' => ['ฮ ฮœ', 'ฮœฮœ', 'ฯ€ฮผ', 'ฮผฮผ'], + 'months' => ['ฮ™ฮฑฮฝฮฟฯ…ฮฑฯฮฏฮฟฯ…', 'ฮฆฮตฮฒฯฮฟฯ…ฮฑฯฮฏฮฟฯ…', 'ฮœฮฑฯฯ„ฮฏฮฟฯ…', 'ฮ‘ฯ€ฯฮนฮปฮฏฮฟฯ…', 'ฮœฮฑฮฮฟฯ…', 'ฮ™ฮฟฯ…ฮฝฮฏฮฟฯ…', 'ฮ™ฮฟฯ…ฮปฮฏฮฟฯ…', 'ฮ‘ฯ…ฮณฮฟฯฯƒฯ„ฮฟฯ…', 'ฮฃฮตฯ€ฯ„ฮตฮผฮฒฯฮฏฮฟฯ…', 'ฮŸฮบฯ„ฯ‰ฮฒฯฮฏฮฟฯ…', 'ฮฮฟฮตฮผฮฒฯฮฏฮฟฯ…', 'ฮ”ฮตฮบฮตฮผฮฒฯฮฏฮฟฯ…'], + 'months_standalone' => ['ฮ™ฮฑฮฝฮฟฯ…ฮฌฯฮนฮฟฯ‚', 'ฮฆฮตฮฒฯฮฟฯ…ฮฌฯฮนฮฟฯ‚', 'ฮœฮฌฯฯ„ฮนฮฟฯ‚', 'ฮ‘ฯ€ฯฮฏฮปฮนฮฟฯ‚', 'ฮœฮฌฮนฮฟฯ‚', 'ฮ™ฮฟฯฮฝฮนฮฟฯ‚', 'ฮ™ฮฟฯฮปฮนฮฟฯ‚', 'ฮ‘ฯฮณฮฟฯ…ฯƒฯ„ฮฟฯ‚', 'ฮฃฮตฯ€ฯ„ฮญฮผฮฒฯฮนฮฟฯ‚', 'ฮŸฮบฯ„ฯŽฮฒฯฮนฮฟฯ‚', 'ฮฮฟฮญฮผฮฒฯฮนฮฟฯ‚', 'ฮ”ฮตฮบฮญฮผฮฒฯฮนฮฟฯ‚'], + 'months_regexp' => '/(D[oD]?[\s,]+MMMM|L{2,4}|l{2,4})/', + 'months_short' => ['ฮ™ฮฑฮฝ', 'ฮฆฮตฮฒ', 'ฮœฮฑฯ', 'ฮ‘ฯ€ฯ', 'ฮœฮฑฯŠ', 'ฮ™ฮฟฯ…ฮฝ', 'ฮ™ฮฟฯ…ฮป', 'ฮ‘ฯ…ฮณ', 'ฮฃฮตฯ€', 'ฮŸฮบฯ„', 'ฮฮฟฮต', 'ฮ”ฮตฮบ'], + 'weekdays' => ['ฮšฯ…ฯฮนฮฑฮบฮฎ', 'ฮ”ฮตฯ…ฯ„ฮญฯฮฑ', 'ฮคฯฮฏฯ„ฮท', 'ฮคฮตฯ„ฮฌฯฯ„ฮท', 'ฮ ฮญฮผฯ€ฯ„ฮท', 'ฮ ฮฑฯฮฑฯƒฮบฮตฯ…ฮฎ', 'ฮฃฮฌฮฒฮฒฮฑฯ„ฮฟ'], + 'weekdays_short' => ['ฮšฯ…ฯ', 'ฮ”ฮตฯ…', 'ฮคฯฮน', 'ฮคฮตฯ„', 'ฮ ฮตฮผ', 'ฮ ฮฑฯ', 'ฮฃฮฑฮฒ'], + 'weekdays_min' => ['ฮšฯ…', 'ฮ”ฮต', 'ฮคฯ', 'ฮคฮต', 'ฮ ฮต', 'ฮ ฮฑ', 'ฮฃฮฑ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ฮบฮฑฮน '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/el_CY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/el_CY.php new file mode 100644 index 0000000000..8a693c15d1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/el_CY.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Greek Debian Translation Team bug-glibc@gnu.org + */ +return array_replace_recursive(require __DIR__.'/el.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/el_GR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/el_GR.php new file mode 100644 index 0000000000..df196af940 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/el_GR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/el.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en.php new file mode 100644 index 0000000000..f81f617e31 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Milos Sakovic + * - Paul + * - Pete Scopes (pdscopes) + */ +return [ + /* + * {1}, {0} and ]1,Inf[ are not needed as it's the default for English pluralization. + * But as some languages are using en.php as a fallback, it's better to specify it + * explicitly so those languages also fallback to English pluralization when a unit + * is missing. + */ + 'year' => '{1}:count year|{0}:count years|]1,Inf[:count years', + 'a_year' => '{1}a year|{0}:count years|]1,Inf[:count years', + 'y' => '{1}:countyr|{0}:countyrs|]1,Inf[:countyrs', + 'month' => '{1}:count month|{0}:count months|]1,Inf[:count months', + 'a_month' => '{1}a month|{0}:count months|]1,Inf[:count months', + 'm' => '{1}:countmo|{0}:countmos|]1,Inf[:countmos', + 'week' => '{1}:count week|{0}:count weeks|]1,Inf[:count weeks', + 'a_week' => '{1}a week|{0}:count weeks|]1,Inf[:count weeks', + 'w' => ':countw', + 'day' => '{1}:count day|{0}:count days|]1,Inf[:count days', + 'a_day' => '{1}a day|{0}:count days|]1,Inf[:count days', + 'd' => ':countd', + 'hour' => '{1}:count hour|{0}:count hours|]1,Inf[:count hours', + 'a_hour' => '{1}an hour|{0}:count hours|]1,Inf[:count hours', + 'h' => ':counth', + 'minute' => '{1}:count minute|{0}:count minutes|]1,Inf[:count minutes', + 'a_minute' => '{1}a minute|{0}:count minutes|]1,Inf[:count minutes', + 'min' => ':countm', + 'second' => '{1}:count second|{0}:count seconds|]1,Inf[:count seconds', + 'a_second' => '{1}a few seconds|{0}:count seconds|]1,Inf[:count seconds', + 's' => ':counts', + 'millisecond' => '{1}:count millisecond|{0}:count milliseconds|]1,Inf[:count milliseconds', + 'a_millisecond' => '{1}a millisecond|{0}:count milliseconds|]1,Inf[:count milliseconds', + 'ms' => ':countms', + 'microsecond' => '{1}:count microsecond|{0}:count microseconds|]1,Inf[:count microseconds', + 'a_microsecond' => '{1}a microsecond|{0}:count microseconds|]1,Inf[:count microseconds', + 'ยตs' => ':countยตs', + 'ago' => ':time ago', + 'from_now' => ':time from now', + 'after' => ':time after', + 'before' => ':time before', + 'diff_now' => 'just now', + 'diff_today' => 'today', + 'diff_yesterday' => 'yesterday', + 'diff_tomorrow' => 'tomorrow', + 'diff_before_yesterday' => 'before yesterday', + 'diff_after_tomorrow' => 'after tomorrow', + 'period_recurrences' => '{1}once|{0}:count times|]1,Inf[:count times', + 'period_interval' => 'every :interval', + 'period_start_date' => 'from :date', + 'period_end_date' => 'to :date', + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + 'weekdays_short' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'weekdays_min' => ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + + return $number.( + ((int) ($number % 100 / 10) === 1) ? 'th' : ( + ($lastDigit === 1) ? 'st' : ( + ($lastDigit === 2) ? 'nd' : ( + ($lastDigit === 3) ? 'rd' : 'th' + ) + ) + ) + ); + }, + 'list' => [', ', ' and '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_001.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_001.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_001.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_150.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_150.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_150.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AG.php new file mode 100644 index 0000000000..2c1c64f061 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AG.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AI.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AS.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AS.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AT.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AT.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AU.php new file mode 100644 index 0000000000..f16bd4f549 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AU.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - Franรงois B + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BB.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BB.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BB.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BE.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BI.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BM.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BS.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BS.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BW.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BZ.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CA.php new file mode 100644 index 0000000000..e656086882 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CA.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Zhan Tong Zhang + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'YYYY-MM-DD', + 'LL' => 'MMMM D, YYYY', + 'LLL' => 'MMMM D, YYYY h:mm A', + 'LLLL' => 'dddd, MMMM D, YYYY h:mm A', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CC.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CH.php new file mode 100644 index 0000000000..10d9cd8f1a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CH.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CK.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CK.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CM.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CX.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CX.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CX.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CY.php new file mode 100644 index 0000000000..a44c3508e0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CY.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - NehaGautam + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DE.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DG.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DK.php new file mode 100644 index 0000000000..9e8a8c68bb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DK.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Danish Standards Association bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DM.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ER.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ER.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ER.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FI.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FJ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FJ.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FJ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FK.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FK.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FM.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GB.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GB.php new file mode 100644 index 0000000000..67d9fd649c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GB.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GD.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GD.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GD.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GG.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GH.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GH.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GI.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GM.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GU.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GY.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GY.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_HK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_HK.php new file mode 100644 index 0000000000..34aae989b4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_HK.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IE.php new file mode 100644 index 0000000000..c8d3c2fc95 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IE.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Martin McWhorter + * - Franรงois B + * - Chris Cartlidge + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IL.php new file mode 100644 index 0000000000..e607924e71 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IL.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Yoav Amit + * - Franรงois B + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IM.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IN.php new file mode 100644 index 0000000000..00414e9ad8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YY', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IO.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IO.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ISO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ISO.php new file mode 100644 index 0000000000..11457b0c82 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ISO.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'dddd, YYYY MMMM DD HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_JE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_JE.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_JE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_JM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_JM.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_JM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KE.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KI.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KN.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KN.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KY.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KY.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LC.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LR.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LR.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LS.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LS.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MG.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MH.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MO.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MP.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MP.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MP.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MS.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MS.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MT.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MU.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MW.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MW.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MY.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MY.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NA.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NA.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NF.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NF.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NF.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NG.php new file mode 100644 index 0000000000..67bceaad7a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NG.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NL.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NL.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NR.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NR.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NU.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NZ.php new file mode 100644 index 0000000000..6a206a0d0c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NZ.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Mayank Badola + * - Luke McGregor + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PG.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PH.php new file mode 100644 index 0000000000..34aae989b4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PH.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PK.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PN.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PN.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PR.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PW.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PW.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_RW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_RW.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_RW.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SB.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SB.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SB.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SC.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SD.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SD.php new file mode 100644 index 0000000000..c4e2557e9f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SD.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SE.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SG.php new file mode 100644 index 0000000000..5ee95241d3 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SG.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SH.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SH.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SI.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SL.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SL.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SS.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SS.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SX.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SX.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SX.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SZ.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TC.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TK.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TK.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TO.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TO.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TT.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TV.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TV.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TV.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TZ.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_UG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_UG.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_UG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_UM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_UM.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_UM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_US.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_US.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_US.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_US_Posix.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_US_Posix.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_US_Posix.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VC.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VG.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VI.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VU.php new file mode 100644 index 0000000000..e2dd81db54 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_WS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_WS.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_WS.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZA.php new file mode 100644 index 0000000000..48ea9471cf --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZA.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YY', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZM.php new file mode 100644 index 0000000000..d8a8cb5987 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZM.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ANLoc Martin Benjamin locales@africanlocalization.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZW.php new file mode 100644 index 0000000000..f086dc6326 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/eo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/eo.php new file mode 100644 index 0000000000..7c2efba2db --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/eo.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Franรงois B + * - Mia Nordentoft + * - JD Isaacks + */ +return [ + 'year' => ':count jaro|:count jaroj', + 'a_year' => 'jaro|:count jaroj', + 'y' => ':count j.', + 'month' => ':count monato|:count monatoj', + 'a_month' => 'monato|:count monatoj', + 'm' => ':count mo.', + 'week' => ':count semajno|:count semajnoj', + 'a_week' => 'semajno|:count semajnoj', + 'w' => ':count sem.', + 'day' => ':count tago|:count tagoj', + 'a_day' => 'tago|:count tagoj', + 'd' => ':count t.', + 'hour' => ':count horo|:count horoj', + 'a_hour' => 'horo|:count horoj', + 'h' => ':count h.', + 'minute' => ':count minuto|:count minutoj', + 'a_minute' => 'minuto|:count minutoj', + 'min' => ':count min.', + 'second' => ':count sekundo|:count sekundoj', + 'a_second' => 'sekundoj|:count sekundoj', + 's' => ':count sek.', + 'ago' => 'antaลญ :time', + 'from_now' => 'post :time', + 'after' => ':time poste', + 'before' => ':time antaลญe', + 'diff_yesterday' => 'Hieraลญ', + 'diff_yesterday_regexp' => 'Hieraลญ(?:\\s+je)?', + 'diff_today' => 'Hodiaลญ', + 'diff_today_regexp' => 'Hodiaลญ(?:\\s+je)?', + 'diff_tomorrow' => 'Morgaลญ', + 'diff_tomorrow_regexp' => 'Morgaลญ(?:\\s+je)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'D[-a de] MMMM, YYYY', + 'LLL' => 'D[-a de] MMMM, YYYY HH:mm', + 'LLLL' => 'dddd, [la] D[-a de] MMMM, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hodiaลญ je] LT', + 'nextDay' => '[Morgaลญ je] LT', + 'nextWeek' => 'dddd [je] LT', + 'lastDay' => '[Hieraลญ je] LT', + 'lastWeek' => '[pasinta] dddd [je] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numbera', + 'meridiem' => ['a.t.m.', 'p.t.m.'], + 'months' => ['januaro', 'februaro', 'marto', 'aprilo', 'majo', 'junio', 'julio', 'aลญgusto', 'septembro', 'oktobro', 'novembro', 'decembro'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aลญg', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['dimanฤ‰o', 'lundo', 'mardo', 'merkredo', 'ฤตaลญdo', 'vendredo', 'sabato'], + 'weekdays_short' => ['dim', 'lun', 'mard', 'merk', 'ฤตaลญ', 'ven', 'sab'], + 'weekdays_min' => ['di', 'lu', 'ma', 'me', 'ฤตa', 've', 'sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' kaj '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es.php new file mode 100644 index 0000000000..1c4fcfd0ba --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - kostas + * - Franรงois B + * - Tim Fish + * - Claire Coloma + * - Steven Heinrich + * - JD Isaacks + * - Raphael Amorim + * - Jorge Y. Castillo + * - Vรญctor Dรญaz + * - Diego + * - Sebastian Thierer + * - quinterocesar + * - Daniel Commesse Liรฉvanos (danielcommesse) + * - Pete Scopes (pdscopes) + * - gam04 + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count aรฑo|:count aรฑos', + 'a_year' => 'un aรฑo|:count aรฑos', + 'y' => ':count aรฑo|:count aรฑos', + 'month' => ':count mes|:count meses', + 'a_month' => 'un mes|:count meses', + 'm' => ':count mes|:count meses', + 'week' => ':count semana|:count semanas', + 'a_week' => 'una semana|:count semanas', + 'w' => ':countsem', + 'day' => ':count dรญa|:count dรญas', + 'a_day' => 'un dรญa|:count dรญas', + 'd' => ':countd', + 'hour' => ':count hora|:count horas', + 'a_hour' => 'una hora|:count horas', + 'h' => ':counth', + 'minute' => ':count minuto|:count minutos', + 'a_minute' => 'un minuto|:count minutos', + 'min' => ':countm', + 'second' => ':count segundo|:count segundos', + 'a_second' => 'unos segundos|:count segundos', + 's' => ':counts', + 'millisecond' => ':count milisegundo|:count milisegundos', + 'a_millisecond' => 'un milisegundo|:count milisegundos', + 'ms' => ':countms', + 'microsecond' => ':count microsegundo|:count microsegundos', + 'a_microsecond' => 'un microsegundo|:count microsegundos', + 'ยตs' => ':countยตs', + 'ago' => 'hace :time', + 'from_now' => 'en :time', + 'after' => ':time despuรฉs', + 'before' => ':time antes', + 'diff_now' => 'ahora mismo', + 'diff_today' => 'hoy', + 'diff_today_regexp' => 'hoy(?:\\s+a)?(?:\\s+las)?', + 'diff_yesterday' => 'ayer', + 'diff_yesterday_regexp' => 'ayer(?:\\s+a)?(?:\\s+las)?', + 'diff_tomorrow' => 'maรฑana', + 'diff_tomorrow_regexp' => 'maรฑana(?:\\s+a)?(?:\\s+las)?', + 'diff_before_yesterday' => 'anteayer', + 'diff_after_tomorrow' => 'pasado maรฑana', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [de] MMMM [de] YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY H:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $current) { + return '[hoy a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'nextDay' => function (CarbonInterface $current) { + return '[maรฑana a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'nextWeek' => function (CarbonInterface $current) { + return 'dddd [a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'lastDay' => function (CarbonInterface $current) { + return '[ayer a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'lastWeek' => function (CarbonInterface $current) { + return '[el] dddd [pasado a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'sameElse' => 'L', + ], + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + 'mmm_suffix' => '.', + 'ordinal' => ':numberยบ', + 'weekdays' => ['domingo', 'lunes', 'martes', 'miรฉrcoles', 'jueves', 'viernes', 'sรกbado'], + 'weekdays_short' => ['dom.', 'lun.', 'mar.', 'miรฉ.', 'jue.', 'vie.', 'sรกb.'], + 'weekdays_min' => ['do', 'lu', 'ma', 'mi', 'ju', 'vi', 'sรก'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' y '], + 'meridiem' => ['a. m.', 'p. m.'], + 'ordinal_words' => [ + 'of' => 'de', + 'first' => 'primer', + 'second' => 'segundo', + 'third' => 'tercer', + 'fourth' => 'cuarto', + 'fifth' => 'quinto', + 'last' => 'รบltimo', + ], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_419.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_419.php new file mode 100644 index 0000000000..a74806e847 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_419.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_AR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_AR.php new file mode 100644 index 0000000000..a74806e847 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_AR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_BO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_BO.php new file mode 100644 index 0000000000..c9b8432efd --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_BO.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_BR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_BR.php new file mode 100644 index 0000000000..378d0547ca --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_BR.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_BZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_BZ.php new file mode 100644 index 0000000000..378d0547ca --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_BZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_CL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_CL.php new file mode 100644 index 0000000000..a74806e847 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_CL.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_CO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_CO.php new file mode 100644 index 0000000000..a74806e847 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_CO.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_CR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_CR.php new file mode 100644 index 0000000000..553fc09f4c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_CR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_CU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_CU.php new file mode 100644 index 0000000000..f02e1a66b1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_CU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_DO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_DO.php new file mode 100644 index 0000000000..0f855bac4d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_DO.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - kostas + * - Franรงois B + * - Tim Fish + * - Chiel Robben + * - Claire Coloma + * - Steven Heinrich + * - JD Isaacks + * - Raphael Amorim + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'diff_before_yesterday' => 'anteayer', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'LLL' => 'D [de] MMMM [de] YYYY h:mm A', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY h:mm A', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_EA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_EA.php new file mode 100644 index 0000000000..f02e1a66b1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_EA.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_EC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_EC.php new file mode 100644 index 0000000000..a74806e847 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_EC.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_ES.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_ES.php new file mode 100644 index 0000000000..19217c2719 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_ES.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return require __DIR__.'/es.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_GQ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_GQ.php new file mode 100644 index 0000000000..f02e1a66b1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_GQ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_GT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_GT.php new file mode 100644 index 0000000000..a74806e847 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_GT.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_HN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_HN.php new file mode 100644 index 0000000000..a74806e847 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_HN.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_IC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_IC.php new file mode 100644 index 0000000000..f02e1a66b1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_IC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_MX.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_MX.php new file mode 100644 index 0000000000..61e14cfa42 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_MX.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'diff_before_yesterday' => 'antier', + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_NI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_NI.php new file mode 100644 index 0000000000..6b964c14d8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_NI.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PA.php new file mode 100644 index 0000000000..a74806e847 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PA.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PE.php new file mode 100644 index 0000000000..a74806e847 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PE.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PH.php new file mode 100644 index 0000000000..deae06a191 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PH.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY h:mm a', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY h:mm a', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PR.php new file mode 100644 index 0000000000..6b964c14d8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PY.php new file mode 100644 index 0000000000..a74806e847 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PY.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_SV.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_SV.php new file mode 100644 index 0000000000..00db08e266 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_SV.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_US.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_US.php new file mode 100644 index 0000000000..f333136f4e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_US.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - Josh Soref + * - Jรธrn ร˜lmheim + * - Craig Patik + * - bustta + * - Franรงois B + * - Tim Fish + * - Claire Coloma + * - Steven Heinrich + * - JD Isaacks + * - Raphael Amorim + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'diff_before_yesterday' => 'anteayer', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'MM/DD/YYYY', + 'LL' => 'MMMM [de] D [de] YYYY', + 'LLL' => 'MMMM [de] D [de] YYYY h:mm A', + 'LLLL' => 'dddd, MMMM [de] D [de] YYYY h:mm A', + ], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_UY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_UY.php new file mode 100644 index 0000000000..39baff8b37 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_UY.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'setiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'set', 'oct', 'nov', 'dic'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_VE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_VE.php new file mode 100644 index 0000000000..a74806e847 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_VE.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/et.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/et.php new file mode 100644 index 0000000000..f49c8806fd --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/et.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Andres Ivanov + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - RM87 + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Esko Lehtme + * - Mart Karu + * - Nicolรกs Hock Isaza + * - Kevin Valdek + * - Zahhar Kirillov + * - Joรฃo Magalhรฃes + * - Ingmar + * - Illimar Tambek + * - Mihkel + */ +return [ + 'year' => ':count aasta|:count aastat', + 'y' => ':count a', + 'month' => ':count kuu|:count kuud', + 'm' => ':count k', + 'week' => ':count nรคdal|:count nรคdalat', + 'w' => ':count nรคd', + 'day' => ':count pรคev|:count pรคeva', + 'd' => ':count p', + 'hour' => ':count tund|:count tundi', + 'h' => ':count t', + 'minute' => ':count minut|:count minutit', + 'min' => ':count min', + 'second' => ':count sekund|:count sekundit', + 's' => ':count s', + 'ago' => ':time tagasi', + 'from_now' => ':time pรคrast', + 'after' => ':time pรคrast', + 'before' => ':time enne', + 'year_from_now' => ':count aasta', + 'month_from_now' => ':count kuu', + 'week_from_now' => ':count nรคdala', + 'day_from_now' => ':count pรคeva', + 'hour_from_now' => ':count tunni', + 'minute_from_now' => ':count minuti', + 'second_from_now' => ':count sekundi', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'diff_now' => 'nรผรผd', + 'diff_today' => 'tรคna', + 'diff_yesterday' => 'eile', + 'diff_tomorrow' => 'homme', + 'diff_before_yesterday' => 'รผleeile', + 'diff_after_tomorrow' => 'รผlehomme', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[tรคna] LT', + 'nextDay' => '[homme] LT', + 'lastDay' => '[eile] LT', + 'nextWeek' => 'dddd LT', + 'lastWeek' => '[eelmine] dddd LT', + 'sameElse' => 'L', + ], + 'months' => ['jaanuar', 'veebruar', 'mรคrts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'], + 'months_short' => ['jaan', 'veebr', 'mรคrts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'], + 'weekdays' => ['pรผhapรคev', 'esmaspรคev', 'teisipรคev', 'kolmapรคev', 'neljapรคev', 'reede', 'laupรคev'], + 'weekdays_short' => ['P', 'E', 'T', 'K', 'N', 'R', 'L'], + 'weekdays_min' => ['P', 'E', 'T', 'K', 'N', 'R', 'L'], + 'list' => [', ', ' ja '], + 'meridiem' => ['enne lรตunat', 'pรคrast lรตunat'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/et_EE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/et_EE.php new file mode 100644 index 0000000000..0f112b34d1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/et_EE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/et.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/eu.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/eu.php new file mode 100644 index 0000000000..a543f1a67a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/eu.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Franรงois B + * - JD Isaacks + */ +return [ + 'year' => 'urte bat|:count urte', + 'y' => 'Urte 1|:count urte', + 'month' => 'hilabete bat|:count hilabete', + 'm' => 'Hile 1|:count hile', + 'week' => 'Aste 1|:count aste', + 'w' => 'Aste 1|:count aste', + 'day' => 'egun bat|:count egun', + 'd' => 'Egun 1|:count egun', + 'hour' => 'ordu bat|:count ordu', + 'h' => 'Ordu 1|:count ordu', + 'minute' => 'minutu bat|:count minutu', + 'min' => 'Minutu 1|:count minutu', + 'second' => 'segundo batzuk|:count segundo', + 's' => 'Segundu 1|:count segundu', + 'ago' => 'duela :time', + 'from_now' => ':time barru', + 'after' => ':time geroago', + 'before' => ':time lehenago', + 'diff_now' => 'orain', + 'diff_today' => 'gaur', + 'diff_yesterday' => 'atzo', + 'diff_tomorrow' => 'bihar', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY[ko] MMMM[ren] D[a]', + 'LLL' => 'YYYY[ko] MMMM[ren] D[a] HH:mm', + 'LLLL' => 'dddd, YYYY[ko] MMMM[ren] D[a] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[gaur] LT[etan]', + 'nextDay' => '[bihar] LT[etan]', + 'nextWeek' => 'dddd LT[etan]', + 'lastDay' => '[atzo] LT[etan]', + 'lastWeek' => '[aurreko] dddd LT[etan]', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['urtarrila', 'otsaila', 'martxoa', 'apirila', 'maiatza', 'ekaina', 'uztaila', 'abuztua', 'iraila', 'urria', 'azaroa', 'abendua'], + 'months_short' => ['urt.', 'ots.', 'mar.', 'api.', 'mai.', 'eka.', 'uzt.', 'abu.', 'ira.', 'urr.', 'aza.', 'abe.'], + 'weekdays' => ['igandea', 'astelehena', 'asteartea', 'asteazkena', 'osteguna', 'ostirala', 'larunbata'], + 'weekdays_short' => ['ig.', 'al.', 'ar.', 'az.', 'og.', 'ol.', 'lr.'], + 'weekdays_min' => ['ig', 'al', 'ar', 'az', 'og', 'ol', 'lr'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' eta '], + 'meridiem' => ['g', 'a'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/eu_ES.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/eu_ES.php new file mode 100644 index 0000000000..0d1e82a9fc --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/eu_ES.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/eu.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ewo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ewo.php new file mode 100644 index 0000000000..7808ab5043 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ewo.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['kรญkรญrรญg', 'ngษ™gรณgษ™le'], + 'weekdays' => ['sษ”ฬndษ”', 'mษ”ฬndi', 'sษ”ฬndษ” mษ™lรบ mษ™ฬbษ›ฬŒ', 'sษ”ฬndษ” mษ™lรบ mษ™ฬlษ›ฬ', 'sษ”ฬndษ” mษ™lรบ mษ™ฬnyi', 'fรบladรฉ', 'sรฉradรฉ'], + 'weekdays_short' => ['sษ”ฬn', 'mษ”ฬn', 'smb', 'sml', 'smn', 'fรบl', 'sรฉr'], + 'weekdays_min' => ['sษ”ฬn', 'mษ”ฬn', 'smb', 'sml', 'smn', 'fรบl', 'sรฉr'], + 'months' => ['ngษ”n osรบ', 'ngษ”n bษ›ฬŒ', 'ngษ”n lรกla', 'ngษ”n nyina', 'ngษ”n tรกna', 'ngษ”n samษ™na', 'ngษ”n zamgbรกla', 'ngษ”n mwom', 'ngษ”n ebulรบ', 'ngษ”n awรณm', 'ngษ”n awรณm ai dziรก', 'ngษ”n awรณm ai bษ›ฬŒ'], + 'months_short' => ['ngo', 'ngb', 'ngl', 'ngn', 'ngt', 'ngs', 'ngz', 'ngm', 'nge', 'nga', 'ngad', 'ngab'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + // Too unreliable + /* + 'year' => ':count mbu', // less reliable + 'y' => ':count mbu', // less reliable + 'a_year' => ':count mbu', // less reliable + + 'month' => ':count ngรฒn', // less reliable + 'm' => ':count ngรฒn', // less reliable + 'a_month' => ':count ngรฒn', // less reliable + + 'week' => ':count mรซsรซ', // less reliable + 'w' => ':count mรซsรซ', // less reliable + 'a_week' => ':count mรซsรซ', // less reliable + + 'day' => ':count mรซsรซ', // less reliable + 'd' => ':count mรซsรซ', // less reliable + 'a_day' => ':count mรซsรซ', // less reliable + + 'hour' => ':count awola', // less reliable + 'h' => ':count awola', // less reliable + 'a_hour' => ':count awola', // less reliable + + 'minute' => ':count awola', // less reliable + 'min' => ':count awola', // less reliable + 'a_minute' => ':count awola', // less reliable + */ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fa.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fa.php new file mode 100644 index 0000000000..72e0308545 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fa.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Franรงois B + * - Nasser Ghiasi + * - JD Isaacks + * - Hossein Jabbari + * - nimamo + * - hafezdivandari + * - Hassan Pezeshk (hpez) + */ +return [ + 'year' => ':count ุณุงู„', + 'a_year' => 'Œฺฉ ุณุงู„'.'|:count '.'ุณุงู„', + 'y' => ':count ุณุงู„', + 'month' => ':count ู…ุงู‡', + 'a_month' => 'Œฺฉ ู…ุงู‡'.'|:count '.'ู…ุงู‡', + 'm' => ':count ู…ุงู‡', + 'week' => ':count ู‡ูุชู‡', + 'a_week' => 'Œฺฉ ู‡ูุชู‡'.'|:count '.'ู‡ูุชู‡', + 'w' => ':count ู‡ูุชู‡', + 'day' => ':count ุฑูˆุฒ', + 'a_day' => 'Œฺฉ ุฑูˆุฒ'.'|:count '.'ุฑูˆุฒ', + 'd' => ':count ุฑูˆุฒ', + 'hour' => ':count ุณุงุนุช', + 'a_hour' => 'Œฺฉ ุณุงุนุช'.'|:count '.'ุณุงุนุช', + 'h' => ':count ุณุงุนุช', + 'minute' => ':count ุฏู‚Œู‚ู‡', + 'a_minute' => 'Œฺฉ ุฏู‚Œู‚ู‡'.'|:count '.'ุฏู‚Œู‚ู‡', + 'min' => ':count ุฏู‚Œู‚ู‡', + 'second' => ':count ุซุงู†Œู‡', + 's' => ':count ุซุงู†Œู‡', + 'ago' => ':time ูพŒุด', + 'from_now' => ':time ุฏŒฺฏุฑ', + 'after' => ':time ูพุณ ุงุฒ', + 'before' => ':time ูพŒุด ุงุฒ', + 'diff_now' => 'ุงฺฉู†ูˆู†', + 'diff_today' => 'ุงู…ุฑูˆุฒ', + 'diff_today_regexp' => 'ุงู…ุฑูˆุฒ(?:\\s+ุณุงุนุช)?', + 'diff_yesterday' => 'ุฏŒุฑูˆุฒ', + 'diff_yesterday_regexp' => 'ุฏŒุฑูˆุฒ(?:\\s+ุณุงุนุช)?', + 'diff_tomorrow' => 'ูุฑุฏุง', + 'diff_tomorrow_regexp' => 'ูุฑุฏุง(?:\\s+ุณุงุนุช)?', + 'formats' => [ + 'LT' => 'OH:Om', + 'LTS' => 'OH:Om:Os', + 'L' => 'OD/OM/OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY OH:Om', + 'LLLL' => 'dddd, OD MMMM OY OH:Om', + ], + 'calendar' => [ + 'sameDay' => '[ุงู…ุฑูˆุฒ ุณุงุนุช] LT', + 'nextDay' => '[ูุฑุฏุง ุณุงุนุช] LT', + 'nextWeek' => 'dddd [ุณุงุนุช] LT', + 'lastDay' => '[ุฏŒุฑูˆุฒ ุณุงุนุช] LT', + 'lastWeek' => 'dddd [ูพŒุด] [ุณุงุนุช] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':timeู…', + 'meridiem' => ['ู‚ุจู„ ุงุฒ ุธู‡ุฑ', 'ุจุนุฏ ุงุฒ ุธู‡ุฑ'], + 'months' => ['ฺ˜ุงู†ูˆŒู‡', 'ููˆุฑŒู‡', 'ู…ุงุฑุณ', 'ุขูˆุฑŒู„', 'ู…ู‡', 'ฺ˜ูˆุฆู†', 'ฺ˜ูˆุฆŒู‡', 'ุงูˆุช', 'ุณูพุชุงู…ุจุฑ', 'ุงฺฉุชุจุฑ', 'ู†ูˆุงู…ุจุฑ', 'ุฏุณุงู…ุจุฑ'], + 'months_short' => ['ฺ˜ุงู†ูˆŒู‡', 'ููˆุฑŒู‡', 'ู…ุงุฑุณ', 'ุขูˆุฑŒู„', 'ู…ู‡', 'ฺ˜ูˆุฆู†', 'ฺ˜ูˆุฆŒู‡', 'ุงูˆุช', 'ุณูพุชุงู…ุจุฑ', 'ุงฺฉุชุจุฑ', 'ู†ูˆุงู…ุจุฑ', 'ุฏุณุงู…ุจุฑ'], + 'weekdays' => ['Œฺฉุดู†ุจู‡', 'ุฏูˆุดู†ุจู‡', 'ุณู‡โ€Œุดู†ุจู‡', 'ฺ†ู‡ุงุฑุดู†ุจู‡', 'ูพู†ุฌุดู†ุจู‡', 'ุฌู…ุนู‡', 'ุดู†ุจู‡'], + 'weekdays_short' => ['Œฺฉุดู†ุจู‡', 'ุฏูˆุดู†ุจู‡', 'ุณู‡โ€Œุดู†ุจู‡', 'ฺ†ู‡ุงุฑุดู†ุจู‡', 'ูพู†ุฌุดู†ุจู‡', 'ุฌู…ุนู‡', 'ุดู†ุจู‡'], + 'weekdays_min' => ['Œ', 'ุฏ', 'ุณ', 'ฺ†', 'ูพ', 'ุฌ', 'ุด'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'list' => ['ุŒ ', ' ูˆ '], + 'alt_numbers' => ['ฐฐ', 'ฐฑ', 'ฐฒ', 'ฐณ', 'ฐด', 'ฐต', 'ฐถ', 'ฐท', 'ฐธ', 'ฐน', 'ฑฐ', 'ฑฑ', 'ฑฒ', 'ฑณ', 'ฑด', 'ฑต', 'ฑถ', 'ฑท', 'ฑธ', 'ฑน', 'ฒฐ', 'ฒฑ', 'ฒฒ', 'ฒณ', 'ฒด', 'ฒต', 'ฒถ', 'ฒท', 'ฒธ', 'ฒน', 'ณฐ', 'ณฑ', 'ณฒ', 'ณณ', 'ณด', 'ณต', 'ณถ', 'ณท', 'ณธ', 'ณน', 'ดฐ', 'ดฑ', 'ดฒ', 'ดณ', 'ดด', 'ดต', 'ดถ', 'ดท', 'ดธ', 'ดน', 'ตฐ', 'ตฑ', 'ตฒ', 'ตณ', 'ตด', 'ตต', 'ตถ', 'ตท', 'ตธ', 'ตน', 'ถฐ', 'ถฑ', 'ถฒ', 'ถณ', 'ถด', 'ถต', 'ถถ', 'ถท', 'ถธ', 'ถน', 'ทฐ', 'ทฑ', 'ทฒ', 'ทณ', 'ทด', 'ทต', 'ทถ', 'ทท', 'ทธ', 'ทน', 'ธฐ', 'ธฑ', 'ธฒ', 'ธณ', 'ธด', 'ธต', 'ธถ', 'ธท', 'ธธ', 'ธน', 'นฐ', 'นฑ', 'นฒ', 'นณ', 'นด', 'นต', 'นถ', 'นท', 'นธ', 'นน'], + 'months_short_standalone' => ['ฺ˜ุงู†ูˆŒู‡', 'ููˆุฑŒู‡', 'ู…ุงุฑุณ', 'ุขูˆุฑŒู„', 'ู…ู‡', 'ฺ˜ูˆุฆู†', 'ฺ˜ูˆุฆŒู‡', 'ุงูˆุช', 'ุณูพุชุงู…ุจุฑ', 'ุงฺฉุชุจุฑ', 'ู†ูˆุงู…ุจุฑ', 'ุฏุณุงู…ุจุฑ'], + 'weekend' => [5, 5], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fa_AF.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fa_AF.php new file mode 100644 index 0000000000..6947100467 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fa_AF.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fa.php', [ + 'meridiem' => ['ู‚', 'ุจ'], + 'weekend' => [4, 5], + 'formats' => [ + 'L' => 'OY/OM/OD', + 'LL' => 'OD MMM OY', + 'LLL' => 'OD MMMM OYุŒโ€ H:mm', + 'LLLL' => 'dddd OD MMMM OYุŒโ€ H:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fa_IR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fa_IR.php new file mode 100644 index 0000000000..08d01825fb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fa_IR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fa.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff.php new file mode 100644 index 0000000000..9525c95ad2 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'months' => ['siilo', 'colte', 'mbooy', 'seeษ—to', 'duujal', 'korse', 'morso', 'juko', 'siilto', 'yarkomaa', 'jolal', 'bowte'], + 'months_short' => ['sii', 'col', 'mbo', 'see', 'duu', 'kor', 'mor', 'juk', 'slt', 'yar', 'jol', 'bow'], + 'weekdays' => ['dewo', 'aaษ“nde', 'mawbaare', 'njeslaare', 'naasaande', 'mawnde', 'hoore-biir'], + 'weekdays_short' => ['dew', 'aaษ“', 'maw', 'nje', 'naa', 'mwd', 'hbi'], + 'weekdays_min' => ['dew', 'aaษ“', 'maw', 'nje', 'naa', 'mwd', 'hbi'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['subaka', 'kikiiษ—e'], + + 'year' => ':count baret', // less reliable + 'y' => ':count baret', // less reliable + 'a_year' => ':count baret', // less reliable + + 'month' => ':count lewru', // less reliable + 'm' => ':count lewru', // less reliable + 'a_month' => ':count lewru', // less reliable + + 'week' => ':count naange', // less reliable + 'w' => ':count naange', // less reliable + 'a_week' => ':count naange', // less reliable + + 'day' => ':count dian', // less reliable + 'd' => ':count dian', // less reliable + 'a_day' => ':count dian', // less reliable + + 'hour' => ':count montor', // less reliable + 'h' => ':count montor', // less reliable + 'a_hour' => ':count montor', // less reliable + + 'minute' => ':count tokossuoum', // less reliable + 'min' => ':count tokossuoum', // less reliable + 'a_minute' => ':count tokossuoum', // less reliable + + 'second' => ':count tenen', // less reliable + 's' => ':count tenen', // less reliable + 'a_second' => ':count tenen', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff_CM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff_CM.php new file mode 100644 index 0000000000..b797ac09bb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff_CM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ff.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff_GN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff_GN.php new file mode 100644 index 0000000000..b797ac09bb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff_GN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ff.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff_MR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff_MR.php new file mode 100644 index 0000000000..2f4c29f626 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff_MR.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ff.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff_SN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff_SN.php new file mode 100644 index 0000000000..1e4c8b6cb6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ff_SN.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pular-Fulfulde.org Ibrahima Sarr admin@pulaar-fulfulde.org + */ +return require __DIR__.'/ff.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fi.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fi.php new file mode 100644 index 0000000000..edf2d6d359 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fi.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Janne Warรฉn + * - digitalfrost + * - Tsutomu Kuroda + * - Roope Salmi + * - tjku + * - Max Melentiev + * - Sami Haahtinen + * - Teemu Leisti + * - Artem Ignatyev + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Robert Bjarnason + * - Aaron Patterson + * - Nicolรกs Hock Isaza + * - Tom Hughes + * - Sven Fuchs + * - Petri Kivikangas + * - Nizar Jouini + * - Marko Seppae + * - Tomi Mynttinen (Pikseli) + * - Petteri (powergrip) + */ +return [ + 'year' => ':count vuosi|:count vuotta', + 'y' => ':count v', + 'month' => ':count kuukausi|:count kuukautta', + 'm' => ':count kk', + 'week' => ':count viikko|:count viikkoa', + 'w' => ':count vk', + 'day' => ':count pรคivรค|:count pรคivรครค', + 'd' => ':count pv', + 'hour' => ':count tunti|:count tuntia', + 'h' => ':count t', + 'minute' => ':count minuutti|:count minuuttia', + 'min' => ':count min', + 'second' => ':count sekunti|:count sekuntia', + 'a_second' => 'muutama sekunti|:count sekuntia', + 's' => ':count s', + 'ago' => ':time sitten', + 'from_now' => ':time pรครคstรค', + 'year_from_now' => ':count vuoden', + 'month_from_now' => ':count kuukauden', + 'week_from_now' => ':count viikon', + 'day_from_now' => ':count pรคivรคn', + 'hour_from_now' => ':count tunnin', + 'minute_from_now' => ':count minuutin', + 'second_from_now' => ':count sekunnin', + 'after' => ':time sen jรคlkeen', + 'before' => ':time ennen', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ja '], + 'diff_now' => 'nyt', + 'diff_yesterday' => 'eilen', + 'diff_tomorrow' => 'huomenna', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm:ss', + 'L' => 'D.M.YYYY', + 'LL' => 'dddd D. MMMM[ta] YYYY', + 'll' => 'ddd D. MMM YYYY', + 'LLL' => 'D.MM. HH.mm', + 'LLLL' => 'D. MMMM[ta] YYYY HH.mm', + 'llll' => 'D. MMM YY HH.mm', + ], + 'weekdays' => ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'], + 'weekdays_short' => ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'], + 'weekdays_min' => ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'], + 'months' => ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesรคkuu', 'heinรคkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'], + 'months_short' => ['tammi', 'helmi', 'maalis', 'huhti', 'touko', 'kesรค', 'heinรค', 'elo', 'syys', 'loka', 'marras', 'joulu'], + 'meridiem' => ['aamupรคivรค', 'iltapรคivรค'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fi_FI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fi_FI.php new file mode 100644 index 0000000000..920f1caa74 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fi_FI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fi.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fil.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fil.php new file mode 100644 index 0000000000..61114e3a6d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fil.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/fil_PH.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fil_PH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fil_PH.php new file mode 100644 index 0000000000..bcf1580782 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fil_PH.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rene Torres Rene Torres, Pablo Saratxaga rgtorre@rocketmail.com, pablo@mandrakesoft.com + * - Jaycee Mariano (alohajaycee) + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YY', + ], + 'months' => ['Enero', 'Pebrero', 'Marso', 'Abril', 'Mayo', 'Hunyo', 'Hulyo', 'Agosto', 'Setyembre', 'Oktubre', 'Nobyembre', 'Disyembre'], + 'months_short' => ['Ene', 'Peb', 'Mar', 'Abr', 'May', 'Hun', 'Hul', 'Ago', 'Set', 'Okt', 'Nob', 'Dis'], + 'weekdays' => ['Linggo', 'Lunes', 'Martes', 'Miyerkoles', 'Huwebes', 'Biyernes', 'Sabado'], + 'weekdays_short' => ['Lin', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'], + 'weekdays_min' => ['Lin', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['N.U.', 'N.H.'], + + 'before' => ':time bago', + 'after' => ':time pagkatapos', + + 'year' => ':count taon', + 'y' => ':count taon', + 'a_year' => ':count taon', + + 'month' => ':count buwan', + 'm' => ':count buwan', + 'a_month' => ':count buwan', + + 'week' => ':count linggo', + 'w' => ':count linggo', + 'a_week' => ':count linggo', + + 'day' => ':count araw', + 'd' => ':count araw', + 'a_day' => ':count araw', + + 'hour' => ':count oras', + 'h' => ':count oras', + 'a_hour' => ':count oras', + + 'minute' => ':count minuto', + 'min' => ':count minuto', + 'a_minute' => ':count minuto', + + 'second' => ':count segundo', + 's' => ':count segundo', + 'a_second' => ':count segundo', + + 'ago' => ':time ang nakalipas', + 'from_now' => 'sa :time', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fo.php new file mode 100644 index 0000000000..6a14a6fb84 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fo.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kristian Sakarisson + * - Franรงois B + * - JD Isaacks + * - Sverri Mohr Olsen + */ +return [ + 'year' => 'eitt รกr|:count รกr', + 'y' => ':count รกr|:count รกr', + 'month' => 'ein mรกnaรฐi|:count mรกnaรฐir', + 'm' => ':count mรกnaรฐur|:count mรกnaรฐir', + 'week' => ':count vika|:count vikur', + 'w' => ':count vika|:count vikur', + 'day' => 'ein dagur|:count dagar', + 'd' => ':count dag|:count dagar', + 'hour' => 'ein tรญmi|:count tรญmar', + 'h' => ':count tรญmi|:count tรญmar', + 'minute' => 'ein minutt|:count minuttir', + 'min' => ':count minutt|:count minuttir', + 'second' => 'fรก sekund|:count sekundir', + 's' => ':count sekund|:count sekundir', + 'ago' => ':time sรญรฐani', + 'from_now' => 'um :time', + 'after' => ':time aftanรก', + 'before' => ':time รกรฐrenn', + 'diff_today' => 'ร', + 'diff_yesterday' => 'ร', + 'diff_yesterday_regexp' => 'ร(?:\\s+gjรกr)?(?:\\s+kl.)?', + 'diff_tomorrow' => 'ร', + 'diff_tomorrow_regexp' => 'ร(?:\\s+morgin)?(?:\\s+kl.)?', + 'diff_today_regexp' => 'ร(?:\\s+dag)?(?:\\s+kl.)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D. MMMM, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ร dag kl.] LT', + 'nextDay' => '[ร morgin kl.] LT', + 'nextWeek' => 'dddd [kl.] LT', + 'lastDay' => '[ร gjรกr kl.] LT', + 'lastWeek' => '[sรญรฐstu] dddd [kl] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mars', 'aprรญl', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['sunnudagur', 'mรกnadagur', 'tรฝsdagur', 'mikudagur', 'hรณsdagur', 'frรญggjadagur', 'leygardagur'], + 'weekdays_short' => ['sun', 'mรกn', 'tรฝs', 'mik', 'hรณs', 'frรญ', 'ley'], + 'weekdays_min' => ['su', 'mรก', 'tรฝ', 'mi', 'hรณ', 'fr', 'le'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fo_DK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fo_DK.php new file mode 100644 index 0000000000..657f2c5bb5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fo_DK.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fo.php', [ + 'formats' => [ + 'L' => 'DD.MM.yy', + 'LL' => 'DD.MM.YYYY', + 'LLL' => 'D. MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY, HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fo_FO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fo_FO.php new file mode 100644 index 0000000000..6d7361673b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fo_FO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fo.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr.php new file mode 100644 index 0000000000..f4c7247b43 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dieter Sting + * - Franรงois B + * - Maxime VALY + * - JD Isaacks + * - Dieter Sting + * - Franรงois B + * - JD Isaacks + * - Sebastian Thierer + * - Fastfuel + * - Pete Scopes (pdscopes) + */ +return [ + 'year' => ':count an|:count ans', + 'a_year' => 'un an|:count ans', + 'y' => ':count an|:count ans', + 'month' => ':count mois|:count mois', + 'a_month' => 'un mois|:count mois', + 'm' => ':count mois', + 'week' => ':count semaine|:count semaines', + 'a_week' => 'une semaine|:count semaines', + 'w' => ':count sem.', + 'day' => ':count jour|:count jours', + 'a_day' => 'un jour|:count jours', + 'd' => ':count j', + 'hour' => ':count heure|:count heures', + 'a_hour' => 'une heure|:count heures', + 'h' => ':count h', + 'minute' => ':count minute|:count minutes', + 'a_minute' => 'une minute|:count minutes', + 'min' => ':count min', + 'second' => ':count seconde|:count secondes', + 'a_second' => 'quelques secondes|:count secondes', + 's' => ':count s', + 'millisecond' => ':count milliseconde|:count millisecondes', + 'a_millisecond' => 'une milliseconde|:count millisecondes', + 'ms' => ':countms', + 'microsecond' => ':count microseconde|:count microsecondes', + 'a_microsecond' => 'une microseconde|:count microsecondes', + 'ยตs' => ':countยตs', + 'ago' => 'il y a :time', + 'from_now' => 'dans :time', + 'after' => ':time aprรจs', + 'before' => ':time avant', + 'diff_now' => "ร  l'instant", + 'diff_today' => "aujourd'hui", + 'diff_today_regexp' => "aujourd'hui(?:\s+ร )?", + 'diff_yesterday' => 'hier', + 'diff_yesterday_regexp' => 'hier(?:\s+ร )?', + 'diff_tomorrow' => 'demain', + 'diff_tomorrow_regexp' => 'demain(?:\s+ร )?', + 'diff_before_yesterday' => 'avant-hier', + 'diff_after_tomorrow' => 'aprรจs-demain', + 'period_recurrences' => ':count fois', + 'period_interval' => 'tous les :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'ร  :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Aujourdโ€™hui ร ] LT', + 'nextDay' => '[Demain ร ] LT', + 'nextWeek' => 'dddd [ร ] LT', + 'lastDay' => '[Hier ร ] LT', + 'lastWeek' => 'dddd [dernier ร ] LT', + 'sameElse' => 'L', + ], + 'months' => ['janvier', 'fรฉvrier', 'mars', 'avril', 'mai', 'juin', 'juillet', 'aoรปt', 'septembre', 'octobre', 'novembre', 'dรฉcembre'], + 'months_short' => ['janv.', 'fรฉvr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'aoรปt', 'sept.', 'oct.', 'nov.', 'dรฉc.'], + 'weekdays' => ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'], + 'weekdays_short' => ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'], + 'weekdays_min' => ['di', 'lu', 'ma', 'me', 'je', 've', 'sa'], + 'ordinal' => function ($number, $period) { + switch ($period) { + // In French, only the first has to be ordinal, other number remains cardinal + // @link https://fr.wikihow.com/%C3%A9crire-la-date-en-fran%C3%A7ais + case 'D': + return $number.($number === 1 ? 'er' : ''); + + default: + case 'M': + case 'Q': + case 'DDD': + case 'd': + return $number.($number === 1 ? 'er' : 'e'); + + // Words with feminine grammatical gender: semaine + case 'w': + case 'W': + return $number.($number === 1 ? 're' : 'e'); + } + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' et '], + 'ordinal_words' => [ + 'of' => 'de', + 'first' => 'premier', + 'second' => 'deuxiรจme', + 'third' => 'troisiรจme', + 'fourth' => 'quatriรจme', + 'fifth' => 'cinquiรจme', + 'last' => 'dernier', + ], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BE.php new file mode 100644 index 0000000000..f6cafe87c6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BE.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'months_short' => ['jan', 'fรฉv', 'mar', 'avr', 'mai', 'jun', 'jui', 'aoรป', 'sep', 'oct', 'nov', 'dรฉc'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BF.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BF.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BI.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BJ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BJ.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BJ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BL.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CA.php new file mode 100644 index 0000000000..c9f6346fca --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CA.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dieter Sting + * - Franรงois B + * - Maxime VALY + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CD.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CD.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CD.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CF.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CF.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CG.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CH.php new file mode 100644 index 0000000000..8674c27d95 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CH.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dieter Sting + * - Franรงois B + * - Gaspard Bucher + * - Maxime VALY + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CI.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CM.php new file mode 100644 index 0000000000..67d3787859 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_CM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'meridiem' => ['mat.', 'soir'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_DJ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_DJ.php new file mode 100644 index 0000000000..2f060869e4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_DJ.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_DZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_DZ.php new file mode 100644 index 0000000000..ae8db5fa46 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_DZ.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_FR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_FR.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_FR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GA.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GF.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GF.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GN.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GP.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GP.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GP.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GQ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GQ.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_GQ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_HT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_HT.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_HT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_KM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_KM.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_KM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_LU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_LU.php new file mode 100644 index 0000000000..8e37d852bf --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_LU.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months_short' => ['jan', 'fรฉv', 'mar', 'avr', 'mai', 'jun', 'jui', 'aoรป', 'sep', 'oct', 'nov', 'dรฉc'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MA.php new file mode 100644 index 0000000000..1bf034dc6d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MA.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MC.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MC.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MF.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MF.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MG.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_ML.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_ML.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_ML.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MQ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MQ.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MQ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MR.php new file mode 100644 index 0000000000..37cf83f01b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MR.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MU.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_MU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_NC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_NC.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_NC.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_NE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_NE.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_NE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_PF.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_PF.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_PF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_PM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_PM.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_PM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_RE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_RE.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_RE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_RW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_RW.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_RW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_SC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_SC.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_SC.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_SN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_SN.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_SN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_SY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_SY.php new file mode 100644 index 0000000000..ae8db5fa46 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_SY.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_TD.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_TD.php new file mode 100644 index 0000000000..37cf83f01b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_TD.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_TG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_TG.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_TG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_TN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_TN.php new file mode 100644 index 0000000000..6905e7a84b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_TN.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_VU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_VU.php new file mode 100644 index 0000000000..37cf83f01b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_VU.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_WF.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_WF.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_WF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_YT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_YT.php new file mode 100644 index 0000000000..ec3ee359c9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_YT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fur.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fur.php new file mode 100644 index 0000000000..36c2564f64 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fur.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/fur_IT.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fur_IT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fur_IT.php new file mode 100644 index 0000000000..0147a596ac --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fur_IT.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD. MM. YY', + 'LL' => 'DD di MMMM dal YYYY', + 'LLL' => 'DD di MMM HH:mm', + 'LLLL' => 'DD di MMMM dal YYYY HH:mm', + ], + 'months' => ['zenรขr', 'fevrรขr', 'marรง', 'avrรฎl', 'mai', 'jugn', 'lui', 'avost', 'setembar', 'otubar', 'novembar', 'dicembar'], + 'months_short' => ['zen', 'fev', 'mar', 'avr', 'mai', 'jug', 'lui', 'avo', 'set', 'otu', 'nov', 'dic'], + 'weekdays' => ['domenie', 'lunis', 'martars', 'miercus', 'joibe', 'vinars', 'sabide'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mie', 'joi', 'vin', 'sab'], + 'weekdays_min' => ['dom', 'lun', 'mar', 'mie', 'joi', 'vin', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'year' => ':count an', + 'month' => ':count mรชs', + 'week' => ':count setemane', + 'day' => ':count zornade', + 'hour' => ':count ore', + 'minute' => ':count minรปt', + 'second' => ':count secont', +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy.php new file mode 100644 index 0000000000..c1b543970d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Tim Fish + * - JD Isaacks + */ +return [ + 'year' => ':count jier|:count jierren', + 'a_year' => 'ien jier|:count jierren', + 'y' => ':count j', + 'month' => ':count moanne|:count moannen', + 'a_month' => 'ien moanne|:count moannen', + 'm' => ':count moa.', + 'week' => ':count wike|:count wiken', + 'a_week' => 'in wike|:count wiken', + 'a' => ':count w.', + 'day' => ':count dei|:count dagen', + 'a_day' => 'ien dei|:count dagen', + 'd' => ':count d.', + 'hour' => ':count oere|:count oeren', + 'a_hour' => 'ien oere|:count oeren', + 'h' => ':count o.', + 'minute' => ':count minรบt|:count minuten', + 'a_minute' => 'ien minรบt|:count minuten', + 'min' => ':count min.', + 'second' => ':count sekonde|:count sekonden', + 'a_second' => 'in pear sekonden|:count sekonden', + 's' => ':count s.', + 'ago' => ':time lyn', + 'from_now' => 'oer :time', + 'diff_yesterday' => 'juster', + 'diff_yesterday_regexp' => 'juster(?:\\s+om)?', + 'diff_today' => 'hjoed', + 'diff_today_regexp' => 'hjoed(?:\\s+om)?', + 'diff_tomorrow' => 'moarn', + 'diff_tomorrow_regexp' => 'moarn(?:\\s+om)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[hjoed om] LT', + 'nextDay' => '[moarn om] LT', + 'nextWeek' => 'dddd [om] LT', + 'lastDay' => '[juster om] LT', + 'lastWeek' => '[รดfrรปne] dddd [om] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); + }, + 'months' => ['jannewaris', 'febrewaris', 'maart', 'april', 'maaie', 'juny', 'july', 'augustus', 'septimber', 'oktober', 'novimber', 'desimber'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'mmm_suffix' => '.', + 'weekdays' => ['snein', 'moandei', 'tiisdei', 'woansdei', 'tongersdei', 'freed', 'sneon'], + 'weekdays_short' => ['si.', 'mo.', 'ti.', 'wo.', 'to.', 'fr.', 'so.'], + 'weekdays_min' => ['Si', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' en '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy_DE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy_DE.php new file mode 100644 index 0000000000..8559d5c234 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy_DE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Jaunuwoa', 'Februwoa', 'Moaz', 'Aprell', 'Mai', 'Juni', 'Juli', 'August', 'Septamba', 'Oktoba', 'Nowamba', 'Dezamba'], + 'months_short' => ['Jan', 'Feb', 'Moz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Now', 'Dez'], + 'weekdays' => ['Sinndag', 'Mondag', 'Dingsdag', 'Meddwรคakj', 'Donnadag', 'Friedag', 'Sinnowend'], + 'weekdays_short' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'weekdays_min' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy_NL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy_NL.php new file mode 100644 index 0000000000..01cc96c348 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy_NL.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/fy.php', [ + 'formats' => [ + 'L' => 'DD-MM-YY', + ], + 'months' => ['Jannewaris', 'Febrewaris', 'Maart', 'April', 'Maaie', 'Juny', 'July', 'Augustus', 'Septimber', 'Oktober', 'Novimber', 'Desimber'], + 'months_short' => ['Jan', 'Feb', 'Mrt', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Snein', 'Moandei', 'Tiisdei', 'Woansdei', 'Tongersdei', 'Freed', 'Sneon'], + 'weekdays_short' => ['Sn', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'Sn'], + 'weekdays_min' => ['Sn', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'Sn'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ga.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ga.php new file mode 100644 index 0000000000..9f07a26cec --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ga.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Thanks to Andrรฉ Silva : https://github.com/askpt + */ + +return [ + 'year' => ':count bliain', + 'a_year' => '{1}bliain|:count bliain', + 'y' => ':countb', + 'month' => ':count mรญ', + 'a_month' => '{1}mรญ|:count mรญ', + 'm' => ':countm', + 'week' => ':count sheachtain', + 'a_week' => '{1}sheachtain|:count sheachtain', + 'w' => ':countsh', + 'day' => ':count lรก', + 'a_day' => '{1}lรก|:count lรก', + 'd' => ':countl', + 'hour' => ':count uair an chloig', + 'a_hour' => '{1}uair an chloig|:count uair an chloig', + 'h' => ':countu', + 'minute' => ':count nรณimรฉad', + 'a_minute' => '{1}nรณimรฉad|:count nรณimรฉad', + 'min' => ':countn', + 'second' => ':count soicind', + 'a_second' => '{1}cรบpla soicind|:count soicind', + 's' => ':countso', + 'ago' => ':time รณ shin', + 'from_now' => 'i :time', + 'after' => ':time tar รฉis', + 'before' => ':time roimh', + 'diff_now' => 'anois', + 'diff_today' => 'Inniu', + 'diff_today_regexp' => 'Inniu(?:\\s+ag)?', + 'diff_yesterday' => 'innรฉ', + 'diff_yesterday_regexp' => 'Innรฉ(?:\\s+aig)?', + 'diff_tomorrow' => 'amรกrach', + 'diff_tomorrow_regexp' => 'Amรกrach(?:\\s+ag)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Inniu ag] LT', + 'nextDay' => '[Amรกrach ag] LT', + 'nextWeek' => 'dddd [ag] LT', + 'lastDay' => '[Innรฉ aig] LT', + 'lastWeek' => 'dddd [seo caite] [ag] LT', + 'sameElse' => 'L', + ], + 'months' => ['Eanรกir', 'Feabhra', 'Mรกrta', 'Aibreรกn', 'Bealtaine', 'Mรฉitheamh', 'Iรบil', 'Lรบnasa', 'Meรกn Fรณmhair', 'Deaireadh Fรณmhair', 'Samhain', 'Nollaig'], + 'months_short' => ['Eanรก', 'Feab', 'Mรกrt', 'Aibr', 'Beal', 'Mรฉit', 'Iรบil', 'Lรบna', 'Meรกn', 'Deai', 'Samh', 'Noll'], + 'weekdays' => ['Dรฉ Domhnaigh', 'Dรฉ Luain', 'Dรฉ Mรกirt', 'Dรฉ Cรฉadaoin', 'Dรฉardaoin', 'Dรฉ hAoine', 'Dรฉ Satharn'], + 'weekdays_short' => ['Dom', 'Lua', 'Mรกi', 'Cรฉa', 'Dรฉa', 'hAo', 'Sat'], + 'weekdays_min' => ['Do', 'Lu', 'Mรก', 'Ce', 'Dรฉ', 'hA', 'Sa'], + 'ordinal' => function ($number) { + return $number.($number === 1 ? 'd' : ($number % 10 === 2 ? 'na' : 'mh')); + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' agus '], + 'meridiem' => ['r.n.', 'i.n.'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ga_IE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ga_IE.php new file mode 100644 index 0000000000..57b0c4fb48 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ga_IE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ga.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gd.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gd.php new file mode 100644 index 0000000000..63d064dddc --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gd.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Jon Ashdown + */ +return [ + 'year' => ':count bliadhna', + 'a_year' => '{1}bliadhna|:count bliadhna', + 'y' => ':count b.', + 'month' => ':count mรฌosan', + 'a_month' => '{1}mรฌos|:count mรฌosan', + 'm' => ':count ms.', + 'week' => ':count seachdainean', + 'a_week' => '{1}seachdain|:count seachdainean', + 'w' => ':count s.', + 'day' => ':count latha', + 'a_day' => '{1}latha|:count latha', + 'd' => ':count l.', + 'hour' => ':count uairean', + 'a_hour' => '{1}uair|:count uairean', + 'h' => ':count u.', + 'minute' => ':count mionaidean', + 'a_minute' => '{1}mionaid|:count mionaidean', + 'min' => ':count md.', + 'second' => ':count diogan', + 'a_second' => '{1}beagan diogan|:count diogan', + 's' => ':count d.', + 'ago' => 'bho chionn :time', + 'from_now' => 'ann an :time', + 'diff_yesterday' => 'An-dรจ', + 'diff_yesterday_regexp' => 'An-dรจ(?:\\s+aig)?', + 'diff_today' => 'An-diugh', + 'diff_today_regexp' => 'An-diugh(?:\\s+aig)?', + 'diff_tomorrow' => 'A-mร ireach', + 'diff_tomorrow_regexp' => 'A-mร ireach(?:\\s+aig)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[An-diugh aig] LT', + 'nextDay' => '[A-mร ireach aig] LT', + 'nextWeek' => 'dddd [aig] LT', + 'lastDay' => '[An-dรจ aig] LT', + 'lastWeek' => 'dddd [seo chaidh] [aig] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.($number === 1 ? 'd' : ($number % 10 === 2 ? 'na' : 'mh')); + }, + 'months' => ['Am Faoilleach', 'An Gearran', 'Am Mร rt', 'An Giblean', 'An Cรจitean', 'An t-ร’gmhios', 'An t-Iuchar', 'An Lรนnastal', 'An t-Sultain', 'An Dร mhair', 'An t-Samhain', 'An Dรนbhlachd'], + 'months_short' => ['Faoi', 'Gear', 'Mร rt', 'Gibl', 'Cรจit', 'ร’gmh', 'Iuch', 'Lรนn', 'Sult', 'Dร mh', 'Samh', 'Dรนbh'], + 'weekdays' => ['Didรฒmhnaich', 'Diluain', 'Dimร irt', 'Diciadain', 'Diardaoin', 'Dihaoine', 'Disathairne'], + 'weekdays_short' => ['Did', 'Dil', 'Dim', 'Dic', 'Dia', 'Dih', 'Dis'], + 'weekdays_min' => ['Dรฒ', 'Lu', 'Mร ', 'Ci', 'Ar', 'Ha', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' agus '], + 'meridiem' => ['m', 'f'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gd_GB.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gd_GB.php new file mode 100644 index 0000000000..4fc26b3dc5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gd_GB.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/gd.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gez.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gez.php new file mode 100644 index 0000000000..b8a2f0ebe0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gez.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/gez_ER.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gez_ER.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gez_ER.php new file mode 100644 index 0000000000..f19d1df1ed --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gez_ER.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['แŒ แˆแˆจ', 'แŠจแ‰ฐแ‰ฐ', 'แˆ˜แŒˆแ‰ ', 'แŠ แŠ€แ‹˜', 'แŒแŠ•แ‰ฃแ‰ต', 'แˆ แŠ•แ‹จ', 'แˆแˆ˜แˆˆ', 'แАแˆแˆฐ', 'แŠจแˆจแˆ˜', 'แŒ แ‰€แˆ˜', 'แŠ€แ‹ฐแˆจ', 'แŠ€แˆ แˆ '], + 'months_short' => ['แŒ แˆแˆจ', 'แŠจแ‰ฐแ‰ฐ', 'แˆ˜แŒˆแ‰ ', 'แŠ แŠ€แ‹˜', 'แŒแŠ•แ‰ฃ', 'แˆ แŠ•แ‹จ', 'แˆแˆ˜แˆˆ', 'แАแˆแˆฐ', 'แŠจแˆจแˆ˜', 'แŒ แ‰€แˆ˜', 'แŠ€แ‹ฐแˆจ', 'แŠ€แˆ แˆ '], + 'weekdays' => ['แŠฅแЁแ‹ต', 'แˆฐแŠ‘แ‹ญ', 'แˆ แˆ‰แˆต', 'แˆซแ‰ฅแ‹•', 'แˆแˆ™แˆต', 'แ‹“แˆญแ‰ ', 'แ‰€แ‹ณแˆšแ‰ต'], + 'weekdays_short' => ['แŠฅแЁแ‹ต', 'แˆฐแŠ‘แ‹ญ', 'แˆ แˆ‰แˆต', 'แˆซแ‰ฅแ‹•', 'แˆแˆ™แˆต', 'แ‹“แˆญแ‰ ', 'แ‰€แ‹ณแˆš'], + 'weekdays_min' => ['แŠฅแЁแ‹ต', 'แˆฐแŠ‘แ‹ญ', 'แˆ แˆ‰แˆต', 'แˆซแ‰ฅแ‹•', 'แˆแˆ™แˆต', 'แ‹“แˆญแ‰ ', 'แ‰€แ‹ณแˆš'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['แŒฝแ‰ฃแˆ•', 'แˆแˆดแ‰ต'], + + 'month' => ':count แ‹ˆแˆญแˆ•', // less reliable + 'm' => ':count แ‹ˆแˆญแˆ•', // less reliable + 'a_month' => ':count แ‹ˆแˆญแˆ•', // less reliable + + 'week' => ':count แˆฐแ‰ฅแ‹‘', // less reliable + 'w' => ':count แˆฐแ‰ฅแ‹‘', // less reliable + 'a_week' => ':count แˆฐแ‰ฅแ‹‘', // less reliable + + 'hour' => ':count แŠ แŠ•แ‰ตแˆ™', // less reliable + 'h' => ':count แŠ แŠ•แ‰ตแˆ™', // less reliable + 'a_hour' => ':count แŠ แŠ•แ‰ตแˆ™', // less reliable + + 'minute' => ':count แŠ•แŠกแˆต', // less reliable + 'min' => ':count แŠ•แŠกแˆต', // less reliable + 'a_minute' => ':count แŠ•แŠกแˆต', // less reliable + + 'year' => ':count แ‹“แˆ˜แ‰ต', + 'y' => ':count แ‹“แˆ˜แ‰ต', + 'a_year' => ':count แ‹“แˆ˜แ‰ต', + + 'day' => ':count แ‹•แˆˆแ‰ต', + 'd' => ':count แ‹•แˆˆแ‰ต', + 'a_day' => ':count แ‹•แˆˆแ‰ต', + + 'second' => ':count แŠซแˆแŠฅ', + 's' => ':count แŠซแˆแŠฅ', + 'a_second' => ':count แŠซแˆแŠฅ', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gez_ET.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gez_ET.php new file mode 100644 index 0000000000..393300962a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gez_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['แŒƒแŠ•แ‹ฉแ‹ˆแˆช', 'แŒแ‰ฅแˆฉแ‹ˆแˆช', 'แˆ›แˆญแ‰ฝ', 'แŠคแ•แˆจแˆ', 'แˆœแ‹ญ', 'แŒแŠ•', 'แŒแˆ‹แ‹ญ', 'แŠฆแŒˆแˆตแ‰ต', 'แˆดแ•แ‰ดแˆแ‰ แˆญ', 'แŠฆแŠญแ‰ฐแ‹แ‰ แˆญ', 'แŠ–แ‰ฌแˆแ‰ แˆญ', 'แ‹ฒแˆดแˆแ‰ แˆญ'], + 'months_short' => ['แŒƒแŠ•แ‹ฉ', 'แŒแ‰ฅแˆฉ', 'แˆ›แˆญแ‰ฝ', 'แŠคแ•แˆจ', 'แˆœแ‹ญ ', 'แŒแŠ• ', 'แŒแˆ‹แ‹ญ', 'แŠฆแŒˆแˆต', 'แˆดแ•แ‰ด', 'แŠฆแŠญแ‰ฐ', 'แŠ–แ‰ฌแˆ', 'แ‹ฒแˆดแˆ'], + 'weekdays' => ['แŠฅแЁแ‹ต', 'แˆฐแŠ‘แ‹ญ', 'แˆ แˆ‰แˆต', 'แˆซแ‰ฅแ‹•', 'แˆแˆ™แˆต', 'แ‹“แˆญแ‰ ', 'แ‰€แ‹ณแˆšแ‰ต'], + 'weekdays_short' => ['แŠฅแЁแ‹ต', 'แˆฐแŠ‘แ‹ญ', 'แˆ แˆ‰แˆต', 'แˆซแ‰ฅแ‹•', 'แˆแˆ™แˆต', 'แ‹“แˆญแ‰ ', 'แ‰€แ‹ณแˆš'], + 'weekdays_min' => ['แŠฅแЁแ‹ต', 'แˆฐแŠ‘แ‹ญ', 'แˆ แˆ‰แˆต', 'แˆซแ‰ฅแ‹•', 'แˆแˆ™แˆต', 'แ‹“แˆญแ‰ ', 'แ‰€แ‹ณแˆš'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['แŒฝแ‰ฃแˆ•', 'แˆแˆดแ‰ต'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gl.php new file mode 100644 index 0000000000..088b0f281c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gl.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Fidel Pita + * - JD Isaacks + * - Diego Vilarinฬƒo + * - Sebastian Thierer + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count ano|:count anos', + 'a_year' => 'un ano|:count anos', + 'y' => ':count a.', + 'month' => ':count mes|:count meses', + 'a_month' => 'un mes|:count meses', + 'm' => ':count mes.', + 'week' => ':count semana|:count semanas', + 'a_week' => 'unha semana|:count semanas', + 'w' => ':count sem.', + 'day' => ':count dรญa|:count dรญas', + 'a_day' => 'un dรญa|:count dรญas', + 'd' => ':count d.', + 'hour' => ':count hora|:count horas', + 'a_hour' => 'unha hora|:count horas', + 'h' => ':count h.', + 'minute' => ':count minuto|:count minutos', + 'a_minute' => 'un minuto|:count minutos', + 'min' => ':count min.', + 'second' => ':count segundo|:count segundos', + 'a_second' => 'uns segundos|:count segundos', + 's' => ':count seg.', + 'ago' => 'hai :time', + 'from_now' => function ($time) { + if (str_starts_with($time, 'un')) { + return "n$time"; + } + + return "en $time"; + }, + 'diff_now' => 'agora', + 'diff_today' => 'hoxe', + 'diff_today_regexp' => 'hoxe(?:\\s+รกs)?', + 'diff_yesterday' => 'onte', + 'diff_yesterday_regexp' => 'onte(?:\\s+รก)?', + 'diff_tomorrow' => 'maรฑรก', + 'diff_tomorrow_regexp' => 'maรฑรก(?:\\s+รกs)?', + 'after' => ':time despois', + 'before' => ':time antes', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [de] MMMM [de] YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY H:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $current) { + return '[hoxe '.($current->hour !== 1 ? 'รกs' : 'รก').'] LT'; + }, + 'nextDay' => function (CarbonInterface $current) { + return '[maรฑรก '.($current->hour !== 1 ? 'รกs' : 'รก').'] LT'; + }, + 'nextWeek' => function (CarbonInterface $current) { + return 'dddd ['.($current->hour !== 1 ? 'รกs' : 'รก').'] LT'; + }, + 'lastDay' => function (CarbonInterface $current) { + return '[onte '.($current->hour !== 1 ? 'รก' : 'a').'] LT'; + }, + 'lastWeek' => function (CarbonInterface $current) { + return '[o] dddd [pasado '.($current->hour !== 1 ? 'รกs' : 'รก').'] LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberยบ', + 'months' => ['xaneiro', 'febreiro', 'marzo', 'abril', 'maio', 'xuรฑo', 'xullo', 'agosto', 'setembro', 'outubro', 'novembro', 'decembro'], + 'months_short' => ['xan.', 'feb.', 'mar.', 'abr.', 'mai.', 'xuรฑ.', 'xul.', 'ago.', 'set.', 'out.', 'nov.', 'dec.'], + 'weekdays' => ['domingo', 'luns', 'martes', 'mรฉrcores', 'xoves', 'venres', 'sรกbado'], + 'weekdays_short' => ['dom.', 'lun.', 'mar.', 'mรฉr.', 'xov.', 'ven.', 'sรกb.'], + 'weekdays_min' => ['do', 'lu', 'ma', 'mรฉ', 'xo', 've', 'sรก'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], + 'meridiem' => ['a.m.', 'p.m.'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gl_ES.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gl_ES.php new file mode 100644 index 0000000000..9d6c1d96b1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gl_ES.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/gl.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gom.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gom.php new file mode 100644 index 0000000000..2a0584f88a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gom.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/gom_Latn.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gom_Latn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gom_Latn.php new file mode 100644 index 0000000000..612bb88652 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gom_Latn.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'year' => ':count voros|:count vorsam', + 'y' => ':countv', + 'month' => ':count mhoino|:count mhoine', + 'm' => ':countmh', + 'week' => ':count satolleacho|:count satolleache', + 'w' => ':countsa|:countsa', + 'day' => ':count dis', + 'd' => ':countd', + 'hour' => ':count hor|:count horam', + 'h' => ':counth', + 'minute' => ':count minute|:count mintam', + 'min' => ':countm', + 'second' => ':count second', + 's' => ':counts', + + 'diff_today' => 'Aiz', + 'diff_yesterday' => 'Kal', + 'diff_tomorrow' => 'Faleam', + 'formats' => [ + 'LT' => 'A h:mm [vazta]', + 'LTS' => 'A h:mm:ss [vazta]', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY A h:mm [vazta]', + 'LLLL' => 'dddd, MMMM[achea] Do, YYYY, A h:mm [vazta]', + 'llll' => 'ddd, D MMM YYYY, A h:mm [vazta]', + ], + + 'calendar' => [ + 'sameDay' => '[Aiz] LT', + 'nextDay' => '[Faleam] LT', + 'nextWeek' => '[Ieta to] dddd[,] LT', + 'lastDay' => '[Kal] LT', + 'lastWeek' => '[Fatlo] dddd[,] LT', + 'sameElse' => 'L', + ], + + 'months' => ['Janer', 'Febrer', 'Mars', 'Abril', 'Mai', 'Jun', 'Julai', 'Agost', 'Setembr', 'Otubr', 'Novembr', 'Dezembr'], + 'months_short' => ['Jan.', 'Feb.', 'Mars', 'Abr.', 'Mai', 'Jun', 'Jul.', 'Ago.', 'Set.', 'Otu.', 'Nov.', 'Dez.'], + 'weekdays' => ['Aitar', 'Somar', 'Mongllar', 'Budvar', 'Brestar', 'Sukrar', 'Son\'var'], + 'weekdays_short' => ['Ait.', 'Som.', 'Mon.', 'Bud.', 'Bre.', 'Suk.', 'Son.'], + 'weekdays_min' => ['Ai', 'Sm', 'Mo', 'Bu', 'Br', 'Su', 'Sn'], + + 'ordinal' => function ($number, $period) { + return $number.($period === 'D' ? 'er' : ''); + }, + + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'rati'; + } + if ($hour < 12) { + return 'sokalli'; + } + if ($hour < 16) { + return 'donparam'; + } + if ($hour < 20) { + return 'sanje'; + } + + return 'rati'; + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ani '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gsw.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gsw.php new file mode 100644 index 0000000000..c5c850edb1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gsw.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Christopher Dell + * - Akira Matsuda + * - Enrique Vidal + * - Simone Carletti + * - Henning Kiel + * - Aaron Patterson + * - Florian Hanke + */ +return [ + 'year' => ':count Johr', + 'month' => ':count Monet', + 'week' => ':count Woche', + 'day' => ':count Tag', + 'hour' => ':count Schtund', + 'minute' => ':count Minute', + 'second' => ':count Sekunde', + 'weekdays' => ['Sunntig', 'Mรคntig', 'Ziischtig', 'Mittwuch', 'Dunschtig', 'Friitig', 'Samschtig'], + 'weekdays_short' => ['Su', 'Mรค', 'Zi', 'Mi', 'Du', 'Fr', 'Sa'], + 'weekdays_min' => ['Su', 'Mรค', 'Zi', 'Mi', 'Du', 'Fr', 'Sa'], + 'months' => ['Januar', 'Februar', 'Mรคrz', 'April', 'Mai', 'Juni', 'Juli', 'Auguscht', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan', 'Feb', 'Mรคr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + 'meridiem' => ['am Vormittag', 'am Namittag'], + 'ordinal' => ':number.', + 'list' => [', ', ' und '], + 'diff_now' => 'now', + 'diff_yesterday' => 'geschter', + 'diff_tomorrow' => 'moorn', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'Do MMMM YYYY', + 'LLL' => 'Do MMMM, HH:mm [Uhr]', + 'LLLL' => 'dddd, Do MMMM YYYY, HH:mm [Uhr]', + ], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gsw_CH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gsw_CH.php new file mode 100644 index 0000000000..594eb25dcb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gsw_CH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/gsw.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gsw_FR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gsw_FR.php new file mode 100644 index 0000000000..3581dcfb7f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gsw_FR.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/gsw.php', [ + 'meridiem' => ['vorm.', 'nam.'], + 'months' => ['Januar', 'Februar', 'Mรคrz', 'April', 'Mai', 'Juni', 'Juli', 'Auguscht', 'Septรคmber', 'Oktoober', 'Novรคmber', 'Dezรคmber'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LLL' => 'Do MMMM YYYY HH:mm', + 'LLLL' => 'dddd, Do MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gsw_LI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gsw_LI.php new file mode 100644 index 0000000000..3581dcfb7f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gsw_LI.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/gsw.php', [ + 'meridiem' => ['vorm.', 'nam.'], + 'months' => ['Januar', 'Februar', 'Mรคrz', 'April', 'Mai', 'Juni', 'Juli', 'Auguscht', 'Septรคmber', 'Oktoober', 'Novรคmber', 'Dezรคmber'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LLL' => 'Do MMMM YYYY HH:mm', + 'LLLL' => 'dddd, Do MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gu.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gu.php new file mode 100644 index 0000000000..8bc4311409 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gu.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Kaushik Thanki + * - Josh Soref + */ +return [ + 'year' => 'เชเช• เชตเชฐเซเชท|:count เชตเชฐเซเชท', + 'y' => ':countเชตเชฐเซเชท|:countเชตเชฐเซเชทเซ‹', + 'month' => 'เชเช• เชฎเชนเชฟเชจเซ‹|:count เชฎเชนเชฟเชจเชพ', + 'm' => ':countเชฎเชนเชฟเชจเซ‹|:countเชฎเชนเชฟเชจเชพ', + 'week' => ':count เช…เช เชตเชพเชกเชฟเชฏเซเช‚|:count เช…เช เชตเชพเชกเชฟเชฏเชพ', + 'w' => ':countเช…เช .|:countเช…เช .', + 'day' => 'เชเช• เชฆเชฟเชตเชธ|:count เชฆเชฟเชตเชธ', + 'd' => ':countเชฆเชฟ.|:countเชฆเชฟ.', + 'hour' => 'เชเช• เช•เชฒเชพเช•|:count เช•เชฒเชพเช•', + 'h' => ':countเช•.|:countเช•.', + 'minute' => 'เชเช• เชฎเชฟเชจเชฟเชŸ|:count เชฎเชฟเชจเชฟเชŸ', + 'min' => ':countเชฎเชฟ.|:countเชฎเชฟ.', + 'second' => 'เช…เชฎเซเช• เชชเชณเซ‹|:count เชธเซ‡เช•เช‚เชก', + 's' => ':countเชธเซ‡.|:countเชธเซ‡.', + 'ago' => ':time เชชเซ‡เชนเชฒเชพ', + 'from_now' => ':time เชฎเชพ', + 'after' => ':time เชชเช›เซ€', + 'before' => ':time เชชเชนเซ‡เชฒเชพ', + 'diff_now' => 'เชนเชฎเชฃเชพเช‚', + 'diff_today' => 'เช†เชœ', + 'diff_yesterday' => 'เช—เช‡เช•เชพเชฒเซ‡', + 'diff_tomorrow' => 'เช•เชพเชฒเซ‡', + 'formats' => [ + 'LT' => 'A h:mm เชตเชพเช—เซเชฏเซ‡', + 'LTS' => 'A h:mm:ss เชตเชพเช—เซเชฏเซ‡', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm เชตเชพเช—เซเชฏเซ‡', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm เชตเชพเช—เซเชฏเซ‡', + ], + 'calendar' => [ + 'sameDay' => '[เช†เชœ] LT', + 'nextDay' => '[เช•เชพเชฒเซ‡] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[เช—เช‡เช•เชพเชฒเซ‡] LT', + 'lastWeek' => '[เชชเชพเช›เชฒเชพ] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'เชฐเชพเชค'; + } + if ($hour < 10) { + return 'เชธเชตเชพเชฐ'; + } + if ($hour < 17) { + return 'เชฌเชชเซ‹เชฐ'; + } + if ($hour < 20) { + return 'เชธเชพเช‚เชœ'; + } + + return 'เชฐเชพเชค'; + }, + 'months' => ['เชœเชพเชจเซเชฏเซเช†เชฐเซ€', 'เชซเซ‡เชฌเซเชฐเซเช†เชฐเซ€', 'เชฎเชพเชฐเซเชš', 'เชเชชเซเชฐเชฟเชฒ', 'เชฎเซ‡', 'เชœเซ‚เชจ', 'เชœเซเชฒเชพเชˆ', 'เช‘เช—เชธเซเชŸ', 'เชธเชชเซเชŸเซ‡เชฎเซเชฌเชฐ', 'เช‘เช•เซเชŸเซเชฌเชฐ', 'เชจเชตเซ‡เชฎเซเชฌเชฐ', 'เชกเชฟเชธเซ‡เชฎเซเชฌเชฐ'], + 'months_short' => ['เชœเชพเชจเซเชฏเซ.', 'เชซเซ‡เชฌเซเชฐเซ.', 'เชฎเชพเชฐเซเชš', 'เชเชชเซเชฐเชฟ.', 'เชฎเซ‡', 'เชœเซ‚เชจ', 'เชœเซเชฒเชพ.', 'เช‘เช—.', 'เชธเชชเซเชŸเซ‡.', 'เช‘เช•เซเชŸเซ.', 'เชจเชตเซ‡.', 'เชกเชฟเชธเซ‡.'], + 'weekdays' => ['เชฐเชตเชฟเชตเชพเชฐ', 'เชธเซ‹เชฎเชตเชพเชฐ', 'เชฎเช‚เช—เชณเชตเชพเชฐ', 'เชฌเซเชงเซเชตเชพเชฐ', 'เช—เซเชฐเซเชตเชพเชฐ', 'เชถเซเช•เซเชฐเชตเชพเชฐ', 'เชถเชจเชฟเชตเชพเชฐ'], + 'weekdays_short' => ['เชฐเชตเชฟ', 'เชธเซ‹เชฎ', 'เชฎเช‚เช—เชณ', 'เชฌเซเชงเซ', 'เช—เซเชฐเซ', 'เชถเซเช•เซเชฐ', 'เชถเชจเชฟ'], + 'weekdays_min' => ['เชฐ', 'เชธเซ‹', 'เชฎเช‚', 'เชฌเซ', 'เช—เซ', 'เชถเซ', 'เชถ'], + 'list' => [', ', ' เช…เชจเซ‡ '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gu_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gu_IN.php new file mode 100644 index 0000000000..02654b1f66 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gu_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/gu.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/guz.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/guz.php new file mode 100644 index 0000000000..6230165c10 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/guz.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Ma', 'Mo'], + 'weekdays' => ['Chumapiri', 'Chumatato', 'Chumaine', 'Chumatano', 'Aramisi', 'Ichuma', 'Esabato'], + 'weekdays_short' => ['Cpr', 'Ctt', 'Cmn', 'Cmt', 'Ars', 'Icm', 'Est'], + 'weekdays_min' => ['Cpr', 'Ctt', 'Cmn', 'Cmt', 'Ars', 'Icm', 'Est'], + 'months' => ['Chanuari', 'Feburari', 'Machi', 'Apiriri', 'Mei', 'Juni', 'Chulai', 'Agosti', 'Septemba', 'Okitoba', 'Nobemba', 'Disemba'], + 'months_short' => ['Can', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Cul', 'Agt', 'Sep', 'Okt', 'Nob', 'Dis'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'month' => ':count omotunyi', // less reliable + 'm' => ':count omotunyi', // less reliable + 'a_month' => ':count omotunyi', // less reliable + + 'week' => ':count isano naibere', // less reliable + 'w' => ':count isano naibere', // less reliable + 'a_week' => ':count isano naibere', // less reliable + + 'second' => ':count ibere', // less reliable + 's' => ':count ibere', // less reliable + 'a_second' => ':count ibere', // less reliable + + 'year' => ':count omwaka', + 'y' => ':count omwaka', + 'a_year' => ':count omwaka', + + 'day' => ':count rituko', + 'd' => ':count rituko', + 'a_day' => ':count rituko', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gv.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gv.php new file mode 100644 index 0000000000..7c52b940a7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gv.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/gv_GB.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gv_GB.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gv_GB.php new file mode 100644 index 0000000000..6b1168f994 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gv_GB.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alastair McKinstry bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Jerrey-geuree', 'Toshiaght-arree', 'Mayrnt', 'Averil', 'Boaldyn', 'Mean-souree', 'Jerrey-souree', 'Luanistyn', 'Mean-fouyir', 'Jerrey-fouyir', 'Mee Houney', 'Mee ny Nollick'], + 'months_short' => ['J-guer', 'T-arree', 'Mayrnt', 'Avrril', 'Boaldyn', 'M-souree', 'J-souree', 'Luanistyn', 'M-fouyir', 'J-fouyir', 'M.Houney', 'M.Nollick'], + 'weekdays' => ['Jedoonee', 'Jelhein', 'Jemayrt', 'Jercean', 'Jerdein', 'Jeheiney', 'Jesarn'], + 'weekdays_short' => ['Jed', 'Jel', 'Jem', 'Jerc', 'Jerd', 'Jeh', 'Jes'], + 'weekdays_min' => ['Jed', 'Jel', 'Jem', 'Jerc', 'Jerd', 'Jeh', 'Jes'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count blein', + 'y' => ':count blein', + 'a_year' => ':count blein', + + 'month' => ':count mee', + 'm' => ':count mee', + 'a_month' => ':count mee', + + 'week' => ':count shiaghtin', + 'w' => ':count shiaghtin', + 'a_week' => ':count shiaghtin', + + 'day' => ':count laa', + 'd' => ':count laa', + 'a_day' => ':count laa', + + 'hour' => ':count oor', + 'h' => ':count oor', + 'a_hour' => ':count oor', + + 'minute' => ':count feer veg', + 'min' => ':count feer veg', + 'a_minute' => ':count feer veg', + + 'second' => ':count derrey', + 's' => ':count derrey', + 'a_second' => ':count derrey', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ha.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ha.php new file mode 100644 index 0000000000..cd8e34d0d5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ha.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM, YYYY HH:mm', + ], + 'months' => ['Janairu', 'Faburairu', 'Maris', 'Afirilu', 'Mayu', 'Yuni', 'Yuli', 'Agusta', 'Satumba', 'Oktoba', 'Nuwamba', 'Disamba'], + 'months_short' => ['Jan', 'Fab', 'Mar', 'Afi', 'May', 'Yun', 'Yul', 'Agu', 'Sat', 'Okt', 'Nuw', 'Dis'], + 'weekdays' => ['Lahadi', 'Litini', 'Talata', 'Laraba', 'Alhamis', 'Jumaสผa', 'Asabar'], + 'weekdays_short' => ['Lah', 'Lit', 'Tal', 'Lar', 'Alh', 'Jum', 'Asa'], + 'weekdays_min' => ['Lh', 'Li', 'Ta', 'Lr', 'Al', 'Ju', 'As'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => 'shekara :count', + 'y' => 'shekara :count', + 'a_year' => 'shekara :count', + + 'month' => ':count wรกtร a', + 'm' => ':count wรกtร a', + 'a_month' => ':count wรกtร a', + + 'week' => ':count mako', + 'w' => ':count mako', + 'a_week' => ':count mako', + + 'day' => ':count rana', + 'd' => ':count rana', + 'a_day' => ':count rana', + + 'hour' => ':count รกwร a', + 'h' => ':count รกwร a', + 'a_hour' => ':count รกwร a', + + 'minute' => 'minti :count', + 'min' => 'minti :count', + 'a_minute' => 'minti :count', + + 'second' => ':count nรก bรญyรบ', + 's' => ':count nรก bรญyรบ', + 'a_second' => ':count nรก bรญyรบ', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ha_GH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ha_GH.php new file mode 100644 index 0000000000..f9f99a7351 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ha_GH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ha.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ha_NE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ha_NE.php new file mode 100644 index 0000000000..f9f99a7351 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ha_NE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ha.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ha_NG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ha_NG.php new file mode 100644 index 0000000000..f9f99a7351 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ha_NG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ha.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hak.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hak.php new file mode 100644 index 0000000000..6c3260e8c0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hak.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hak_TW.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hak_TW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hak_TW.php new file mode 100644 index 0000000000..fe23986504 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hak_TW.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYYๅนดMMๆœˆDDๆ—ฅ', + ], + 'months' => ['ไธ€ๆœˆ', 'ไบŒๆœˆ', 'ไธ‰ๆœˆ', 'ๅ››ๆœˆ', 'ไบ”ๆœˆ', 'ๅ…ญๆœˆ', 'ไธƒๆœˆ', 'ๅ…ซๆœˆ', 'ไนๆœˆ', 'ๅๆœˆ', 'ๅไธ€ๆœˆ', 'ๅไบŒๆœˆ'], + 'months_short' => [' 1ๆœˆ', ' 2ๆœˆ', ' 3ๆœˆ', ' 4ๆœˆ', ' 5ๆœˆ', ' 6ๆœˆ', ' 7ๆœˆ', ' 8ๆœˆ', ' 9ๆœˆ', '10ๆœˆ', '11ๆœˆ', '12ๆœˆ'], + 'weekdays' => ['็ฆฎๆ‹œๆ—ฅ', '็ฆฎๆ‹œไธ€', '็ฆฎๆ‹œไบŒ', '็ฆฎๆ‹œไธ‰', '็ฆฎๆ‹œๅ››', '็ฆฎๆ‹œไบ”', '็ฆฎๆ‹œๅ…ญ'], + 'weekdays_short' => ['ๆ—ฅ', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ'], + 'weekdays_min' => ['ๆ—ฅ', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ไธŠๆ™', 'ไธ‹ๆ™'], + + 'year' => ':count ngien11', + 'y' => ':count ngien11', + 'a_year' => ':count ngien11', + + 'month' => ':count ngieฬt', + 'm' => ':count ngieฬt', + 'a_month' => ':count ngieฬt', + + 'week' => ':count lรฎ-pai', + 'w' => ':count lรฎ-pai', + 'a_week' => ':count lรฎ-pai', + + 'day' => ':count ngit', + 'd' => ':count ngit', + 'a_day' => ':count ngit', + + 'hour' => ':count sแนณฬ€', + 'h' => ':count sแนณฬ€', + 'a_hour' => ':count sแนณฬ€', + + 'minute' => ':count fรปn', + 'min' => ':count fรปn', + 'a_minute' => ':count fรปn', + + 'second' => ':count miรฉu', + 's' => ':count miรฉu', + 'a_second' => ':count miรฉu', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/haw.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/haw.php new file mode 100644 index 0000000000..cdd36861ef --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/haw.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['Ianuali', 'Pepeluali', 'Malaki', 'สปApelila', 'Mei', 'Iune', 'Iulai', 'สปAukake', 'Kepakemapa', 'สปOkakopa', 'Nowemapa', 'Kekemapa'], + 'months_short' => ['Ian.', 'Pep.', 'Mal.', 'สปAp.', 'Mei', 'Iun.', 'Iul.', 'สปAu.', 'Kep.', 'สปOk.', 'Now.', 'Kek.'], + 'weekdays' => ['Lฤpule', 'Poสปakahi', 'Poสปalua', 'Poสปakolu', 'Poสปahฤ', 'Poสปalima', 'Poสปaono'], + 'weekdays_short' => ['LP', 'P1', 'P2', 'P3', 'P4', 'P5', 'P6'], + 'weekdays_min' => ['S', 'M', 'T', 'W', 'T', 'F', 'S'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], + + 'year' => ':count makahiki', + 'y' => ':count makahiki', + 'a_year' => ':count makahiki', + + 'month' => ':count mahina', + 'm' => ':count mahina', + 'a_month' => ':count mahina', + + 'week' => ':count pule', + 'w' => ':count pule', + 'a_week' => ':count pule', + + 'day' => ':count lฤ', + 'd' => ':count lฤ', + 'a_day' => ':count lฤ', + + 'hour' => ':count hola', + 'h' => ':count hola', + 'a_hour' => ':count hola', + + 'minute' => ':count minuke', + 'min' => ':count minuke', + 'a_minute' => ':count minuke', + + 'second' => ':count lua', + 's' => ':count lua', + 'a_second' => ':count lua', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/he.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/he.php new file mode 100644 index 0000000000..c3fb3e9763 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/he.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Daniel Cohen Gindi + * - JD Isaacks + * - Itai Nathaniel + * - GabMic + * - Yaakov Dahan (yakidahan) + */ +return [ + 'year' => 'ืฉื ื”|{2}ืฉื ืชื™ื™ื|:count ืฉื ื™ื', + 'y' => 'ืฉื ื”|:count ืฉื ืณ', + 'month' => 'ื—ื•ื“ืฉ|{2}ื—ื•ื“ืฉื™ื™ื|:count ื—ื•ื“ืฉื™ื', + 'm' => 'ื—ื•ื“ืฉ|:count ื—ื•ืณ', + 'week' => 'ืฉื‘ื•ืข|{2}ืฉื‘ื•ืขื™ื™ื|:count ืฉื‘ื•ืขื•ืช', + 'w' => 'ืฉื‘ื•ืข|:count ืฉื‘ืณ', + 'day' => 'ื™ื•ื|{2}ื™ื•ืžื™ื™ื|:count ื™ืžื™ื', + 'd' => 'ื™ื•ื|:count ื™ืžืณ', + 'hour' => 'ืฉืขื”|{2}ืฉืขืชื™ื™ื|:count ืฉืขื•ืช', + 'h' => 'ืฉืขื”|:count ืฉืขืณ', + 'minute' => 'ื“ืงื”|{2}ืฉืชื™ ื“ืงื•ืช|:count ื“ืงื•ืช', + 'min' => 'ื“ืงื”|:count ื“ืงืณ', + 'second' => 'ืฉื ื™ื™ื”|:count ืฉื ื™ื•ืช', + 'a_second' => 'ื›ืžื” ืฉื ื™ื•ืช|:count ืฉื ื™ื•ืช', + 's' => 'ืฉื ื™ื”|:count ืฉื ืณ', + 'ago' => 'ืœืคื ื™ :time', + 'from_now' => 'ื‘ืขื•ื“ :time ืžืขื›ืฉื™ื•', + 'after' => 'ืื—ืจื™ :time', + 'before' => 'ืœืคื ื™ :time', + 'diff_now' => 'ืขื›ืฉื™ื•', + 'diff_today' => 'ื”ื™ื•ื', + 'diff_today_regexp' => 'ื”ื™ื•ื(?:\\s+ื‘ึพ)?', + 'diff_yesterday' => 'ืืชืžื•ืœ', + 'diff_yesterday_regexp' => 'ืืชืžื•ืœ(?:\\s+ื‘ึพ)?', + 'diff_tomorrow' => 'ืžื—ืจ', + 'diff_tomorrow_regexp' => 'ืžื—ืจ(?:\\s+ื‘ึพ)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [ื‘]MMMM YYYY', + 'LLL' => 'D [ื‘]MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D [ื‘]MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ื”ื™ื•ื ื‘ึพ]LT', + 'nextDay' => '[ืžื—ืจ ื‘ึพ]LT', + 'nextWeek' => 'dddd [ื‘ืฉืขื”] LT', + 'lastDay' => '[ืืชืžื•ืœ ื‘ึพ]LT', + 'lastWeek' => '[ื‘ื™ื•ื] dddd [ื”ืื—ืจื•ืŸ ื‘ืฉืขื”] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour, $minute, $isLower) { + if ($hour < 5) { + return 'ืœืคื ื•ืช ื‘ื•ืงืจ'; + } + if ($hour < 10) { + return 'ื‘ื‘ื•ืงืจ'; + } + if ($hour < 12) { + return $isLower ? 'ืœืคื ื”"ืฆ' : 'ืœืคื ื™ ื”ืฆื”ืจื™ื™ื'; + } + if ($hour < 18) { + return $isLower ? 'ืื—ื”"ืฆ' : 'ืื—ืจื™ ื”ืฆื”ืจื™ื™ื'; + } + + return 'ื‘ืขืจื‘'; + }, + 'months' => ['ื™ื ื•ืืจ', 'ืคื‘ืจื•ืืจ', 'ืžืจืฅ', 'ืืคืจื™ืœ', 'ืžืื™', 'ื™ื•ื ื™', 'ื™ื•ืœื™', 'ืื•ื’ื•ืกื˜', 'ืกืคื˜ืžื‘ืจ', 'ืื•ืงื˜ื•ื‘ืจ', 'ื ื•ื‘ืžื‘ืจ', 'ื“ืฆืžื‘ืจ'], + 'months_short' => ['ื™ื ื•ืณ', 'ืคื‘ืจืณ', 'ืžืจืฅ', 'ืืคืจืณ', 'ืžืื™', 'ื™ื•ื ื™', 'ื™ื•ืœื™', 'ืื•ื’ืณ', 'ืกืคื˜ืณ', 'ืื•ืงืณ', 'ื ื•ื‘ืณ', 'ื“ืฆืžืณ'], + 'weekdays' => ['ืจืืฉื•ืŸ', 'ืฉื ื™', 'ืฉืœื™ืฉื™', 'ืจื‘ื™ืขื™', 'ื—ืžื™ืฉื™', 'ืฉื™ืฉื™', 'ืฉื‘ืช'], + 'weekdays_short' => ['ืืณ', 'ื‘ืณ', 'ื’ืณ', 'ื“ืณ', 'ื”ืณ', 'ื•ืณ', 'ืฉืณ'], + 'weekdays_min' => ['ื', 'ื‘', 'ื’', 'ื“', 'ื”', 'ื•', 'ืฉ'], + 'list' => [', ', ' ื• -'], + 'weekend' => [5, 6], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/he_IL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/he_IL.php new file mode 100644 index 0000000000..14fab3e9b0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/he_IL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/he.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hi.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hi.php new file mode 100644 index 0000000000..70c57a2973 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hi.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - abhimanyu003 + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => 'เคเค• เคตเคฐเฅเคท|:count เคตเคฐเฅเคท', + 'y' => '1 เคตเคฐเฅเคท|:count เคตเคฐเฅเคทเฅ‹เค‚', + 'month' => 'เคเค• เคฎเคนเฅ€เคจเฅ‡|:count เคฎเคนเฅ€เคจเฅ‡', + 'm' => '1 เคฎเคพเคน|:count เคฎเคนเฅ€เคจเฅ‡', + 'week' => '1 เคธเคชเฅเคคเคพเคน|:count เคธเคชเฅเคคเคพเคน', + 'w' => '1 เคธเคชเฅเคคเคพเคน|:count เคธเคชเฅเคคเคพเคน', + 'day' => 'เคเค• เคฆเคฟเคจ|:count เคฆเคฟเคจ', + 'd' => '1 เคฆเคฟเคจ|:count เคฆเคฟเคจเฅ‹เค‚', + 'hour' => 'เคเค• เค˜เค‚เคŸเคพ|:count เค˜เค‚เคŸเฅ‡', + 'h' => '1 เค˜เค‚เคŸเคพ|:count เค˜เค‚เคŸเฅ‡', + 'minute' => 'เคเค• เคฎเคฟเคจเคŸ|:count เคฎเคฟเคจเคŸ', + 'min' => '1 เคฎเคฟเคจเคŸ|:count เคฎเคฟเคจเคŸเฅ‹เค‚', + 'second' => 'เค•เฅเค› เคนเฅ€ เค•เฅเคทเคฃ|:count เคธเฅ‡เค•เค‚เคก', + 's' => '1 เคธเฅ‡เค•เค‚เคก|:count เคธเฅ‡เค•เค‚เคก', + 'ago' => ':time เคชเคนเคฒเฅ‡', + 'from_now' => ':time เคฎเฅ‡เค‚', + 'after' => ':time เค•เฅ‡ เคฌเคพเคฆ', + 'before' => ':time เค•เฅ‡ เคชเคนเคฒเฅ‡', + 'diff_now' => 'เค…เคฌ', + 'diff_today' => 'เค†เคœ', + 'diff_yesterday' => 'เค•เคฒ', + 'diff_tomorrow' => 'เค•เคฒ', + 'formats' => [ + 'LT' => 'A h:mm เคฌเคœเฅ‡', + 'LTS' => 'A h:mm:ss เคฌเคœเฅ‡', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm เคฌเคœเฅ‡', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm เคฌเคœเฅ‡', + ], + 'calendar' => [ + 'sameDay' => '[เค†เคœ] LT', + 'nextDay' => '[เค•เคฒ] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[เค•เคฒ] LT', + 'lastWeek' => '[เคชเคฟเค›เคฒเฅ‡] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'เคฐเคพเคค'; + } + if ($hour < 10) { + return 'เคธเฅเคฌเคน'; + } + if ($hour < 17) { + return 'เคฆเฅ‹เคชเคนเคฐ'; + } + if ($hour < 20) { + return 'เคถเคพเคฎ'; + } + + return 'เคฐเคพเคค'; + }, + 'months' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคผเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅˆเคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเคฎเฅเคฌเคฐ', 'เค…เค•เฅเคŸเฅ‚เคฌเคฐ', 'เคจเคตเคฎเฅเคฌเคฐ', 'เคฆเคฟเคธเคฎเฅเคฌเคฐ'], + 'months_short' => ['เคœเคจ.', 'เคซเคผเคฐ.', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅˆ.', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒ.', 'เค…เค—.', 'เคธเคฟเคค.', 'เค…เค•เฅเคŸเฅ‚.', 'เคจเคต.', 'เคฆเคฟเคธ.'], + 'weekdays' => ['เคฐเคตเคฟเคตเคพเคฐ', 'เคธเฅ‹เคฎเคตเคพเคฐ', 'เคฎเค‚เค—เคฒเคตเคพเคฐ', 'เคฌเฅเคงเคตเคพเคฐ', 'เค—เฅเคฐเฅ‚เคตเคพเคฐ', 'เคถเฅเค•เฅเคฐเคตเคพเคฐ', 'เคถเคจเคฟเคตเคพเคฐ'], + 'weekdays_short' => ['เคฐเคตเคฟ', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคง', 'เค—เฅเคฐเฅ‚', 'เคถเฅเค•เฅเคฐ', 'เคถเคจเคฟ'], + 'weekdays_min' => ['เคฐ', 'เคธเฅ‹', 'เคฎเค‚', 'เคฌเฅ', 'เค—เฅ', 'เคถเฅ', 'เคถ'], + 'list' => [', ', ' เค”เคฐ '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hi_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hi_IN.php new file mode 100644 index 0000000000..749dd97c9f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hi_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/hi.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hif.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hif.php new file mode 100644 index 0000000000..65791dd434 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hif.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hif_FJ.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hif_FJ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hif_FJ.php new file mode 100644 index 0000000000..30ad5e74c8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hif_FJ.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Ravivar', 'Somvar', 'Mangalvar', 'Budhvar', 'Guruvar', 'Shukravar', 'Shanivar'], + 'weekdays_short' => ['Ravi', 'Som', 'Mangal', 'Budh', 'Guru', 'Shukra', 'Shani'], + 'weekdays_min' => ['Ravi', 'Som', 'Mangal', 'Budh', 'Guru', 'Shukra', 'Shani'], + 'meridiem' => ['Purvahan', 'Aparaahna'], + + 'hour' => ':count minit', // less reliable + 'h' => ':count minit', // less reliable + 'a_hour' => ':count minit', // less reliable + + 'year' => ':count saal', + 'y' => ':count saal', + 'a_year' => ':count saal', + + 'month' => ':count Mahina', + 'm' => ':count Mahina', + 'a_month' => ':count Mahina', + + 'week' => ':count Hafta', + 'w' => ':count Hafta', + 'a_week' => ':count Hafta', + + 'day' => ':count Din', + 'd' => ':count Din', + 'a_day' => ':count Din', + + 'minute' => ':count Minit', + 'min' => ':count Minit', + 'a_minute' => ':count Minit', + + 'second' => ':count Second', + 's' => ':count Second', + 'a_second' => ':count Second', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hne.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hne.php new file mode 100644 index 0000000000..4bcb05c73c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hne.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hne_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hne_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hne_IN.php new file mode 100644 index 0000000000..a5ca758b65 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hne_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเคฐเฅ‡เคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเคฎเคฌเคฐ', 'เค…เค•เคŸเฅ‚เคฌเคฐ', 'เคจเคตเคฎเคฌเคฐ', 'เคฆเคฟเคธเคฎเคฌเคฐ'], + 'months_short' => ['เคœเคจ', 'เคซเคฐ', 'เคฎเคพเคฐเฅเคš', 'เค…เคช', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพ', 'เค…เค—', 'เคธเคฟเคค', 'เค…เค•เคŸเฅ‚', 'เคจเคต', 'เคฆเคฟเคธ'], + 'weekdays' => ['เค‡เคคเคตเคพเคฐ', 'เคธเฅ‹เคฎเคตเคพเคฐ', 'เคฎเค‚เค—เคฒเคตเคพเคฐ', 'เคฌเฅเคงเคตเคพเคฐ', 'เคฌเคฟเคฐเคธเคชเคค', 'เคธเฅเค•เคฐเคตเคพเคฐ', 'เคธเคจเคฟเคตเคพเคฐ'], + 'weekdays_short' => ['เค‡เคค', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—', 'เคฌเฅเคง', 'เคฌเคฟเคฐ', 'เคธเฅเค•', 'เคธเคจเคฟ'], + 'weekdays_min' => ['เค‡เคค', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—', 'เคฌเฅเคง', 'เคฌเคฟเคฐ', 'เคธเฅเค•', 'เคธเคจเคฟ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เคฌเคฟเคนเคฟเคจเคฟเคฏเคพเค', 'เคฎเค‚เคเคจเคฟเคฏเคพเค'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr.php new file mode 100644 index 0000000000..cfd85fd408 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Franรงois B + * - Tim Fish + * - shaishavgandhi05 + * - Serhan Apaydฤฑn + * - JD Isaacks + * - tomhorvat + * - Josh Soref + * - Franรงois B + * - shaishavgandhi05 + * - Serhan Apaydฤฑn + * - JD Isaacks + * - tomhorvat + * - Stjepan Majdak + * - Vanja Retkovac (vr00) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count godinu|:count godine|:count godina', + 'y' => ':count god.|:count god.|:count god.', + 'month' => ':count mjesec|:count mjeseca|:count mjeseci', + 'm' => ':count mj.|:count mj.|:count mj.', + 'week' => ':count tjedan|:count tjedna|:count tjedana', + 'w' => ':count tj.|:count tj.|:count tj.', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count d.|:count d.|:count d.', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count sat|:count sata|:count sati', + 'minute' => ':count minutu|:count minute|:count minuta', + 'min' => ':count min.|:count min.|:count min.', + 'second' => ':count sekundu|:count sekunde|:count sekundi', + 'a_second' => 'nekoliko sekundi|:count sekunde|:count sekundi', + 's' => ':count sek.|:count sek.|:count sek.', + 'ago' => 'prije :time', + 'from_now' => 'za :time', + 'after' => ':time poslije', + 'before' => ':time prije', + 'diff_now' => 'sad', + 'diff_today' => 'danas', + 'diff_today_regexp' => 'danas(?:\\s+u)?', + 'diff_yesterday' => 'juฤer', + 'diff_yesterday_regexp' => 'juฤer(?:\\s+u)?', + 'diff_tomorrow' => 'sutra', + 'diff_tomorrow_regexp' => 'sutra(?:\\s+u)?', + 'diff_before_yesterday' => 'prekjuฤer', + 'diff_after_tomorrow' => 'prekosutra', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D. M. YYYY.', + 'LL' => 'D. MMMM YYYY.', + 'LLL' => 'D. MMMM YYYY. H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY. H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danas u] LT', + 'nextDay' => '[sutra u] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastDay' => '[juฤer u] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + case 3: + return '[proลกlu] dddd [u] LT'; + case 6: + return '[proลกle] [subote] [u] LT'; + default: + return '[proลกli] dddd [u] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['sijeฤnja', 'veljaฤe', 'oลพujka', 'travnja', 'svibnja', 'lipnja', 'srpnja', 'kolovoza', 'rujna', 'listopada', 'studenoga', 'prosinca'], + 'months_standalone' => ['sijeฤanj', 'veljaฤa', 'oลพujak', 'travanj', 'svibanj', 'lipanj', 'srpanj', 'kolovoz', 'rujan', 'listopad', 'studeni', 'prosinac'], + 'months_short' => ['sij.', 'velj.', 'oลพu.', 'tra.', 'svi.', 'lip.', 'srp.', 'kol.', 'ruj.', 'lis.', 'stu.', 'pro.'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['nedjelju', 'ponedjeljak', 'utorak', 'srijedu', 'ฤetvrtak', 'petak', 'subotu'], + 'weekdays_standalone' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'ฤetvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sri.', 'ฤet.', 'pet.', 'sub.'], + 'weekdays_min' => ['ne', 'po', 'ut', 'sr', 'ฤe', 'pe', 'su'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr_BA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr_BA.php new file mode 100644 index 0000000000..7763a4587a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr_BA.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - DarkoDevelop + */ +return array_replace_recursive(require __DIR__.'/hr.php', [ + 'weekdays' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'ฤetvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned', 'pon', 'uto', 'sri', 'ฤet', 'pet', 'sub'], + 'weekdays_min' => ['ned', 'pon', 'uto', 'sri', 'ฤet', 'pet', 'sub'], + 'months' => ['sijeฤnja', 'veljaฤe', 'oลพujka', 'travnja', 'svibnja', 'lipnja', 'srpnja', 'kolovoza', 'rujna', 'listopada', 'studenoga', 'prosinca'], + 'months_short' => ['sij', 'velj', 'oลพu', 'tra', 'svi', 'lip', 'srp', 'kol', 'ruj', 'lis', 'stu', 'pro'], + 'months_standalone' => ['sijeฤanj', 'veljaฤa', 'oลพujak', 'travanj', 'svibanj', 'lipanj', 'srpanj', 'kolovoz', 'rujan', 'listopad', 'studeni', 'prosinac'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D. M. yy.', + 'LL' => 'D. MMM YYYY.', + 'LLL' => 'D. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY. HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr_HR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr_HR.php new file mode 100644 index 0000000000..db74d8c70b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr_HR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/hr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hsb.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hsb.php new file mode 100644 index 0000000000..3537b8ba25 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hsb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hsb_DE.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hsb_DE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hsb_DE.php new file mode 100644 index 0000000000..6ba227161b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hsb_DE.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Information from Michael Wolf Andrzej Krzysztofowicz ankry@mif.pg.gda.pl + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'DD. MMMM YYYY', + 'LLL' => 'DD. MMMM, HH:mm [hodลบ.]', + 'LLLL' => 'dddd, DD. MMMM YYYY, HH:mm [hodลบ.]', + ], + 'months' => ['januara', 'februara', 'mฤ›rca', 'apryla', 'meje', 'junija', 'julija', 'awgusta', 'septembra', 'oktobra', 'nowembra', 'decembra'], + 'months_short' => ['Jan', 'Feb', 'Mฤ›r', 'Apr', 'Mej', 'Jun', 'Jul', 'Awg', 'Sep', 'Okt', 'Now', 'Dec'], + 'weekdays' => ['Njedลบela', 'Pรณndลบela', 'Wutora', 'Srjeda', 'ล tvรณrtk', 'Pjatk', 'Sobota'], + 'weekdays_short' => ['Nj', 'Pรณ', 'Wu', 'Sr', 'ล t', 'Pj', 'So'], + 'weekdays_min' => ['Nj', 'Pรณ', 'Wu', 'Sr', 'ล t', 'Pj', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count lฤ›to', + 'y' => ':count lฤ›to', + 'a_year' => ':count lฤ›to', + + 'month' => ':count mฤ›sac', + 'm' => ':count mฤ›sac', + 'a_month' => ':count mฤ›sac', + + 'week' => ':count tydลบeล„', + 'w' => ':count tydลบeล„', + 'a_week' => ':count tydลบeล„', + + 'day' => ':count dลบeล„', + 'd' => ':count dลบeล„', + 'a_day' => ':count dลบeล„', + + 'hour' => ':count hodลบina', + 'h' => ':count hodลบina', + 'a_hour' => ':count hodลบina', + + 'minute' => ':count chwila', + 'min' => ':count chwila', + 'a_minute' => ':count chwila', + + 'second' => ':count druhi', + 's' => ':count druhi', + 'a_second' => ':count druhi', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ht.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ht.php new file mode 100644 index 0000000000..ebd12ad11c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ht.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ht_HT.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ht_HT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ht_HT.php new file mode 100644 index 0000000000..139b813b26 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ht_HT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sugar Labs // OLPC sugarlabs.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['janvye', 'fevriye', 'mas', 'avril', 'me', 'jen', 'jiyรจ', 'out', 'septanm', 'oktรฒb', 'novanm', 'desanm'], + 'months_short' => ['jan', 'fev', 'mas', 'avr', 'me', 'jen', 'jiy', 'out', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['dimanch', 'lendi', 'madi', 'mรจkredi', 'jedi', 'vandredi', 'samdi'], + 'weekdays_short' => ['dim', 'len', 'mad', 'mรจk', 'jed', 'van', 'sam'], + 'weekdays_min' => ['dim', 'len', 'mad', 'mรจk', 'jed', 'van', 'sam'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count lane', + 'y' => ':count lane', + 'a_year' => ':count lane', + + 'month' => 'mwa :count', + 'm' => 'mwa :count', + 'a_month' => 'mwa :count', + + 'week' => 'semรจn :count', + 'w' => 'semรจn :count', + 'a_week' => 'semรจn :count', + + 'day' => ':count jou', + 'd' => ':count jou', + 'a_day' => ':count jou', + + 'hour' => ':count lรจ', + 'h' => ':count lรจ', + 'a_hour' => ':count lรจ', + + 'minute' => ':count minit', + 'min' => ':count minit', + 'a_minute' => ':count minit', + + 'second' => ':count segonn', + 's' => ':count segonn', + 'a_second' => ':count segonn', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hu.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hu.php new file mode 100644 index 0000000000..b7583eecd7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hu.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Adam Brunner + * - Brett Johnson + * - balping + */ + +use Carbon\CarbonInterface; + +$huWeekEndings = ['vasรกrnap', 'hรฉtfล‘n', 'kedden', 'szerdรกn', 'csรผtรถrtรถkรถn', 'pรฉnteken', 'szombaton']; + +return [ + 'year' => ':count รฉv', + 'y' => ':count รฉv', + 'month' => ':count hรณnap', + 'm' => ':count hรณnap', + 'week' => ':count hรฉt', + 'w' => ':count hรฉt', + 'day' => ':count nap', + 'd' => ':count nap', + 'hour' => ':count รณra', + 'h' => ':count รณra', + 'minute' => ':count perc', + 'min' => ':count perc', + 'second' => ':count mรกsodperc', + 's' => ':count mรกsodperc', + 'ago' => ':time', + 'from_now' => ':time mรบlva', + 'after' => ':time kรฉsล‘bb', + 'before' => ':time korรกbban', + 'year_ago' => ':count รฉve', + 'y_ago' => ':count รฉve', + 'month_ago' => ':count hรณnapja', + 'm_ago' => ':count hรณnapja', + 'week_ago' => ':count hete', + 'w_ago' => ':count hete', + 'day_ago' => ':count napja', + 'd_ago' => ':count napja', + 'hour_ago' => ':count รณrรกja', + 'h_ago' => ':count รณrรกja', + 'minute_ago' => ':count perce', + 'min_ago' => ':count perce', + 'second_ago' => ':count mรกsodperce', + 's_ago' => ':count mรกsodperce', + 'year_after' => ':count รฉvvel', + 'y_after' => ':count รฉvvel', + 'month_after' => ':count hรณnappal', + 'm_after' => ':count hรณnappal', + 'week_after' => ':count hรฉttel', + 'w_after' => ':count hรฉttel', + 'day_after' => ':count nappal', + 'd_after' => ':count nappal', + 'hour_after' => ':count รณrรกval', + 'h_after' => ':count รณrรกval', + 'minute_after' => ':count perccel', + 'min_after' => ':count perccel', + 'second_after' => ':count mรกsodperccel', + 's_after' => ':count mรกsodperccel', + 'year_before' => ':count รฉvvel', + 'y_before' => ':count รฉvvel', + 'month_before' => ':count hรณnappal', + 'm_before' => ':count hรณnappal', + 'week_before' => ':count hรฉttel', + 'w_before' => ':count hรฉttel', + 'day_before' => ':count nappal', + 'd_before' => ':count nappal', + 'hour_before' => ':count รณrรกval', + 'h_before' => ':count รณrรกval', + 'minute_before' => ':count perccel', + 'min_before' => ':count perccel', + 'second_before' => ':count mรกsodperccel', + 's_before' => ':count mรกsodperccel', + 'months' => ['januรกr', 'februรกr', 'mรกrcius', 'รกprilis', 'mรกjus', 'jรบnius', 'jรบlius', 'augusztus', 'szeptember', 'oktรณber', 'november', 'december'], + 'months_short' => ['jan.', 'febr.', 'mรกrc.', 'รกpr.', 'mรกj.', 'jรบn.', 'jรบl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['vasรกrnap', 'hรฉtfล‘', 'kedd', 'szerda', 'csรผtรถrtรถk', 'pรฉntek', 'szombat'], + 'weekdays_short' => ['vas', 'hรฉt', 'kedd', 'sze', 'csรผt', 'pรฉn', 'szo'], + 'weekdays_min' => ['v', 'h', 'k', 'sze', 'cs', 'p', 'sz'], + 'ordinal' => ':number.', + 'diff_now' => 'most', + 'diff_today' => 'ma', + 'diff_yesterday' => 'tegnap', + 'diff_tomorrow' => 'holnap', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'YYYY.MM.DD.', + 'LL' => 'YYYY. MMMM D.', + 'LLL' => 'YYYY. MMMM D. H:mm', + 'LLLL' => 'YYYY. MMMM D., dddd H:mm', + ], + 'calendar' => [ + 'sameDay' => '[ma] LT[-kor]', + 'nextDay' => '[holnap] LT[-kor]', + 'nextWeek' => function (CarbonInterface $date) use ($huWeekEndings) { + return '['.$huWeekEndings[$date->dayOfWeek].'] LT[-kor]'; + }, + 'lastDay' => '[tegnap] LT[-kor]', + 'lastWeek' => function (CarbonInterface $date) use ($huWeekEndings) { + return '[mรบlt '.$huWeekEndings[$date->dayOfWeek].'] LT[-kor]'; + }, + 'sameElse' => 'L', + ], + 'meridiem' => ['DE', 'DU'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' รฉs '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hu_HU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hu_HU.php new file mode 100644 index 0000000000..b1c4854179 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hu_HU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/hu.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hy.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hy.php new file mode 100644 index 0000000000..8b129947d2 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hy.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - mhamlet + */ +return [ + 'year' => ':count ีฟีกึ€ีซ', + 'a_year' => 'ีฟีกึ€ีซ|:count ีฟีกึ€ีซ', + 'y' => ':countีฟ', + 'month' => ':count ีกีดีซีฝ', + 'a_month' => 'ีกีดีซีฝ|:count ีกีดีซีฝ', + 'm' => ':countีกีด', + 'week' => ':count ีทีกีขีกีฉ', + 'a_week' => 'ีทีกีขีกีฉ|:count ีทีกีขีกีฉ', + 'w' => ':countีท', + 'day' => ':count ึ…ึ€', + 'a_day' => 'ึ…ึ€|:count ึ…ึ€', + 'd' => ':countึ…ึ€', + 'hour' => ':count ีชีกีด', + 'a_hour' => 'ีชีกีด|:count ีชีกีด', + 'h' => ':countีช', + 'minute' => ':count ึ€ีธีบีฅ', + 'a_minute' => 'ึ€ีธีบีฅ|:count ึ€ีธีบีฅ', + 'min' => ':countึ€', + 'second' => ':count ีพีกีตึ€ีฏีตีกีถ', + 'a_second' => 'ีดีซ ึ„ีกีถีซ ีพีกีตึ€ีฏีตีกีถ|:count ีพีกีตึ€ีฏีตีกีถ', + 's' => ':countีพึ€ีฏ', + 'ago' => ':time ีกีผีกีป', + 'from_now' => ':timeีซึ', + 'after' => ':time ีฐีฅีฟีธ', + 'before' => ':time ีกีผีกีป', + 'diff_now' => 'ีฐีซีดีก', + 'diff_today' => 'ีกีตีฝึ…ึ€', + 'diff_yesterday' => 'ีฅึ€ีฅีฏ', + 'diff_tomorrow' => 'ีพีกีฒีจ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY ีฉ.', + 'LLL' => 'D MMMM YYYY ีฉ., HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY ีฉ., HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ีกีตีฝึ…ึ€] LT', + 'nextDay' => '[ีพีกีฒีจ] LT', + 'nextWeek' => 'dddd [ึ…ึ€ีจ ีชีกีดีจ] LT', + 'lastDay' => '[ีฅึ€ีฅีฏ] LT', + 'lastWeek' => '[ีกีถึีกีฎ] dddd [ึ…ึ€ีจ ีชีกีดีจ] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'DDD': + case 'w': + case 'W': + case 'DDDo': + return $number.($number === 1 ? '-ีซีถ' : '-ึ€ีค'); + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ีฃีซีทีฅึ€ีพีก'; + } + if ($hour < 12) { + return 'ีกีผีกีพีธีฟีพีก'; + } + if ($hour < 17) { + return 'ึีฅึ€ีฅีฏีพีก'; + } + + return 'ีฅึ€ีฅีฏีธีตีกีถ'; + }, + 'months' => ['ีฐีธึ‚ีถีพีกึ€ีซ', 'ึƒีฅีฟึ€ีพีกึ€ีซ', 'ีดีกึ€ีฟีซ', 'ีกีบึ€ีซีฌีซ', 'ีดีกีตีซีฝีซ', 'ีฐีธึ‚ีถีซีฝีซ', 'ีฐีธึ‚ีฌีซีฝีซ', 'ึ…ีฃีธีฝีฟีธีฝีซ', 'ีฝีฅีบีฟีฅีดีขีฅึ€ีซ', 'ีฐีธีฏีฟีฅีดีขีฅึ€ีซ', 'ีถีธีตีฅีดีขีฅึ€ีซ', 'ีคีฅีฏีฟีฅีดีขีฅึ€ีซ'], + 'months_standalone' => ['ีฐีธึ‚ีถีพีกึ€', 'ึƒีฅีฟึ€ีพีกึ€', 'ีดีกึ€ีฟ', 'ีกีบึ€ีซีฌ', 'ีดีกีตีซีฝ', 'ีฐีธึ‚ีถีซีฝ', 'ีฐีธึ‚ีฌีซีฝ', 'ึ…ีฃีธีฝีฟีธีฝ', 'ีฝีฅีบีฟีฅีดีขีฅึ€', 'ีฐีธีฏีฟีฅีดีขีฅึ€', 'ีถีธีตีฅีดีขีฅึ€', 'ีคีฅีฏีฟีฅีดีขีฅึ€'], + 'months_short' => ['ีฐีถีพ', 'ึƒีฟึ€', 'ีดึ€ีฟ', 'ีกีบึ€', 'ีดีตีฝ', 'ีฐีถีฝ', 'ีฐีฌีฝ', 'ึ…ีฃีฝ', 'ีฝีบีฟ', 'ีฐีฏีฟ', 'ีถีดีข', 'ีคีฏีฟ'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['ีฏีซึ€ีกีฏีซ', 'ีฅึ€ีฏีธึ‚ีทีกีขีฉีซ', 'ีฅึ€ีฅึ„ีทีกีขีฉีซ', 'ีนีธึ€ีฅึ„ีทีกีขีฉีซ', 'ีฐีซีถีฃีทีกีขีฉีซ', 'ีธึ‚ึ€ีขีกีฉ', 'ีทีกีขีกีฉ'], + 'weekdays_short' => ['ีฏึ€ีฏ', 'ีฅึ€ีฏ', 'ีฅึ€ึ„', 'ีนึ€ึ„', 'ีฐีถีฃ', 'ีธึ‚ึ€ีข', 'ีทีขีฉ'], + 'weekdays_min' => ['ีฏึ€ีฏ', 'ีฅึ€ีฏ', 'ีฅึ€ึ„', 'ีนึ€ึ„', 'ีฐีถีฃ', 'ีธึ‚ึ€ีข', 'ีทีขีฉ'], + 'list' => [', ', ' ีฅึ‚ '], + 'first_day_of_week' => 1, +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hy_AM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hy_AM.php new file mode 100644 index 0000000000..4587df56c5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hy_AM.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Franรงois B + * - Tim Fish + * - Serhan Apaydฤฑn + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/hy.php', [ + 'from_now' => ':time ีฐีฅีฟีธ', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/i18n.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/i18n.php new file mode 100644 index 0000000000..e65449b8d2 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/i18n.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'months' => ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'], + 'months_short' => ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'], + 'weekdays' => ['1', '2', '3', '4', '5', '6', '7'], + 'weekdays_short' => ['1', '2', '3', '4', '5', '6', '7'], + 'weekdays_min' => ['1', '2', '3', '4', '5', '6', '7'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 4, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ia.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ia.php new file mode 100644 index 0000000000..0a0d5e6115 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ia.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ia_FR.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ia_FR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ia_FR.php new file mode 100644 index 0000000000..de4b2fa09e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ia_FR.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Fedora Project Nik Kalach nikka@fedoraproject.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['januario', 'februario', 'martio', 'april', 'maio', 'junio', 'julio', 'augusto', 'septembre', 'octobre', 'novembre', 'decembre'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'], + 'weekdays' => ['dominica', 'lunedi', 'martedi', 'mercuridi', 'jovedi', 'venerdi', 'sabbato'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mer', 'jov', 'ven', 'sab'], + 'weekdays_min' => ['dom', 'lun', 'mar', 'mer', 'jov', 'ven', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => 'anno :count', + 'y' => 'anno :count', + 'a_year' => 'anno :count', + + 'month' => ':count mense', + 'm' => ':count mense', + 'a_month' => ':count mense', + + 'week' => ':count septimana', + 'w' => ':count septimana', + 'a_week' => ':count septimana', + + 'day' => ':count die', + 'd' => ':count die', + 'a_day' => ':count die', + + 'hour' => ':count hora', + 'h' => ':count hora', + 'a_hour' => ':count hora', + + 'minute' => ':count minuscule', + 'min' => ':count minuscule', + 'a_minute' => ':count minuscule', + + 'second' => ':count secunda', + 's' => ':count secunda', + 'a_second' => ':count secunda', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/id.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/id.php new file mode 100644 index 0000000000..afaf78f298 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/id.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - du + * - JD Isaacks + * - Nafies Luthfi + * - Raymundus Jati Primanda (mundusjp) + * - diankur313 + * - a-wip0 + */ +return [ + 'year' => ':count tahun', + 'a_year' => '{1}setahun|]1,Inf[:count tahun', + 'y' => ':countthn', + 'month' => ':count bulan', + 'a_month' => '{1}sebulan|]1,Inf[:count bulan', + 'm' => ':countbln', + 'week' => ':count minggu', + 'a_week' => '{1}seminggu|]1,Inf[:count minggu', + 'w' => ':countmgg', + 'day' => ':count hari', + 'a_day' => '{1}sehari|]1,Inf[:count hari', + 'd' => ':counthr', + 'hour' => ':count jam', + 'a_hour' => '{1}sejam|]1,Inf[:count jam', + 'h' => ':countj', + 'minute' => ':count menit', + 'a_minute' => '{1}semenit|]1,Inf[:count menit', + 'min' => ':countmnt', + 'second' => ':count detik', + 'a_second' => '{1}beberapa detik|]1,Inf[:count detik', + 's' => ':countdt', + 'ago' => ':time yang lalu', + 'from_now' => ':time dari sekarang', + 'after' => ':time setelahnya', + 'before' => ':time sebelumnya', + 'diff_now' => 'sekarang', + 'diff_today' => 'Hari', + 'diff_today_regexp' => 'Hari(?:\\s+ini)?(?:\\s+pukul)?', + 'diff_yesterday' => 'kemarin', + 'diff_yesterday_regexp' => 'Kemarin(?:\\s+pukul)?', + 'diff_tomorrow' => 'besok', + 'diff_tomorrow_regexp' => 'Besok(?:\\s+pukul)?', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [pukul] HH.mm', + 'LLLL' => 'dddd, D MMMM YYYY [pukul] HH.mm', + ], + 'calendar' => [ + 'sameDay' => '[Hari ini pukul] LT', + 'nextDay' => '[Besok pukul] LT', + 'nextWeek' => 'dddd [pukul] LT', + 'lastDay' => '[Kemarin pukul] LT', + 'lastWeek' => 'dddd [lalu pukul] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 11) { + return 'pagi'; + } + if ($hour < 15) { + return 'siang'; + } + if ($hour < 19) { + return 'sore'; + } + + return 'malam'; + }, + 'months' => ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agt', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'], + 'weekdays_short' => ['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab'], + 'weekdays_min' => ['Mg', 'Sn', 'Sl', 'Rb', 'Km', 'Jm', 'Sb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' dan '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/id_ID.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/id_ID.php new file mode 100644 index 0000000000..d5953a14eb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/id_ID.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/id.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ig.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ig.php new file mode 100644 index 0000000000..de51e9cc47 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ig.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ig_NG.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ig_NG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ig_NG.php new file mode 100644 index 0000000000..0034e35d8e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ig_NG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Jenแปฅwarแป‹', 'Febrแปฅwarแป‹', 'Maachแป‹', 'Eprel', 'Mee', 'Juun', 'Julaแป‹', 'แปŒgแปแปst', 'Septemba', 'แปŒktoba', 'Novemba', 'Disemba'], + 'months_short' => ['Jen', 'Feb', 'Maa', 'Epr', 'Mee', 'Juu', 'Jul', 'แปŒgแป', 'Sep', 'แปŒkt', 'Nov', 'Dis'], + 'weekdays' => ['sแปnde', 'mแปnde', 'tuzde', 'wenzde', 'tแปsde', 'fraแป‹de', 'satแปde'], + 'weekdays_short' => ['sแปn', 'mแปn', 'tuz', 'wen', 'tแปs', 'fra', 'sat'], + 'weekdays_min' => ['sแปn', 'mแปn', 'tuz', 'wen', 'tแปs', 'fra', 'sat'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => 'afo :count', + 'y' => 'afo :count', + 'a_year' => 'afo :count', + + 'month' => 'รถnwa :count', + 'm' => 'รถnwa :count', + 'a_month' => 'รถnwa :count', + + 'week' => 'izu :count', + 'w' => 'izu :count', + 'a_week' => 'izu :count', + + 'day' => 'แปฅbแปchแป‹ :count', + 'd' => 'แปฅbแปchแป‹ :count', + 'a_day' => 'แปฅbแปchแป‹ :count', + + 'hour' => 'awa :count', + 'h' => 'awa :count', + 'a_hour' => 'awa :count', + + 'minute' => 'minit :count', + 'min' => 'minit :count', + 'a_minute' => 'minit :count', + + 'second' => 'sekแปnd :count', + 's' => 'sekแปnd :count', + 'a_second' => 'sekแปnd :count', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ii.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ii.php new file mode 100644 index 0000000000..a4246c2776 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ii.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['๊Žธ๊„‘', '๊ฏ๊‹’'], + 'weekdays' => ['๊‘ญ๊†๊‘', '๊†๊Š‚๊‹', '๊†๊Š‚๊‘', '๊†๊Š‚๊Œ•', '๊†๊Š‚๊‡–', '๊†๊Š‚๊‰ฌ', '๊†๊Š‚๊ƒ˜'], + 'weekdays_short' => ['๊‘ญ๊†', '๊†๊‹', '๊†๊‘', '๊†๊Œ•', '๊†๊‡–', '๊†๊‰ฌ', '๊†๊ƒ˜'], + 'weekdays_min' => ['๊‘ญ๊†', '๊†๊‹', '๊†๊‘', '๊†๊Œ•', '๊†๊‡–', '๊†๊‰ฌ', '๊†๊ƒ˜'], + 'months' => null, + 'months_short' => ['๊‹๊†ช', '๊‘๊†ช', '๊Œ•๊†ช', '๊‡–๊†ช', '๊‰ฌ๊†ช', '๊ƒ˜๊†ช', '๊ƒ๊†ช', '๊‰†๊†ช', '๊ˆฌ๊†ช', '๊Šฐ๊†ช', '๊Šฐ๊Šช๊†ช', '๊Šฐ๊‘‹๊†ช'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D h:mm a', + 'LLLL' => 'YYYY MMMM D, dddd h:mm a', + ], + + 'year' => ':count ๊’‰', // less reliable + 'y' => ':count ๊’‰', // less reliable + 'a_year' => ':count ๊’‰', // less reliable + + 'month' => ':count ๊†ช', + 'm' => ':count ๊†ช', + 'a_month' => ':count ๊†ช', + + 'week' => ':count ๊ƒ', // less reliable + 'w' => ':count ๊ƒ', // less reliable + 'a_week' => ':count ๊ƒ', // less reliable + + 'day' => ':count ๊œ', // less reliable + 'd' => ':count ๊œ', // less reliable + 'a_day' => ':count ๊œ', // less reliable + + 'hour' => ':count ๊„ฎ๊ˆ‰', + 'h' => ':count ๊„ฎ๊ˆ‰', + 'a_hour' => ':count ๊„ฎ๊ˆ‰', + + 'minute' => ':count ๊€„๊Šญ', // less reliable + 'min' => ':count ๊€„๊Šญ', // less reliable + 'a_minute' => ':count ๊€„๊Šญ', // less reliable + + 'second' => ':count ๊‡…', // less reliable + 's' => ':count ๊‡…', // less reliable + 'a_second' => ':count ๊‡…', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ik.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ik.php new file mode 100644 index 0000000000..7a13aa2db1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ik.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ik_CA.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ik_CA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ik_CA.php new file mode 100644 index 0000000000..bb2a109b94 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ik_CA.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Siqiรฑรฑaatchiaq', 'Siqiรฑรฑaasrugruk', 'Paniqsiqsiivik', 'Qilฤกich Tatqiat', 'Suppivik', 'Iฤกรฑivik', 'Itchavik', 'Tiรฑรฑivik', 'Amiฤกaiqsivik', 'Sikkuvik', 'Nippivik', 'Siqiรฑฤกiแธทaq'], + 'months_short' => ['Sรฑt', 'Sรฑs', 'Pan', 'Qil', 'Sup', 'Iฤกรฑ', 'Itc', 'Tiรฑ', 'Ami', 'Sik', 'Nip', 'Siq'], + 'weekdays' => ['Minฤกuiqsioiq', 'Savaล‚ล‚iq', 'Ilaqtchiioiq', 'Qitchiioiq', 'Sisamiioiq', 'Tallimmiioiq', 'Maqinฤกuoiq'], + 'weekdays_short' => ['Min', 'Sav', 'Ila', 'Qit', 'Sis', 'Tal', 'Maq'], + 'weekdays_min' => ['Min', 'Sav', 'Ila', 'Qit', 'Sis', 'Tal', 'Maq'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ukiuq', + 'y' => ':count ukiuq', + 'a_year' => ':count ukiuq', + + 'month' => ':count Tatqiat', + 'm' => ':count Tatqiat', + 'a_month' => ':count Tatqiat', + + 'week' => ':count tatqiat', // less reliable + 'w' => ':count tatqiat', // less reliable + 'a_week' => ':count tatqiat', // less reliable + + 'day' => ':count siqiรฑiq', // less reliable + 'd' => ':count siqiรฑiq', // less reliable + 'a_day' => ':count siqiรฑiq', // less reliable + + 'hour' => ':count Siแธทa', // less reliable + 'h' => ':count Siแธทa', // less reliable + 'a_hour' => ':count Siแธทa', // less reliable + + 'second' => ':count iฤกรฑiq', // less reliable + 's' => ':count iฤกรฑiq', // less reliable + 'a_second' => ':count iฤกรฑiq', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/in.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/in.php new file mode 100644 index 0000000000..d5953a14eb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/in.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/id.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/is.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/is.php new file mode 100644 index 0000000000..9990168c2d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/is.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kristjรกn Ingi Geirsson + */ +return [ + 'year' => '1 รกr|:count รกr', + 'y' => '1 รกr|:count รกr', + 'month' => '1 mรกnuรฐur|:count mรกnuรฐir', + 'm' => '1 mรกnuรฐur|:count mรกnuรฐir', + 'week' => '1 vika|:count vikur', + 'w' => '1 vika|:count vikur', + 'day' => '1 dagur|:count dagar', + 'd' => '1 dagur|:count dagar', + 'hour' => '1 klukkutรญmi|:count klukkutรญmar', + 'h' => '1 klukkutรญmi|:count klukkutรญmar', + 'minute' => '1 mรญnรบta|:count mรญnรบtur', + 'min' => '1 mรญnรบta|:count mรญnรบtur', + 'second' => '1 sekรบnda|:count sekรบndur', + 's' => '1 sekรบnda|:count sekรบndur', + 'ago' => ':time sรญรฐan', + 'from_now' => ':time sรญรฐan', + 'after' => ':time eftir', + 'before' => ':time fyrir', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], + 'meridiem' => ['fh', 'eh'], + 'diff_now' => 'nรบna', + 'diff_yesterday' => 'รญ gรฆr', + 'diff_tomorrow' => 'รก morgun', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM [kl.] HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY [kl.] HH:mm', + ], + 'weekdays' => ['sunnudaginn', 'mรกnudaginn', 'รพriรฐjudaginn', 'miรฐvikudaginn', 'fimmtudaginn', 'fรถstudaginn', 'laugardaginn'], + 'weekdays_short' => ['sun', 'mรกn', 'รพri', 'miรฐ', 'fim', 'fรถs', 'lau'], + 'weekdays_min' => ['sun', 'mรกn', 'รพri', 'miรฐ', 'fim', 'fรถs', 'lau'], + 'months' => ['janรบar', 'febrรบar', 'mars', 'aprรญl', 'maรญ', 'jรบnรญ', 'jรบlรญ', 'รกgรบst', 'september', 'oktรณber', 'nรณvember', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maรญ', 'jรบn', 'jรบl', 'รกgรบ', 'sep', 'okt', 'nรณv', 'des'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/is_IS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/is_IS.php new file mode 100644 index 0000000000..4d35c44899 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/is_IS.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/is.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it.php new file mode 100644 index 0000000000..49875d7efe --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ash + * - Franรงois B + * - Marco Perrando + * - Massimiliano Caniparoli + * - JD Isaacks + * - Andrea Martini + * - Francesco Marasco + * - Tizianoz93 + * - Davide Casiraghi (davide-casiraghi) + * - Pete Scopes (pdscopes) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count anno|:count anni', + 'a_year' => 'un anno|:count anni', + 'y' => ':count anno|:count anni', + 'month' => ':count mese|:count mesi', + 'a_month' => 'un mese|:count mesi', + 'm' => ':count mese|:count mesi', + 'week' => ':count settimana|:count settimane', + 'a_week' => 'una settimana|:count settimane', + 'w' => ':count set.', + 'day' => ':count giorno|:count giorni', + 'a_day' => 'un giorno|:count giorni', + 'd' => ':count g|:count gg', + 'hour' => ':count ora|:count ore', + 'a_hour' => 'un\'ora|:count ore', + 'h' => ':count h', + 'minute' => ':count minuto|:count minuti', + 'a_minute' => 'un minuto|:count minuti', + 'min' => ':count min.', + 'second' => ':count secondo|:count secondi', + 'a_second' => 'alcuni secondi|:count secondi', + 's' => ':count sec.', + 'millisecond' => ':count millisecondo|:count millisecondi', + 'a_millisecond' => 'un millisecondo|:count millisecondi', + 'ms' => ':countms', + 'microsecond' => ':count microsecondo|:count microsecondi', + 'a_microsecond' => 'un microsecondo|:count microsecondi', + 'ยตs' => ':countยตs', + 'ago' => ':time fa', + 'from_now' => function ($time) { + return (preg_match('/^\d.+$/', $time) ? 'tra' : 'in')." $time"; + }, + 'after' => ':time dopo', + 'before' => ':time prima', + 'diff_now' => 'proprio ora', + 'diff_today' => 'Oggi', + 'diff_today_regexp' => 'Oggi(?:\\s+alle)?', + 'diff_yesterday' => 'ieri', + 'diff_yesterday_regexp' => 'Ieri(?:\\s+alle)?', + 'diff_tomorrow' => 'domani', + 'diff_tomorrow_regexp' => 'Domani(?:\\s+alle)?', + 'diff_before_yesterday' => 'l\'altro ieri', + 'diff_after_tomorrow' => 'dopodomani', + 'period_interval' => 'ogni :interval', + 'period_start_date' => 'dal :date', + 'period_end_date' => 'al :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Oggi alle] LT', + 'nextDay' => '[Domani alle] LT', + 'nextWeek' => 'dddd [alle] LT', + 'lastDay' => '[Ieri alle] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[la scorsa] dddd [alle] LT'; + default: + return '[lo scorso] dddd [alle] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberยบ', + 'months' => ['gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', 'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre'], + 'months_short' => ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'], + 'weekdays' => ['domenica', 'lunedรฌ', 'martedรฌ', 'mercoledรฌ', 'giovedรฌ', 'venerdรฌ', 'sabato'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'], + 'weekdays_min' => ['do', 'lu', 'ma', 'me', 'gi', 've', 'sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], + 'ordinal_words' => [ + 'of' => 'di', + 'first' => 'primo', + 'second' => 'secondo', + 'third' => 'terzo', + 'fourth' => 'quarto', + 'fifth' => 'quinto', + 'last' => 'ultimo', + ], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it_CH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it_CH.php new file mode 100644 index 0000000000..c23cc50e3d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it_CH.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Propaganistas + */ +return array_replace_recursive(require __DIR__.'/it.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it_IT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it_IT.php new file mode 100644 index 0000000000..a5d198180e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it_IT.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return require __DIR__.'/it.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it_SM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it_SM.php new file mode 100644 index 0000000000..5e8fc92f48 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it_SM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/it.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it_VA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it_VA.php new file mode 100644 index 0000000000..5e8fc92f48 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it_VA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/it.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iu.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iu.php new file mode 100644 index 0000000000..4fa974274f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iu.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/iu_CA.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iu_CA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iu_CA.php new file mode 100644 index 0000000000..6ab7e1497f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iu_CA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YY', + ], + 'months' => ['แ”ฎแ“„แŠแ“•', 'แ••แ•—แŠแ“•', 'แ’ชแ”…แ“ฏ', 'แƒแณแ“—', 'แ’ชแƒ', 'แ”ชแ“‚', 'แ”ชแ“šแƒ', 'แŠแ’‹แ“ฏ', 'แ“ฏแ‘Žแ•™', 'แŠแ‘ฆแ‘แ•™', 'แ“„แ••แ•™', 'แ‘Žแ“ฏแ•แ•™'], + 'months_short' => ['แ”ฎแ“„', 'แ••แ•—', 'แ’ชแ”…', 'แƒแณ', 'แ’ชแƒ', 'แ”ชแ“‚', 'แ”ชแ“š', 'แŠแ’‹', 'แ“ฏแ‘Ž', 'แŠแ‘ฆ', 'แ“„แ••', 'แ‘Žแ“ฏ'], + 'weekdays' => ['แ“ˆแ‘ฆแ‘Žแ–‘แ”ญแ•แ••แ’ƒ', 'แ“‡แ’กแ’แ”พแ”ญแ…', 'แ“‡แ’กแ’แ”พแ”ญแ…แ“•แ–…แ‘ญแ‘ฆ', 'แฑแ–“แ“ฒแ“•แ–…แ“ฏแ…แ‘ฆ', 'แ•ฟแ‘Žแ–…แ‘ฐแ‘ฆ', 'แ…แ“ชแ“—แ•ˆแ“˜แ‘แƒแ“‡แ–…', 'แ“ฏแ•™แ‘–แ••แ’ƒ'], + 'weekdays_short' => ['แ“ˆ', 'แ“‡', 'แ“•', 'แฑ', 'แ•ฟ', 'แ…', 'แ“ฏ'], + 'weekdays_min' => ['แ“ˆ', 'แ“‡', 'แ“•', 'แฑ', 'แ•ฟ', 'แ…', 'แ“ฏ'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count แ…แ‘ญแ…แ–…', + 'y' => ':count แ…แ‘ญแ…แ–…', + 'a_year' => ':count แ…แ‘ญแ…แ–…', + + 'month' => ':count qaammat', + 'm' => ':count qaammat', + 'a_month' => ':count qaammat', + + 'week' => ':count sapaatip akunnera', + 'w' => ':count sapaatip akunnera', + 'a_week' => ':count sapaatip akunnera', + + 'day' => ':count ulloq', + 'd' => ':count ulloq', + 'a_day' => ':count ulloq', + + 'hour' => ':count ikarraq', + 'h' => ':count ikarraq', + 'a_hour' => ':count ikarraq', + + 'minute' => ':count titiqqaralaaq', // less reliable + 'min' => ':count titiqqaralaaq', // less reliable + 'a_minute' => ':count titiqqaralaaq', // less reliable + + 'second' => ':count marluk', // less reliable + 's' => ':count marluk', // less reliable + 'a_second' => ':count marluk', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iw.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iw.php new file mode 100644 index 0000000000..a26e35062c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iw.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['ื™ื ื•ืืจ', 'ืคื‘ืจื•ืืจ', 'ืžืจืฅ', 'ืืคืจื™ืœ', 'ืžืื™', 'ื™ื•ื ื™', 'ื™ื•ืœื™', 'ืื•ื’ื•ืกื˜', 'ืกืคื˜ืžื‘ืจ', 'ืื•ืงื˜ื•ื‘ืจ', 'ื ื•ื‘ืžื‘ืจ', 'ื“ืฆืžื‘ืจ'], + 'months_short' => ['ื™ื ื•ืณ', 'ืคื‘ืจืณ', 'ืžืจืฅ', 'ืืคืจืณ', 'ืžืื™', 'ื™ื•ื ื™', 'ื™ื•ืœื™', 'ืื•ื’ืณ', 'ืกืคื˜ืณ', 'ืื•ืงืณ', 'ื ื•ื‘ืณ', 'ื“ืฆืžืณ'], + 'weekdays' => ['ื™ื•ื ืจืืฉื•ืŸ', 'ื™ื•ื ืฉื ื™', 'ื™ื•ื ืฉืœื™ืฉื™', 'ื™ื•ื ืจื‘ื™ืขื™', 'ื™ื•ื ื—ืžื™ืฉื™', 'ื™ื•ื ืฉื™ืฉื™', 'ื™ื•ื ืฉื‘ืช'], + 'weekdays_short' => ['ื™ื•ื ืืณ', 'ื™ื•ื ื‘ืณ', 'ื™ื•ื ื’ืณ', 'ื™ื•ื ื“ืณ', 'ื™ื•ื ื”ืณ', 'ื™ื•ื ื•ืณ', 'ืฉื‘ืช'], + 'weekdays_min' => ['ืืณ', 'ื‘ืณ', 'ื’ืณ', 'ื“ืณ', 'ื”ืณ', 'ื•ืณ', 'ืฉืณ'], + 'meridiem' => ['ืœืคื ื”ืดืฆ', 'ืื—ื”ืดืฆ'], + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D.M.YYYY', + 'LL' => 'D ื‘MMM YYYY', + 'LLL' => 'D ื‘MMMM YYYY H:mm', + 'LLLL' => 'dddd, D ื‘MMMM YYYY H:mm', + ], + + 'year' => ':count ืฉื ื”', + 'y' => ':count ืฉื ื”', + 'a_year' => ':count ืฉื ื”', + + 'month' => ':count ื—ื•ื“ืฉ', + 'm' => ':count ื—ื•ื“ืฉ', + 'a_month' => ':count ื—ื•ื“ืฉ', + + 'week' => ':count ืฉื‘ื•ืข', + 'w' => ':count ืฉื‘ื•ืข', + 'a_week' => ':count ืฉื‘ื•ืข', + + 'day' => ':count ื™ื•ื', + 'd' => ':count ื™ื•ื', + 'a_day' => ':count ื™ื•ื', + + 'hour' => ':count ืฉืขื”', + 'h' => ':count ืฉืขื”', + 'a_hour' => ':count ืฉืขื”', + + 'minute' => ':count ื“ืงื”', + 'min' => ':count ื“ืงื”', + 'a_minute' => ':count ื“ืงื”', + + 'second' => ':count ืฉื ื™ื”', + 's' => ':count ืฉื ื™ื”', + 'a_second' => ':count ืฉื ื™ื”', + + 'ago' => 'ืœืคื ื™ :time', + 'from_now' => 'ื‘ืขื•ื“ :time', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ja.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ja.php new file mode 100644 index 0000000000..1ca675195f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ja.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Takuya Sawada + * - Atsushi Tanaka + * - Franรงois B + * - Jason Katz-Brown + * - Serhan Apaydฤฑn + * - XueWei + * - JD Isaacks + * - toyama satoshi + * - atakigawa + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':countๅนด', + 'y' => ':countๅนด', + 'month' => ':countใƒถๆœˆ', + 'm' => ':countใƒถๆœˆ', + 'week' => ':count้€ฑ้–“', + 'w' => ':count้€ฑ้–“', + 'day' => ':countๆ—ฅ', + 'd' => ':countๆ—ฅ', + 'hour' => ':countๆ™‚้–“', + 'h' => ':countๆ™‚้–“', + 'minute' => ':countๅˆ†', + 'min' => ':countๅˆ†', + 'second' => ':count็ง’', + 'a_second' => '{1}ๆ•ฐ็ง’|]1,Inf[:count็ง’', + 's' => ':count็ง’', + 'ago' => ':timeๅ‰', + 'from_now' => ':timeๅพŒ', + 'after' => ':timeๅพŒ', + 'before' => ':timeๅ‰', + 'diff_now' => 'ไปŠ', + 'diff_today' => 'ไปŠๆ—ฅ', + 'diff_yesterday' => 'ๆ˜จๆ—ฅ', + 'diff_tomorrow' => 'ๆ˜Žๆ—ฅ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYYๅนดMๆœˆDๆ—ฅ', + 'LLL' => 'YYYYๅนดMๆœˆDๆ—ฅ HH:mm', + 'LLLL' => 'YYYYๅนดMๆœˆDๆ—ฅ dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ไปŠๆ—ฅ] LT', + 'nextDay' => '[ๆ˜Žๆ—ฅ] LT', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($other->week !== $current->week) { + return '[ๆฅ้€ฑ]dddd LT'; + } + + return 'dddd LT'; + }, + 'lastDay' => '[ๆ˜จๆ—ฅ] LT', + 'lastWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($other->week !== $current->week) { + return '[ๅ…ˆ้€ฑ]dddd LT'; + } + + return 'dddd LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'ๆ—ฅ'; + default: + return $number; + } + }, + 'meridiem' => ['ๅˆๅ‰', 'ๅˆๅพŒ'], + 'months' => ['1ๆœˆ', '2ๆœˆ', '3ๆœˆ', '4ๆœˆ', '5ๆœˆ', '6ๆœˆ', '7ๆœˆ', '8ๆœˆ', '9ๆœˆ', '10ๆœˆ', '11ๆœˆ', '12ๆœˆ'], + 'months_short' => ['1ๆœˆ', '2ๆœˆ', '3ๆœˆ', '4ๆœˆ', '5ๆœˆ', '6ๆœˆ', '7ๆœˆ', '8ๆœˆ', '9ๆœˆ', '10ๆœˆ', '11ๆœˆ', '12ๆœˆ'], + 'weekdays' => ['ๆ—ฅๆ›œๆ—ฅ', 'ๆœˆๆ›œๆ—ฅ', '็ซๆ›œๆ—ฅ', 'ๆฐดๆ›œๆ—ฅ', 'ๆœจๆ›œๆ—ฅ', '้‡‘ๆ›œๆ—ฅ', 'ๅœŸๆ›œๆ—ฅ'], + 'weekdays_short' => ['ๆ—ฅ', 'ๆœˆ', '็ซ', 'ๆฐด', 'ๆœจ', '้‡‘', 'ๅœŸ'], + 'weekdays_min' => ['ๆ—ฅ', 'ๆœˆ', '็ซ', 'ๆฐด', 'ๆœจ', '้‡‘', 'ๅœŸ'], + 'list' => 'ใ€', + 'alt_numbers' => ['ใ€‡', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ', 'ไธƒ', 'ๅ…ซ', 'ไน', 'ๅ', 'ๅไธ€', 'ๅไบŒ', 'ๅไธ‰', 'ๅๅ››', 'ๅไบ”', 'ๅๅ…ญ', 'ๅไธƒ', 'ๅๅ…ซ', 'ๅไน', 'ไบŒๅ', 'ไบŒๅไธ€', 'ไบŒๅไบŒ', 'ไบŒๅไธ‰', 'ไบŒๅๅ››', 'ไบŒๅไบ”', 'ไบŒๅๅ…ญ', 'ไบŒๅไธƒ', 'ไบŒๅๅ…ซ', 'ไบŒๅไน', 'ไธ‰ๅ', 'ไธ‰ๅไธ€', 'ไธ‰ๅไบŒ', 'ไธ‰ๅไธ‰', 'ไธ‰ๅๅ››', 'ไธ‰ๅไบ”', 'ไธ‰ๅๅ…ญ', 'ไธ‰ๅไธƒ', 'ไธ‰ๅๅ…ซ', 'ไธ‰ๅไน', 'ๅ››ๅ', 'ๅ››ๅไธ€', 'ๅ››ๅไบŒ', 'ๅ››ๅไธ‰', 'ๅ››ๅๅ››', 'ๅ››ๅไบ”', 'ๅ››ๅๅ…ญ', 'ๅ››ๅไธƒ', 'ๅ››ๅๅ…ซ', 'ๅ››ๅไน', 'ไบ”ๅ', 'ไบ”ๅไธ€', 'ไบ”ๅไบŒ', 'ไบ”ๅไธ‰', 'ไบ”ๅๅ››', 'ไบ”ๅไบ”', 'ไบ”ๅๅ…ญ', 'ไบ”ๅไธƒ', 'ไบ”ๅๅ…ซ', 'ไบ”ๅไน', 'ๅ…ญๅ', 'ๅ…ญๅไธ€', 'ๅ…ญๅไบŒ', 'ๅ…ญๅไธ‰', 'ๅ…ญๅๅ››', 'ๅ…ญๅไบ”', 'ๅ…ญๅๅ…ญ', 'ๅ…ญๅไธƒ', 'ๅ…ญๅๅ…ซ', 'ๅ…ญๅไน', 'ไธƒๅ', 'ไธƒๅไธ€', 'ไธƒๅไบŒ', 'ไธƒๅไธ‰', 'ไธƒๅๅ››', 'ไธƒๅไบ”', 'ไธƒๅๅ…ญ', 'ไธƒๅไธƒ', 'ไธƒๅๅ…ซ', 'ไธƒๅไน', 'ๅ…ซๅ', 'ๅ…ซๅไธ€', 'ๅ…ซๅไบŒ', 'ๅ…ซๅไธ‰', 'ๅ…ซๅๅ››', 'ๅ…ซๅไบ”', 'ๅ…ซๅๅ…ญ', 'ๅ…ซๅไธƒ', 'ๅ…ซๅๅ…ซ', 'ๅ…ซๅไน', 'ไนๅ', 'ไนๅไธ€', 'ไนๅไบŒ', 'ไนๅไธ‰', 'ไนๅๅ››', 'ไนๅไบ”', 'ไนๅๅ…ญ', 'ไนๅไธƒ', 'ไนๅๅ…ซ', 'ไนๅไน'], + 'alt_numbers_pow' => [ + 10000 => 'ไธ‡', + 1000 => 'ๅƒ', + 100 => '็™พ', + ], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ja_JP.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ja_JP.php new file mode 100644 index 0000000000..c28362534d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ja_JP.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ja.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jgo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jgo.php new file mode 100644 index 0000000000..6a1e77a855 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jgo.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jmc.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jmc.php new file mode 100644 index 0000000000..ed92e8e70e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jmc.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['utuko', 'kyiukonyi'], + 'weekdays' => ['Jumapilyi', 'Jumatatuu', 'Jumanne', 'Jumatanu', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprilyi', 'Mei', 'Junyi', 'Julyai', 'Agusti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jv.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jv.php new file mode 100644 index 0000000000..bcbe044e3b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jv.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - tgfjt + * - JD Isaacks + */ +return [ + 'year' => '{1}setaun|]1,Inf[:count taun', + 'month' => '{1}sewulan|]1,Inf[:count wulan', + 'week' => '{1}sakminggu|]1,Inf[:count minggu', + 'day' => '{1}sedinten|]1,Inf[:count dinten', + 'hour' => '{1}setunggal jam|]1,Inf[:count jam', + 'minute' => '{1}setunggal menit|]1,Inf[:count menit', + 'second' => '{1}sawetawis detik|]1,Inf[:count detik', + 'ago' => ':time ingkang kepengker', + 'from_now' => 'wonten ing :time', + 'diff_today' => 'Dinten', + 'diff_yesterday' => 'Kala', + 'diff_yesterday_regexp' => 'Kala(?:\\s+wingi)?(?:\\s+pukul)?', + 'diff_tomorrow' => 'Mbenjang', + 'diff_tomorrow_regexp' => 'Mbenjang(?:\\s+pukul)?', + 'diff_today_regexp' => 'Dinten(?:\\s+puniko)?(?:\\s+pukul)?', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [pukul] HH.mm', + 'LLLL' => 'dddd, D MMMM YYYY [pukul] HH.mm', + ], + 'calendar' => [ + 'sameDay' => '[Dinten puniko pukul] LT', + 'nextDay' => '[Mbenjang pukul] LT', + 'nextWeek' => 'dddd [pukul] LT', + 'lastDay' => '[Kala wingi pukul] LT', + 'lastWeek' => 'dddd [kepengker pukul] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 11) { + return 'enjing'; + } + if ($hour < 15) { + return 'siyang'; + } + if ($hour < 19) { + return 'sonten'; + } + + return 'ndalu'; + }, + 'months' => ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'Nopember', 'Desember'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ags', 'Sep', 'Okt', 'Nop', 'Des'], + 'weekdays' => ['Minggu', 'Senen', 'Seloso', 'Rebu', 'Kemis', 'Jemuwah', 'Septu'], + 'weekdays_short' => ['Min', 'Sen', 'Sel', 'Reb', 'Kem', 'Jem', 'Sep'], + 'weekdays_min' => ['Mg', 'Sn', 'Sl', 'Rb', 'Km', 'Jm', 'Sp'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' lan '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ka.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ka.php new file mode 100644 index 0000000000..a5d563d3c6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ka.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Tornike Razmadze + * - Franรงois B + * - Lasha Dolidze + * - Tim Fish + * - JD Isaacks + * - Tornike Razmadze + * - Franรงois B + * - Lasha Dolidze + * - JD Isaacks + * - LONGMAN + * - Avtandil Kikabidze (akalongman) + * - Levan Velijanashvili (Stichoza) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count แƒฌแƒ”แƒšแƒ˜', + 'y' => ':count แƒฌแƒ”แƒšแƒ˜', + 'a_year' => '{1}แƒฌแƒ”แƒšแƒ˜|]1,Inf[:count แƒฌแƒ”แƒšแƒ˜', + 'month' => ':count แƒ—แƒ•แƒ”', + 'm' => ':count แƒ—แƒ•แƒ”', + 'a_month' => '{1}แƒ—แƒ•แƒ”|]1,Inf[:count แƒ—แƒ•แƒ”', + 'week' => ':count แƒ™แƒ•แƒ˜แƒ แƒ', + 'w' => ':count แƒ™แƒ•แƒ˜แƒ แƒ', + 'a_week' => '{1}แƒ™แƒ•แƒ˜แƒ แƒ|]1,Inf[:count แƒ™แƒ•แƒ˜แƒ แƒ', + 'day' => ':count แƒ“แƒฆแƒ”', + 'd' => ':count แƒ“แƒฆแƒ”', + 'a_day' => '{1}แƒ“แƒฆแƒ”|]1,Inf[:count แƒ“แƒฆแƒ”', + 'hour' => ':count แƒกแƒแƒแƒ—แƒ˜', + 'h' => ':count แƒกแƒแƒแƒ—แƒ˜', + 'a_hour' => '{1}แƒกแƒแƒแƒ—แƒ˜|]1,Inf[:count แƒกแƒแƒแƒ—แƒ˜', + 'minute' => ':count แƒฌแƒฃแƒ—แƒ˜', + 'min' => ':count แƒฌแƒฃแƒ—แƒ˜', + 'a_minute' => '{1}แƒฌแƒฃแƒ—แƒ˜|]1,Inf[:count แƒฌแƒฃแƒ—แƒ˜', + 'second' => ':count แƒฌแƒแƒ›แƒ˜', + 's' => ':count แƒฌแƒแƒ›แƒ˜', + 'a_second' => '{1}แƒ แƒแƒ›แƒ“แƒ”แƒœแƒ˜แƒ›แƒ” แƒฌแƒแƒ›แƒ˜|]1,Inf[:count แƒฌแƒแƒ›แƒ˜', + 'ago' => function ($time) { + $replacements = [ + // year + 'แƒฌแƒ”แƒšแƒ˜' => 'แƒฌแƒšแƒ˜แƒก', + // month + 'แƒ—แƒ•แƒ”' => 'แƒ—แƒ•แƒ˜แƒก', + // week + 'แƒ™แƒ•แƒ˜แƒ แƒ' => 'แƒ™แƒ•แƒ˜แƒ แƒ˜แƒก', + // day + 'แƒ“แƒฆแƒ”' => 'แƒ“แƒฆแƒ˜แƒก', + // hour + 'แƒกแƒแƒแƒ—แƒ˜' => 'แƒกแƒแƒแƒ—แƒ˜แƒก', + // minute + 'แƒฌแƒฃแƒ—แƒ˜' => 'แƒฌแƒฃแƒ—แƒ˜แƒก', + // second + 'แƒฌแƒแƒ›แƒ˜' => 'แƒฌแƒแƒ›แƒ˜แƒก', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return "$time แƒฌแƒ˜แƒœ"; + }, + 'from_now' => function ($time) { + $replacements = [ + // year + 'แƒฌแƒ”แƒšแƒ˜' => 'แƒฌแƒ”แƒšแƒ˜แƒฌแƒแƒ“แƒจแƒ˜', + // week + 'แƒ™แƒ•แƒ˜แƒ แƒ' => 'แƒ™แƒ•แƒ˜แƒ แƒแƒจแƒ˜', + // day + 'แƒ“แƒฆแƒ”' => 'แƒ“แƒฆแƒ”แƒจแƒ˜', + // month + 'แƒ—แƒ•แƒ”' => 'แƒ—แƒ•แƒ”แƒจแƒ˜', + // hour + 'แƒกแƒแƒแƒ—แƒ˜' => 'แƒกแƒแƒแƒ—แƒจแƒ˜', + // minute + 'แƒฌแƒฃแƒ—แƒ˜' => 'แƒฌแƒฃแƒ—แƒจแƒ˜', + // second + 'แƒฌแƒแƒ›แƒ˜' => 'แƒฌแƒแƒ›แƒจแƒ˜', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return $time; + }, + 'after' => function ($time) { + $replacements = [ + // year + 'แƒฌแƒ”แƒšแƒ˜' => 'แƒฌแƒšแƒ˜แƒก', + // month + 'แƒ—แƒ•แƒ”' => 'แƒ—แƒ•แƒ˜แƒก', + // week + 'แƒ™แƒ•แƒ˜แƒ แƒ' => 'แƒ™แƒ•แƒ˜แƒ แƒ˜แƒก', + // day + 'แƒ“แƒฆแƒ”' => 'แƒ“แƒฆแƒ˜แƒก', + // hour + 'แƒกแƒแƒแƒ—แƒ˜' => 'แƒกแƒแƒแƒ—แƒ˜แƒก', + // minute + 'แƒฌแƒฃแƒ—แƒ˜' => 'แƒฌแƒฃแƒ—แƒ˜แƒก', + // second + 'แƒฌแƒแƒ›แƒ˜' => 'แƒฌแƒแƒ›แƒ˜แƒก', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return "$time แƒจแƒ”แƒ›แƒ“แƒ”แƒ’"; + }, + 'before' => function ($time) { + $replacements = [ + // year + 'แƒฌแƒ”แƒšแƒ˜' => 'แƒฌแƒšแƒ˜แƒ—', + // month + 'แƒ—แƒ•แƒ”' => 'แƒ—แƒ•แƒ˜แƒ—', + // week + 'แƒ™แƒ•แƒ˜แƒ แƒ' => 'แƒ™แƒ•แƒ˜แƒ แƒ˜แƒ—', + // day + 'แƒ“แƒฆแƒ”' => 'แƒ“แƒฆแƒ˜แƒ—', + // hour + 'แƒกแƒแƒแƒ—แƒ˜' => 'แƒกแƒแƒแƒ—แƒ˜แƒ—', + // minute + 'แƒฌแƒฃแƒ—แƒ˜' => 'แƒฌแƒฃแƒ—แƒ˜แƒ—', + // second + 'แƒฌแƒแƒ›แƒ˜' => 'แƒฌแƒแƒ›แƒ˜แƒ—', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return "$time แƒแƒ“แƒ แƒ”"; + }, + 'diff_now' => 'แƒแƒฎแƒšแƒ', + 'diff_today' => 'แƒ“แƒฆแƒ”แƒก', + 'diff_yesterday' => 'แƒ’แƒฃแƒจแƒ˜แƒœ', + 'diff_tomorrow' => 'แƒฎแƒ•แƒแƒš', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[แƒ“แƒฆแƒ”แƒก], LT[-แƒ–แƒ”]', + 'nextDay' => '[แƒฎแƒ•แƒแƒš], LT[-แƒ–แƒ”]', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + return ($current->isSameWeek($other) ? '' : '[แƒจแƒ”แƒ›แƒ“แƒ”แƒ’] ').'dddd, LT[-แƒ–แƒ”]'; + }, + 'lastDay' => '[แƒ’แƒฃแƒจแƒ˜แƒœ], LT[-แƒ–แƒ”]', + 'lastWeek' => '[แƒฌแƒ˜แƒœแƒ] dddd, LT-แƒ–แƒ”', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + if ($number === 0) { + return $number; + } + if ($number === 1) { + return $number.'-แƒšแƒ˜'; + } + if (($number < 20) || ($number <= 100 && ($number % 20 === 0)) || ($number % 100 === 0)) { + return 'แƒ›แƒ”-'.$number; + } + + return $number.'-แƒ”'; + }, + 'months' => ['แƒ˜แƒแƒœแƒ•แƒแƒ แƒ˜', 'แƒ—แƒ”แƒ‘แƒ”แƒ แƒ•แƒแƒšแƒ˜', 'แƒ›แƒแƒ แƒขแƒ˜', 'แƒแƒžแƒ แƒ˜แƒšแƒ˜', 'แƒ›แƒแƒ˜แƒกแƒ˜', 'แƒ˜แƒ•แƒœแƒ˜แƒกแƒ˜', 'แƒ˜แƒ•แƒšแƒ˜แƒกแƒ˜', 'แƒแƒ’แƒ•แƒ˜แƒกแƒขแƒ', 'แƒกแƒ”แƒฅแƒขแƒ”แƒ›แƒ‘แƒ”แƒ แƒ˜', 'แƒแƒฅแƒขแƒแƒ›แƒ‘แƒ”แƒ แƒ˜', 'แƒœแƒแƒ”แƒ›แƒ‘แƒ”แƒ แƒ˜', 'แƒ“แƒ”แƒ™แƒ”แƒ›แƒ‘แƒ”แƒ แƒ˜'], + 'months_standalone' => ['แƒ˜แƒแƒœแƒ•แƒแƒ แƒก', 'แƒ—แƒ”แƒ‘แƒ”แƒ แƒ•แƒแƒšแƒก', 'แƒ›แƒแƒ แƒขแƒก', 'แƒแƒžแƒ แƒ˜แƒšแƒก', 'แƒ›แƒแƒ˜แƒกแƒก', 'แƒ˜แƒ•แƒœแƒ˜แƒกแƒก', 'แƒ˜แƒ•แƒšแƒ˜แƒกแƒก', 'แƒแƒ’แƒ•แƒ˜แƒกแƒขแƒแƒก', 'แƒกแƒ”แƒฅแƒขแƒ”แƒ›แƒ‘แƒ”แƒ แƒก', 'แƒแƒฅแƒขแƒแƒ›แƒ‘แƒ”แƒ แƒก', 'แƒœแƒแƒ”แƒ›แƒ‘แƒ”แƒ แƒก', 'แƒ“แƒ”แƒ™แƒ”แƒ›แƒ‘แƒ”แƒ แƒก'], + 'months_short' => ['แƒ˜แƒแƒœ', 'แƒ—แƒ”แƒ‘', 'แƒ›แƒแƒ ', 'แƒแƒžแƒ ', 'แƒ›แƒแƒ˜', 'แƒ˜แƒ•แƒœ', 'แƒ˜แƒ•แƒš', 'แƒแƒ’แƒ•', 'แƒกแƒ”แƒฅ', 'แƒแƒฅแƒข', 'แƒœแƒแƒ”', 'แƒ“แƒ”แƒ™'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['แƒ™แƒ•แƒ˜แƒ แƒแƒก', 'แƒแƒ แƒจแƒแƒ‘แƒแƒ—แƒก', 'แƒกแƒแƒ›แƒจแƒแƒ‘แƒแƒ—แƒก', 'แƒแƒ—แƒฎแƒจแƒแƒ‘แƒแƒ—แƒก', 'แƒฎแƒฃแƒ—แƒจแƒแƒ‘แƒแƒ—แƒก', 'แƒžแƒแƒ แƒแƒกแƒ™แƒ”แƒ•แƒก', 'แƒจแƒแƒ‘แƒแƒ—แƒก'], + 'weekdays_standalone' => ['แƒ™แƒ•แƒ˜แƒ แƒ', 'แƒแƒ แƒจแƒแƒ‘แƒแƒ—แƒ˜', 'แƒกแƒแƒ›แƒจแƒแƒ‘แƒแƒ—แƒ˜', 'แƒแƒ—แƒฎแƒจแƒแƒ‘แƒแƒ—แƒ˜', 'แƒฎแƒฃแƒ—แƒจแƒแƒ‘แƒแƒ—แƒ˜', 'แƒžแƒแƒ แƒแƒกแƒ™แƒ”แƒ•แƒ˜', 'แƒจแƒแƒ‘แƒแƒ—แƒ˜'], + 'weekdays_short' => ['แƒ™แƒ•แƒ˜', 'แƒแƒ แƒจ', 'แƒกแƒแƒ›', 'แƒแƒ—แƒฎ', 'แƒฎแƒฃแƒ—', 'แƒžแƒแƒ ', 'แƒจแƒแƒ‘'], + 'weekdays_min' => ['แƒ™แƒ•', 'แƒแƒ ', 'แƒกแƒ', 'แƒแƒ—', 'แƒฎแƒฃ', 'แƒžแƒ', 'แƒจแƒ'], + 'weekdays_regexp' => '/^([^d].*|.*[^d])$/', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' แƒ“แƒ '], + 'meridiem' => function ($hour) { + if ($hour >= 4) { + if ($hour < 11) { + return 'แƒ“แƒ˜แƒšแƒ˜แƒก'; + } + + if ($hour < 16) { + return 'แƒจแƒฃแƒแƒ“แƒฆแƒ˜แƒก'; + } + + if ($hour < 22) { + return 'แƒกแƒแƒฆแƒแƒ›แƒแƒก'; + } + } + + return 'แƒฆแƒแƒ›แƒ˜แƒก'; + }, +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ka_GE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ka_GE.php new file mode 100644 index 0000000000..a26d930562 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ka_GE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ka.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kab.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kab.php new file mode 100644 index 0000000000..94d6473734 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kab.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kab_DZ.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kab_DZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kab_DZ.php new file mode 100644 index 0000000000..796660be9b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kab_DZ.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - belkacem77@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Yennayer', 'Fuแน›ar', 'Meษฃres', 'Yebrir', 'Mayyu', 'Yunyu', 'Yulyu', 'ษฃuct', 'Ctembeแน›', 'Tubeแน›', 'Wambeแน›', 'Dujembeแน›'], + 'months_short' => ['Yen', 'Fur', 'Meษฃ', 'Yeb', 'May', 'Yun', 'Yul', 'ษฃuc', 'Cte', 'Tub', 'Wam', 'Duj'], + 'weekdays' => ['Acer', 'Arim', 'Aram', 'Ahad', 'Amhad', 'Sem', 'Sed'], + 'weekdays_short' => ['Ace', 'Ari', 'Ara', 'Aha', 'Amh', 'Sem', 'Sed'], + 'weekdays_min' => ['Ace', 'Ari', 'Ara', 'Aha', 'Amh', 'Sem', 'Sed'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['FT', 'MD'], + + 'year' => ':count n yiseggasen', + 'y' => ':count n yiseggasen', + 'a_year' => ':count n yiseggasen', + + 'month' => ':count n wayyuren', + 'm' => ':count n wayyuren', + 'a_month' => ':count n wayyuren', + + 'week' => ':count n ledwaแน›', // less reliable + 'w' => ':count n ledwaแน›', // less reliable + 'a_week' => ':count n ledwaแน›', // less reliable + + 'day' => ':count n wussan', + 'd' => ':count n wussan', + 'a_day' => ':count n wussan', + + 'hour' => ':count n tsaษ›tin', + 'h' => ':count n tsaษ›tin', + 'a_hour' => ':count n tsaษ›tin', + + 'minute' => ':count n tedqiqin', + 'min' => ':count n tedqiqin', + 'a_minute' => ':count n tedqiqin', + + 'second' => ':count tasdidt', // less reliable + 's' => ':count tasdidt', // less reliable + 'a_second' => ':count tasdidt', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kam.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kam.php new file mode 100644 index 0000000000..0fc70d7087 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kam.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ฤจyakwakya', 'ฤจyawฤฉoo'], + 'weekdays' => ['Wa kyumwa', 'Wa kwambฤฉlฤฉlya', 'Wa kelฤฉ', 'Wa katatลฉ', 'Wa kana', 'Wa katano', 'Wa thanthatลฉ'], + 'weekdays_short' => ['Wky', 'Wkw', 'Wkl', 'Wtลฉ', 'Wkn', 'Wtn', 'Wth'], + 'weekdays_min' => ['Wky', 'Wkw', 'Wkl', 'Wtลฉ', 'Wkn', 'Wtn', 'Wth'], + 'months' => ['Mwai wa mbee', 'Mwai wa kelฤฉ', 'Mwai wa katatลฉ', 'Mwai wa kana', 'Mwai wa katano', 'Mwai wa thanthatลฉ', 'Mwai wa muonza', 'Mwai wa nyaanya', 'Mwai wa kenda', 'Mwai wa ฤฉkumi', 'Mwai wa ฤฉkumi na ฤฉmwe', 'Mwai wa ฤฉkumi na ilฤฉ'], + 'months_short' => ['Mbe', 'Kel', 'Ktลฉ', 'Kan', 'Ktn', 'Tha', 'Moo', 'Nya', 'Knd', 'ฤจku', 'ฤจkm', 'ฤจkl'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + // Too unreliable + /* + 'year' => ':count mbua', // less reliable + 'y' => ':count mbua', // less reliable + 'a_year' => ':count mbua', // less reliable + + 'month' => ':count ndakitali', // less reliable + 'm' => ':count ndakitali', // less reliable + 'a_month' => ':count ndakitali', // less reliable + + 'day' => ':count wia', // less reliable + 'd' => ':count wia', // less reliable + 'a_day' => ':count wia', // less reliable + + 'hour' => ':count orasan', // less reliable + 'h' => ':count orasan', // less reliable + 'a_hour' => ':count orasan', // less reliable + + 'minute' => ':count orasan', // less reliable + 'min' => ':count orasan', // less reliable + 'a_minute' => ':count orasan', // less reliable + */ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kde.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kde.php new file mode 100644 index 0000000000..fbcc9f3dc6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kde.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Muhi', 'Chilo'], + 'weekdays' => ['Liduva lyapili', 'Liduva lyatatu', 'Liduva lyanchechi', 'Liduva lyannyano', 'Liduva lyannyano na linji', 'Liduva lyannyano na mavili', 'Liduva litandi'], + 'weekdays_short' => ['Ll2', 'Ll3', 'Ll4', 'Ll5', 'Ll6', 'Ll7', 'Ll1'], + 'weekdays_min' => ['Ll2', 'Ll3', 'Ll4', 'Ll5', 'Ll6', 'Ll7', 'Ll1'], + 'months' => ['Mwedi Ntandi', 'Mwedi wa Pili', 'Mwedi wa Tatu', 'Mwedi wa Nchechi', 'Mwedi wa Nnyano', 'Mwedi wa Nnyano na Umo', 'Mwedi wa Nnyano na Mivili', 'Mwedi wa Nnyano na Mitatu', 'Mwedi wa Nnyano na Nchechi', 'Mwedi wa Nnyano na Nnyano', 'Mwedi wa Nnyano na Nnyano na U', 'Mwedi wa Nnyano na Nnyano na M'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kea.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kea.php new file mode 100644 index 0000000000..8b6c21b988 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kea.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['a', 'p'], + 'weekdays' => ['dumingu', 'sigunda-fera', 'tersa-fera', 'kuarta-fera', 'kinta-fera', 'sesta-fera', 'sabadu'], + 'weekdays_short' => ['dum', 'sig', 'ter', 'kua', 'kin', 'ses', 'sab'], + 'weekdays_min' => ['du', 'si', 'te', 'ku', 'ki', 'se', 'sa'], + 'weekdays_standalone' => ['dumingu', 'sigunda-fera', 'tersa-fera', 'kuarta-fera', 'kinta-fera', 'sesta-fera', 'sรกbadu'], + 'months' => ['Janeru', 'Febreru', 'Marsu', 'Abril', 'Maiu', 'Junhu', 'Julhu', 'Agostu', 'Setenbru', 'Otubru', 'Nuvenbru', 'Dizenbru'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Otu', 'Nuv', 'Diz'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D [di] MMMM [di] YYYY HH:mm', + 'LLLL' => 'dddd, D [di] MMMM [di] YYYY HH:mm', + ], + + 'year' => ':count otunu', // less reliable + 'y' => ':count otunu', // less reliable + 'a_year' => ':count otunu', // less reliable + + 'week' => ':count dรญa dumingu', // less reliable + 'w' => ':count dรญa dumingu', // less reliable + 'a_week' => ':count dรญa dumingu', // less reliable + + 'day' => ':count diรข', // less reliable + 'd' => ':count diรข', // less reliable + 'a_day' => ':count diรข', // less reliable + + 'minute' => ':count sugundu', // less reliable + 'min' => ':count sugundu', // less reliable + 'a_minute' => ':count sugundu', // less reliable + + 'second' => ':count dรณs', // less reliable + 's' => ':count dรณs', // less reliable + 'a_second' => ':count dรณs', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/khq.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/khq.php new file mode 100644 index 0000000000..7a834cf0b6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/khq.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Adduha', 'Aluula'], + 'weekdays' => ['Alhadi', 'Atini', 'Atalata', 'Alarba', 'Alhamiisa', 'Aljuma', 'Assabdu'], + 'weekdays_short' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alj', 'Ass'], + 'weekdays_min' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alj', 'Ass'], + 'months' => ['ลฝanwiye', 'Feewiriye', 'Marsi', 'Awiril', 'Me', 'ลฝuweล‹', 'ลฝuyye', 'Ut', 'Sektanbur', 'Oktoobur', 'Noowanbur', 'Deesanbur'], + 'months_short' => ['ลฝan', 'Fee', 'Mar', 'Awi', 'Me', 'ลฝuw', 'ลฝuy', 'Ut', 'Sek', 'Okt', 'Noo', 'Dee'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ki.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ki.php new file mode 100644 index 0000000000..d86afc5000 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ki.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Kiroko', 'Hwaฤฉ-inฤฉ'], + 'weekdays' => ['Kiumia', 'Njumatatลฉ', 'Njumaine', 'Njumatana', 'Aramithi', 'Njumaa', 'Njumamothi'], + 'weekdays_short' => ['KMA', 'NTT', 'NMN', 'NMT', 'ART', 'NMA', 'NMM'], + 'weekdays_min' => ['KMA', 'NTT', 'NMN', 'NMT', 'ART', 'NMA', 'NMM'], + 'months' => ['Njenuarฤฉ', 'Mwere wa kerฤฉ', 'Mwere wa gatatลฉ', 'Mwere wa kana', 'Mwere wa gatano', 'Mwere wa gatandatลฉ', 'Mwere wa mลฉgwanja', 'Mwere wa kanana', 'Mwere wa kenda', 'Mwere wa ikลฉmi', 'Mwere wa ikลฉmi na ลฉmwe', 'Ndithemba'], + 'months_short' => ['JEN', 'WKR', 'WGT', 'WKN', 'WTN', 'WTD', 'WMJ', 'WNN', 'WKD', 'WIK', 'WMW', 'DIT'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count mฤฉaka', // less reliable + 'y' => ':count mฤฉaka', // less reliable + 'a_year' => ':count mฤฉaka', // less reliable + + 'month' => ':count mweri', // less reliable + 'm' => ':count mweri', // less reliable + 'a_month' => ':count mweri', // less reliable + + 'week' => ':count kiumia', // less reliable + 'w' => ':count kiumia', // less reliable + 'a_week' => ':count kiumia', // less reliable + + 'day' => ':count mลฉthenya', // less reliable + 'd' => ':count mลฉthenya', // less reliable + 'a_day' => ':count mลฉthenya', // less reliable + + 'hour' => ':count thaa', // less reliable + 'h' => ':count thaa', // less reliable + 'a_hour' => ':count thaa', // less reliable + + 'minute' => ':count mundu', // less reliable + 'min' => ':count mundu', // less reliable + 'a_minute' => ':count mundu', // less reliable + + 'second' => ':count igego', // less reliable + 's' => ':count igego', // less reliable + 'a_second' => ':count igego', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kk.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kk.php new file mode 100644 index 0000000000..59fa9affb0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kk.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Franรงois B + * - Talat Uspanov + * - ะัƒั€ะปะฐะฝ ะ ะฐั…ะธะผะถะฐะฝะพะฒ + * - Toleugazy Kali + */ +return [ + 'year' => ':count ะถั‹ะป', + 'a_year' => '{1}ะฑั–ั€ ะถั‹ะป|:count ะถั‹ะป', + 'y' => ':count ะถ.', + 'month' => ':count ะฐะน', + 'a_month' => '{1}ะฑั–ั€ ะฐะน|:count ะฐะน', + 'm' => ':count ะฐะน', + 'week' => ':count ะฐะฟั‚ะฐ', + 'a_week' => '{1}ะฑั–ั€ ะฐะฟั‚ะฐ', + 'w' => ':count ะฐะฟ.', + 'day' => ':count ะบาฏะฝ', + 'a_day' => '{1}ะฑั–ั€ ะบาฏะฝ|:count ะบาฏะฝ', + 'd' => ':count ะบ.', + 'hour' => ':count ัะฐา“ะฐั‚', + 'a_hour' => '{1}ะฑั–ั€ ัะฐา“ะฐั‚|:count ัะฐา“ะฐั‚', + 'h' => ':count ัะฐ.', + 'minute' => ':count ะผะธะฝัƒั‚', + 'a_minute' => '{1}ะฑั–ั€ ะผะธะฝัƒั‚|:count ะผะธะฝัƒั‚', + 'min' => ':count ะผ.', + 'second' => ':count ัะตะบัƒะฝะด', + 'a_second' => '{1}ะฑั–ั€ะฝะตัˆะต ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะด', + 's' => ':count ัะต.', + 'ago' => ':time ะฑาฑั€ั‹ะฝ', + 'from_now' => ':time ั–ัˆั–ะฝะดะต', + 'after' => ':time ะบะตะนั–ะฝ', + 'before' => ':time ะฑาฑั€ั‹ะฝ', + 'diff_now' => 'า›ะฐะทั–ั€', + 'diff_today' => 'ะ‘าฏะณั–ะฝ', + 'diff_today_regexp' => 'ะ‘าฏะณั–ะฝ(?:\\s+ัะฐา“ะฐั‚)?', + 'diff_yesterday' => 'ะบะตัˆะต', + 'diff_yesterday_regexp' => 'ะšะตัˆะต(?:\\s+ัะฐา“ะฐั‚)?', + 'diff_tomorrow' => 'ะตั€ั‚ะตาฃ', + 'diff_tomorrow_regexp' => 'ะ•ั€ั‚ะตาฃ(?:\\s+ัะฐา“ะฐั‚)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ะ‘าฏะณั–ะฝ ัะฐา“ะฐั‚] LT', + 'nextDay' => '[ะ•ั€ั‚ะตาฃ ัะฐา“ะฐั‚] LT', + 'nextWeek' => 'dddd [ัะฐา“ะฐั‚] LT', + 'lastDay' => '[ะšะตัˆะต ัะฐา“ะฐั‚] LT', + 'lastWeek' => '[ำจั‚ะบะตะฝ ะฐะฟั‚ะฐะฝั‹าฃ] dddd [ัะฐา“ะฐั‚] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + static $suffixes = [ + 0 => '-ัˆั–', + 1 => '-ัˆั–', + 2 => '-ัˆั–', + 3 => '-ัˆั–', + 4 => '-ัˆั–', + 5 => '-ัˆั–', + 6 => '-ัˆั‹', + 7 => '-ัˆั–', + 8 => '-ัˆั–', + 9 => '-ัˆั‹', + 10 => '-ัˆั‹', + 20 => '-ัˆั‹', + 30 => '-ัˆั‹', + 40 => '-ัˆั‹', + 50 => '-ัˆั–', + 60 => '-ัˆั‹', + 70 => '-ัˆั–', + 80 => '-ัˆั–', + 90 => '-ัˆั‹', + 100 => '-ัˆั–', + ]; + + return $number.($suffixes[$number] ?? $suffixes[$number % 10] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'months' => ['า›ะฐาฃั‚ะฐั€', 'ะฐา›ะฟะฐะฝ', 'ะฝะฐัƒั€ั‹ะท', 'ัำ™ัƒั–ั€', 'ะผะฐะผั‹ั€', 'ะผะฐัƒัั‹ะผ', 'ัˆั–ะปะดะต', 'ั‚ะฐะผั‹ะท', 'า›ั‹ั€ะบาฏะนะตะบ', 'า›ะฐะทะฐะฝ', 'า›ะฐั€ะฐัˆะฐ', 'ะถะตะปั‚ะพา›ัะฐะฝ'], + 'months_short' => ['า›ะฐาฃ', 'ะฐา›ะฟ', 'ะฝะฐัƒ', 'ัำ™ัƒ', 'ะผะฐะผ', 'ะผะฐัƒ', 'ัˆั–ะป', 'ั‚ะฐะผ', 'า›ั‹ั€', 'า›ะฐะท', 'า›ะฐั€', 'ะถะตะป'], + 'weekdays' => ['ะถะตะบัะตะฝะฑั–', 'ะดาฏะนัะตะฝะฑั–', 'ัะตะนัะตะฝะฑั–', 'ัำ™ั€ัะตะฝะฑั–', 'ะฑะตะนัะตะฝะฑั–', 'ะถาฑะผะฐ', 'ัะตะฝะฑั–'], + 'weekdays_short' => ['ะถะตะบ', 'ะดาฏะน', 'ัะตะน', 'ัำ™ั€', 'ะฑะตะน', 'ะถาฑะผ', 'ัะตะฝ'], + 'weekdays_min' => ['ะถะบ', 'ะดะน', 'ัะน', 'ัั€', 'ะฑะน', 'ะถะผ', 'ัะฝ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ะถำ™ะฝะต '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kk_KZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kk_KZ.php new file mode 100644 index 0000000000..7dc5ebccc9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kk_KZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/kk.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kkj.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kkj.php new file mode 100644 index 0000000000..6a1e77a855 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kkj.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kl.php new file mode 100644 index 0000000000..7329a07534 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kl.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kl_GL.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kl_GL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kl_GL.php new file mode 100644 index 0000000000..4fed720a38 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kl_GL.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Danish Standards Association bug-glibc-locales@gnu.org + * - John Eyรฐstein Johannesen (mashema) + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd [d.] D. MMMM YYYY [kl.] HH:mm', + ], + 'months' => ['januaarip', 'februaarip', 'marsip', 'apriilip', 'maajip', 'juunip', 'juulip', 'aggustip', 'septembarip', 'oktobarip', 'novembarip', 'decembarip'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['sapaat', 'ataasinngorneq', 'marlunngorneq', 'pingasunngorneq', 'sisamanngorneq', 'tallimanngorneq', 'arfininngorneq'], + 'weekdays_short' => ['sap', 'ata', 'mar', 'pin', 'sis', 'tal', 'arf'], + 'weekdays_min' => ['sap', 'ata', 'mar', 'pin', 'sis', 'tal', 'arf'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => '{1}ukioq :count|{0}:count ukiut|]1,Inf[ukiut :count', + 'a_year' => '{1}ukioq|{0}:count ukiut|]1,Inf[ukiut :count', + 'y' => '{1}:countyr|{0}:countyrs|]1,Inf[:countyrs', + + 'month' => '{1}qaammat :count|{0}:count qaammatit|]1,Inf[qaammatit :count', + 'a_month' => '{1}qaammat|{0}:count qaammatit|]1,Inf[qaammatit :count', + 'm' => '{1}:countmo|{0}:countmos|]1,Inf[:countmos', + + 'week' => '{1}:count sap. ak.|{0}:count sap. ak.|]1,Inf[:count sap. ak.', + 'a_week' => '{1}a sap. ak.|{0}:count sap. ak.|]1,Inf[:count sap. ak.', + 'w' => ':countw', + + 'day' => '{1}:count ulloq|{0}:count ullut|]1,Inf[:count ullut', + 'a_day' => '{1}a ulloq|{0}:count ullut|]1,Inf[:count ullut', + 'd' => ':countd', + + 'hour' => '{1}:count tiimi|{0}:count tiimit|]1,Inf[:count tiimit', + 'a_hour' => '{1}tiimi|{0}:count tiimit|]1,Inf[:count tiimit', + 'h' => ':counth', + + 'minute' => '{1}:count minutsi|{0}:count minutsit|]1,Inf[:count minutsit', + 'a_minute' => '{1}a minutsi|{0}:count minutsit|]1,Inf[:count minutsit', + 'min' => ':countm', + + 'second' => '{1}:count sikunti|{0}:count sikuntit|]1,Inf[:count sikuntit', + 'a_second' => '{1}sikunti|{0}:count sikuntit|]1,Inf[:count sikuntit', + 's' => ':counts', + + 'ago' => ':time matuma siorna', + +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kln.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kln.php new file mode 100644 index 0000000000..b9c39968bd --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kln.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['krn', 'koosk'], + 'weekdays' => ['Kotisap', 'Kotaai', 'Koaengโ€™', 'Kosomok', 'Koangโ€™wan', 'Komuut', 'Kolo'], + 'weekdays_short' => ['Kts', 'Kot', 'Koo', 'Kos', 'Koa', 'Kom', 'Kol'], + 'weekdays_min' => ['Kts', 'Kot', 'Koo', 'Kos', 'Koa', 'Kom', 'Kol'], + 'months' => ['Mulgul', 'Ngโ€™atyaato', 'Kiptaamo', 'Iwootkuut', 'Mamuut', 'Paagi', 'Ngโ€™eiyeet', 'Rooptui', 'Bureet', 'Epeeso', 'Kipsuunde ne taai', 'Kipsuunde nebo aengโ€™'], + 'months_short' => ['Mul', 'Ngat', 'Taa', 'Iwo', 'Mam', 'Paa', 'Nge', 'Roo', 'Bur', 'Epe', 'Kpt', 'Kpa'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count maghatiat', // less reliable + 'y' => ':count maghatiat', // less reliable + 'a_year' => ':count maghatiat', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/km.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/km.php new file mode 100644 index 0000000000..da790ac852 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/km.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kruy Vanna + * - Sereysethy Touch + * - JD Isaacks + * - Sovichet Tep + */ +return [ + 'year' => '{1}แž˜แžฝแž™แž†แŸ’แž“แžถแŸ†|]1,Inf[:count แž†แŸ’แž“แžถแŸ†', + 'y' => ':count แž†แŸ’แž“แžถแŸ†', + 'month' => '{1}แž˜แžฝแž™แžแŸ‚|]1,Inf[:count แžแŸ‚', + 'm' => ':count แžแŸ‚', + 'week' => ':count แžŸแž”แŸ’แžŠแžถแž แŸ', + 'w' => ':count แžŸแž”แŸ’แžŠแžถแž แŸ', + 'day' => '{1}แž˜แžฝแž™แžแŸ’แž„แŸƒ|]1,Inf[:count แžแŸ’แž„แŸƒ', + 'd' => ':count แžแŸ’แž„แŸƒ', + 'hour' => '{1}แž˜แžฝแž™แž˜แŸ‰แŸ„แž„|]1,Inf[:count แž˜แŸ‰แŸ„แž„', + 'h' => ':count แž˜แŸ‰แŸ„แž„', + 'minute' => '{1}แž˜แžฝแž™แž“แžถแž‘แžธ|]1,Inf[:count แž“แžถแž‘แžธ', + 'min' => ':count แž“แžถแž‘แžธ', + 'second' => '{1}แž”แŸ‰แžปแž“แŸ’แž˜แžถแž“แžœแžทแž“แžถแž‘แžธ|]1,Inf[:count แžœแžทแž“แžถแž‘แžธ', + 's' => ':count แžœแžทแž“แžถแž‘แžธ', + 'ago' => ':timeแž˜แžปแž“', + 'from_now' => ':timeแž‘แŸ€แž', + 'after' => 'แž“แŸ…โ€‹แž€แŸ’แžšแŸ„แž™ :time', + 'before' => 'แž“แŸ…โ€‹แž˜แžปแž“ :time', + 'diff_now' => 'แžฅแžกแžผแžœ', + 'diff_today' => 'แžแŸ’แž„แŸƒแž“แŸแŸ‡', + 'diff_today_regexp' => 'แžแŸ’แž„แŸƒแž“แŸแŸ‡(?:\\s+แž˜แŸ‰แŸ„แž„)?', + 'diff_yesterday' => 'แž˜แŸ’แžŸแžทแž›แž˜แžทแž‰', + 'diff_yesterday_regexp' => 'แž˜แŸ’แžŸแžทแž›แž˜แžทแž‰(?:\\s+แž˜แŸ‰แŸ„แž„)?', + 'diff_tomorrow' => 'แžแŸ’แž„แŸƒโ€‹แžŸแŸ’แžขแŸ‚แž€', + 'diff_tomorrow_regexp' => 'แžŸแŸ’แžขแŸ‚แž€(?:\\s+แž˜แŸ‰แŸ„แž„)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[แžแŸ’แž„แŸƒแž“แŸแŸ‡ แž˜แŸ‰แŸ„แž„] LT', + 'nextDay' => '[แžŸแŸ’แžขแŸ‚แž€ แž˜แŸ‰แŸ„แž„] LT', + 'nextWeek' => 'dddd [แž˜แŸ‰แŸ„แž„] LT', + 'lastDay' => '[แž˜แŸ’แžŸแžทแž›แž˜แžทแž‰ แž˜แŸ‰แŸ„แž„] LT', + 'lastWeek' => 'dddd [แžŸแž”แŸ’แžแžถแž แŸแž˜แžปแž“] [แž˜แŸ‰แŸ„แž„] LT', + 'sameElse' => 'L', + ], + 'ordinal' => 'แž‘แžธ:number', + 'meridiem' => ['แž–แŸ’แžšแžนแž€', 'แž›แŸ’แž„แžถแž…'], + 'months' => ['แž˜แž€แžšแžถ', 'แž€แžปแž˜แŸ’แž—แŸˆ', 'แž˜แžธแž“แžถ', 'แž˜แŸแžŸแžถ', 'แžงแžŸแž—แžถ', 'แž˜แžทแžแžปแž“แžถ', 'แž€แž€แŸ’แž€แžŠแžถ', 'แžŸแžธแž แžถ', 'แž€แž‰แŸ’แž‰แžถ', 'แžแžปแž›แžถ', 'แžœแžทแž…แŸ’แž†แžทแž€แžถ', 'แž’แŸ’แž“แžผ'], + 'months_short' => ['แž˜แž€แžšแžถ', 'แž€แžปแž˜แŸ’แž—แŸˆ', 'แž˜แžธแž“แžถ', 'แž˜แŸแžŸแžถ', 'แžงแžŸแž—แžถ', 'แž˜แžทแžแžปแž“แžถ', 'แž€แž€แŸ’แž€แžŠแžถ', 'แžŸแžธแž แžถ', 'แž€แž‰แŸ’แž‰แžถ', 'แžแžปแž›แžถ', 'แžœแžทแž…แŸ’แž†แžทแž€แžถ', 'แž’แŸ’แž“แžผ'], + 'weekdays' => ['แžขแžถแž‘แžทแžแŸ’แž™', 'แž…แŸแž“แŸ’แž‘', 'แžขแž„แŸ’แž‚แžถแžš', 'แž–แžปแž’', 'แž–แŸ’แžšแž แžŸแŸ’แž”แžแžทแŸ', 'แžŸแžปแž€แŸ’แžš', 'แžŸแŸ…แžšแŸ'], + 'weekdays_short' => ['แžขแžถ', 'แž…', 'แžข', 'แž–', 'แž–แŸ’แžš', 'แžŸแžป', 'แžŸ'], + 'weekdays_min' => ['แžขแžถ', 'แž…', 'แžข', 'แž–', 'แž–แŸ’แžš', 'แžŸแžป', 'แžŸ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', 'แž“แžทแž„ '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/km_KH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/km_KH.php new file mode 100644 index 0000000000..92e5fdbd26 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/km_KH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/km.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kn.php new file mode 100644 index 0000000000..0d2ad08b4f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kn.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - MOHAN M U + * - Franรงois B + * - rajeevnaikte + */ +return [ + 'year' => '{1}เฒ’เฒ‚เฒฆเณ เฒตเฒฐเณเฒท|]1,Inf[:count เฒตเฒฐเณเฒท', + 'month' => '{1}เฒ’เฒ‚เฒฆเณ เฒคเฒฟเฒ‚เฒ—เฒณเณ|]1,Inf[:count เฒคเฒฟเฒ‚เฒ—เฒณเณ', + 'week' => '{1}เฒ’เฒ‚เฒฆเณ เฒตเฒพเฒฐ|]1,Inf[:count เฒตเฒพเฒฐเฒ—เฒณเณ', + 'day' => '{1}เฒ’เฒ‚เฒฆเณ เฒฆเฒฟเฒจ|]1,Inf[:count เฒฆเฒฟเฒจ', + 'hour' => '{1}เฒ’เฒ‚เฒฆเณ เฒ—เฒ‚เฒŸเณ†|]1,Inf[:count เฒ—เฒ‚เฒŸเณ†', + 'minute' => '{1}เฒ’เฒ‚เฒฆเณ เฒจเฒฟเฒฎเฒฟเฒท|]1,Inf[:count เฒจเฒฟเฒฎเฒฟเฒท', + 'second' => '{1}เฒ•เณ†เฒฒเฒตเณ เฒ•เณเฒทเฒฃเฒ—เฒณเณ|]1,Inf[:count เฒธเณ†เฒ•เณ†เฒ‚เฒกเณเฒ—เฒณเณ', + 'ago' => ':time เฒนเฒฟเฒ‚เฒฆเณ†', + 'from_now' => ':time เฒจเฒ‚เฒคเฒฐ', + 'diff_now' => 'เฒˆเฒ—', + 'diff_today' => 'เฒ‡เฒ‚เฒฆเณ', + 'diff_yesterday' => 'เฒจเฒฟเฒจเณเฒจเณ†', + 'diff_tomorrow' => 'เฒจเฒพเฒณเณ†', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm', + ], + 'calendar' => [ + 'sameDay' => '[เฒ‡เฒ‚เฒฆเณ] LT', + 'nextDay' => '[เฒจเฒพเฒณเณ†] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[เฒจเฒฟเฒจเณเฒจเณ†] LT', + 'lastWeek' => '[เฒ•เณ†เณ‚เฒจเณ†เฒฏ] dddd, LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberเฒจเณ†เณ•', + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'เฒฐเฒพเฒคเณเฒฐเฒฟ'; + } + if ($hour < 10) { + return 'เฒฌเณ†เฒณเฒฟเฒ—เณเฒ—เณ†'; + } + if ($hour < 17) { + return 'เฒฎเฒงเณเฒฏเฒพเฒนเณเฒจ'; + } + if ($hour < 20) { + return 'เฒธเฒ‚เฒœเณ†'; + } + + return 'เฒฐเฒพเฒคเณเฒฐเฒฟ'; + }, + 'months' => ['เฒœเฒจเฒตเฒฐเฒฟ', 'เฒซเณ†เฒฌเณเฒฐเฒตเฒฐเฒฟ', 'เฒฎเฒพเฒฐเณเฒšเณ', 'เฒเฒชเณเฒฐเฒฟเฒฒเณ', 'เฒฎเณ†เณ•', 'เฒœเณ‚เฒจเณ', 'เฒœเณเฒฒเณ†เณ–', 'เฒ†เฒ—เฒธเณเฒŸเณ', 'เฒธเณ†เฒชเณเฒŸเณ†เฒ‚เฒฌเฒฐเณ', 'เฒ…เฒ•เณเฒŸเณ†เณ‚เณ•เฒฌเฒฐเณ', 'เฒจเฒตเณ†เฒ‚เฒฌเฒฐเณ', 'เฒกเฒฟเฒธเณ†เฒ‚เฒฌเฒฐเณ'], + 'months_short' => ['เฒœเฒจ', 'เฒซเณ†เฒฌเณเฒฐ', 'เฒฎเฒพเฒฐเณเฒšเณ', 'เฒเฒชเณเฒฐเฒฟเฒฒเณ', 'เฒฎเณ†เณ•', 'เฒœเณ‚เฒจเณ', 'เฒœเณเฒฒเณ†เณ–', 'เฒ†เฒ—เฒธเณเฒŸเณ', 'เฒธเณ†เฒชเณเฒŸเณ†เฒ‚', 'เฒ…เฒ•เณเฒŸเณ†เณ‚เณ•', 'เฒจเฒตเณ†เฒ‚', 'เฒกเฒฟเฒธเณ†เฒ‚'], + 'weekdays' => ['เฒญเฒพเฒจเณเฒตเฒพเฒฐ', 'เฒธเณ†เณ‚เณ•เฒฎเฒตเฒพเฒฐ', 'เฒฎเฒ‚เฒ—เฒณเฒตเฒพเฒฐ', 'เฒฌเณเฒงเฒตเฒพเฒฐ', 'เฒ—เณเฒฐเณเฒตเฒพเฒฐ', 'เฒถเณเฒ•เณเฒฐเฒตเฒพเฒฐ', 'เฒถเฒจเฒฟเฒตเฒพเฒฐ'], + 'weekdays_short' => ['เฒญเฒพเฒจเณ', 'เฒธเณ†เณ‚เณ•เฒฎ', 'เฒฎเฒ‚เฒ—เฒณ', 'เฒฌเณเฒง', 'เฒ—เณเฒฐเณ', 'เฒถเณเฒ•เณเฒฐ', 'เฒถเฒจเฒฟ'], + 'weekdays_min' => ['เฒญเฒพ', 'เฒธเณ†เณ‚เณ•', 'เฒฎเฒ‚', 'เฒฌเณ', 'เฒ—เณ', 'เฒถเณ', 'เฒถ'], + 'list' => ', ', + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kn_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kn_IN.php new file mode 100644 index 0000000000..30e3d8869c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kn_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/kn.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ko.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ko.php new file mode 100644 index 0000000000..4fa6237657 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ko.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - FourwingsY + * - Franรงois B + * - Jason Katz-Brown + * - Seokjun Kim + * - Junho Kim + * - JD Isaacks + * - Juwon Kim + */ +return [ + 'year' => ':count๋…„', + 'a_year' => '{1}์ผ๋…„|]1,Inf[:count๋…„', + 'y' => ':count๋…„', + 'month' => ':count๊ฐœ์›”', + 'a_month' => '{1}ํ•œ๋‹ฌ|]1,Inf[:count๊ฐœ์›”', + 'm' => ':count๊ฐœ์›”', + 'week' => ':count์ฃผ', + 'a_week' => '{1}์ผ์ฃผ์ผ|]1,Inf[:count ์ฃผ', + 'w' => ':count์ฃผ์ผ', + 'day' => ':count์ผ', + 'a_day' => '{1}ํ•˜๋ฃจ|]1,Inf[:count์ผ', + 'd' => ':count์ผ', + 'hour' => ':count์‹œ๊ฐ„', + 'a_hour' => '{1}ํ•œ์‹œ๊ฐ„|]1,Inf[:count์‹œ๊ฐ„', + 'h' => ':count์‹œ๊ฐ„', + 'minute' => ':count๋ถ„', + 'a_minute' => '{1}์ผ๋ถ„|]1,Inf[:count๋ถ„', + 'min' => ':count๋ถ„', + 'second' => ':count์ดˆ', + 'a_second' => '{1}๋ช‡์ดˆ|]1,Inf[:count์ดˆ', + 's' => ':count์ดˆ', + 'ago' => ':time ์ „', + 'from_now' => ':time ํ›„', + 'after' => ':time ํ›„', + 'before' => ':time ์ „', + 'diff_now' => '์ง€๊ธˆ', + 'diff_today' => '์˜ค๋Š˜', + 'diff_yesterday' => '์–ด์ œ', + 'diff_tomorrow' => '๋‚ด์ผ', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'YYYY.MM.DD.', + 'LL' => 'YYYY๋…„ MMMM D์ผ', + 'LLL' => 'YYYY๋…„ MMMM D์ผ A h:mm', + 'LLLL' => 'YYYY๋…„ MMMM D์ผ dddd A h:mm', + ], + 'calendar' => [ + 'sameDay' => '์˜ค๋Š˜ LT', + 'nextDay' => '๋‚ด์ผ LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '์–ด์ œ LT', + 'lastWeek' => '์ง€๋‚œ์ฃผ dddd LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'์ผ'; + case 'M': + return $number.'์›”'; + case 'w': + case 'W': + return $number.'์ฃผ'; + default: + return $number; + } + }, + 'meridiem' => ['์˜ค์ „', '์˜คํ›„'], + 'months' => ['1์›”', '2์›”', '3์›”', '4์›”', '5์›”', '6์›”', '7์›”', '8์›”', '9์›”', '10์›”', '11์›”', '12์›”'], + 'months_short' => ['1์›”', '2์›”', '3์›”', '4์›”', '5์›”', '6์›”', '7์›”', '8์›”', '9์›”', '10์›”', '11์›”', '12์›”'], + 'weekdays' => ['์ผ์š”์ผ', '์›”์š”์ผ', 'ํ™”์š”์ผ', '์ˆ˜์š”์ผ', '๋ชฉ์š”์ผ', '๊ธˆ์š”์ผ', 'ํ† ์š”์ผ'], + 'weekdays_short' => ['์ผ', '์›”', 'ํ™”', '์ˆ˜', '๋ชฉ', '๊ธˆ', 'ํ† '], + 'weekdays_min' => ['์ผ', '์›”', 'ํ™”', '์ˆ˜', '๋ชฉ', '๊ธˆ', 'ํ† '], + 'list' => ' ', +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ko_KP.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ko_KP.php new file mode 100644 index 0000000000..4ba802b359 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ko_KP.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ko.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ko_KR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ko_KR.php new file mode 100644 index 0000000000..9d873a27b0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ko_KR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ko.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kok.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kok.php new file mode 100644 index 0000000000..4adcddccac --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kok.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kok_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kok_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kok_IN.php new file mode 100644 index 0000000000..92ba844c5c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kok_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D-M-YY', + ], + 'months' => ['เคœเคพเคจเฅ‡เคตเคพเคฐเฅ€', 'เคซเฅ‡เคฌเฅเคฐเฅเคตเคพเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เคเคชเฅเคฐเคฟเคฒ', 'เคฎเฅ‡', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเฅˆ', 'เค“เค—เคธเฅเคŸ', 'เคธเฅ‡เคชเฅเคŸเฅ‡เค‚เคฌเคฐ', 'เค“เค•เฅเคŸเฅ‹เคฌเคฐ', 'เคจเฅ‹เคตเฅเคนเฅ‡เค‚เคฌเคฐ', 'เคกเคฟเคธเฅ‡เค‚เคฌเคฐ'], + 'months_short' => ['เคœเคพเคจเฅ‡เคตเคพเคฐเฅ€', 'เคซเฅ‡เคฌเฅเคฐเฅเคตเคพเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เคเคชเฅเคฐเคฟเคฒ', 'เคฎเฅ‡', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเฅˆ', 'เค“เค—เคธเฅเคŸ', 'เคธเฅ‡เคชเฅเคŸเฅ‡เค‚เคฌเคฐ', 'เค“เค•เฅเคŸเฅ‹เคฌเคฐ', 'เคจเฅ‹เคตเฅเคนเฅ‡เค‚เคฌเคฐ', 'เคกเคฟเคธเฅ‡เค‚เคฌเคฐ'], + 'weekdays' => ['เค†เคฏเคคเคพเคฐ', 'เคธเฅ‹เคฎเคพเคฐ', 'เคฎเค‚เค—เคณเคตเคพเคฐ', 'เคฌเฅเคงเคตเคพเคฐ', 'เคฌเฅ‡เคฐเฅ‡เคธเคคเคพเคฐ', 'เคถเฅเค•เคฐเคพเคฐ', 'เคถเฅ‡เคจเคตเคพเคฐ'], + 'weekdays_short' => ['เค†เคฏเคคเคพเคฐ', 'เคธเฅ‹เคฎเคพเคฐ', 'เคฎเค‚เค—เคณเคตเคพเคฐ', 'เคฌเฅเคงเคตเคพเคฐ', 'เคฌเฅ‡เคฐเฅ‡เคธเคคเคพเคฐ', 'เคถเฅเค•เคฐเคพเคฐ', 'เคถเฅ‡เคจเคตเคพเคฐ'], + 'weekdays_min' => ['เค†เคฏเคคเคพเคฐ', 'เคธเฅ‹เคฎเคพเคฐ', 'เคฎเค‚เค—เคณเคตเคพเคฐ', 'เคฌเฅเคงเคตเคพเคฐ', 'เคฌเฅ‡เคฐเฅ‡เคธเคคเคพเคฐ', 'เคถเฅเค•เคฐเคพเคฐ', 'เคถเฅ‡เคจเคตเคพเคฐ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เคฎ.เคชเฅ‚.', 'เคฎ.เคจเค‚.'], + + 'year' => ':count เคตเฅˆเคถเคพเค•เฅ', // less reliable + 'y' => ':count เคตเฅˆเคถเคพเค•เฅ', // less reliable + 'a_year' => ':count เคตเฅˆเคถเคพเค•เฅ', // less reliable + + 'week' => ':count เค†เคฆเคฟเคคเฅเคฏเคตเคพเคฐ', // less reliable + 'w' => ':count เค†เคฆเคฟเคคเฅเคฏเคตเคพเคฐ', // less reliable + 'a_week' => ':count เค†เคฆเคฟเคคเฅเคฏเคตเคพเคฐ', // less reliable + + 'minute' => ':count เคจเฅ‹เค‚เคฆ', // less reliable + 'min' => ':count เคจเฅ‹เค‚เคฆ', // less reliable + 'a_minute' => ':count เคจเฅ‹เค‚เคฆ', // less reliable + + 'second' => ':count เคคเฅ‡เค‚เค•เฅ‹', // less reliable + 's' => ':count เคคเฅ‡เค‚เค•เฅ‹', // less reliable + 'a_second' => ':count เคคเฅ‡เค‚เค•เฅ‹', // less reliable + + 'month' => ':count เคฎเฅˆเคจเฅ‹', + 'm' => ':count เคฎเฅˆเคจเฅ‹', + 'a_month' => ':count เคฎเฅˆเคจเฅ‹', + + 'day' => ':count เคฆเคฟเคตเคธเฅ', + 'd' => ':count เคฆเคฟเคตเคธเฅ', + 'a_day' => ':count เคฆเคฟเคตเคธเฅ', + + 'hour' => ':count เค˜เค‚เคคเฅ‡', + 'h' => ':count เค˜เค‚เคคเฅ‡', + 'a_hour' => ':count เค˜เค‚เคคเฅ‡', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks.php new file mode 100644 index 0000000000..9876079051 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ks_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN.php new file mode 100644 index 0000000000..ce9d5d4ae9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'M/D/YY', + ], + 'months' => ['ุฌู†ุคุฑŒ', 'ูุฑุคุฑŒ', 'ู…ุงุฑู•ฺ†', 'ุงูพุฑŒู„', 'ู…Œู”', 'ุฌูˆู—ู†', 'ุฌูˆู—ู„ุงŒŒ', 'ุงฺฏุณุช', 'ุณุชู…ุจุฑ', 'ุงฺฉุชูˆู—ุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'months_short' => ['ุฌู†ุคุฑŒ', 'ูุฑุคุฑŒ', 'ู…ุงุฑู•ฺ†', 'ุงูพุฑŒู„', 'ู…Œู”', 'ุฌูˆู—ู†', 'ุฌูˆู—ู„ุงŒŒ', 'ุงฺฏุณุช', 'ุณุชู…ุจุฑ', 'ุงฺฉุชูˆู—ุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'weekdays' => ['ุขุชู‡ูˆุงุฑ', 'ฺ˜ุกู†ุฏุฑูˆุงุฑ', 'ุจูˆุกฺบูˆุงุฑ', 'ุจูˆุฏู‡ูˆุงุฑ', 'ุจุฑู‰ุณูˆุงุฑ', 'ุฌู…ุน', 'ุจูนูˆุงุฑ'], + 'weekdays_short' => ['ุขุชู‡ูˆุงุฑ', 'ฺ˜ุกู†ุชุฑูˆุงุฑ', 'ุจูˆุกฺบูˆุงุฑ', 'ุจูˆุฏู‡ูˆุงุฑ', 'ุจุฑู‰ุณูˆุงุฑ', 'ุฌู…ุน', 'ุจูนูˆุงุฑ'], + 'weekdays_min' => ['ุขุชู‡ูˆุงุฑ', 'ฺ˜ุกู†ุชุฑูˆุงุฑ', 'ุจูˆุกฺบูˆุงุฑ', 'ุจูˆุฏู‡ูˆุงุฑ', 'ุจุฑู‰ุณูˆุงุฑ', 'ุฌู…ุน', 'ุจูนูˆุงุฑ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ุฏูˆูพฺพุฑุจุฑูˆู†ฺพ', 'ุฏูˆูพฺพุฑูพุชฺพ'], + + 'year' => ':count ุขุจ', // less reliable + 'y' => ':count ุขุจ', // less reliable + 'a_year' => ':count ุขุจ', // less reliable + + 'month' => ':count เคฐเคพเคจเฅ', // less reliable + 'm' => ':count เคฐเคพเคจเฅ', // less reliable + 'a_month' => ':count เคฐเคพเคจเฅ', // less reliable + + 'week' => ':count ุขุชฺพู•ูˆุงุฑ', // less reliable + 'w' => ':count ุขุชฺพู•ูˆุงุฑ', // less reliable + 'a_week' => ':count ุขุชฺพู•ูˆุงุฑ', // less reliable + + 'hour' => ':count ุณ„ู†', // less reliable + 'h' => ':count ุณ„ู†', // less reliable + 'a_hour' => ':count ุณ„ู†', // less reliable + + 'minute' => ':count ููŽู†', // less reliable + 'min' => ':count ููŽู†', // less reliable + 'a_minute' => ':count ููŽู†', // less reliable + + 'second' => ':count เคฆเฅ‹สผเคฏเฅเคฎ', // less reliable + 's' => ':count เคฆเฅ‹สผเคฏเฅเคฎ', // less reliable + 'a_second' => ':count เคฆเฅ‹สผเคฏเฅเคฎ', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN@devanagari.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN@devanagari.php new file mode 100644 index 0000000000..a2ae8b64f9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN@devanagari.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ks-gnome-trans-commits@lists.code.indlinux.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'M/D/YY', + ], + 'months' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคผเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅ‡เคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเคฎเฅเคฌเคฐ', 'เค…เค•เฅเคŸเฅ‚เคฌเคฐ', 'เคจเคตเคฎเฅเคฌเคฐ', 'เคฆเคฟเคธเคฎเฅเคฌเคฐ'], + 'months_short' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคผเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅ‡เคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเคฎเฅเคฌเคฐ', 'เค…เค•เฅเคŸเฅ‚เคฌเคฐ', 'เคจเคตเคฎเฅเคฌเคฐ', 'เคฆเคฟเคธเคฎเฅเคฌเคฐ'], + 'weekdays' => ['เค†เคฅเคตเคพเคฐ', 'เคšเฅ…เคผเคฆเฅเคฐเคตเคพเคฐ', 'เคฌเฅ‹เคฎเคตเคพเคฐ', 'เคฌเฅเคตเคฆเคตเคพเคฐ', 'เคฌเฅเคฐเคธเคตเคพเคฐ', 'เคถเฅ‹เค•เฅเคฐเคตเคพเคฐ', 'เคฌเคŸเฅเคตเคพเคฐ'], + 'weekdays_short' => ['เค†เคฅ ', 'เคšเฅ…เคผเคฆเฅเคฐ', 'เคฌเฅ‹เคฎ', 'เคฌเฅเคตเคฆ', 'เคฌเฅเคฐเคธ', 'เคถเฅ‹เค•เฅเคฐ', 'เคฌเคŸเฅ'], + 'weekdays_min' => ['เค†เคฅ ', 'เคšเฅ…เคผเคฆเฅเคฐ', 'เคฌเฅ‹เคฎ', 'เคฌเฅเคตเคฆ', 'เคฌเฅเคฐเคธ', 'เคถเฅ‹เค•เฅเคฐ', 'เคฌเคŸเฅ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เคชเฅ‚เคฐเฅเคตเคพเคนเฅเคจ', 'เค…เคชเคฐเคพเคนเฅเคจ'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ksb.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ksb.php new file mode 100644 index 0000000000..aaa00614cb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ksb.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['makeo', 'nyiaghuo'], + 'weekdays' => ['Jumaapii', 'Jumaatatu', 'Jumaane', 'Jumaatano', 'Alhamisi', 'Ijumaa', 'Jumaamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jmn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jmn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januali', 'Febluali', 'Machi', 'Aplili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ksf.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ksf.php new file mode 100644 index 0000000000..84a5967298 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ksf.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['sรกrรบwรก', 'cษ›ษ›ฬnko'], + 'weekdays' => ['sษ”ฬndว', 'lวndรญ', 'maadรญ', 'mษ›krษ›dรญ', 'jววdรญ', 'jรบmbรก', 'samdรญ'], + 'weekdays_short' => ['sษ”ฬn', 'lวn', 'maa', 'mษ›k', 'jวว', 'jรบm', 'sam'], + 'weekdays_min' => ['sษ”ฬn', 'lวn', 'maa', 'mษ›k', 'jวว', 'jรบm', 'sam'], + 'months' => ['ล‹wรญรญ a ntษ”ฬntษ”', 'ล‹wรญรญ akว bษ›ฬษ›', 'ล‹wรญรญ akว rรกรก', 'ล‹wรญรญ akว nin', 'ล‹wรญรญ akว tรกan', 'ล‹wรญรญ akว tรกafษ”k', 'ล‹wรญรญ akว tรกabษ›ษ›', 'ล‹wรญรญ akว tรกaraa', 'ล‹wรญรญ akว tรกanin', 'ล‹wรญรญ akว ntษ›k', 'ล‹wรญรญ akว ntษ›k di bษ”ฬk', 'ล‹wรญรญ akว ntษ›k di bษ›ฬษ›'], + 'months_short' => ['ล‹1', 'ล‹2', 'ล‹3', 'ล‹4', 'ล‹5', 'ล‹6', 'ล‹7', 'ล‹8', 'ล‹9', 'ล‹10', 'ล‹11', 'ล‹12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ksh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ksh.php new file mode 100644 index 0000000000..95457e2435 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ksh.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['v.M.', 'n.M.'], + 'weekdays' => ['Sunndaach', 'Mohndaach', 'Dinnsdaach', 'Metwoch', 'Dunnersdaach', 'Friidaach', 'Samsdaach'], + 'weekdays_short' => ['Su.', 'Mo.', 'Di.', 'Me.', 'Du.', 'Fr.', 'Sa.'], + 'weekdays_min' => ['Su', 'Mo', 'Di', 'Me', 'Du', 'Fr', 'Sa'], + 'months' => ['Jannewa', 'Fรคbrowa', 'Mรครคz', 'Aprell', 'Mai', 'Juuni', 'Juuli', 'OujoรŸ', 'Septรคmber', 'Oktohber', 'Novรคmber', 'Dezรคmber'], + 'months_short' => ['Jan', 'Fรคb', 'Mรคz', 'Apr', 'Mai', 'Jun', 'Jul', 'Ouj', 'Sรคp', 'Okt', 'Nov', 'Dez'], + 'months_short_standalone' => ['Jan.', 'Fรคb.', 'Mรคz.', 'Apr.', 'Mai', 'Jun.', 'Jul.', 'Ouj.', 'Sรคp.', 'Okt.', 'Nov.', 'Dez.'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D. M. YYYY', + 'LL' => 'D. MMM. YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, [dรค] D. MMMM YYYY HH:mm', + ], + + 'year' => ':count Johr', + 'y' => ':count Johr', + 'a_year' => ':count Johr', + + 'month' => ':count Moohnd', + 'm' => ':count Moohnd', + 'a_month' => ':count Moohnd', + + 'week' => ':count woch', + 'w' => ':count woch', + 'a_week' => ':count woch', + + 'day' => ':count Daach', + 'd' => ':count Daach', + 'a_day' => ':count Daach', + + 'hour' => ':count Uhr', + 'h' => ':count Uhr', + 'a_hour' => ':count Uhr', + + 'minute' => ':count Menutt', + 'min' => ':count Menutt', + 'a_minute' => ':count Menutt', + + 'second' => ':count Sekลฏndt', + 's' => ':count Sekลฏndt', + 'a_second' => ':count Sekลฏndt', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ku.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ku.php new file mode 100644 index 0000000000..189960c8ab --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ku.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Unicode, Inc. + */ + +return [ + 'ago' => 'berรฎ :time', + 'from_now' => 'di :time de', + 'after' => ':time piลŸtรฎ', + 'before' => ':time berรช', + 'year' => ':count sal', + 'year_ago' => ':count salรช|:count salan', + 'year_from_now' => 'salekรช|:count salan', + 'month' => ':count meh', + 'week' => ':count hefte', + 'day' => ':count roj', + 'hour' => ':count saet', + 'minute' => ':count deqรฎqe', + 'second' => ':count saniye', + 'months' => ['rรชbendanรช', 'reลŸemiyรช', 'adarรช', 'avrรชlรช', 'gulanรช', 'pรปลŸperรช', 'tรฎrmehรช', 'gelawรชjรช', 'rezberรช', 'kewรงรชrรช', 'sermawezรช', 'berfanbarรช'], + 'months_standalone' => ['rรชbendan', 'reลŸemรฎ', 'adar', 'avrรชl', 'gulan', 'pรปลŸper', 'tรฎrmeh', 'gelawรชj', 'rezber', 'kewรงรชr', 'sermawez', 'berfanbar'], + 'months_short' => ['rรชb', 'reลŸ', 'ada', 'avr', 'gul', 'pรปลŸ', 'tรฎr', 'gel', 'rez', 'kew', 'ser', 'ber'], + 'weekdays' => ['yekลŸem', 'duลŸem', 'sรชลŸem', 'รงarลŸem', 'pรชncลŸem', 'รฎn', 'ลŸemรฎ'], + 'weekdays_short' => ['yลŸ', 'dลŸ', 'sลŸ', 'รงลŸ', 'pลŸ', 'รฎn', 'ลŸ'], + 'weekdays_min' => ['Y', 'D', 'S', 'ร‡', 'P', 'รŽ', 'ลž'], + 'list' => [', ', ' รป '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ku_TR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ku_TR.php new file mode 100644 index 0000000000..4243a82fc5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ku_TR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ku.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kw.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kw.php new file mode 100644 index 0000000000..26e242e7d7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kw_GB.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kw_GB.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kw_GB.php new file mode 100644 index 0000000000..00bf52bd56 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kw_GB.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alastair McKinstry bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['mis Genver', 'mis Hwevrer', 'mis Meurth', 'mis Ebrel', 'mis Me', 'mis Metheven', 'mis Gortheren', 'mis Est', 'mis Gwynngala', 'mis Hedra', 'mis Du', 'mis Kevardhu'], + 'months_short' => ['Gen', 'Hwe', 'Meu', 'Ebr', 'Me', 'Met', 'Gor', 'Est', 'Gwn', 'Hed', 'Du', 'Kev'], + 'weekdays' => ['De Sul', 'De Lun', 'De Merth', 'De Merher', 'De Yow', 'De Gwener', 'De Sadorn'], + 'weekdays_short' => ['Sul', 'Lun', 'Mth', 'Mhr', 'Yow', 'Gwe', 'Sad'], + 'weekdays_min' => ['Sul', 'Lun', 'Mth', 'Mhr', 'Yow', 'Gwe', 'Sad'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count bledhen', + 'y' => ':count bledhen', + 'a_year' => ':count bledhen', + + 'month' => ':count mis', + 'm' => ':count mis', + 'a_month' => ':count mis', + + 'week' => ':count seythen', + 'w' => ':count seythen', + 'a_week' => ':count seythen', + + 'day' => ':count dydh', + 'd' => ':count dydh', + 'a_day' => ':count dydh', + + 'hour' => ':count eur', + 'h' => ':count eur', + 'a_hour' => ':count eur', + + 'minute' => ':count mynysen', + 'min' => ':count mynysen', + 'a_minute' => ':count mynysen', + + 'second' => ':count pryjwyth', + 's' => ':count pryjwyth', + 'a_second' => ':count pryjwyth', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ky.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ky.php new file mode 100644 index 0000000000..e0d1af103b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ky.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - acutexyz + * - Josh Soref + * - Franรงois B + * - Chyngyz Arystan uulu + * - Chyngyz + * - acutexyz + * - Josh Soref + * - Franรงois B + * - Chyngyz Arystan uulu + */ +return [ + 'year' => ':count ะถั‹ะป', + 'a_year' => '{1}ะฑะธั€ ะถั‹ะป|:count ะถั‹ะป', + 'y' => ':count ะถั‹ะป', + 'month' => ':count ะฐะน', + 'a_month' => '{1}ะฑะธั€ ะฐะน|:count ะฐะน', + 'm' => ':count ะฐะน', + 'week' => ':count ะฐะฟั‚ะฐ', + 'a_week' => '{1}ะฑะธั€ ะฐะฟั‚ะฐ|:count ะฐะฟั‚ะฐ', + 'w' => ':count ะฐะฟั‚.', + 'day' => ':count ะบาฏะฝ', + 'a_day' => '{1}ะฑะธั€ ะบาฏะฝ|:count ะบาฏะฝ', + 'd' => ':count ะบาฏะฝ', + 'hour' => ':count ัะฐะฐั‚', + 'a_hour' => '{1}ะฑะธั€ ัะฐะฐั‚|:count ัะฐะฐั‚', + 'h' => ':count ัะฐะฐั‚.', + 'minute' => ':count ะผาฏะฝำฉั‚', + 'a_minute' => '{1}ะฑะธั€ ะผาฏะฝำฉั‚|:count ะผาฏะฝำฉั‚', + 'min' => ':count ะผาฏะฝ.', + 'second' => ':count ัะตะบัƒะฝะด', + 'a_second' => '{1}ะฑะธั€ะฝะตั‡ะต ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะด', + 's' => ':count ัะตะบ.', + 'ago' => ':time ะผัƒั€ัƒะฝ', + 'from_now' => ':time ะธั‡ะธะฝะดะต', + 'diff_now' => 'ะฐะทั‹ั€', + 'diff_today' => 'ะ‘าฏะณาฏะฝ', + 'diff_today_regexp' => 'ะ‘าฏะณาฏะฝ(?:\\s+ัะฐะฐั‚)?', + 'diff_yesterday' => 'ะบะตั‡ัั', + 'diff_yesterday_regexp' => 'ะšะตั‡ะต(?:\\s+ัะฐะฐั‚)?', + 'diff_tomorrow' => 'ัั€ั‚ะตาฃ', + 'diff_tomorrow_regexp' => 'ะญั€ั‚ะตาฃ(?:\\s+ัะฐะฐั‚)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ะ‘าฏะณาฏะฝ ัะฐะฐั‚] LT', + 'nextDay' => '[ะญั€ั‚ะตาฃ ัะฐะฐั‚] LT', + 'nextWeek' => 'dddd [ัะฐะฐั‚] LT', + 'lastDay' => '[ะšะตั‡ะต ัะฐะฐั‚] LT', + 'lastWeek' => '[ำจั‚ะบะตะฝ ะฐะฟั‚ะฐะฝั‹ะฝ] dddd [ะบาฏะฝาฏ] [ัะฐะฐั‚] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + static $suffixes = [ + 0 => '-ั‡าฏ', + 1 => '-ั‡ะธ', + 2 => '-ั‡ะธ', + 3 => '-ั‡าฏ', + 4 => '-ั‡าฏ', + 5 => '-ั‡ะธ', + 6 => '-ั‡ั‹', + 7 => '-ั‡ะธ', + 8 => '-ั‡ะธ', + 9 => '-ั‡ัƒ', + 10 => '-ั‡ัƒ', + 20 => '-ั‡ั‹', + 30 => '-ั‡ัƒ', + 40 => '-ั‡ั‹', + 50 => '-ั‡าฏ', + 60 => '-ั‡ั‹', + 70 => '-ั‡ะธ', + 80 => '-ั‡ะธ', + 90 => '-ั‡ัƒ', + 100 => '-ั‡าฏ', + ]; + + return $number.($suffixes[$number] ?? $suffixes[$number % 10] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'months' => ['ัะฝะฒะฐั€ัŒ', 'ั„ะตะฒั€ะฐะปัŒ', 'ะผะฐั€ั‚', 'ะฐะฟั€ะตะปัŒ', 'ะผะฐะน', 'ะธัŽะฝัŒ', 'ะธัŽะปัŒ', 'ะฐะฒะณัƒัั‚', 'ัะตะฝั‚ัะฑั€ัŒ', 'ะพะบั‚ัะฑั€ัŒ', 'ะฝะพัะฑั€ัŒ', 'ะดะตะบะฐะฑั€ัŒ'], + 'months_short' => ['ัะฝะฒ', 'ั„ะตะฒ', 'ะผะฐั€ั‚', 'ะฐะฟั€', 'ะผะฐะน', 'ะธัŽะฝัŒ', 'ะธัŽะปัŒ', 'ะฐะฒะณ', 'ัะตะฝ', 'ะพะบั‚', 'ะฝะพั', 'ะดะตะบ'], + 'weekdays' => ['ะ–ะตะบัˆะตะผะฑะธ', 'ะ”าฏะนัˆำฉะผะฑาฏ', 'ะจะตะนัˆะตะผะฑะธ', 'ะจะฐั€ัˆะตะผะฑะธ', 'ะ‘ะตะนัˆะตะผะฑะธ', 'ะ–ัƒะผะฐ', 'ะ˜ัˆะตะผะฑะธ'], + 'weekdays_short' => ['ะ–ะตะบ', 'ะ”าฏะน', 'ะจะตะน', 'ะจะฐั€', 'ะ‘ะตะน', 'ะ–ัƒะผ', 'ะ˜ัˆะต'], + 'weekdays_min' => ['ะ–ะบ', 'ะ”ะน', 'ะจะน', 'ะจั€', 'ะ‘ะน', 'ะ–ะผ', 'ะ˜ัˆ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => ' ', + 'meridiem' => ['ั‚ะฐาฃะบั‹', 'ั‚าฏัˆั‚ำฉะฝ ะบะธะนะธะฝะบะธ'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ky_KG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ky_KG.php new file mode 100644 index 0000000000..9923a31e8f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ky_KG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ky.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lag.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lag.php new file mode 100644 index 0000000000..f3f57f6a69 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lag.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['TOO', 'MUU'], + 'weekdays' => ['Jumapรญiri', 'Jumatรกtu', 'Jumaรญne', 'Jumatรกano', 'Alamรญisi', 'Ijumรกa', 'Jumamรณosi'], + 'weekdays_short' => ['Pรญili', 'Tรกatu', 'รne', 'Tรกano', 'Alh', 'Ijm', 'Mรณosi'], + 'weekdays_min' => ['Pรญili', 'Tรกatu', 'รne', 'Tรกano', 'Alh', 'Ijm', 'Mรณosi'], + 'months' => ['Kส‰fรบngatษจ', 'Kส‰naanษจ', 'Kส‰keenda', 'Kwiikumi', 'Kwiinyambรกla', 'Kwiidwaata', 'Kส‰mส‰ส‰nchษจ', 'Kส‰vษจษจrษจ', 'Kส‰saatส‰', 'Kwiinyi', 'Kส‰saano', 'Kส‰sasatส‰'], + 'months_short' => ['Fรบngatษจ', 'Naanษจ', 'Keenda', 'Ikรบmi', 'Inyambala', 'Idwaata', 'Mส‰ส‰nchษจ', 'Vษจษจrษจ', 'Saatส‰', 'Inyi', 'Saano', 'Sasatส‰'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lb.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lb.php new file mode 100644 index 0000000000..7636655e3a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lb.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - dan-nl + * - Simon Lelorrain (slelorrain) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count Joer', + 'y' => ':countJ', + 'month' => ':count Mount|:count Mรฉint', + 'm' => ':countMo', + 'week' => ':count Woch|:count Wochen', + 'w' => ':countWo|:countWo', + 'day' => ':count Dag|:count Deeg', + 'd' => ':countD', + 'hour' => ':count Stonn|:count Stonnen', + 'h' => ':countSto', + 'minute' => ':count Minutt|:count Minutten', + 'min' => ':countM', + 'second' => ':count Sekonn|:count Sekonnen', + 's' => ':countSek', + + 'ago' => 'virun :time', + 'from_now' => 'an :time', + 'before' => ':time virdrun', + 'after' => ':time duerno', + + 'diff_today' => 'Haut', + 'diff_yesterday' => 'Gรซschter', + 'diff_yesterday_regexp' => 'Gรซschter(?:\\s+um)?', + 'diff_tomorrow' => 'Muer', + 'diff_tomorrow_regexp' => 'Muer(?:\\s+um)?', + 'diff_today_regexp' => 'Haut(?:\\s+um)?', + 'formats' => [ + 'LT' => 'H:mm [Auer]', + 'LTS' => 'H:mm:ss [Auer]', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm [Auer]', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm [Auer]', + ], + + 'calendar' => [ + 'sameDay' => '[Haut um] LT', + 'nextDay' => '[Muer um] LT', + 'nextWeek' => 'dddd [um] LT', + 'lastDay' => '[Gรซschter um] LT', + 'lastWeek' => function (CarbonInterface $date) { + // Different date string for 'Dรซnschdeg' (Tuesday) and 'Donneschdeg' (Thursday) due to phonological rule + switch ($date->dayOfWeek) { + case 2: + case 4: + return '[Leschten] dddd [um] LT'; + default: + return '[Leschte] dddd [um] LT'; + } + }, + 'sameElse' => 'L', + ], + + 'months' => ['Januar', 'Februar', 'Mรคerz', 'Abrรซll', 'Mee', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan.', 'Febr.', 'Mrz.', 'Abr.', 'Mee', 'Jun.', 'Jul.', 'Aug.', 'Sept.', 'Okt.', 'Nov.', 'Dez.'], + 'weekdays' => ['Sonndeg', 'Mรฉindeg', 'Dรซnschdeg', 'Mรซttwoch', 'Donneschdeg', 'Freideg', 'Samschdeg'], + 'weekdays_short' => ['So.', 'Mรฉ.', 'Dรซ.', 'Mรซ.', 'Do.', 'Fr.', 'Sa.'], + 'weekdays_min' => ['So', 'Mรฉ', 'Dรซ', 'Mรซ', 'Do', 'Fr', 'Sa'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' an '], + 'meridiem' => ['moies', 'mรซttes'], + 'weekdays_short_standalone' => ['Son', 'Mรฉi', 'Dรซn', 'Mรซt', 'Don', 'Fre', 'Sam'], + 'months_short_standalone' => ['Jan', 'Feb', 'Mรคe', 'Abr', 'Mee', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lb_LU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lb_LU.php new file mode 100644 index 0000000000..414bd4d011 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lb_LU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/lb.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lg.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lg.php new file mode 100644 index 0000000000..48bc68befb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lg.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/lg_UG.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lg_UG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lg_UG.php new file mode 100644 index 0000000000..aa0221404e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lg_UG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Akademe ya Luganda Kizito Birabwa kompyuta@kizito.uklinux.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Janwaliyo', 'Febwaliyo', 'Marisi', 'Apuli', 'Maayi', 'Juuni', 'Julaayi', 'Agusito', 'Sebuttemba', 'Okitobba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apu', 'Maa', 'Juu', 'Jul', 'Agu', 'Seb', 'Oki', 'Nov', 'Des'], + 'weekdays' => ['Sabiiti', 'Balaza', 'Lwakubiri', 'Lwakusatu', 'Lwakuna', 'Lwakutaano', 'Lwamukaaga'], + 'weekdays_short' => ['Sab', 'Bal', 'Lw2', 'Lw3', 'Lw4', 'Lw5', 'Lw6'], + 'weekdays_min' => ['Sab', 'Bal', 'Lw2', 'Lw3', 'Lw4', 'Lw5', 'Lw6'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'month' => ':count njuba', // less reliable + 'm' => ':count njuba', // less reliable + 'a_month' => ':count njuba', // less reliable + + 'year' => ':count mwaaka', + 'y' => ':count mwaaka', + 'a_year' => ':count mwaaka', + + 'week' => ':count sabbiiti', + 'w' => ':count sabbiiti', + 'a_week' => ':count sabbiiti', + + 'day' => ':count lunaku', + 'd' => ':count lunaku', + 'a_day' => ':count lunaku', + + 'hour' => 'saawa :count', + 'h' => 'saawa :count', + 'a_hour' => 'saawa :count', + + 'minute' => 'ddakiika :count', + 'min' => 'ddakiika :count', + 'a_minute' => 'ddakiika :count', + + 'second' => ':count kyสผokubiri', + 's' => ':count kyสผokubiri', + 'a_second' => ':count kyสผokubiri', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/li.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/li.php new file mode 100644 index 0000000000..86c3009eb7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/li.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/li_NL.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/li_NL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/li_NL.php new file mode 100644 index 0000000000..6c5feb79fc --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/li_NL.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['jannewarie', 'fibberwarie', 'miert', 'eprรจl', 'meij', 'junie', 'julie', 'augustus', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'fib', 'mie', 'epr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['zรณndig', 'maondig', 'daensdig', 'goonsdig', 'dรณnderdig', 'vriedig', 'zaoterdig'], + 'weekdays_short' => ['zรณn', 'mao', 'dae', 'goo', 'dรณn', 'vri', 'zao'], + 'weekdays_min' => ['zรณn', 'mao', 'dae', 'goo', 'dรณn', 'vri', 'zao'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'minute' => ':count momรชnt', // less reliable + 'min' => ':count momรชnt', // less reliable + 'a_minute' => ':count momรชnt', // less reliable + + 'year' => ':count jaor', + 'y' => ':count jaor', + 'a_year' => ':count jaor', + + 'month' => ':count maond', + 'm' => ':count maond', + 'a_month' => ':count maond', + + 'week' => ':count waek', + 'w' => ':count waek', + 'a_week' => ':count waek', + + 'day' => ':count daag', + 'd' => ':count daag', + 'a_day' => ':count daag', + + 'hour' => ':count oer', + 'h' => ':count oer', + 'a_hour' => ':count oer', + + 'second' => ':count Secรณnd', + 's' => ':count Secรณnd', + 'a_second' => ':count Secรณnd', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lij.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lij.php new file mode 100644 index 0000000000..45732b55d9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lij.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/lij_IT.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lij_IT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lij_IT.php new file mode 100644 index 0000000000..f8726fd2ca --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lij_IT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Gastaldi alessio.gastaldi@libero.it + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['zenรข', 'fevrรข', 'marzo', 'avrรฎ', 'mazzo', 'zรปgno', 'lรปggio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dixembre'], + 'months_short' => ['zen', 'fev', 'mar', 'arv', 'maz', 'zรปg', 'lรปg', 'ago', 'set', 'รถtt', 'nov', 'dix'], + 'weekdays' => ['domenega', 'lรปnedรฌ', 'martedรฌ', 'mercUrdรฌ', 'zรชggia', 'venardรฌ', 'sabbo'], + 'weekdays_short' => ['dom', 'lรปn', 'mar', 'mer', 'zรชu', 'ven', 'sab'], + 'weekdays_min' => ['dom', 'lรปn', 'mar', 'mer', 'zรชu', 'ven', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count etรฆ', // less reliable + 'y' => ':count etรฆ', // less reliable + 'a_year' => ':count etรฆ', // less reliable + + 'month' => ':count meize', + 'm' => ':count meize', + 'a_month' => ':count meize', + + 'week' => ':count settemannha', + 'w' => ':count settemannha', + 'a_week' => ':count settemannha', + + 'day' => ':count giorno', + 'd' => ':count giorno', + 'a_day' => ':count giorno', + + 'hour' => ':count relรฉuio', // less reliable + 'h' => ':count relรฉuio', // less reliable + 'a_hour' => ':count relรฉuio', // less reliable + + 'minute' => ':count menรปo', + 'min' => ':count menรปo', + 'a_minute' => ':count menรปo', + + 'second' => ':count segondo', + 's' => ':count segondo', + 'a_second' => ':count segondo', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lkt.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lkt.php new file mode 100644 index 0000000000..ae73a97b97 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lkt.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + + 'month' => ':count haล‹wรญ', // less reliable + 'm' => ':count haล‹wรญ', // less reliable + 'a_month' => ':count haล‹wรญ', // less reliable + + 'week' => ':count ลกakรณwiล‹', // less reliable + 'w' => ':count ลกakรณwiล‹', // less reliable + 'a_week' => ':count ลกakรณwiล‹', // less reliable + + 'hour' => ':count maza ลกkaล‹ลกkaล‹', // less reliable + 'h' => ':count maza ลกkaล‹ลกkaล‹', // less reliable + 'a_hour' => ':count maza ลกkaล‹ลกkaล‹', // less reliable + + 'minute' => ':count ฤรญkสผala', // less reliable + 'min' => ':count ฤรญkสผala', // less reliable + 'a_minute' => ':count ฤรญkสผala', // less reliable + + 'year' => ':count wanรญyetu', + 'y' => ':count wanรญyetu', + 'a_year' => ':count wanรญyetu', + + 'day' => ':count aล‹pรฉtu', + 'd' => ':count aล‹pรฉtu', + 'a_day' => ':count aล‹pรฉtu', + + 'second' => ':count icinuล‹pa', + 's' => ':count icinuล‹pa', + 'a_second' => ':count icinuล‹pa', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln.php new file mode 100644 index 0000000000..9d5c35dd89 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ubuntu Renรฉ Manassรฉ GALEKWA renemanasse@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'months' => ['sรกnzรก ya yambo', 'sรกnzรก ya mรญbalรฉ', 'sรกnzรก ya mรญsรกto', 'sรกnzรก ya mรญnei', 'sรกnzรก ya mรญtรกno', 'sรกnzรก ya motรณbรก', 'sรกnzรก ya nsambo', 'sรกnzรก ya mwambe', 'sรกnzรก ya libwa', 'sรกnzรก ya zรณmi', 'sรกnzรก ya zรณmi na mษ”ฬŒkษ”ฬ', 'sรกnzรก ya zรณmi na mรญbalรฉ'], + 'months_short' => ['yan', 'fbl', 'msi', 'apl', 'mai', 'yun', 'yul', 'agt', 'stb', 'ษ”tb', 'nvb', 'dsb'], + 'weekdays' => ['Lomรญngo', 'Mosรกlรก mษ”ฬŒkษ”ฬ', 'Misรกlรก mรญbalรฉ', 'Misรกlรก mรญsรกto', 'Misรกlรก mรญnei', 'Misรกlรก mรญtรกno', 'Mpษ”ฬsษ”'], + 'weekdays_short' => ['m1.', 'm2.', 'm3.', 'm4.', 'm5.', 'm6.', 'm7.'], + 'weekdays_min' => ['m1.', 'm2.', 'm3.', 'm4.', 'm5.', 'm6.', 'm7.'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => 'mbula :count', + 'y' => 'mbula :count', + 'a_year' => 'mbula :count', + + 'month' => 'sรกnzรก :count', + 'm' => 'sรกnzรก :count', + 'a_month' => 'sรกnzรก :count', + + 'week' => 'mpษ”ฬsษ” :count', + 'w' => 'mpษ”ฬsษ” :count', + 'a_week' => 'mpษ”ฬsษ” :count', + + 'day' => 'mokษ”lษ” :count', + 'd' => 'mokษ”lษ” :count', + 'a_day' => 'mokษ”lษ” :count', + + 'hour' => 'ngonga :count', + 'h' => 'ngonga :count', + 'a_hour' => 'ngonga :count', + + 'minute' => 'miniti :count', + 'min' => 'miniti :count', + 'a_minute' => 'miniti :count', + + 'second' => 'segษ”nde :count', + 's' => 'segษ”nde :count', + 'a_second' => 'segษ”nde :count', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln_AO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln_AO.php new file mode 100644 index 0000000000..7fdb7f1b8e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln_AO.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ln.php', [ + 'weekdays' => ['eyenga', 'mokษ”lษ” mwa yambo', 'mokษ”lษ” mwa mรญbalรฉ', 'mokษ”lษ” mwa mรญsรกto', 'mokษ”lษ” ya mรญnรฉi', 'mokษ”lษ” ya mรญtรกno', 'mpษ”ฬsษ”'], + 'weekdays_short' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'weekdays_min' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'meridiem' => ['ntษ”ฬngษ”ฬ', 'mpรณkwa'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln_CD.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln_CD.php new file mode 100644 index 0000000000..13635fcca7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln_CD.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ubuntu Renรฉ Manassรฉ GALEKWA renemanasse@gmail.com + */ +return require __DIR__.'/ln.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln_CF.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln_CF.php new file mode 100644 index 0000000000..7fdb7f1b8e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln_CF.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ln.php', [ + 'weekdays' => ['eyenga', 'mokษ”lษ” mwa yambo', 'mokษ”lษ” mwa mรญbalรฉ', 'mokษ”lษ” mwa mรญsรกto', 'mokษ”lษ” ya mรญnรฉi', 'mokษ”lษ” ya mรญtรกno', 'mpษ”ฬsษ”'], + 'weekdays_short' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'weekdays_min' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'meridiem' => ['ntษ”ฬngษ”ฬ', 'mpรณkwa'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln_CG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln_CG.php new file mode 100644 index 0000000000..7fdb7f1b8e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ln_CG.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ln.php', [ + 'weekdays' => ['eyenga', 'mokษ”lษ” mwa yambo', 'mokษ”lษ” mwa mรญbalรฉ', 'mokษ”lษ” mwa mรญsรกto', 'mokษ”lษ” ya mรญnรฉi', 'mokษ”lษ” ya mรญtรกno', 'mpษ”ฬsษ”'], + 'weekdays_short' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'weekdays_min' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'meridiem' => ['ntษ”ฬngษ”ฬ', 'mpรณkwa'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lo.php new file mode 100644 index 0000000000..48715f5c59 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lo.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - ryanhart2 + */ +return [ + 'year' => ':count เบ›เบต', + 'y' => ':count เบ›เบต', + 'month' => ':count เป€เบ”เบทเบญเบ™', + 'm' => ':count เบ”. ', + 'week' => ':count เบญเบฒเบ—เบดเบ”', + 'w' => ':count เบญเบ—. ', + 'day' => ':count เบกเบทเป‰', + 'd' => ':count เบกเบทเป‰', + 'hour' => ':count เบŠเบปเปˆเบงเป‚เบกเบ‡', + 'h' => ':count เบŠเบก. ', + 'minute' => ':count เบ™เบฒเบ—เบต', + 'min' => ':count เบ™เบ—. ', + 'second' => '{1}เบšเปเปˆเป€เบ—เบปเปˆเบฒเปƒเบ”เบงเบดเบ™เบฒเบ—เบต|]1,Inf[:count เบงเบดเบ™เบฒเบ—เบต', + 's' => ':count เบงเบด. ', + 'ago' => ':timeเบœเปˆเบฒเบ™เบกเบฒ', + 'from_now' => 'เบญเบตเบ :time', + 'diff_now' => 'เบ•เบญเบ™เบ™เบตเป‰', + 'diff_today' => 'เบกเบทเป‰เบ™เบตเป‰เป€เบงเบฅเบฒ', + 'diff_yesterday' => 'เบกเบทเป‰เบงเบฒเบ™เบ™เบตเป‰เป€เบงเบฅเบฒ', + 'diff_tomorrow' => 'เบกเบทเป‰เบญเบทเปˆเบ™เป€เบงเบฅเบฒ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'เบงเบฑเบ™dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[เบกเบทเป‰เบ™เบตเป‰เป€เบงเบฅเบฒ] LT', + 'nextDay' => '[เบกเบทเป‰เบญเบทเปˆเบ™เป€เบงเบฅเบฒ] LT', + 'nextWeek' => '[เบงเบฑเบ™]dddd[เปœเป‰เบฒเป€เบงเบฅเบฒ] LT', + 'lastDay' => '[เบกเบทเป‰เบงเบฒเบ™เบ™เบตเป‰เป€เบงเบฅเบฒ] LT', + 'lastWeek' => '[เบงเบฑเบ™]dddd[เปเบฅเป‰เบงเบ™เบตเป‰เป€เบงเบฅเบฒ] LT', + 'sameElse' => 'L', + ], + 'ordinal' => 'เบ—เบตเปˆ:number', + 'meridiem' => ['เบ•เบญเบ™เป€เบŠเบปเป‰เบฒ', 'เบ•เบญเบ™เปเบฅเบ‡'], + 'months' => ['เบกเบฑเบ‡เบเบญเบ™', 'เบเบธเบกเบžเบฒ', 'เบกเบตเบ™เบฒ', 'เป€เบกเบชเบฒ', 'เบžเบถเบ”เบชเบฐเบžเบฒ', 'เบกเบดเบ–เบธเบ™เบฒ', 'เบเปเบฅเบฐเบเบปเบ”', 'เบชเบดเบ‡เบซเบฒ', 'เบเบฑเบ™เบเบฒ', 'เบ•เบธเบฅเบฒ', 'เบžเบฐเบˆเบดเบ', 'เบ—เบฑเบ™เบงเบฒ'], + 'months_short' => ['เบกเบฑเบ‡เบเบญเบ™', 'เบเบธเบกเบžเบฒ', 'เบกเบตเบ™เบฒ', 'เป€เบกเบชเบฒ', 'เบžเบถเบ”เบชเบฐเบžเบฒ', 'เบกเบดเบ–เบธเบ™เบฒ', 'เบเปเบฅเบฐเบเบปเบ”', 'เบชเบดเบ‡เบซเบฒ', 'เบเบฑเบ™เบเบฒ', 'เบ•เบธเบฅเบฒ', 'เบžเบฐเบˆเบดเบ', 'เบ—เบฑเบ™เบงเบฒ'], + 'weekdays' => ['เบญเบฒเบ—เบดเบ”', 'เบˆเบฑเบ™', 'เบญเบฑเบ‡เบ„เบฒเบ™', 'เบžเบธเบ”', 'เบžเบฐเบซเบฑเบ”', 'เบชเบธเบ', 'เป€เบชเบปเบฒ'], + 'weekdays_short' => ['เบ—เบดเบ”', 'เบˆเบฑเบ™', 'เบญเบฑเบ‡เบ„เบฒเบ™', 'เบžเบธเบ”', 'เบžเบฐเบซเบฑเบ”', 'เบชเบธเบ', 'เป€เบชเบปเบฒ'], + 'weekdays_min' => ['เบ—', 'เบˆ', 'เบญเบ„', 'เบž', 'เบžเบซ', 'เบชเบ', 'เบช'], + 'list' => [', ', 'เปเบฅเบฐ '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lo_LA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lo_LA.php new file mode 100644 index 0000000000..9b7fd9bf7d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lo_LA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/lo.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lrc.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lrc.php new file mode 100644 index 0000000000..546e67916d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lrc.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + + 'minute' => ':count ู‡ู†ุฑ', // less reliable + 'min' => ':count ู‡ู†ุฑ', // less reliable + 'a_minute' => ':count ู‡ู†ุฑ', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lrc_IQ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lrc_IQ.php new file mode 100644 index 0000000000..d42f5e9711 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lrc_IQ.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/lrc.php', [ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lt.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lt.php new file mode 100644 index 0000000000..7d1b6f7439 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lt.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - valdas406 + * - Justas Palumickas + * - Max Melentiev + * - Andrius Janauskas + * - Juanito Fatas + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolรกs Hock Isaza + * - Laurynas Butkus + * - Sven Fuchs + * - Dominykas Tijลซnaitis + * - Justinas Bolys + * - Riฤardas + * - Kirill Chalkin + * - Rolandas + * - Justinas (Gamesh) + */ +return [ + 'year' => ':count metai|:count metai|:count metลณ', + 'y' => ':count m.', + 'month' => ':count mฤ—nuo|:count mฤ—nesiai|:count mฤ—nesฤฏ', + 'm' => ':count mฤ—n.', + 'week' => ':count savaitฤ—|:count savaitฤ—s|:count savaitฤ™', + 'w' => ':count sav.', + 'day' => ':count diena|:count dienos|:count dienลณ', + 'd' => ':count d.', + 'hour' => ':count valanda|:count valandos|:count valandลณ', + 'h' => ':count val.', + 'minute' => ':count minutฤ—|:count minutฤ—s|:count minutฤ™', + 'min' => ':count min.', + 'second' => ':count sekundฤ—|:count sekundฤ—s|:count sekundลพiลณ', + 's' => ':count sek.', + + 'year_ago' => ':count metus|:count metus|:count metลณ', + 'month_ago' => ':count mฤ—nesฤฏ|:count mฤ—nesius|:count mฤ—nesiลณ', + 'week_ago' => ':count savaitฤ™|:count savaites|:count savaiฤiลณ', + 'day_ago' => ':count dienฤ…|:count dienas|:count dienลณ', + 'hour_ago' => ':count valandฤ…|:count valandas|:count valandลณ', + 'minute_ago' => ':count minutฤ™|:count minutes|:count minuฤiลณ', + 'second_ago' => ':count sekundฤ™|:count sekundes|:count sekundลพiลณ', + + 'year_from_now' => ':count metลณ', + 'month_from_now' => ':count mฤ—nesio|:count mฤ—nesiลณ|:count mฤ—nesiลณ', + 'week_from_now' => ':count savaitฤ—s|:count savaiฤiลณ|:count savaiฤiลณ', + 'day_from_now' => ':count dienos|:count dienลณ|:count dienลณ', + 'hour_from_now' => ':count valandos|:count valandลณ|:count valandลณ', + 'minute_from_now' => ':count minutฤ—s|:count minuฤiลณ|:count minuฤiลณ', + 'second_from_now' => ':count sekundฤ—s|:count sekundลพiลณ|:count sekundลพiลณ', + + 'year_after' => ':count metลณ', + 'month_after' => ':count mฤ—nesio|:count mฤ—nesiลณ|:count mฤ—nesiลณ', + 'week_after' => ':count savaitฤ—s|:count savaiฤiลณ|:count savaiฤiลณ', + 'day_after' => ':count dienos|:count dienลณ|:count dienลณ', + 'hour_after' => ':count valandos|:count valandลณ|:count valandลณ', + 'minute_after' => ':count minutฤ—s|:count minuฤiลณ|:count minuฤiลณ', + 'second_after' => ':count sekundฤ—s|:count sekundลพiลณ|:count sekundลพiลณ', + + 'ago' => 'prieลก :time', + 'from_now' => ':time nuo dabar', + 'after' => 'po :time', + 'before' => 'uลพ :time', + + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'diff_now' => 'kฤ… tik', + 'diff_today' => 'ล iandien', + 'diff_yesterday' => 'vakar', + 'diff_yesterday_regexp' => 'Vakar', + 'diff_tomorrow' => 'rytoj', + 'diff_tomorrow_regexp' => 'Rytoj', + 'diff_before_yesterday' => 'uลพvakar', + 'diff_after_tomorrow' => 'poryt', + + 'period_recurrences' => 'kartฤ…|:count kartลณ', + 'period_interval' => 'kiekvienฤ… :interval', + 'period_start_date' => 'nuo :date', + 'period_end_date' => 'iki :date', + + 'months' => ['sausio', 'vasario', 'kovo', 'balandลพio', 'geguลพฤ—s', 'birลพelio', 'liepos', 'rugpjลซฤio', 'rugsฤ—jo', 'spalio', 'lapkriฤio', 'gruodลพio'], + 'months_standalone' => ['sausis', 'vasaris', 'kovas', 'balandis', 'geguลพฤ—', 'birลพelis', 'liepa', 'rugpjลซtis', 'rugsฤ—jis', 'spalis', 'lapkritis', 'gruodis'], + 'months_regexp' => '/(L{2,4}|D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|MMMM?(\[[^\[\]]*\]|\s)+D[oD]?)/', + 'months_short' => ['sau', 'vas', 'kov', 'bal', 'geg', 'bir', 'lie', 'rgp', 'rgs', 'spa', 'lap', 'gru'], + 'weekdays' => ['sekmadienฤฏ', 'pirmadienฤฏ', 'antradienฤฏ', 'treฤiadienฤฏ', 'ketvirtadienฤฏ', 'penktadienฤฏ', 'ลกeลกtadienฤฏ'], + 'weekdays_standalone' => ['sekmadienis', 'pirmadienis', 'antradienis', 'treฤiadienis', 'ketvirtadienis', 'penktadienis', 'ลกeลกtadienis'], + 'weekdays_short' => ['sek', 'pir', 'ant', 'tre', 'ket', 'pen', 'ลกeลก'], + 'weekdays_min' => ['se', 'pi', 'an', 'tr', 'ke', 'pe', 'ลกe'], + 'list' => [', ', ' ir '], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ล iandien] LT', + 'nextDay' => '[Rytoj] LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '[Vakar] LT', + 'lastWeek' => '[Paskutinฤฏ] dddd LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + switch ($number) { + case 0: + return '0-is'; + case 3: + return '3-ias'; + default: + return "$number-as"; + } + }, + 'meridiem' => ['prieลกpiet', 'popiet'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lt_LT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lt_LT.php new file mode 100644 index 0000000000..f772d38b68 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lt_LT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/lt.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lu.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lu.php new file mode 100644 index 0000000000..c8cd83af01 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lu.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Dinda', 'Dilolo'], + 'weekdays' => ['Lumingu', 'Nkodya', 'Ndร ayร ', 'Ndangรน', 'Njรฒwa', 'Ngรฒvya', 'Lubingu'], + 'weekdays_short' => ['Lum', 'Nko', 'Ndy', 'Ndg', 'Njw', 'Ngv', 'Lub'], + 'weekdays_min' => ['Lum', 'Nko', 'Ndy', 'Ndg', 'Njw', 'Ngv', 'Lub'], + 'months' => ['Ciongo', 'Lรนishi', 'Lusรฒlo', 'Mรนuyร ', 'Lumรนngรนlรน', 'Lufuimi', 'Kabร lร shรฌpรน', 'Lรนshรฌkร ', 'Lutongolo', 'Lungรนdi', 'Kaswรจkรจsรจ', 'Ciswร '], + 'months_short' => ['Cio', 'Lui', 'Lus', 'Muu', 'Lum', 'Luf', 'Kab', 'Lush', 'Lut', 'Lun', 'Kas', 'Cis'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/luo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/luo.php new file mode 100644 index 0000000000..b55af7318c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/luo.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['OD', 'OT'], + 'weekdays' => ['Jumapil', 'Wuok Tich', 'Tich Ariyo', 'Tich Adek', 'Tich Angโ€™wen', 'Tich Abich', 'Ngeso'], + 'weekdays_short' => ['JMP', 'WUT', 'TAR', 'TAD', 'TAN', 'TAB', 'NGS'], + 'weekdays_min' => ['JMP', 'WUT', 'TAR', 'TAD', 'TAN', 'TAB', 'NGS'], + 'months' => ['Dwe mar Achiel', 'Dwe mar Ariyo', 'Dwe mar Adek', 'Dwe mar Angโ€™wen', 'Dwe mar Abich', 'Dwe mar Auchiel', 'Dwe mar Abiriyo', 'Dwe mar Aboro', 'Dwe mar Ochiko', 'Dwe mar Apar', 'Dwe mar gi achiel', 'Dwe mar Apar gi ariyo'], + 'months_short' => ['DAC', 'DAR', 'DAD', 'DAN', 'DAH', 'DAU', 'DAO', 'DAB', 'DOC', 'DAP', 'DGI', 'DAG'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => 'higni :count', + 'y' => 'higni :count', + 'a_year' => ':higni :count', + + 'month' => 'dweche :count', + 'm' => 'dweche :count', + 'a_month' => 'dweche :count', + + 'week' => 'jumbe :count', + 'w' => 'jumbe :count', + 'a_week' => 'jumbe :count', + + 'day' => 'ndalo :count', + 'd' => 'ndalo :count', + 'a_day' => 'ndalo :count', + + 'hour' => 'seche :count', + 'h' => 'seche :count', + 'a_hour' => 'seche :count', + + 'minute' => 'dakika :count', + 'min' => 'dakika :count', + 'a_minute' => 'dakika :count', + + 'second' => 'nus dakika :count', + 's' => 'nus dakika :count', + 'a_second' => 'nus dakika :count', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/luy.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/luy.php new file mode 100644 index 0000000000..2b37e3e323 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/luy.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Jumapiri', 'Jumatatu', 'Jumanne', 'Jumatano', 'Murwa wa Kanne', 'Murwa wa Katano', 'Jumamosi'], + 'weekdays_short' => ['J2', 'J3', 'J4', 'J5', 'Al', 'Ij', 'J1'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Al', 'Ij', 'J1'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + // Too unreliable + /* + 'year' => ':count liliino', // less reliable + 'y' => ':count liliino', // less reliable + 'a_year' => ':count liliino', // less reliable + + 'month' => ':count kumwesi', // less reliable + 'm' => ':count kumwesi', // less reliable + 'a_month' => ':count kumwesi', // less reliable + + 'week' => ':count olutambi', // less reliable + 'w' => ':count olutambi', // less reliable + 'a_week' => ':count olutambi', // less reliable + + 'day' => ':count luno', // less reliable + 'd' => ':count luno', // less reliable + 'a_day' => ':count luno', // less reliable + + 'hour' => ':count ekengele', // less reliable + 'h' => ':count ekengele', // less reliable + 'a_hour' => ':count ekengele', // less reliable + + 'minute' => ':count omundu', // less reliable + 'min' => ':count omundu', // less reliable + 'a_minute' => ':count omundu', // less reliable + + 'second' => ':count liliino', // less reliable + 's' => ':count liliino', // less reliable + 'a_second' => ':count liliino', // less reliable + */ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lv.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lv.php new file mode 100644 index 0000000000..d5cba7ca04 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lv.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\CarbonInterface; + +/** + * This file is part of the Carbon package. + * + * (c) Brian Nesbitt + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - pirminis + * - Tsutomu Kuroda + * - tjku + * - Andris Zฤฤฃeris + * - Max Melentiev + * - Edgars Beigarts + * - Juanito Fatas + * - Vitauts Stoฤka + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Kaspars Bankovskis + * - Nicolรกs Hock Isaza + * - Viesturs Kavacs (Kavacky) + * - zakse + * - Janis Eglitis (janiseglitis) + * - Guntars + * - Juris Sudmalis + */ +$daysOfWeek = ['svฤ“tdiena', 'pirmdiena', 'otrdiena', 'treลกdiena', 'ceturtdiena', 'piektdiena', 'sestdiena']; +$daysOfWeekLocativum = ['svฤ“tdien', 'pirmdien', 'otrdien', 'treลกdien', 'ceturtdien', 'piektdien', 'sestdien']; + +$transformDiff = function ($input) { + return strtr($input, [ + // Nominative => "pirms/pฤ“c" Dative + 'gads' => 'gada', + 'gadi' => 'gadiem', + 'gadu' => 'gadiem', + 'mฤ“nesis' => 'mฤ“neลกa', + 'mฤ“neลกi' => 'mฤ“neลกiem', + 'mฤ“neลกu' => 'mฤ“neลกiem', + 'nedฤ“ฤผa' => 'nedฤ“ฤผas', + 'nedฤ“ฤผas' => 'nedฤ“ฤผฤm', + 'nedฤ“ฤผu' => 'nedฤ“ฤผฤm', + 'diena' => 'dienas', + 'dienas' => 'dienฤm', + 'dienu' => 'dienฤm', + 'stunda' => 'stundas', + 'stundas' => 'stundฤm', + 'stundu' => 'stundฤm', + 'minลซte' => 'minลซtes', + 'minลซtes' => 'minลซtฤ“m', + 'minลซลกu' => 'minลซtฤ“m', + 'sekunde' => 'sekundes', + 'sekundes' => 'sekundฤ“m', + 'sekunลพu' => 'sekundฤ“m', + ]); +}; + +return [ + 'ago' => function ($time) use ($transformDiff) { + return 'pirms '.$transformDiff($time); + }, + 'from_now' => function ($time) use ($transformDiff) { + return 'pฤ“c '.$transformDiff($time); + }, + + 'year' => '0 gadu|:count gads|:count gadi', + 'y' => ':count g.', + 'a_year' => '{1}gads|0 gadu|:count gads|:count gadi', + 'month' => '0 mฤ“neลกu|:count mฤ“nesis|:count mฤ“neลกi', + 'm' => ':count mฤ“n.', + 'a_month' => '{1}mฤ“nesis|0 mฤ“neลกu|:count mฤ“nesis|:count mฤ“neลกi', + 'week' => '0 nedฤ“ฤผu|:count nedฤ“ฤผa|:count nedฤ“ฤผas', + 'w' => ':count ned.', + 'a_week' => '{1}nedฤ“ฤผa|0 nedฤ“ฤผu|:count nedฤ“ฤผa|:count nedฤ“ฤผas', + 'day' => '0 dienu|:count diena|:count dienas', + 'd' => ':count d.', + 'a_day' => '{1}diena|0 dienu|:count diena|:count dienas', + 'hour' => '0 stundu|:count stunda|:count stundas', + 'h' => ':count st.', + 'a_hour' => '{1}stunda|0 stundu|:count stunda|:count stundas', + 'minute' => '0 minลซลกu|:count minลซte|:count minลซtes', + 'min' => ':count min.', + 'a_minute' => '{1}minลซte|0 minลซลกu|:count minลซte|:count minลซtes', + 'second' => '0 sekunลพu|:count sekunde|:count sekundes', + 's' => ':count sek.', + 'a_second' => '{1}sekunde|0 sekunลพu|:count sekunde|:count sekundes', + + 'after' => ':time vฤ“lฤk', + 'year_after' => '0 gadus|:count gadu|:count gadus', + 'a_year_after' => '{1}gadu|0 gadus|:count gadu|:count gadus', + 'month_after' => '0 mฤ“neลกus|:count mฤ“nesi|:count mฤ“neลกus', + 'a_month_after' => '{1}mฤ“nesi|0 mฤ“neลกus|:count mฤ“nesi|:count mฤ“neลกus', + 'week_after' => '0 nedฤ“ฤผas|:count nedฤ“ฤผu|:count nedฤ“ฤผas', + 'a_week_after' => '{1}nedฤ“ฤผu|0 nedฤ“ฤผas|:count nedฤ“ฤผu|:count nedฤ“ฤผas', + 'day_after' => '0 dienas|:count dienu|:count dienas', + 'a_day_after' => '{1}dienu|0 dienas|:count dienu|:count dienas', + 'hour_after' => '0 stundas|:count stundu|:count stundas', + 'a_hour_after' => '{1}stundu|0 stundas|:count stundu|:count stundas', + 'minute_after' => '0 minลซtes|:count minลซti|:count minลซtes', + 'a_minute_after' => '{1}minลซti|0 minลซtes|:count minลซti|:count minลซtes', + 'second_after' => '0 sekundes|:count sekundi|:count sekundes', + 'a_second_after' => '{1}sekundi|0 sekundes|:count sekundi|:count sekundes', + + 'before' => ':time agrฤk', + 'year_before' => '0 gadus|:count gadu|:count gadus', + 'a_year_before' => '{1}gadu|0 gadus|:count gadu|:count gadus', + 'month_before' => '0 mฤ“neลกus|:count mฤ“nesi|:count mฤ“neลกus', + 'a_month_before' => '{1}mฤ“nesi|0 mฤ“neลกus|:count mฤ“nesi|:count mฤ“neลกus', + 'week_before' => '0 nedฤ“ฤผas|:count nedฤ“ฤผu|:count nedฤ“ฤผas', + 'a_week_before' => '{1}nedฤ“ฤผu|0 nedฤ“ฤผas|:count nedฤ“ฤผu|:count nedฤ“ฤผas', + 'day_before' => '0 dienas|:count dienu|:count dienas', + 'a_day_before' => '{1}dienu|0 dienas|:count dienu|:count dienas', + 'hour_before' => '0 stundas|:count stundu|:count stundas', + 'a_hour_before' => '{1}stundu|0 stundas|:count stundu|:count stundas', + 'minute_before' => '0 minลซtes|:count minลซti|:count minลซtes', + 'a_minute_before' => '{1}minลซti|0 minลซtes|:count minลซti|:count minลซtes', + 'second_before' => '0 sekundes|:count sekundi|:count sekundes', + 'a_second_before' => '{1}sekundi|0 sekundes|:count sekundi|:count sekundes', + + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' un '], + + 'diff_now' => 'tagad', + 'diff_today' => 'ลกodien', + 'diff_yesterday' => 'vakar', + 'diff_before_yesterday' => 'aizvakar', + 'diff_tomorrow' => 'rฤซt', + 'diff_after_tomorrow' => 'parฤซt', + + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY.', + 'LL' => 'YYYY. [gada] D. MMMM', + 'LLL' => 'DD.MM.YYYY., HH:mm', + 'LLLL' => 'YYYY. [gada] D. MMMM, HH:mm', + ], + + 'calendar' => [ + 'sameDay' => '[ลกodien] [plkst.] LT', + 'nextDay' => '[rฤซt] [plkst.] LT', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) use ($daysOfWeekLocativum) { + if ($current->week !== $other->week) { + return '[nฤkoลกo] ['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; + } + + return '['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; + }, + 'lastDay' => '[vakar] [plkst.] LT', + 'lastWeek' => function (CarbonInterface $current) use ($daysOfWeekLocativum) { + return '[pagฤjuลกo] ['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; + }, + 'sameElse' => 'L', + ], + + 'weekdays' => $daysOfWeek, + 'weekdays_short' => ['Sv.', 'P.', 'O.', 'T.', 'C.', 'Pk.', 'S.'], + 'weekdays_min' => ['Sv.', 'P.', 'O.', 'T.', 'C.', 'Pk.', 'S.'], + 'months' => ['janvฤris', 'februฤris', 'marts', 'aprฤซlis', 'maijs', 'jลซnijs', 'jลซlijs', 'augusts', 'septembris', 'oktobris', 'novembris', 'decembris'], + 'months_standalone' => ['janvฤrฤซ', 'februฤrฤซ', 'martฤ', 'aprฤซlฤซ', 'maijฤ', 'jลซnijฤ', 'jลซlijฤ', 'augustฤ', 'septembrฤซ', 'oktobrฤซ', 'novembrฤซ', 'decembrฤซ'], + 'months_short' => ['janv.', 'febr.', 'martฤ', 'apr.', 'maijฤ', 'jลซn.', 'jลซl.', 'aug.', 'sept.', 'okt.', 'nov.', 'dec.'], + 'meridiem' => ['priekลกpusdiena', 'pฤ“cpusdiena'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lv_LV.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lv_LV.php new file mode 100644 index 0000000000..ee91c369f5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lv_LV.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/lv.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lzh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lzh.php new file mode 100644 index 0000000000..1180c6bb1d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lzh.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/lzh_TW.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lzh_TW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lzh_TW.php new file mode 100644 index 0000000000..3b1493eef1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lzh_TW.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'OY[ๅนด]MMMMOD[ๆ—ฅ]', + ], + 'months' => ['ไธ€ๆœˆ', 'ไบŒๆœˆ', 'ไธ‰ๆœˆ', 'ๅ››ๆœˆ', 'ไบ”ๆœˆ', 'ๅ…ญๆœˆ', 'ไธƒๆœˆ', 'ๅ…ซๆœˆ', 'ไนๆœˆ', 'ๅๆœˆ', 'ๅไธ€ๆœˆ', 'ๅไบŒๆœˆ'], + 'months_short' => [' ไธ€ ', ' ไบŒ ', ' ไธ‰ ', ' ๅ›› ', ' ไบ” ', ' ๅ…ญ ', ' ไธƒ ', ' ๅ…ซ ', ' ไน ', ' ๅ ', 'ๅไธ€', 'ๅไบŒ'], + 'weekdays' => ['้€ฑๆ—ฅ', '้€ฑไธ€', '้€ฑไบŒ', '้€ฑไธ‰', '้€ฑๅ››', '้€ฑไบ”', '้€ฑๅ…ญ'], + 'weekdays_short' => ['ๆ—ฅ', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ'], + 'weekdays_min' => ['ๆ—ฅ', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ'], + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['ใ€‡', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ', 'ไธƒ', 'ๅ…ซ', 'ไน', 'ๅ', 'ๅไธ€', 'ๅไบŒ', 'ๅไธ‰', 'ๅๅ››', 'ๅไบ”', 'ๅๅ…ญ', 'ๅไธƒ', 'ๅๅ…ซ', 'ๅไน', 'ๅปฟ', 'ๅปฟไธ€', 'ๅปฟไบŒ', 'ๅปฟไธ‰', 'ๅปฟๅ››', 'ๅปฟไบ”', 'ๅปฟๅ…ญ', 'ๅปฟไธƒ', 'ๅปฟๅ…ซ', 'ๅปฟไน', 'ๅ…', 'ๅ…ไธ€'], + 'meridiem' => ['ๆœ', 'ๆšฎ'], + + 'year' => ':count ๅค', // less reliable + 'y' => ':count ๅค', // less reliable + 'a_year' => ':count ๅค', // less reliable + + 'month' => ':count ๆœˆ', // less reliable + 'm' => ':count ๆœˆ', // less reliable + 'a_month' => ':count ๆœˆ', // less reliable + + 'hour' => ':count ๆฐง', // less reliable + 'h' => ':count ๆฐง', // less reliable + 'a_hour' => ':count ๆฐง', // less reliable + + 'minute' => ':count ้ปž', // less reliable + 'min' => ':count ้ปž', // less reliable + 'a_minute' => ':count ้ปž', // less reliable + + 'second' => ':count ๆฅš', // less reliable + 's' => ':count ๆฅš', // less reliable + 'a_second' => ':count ๆฅš', // less reliable + + 'week' => ':count ๆ˜ŸๆœŸ', + 'w' => ':count ๆ˜ŸๆœŸ', + 'a_week' => ':count ๆ˜ŸๆœŸ', + + 'day' => ':count ๆ—ฅ(ๆ›†ๆณ•)', + 'd' => ':count ๆ—ฅ(ๆ›†ๆณ•)', + 'a_day' => ':count ๆ—ฅ(ๆ›†ๆณ•)', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mag.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mag.php new file mode 100644 index 0000000000..7532436d0d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mag.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mag_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mag_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mag_IN.php new file mode 100644 index 0000000000..193f67a7ed --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mag_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bhashaghar@googlegroups.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคผเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅ‡เคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเคฎเฅเคฌเคฐ', 'เค…เค•เฅเคŸเฅ‚เคฌเคฐ', 'เคจเคตเคฎเฅเคฌเคฐ', 'เคฆเคฟเคธเคฎเฅเคฌเคฐ'], + 'months_short' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคผเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅ‡เคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเคฎเฅเคฌเคฐ', 'เค…เค•เฅเคŸเฅ‚เคฌเคฐ', 'เคจเคตเคฎเฅเคฌเคฐ', 'เคฆเคฟเคธเคฎเฅเคฌเคฐ'], + 'weekdays' => ['เคเคคเคตเคพเคฐ', 'เคธเฅ‹เคฎเคพเคฐ', 'เคฎเค‚เค—เคฐ', 'เคฌเฅเคง', 'เคฌเคฟเคซเฅ‡', 'เคธเฅ‚เค•', 'เคธเคจเคฟเคšเคฐ'], + 'weekdays_short' => ['เคเคคเคตเคพเคฐ', 'เคธเฅ‹เคฎเคพเคฐ', 'เคฎเค‚เค—เคฐ', 'เคฌเฅเคง', 'เคฌเคฟเคซเฅ‡', 'เคธเฅ‚เค•', 'เคธเคจเคฟเคšเคฐ'], + 'weekdays_min' => ['เคเคคเคตเคพเคฐ', 'เคธเฅ‹เคฎเคพเคฐ', 'เคฎเค‚เค—เคฐ', 'เคฌเฅเคง', 'เคฌเคฟเคซเฅ‡', 'เคธเฅ‚เค•', 'เคธเคจเคฟเคšเคฐ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เคชเฅ‚เคฐเฅเคตเคพเคนเฅเคจ', 'เค…เคชเคฐเคพเคนเฅเคจ'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mai.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mai.php new file mode 100644 index 0000000000..792b973990 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mai.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mai_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mai_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mai_IN.php new file mode 100644 index 0000000000..03049d4586 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mai_IN.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Maithili Computing Research Center, Pune, India rajeshkajha@yahoo.com,akhilesh.k@samusng.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เคฌเฅˆเคธเคพเค–', 'เคœเฅ‡เค ', 'เค…เคทเคพเคขเคผ', 'เคธเคพเคตเฅ‹เคจ', 'เคญเคพเคฆเฅ‹', 'เค†เคธเคฟเคจ', 'เค•เคพเคคเคฟเค•', 'เค…เค—เคนเคจ', 'เคชเฅ‚เคธ', 'เคฎเคพเค˜', 'เคซเคพเค—เฅเคจ', 'เคšเฅˆเคคเคฟ'], + 'months_short' => ['เคฌเฅˆเคธเคพเค–', 'เคœเฅ‡เค ', 'เค…เคทเคพเคขเคผ', 'เคธเคพเคตเฅ‹เคจ', 'เคญเคพเคฆเฅ‹', 'เค†เคธเคฟเคจ', 'เค•เคพเคคเคฟเค•', 'เค…เค—เคนเคจ', 'เคชเฅ‚เคธ', 'เคฎเคพเค˜', 'เคซเคพเค—เฅเคจ', 'เคšเฅˆเคคเคฟ'], + 'weekdays' => ['เคฐเคตเคฟเคฆเคฟเคจ', 'เคธเฅ‹เคฎเคฆเคฟเคจ', 'เคฎเค‚เค—เคฒเคฆเคฟเคจ', 'เคฌเฅเคงเคฆเคฟเคจ', 'เคฌเฅƒเคนเคธเฅเคชเคคเฅ€เคฆเคฟเคจ', 'เคถเฅเค•เฅเคฐเคฆเคฟเคจ', 'เคถเคจเฅ€เคฆเคฟเคจ'], + 'weekdays_short' => ['เคฐเคตเคฟ', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคง', 'เคฌเฅƒเคนเคธเฅเคชเคคเฅ€', 'เคถเฅเค•เฅเคฐ', 'เคถเคจเฅ€'], + 'weekdays_min' => ['เคฐเคตเคฟ', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคง', 'เคฌเฅƒเคนเคธเฅเคชเคคเฅ€', 'เคถเฅเค•เฅเคฐ', 'เคถเคจเฅ€'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เคชเฅ‚เคฐเฅเคตเคพเคนเฅเคจ', 'เค…เคชเคฐเคพเคนเฅเคจ'], + + 'year' => ':count เค‹เคคเฅ', // less reliable + 'y' => ':count เค‹เคคเฅ', // less reliable + 'a_year' => ':count เค‹เคคเฅ', // less reliable + + 'month' => ':count เคฎเคนเคฟเคจเคพ', + 'm' => ':count เคฎเคนเคฟเคจเคพ', + 'a_month' => ':count เคฎเคนเคฟเคจเคพ', + + 'week' => ':count เคถเฅเคฐเฅ‡เคฃเฅ€:เค•เฅเคฏเคพเคฒเฅ‡เคจเฅเคกเคฐ', // less reliable + 'w' => ':count เคถเฅเคฐเฅ‡เคฃเฅ€:เค•เฅเคฏเคพเคฒเฅ‡เคจเฅเคกเคฐ', // less reliable + 'a_week' => ':count เคถเฅเคฐเฅ‡เคฃเฅ€:เค•เฅเคฏเคพเคฒเฅ‡เคจเฅเคกเคฐ', // less reliable + + 'day' => ':count เคฆเคฟเคจ', + 'd' => ':count เคฆเคฟเคจ', + 'a_day' => ':count เคฆเคฟเคจ', + + 'hour' => ':count เค˜เคฃเฅเคŸเคพ', + 'h' => ':count เค˜เคฃเฅเคŸเคพ', + 'a_hour' => ':count เค˜เคฃเฅเคŸเคพ', + + 'minute' => ':count เคธเคฎเคฏ', // less reliable + 'min' => ':count เคธเคฎเคฏ', // less reliable + 'a_minute' => ':count เคธเคฎเคฏ', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mas.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mas.php new file mode 100644 index 0000000000..cbd610c287 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mas.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ฦnkakษ›nyรก', 'ฦndรกmรข'], + 'weekdays' => ['Jumapรญlรญ', 'Jumatรกtu', 'Jumane', 'Jumatรกnษ”', 'Alaรกmisi', 'Jumรกa', 'Jumamรณsi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Oladalส‰ฬ', 'Arรกt', 'ฦ†ษ›nษจฬษ”ษจล‹ษ”k', 'Olodoyรญรณrรญรช inkรณkรบรข', 'Oloilรฉpลซnyฤซฤ“ inkรณkรบรข', 'Kรบjรบษ”rษ”k', 'Mรณrusรกsin', 'ฦ†lษ”ฬษจฬbษ”ฬrรกrษ›', 'Kรบshรฎn', 'Olgรญsan', 'Pส‰shส‰ฬka', 'Ntส‰ฬล‹ส‰ฬs'], + 'months_short' => ['Dal', 'Arรก', 'ฦ†ษ›n', 'Doy', 'Lรฉp', 'Rok', 'Sรกs', 'Bษ”ฬr', 'Kรบs', 'Gรญs', 'Shส‰ฬ', 'Ntส‰ฬ'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count olameyu', // less reliable + 'y' => ':count olameyu', // less reliable + 'a_year' => ':count olameyu', // less reliable + + 'week' => ':count engolongeare orwiki', // less reliable + 'w' => ':count engolongeare orwiki', // less reliable + 'a_week' => ':count engolongeare orwiki', // less reliable + + 'hour' => ':count esahabu', // less reliable + 'h' => ':count esahabu', // less reliable + 'a_hour' => ':count esahabu', // less reliable + + 'second' => ':count are', // less reliable + 's' => ':count are', // less reliable + 'a_second' => ':count are', // less reliable + + 'month' => ':count olapa', + 'm' => ':count olapa', + 'a_month' => ':count olapa', + + 'day' => ':count enkolongสผ', + 'd' => ':count enkolongสผ', + 'a_day' => ':count enkolongสผ', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mas_TZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mas_TZ.php new file mode 100644 index 0000000000..56e2905399 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mas_TZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/mas.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mer.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mer.php new file mode 100644 index 0000000000..2e14597fac --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mer.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Rลจ', 'ลจG'], + 'weekdays' => ['Kiumia', 'Muramuko', 'Wairi', 'Wethatu', 'Wena', 'Wetano', 'Jumamosi'], + 'weekdays_short' => ['KIU', 'MRA', 'WAI', 'WET', 'WEN', 'WTN', 'JUM'], + 'weekdays_min' => ['KIU', 'MRA', 'WAI', 'WET', 'WEN', 'WTN', 'JUM'], + 'months' => ['Januarฤฉ', 'Feburuarฤฉ', 'Machi', 'ฤจpurลฉ', 'Mฤฉฤฉ', 'Njuni', 'Njuraฤฉ', 'Agasti', 'Septemba', 'Oktลฉba', 'Novemba', 'Dicemba'], + 'months_short' => ['JAN', 'FEB', 'MAC', 'ฤจPU', 'Mฤจฤจ', 'NJU', 'NJR', 'AGA', 'SPT', 'OKT', 'NOV', 'DEC'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count murume', // less reliable + 'y' => ':count murume', // less reliable + 'a_year' => ':count murume', // less reliable + + 'month' => ':count muchaara', // less reliable + 'm' => ':count muchaara', // less reliable + 'a_month' => ':count muchaara', // less reliable + + 'minute' => ':count monto', // less reliable + 'min' => ':count monto', // less reliable + 'a_minute' => ':count monto', // less reliable + + 'second' => ':count gikeno', // less reliable + 's' => ':count gikeno', // less reliable + 'a_second' => ':count gikeno', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mfe.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mfe.php new file mode 100644 index 0000000000..4d6e6b6973 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mfe.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mfe_MU.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mfe_MU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mfe_MU.php new file mode 100644 index 0000000000..2d27b4578d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mfe_MU.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['zanvie', 'fevriye', 'mars', 'avril', 'me', 'zin', 'zilye', 'out', 'septam', 'oktob', 'novam', 'desam'], + 'months_short' => ['zan', 'fev', 'mar', 'avr', 'me', 'zin', 'zil', 'out', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['dimans', 'lindi', 'mardi', 'merkredi', 'zedi', 'vandredi', 'samdi'], + 'weekdays_short' => ['dim', 'lin', 'mar', 'mer', 'ze', 'van', 'sam'], + 'weekdays_min' => ['dim', 'lin', 'mar', 'mer', 'ze', 'van', 'sam'], + + 'year' => ':count bananรฉ', + 'y' => ':count bananรฉ', + 'a_year' => ':count bananรฉ', + + 'month' => ':count mwa', + 'm' => ':count mwa', + 'a_month' => ':count mwa', + + 'week' => ':count sรฉmenn', + 'w' => ':count sรฉmenn', + 'a_week' => ':count sรฉmenn', + + 'day' => ':count zour', + 'd' => ':count zour', + 'a_day' => ':count zour', + + 'hour' => ':count -er-tan', + 'h' => ':count -er-tan', + 'a_hour' => ':count -er-tan', + + 'minute' => ':count minitt', + 'min' => ':count minitt', + 'a_minute' => ':count minitt', + + 'second' => ':count dรฉziรฉm', + 's' => ':count dรฉziรฉm', + 'a_second' => ':count dรฉziรฉm', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mg.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mg.php new file mode 100644 index 0000000000..40bc2a8259 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mg.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mg_MG.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mg_MG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mg_MG.php new file mode 100644 index 0000000000..6a14535a5b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mg_MG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - The Debian Project modified by GNU//Linux Malagasy Rado Ramarotafika,Do-Risika RAFIEFERANTSIARONJY rado@linuxmg.org,dourix@free.fr + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Janoary', 'Febroary', 'Martsa', 'Aprily', 'Mey', 'Jona', 'Jolay', 'Aogositra', 'Septambra', 'Oktobra', 'Novambra', 'Desambra'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mey', 'Jon', 'Jol', 'Aog', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['alahady', 'alatsinainy', 'talata', 'alarobia', 'alakamisy', 'zoma', 'sabotsy'], + 'weekdays_short' => ['lhd', 'lts', 'tlt', 'lrb', 'lkm', 'zom', 'sab'], + 'weekdays_min' => ['lhd', 'lts', 'tlt', 'lrb', 'lkm', 'zom', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'minute' => ':count minitra', // less reliable + 'min' => ':count minitra', // less reliable + 'a_minute' => ':count minitra', // less reliable + + 'year' => ':count taona', + 'y' => ':count taona', + 'a_year' => ':count taona', + + 'month' => ':count volana', + 'm' => ':count volana', + 'a_month' => ':count volana', + + 'week' => ':count herinandro', + 'w' => ':count herinandro', + 'a_week' => ':count herinandro', + + 'day' => ':count andro', + 'd' => ':count andro', + 'a_day' => ':count andro', + + 'hour' => ':count ora', + 'h' => ':count ora', + 'a_hour' => ':count ora', + + 'second' => ':count segondra', + 's' => ':count segondra', + 'a_second' => ':count segondra', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mgh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mgh.php new file mode 100644 index 0000000000..2a80960d73 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mgh.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['wichishu', 'mchochilโ€™l'], + 'weekdays' => ['Sabato', 'Jumatatu', 'Jumanne', 'Jumatano', 'Arahamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Sab', 'Jtt', 'Jnn', 'Jtn', 'Ara', 'Iju', 'Jmo'], + 'weekdays_min' => ['Sab', 'Jtt', 'Jnn', 'Jtn', 'Ara', 'Iju', 'Jmo'], + 'months' => ['Mweri wo kwanza', 'Mweri wo unayeli', 'Mweri wo uneraru', 'Mweri wo unecheshe', 'Mweri wo unethanu', 'Mweri wo thanu na mocha', 'Mweri wo saba', 'Mweri wo nane', 'Mweri wo tisa', 'Mweri wo kumi', 'Mweri wo kumi na moja', 'Mweri wo kumi na yelโ€™li'], + 'months_short' => ['Kwa', 'Una', 'Rar', 'Che', 'Tha', 'Moc', 'Sab', 'Nan', 'Tis', 'Kum', 'Moj', 'Yel'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mgo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mgo.php new file mode 100644 index 0000000000..a126c9ff06 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mgo.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Aneg 1', 'Aneg 2', 'Aneg 3', 'Aneg 4', 'Aneg 5', 'Aneg 6', 'Aneg 7'], + 'weekdays_short' => ['Aneg 1', 'Aneg 2', 'Aneg 3', 'Aneg 4', 'Aneg 5', 'Aneg 6', 'Aneg 7'], + 'weekdays_min' => ['1', '2', '3', '4', '5', '6', '7'], + 'months' => ['imษ™g mbegtug', 'imeg ร bรนbรฌ', 'imeg mbษ™ล‹chubi', 'imษ™g ngwษ™ฬ€t', 'imษ™g fog', 'imษ™g ichiibษ”d', 'imษ™g ร dรนmbษ™ฬ€ล‹', 'imษ™g ichika', 'imษ™g kud', 'imษ™g tรจsiสผe', 'imษ™g zรฒ', 'imษ™g krizmed'], + 'months_short' => ['mbegtug', 'imeg ร bรนbรฌ', 'imeg mbษ™ล‹chubi', 'imษ™g ngwษ™ฬ€t', 'imษ™g fog', 'imษ™g ichiibษ”d', 'imษ™g ร dรนmbษ™ฬ€ล‹', 'imษ™g ichika', 'imษ™g kud', 'imษ™g tรจsiสผe', 'imษ™g zรฒ', 'imษ™g krizmed'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'dddd, YYYY MMMM DD HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mhr.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mhr.php new file mode 100644 index 0000000000..6bbc9f6dd0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mhr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mhr_RU.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mhr_RU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mhr_RU.php new file mode 100644 index 0000000000..309ead9d4f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mhr_RU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - PeshSajSoft Ltd. Vyacheslav Kileev slavakileev@yandex.ru + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY.MM.DD', + ], + 'months' => ['ะจะพั€ั‹ะบะนะพะป', 'ะŸัƒั€ะณั‹ะถ', 'ำฐัั€ะฝั', 'ะ’ำฑะดัˆะพั€', 'ะะณะฐ', 'ะŸะตะปะตะดั‹ัˆ', 'ะกำฑั€ะตะผ', 'ะกะพั€ะปะฐ', 'ะ˜ะดั‹ะผ', 'ะจั‹ะถะฐ', 'ะšั‹ะปะผะต', 'ะขะตะปะต'], + 'months_short' => ['ะจั€ะบ', 'ะŸะณะถ', 'ำฐั€ะฝ', 'ะ’ัˆั€', 'ะะณะฐ', 'ะŸะดัˆ', 'ะกั€ะผ', 'ะกั€ะป', 'ะ˜ะดะผ', 'ะจั‹ะถ', 'ะšะปะผ', 'ะขะตะป'], + 'weekdays' => ['ะ ัƒัˆะฐั€ะฝั', 'ะจะพั‡ะผะพ', 'ะšัƒัˆะบั‹ะถะผะพ', 'ะ’ำฑั€ะณะตั‡ะต', 'ะ˜ะทะฐั€ะฝั', 'ะšัƒะณะฐั€ะฝั', 'ะจัƒะผะฐั‚ะบะตั‡ะต'], + 'weekdays_short' => ['ะ ัˆั€', 'ะจั‡ะผ', 'ะšะถะผ', 'ะ’ะณั‡', 'ะ˜ะทั€', 'ะšะณั€', 'ะจะผั‚'], + 'weekdays_min' => ['ะ ัˆั€', 'ะจั‡ะผ', 'ะšะถะผ', 'ะ’ะณั‡', 'ะ˜ะทั€', 'ะšะณั€', 'ะจะผั‚'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ะธะดะฐะปั‹ะบ', + 'y' => ':count ะธะดะฐะปั‹ะบ', + 'a_year' => ':count ะธะดะฐะปั‹ะบ', + + 'month' => ':count ะขั‹ะปะทะต', + 'm' => ':count ะขั‹ะปะทะต', + 'a_month' => ':count ะขั‹ะปะทะต', + + 'week' => ':count ะฐั€ะฝั', + 'w' => ':count ะฐั€ะฝั', + 'a_week' => ':count ะฐั€ะฝั', + + 'day' => ':count ะบะตั‡ะต', + 'd' => ':count ะบะตั‡ะต', + 'a_day' => ':count ะบะตั‡ะต', + + 'hour' => ':count ั‡ะฐั', + 'h' => ':count ั‡ะฐั', + 'a_hour' => ':count ั‡ะฐั', + + 'minute' => ':count ะผะธะฝัƒั‚', + 'min' => ':count ะผะธะฝัƒั‚', + 'a_minute' => ':count ะผะธะฝัƒั‚', + + 'second' => ':count ะบะพะบั‹ะผัˆะฐะฝ', + 's' => ':count ะบะพะบั‹ะผัˆะฐะฝ', + 'a_second' => ':count ะบะพะบั‹ะผัˆะฐะฝ', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mi.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mi.php new file mode 100644 index 0000000000..b7f51ec234 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mi.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - John Corrigan + * - Franรงois B + */ +return [ + 'year' => ':count tau', + 'a_year' => '{1}he tau|:count tau', + 'month' => ':count marama', + 'a_month' => '{1}he marama|:count marama', + 'week' => ':count wiki', + 'a_week' => '{1}he wiki|:count wiki', + 'day' => ':count ra', + 'a_day' => '{1}he ra|:count ra', + 'hour' => ':count haora', + 'a_hour' => '{1}te haora|:count haora', + 'minute' => ':count meneti', + 'a_minute' => '{1}he meneti|:count meneti', + 'second' => ':count hฤ“kona', + 'a_second' => '{1}te hฤ“kona ruarua|:count hฤ“kona', + 'ago' => ':time i mua', + 'from_now' => 'i roto i :time', + 'diff_yesterday' => 'inanahi', + 'diff_yesterday_regexp' => 'inanahi(?:\\s+i)?', + 'diff_today' => 'i teie', + 'diff_today_regexp' => 'i teie(?:\\s+mahana,)?(?:\\s+i)?', + 'diff_tomorrow' => 'apopo', + 'diff_tomorrow_regexp' => 'apopo(?:\\s+i)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [i] HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY [i] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[i teie mahana, i] LT', + 'nextDay' => '[apopo i] LT', + 'nextWeek' => 'dddd [i] LT', + 'lastDay' => '[inanahi i] LT', + 'lastWeek' => 'dddd [whakamutunga i] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberยบ', + 'months' => ['Kohi-tฤte', 'Hui-tanguru', 'Poutลซ-te-rangi', 'Paenga-whฤwhฤ', 'Haratua', 'Pipiri', 'Hลngoingoi', 'Here-turi-kลkฤ', 'Mahuru', 'Whiringa-ฤ-nuku', 'Whiringa-ฤ-rangi', 'Hakihea'], + 'months_short' => ['Kohi', 'Hui', 'Pou', 'Pae', 'Hara', 'Pipi', 'Hลngoi', 'Here', 'Mahu', 'Whi-nu', 'Whi-ra', 'Haki'], + 'weekdays' => ['Rฤtapu', 'Mane', 'Tลซrei', 'Wenerei', 'Tฤite', 'Paraire', 'Hฤtarei'], + 'weekdays_short' => ['Ta', 'Ma', 'Tลซ', 'We', 'Tฤi', 'Pa', 'Hฤ'], + 'weekdays_min' => ['Ta', 'Ma', 'Tลซ', 'We', 'Tฤi', 'Pa', 'Hฤ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' me te '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mi_NZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mi_NZ.php new file mode 100644 index 0000000000..6b964e3afe --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mi_NZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mi.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/miq.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/miq.php new file mode 100644 index 0000000000..51e5a9855f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/miq.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/miq_NI.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/miq_NI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/miq_NI.php new file mode 100644 index 0000000000..57faa318ea --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/miq_NI.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['siakwa kati', 'kuswa kati', 'kakamuk kati', 'lรฎ wainhka kati', 'lih mairin kati', 'lรฎ kati', 'pastara kati', 'sikla kati', 'wรฎs kati', 'waupasa kati', 'yahbra kati', 'trisu kati'], + 'months_short' => ['siakwa kati', 'kuswa kati', 'kakamuk kati', 'lรฎ wainhka kati', 'lih mairin kati', 'lรฎ kati', 'pastara kati', 'sikla kati', 'wรฎs kati', 'waupasa kati', 'yahbra kati', 'trisu kati'], + 'weekdays' => ['sandi', 'mundi', 'tiusdi', 'wensde', 'tausde', 'praidi', 'satadi'], + 'weekdays_short' => ['san', 'mun', 'tius', 'wens', 'taus', 'prai', 'sat'], + 'weekdays_min' => ['san', 'mun', 'tius', 'wens', 'taus', 'prai', 'sat'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 7, + 'meridiem' => ['VM', 'NM'], + + 'month' => ':count kati', // less reliable + 'm' => ':count kati', // less reliable + 'a_month' => ':count kati', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mjw.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mjw.php new file mode 100644 index 0000000000..617154cd09 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mjw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mjw_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mjw_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mjw_IN.php new file mode 100644 index 0000000000..58ed0d1899 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mjw_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Jor Teron bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['Arkoi', 'Thangthang', 'There', 'Jangmi', 'Aru', 'Vosik', 'Jakhong', 'Paipai', 'Chiti', 'Phere', 'Phaikuni', 'Matijong'], + 'months_short' => ['Ark', 'Thang', 'The', 'Jang', 'Aru', 'Vos', 'Jak', 'Pai', 'Chi', 'Phe', 'Phai', 'Mati'], + 'weekdays' => ['Bhomkuru', 'Urmi', 'Durmi', 'Thelang', 'Theman', 'Bhomta', 'Bhomti'], + 'weekdays_short' => ['Bhom', 'Ur', 'Dur', 'Tkel', 'Tkem', 'Bhta', 'Bhti'], + 'weekdays_min' => ['Bhom', 'Ur', 'Dur', 'Tkel', 'Tkem', 'Bhta', 'Bhti'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mk.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mk.php new file mode 100644 index 0000000000..d822de0981 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mk.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sashko Todorov + * - Josh Soref + * - Franรงois B + * - Serhan Apaydฤฑn + * - Borislav Mickov + * - JD Isaacks + * - Tomi Atanasoski + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count ะณะพะดะธะฝะฐ|:count ะณะพะดะธะฝะธ', + 'a_year' => 'ะณะพะดะธะฝะฐ|:count ะณะพะดะธะฝะธ', + 'y' => ':count ะณะพะด.', + 'month' => ':count ะผะตัะตั†|:count ะผะตัะตั†ะธ', + 'a_month' => 'ะผะตัะตั†|:count ะผะตัะตั†ะธ', + 'm' => ':count ะผะตัะตั†|:count ะผะตัะตั†ะธ', + 'week' => ':count ัะตะดะผะธั†ะฐ|:count ัะตะดะผะธั†ะธ', + 'a_week' => 'ัะตะดะผะธั†ะฐ|:count ัะตะดะผะธั†ะธ', + 'w' => ':count ัะตะดะผะธั†ะฐ|:count ัะตะดะผะธั†ะธ', + 'day' => ':count ะดะตะฝ|:count ะดะตะฝะฐ', + 'a_day' => 'ะดะตะฝ|:count ะดะตะฝะฐ', + 'd' => ':count ะดะตะฝ|:count ะดะตะฝะฐ', + 'hour' => ':count ั‡ะฐั|:count ั‡ะฐัะฐ', + 'a_hour' => 'ั‡ะฐั|:count ั‡ะฐัะฐ', + 'h' => ':count ั‡ะฐั|:count ั‡ะฐัะฐ', + 'minute' => ':count ะผะธะฝัƒั‚ะฐ|:count ะผะธะฝัƒั‚ะธ', + 'a_minute' => 'ะผะธะฝัƒั‚ะฐ|:count ะผะธะฝัƒั‚ะธ', + 'min' => ':count ะผะธะฝ.', + 'second' => ':count ัะตะบัƒะฝะดะฐ|:count ัะตะบัƒะฝะดะธ', + 'a_second' => 'ะฝะตะบะพะปะบัƒ ัะตะบัƒะฝะดะธ|:count ัะตะบัƒะฝะดะธ', + 's' => ':count ัะตะบ.', + 'ago' => 'ะฟั€ะตะด :time', + 'from_now' => 'ะฟะพัะปะต :time', + 'after' => 'ะฟะพ :time', + 'before' => 'ะฟั€ะตะด :time', + 'diff_now' => 'ัะตะณะฐ', + 'diff_today' => 'ะ”ะตะฝะตั', + 'diff_today_regexp' => 'ะ”ะตะฝะตั(?:\\s+ะฒะพ)?', + 'diff_yesterday' => 'ะฒั‡ะตั€ะฐ', + 'diff_yesterday_regexp' => 'ะ’ั‡ะตั€ะฐ(?:\\s+ะฒะพ)?', + 'diff_tomorrow' => 'ัƒั‚ั€ะต', + 'diff_tomorrow_regexp' => 'ะฃั‚ั€ะต(?:\\s+ะฒะพ)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY H:mm', + 'LLLL' => 'dddd, D MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[ะ”ะตะฝะตั ะฒะพ] LT', + 'nextDay' => '[ะฃั‚ั€ะต ะฒะพ] LT', + 'nextWeek' => '[ะ’ะพ] dddd [ะฒะพ] LT', + 'lastDay' => '[ะ’ั‡ะตั€ะฐ ะฒะพ] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + case 3: + case 6: + return '[ะ˜ะทะผะธะฝะฐั‚ะฐั‚ะฐ] dddd [ะฒะพ] LT'; + default: + return '[ะ˜ะทะผะธะฝะฐั‚ะธะพั‚] dddd [ะฒะพ] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + $last2Digits = $number % 100; + if ($number === 0) { + return $number.'-ะตะฒ'; + } + if ($last2Digits === 0) { + return $number.'-ะตะฝ'; + } + if ($last2Digits > 10 && $last2Digits < 20) { + return $number.'-ั‚ะธ'; + } + if ($lastDigit === 1) { + return $number.'-ะฒะธ'; + } + if ($lastDigit === 2) { + return $number.'-ั€ะธ'; + } + if ($lastDigit === 7 || $lastDigit === 8) { + return $number.'-ะผะธ'; + } + + return $number.'-ั‚ะธ'; + }, + 'months' => ['ั˜ะฐะฝัƒะฐั€ะธ', 'ั„ะตะฒั€ัƒะฐั€ะธ', 'ะผะฐั€ั‚', 'ะฐะฟั€ะธะป', 'ะผะฐั˜', 'ั˜ัƒะฝะธ', 'ั˜ัƒะปะธ', 'ะฐะฒะณัƒัั‚', 'ัะตะฟั‚ะตะผะฒั€ะธ', 'ะพะบั‚ะพะผะฒั€ะธ', 'ะฝะพะตะผะฒั€ะธ', 'ะดะตะบะตะผะฒั€ะธ'], + 'months_short' => ['ั˜ะฐะฝ', 'ั„ะตะฒ', 'ะผะฐั€', 'ะฐะฟั€', 'ะผะฐั˜', 'ั˜ัƒะฝ', 'ั˜ัƒะป', 'ะฐะฒะณ', 'ัะตะฟ', 'ะพะบั‚', 'ะฝะพะต', 'ะดะตะบ'], + 'weekdays' => ['ะฝะตะดะตะปะฐ', 'ะฟะพะฝะตะดะตะปะฝะธะบ', 'ะฒั‚ะพั€ะฝะธะบ', 'ัั€ะตะดะฐ', 'ั‡ะตั‚ะฒั€ั‚ะพะบ', 'ะฟะตั‚ะพะบ', 'ัะฐะฑะพั‚ะฐ'], + 'weekdays_short' => ['ะฝะตะด', 'ะฟะพะฝ', 'ะฒั‚ะพ', 'ัั€ะต', 'ั‡ะตั‚', 'ะฟะตั‚', 'ัะฐะฑ'], + 'weekdays_min' => ['ะฝe', 'ะฟo', 'ะฒั‚', 'ัั€', 'ั‡ะต', 'ะฟะต', 'ัa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ะธ '], + 'meridiem' => ['ะะœ', 'ะŸะœ'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mk_MK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mk_MK.php new file mode 100644 index 0000000000..95e2ff9cc5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mk_MK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mk.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ml.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ml.php new file mode 100644 index 0000000000..1abd6c4490 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ml.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - JD Isaacks + */ +return [ + 'year' => ':count เดตเตผเดทเด‚', + 'a_year' => 'เด’เดฐเต เดตเตผเดทเด‚|:count เดตเตผเดทเด‚', + 'month' => ':count เดฎเดพเดธเด‚', + 'a_month' => 'เด’เดฐเต เดฎเดพเดธเด‚|:count เดฎเดพเดธเด‚', + 'week' => ':count เด†เดดเตเดš', + 'a_week' => 'เด’เดฐเดพเดดเตเดš|:count เด†เดดเตเดš', + 'day' => ':count เดฆเดฟเดตเดธเด‚', + 'a_day' => 'เด’เดฐเต เดฆเดฟเดตเดธเด‚|:count เดฆเดฟเดตเดธเด‚', + 'hour' => ':count เดฎเดฃเดฟเด•เตเด•เต‚เตผ', + 'a_hour' => 'เด’เดฐเต เดฎเดฃเดฟเด•เตเด•เต‚เตผ|:count เดฎเดฃเดฟเด•เตเด•เต‚เตผ', + 'minute' => ':count เดฎเดฟเดจเดฟเดฑเตเดฑเต', + 'a_minute' => 'เด’เดฐเต เดฎเดฟเดจเดฟเดฑเตเดฑเต|:count เดฎเดฟเดจเดฟเดฑเตเดฑเต', + 'second' => ':count เดธเต†เด•เตเด•เตปเดกเต', + 'a_second' => 'เด…เตฝเดช เดจเดฟเดฎเดฟเดทเด™เตเด™เตพ|:count เดธเต†เด•เตเด•เตปเดกเต', + 'ago' => ':time เดฎเตเตปเดชเต', + 'from_now' => ':time เด•เดดเดฟเดžเตเดžเต', + 'diff_now' => 'เด‡เดชเตเดชเต‹เตพ', + 'diff_today' => 'เด‡เดจเตเดจเต', + 'diff_yesterday' => 'เด‡เดจเตเดจเดฒเต†', + 'diff_tomorrow' => 'เดจเดพเดณเต†', + 'formats' => [ + 'LT' => 'A h:mm -เดจเต', + 'LTS' => 'A h:mm:ss -เดจเต', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm -เดจเต', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm -เดจเต', + ], + 'calendar' => [ + 'sameDay' => '[เด‡เดจเตเดจเต] LT', + 'nextDay' => '[เดจเดพเดณเต†] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[เด‡เดจเตเดจเดฒเต†] LT', + 'lastWeek' => '[เด•เดดเดฟเดžเตเดž] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'เดฐเดพเดคเตเดฐเดฟ'; + } + if ($hour < 12) { + return 'เดฐเดพเดตเดฟเดฒเต†'; + } + if ($hour < 17) { + return 'เด‰เดšเตเดš เด•เดดเดฟเดžเตเดžเต'; + } + if ($hour < 20) { + return 'เดตเตˆเด•เตเดจเตเดจเต‡เดฐเด‚'; + } + + return 'เดฐเดพเดคเตเดฐเดฟ'; + }, + 'months' => ['เดœเดจเตเดตเดฐเดฟ', 'เดซเต†เดฌเตเดฐเตเดตเดฐเดฟ', 'เดฎเดพเตผเดšเตเดšเต', 'เดเดชเตเดฐเดฟเตฝ', 'เดฎเต‡เดฏเต', 'เดœเต‚เตบ', 'เดœเต‚เดฒเตˆ', 'เด“เด—เดธเตเดฑเตเดฑเต', 'เดธเต†เดชเตเดฑเตเดฑเด‚เดฌเตผ', 'เด’เด•เตเดŸเต‹เดฌเตผ', 'เดจเดตเด‚เดฌเตผ', 'เดกเดฟเดธเด‚เดฌเตผ'], + 'months_short' => ['เดœเดจเต.', 'เดซเต†เดฌเตเดฐเต.', 'เดฎเดพเตผ.', 'เดเดชเตเดฐเดฟ.', 'เดฎเต‡เดฏเต', 'เดœเต‚เตบ', 'เดœเต‚เดฒเตˆ.', 'เด“เด—.', 'เดธเต†เดชเตเดฑเตเดฑ.', 'เด’เด•เตเดŸเต‹.', 'เดจเดตเด‚.', 'เดกเดฟเดธเด‚.'], + 'weekdays' => ['เดžเดพเดฏเดฑเดพเดดเตเดš', 'เดคเดฟเด™เตเด•เดณเดพเดดเตเดš', 'เดšเตŠเดตเตเดตเดพเดดเตเดš', 'เดฌเตเดงเดจเดพเดดเตเดš', 'เดตเตเดฏเดพเดดเดพเดดเตเดš', 'เดตเต†เดณเตเดณเดฟเดฏเดพเดดเตเดš', 'เดถเดจเดฟเดฏเดพเดดเตเดš'], + 'weekdays_short' => ['เดžเดพเดฏเตผ', 'เดคเดฟเด™เตเด•เตพ', 'เดšเตŠเดตเตเดต', 'เดฌเตเดงเตป', 'เดตเตเดฏเดพเดดเด‚', 'เดตเต†เดณเตเดณเดฟ', 'เดถเดจเดฟ'], + 'weekdays_min' => ['เดžเดพ', 'เดคเดฟ', 'เดšเตŠ', 'เดฌเต', 'เดตเตเดฏเดพ', 'เดตเต†', 'เดถ'], + 'list' => ', ', + 'weekend' => [0, 0], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ml_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ml_IN.php new file mode 100644 index 0000000000..000e795864 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ml_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ml.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mn.php new file mode 100644 index 0000000000..38c6434d30 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mn.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Zolzaya Erdenebaatar + * - Tom Hughes + * - Akira Matsuda + * - Christopher Dell + * - Michael Kessler + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolรกs Hock Isaza + * - Ochirkhuyag + * - Batmandakh + * - lucifer-crybaby + */ +return [ + 'year' => ':count ะถะธะป', + 'y' => ':count ะถะธะป', + 'month' => ':count ัะฐั€', + 'm' => ':count ัะฐั€', + 'week' => ':count ะดะพะปะพะพ ั…ะพะฝะพะณ', + 'w' => ':count ะดะพะปะพะพ ั…ะพะฝะพะณ', + 'day' => ':count ำฉะดำฉั€', + 'd' => ':count ำฉะดำฉั€', + 'hour' => ':count ั†ะฐะณ', + 'h' => ':countั†', + 'minute' => ':count ะผะธะฝัƒั‚', + 'min' => ':countะผ', + 'second' => ':count ัะตะบัƒะฝะด', + 's' => ':countั', + + 'ago_mode' => 'last', + 'ago' => ':time ำฉะผะฝำฉ', + 'year_ago' => ':count ะถะธะปะธะนะฝ', + 'y_ago' => ':count ะถะธะปะธะนะฝ', + 'month_ago' => ':count ัะฐั€ั‹ะฝ', + 'm_ago' => ':count ัะฐั€ั‹ะฝ', + 'day_ago' => ':count ั…ะพะฝะพะณะธะนะฝ', + 'd_ago' => ':count ั…ะพะฝะพะณะธะนะฝ', + 'week_ago' => ':count ะดะพะปะพะพ ั…ะพะฝะพะณะธะนะฝ', + 'w_ago' => ':count ะดะพะปะพะพ ั…ะพะฝะพะณะธะนะฝ', + 'hour_ago' => ':count ั†ะฐะณะธะนะฝ', + 'minute_ago' => ':count ะผะธะฝัƒั‚ั‹ะฝ', + 'second_ago' => ':count ัะตะบัƒะฝะดั‹ะฝ', + + 'from_now_mode' => 'last', + 'from_now' => 'ะพะดะพะพะณะพะพั :time', + 'year_from_now' => ':count ะถะธะปะธะนะฝ ะดะฐั€ะฐะฐ', + 'y_from_now' => ':count ะถะธะปะธะนะฝ ะดะฐั€ะฐะฐ', + 'month_from_now' => ':count ัะฐั€ั‹ะฝ ะดะฐั€ะฐะฐ', + 'm_from_now' => ':count ัะฐั€ั‹ะฝ ะดะฐั€ะฐะฐ', + 'day_from_now' => ':count ั…ะพะฝะพะณะธะนะฝ ะดะฐั€ะฐะฐ', + 'd_from_now' => ':count ั…ะพะฝะพะณะธะนะฝ ะดะฐั€ะฐะฐ', + 'hour_from_now' => ':count ั†ะฐะณะธะนะฝ ะดะฐั€ะฐะฐ', + 'minute_from_now' => ':count ะผะธะฝัƒั‚ั‹ะฝ ะดะฐั€ะฐะฐ', + 'second_from_now' => ':count ัะตะบัƒะฝะดั‹ะฝ ะดะฐั€ะฐะฐ', + + 'after_mode' => 'last', + 'after' => ':time ะดะฐั€ะฐะฐ', + 'year_after' => ':count ะถะธะปะธะนะฝ', + 'y_after' => ':count ะถะธะปะธะนะฝ', + 'month_after' => ':count ัะฐั€ั‹ะฝ', + 'm_after' => ':count ัะฐั€ั‹ะฝ', + 'day_after' => ':count ั…ะพะฝะพะณะธะนะฝ', + 'd_after' => ':count ั…ะพะฝะพะณะธะนะฝ', + 'hour_after' => ':count ั†ะฐะณะธะนะฝ', + 'minute_after' => ':count ะผะธะฝัƒั‚ั‹ะฝ', + 'second_after' => ':count ัะตะบัƒะฝะดั‹ะฝ', + + 'before_mode' => 'last', + 'before' => ':time ำฉะผะฝำฉ', + 'year_before' => ':count ะถะธะปะธะนะฝ', + 'y_before' => ':count ะถะธะปะธะนะฝ', + 'month_before' => ':count ัะฐั€ั‹ะฝ', + 'm_before' => ':count ัะฐั€ั‹ะฝ', + 'day_before' => ':count ั…ะพะฝะพะณะธะนะฝ', + 'd_before' => ':count ั…ะพะฝะพะณะธะนะฝ', + 'hour_before' => ':count ั†ะฐะณะธะนะฝ', + 'minute_before' => ':count ะผะธะฝัƒั‚ั‹ะฝ', + 'second_before' => ':count ัะตะบัƒะฝะดั‹ะฝ', + + 'list' => ', ', + 'diff_now' => 'ะพะดะพะพ', + 'diff_yesterday' => 'ำฉั‡ะธะณะดำฉั€', + 'diff_tomorrow' => 'ะผะฐั€ะณะฐะฐัˆ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY MMMM DD', + 'LLL' => 'YY-MM-DD, HH:mm', + 'LLLL' => 'YYYY MMMM DD, HH:mm', + ], + 'weekdays' => ['ะัะผ', 'ะ”ะฐะฒะฐะฐ', 'ะœัะณะผะฐั€', 'ะ›ั…ะฐะณะฒะฐ', 'ะŸาฏั€ัะฒ', 'ะ‘ะฐะฐัะฐะฝ', 'ะ‘ัะผะฑะฐ'], + 'weekdays_short' => ['ะั', 'ะ”ะฐ', 'ะœั', 'ะ›ั…', 'ะŸาฏ', 'ะ‘ะฐ', 'ะ‘ั'], + 'weekdays_min' => ['ะั', 'ะ”ะฐ', 'ะœั', 'ะ›ั…', 'ะŸาฏ', 'ะ‘ะฐ', 'ะ‘ั'], + 'months' => ['1 ัะฐั€', '2 ัะฐั€', '3 ัะฐั€', '4 ัะฐั€', '5 ัะฐั€', '6 ัะฐั€', '7 ัะฐั€', '8 ัะฐั€', '9 ัะฐั€', '10 ัะฐั€', '11 ัะฐั€', '12 ัะฐั€'], + 'months_short' => ['1 ัะฐั€', '2 ัะฐั€', '3 ัะฐั€', '4 ัะฐั€', '5 ัะฐั€', '6 ัะฐั€', '7 ัะฐั€', '8 ัะฐั€', '9 ัะฐั€', '10 ัะฐั€', '11 ัะฐั€', '12 ัะฐั€'], + 'meridiem' => ['ำฉะณะปำฉำฉ', 'ะพั€ะพะน'], + 'first_day_of_week' => 1, +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mn_MN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mn_MN.php new file mode 100644 index 0000000000..e5ce426c01 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mn_MN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mn.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mni.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mni.php new file mode 100644 index 0000000000..cafa2f8741 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mni.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mni_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mni_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mni_IN.php new file mode 100644 index 0000000000..45d430ef94 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mni_IN.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เฆœเฆพเฆจเงเงฑเฆพเฆฐเฆฟ', 'เฆซเง‡เฆฌเงเฆฐเงเงฑเฆพเฆฐเฆฟ', 'เฆฎเฆพเฆฐเงเฆš', 'เฆเฆชเงเฆฐเฆฟเฆฒ', 'เฆฎเง‡', 'เฆœเงเฆจ', 'เฆœเงเฆฒเฆพเฆ‡', 'เฆ†เฆ—เฆทเงเฆŸ', 'เฆธเง‡เฆชเงเฆคเง‡เฆฎเงเฆฌเฆฐ', 'เฆ“เฆ•เงเฆคเง‹เฆฌเฆฐ', 'เฆจเฆฌเง‡เฆฎเงเฆฌเฆฐ', 'เฆกเฆฟเฆธเง‡เฆฎเงเฆฌเฆฐ'], + 'months_short' => ['เฆœเฆพเฆจ', 'เฆซเง‡เฆฌ', 'เฆฎเฆพเฆฐ', 'เฆเฆชเงเฆฐเฆฟ', 'เฆฎเง‡', 'เฆœเงเฆจ', 'เฆœเงเฆฒ', 'เฆ†เฆ—', 'เฆธเง‡เฆช', 'เฆ“เฆ•เงเฆค', 'เฆจเฆฌเง‡', 'เฆกเฆฟเฆธ'], + 'weekdays' => ['เฆจเง‹เฆ‚เฆฎเฆพเฆ‡เฆœเฆฟเฆ‚', 'เฆจเฆฟเฆ‚เฆฅเงŒเฆ•เฆพเฆฌเฆพ', 'เฆฒเงˆเฆฌเฆพเฆ•เฆชเง‹เฆ•เฆชเฆพ', 'เงŸเงเฆฎเฆถเฆ•เงˆเฆถเฆพ', 'เฆถเฆ—เง‹เฆฒเฆถเง‡เฆจ', 'เฆ‡เฆฐเฆพเฆ‡', 'เฆฅเฆพเฆ‚เฆœ'], + 'weekdays_short' => ['เฆจเง‹เฆ‚', 'เฆจเฆฟเฆ‚', 'เฆฒเงˆเฆฌเฆพเฆ•', 'เงŸเงเฆฎ', 'เฆถเฆ—เง‹เฆฒ', 'เฆ‡เฆฐเฆพ', 'เฆฅเฆพเฆ‚'], + 'weekdays_min' => ['เฆจเง‹เฆ‚', 'เฆจเฆฟเฆ‚', 'เฆฒเงˆเฆฌเฆพเฆ•', 'เงŸเงเฆฎ', 'เฆถเฆ—เง‹เฆฒ', 'เฆ‡เฆฐเฆพ', 'เฆฅเฆพเฆ‚'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เฆ.เฆฎ.', 'เฆช.เฆฎ.'], + + 'year' => ':count เฆ‡เฆธเฆฟเฆ‚', // less reliable + 'y' => ':count เฆ‡เฆธเฆฟเฆ‚', // less reliable + 'a_year' => ':count เฆ‡เฆธเฆฟเฆ‚', // less reliable + + 'second' => ':count ๊ฏ…๊ฏค๊ฏก๊ฏŠ๊ฏง๊ฏ€๊ฏฅ๊ฏ•', // less reliable + 's' => ':count ๊ฏ…๊ฏค๊ฏก๊ฏŠ๊ฏง๊ฏ€๊ฏฅ๊ฏ•', // less reliable + 'a_second' => ':count ๊ฏ…๊ฏค๊ฏก๊ฏŠ๊ฏง๊ฏ€๊ฏฅ๊ฏ•', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mo.php new file mode 100644 index 0000000000..102afcde58 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mo.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ro.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mr.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mr.php new file mode 100644 index 0000000000..4aaeafd0c2 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mr.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Vikram-enyota + */ +return [ + 'year' => ':count เคตเคฐเฅเคท', + 'y' => ':count เคตเคฐเฅเคท', + 'month' => ':count เคฎเคนเคฟเคจเคพ|:count เคฎเคนเคฟเคจเฅ‡', + 'm' => ':count เคฎเคนเคฟเคจเคพ|:count เคฎเคนเคฟเคจเฅ‡', + 'week' => ':count เค†เค เคตเคกเคพ|:count เค†เค เคตเคกเฅ‡', + 'w' => ':count เค†เค เคตเคกเคพ|:count เค†เค เคตเคกเฅ‡', + 'day' => ':count เคฆเคฟเคตเคธ', + 'd' => ':count เคฆเคฟเคตเคธ', + 'hour' => ':count เคคเคพเคธ', + 'h' => ':count เคคเคพเคธ', + 'minute' => ':count เคฎเคฟเคจเคฟเคŸเฅ‡', + 'min' => ':count เคฎเคฟเคจเคฟเคŸเฅ‡', + 'second' => ':count เคธเฅ‡เค•เค‚เคฆ', + 's' => ':count เคธเฅ‡เค•เค‚เคฆ', + + 'ago' => ':timeเคชเฅ‚เคฐเฅเคตเฅ€', + 'from_now' => ':timeเคฎเคงเฅเคฏเฅ‡', + 'before' => ':timeเคชเฅ‚เคฐเฅเคตเฅ€', + 'after' => ':timeเคจเค‚เคคเคฐ', + + 'diff_now' => 'เค†เคคเฅเคคเคพ', + 'diff_today' => 'เค†เคœ', + 'diff_yesterday' => 'เค•เคพเคฒ', + 'diff_tomorrow' => 'เค‰เคฆเฅเคฏเคพ', + + 'formats' => [ + 'LT' => 'A h:mm เคตเคพเคœเคคเคพ', + 'LTS' => 'A h:mm:ss เคตเคพเคœเคคเคพ', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm เคตเคพเคœเคคเคพ', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm เคตเคพเคœเคคเคพ', + ], + + 'calendar' => [ + 'sameDay' => '[เค†เคœ] LT', + 'nextDay' => '[เค‰เคฆเฅเคฏเคพ] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[เค•เคพเคฒ] LT', + 'lastWeek' => '[เคฎเคพเค—เฅ€เคฒ] dddd, LT', + 'sameElse' => 'L', + ], + + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'เคฐเคพเคคเฅเคฐเฅ€'; + } + if ($hour < 10) { + return 'เคธเค•เคพเคณเฅ€'; + } + if ($hour < 17) { + return 'เคฆเฅเคชเคพเคฐเฅ€'; + } + if ($hour < 20) { + return 'เคธเคพเคฏเค‚เค•เคพเคณเฅ€'; + } + + return 'เคฐเคพเคคเฅเคฐเฅ€'; + }, + + 'months' => ['เคœเคพเคจเฅ‡เคตเคพเคฐเฅ€', 'เคซเฅ‡เคฌเฅเคฐเฅเคตเคพเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เคเคชเฅเคฐเคฟเคฒ', 'เคฎเฅ‡', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเฅˆ', 'เค‘เค—เคธเฅเคŸ', 'เคธเคชเฅเคŸเฅ‡เค‚เคฌเคฐ', 'เค‘เค•เฅเคŸเฅ‹เคฌเคฐ', 'เคจเฅ‹เคตเฅเคนเฅ‡เค‚เคฌเคฐ', 'เคกเคฟเคธเฅ‡เค‚เคฌเคฐ'], + 'months_short' => ['เคœเคพเคจเฅ‡.', 'เคซเฅ‡เคฌเฅเคฐเฅ.', 'เคฎเคพเคฐเฅเคš.', 'เคเคชเฅเคฐเคฟ.', 'เคฎเฅ‡.', 'เคœเฅ‚เคจ.', 'เคœเฅเคฒเฅˆ.', 'เค‘เค—.', 'เคธเคชเฅเคŸเฅ‡เค‚.', 'เค‘เค•เฅเคŸเฅ‹.', 'เคจเฅ‹เคตเฅเคนเฅ‡เค‚.', 'เคกเคฟเคธเฅ‡เค‚.'], + 'weekdays' => ['เคฐเคตเคฟเคตเคพเคฐ', 'เคธเฅ‹เคฎเคตเคพเคฐ', 'เคฎเค‚เค—เคณเคตเคพเคฐ', 'เคฌเฅเคงเคตเคพเคฐ', 'เค—เฅเคฐเฅ‚เคตเคพเคฐ', 'เคถเฅเค•เฅเคฐเคตเคพเคฐ', 'เคถเคจเคฟเคตเคพเคฐ'], + 'weekdays_short' => ['เคฐเคตเคฟ', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—เคณ', 'เคฌเฅเคง', 'เค—เฅเคฐเฅ‚', 'เคถเฅเค•เฅเคฐ', 'เคถเคจเคฟ'], + 'weekdays_min' => ['เคฐ', 'เคธเฅ‹', 'เคฎเค‚', 'เคฌเฅ', 'เค—เฅ', 'เคถเฅ', 'เคถ'], + 'list' => [', ', ' เค†เคฃเคฟ '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mr_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mr_IN.php new file mode 100644 index 0000000000..7bca919f8c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mr_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms.php new file mode 100644 index 0000000000..c9e80854f9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Azri Jamil + * - JD Isaacks + * - Josh Soref + * - Azri Jamil + * - Hariadi Hinta + * - Ashraf Kamarudin + */ +return [ + 'year' => ':count tahun', + 'a_year' => '{1}setahun|]1,Inf[:count tahun', + 'y' => ':count tahun', + 'month' => ':count bulan', + 'a_month' => '{1}sebulan|]1,Inf[:count bulan', + 'm' => ':count bulan', + 'week' => ':count minggu', + 'a_week' => '{1}seminggu|]1,Inf[:count minggu', + 'w' => ':count minggu', + 'day' => ':count hari', + 'a_day' => '{1}sehari|]1,Inf[:count hari', + 'd' => ':count hari', + 'hour' => ':count jam', + 'a_hour' => '{1}sejam|]1,Inf[:count jam', + 'h' => ':count jam', + 'minute' => ':count minit', + 'a_minute' => '{1}seminit|]1,Inf[:count minit', + 'min' => ':count minit', + 'second' => ':count saat', + 'a_second' => '{1}beberapa saat|]1,Inf[:count saat', + 'millisecond' => ':count milisaat', + 'a_millisecond' => '{1}semilisaat|]1,Inf[:count milliseconds', + 'microsecond' => ':count mikrodetik', + 'a_microsecond' => '{1}semikrodetik|]1,Inf[:count mikrodetik', + 's' => ':count saat', + 'ago' => ':time yang lepas', + 'from_now' => ':time dari sekarang', + 'after' => ':time kemudian', + 'before' => ':time sebelum', + 'diff_now' => 'sekarang', + 'diff_today' => 'Hari', + 'diff_today_regexp' => 'Hari(?:\\s+ini)?(?:\\s+pukul)?', + 'diff_yesterday' => 'semalam', + 'diff_yesterday_regexp' => 'Semalam(?:\\s+pukul)?', + 'diff_tomorrow' => 'esok', + 'diff_tomorrow_regexp' => 'Esok(?:\\s+pukul)?', + 'diff_before_yesterday' => 'kelmarin', + 'diff_after_tomorrow' => 'lusa', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [pukul] HH.mm', + 'LLLL' => 'dddd, D MMMM YYYY [pukul] HH.mm', + ], + 'calendar' => [ + 'sameDay' => '[Hari ini pukul] LT', + 'nextDay' => '[Esok pukul] LT', + 'nextWeek' => 'dddd [pukul] LT', + 'lastDay' => '[Kelmarin pukul] LT', + 'lastWeek' => 'dddd [lepas pukul] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 1) { + return 'tengah malam'; + } + + if ($hour < 12) { + return 'pagi'; + } + + if ($hour < 13) { + return 'tengah hari'; + } + + if ($hour < 19) { + return 'petang'; + } + + return 'malam'; + }, + 'months' => ['Januari', 'Februari', 'Mac', 'April', 'Mei', 'Jun', 'Julai', 'Ogos', 'September', 'Oktober', 'November', 'Disember'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ogs', 'Sep', 'Okt', 'Nov', 'Dis'], + 'weekdays' => ['Ahad', 'Isnin', 'Selasa', 'Rabu', 'Khamis', 'Jumaat', 'Sabtu'], + 'weekdays_short' => ['Ahd', 'Isn', 'Sel', 'Rab', 'Kha', 'Jum', 'Sab'], + 'weekdays_min' => ['Ah', 'Is', 'Sl', 'Rb', 'Km', 'Jm', 'Sb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' dan '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_BN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_BN.php new file mode 100644 index 0000000000..ef837a2d78 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_BN.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ms.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/MM/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, h:mm a', + 'LLLL' => 'dd MMMM YYYY, h:mm a', + ], + 'meridiem' => ['a', 'p'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_MY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_MY.php new file mode 100644 index 0000000000..970d604840 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_MY.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Azri Jamil + * - JD Isaacks + */ +return require __DIR__.'/ms.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_SG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_SG.php new file mode 100644 index 0000000000..77cb83d281 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_SG.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ms.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/MM/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY, h:mm a', + ], + 'meridiem' => ['a', 'p'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mt.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mt.php new file mode 100644 index 0000000000..e8aadcc970 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mt.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alessandro Maruccia + */ +return [ + 'year' => 'sena|:count sni|:count sni|:count sni', + 'y' => 'sa sena|:count snin|:count snin|:count snin', + 'month' => 'xahar|:count xhur|:count xhur|:count xhur', + 'm' => ':count xahar|:count xhur|:count xhur|:count xhur', + 'week' => 'gimgฤงa|:count ฤกimgฤงat|:count ฤกimgฤงat|:count ฤกimgฤงat', + 'w' => 'ฤกimgฤงa|:count ฤกimgฤงat|:count ฤกimgฤงat|:count ฤกimgฤงat', + 'day' => 'ฤกurnata|:count ฤกranet|:count ฤกranet|:count ฤกranet', + 'd' => 'ฤกurnata|:count ฤกranet|:count ฤกranet|:count ฤกranet', + 'hour' => 'siegฤงa|:count siegฤงat|:count siegฤงat|:count siegฤงat', + 'h' => 'siegฤงa|:count sigฤงat|:count sigฤงat|:count sigฤงat', + 'minute' => 'minuta|:count minuti|:count minuti|:count minuti', + 'min' => 'min.|:count min.|:count min.|:count min.', + 'second' => 'ftit sekondi|:count sekondi|:count sekondi|:count sekondi', + 's' => 'sek.|:count sek.|:count sek.|:count sek.', + 'ago' => ':time ilu', + 'from_now' => 'fโ€™ :time', + 'diff_now' => 'issa', + 'diff_today' => 'Illum', + 'diff_today_regexp' => 'Illum(?:\\s+fil-)?', + 'diff_yesterday' => 'lbieraฤง', + 'diff_yesterday_regexp' => 'Il-bieraฤง(?:\\s+fil-)?', + 'diff_tomorrow' => 'gฤงada', + 'diff_tomorrow_regexp' => 'Gฤงada(?:\\s+fil-)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Illum fil-]LT', + 'nextDay' => '[Gฤงada fil-]LT', + 'nextWeek' => 'dddd [fil-]LT', + 'lastDay' => '[Il-bieraฤง fil-]LT', + 'lastWeek' => 'dddd [li gฤงadda] [fil-]LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberยบ', + 'months' => ['Jannar', 'Frar', 'Marzu', 'April', 'Mejju', 'ฤ unju', 'Lulju', 'Awwissu', 'Settembru', 'Ottubru', 'Novembru', 'Diฤ‹embru'], + 'months_short' => ['Jan', 'Fra', 'Mar', 'Apr', 'Mej', 'ฤ un', 'Lul', 'Aww', 'Set', 'Ott', 'Nov', 'Diฤ‹'], + 'weekdays' => ['Il-ฤฆadd', 'It-Tnejn', 'It-Tlieta', 'L-Erbgฤงa', 'Il-ฤฆamis', 'Il-ฤ imgฤงa', 'Is-Sibt'], + 'weekdays_short' => ['ฤฆad', 'Tne', 'Tli', 'Erb', 'ฤฆam', 'ฤ im', 'Sib'], + 'weekdays_min' => ['ฤฆa', 'Tn', 'Tl', 'Er', 'ฤฆa', 'ฤ i', 'Si'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' u '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mt_MT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mt_MT.php new file mode 100644 index 0000000000..9534f687a0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mt_MT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mt.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mua.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mua.php new file mode 100644 index 0000000000..a3a3c6fdd3 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mua.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['comme', 'lilli'], + 'weekdays' => ['Comโ€™yakke', 'Comlaaษ—ii', 'Comzyiiษ—ii', 'Comkolle', 'Comkaldวษ“lii', 'Comgaisuu', 'Comzyeษ“suu'], + 'weekdays_short' => ['Cya', 'Cla', 'Czi', 'Cko', 'Cka', 'Cga', 'Cze'], + 'weekdays_min' => ['Cya', 'Cla', 'Czi', 'Cko', 'Cka', 'Cga', 'Cze'], + 'months' => ['Fฤฉi Loo', 'Cokcwaklaล‹ne', 'Cokcwaklii', 'Fฤฉi Marfoo', 'Madววuutวbijaล‹', 'Mamวล‹gwรฃafahbii', 'Mamวล‹gwรฃalii', 'Madวmbii', 'Fฤฉi Dวษ“lii', 'Fฤฉi Mundaล‹', 'Fฤฉi Gwahlle', 'Fฤฉi Yuru'], + 'months_short' => ['FLO', 'CLA', 'CKI', 'FMF', 'MAD', 'MBI', 'MLI', 'MAM', 'FDE', 'FMU', 'FGW', 'FYU'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/my.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/my.php new file mode 100644 index 0000000000..bbdfba4014 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/my.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + * - Nay Lin Aung + */ +return [ + 'year' => '{1}แ€แ€…แ€บแ€”แ€พแ€…แ€บ|]1,Inf[:count แ€”แ€พแ€…แ€บ', + 'y' => ':count แ€”แ€พแ€…แ€บ', + 'month' => '{1}แ€แ€…แ€บแ€œ|]1,Inf[:count แ€œ', + 'm' => ':count แ€œ', + 'week' => ':count แ€•แ€แ€บ', + 'w' => ':count แ€•แ€แ€บ', + 'day' => '{1}แ€แ€…แ€บแ€›แ€€แ€บ|]1,Inf[:count แ€›แ€€แ€บ', + 'd' => ':count แ€›แ€€แ€บ', + 'hour' => '{1}แ€แ€…แ€บแ€”แ€ฌแ€›แ€ฎ|]1,Inf[:count แ€”แ€ฌแ€›แ€ฎ', + 'h' => ':count แ€”แ€ฌแ€›แ€ฎ', + 'minute' => '{1}แ€แ€…แ€บแ€™แ€ญแ€”แ€…แ€บ|]1,Inf[:count แ€™แ€ญแ€”แ€…แ€บ', + 'min' => ':count แ€™แ€ญแ€”แ€…แ€บ', + 'second' => '{1}แ€…แ€€แ€นแ€€แ€”แ€บ.แ€กแ€”แ€Šแ€บแ€ธแ€„แ€šแ€บ|]1,Inf[:count แ€…แ€€แ€นแ€€แ€”แ€ทแ€บ', + 's' => ':count แ€…แ€€แ€นแ€€แ€”แ€บแ€ท', + 'ago' => 'แ€œแ€ฝแ€”แ€บแ€แ€ฒแ€ทแ€žแ€ฑแ€ฌ :time แ€€', + 'from_now' => 'แ€œแ€ฌแ€™แ€Šแ€บแ€ท :time แ€™แ€พแ€ฌ', + 'after' => ':time แ€€แ€ผแ€ฌแ€•แ€ผแ€ฎแ€ธแ€”แ€ฑแ€ฌแ€€แ€บ', + 'before' => ':time แ€™แ€แ€ญแ€ฏแ€„แ€บแ€แ€„แ€บ', + 'diff_now' => 'แ€กแ€แ€ฏแ€œแ€ฑแ€ธแ€แ€„แ€บ', + 'diff_today' => 'แ€šแ€”แ€ฑ.', + 'diff_yesterday' => 'แ€™แ€”แ€ฑแ€ทแ€€', + 'diff_yesterday_regexp' => 'แ€™แ€”แ€ฑ.แ€€', + 'diff_tomorrow' => 'แ€™แ€”แ€€แ€บแ€–แ€ผแ€”แ€บ', + 'diff_before_yesterday' => 'แ€แ€™แ€ผแ€”แ€บแ€”แ€ฑแ€ทแ€€', + 'diff_after_tomorrow' => 'แ€แ€˜แ€€แ€บแ€แ€ซ', + 'period_recurrences' => ':count แ€€แ€ผแ€ญแ€™แ€บ', + 'formats' => [ + 'LT' => 'Oh:Om A', + 'LTS' => 'Oh:Om:Os A', + 'L' => 'OD/OM/OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY Oh:Om A', + 'LLLL' => 'dddd OD MMMM OY Oh:Om A', + ], + 'calendar' => [ + 'sameDay' => '[แ€šแ€”แ€ฑ.] LT [แ€™แ€พแ€ฌ]', + 'nextDay' => '[แ€™แ€”แ€€แ€บแ€–แ€ผแ€”แ€บ] LT [แ€™แ€พแ€ฌ]', + 'nextWeek' => 'dddd LT [แ€™แ€พแ€ฌ]', + 'lastDay' => '[แ€™แ€”แ€ฑ.แ€€] LT [แ€™แ€พแ€ฌ]', + 'lastWeek' => '[แ€•แ€ผแ€ฎแ€ธแ€แ€ฒแ€ทแ€žแ€ฑแ€ฌ] dddd LT [แ€™แ€พแ€ฌ]', + 'sameElse' => 'L', + ], + 'months' => ['แ€‡แ€”แ€บแ€”แ€แ€ซแ€›แ€ฎ', 'แ€–แ€ฑแ€–แ€ฑแ€ฌแ€บแ€แ€ซแ€›แ€ฎ', 'แ€™แ€แ€บ', 'แ€งแ€•แ€ผแ€ฎ', 'แ€™แ€ฑ', 'แ€‡แ€ฝแ€”แ€บ', 'แ€‡แ€ฐแ€œแ€ญแ€ฏแ€„แ€บ', 'แ€žแ€ผแ€‚แ€ฏแ€แ€บ', 'แ€…แ€€แ€บแ€แ€„แ€บแ€˜แ€ฌ', 'แ€กแ€ฑแ€ฌแ€€แ€บแ€แ€ญแ€ฏแ€˜แ€ฌ', 'แ€”แ€ญแ€ฏแ€แ€„แ€บแ€˜แ€ฌ', 'แ€’แ€ฎแ€‡แ€„แ€บแ€˜แ€ฌ'], + 'months_short' => ['แ€‡แ€”แ€บ', 'แ€–แ€ฑ', 'แ€™แ€แ€บ', 'แ€•แ€ผแ€ฎ', 'แ€™แ€ฑ', 'แ€‡แ€ฝแ€”แ€บ', 'แ€œแ€ญแ€ฏแ€„แ€บ', 'แ€žแ€ผ', 'แ€…แ€€แ€บ', 'แ€กแ€ฑแ€ฌแ€€แ€บ', 'แ€”แ€ญแ€ฏ', 'แ€’แ€ฎ'], + 'weekdays' => ['แ€แ€”แ€„แ€บแ€นแ€‚แ€”แ€ฝแ€ฑ', 'แ€แ€”แ€„แ€บแ€นแ€œแ€ฌ', 'แ€กแ€„แ€บแ€นแ€‚แ€ซ', 'แ€—แ€ฏแ€’แ€นแ€“แ€Ÿแ€ฐแ€ธ', 'แ€€แ€ผแ€ฌแ€žแ€•แ€แ€ฑแ€ธ', 'แ€žแ€ฑแ€ฌแ€€แ€ผแ€ฌ', 'แ€…แ€”แ€ฑ'], + 'weekdays_short' => ['แ€”แ€ฝแ€ฑ', 'แ€œแ€ฌ', 'แ€‚แ€ซ', 'แ€Ÿแ€ฐแ€ธ', 'แ€€แ€ผแ€ฌ', 'แ€žแ€ฑแ€ฌ', 'แ€”แ€ฑ'], + 'weekdays_min' => ['แ€”แ€ฝแ€ฑ', 'แ€œแ€ฌ', 'แ€‚แ€ซ', 'แ€Ÿแ€ฐแ€ธ', 'แ€€แ€ผแ€ฌ', 'แ€žแ€ฑแ€ฌ', 'แ€”แ€ฑ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'alt_numbers' => ['แ€แ€', 'แ€แ', 'แ€แ‚', 'แ€แƒ', 'แ€แ„', 'แ€แ…', 'แ€แ†', 'แ€แ‡', 'แ€แˆ', 'แ€แ‰', 'แแ€', 'แแ', 'แแ‚', 'แแƒ', 'แแ„', 'แแ…', 'แแ†', 'แแ‡', 'แแˆ', 'แแ‰', 'แ‚แ€', 'แ‚แ', 'แ‚แ‚', 'แ‚แƒ', 'แ‚แ„', 'แ‚แ…', 'แ‚แ†', 'แ‚แ‡', 'แ‚แˆ', 'แ‚แ‰', 'แƒแ€', 'แƒแ', 'แƒแ‚', 'แƒแƒ', 'แƒแ„', 'แƒแ…', 'แƒแ†', 'แƒแ‡', 'แƒแˆ', 'แƒแ‰', 'แ„แ€', 'แ„แ', 'แ„แ‚', 'แ„แƒ', 'แ„แ„', 'แ„แ…', 'แ„แ†', 'แ„แ‡', 'แ„แˆ', 'แ„แ‰', 'แ…แ€', 'แ…แ', 'แ…แ‚', 'แ…แƒ', 'แ…แ„', 'แ…แ…', 'แ…แ†', 'แ…แ‡', 'แ…แˆ', 'แ…แ‰', 'แ†แ€', 'แ†แ', 'แ†แ‚', 'แ†แƒ', 'แ†แ„', 'แ†แ…', 'แ†แ†', 'แ†แ‡', 'แ†แˆ', 'แ†แ‰', 'แ‡แ€', 'แ‡แ', 'แ‡แ‚', 'แ‡แƒ', 'แ‡แ„', 'แ‡แ…', 'แ‡แ†', 'แ‡แ‡', 'แ‡แˆ', 'แ‡แ‰', 'แˆแ€', 'แˆแ', 'แˆแ‚', 'แˆแƒ', 'แˆแ„', 'แˆแ…', 'แˆแ†', 'แˆแ‡', 'แˆแˆ', 'แˆแ‰', 'แ‰แ€', 'แ‰แ', 'แ‰แ‚', 'แ‰แƒ', 'แ‰แ„', 'แ‰แ…', 'แ‰แ†', 'แ‰แ‡', 'แ‰แˆ', 'แ‰แ‰'], + 'meridiem' => ['แ€”แ€ถแ€”แ€€แ€บ', 'แ€Šแ€”แ€ฑ'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/my_MM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/my_MM.php new file mode 100644 index 0000000000..a0108dd453 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/my_MM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/my.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mzn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mzn.php new file mode 100644 index 0000000000..70f5f23c99 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mzn.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fa.php', [ + 'months' => ['ฺ˜ุงู†ูˆŒู‡', 'ููˆุฑŒู‡', 'ู…ุงุฑุณ', 'ุขูˆุฑŒู„', 'ู…ู‡', 'ฺ˜ูˆุฆู†', 'ฺ˜ูˆุฆŒู‡', 'ุงูˆุช', 'ุณูพุชุงู…ุจุฑ', 'ุงฺฉุชุจุฑ', 'ู†ูˆุงู…ุจุฑ', 'ุฏุณุงู…ุจุฑ'], + 'months_short' => ['ฺ˜ุงู†ูˆŒู‡', 'ููˆุฑŒู‡', 'ู…ุงุฑุณ', 'ุขูˆุฑŒู„', 'ู…ู‡', 'ฺ˜ูˆุฆู†', 'ฺ˜ูˆุฆŒู‡', 'ุงูˆุช', 'ุณูพุชุงู…ุจุฑ', 'ุงฺฉุชุจุฑ', 'ู†ูˆุงู…ุจุฑ', 'ุฏุณุงู…ุจุฑ'], + 'first_day_of_week' => 6, + 'weekend' => [5, 5], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan.php new file mode 100644 index 0000000000..0affece84a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nan_TW.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW.php new file mode 100644 index 0000000000..5c50aa48d2 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYYๅนดMMๆœˆDDๆ—ฅ', + ], + 'months' => ['ไธ€ๆœˆ', 'ไบŒๆœˆ', 'ไธ‰ๆœˆ', 'ๅ››ๆœˆ', 'ไบ”ๆœˆ', 'ๅ…ญๆœˆ', 'ไธƒๆœˆ', 'ๅ…ซๆœˆ', 'ไนๆœˆ', 'ๅๆœˆ', 'ๅไธ€ๆœˆ', 'ๅไบŒๆœˆ'], + 'months_short' => [' 1ๆœˆ', ' 2ๆœˆ', ' 3ๆœˆ', ' 4ๆœˆ', ' 5ๆœˆ', ' 6ๆœˆ', ' 7ๆœˆ', ' 8ๆœˆ', ' 9ๆœˆ', '10ๆœˆ', '11ๆœˆ', '12ๆœˆ'], + 'weekdays' => ['็ฆฎๆ‹œๆ—ฅ', '็ฆฎๆ‹œไธ€', '็ฆฎๆ‹œไบŒ', '็ฆฎๆ‹œไธ‰', '็ฆฎๆ‹œๅ››', '็ฆฎๆ‹œไบ”', '็ฆฎๆ‹œๅ…ญ'], + 'weekdays_short' => ['ๆ—ฅ', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ'], + 'weekdays_min' => ['ๆ—ฅ', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['้ ‚ๆ™ก', 'ไธ‹ๆ™ก'], + + 'year' => ':count ๅนด', + 'y' => ':count ๅนด', + 'a_year' => ':count ๅนด', + + 'month' => ':count goeฬh', + 'm' => ':count goeฬh', + 'a_month' => ':count goeฬh', + + 'week' => ':count lรฉ-pร i', + 'w' => ':count lรฉ-pร i', + 'a_week' => ':count lรฉ-pร i', + + 'day' => ':count ๆ—ฅ', + 'd' => ':count ๆ—ฅ', + 'a_day' => ':count ๆ—ฅ', + + 'hour' => ':count tiรกm-cheng', + 'h' => ':count tiรกm-cheng', + 'a_hour' => ':count tiรกm-cheng', + + 'minute' => ':count Hun-cheng', + 'min' => ':count Hun-cheng', + 'a_minute' => ':count Hun-cheng', + + 'second' => ':count Biรณ', + 's' => ':count Biรณ', + 'a_second' => ':count Biรณ', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW@latin.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW@latin.php new file mode 100644 index 0000000000..99ca2a425e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW@latin.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Arne Goetje arne@canonical.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'months' => ['1goeฬh', '2goeฬh', '3goeฬh', '4goeฬh', '5goeฬh', '6goeฬh', '7goeฬh', '8goeฬh', '9goeฬh', '10goeฬh', '11goeฬh', '12goeฬh'], + 'months_short' => ['1g', '2g', '3g', '4g', '5g', '6g', '7g', '8g', '9g', '10g', '11g', '12g'], + 'weekdays' => ['lรฉ-pร i-jiฬt', 'pร i-it', 'pร i-jฤซ', 'pร i-saโฟ', 'pร i-sรฌ', 'pร i-gลอ˜', 'pร i-laฬk'], + 'weekdays_short' => ['lp', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6'], + 'weekdays_min' => ['lp', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['tรฉng-poอ˜', 'ฤ“-poอ˜'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/naq.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/naq.php new file mode 100644 index 0000000000..fbd9be91e9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/naq.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['วgoagas', 'วƒuias'], + 'weekdays' => ['Sontaxtsees', 'Mantaxtsees', 'Denstaxtsees', 'Wunstaxtsees', 'Dondertaxtsees', 'Fraitaxtsees', 'Satertaxtsees'], + 'weekdays_short' => ['Son', 'Ma', 'De', 'Wu', 'Do', 'Fr', 'Sat'], + 'weekdays_min' => ['Son', 'Ma', 'De', 'Wu', 'Do', 'Fr', 'Sat'], + 'months' => ['วƒKhanni', 'วƒKhanว€gรดab', 'ว€Khuuวkhรขb', 'วƒHรดaว‚khaib', 'วƒKhaitsรขb', 'Gamaว€aeb', 'ว‚Khoesaob', 'Aoวkhuumรปวkhรขb', 'Taraว€khuumรปวkhรขb', 'ว‚Nรปวnรขiseb', 'ว€Hooว‚gaeb', 'Hรดasoreวkhรขb'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], + + 'year' => ':count kurigu', + 'y' => ':count kurigu', + 'a_year' => ':count kurigu', + + 'month' => ':count วaub', // less reliable + 'm' => ':count วaub', // less reliable + 'a_month' => ':count วaub', // less reliable + + 'week' => ':count hรป', // less reliable + 'w' => ':count hรป', // less reliable + 'a_week' => ':count hรป', // less reliable + + 'day' => ':count ว€hobas', // less reliable + 'd' => ':count ว€hobas', // less reliable + 'a_day' => ':count ว€hobas', // less reliable + + 'hour' => ':count ว‚gaes', // less reliable + 'h' => ':count ว‚gaes', // less reliable + 'a_hour' => ':count ว‚gaes', // less reliable + + 'minute' => ':count minutga', // less reliable + 'min' => ':count minutga', // less reliable + 'a_minute' => ':count minutga', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nb.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nb.php new file mode 100644 index 0000000000..371ee84081 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nb.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Alexander Tรธmmerรฅs + * - Sigurd Gartmann + * - JD Isaacks + */ +return [ + 'year' => ':count รฅr|:count รฅr', + 'a_year' => 'ett รฅr|:count รฅr', + 'y' => ':count รฅr|:count รฅr', + 'month' => ':count mรฅned|:count mรฅneder', + 'a_month' => 'en mรฅned|:count mรฅneder', + 'm' => ':count md.', + 'week' => ':count uke|:count uker', + 'a_week' => 'en uke|:count uker', + 'w' => ':count u.', + 'day' => ':count dag|:count dager', + 'a_day' => 'en dag|:count dager', + 'd' => ':count d.', + 'hour' => ':count time|:count timer', + 'a_hour' => 'en time|:count timer', + 'h' => ':count t', + 'minute' => ':count minutt|:count minutter', + 'a_minute' => 'ett minutt|:count minutter', + 'min' => ':count min', + 'second' => ':count sekund|:count sekunder', + 'a_second' => 'noen sekunder|:count sekunder', + 's' => ':count sek', + 'ago' => ':time siden', + 'from_now' => 'om :time', + 'after' => ':time etter', + 'before' => ':time fรธr', + 'diff_now' => 'akkurat nรฅ', + 'diff_today' => 'i dag', + 'diff_today_regexp' => 'i dag(?:\\s+kl.)?', + 'diff_yesterday' => 'i gรฅr', + 'diff_yesterday_regexp' => 'i gรฅr(?:\\s+kl.)?', + 'diff_tomorrow' => 'i morgen', + 'diff_tomorrow_regexp' => 'i morgen(?:\\s+kl.)?', + 'diff_before_yesterday' => 'i forgรฅrs', + 'diff_after_tomorrow' => 'i overmorgen', + 'period_recurrences' => 'en gang|:count ganger', + 'period_interval' => 'hver :interval', + 'period_start_date' => 'fra :date', + 'period_end_date' => 'til :date', + 'months' => ['januar', 'februar', 'mars', 'april', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['sรธndag', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lรธrdag'], + 'weekdays_short' => ['sรธn', 'man', 'tir', 'ons', 'tor', 'fre', 'lรธr'], + 'weekdays_min' => ['sรธ', 'ma', 'ti', 'on', 'to', 'fr', 'lรธ'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY [kl.] HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[i dag kl.] LT', + 'nextDay' => '[i morgen kl.] LT', + 'nextWeek' => 'dddd [kl.] LT', + 'lastDay' => '[i gรฅr kl.] LT', + 'lastWeek' => '[forrige] dddd [kl.] LT', + 'sameElse' => 'L', + ], + 'list' => [', ', ' og '], + 'meridiem' => ['a.m.', 'p.m.'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nb_NO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nb_NO.php new file mode 100644 index 0000000000..31678c53ed --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nb_NO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nb.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nb_SJ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nb_SJ.php new file mode 100644 index 0000000000..ce0210bc0a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nb_SJ.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/nb.php', [ + 'formats' => [ + 'LL' => 'D. MMM YYYY', + 'LLL' => 'D. MMMM YYYY, HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY, HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nd.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nd.php new file mode 100644 index 0000000000..f75d9a714d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nd.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Sonto', 'Mvulo', 'Sibili', 'Sithathu', 'Sine', 'Sihlanu', 'Mgqibelo'], + 'weekdays_short' => ['Son', 'Mvu', 'Sib', 'Sit', 'Sin', 'Sih', 'Mgq'], + 'weekdays_min' => ['Son', 'Mvu', 'Sib', 'Sit', 'Sin', 'Sih', 'Mgq'], + 'months' => ['Zibandlela', 'Nhlolanja', 'Mbimbitho', 'Mabasa', 'Nkwenkwezi', 'Nhlangula', 'Ntulikazi', 'Ncwabakazi', 'Mpandula', 'Mfumfu', 'Lwezi', 'Mpalakazi'], + 'months_short' => ['Zib', 'Nhlo', 'Mbi', 'Mab', 'Nkw', 'Nhla', 'Ntu', 'Ncw', 'Mpan', 'Mfu', 'Lwe', 'Mpal'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => 'okweminyaka engu-:count', // less reliable + 'y' => 'okweminyaka engu-:count', // less reliable + 'a_year' => 'okweminyaka engu-:count', // less reliable + + 'month' => 'inyanga ezingu-:count', + 'm' => 'inyanga ezingu-:count', + 'a_month' => 'inyanga ezingu-:count', + + 'week' => 'amaviki angu-:count', + 'w' => 'amaviki angu-:count', + 'a_week' => 'amaviki angu-:count', + + 'day' => 'kwamalanga angu-:count', + 'd' => 'kwamalanga angu-:count', + 'a_day' => 'kwamalanga angu-:count', + + 'hour' => 'amahola angu-:count', + 'h' => 'amahola angu-:count', + 'a_hour' => 'amahola angu-:count', + + 'minute' => 'imizuzu engu-:count', + 'min' => 'imizuzu engu-:count', + 'a_minute' => 'imizuzu engu-:count', + + 'second' => 'imizuzwana engu-:count', + 's' => 'imizuzwana engu-:count', + 'a_second' => 'imizuzwana engu-:count', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nds.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nds.php new file mode 100644 index 0000000000..c0b3775e75 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nds.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nds_DE.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nds_DE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nds_DE.php new file mode 100644 index 0000000000..a6c57a9101 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nds_DE.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Jannuaar', 'Feberwaar', 'Mรคrz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan', 'Feb', 'Mรคr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + 'weekdays' => ['Sรผnndag', 'Maandag', 'Dingsdag', 'Middeweek', 'Dunnersdag', 'Freedag', 'Sรผnnavend'], + 'weekdays_short' => ['Sdag', 'Maan', 'Ding', 'Midd', 'Dunn', 'Free', 'Svd.'], + 'weekdays_min' => ['Sd', 'Ma', 'Di', 'Mi', 'Du', 'Fr', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count Johr', + 'y' => ':countJ', + 'a_year' => '{1}een Johr|:count Johr', + + 'month' => ':count Maand', + 'm' => ':countM', + 'a_month' => '{1}een Maand|:count Maand', + + 'week' => ':count Week|:count Weken', + 'w' => ':countW', + 'a_week' => '{1}een Week|:count Week|:count Weken', + + 'day' => ':count Dag|:count Daag', + 'd' => ':countD', + 'a_day' => '{1}een Dag|:count Dag|:count Daag', + + 'hour' => ':count Stรผnn|:count Stรผnnen', + 'h' => ':countSt', + 'a_hour' => '{1}een Stรผnn|:count Stรผnn|:count Stรผnnen', + + 'minute' => ':count Minuut|:count Minuten', + 'min' => ':countm', + 'a_minute' => '{1}een Minuut|:count Minuut|:count Minuten', + + 'second' => ':count Sekunn|:count Sekunnen', + 's' => ':counts', + 'a_second' => 'en poor Sekunnen|:count Sekunn|:count Sekunnen', + + 'ago' => 'vรถr :time', + 'from_now' => 'in :time', + 'before' => ':time vรถrher', + 'after' => ':time later', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nds_NL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nds_NL.php new file mode 100644 index 0000000000..de2c57bcc8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nds_NL.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Jaunuwoa', 'Februwoa', 'Moaz', 'Aprell', 'Mai', 'Juni', 'Juli', 'August', 'Septamba', 'Oktoba', 'Nowamba', 'Dezamba'], + 'months_short' => ['Jan', 'Feb', 'Moz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Now', 'Dez'], + 'weekdays' => ['Sinndag', 'Mondag', 'Dingsdag', 'Meddwรคakj', 'Donnadag', 'Friedag', 'Sinnowend'], + 'weekdays_short' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'weekdays_min' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne.php new file mode 100644 index 0000000000..d4caf0e25f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - nootanghimire + * - Josh Soref + * - Nj Subedi + * - JD Isaacks + */ +return [ + 'year' => 'เคเค• เคฌเคฐเฅเคท|:count เคฌเคฐเฅเคท', + 'y' => ':count เคตเคฐเฅเคท', + 'month' => 'เคเค• เคฎเคนเคฟเคจเคพ|:count เคฎเคนเคฟเคจเคพ', + 'm' => ':count เคฎเคนเคฟเคจเคพ', + 'week' => ':count เคนเคชเฅเคคเคพ', + 'w' => ':count เคนเคชเฅเคคเคพ', + 'day' => 'เคเค• เคฆเคฟเคจ|:count เคฆเคฟเคจ', + 'd' => ':count เคฆเคฟเคจ', + 'hour' => 'เคเค• เค˜เคฃเฅเคŸเคพ|:count เค˜เคฃเฅเคŸเคพ', + 'h' => ':count เค˜เคฃเฅเคŸเคพ', + 'minute' => 'เคเค• เคฎเคฟเคจเฅ‡เคŸ|:count เคฎเคฟเคจเฅ‡เคŸ', + 'min' => ':count เคฎเคฟเคจเฅ‡เคŸ', + 'second' => 'เค•เฅ‡เคนเฅ€ เค•เฅเคทเคฃ|:count เคธเฅ‡เค•เฅ‡เคฃเฅเคก', + 's' => ':count เคธเฅ‡เค•เฅ‡เคฃเฅเคก', + 'ago' => ':time เค…เค—เคพเคกเคฟ', + 'from_now' => ':timeเคฎเคพ', + 'after' => ':time เคชเค›เคฟ', + 'before' => ':time เค…เค˜เคฟ', + 'diff_now' => 'เค…เคนเคฟเคฒเฅ‡', + 'diff_today' => 'เค†เคœ', + 'diff_yesterday' => 'เคนเคฟเคœเฅ‹', + 'diff_tomorrow' => 'เคญเฅ‹เคฒเคฟ', + 'formats' => [ + 'LT' => 'Aเค•เฅ‹ h:mm เคฌเคœเฅ‡', + 'LTS' => 'Aเค•เฅ‹ h:mm:ss เคฌเคœเฅ‡', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, Aเค•เฅ‹ h:mm เคฌเคœเฅ‡', + 'LLLL' => 'dddd, D MMMM YYYY, Aเค•เฅ‹ h:mm เคฌเคœเฅ‡', + ], + 'calendar' => [ + 'sameDay' => '[เค†เคœ] LT', + 'nextDay' => '[เคญเฅ‹เคฒเคฟ] LT', + 'nextWeek' => '[เค†เค‰เคเคฆเฅ‹] dddd[,] LT', + 'lastDay' => '[เคนเคฟเคœเฅ‹] LT', + 'lastWeek' => '[เค—เคเค•เฅ‹] dddd[,] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 3) { + return 'เคฐเคพเคคเคฟ'; + } + if ($hour < 12) { + return 'เคฌเคฟเคนเคพเคจ'; + } + if ($hour < 16) { + return 'เคฆเคฟเค‰เคเคธเฅ‹'; + } + if ($hour < 20) { + return 'เคธเคพเคเค'; + } + + return 'เคฐเคพเคคเคฟ'; + }, + 'months' => ['เคœเคจเคตเคฐเฅ€', 'เคซเฅ‡เคฌเฅเคฐเฅเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเคฟเคฒ', 'เคฎเคˆ', 'เคœเฅเคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคทเฅเคŸ', 'เคธเฅ‡เคชเฅเคŸเฅ‡เคฎเฅเคฌเคฐ', 'เค…เค•เฅเคŸเฅ‹เคฌเคฐ', 'เคจเฅ‹เคญเฅ‡เคฎเฅเคฌเคฐ', 'เคกเคฟเคธเฅ‡เคฎเฅเคฌเคฐ'], + 'months_short' => ['เคœเคจ.', 'เคซเฅ‡เคฌเฅเคฐเฅ.', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเคฟ.', 'เคฎเคˆ', 'เคœเฅเคจ', 'เคœเฅเคฒเคพเคˆ.', 'เค…เค—.', 'เคธเฅ‡เคชเฅเคŸ.', 'เค…เค•เฅเคŸเฅ‹.', 'เคจเฅ‹เคญเฅ‡.', 'เคกเคฟเคธเฅ‡.'], + 'weekdays' => ['เค†เค‡เคคเคฌเคพเคฐ', 'เคธเฅ‹เคฎเคฌเคพเคฐ', 'เคฎเค™เฅเค—เคฒเคฌเคพเคฐ', 'เคฌเฅเคงเคฌเคพเคฐ', 'เคฌเคฟเคนเคฟเคฌเคพเคฐ', 'เคถเฅเค•เฅเคฐเคฌเคพเคฐ', 'เคถเคจเคฟเคฌเคพเคฐ'], + 'weekdays_short' => ['เค†เค‡เคค.', 'เคธเฅ‹เคฎ.', 'เคฎเค™เฅเค—เคฒ.', 'เคฌเฅเคง.', 'เคฌเคฟเคนเคฟ.', 'เคถเฅเค•เฅเคฐ.', 'เคถเคจเคฟ.'], + 'weekdays_min' => ['เค†.', 'เคธเฅ‹.', 'เคฎเค‚.', 'เคฌเฅ.', 'เคฌเคฟ.', 'เคถเฅ.', 'เคถ.'], + 'list' => [', ', ' เคฐ '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne_IN.php new file mode 100644 index 0000000000..f68d00e3e7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne_IN.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ne.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'yy/M/d', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D, h:mm a', + 'LLLL' => 'YYYY MMMM D, dddd, h:mm a', + ], + 'months' => ['เคœเคจเคตเคฐเฅ€', 'เคซเฅ‡เคฌเฅเคฐเฅเค…เคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเคฟเคฒ', 'เคฎเฅ‡', 'เคœเฅเคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคŸ', 'เคธเฅ‡เคชเฅเคŸเฅ‡เคฎเฅเคฌเคฐ', 'เค…เค•เฅเคŸเฅ‹เคฌเคฐ', 'เคจเฅ‹เคญเฅ‡เคฎเฅเคฌเคฐ', 'เคกเคฟเคธเฅ‡เคฎเฅเคฌเคฐ'], + 'months_short' => ['เคœเคจเคตเคฐเฅ€', 'เคซเฅ‡เคฌเฅเคฐเฅเค…เคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเคฟเคฒ', 'เคฎเฅ‡', 'เคœเฅเคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคŸ', 'เคธเฅ‡เคชเฅเคŸเฅ‡เคฎเฅเคฌเคฐ', 'เค…เค•เฅเคŸเฅ‹เคฌเคฐ', 'เคจเฅ‹เคญเฅ‡เคฎเฅเคฌเคฐ', 'เคกเคฟเคธเฅ‡เคฎเฅเคฌเคฐ'], + 'weekend' => [0, 0], + 'meridiem' => ['เคชเฅ‚เคฐเฅเคตเคพเคนเฅเคจ', 'เค…เคชเคฐเคพเคนเฅเคจ'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne_NP.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne_NP.php new file mode 100644 index 0000000000..27840c0fe1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne_NP.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ne.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nhn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nhn.php new file mode 100644 index 0000000000..5a858315c3 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nhn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nhn_MX.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nhn_MX.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nhn_MX.php new file mode 100644 index 0000000000..9db88a1281 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nhn_MX.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + 'weekdays' => ['teoilhuitl', 'ceilhuitl', 'omeilhuitl', 'yeilhuitl', 'nahuilhuitl', 'macuililhuitl', 'chicuaceilhuitl'], + 'weekdays_short' => ['teo', 'cei', 'ome', 'yei', 'nau', 'mac', 'chi'], + 'weekdays_min' => ['teo', 'cei', 'ome', 'yei', 'nau', 'mac', 'chi'], + 'day_of_first_week_of_year' => 1, + + 'month' => ':count metztli', // less reliable + 'm' => ':count metztli', // less reliable + 'a_month' => ':count metztli', // less reliable + + 'week' => ':count tonalli', // less reliable + 'w' => ':count tonalli', // less reliable + 'a_week' => ':count tonalli', // less reliable + + 'day' => ':count tonatih', // less reliable + 'd' => ':count tonatih', // less reliable + 'a_day' => ':count tonatih', // less reliable + + 'minute' => ':count toltecayotl', // less reliable + 'min' => ':count toltecayotl', // less reliable + 'a_minute' => ':count toltecayotl', // less reliable + + 'second' => ':count ome', // less reliable + 's' => ':count ome', // less reliable + 'a_second' => ':count ome', // less reliable + + 'year' => ':count xihuitl', + 'y' => ':count xihuitl', + 'a_year' => ':count xihuitl', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/niu.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/niu.php new file mode 100644 index 0000000000..bd9be8aae4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/niu.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/niu_NU.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/niu_NU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/niu_NU.php new file mode 100644 index 0000000000..6e7a697bd0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/niu_NU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RockET Systems Emani Fakaotimanava-Lui emani@niue.nu + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Ianuali', 'Fepuali', 'Masi', 'Apelila', 'Me', 'Iuni', 'Iulai', 'Aokuso', 'Sepetema', 'Oketopa', 'Novema', 'Tesemo'], + 'months_short' => ['Ian', 'Fep', 'Mas', 'Ape', 'Me', 'Iun', 'Iul', 'Aok', 'Sep', 'Oke', 'Nov', 'Tes'], + 'weekdays' => ['Aho Tapu', 'Aho Gofua', 'Aho Ua', 'Aho Lotu', 'Aho Tuloto', 'Aho Falaile', 'Aho Faiumu'], + 'weekdays_short' => ['Tapu', 'Gofua', 'Ua', 'Lotu', 'Tuloto', 'Falaile', 'Faiumu'], + 'weekdays_min' => ['Tapu', 'Gofua', 'Ua', 'Lotu', 'Tuloto', 'Falaile', 'Faiumu'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count tau', + 'y' => ':count tau', + 'a_year' => ':count tau', + + 'month' => ':count mahina', + 'm' => ':count mahina', + 'a_month' => ':count mahina', + + 'week' => ':count faahi tapu', + 'w' => ':count faahi tapu', + 'a_week' => ':count faahi tapu', + + 'day' => ':count aho', + 'd' => ':count aho', + 'a_day' => ':count aho', + + 'hour' => ':count e tulฤ', + 'h' => ':count e tulฤ', + 'a_hour' => ':count e tulฤ', + + 'minute' => ':count minuti', + 'min' => ':count minuti', + 'a_minute' => ':count minuti', + + 'second' => ':count sekone', + 's' => ':count sekone', + 'a_second' => ':count sekone', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl.php new file mode 100644 index 0000000000..2d737703d3 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Roy + * - Stephan + * - Franรงois B + * - Tim Fish + * - Kevin Huang + * - Jacob Middag + * - JD Isaacks + * - Roy + * - Stephan + * - Franรงois B + * - Tim Fish + * - Jacob Middag + * - JD Isaacks + * - Propaganistas + * - MegaXLR + * - adriaanzon + * - MonkeyPhysics + * - JeroenG + * - RikSomers + * - proclame + * - Rik de Groot (hwdegroot) + */ +return [ + 'year' => ':count jaar|:count jaar', + 'a_year' => 'een jaar|:count jaar', + 'y' => ':countj', + 'month' => ':count maand|:count maanden', + 'a_month' => 'een maand|:count maanden', + 'm' => ':countmnd', + 'week' => ':count week|:count weken', + 'a_week' => 'een week|:count weken', + 'w' => ':countw', + 'day' => ':count dag|:count dagen', + 'a_day' => 'een dag|:count dagen', + 'd' => ':countd', + 'hour' => ':count uur|:count uur', + 'a_hour' => 'een uur|:count uur', + 'h' => ':countu', + 'minute' => ':count minuut|:count minuten', + 'a_minute' => 'een minuut|:count minuten', + 'min' => ':countmin', + 'second' => ':count seconde|:count seconden', + 'a_second' => 'een paar seconden|:count seconden', + 's' => ':counts', + 'ago' => ':time geleden', + 'from_now' => 'over :time', + 'after' => ':time later', + 'before' => ':time eerder', + 'diff_now' => 'nu', + 'diff_today' => 'vandaag', + 'diff_today_regexp' => 'vandaag(?:\\s+om)?', + 'diff_yesterday' => 'gisteren', + 'diff_yesterday_regexp' => 'gisteren(?:\\s+om)?', + 'diff_tomorrow' => 'morgen', + 'diff_tomorrow_regexp' => 'morgen(?:\\s+om)?', + 'diff_after_tomorrow' => 'overmorgen', + 'diff_before_yesterday' => 'eergisteren', + 'period_recurrences' => ':count keer', + 'period_interval' => function (string $interval = '') { + /** @var string $output */ + $output = preg_replace('/^(een|รฉรฉn|1)\s+/u', '', $interval); + + if (preg_match('/^(een|รฉรฉn|1)( jaar|j| uur|u)/u', $interval)) { + return "elk $output"; + } + + return "elke $output"; + }, + 'period_start_date' => 'van :date', + 'period_end_date' => 'tot :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[vandaag om] LT', + 'nextDay' => '[morgen om] LT', + 'nextWeek' => 'dddd [om] LT', + 'lastDay' => '[gisteren om] LT', + 'lastWeek' => '[afgelopen] dddd [om] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); + }, + 'months' => ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'mmm_suffix' => '.', + 'weekdays' => ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + 'weekdays_short' => ['zo.', 'ma.', 'di.', 'wo.', 'do.', 'vr.', 'za.'], + 'weekdays_min' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' en '], + 'meridiem' => ['\'s ochtends', '\'s middags'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_AW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_AW.php new file mode 100644 index 0000000000..5ec136d1bb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_AW.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/nl.php', [ + 'formats' => [ + 'L' => 'DD-MM-YY', + ], + 'months' => ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + 'weekdays_short' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'weekdays_min' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_BE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_BE.php new file mode 100644 index 0000000000..037f5b4aa0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_BE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Roy + * - Stephan + * - Franรงois B + * - Tim Fish + * - Kevin Huang + * - Jacob Middag + * - JD Isaacks + * - Propaganistas + */ +return array_replace_recursive(require __DIR__.'/nl.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_BQ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_BQ.php new file mode 100644 index 0000000000..c269197b73 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_BQ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nl.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_CW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_CW.php new file mode 100644 index 0000000000..c269197b73 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_CW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nl.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_NL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_NL.php new file mode 100644 index 0000000000..14e4853e15 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_NL.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/nl.php', [ + 'months' => ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + 'weekdays_short' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'weekdays_min' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_SR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_SR.php new file mode 100644 index 0000000000..c269197b73 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_SR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nl.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_SX.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_SX.php new file mode 100644 index 0000000000..c269197b73 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl_SX.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nl.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nmg.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nmg.php new file mode 100644 index 0000000000..4d1df6e50d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nmg.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['manรก', 'kugรบ'], + 'weekdays' => ['sษ”ฬndษ”', 'mษ”ฬndษ”', 'sษ”ฬndษ” mafรบ mรกba', 'sษ”ฬndษ” mafรบ mรกlal', 'sษ”ฬndษ” mafรบ mรกna', 'mabรกgรก mรก sukul', 'sรกsadi'], + 'weekdays_short' => ['sษ”ฬn', 'mษ”ฬn', 'smb', 'sml', 'smn', 'mbs', 'sas'], + 'weekdays_min' => ['sษ”ฬn', 'mษ”ฬn', 'smb', 'sml', 'smn', 'mbs', 'sas'], + 'months' => ['ngwษ›n matรกhra', 'ngwษ›n ล„mba', 'ngwษ›n ล„lal', 'ngwษ›n ล„na', 'ngwษ›n ล„tan', 'ngwษ›n ล„tuรณ', 'ngwษ›n hษ›mbuษ›rรญ', 'ngwษ›n lษ”mbi', 'ngwษ›n rษ›bvuรข', 'ngwษ›n wum', 'ngwษ›n wum navว”r', 'krรญsimin'], + 'months_short' => ['ng1', 'ng2', 'ng3', 'ng4', 'ng5', 'ng6', 'ng7', 'ng8', 'ng9', 'ng10', 'ng11', 'kris'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nn.php new file mode 100644 index 0000000000..041f7b29fa --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nn.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Alexander Tรธmmerรฅs + * - ร˜ystein + * - JD Isaacks + * - Gaute Hvoslef Kvalnes (gaute) + */ +return [ + 'year' => ':count รฅr', + 'a_year' => 'eit รฅr|:count รฅr', + 'y' => ':count รฅr', + 'month' => ':count mรฅnad|:count mรฅnader', + 'a_month' => 'ein mรฅnad|:count mรฅnader', + 'm' => ':count md', + 'week' => ':count veke|:count veker', + 'a_week' => 'ei veke|:count veker', + 'w' => ':countv', + 'day' => ':count dag|:count dagar', + 'a_day' => 'ein dag|:count dagar', + 'd' => ':countd', + 'hour' => ':count time|:count timar', + 'a_hour' => 'ein time|:count timar', + 'h' => ':countt', + 'minute' => ':count minutt', + 'a_minute' => 'eit minutt|:count minutt', + 'min' => ':countm', + 'second' => ':count sekund', + 'a_second' => 'nokre sekund|:count sekund', + 's' => ':counts', + 'ago' => ':time sidan', + 'from_now' => 'om :time', + 'after' => ':time etter', + 'before' => ':time fรธr', + 'diff_today' => 'I dag', + 'diff_yesterday' => 'I gรฅr', + 'diff_yesterday_regexp' => 'I gรฅr(?:\\s+klokka)?', + 'diff_tomorrow' => 'I morgon', + 'diff_tomorrow_regexp' => 'I morgon(?:\\s+klokka)?', + 'diff_today_regexp' => 'I dag(?:\\s+klokka)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY [kl.] H:mm', + 'LLLL' => 'dddd D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[I dag klokka] LT', + 'nextDay' => '[I morgon klokka] LT', + 'nextWeek' => 'dddd [klokka] LT', + 'lastDay' => '[I gรฅr klokka] LT', + 'lastWeek' => '[Fรธregรฅande] dddd [klokka] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mars', 'april', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['sundag', 'mรฅndag', 'tysdag', 'onsdag', 'torsdag', 'fredag', 'laurdag'], + 'weekdays_short' => ['sun', 'mรฅn', 'tys', 'ons', 'tor', 'fre', 'lau'], + 'weekdays_min' => ['su', 'mรฅ', 'ty', 'on', 'to', 'fr', 'la'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], + 'meridiem' => ['f.m.', 'e.m.'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nn_NO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nn_NO.php new file mode 100644 index 0000000000..8e16871140 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nn_NO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nn.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nnh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nnh.php new file mode 100644 index 0000000000..007d23993e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nnh.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['mbaสผรกmbaสผ', 'ncwรฒnzรฉm'], + 'weekdays' => null, + 'weekdays_short' => ['lyษ›สผษ›ฬ sแบ…รญล‹tรจ', 'mvfรฒ lyษ›ฬŒสผ', 'mbษ”ฬษ”ntรจ mvfรฒ lyษ›ฬŒสผ', 'tsรจtsษ›ฬ€ษ› lyษ›ฬŒสผ', 'mbษ”ฬษ”ntรจ tsetsษ›ฬ€ษ› lyษ›ฬŒสผ', 'mvfรฒ mร ga lyษ›ฬŒสผ', 'mร ga lyษ›ฬŒสผ'], + 'weekdays_min' => null, + 'months' => null, + 'months_short' => ['saล‹ tsetsษ›ฬ€ษ› lรนm', 'saล‹ kร g ngwรณล‹', 'saล‹ lepyรจ shรบm', 'saล‹ cรฟรณ', 'saล‹ tsษ›ฬ€ษ› cรฟรณ', 'saล‹ njรฟolรกสผ', 'saล‹ tyษ›ฬ€b tyษ›ฬ€b mbส‰ฬ€ล‹', 'saล‹ mbส‰ฬ€ล‹', 'saล‹ ngwษ”ฬ€สผ mbรฟษ›', 'saล‹ tร ล‹a tsetsรกสผ', 'saล‹ mejwoล‹รณ', 'saล‹ lรนm'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => '[lyษ›]ฬŒสผ d [na] MMMM, YYYY HH:mm', + 'LLLL' => 'dddd , [lyษ›]ฬŒสผ d [na] MMMM, YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/no.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/no.php new file mode 100644 index 0000000000..f4497c7596 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/no.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Daniel S. Billing + * - Paul + * - Jimmie Johansson + * - Jens Herlevsen + */ +return array_replace_recursive(require __DIR__.'/nb.php', [ + 'formats' => [ + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'nextWeek' => 'pรฅ dddd [kl.] LT', + 'lastWeek' => '[i] dddd[s kl.] LT', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nr.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nr.php new file mode 100644 index 0000000000..1bc999f9df --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nr_ZA.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nr_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nr_ZA.php new file mode 100644 index 0000000000..f9a7be82e4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nr_ZA.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Janabari', 'uFeberbari', 'uMatjhi', 'u-Apreli', 'Meyi', 'Juni', 'Julayi', 'Arhostosi', 'Septemba', 'Oktoba', 'Usinyikhaba', 'Disemba'], + 'months_short' => ['Jan', 'Feb', 'Mat', 'Apr', 'Mey', 'Jun', 'Jul', 'Arh', 'Sep', 'Okt', 'Usi', 'Dis'], + 'weekdays' => ['uSonto', 'uMvulo', 'uLesibili', 'lesithathu', 'uLesine', 'ngoLesihlanu', 'umGqibelo'], + 'weekdays_short' => ['Son', 'Mvu', 'Bil', 'Tha', 'Ne', 'Hla', 'Gqi'], + 'weekdays_min' => ['Son', 'Mvu', 'Bil', 'Tha', 'Ne', 'Hla', 'Gqi'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nso.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nso.php new file mode 100644 index 0000000000..2a6cabbf16 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nso.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nso_ZA.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nso_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nso_ZA.php new file mode 100644 index 0000000000..b08fe6dcd8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nso_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Janaware', 'Febereware', 'Matลกhe', 'Aprele', 'Mei', 'June', 'Julae', 'Agostose', 'Setemere', 'Oktobere', 'Nofemere', 'Disemere'], + 'months_short' => ['Jan', 'Feb', 'Mat', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Set', 'Okt', 'Nof', 'Dis'], + 'weekdays' => ['LaMorena', 'Moลกupologo', 'Labobedi', 'Laboraro', 'Labone', 'Labohlano', 'Mokibelo'], + 'weekdays_short' => ['Son', 'Moลก', 'Bed', 'Rar', 'Ne', 'Hla', 'Mok'], + 'weekdays_min' => ['Son', 'Moลก', 'Bed', 'Rar', 'Ne', 'Hla', 'Mok'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ngwaga', + 'y' => ':count ngwaga', + 'a_year' => ':count ngwaga', + + 'month' => ':count Kgwedi', + 'm' => ':count Kgwedi', + 'a_month' => ':count Kgwedi', + + 'week' => ':count Beke', + 'w' => ':count Beke', + 'a_week' => ':count Beke', + + 'day' => ':count Letลกatลกi', + 'd' => ':count Letลกatลกi', + 'a_day' => ':count Letลกatลกi', + + 'hour' => ':count Iri', + 'h' => ':count Iri', + 'a_hour' => ':count Iri', + + 'minute' => ':count Motsotso', + 'min' => ':count Motsotso', + 'a_minute' => ':count Motsotso', + + 'second' => ':count motsotswana', + 's' => ':count motsotswana', + 'a_second' => ':count motsotswana', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nus.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nus.php new file mode 100644 index 0000000000..789bc391fe --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nus.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['RW', 'TลŠ'], + 'weekdays' => ['Cรคล‹ kuษ”th', 'Jiec laฬฑt', 'Rษ›w lรคtni', 'Diษ”ฬฑk lรคtni', 'ลŠuaan lรคtni', 'Dhieec lรคtni', 'Bรคkษ›l lรคtni'], + 'weekdays_short' => ['Cรคล‹', 'Jiec', 'Rษ›w', 'Diษ”ฬฑk', 'ลŠuaan', 'Dhieec', 'Bรคkษ›l'], + 'weekdays_min' => ['Cรคล‹', 'Jiec', 'Rษ›w', 'Diษ”ฬฑk', 'ลŠuaan', 'Dhieec', 'Bรคkษ›l'], + 'months' => ['Tiop thar pษ›t', 'Pษ›t', 'Duษ”ฬฑษ”ฬฑล‹', 'Guak', 'Duรคt', 'Kornyoot', 'Pay yieฬฑtni', 'Thoฬฑoฬฑr', 'Tษ›ษ›r', 'Laath', 'Kur', 'Tioฬฑp in diฬฑiฬฑt'], + 'months_short' => ['Tiop', 'Pษ›t', 'Duษ”ฬฑษ”ฬฑ', 'Guak', 'Duรค', 'Kor', 'Pay', 'Thoo', 'Tษ›ษ›', 'Laa', 'Kur', 'Tid'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], + + 'year' => ':count jiรถk', // less reliable + 'y' => ':count jiรถk', // less reliable + 'a_year' => ':count jiรถk', // less reliable + + 'month' => ':count pay', // less reliable + 'm' => ':count pay', // less reliable + 'a_month' => ':count pay', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nyn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nyn.php new file mode 100644 index 0000000000..8660ea426c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nyn.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Sande', 'Orwokubanza', 'Orwakabiri', 'Orwakashatu', 'Orwakana', 'Orwakataano', 'Orwamukaaga'], + 'weekdays_short' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'weekdays_min' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'months' => ['Okwokubanza', 'Okwakabiri', 'Okwakashatu', 'Okwakana', 'Okwakataana', 'Okwamukaaga', 'Okwamushanju', 'Okwamunaana', 'Okwamwenda', 'Okwaikumi', 'Okwaikumi na kumwe', 'Okwaikumi na ibiri'], + 'months_short' => ['KBZ', 'KBR', 'KST', 'KKN', 'KTN', 'KMK', 'KMS', 'KMN', 'KMW', 'KKM', 'KNK', 'KNB'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/oc.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/oc.php new file mode 100644 index 0000000000..c9411d69de --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/oc.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Quentรญ + */ +// @codeCoverageIgnoreStart +use Symfony\Component\Translation\PluralizationRules; + +if (class_exists('Symfony\\Component\\Translation\\PluralizationRules')) { + PluralizationRules::set(static function ($number) { + return $number == 1 ? 0 : 1; + }, 'oc'); +} +// @codeCoverageIgnoreEnd + +return [ + 'year' => ':count an|:count ans', + 'a_year' => 'un an|:count ans', + 'y' => ':count an|:count ans', + 'month' => ':count mes|:count meses', + 'a_month' => 'un mes|:count meses', + 'm' => ':count mes|:count meses', + 'week' => ':count setmana|:count setmanas', + 'a_week' => 'una setmana|:count setmanas', + 'w' => ':count setmana|:count setmanas', + 'day' => ':count jorn|:count jorns', + 'a_day' => 'un jorn|:count jorns', + 'd' => ':count jorn|:count jorns', + 'hour' => ':count ora|:count oras', + 'a_hour' => 'una ora|:count oras', + 'h' => ':count ora|:count oras', + 'minute' => ':count minuta|:count minutas', + 'a_minute' => 'una minuta|:count minutas', + 'min' => ':count minuta|:count minutas', + 'second' => ':count segonda|:count segondas', + 'a_second' => 'una segonda|:count segondas', + 's' => ':count segonda|:count segondas', + 'ago' => 'fa :time', + 'from_now' => 'd\'aquรญ :time', + 'after' => ':time aprรจp', + 'before' => ':time abans', + 'diff_now' => 'ara meteis', + 'diff_today' => 'Uรจi', + 'diff_today_regexp' => 'Uรจi(?:\\s+a)?', + 'diff_yesterday' => 'iรจr', + 'diff_yesterday_regexp' => 'Iรจr(?:\\s+a)?', + 'diff_tomorrow' => 'deman', + 'diff_tomorrow_regexp' => 'Deman(?:\\s+a)?', + 'diff_before_yesterday' => 'iรจr delร ', + 'diff_after_tomorrow' => 'deman passat', + 'period_recurrences' => ':count cรฒp|:count cรฒps', + 'period_interval' => 'cada :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'fins a :date', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM [de] YYYY', + 'LLL' => 'D MMMM [de] YYYY [a] H:mm', + 'LLLL' => 'dddd D MMMM [de] YYYY [a] H:mm', + ], + 'calendar' => [ + 'sameDay' => '[Uรจi a] LT', + 'nextDay' => '[Deman a] LT', + 'nextWeek' => 'dddd [a] LT', + 'lastDay' => '[Iรจr a] LT', + 'lastWeek' => 'dddd [passat a] LT', + 'sameElse' => 'L', + ], + 'months' => ['de geniรจr', 'de febriรจr', 'de marรง', 'd\'abrial', 'de mai', 'de junh', 'de julhet', 'd\'agost', 'de setembre', 'dโ€™octรฒbre', 'de novembre', 'de decembre'], + 'months_standalone' => ['geniรจr', 'febriรจr', 'marรง', 'abrial', 'mai', 'junh', 'julh', 'agost', 'setembre', 'octรฒbre', 'novembre', 'decembre'], + 'months_short' => ['gen.', 'feb.', 'marรง', 'abr.', 'mai', 'junh', 'julh', 'ago.', 'sep.', 'oct.', 'nov.', 'dec.'], + 'weekdays' => ['dimenge', 'diluns', 'dimars', 'dimรจcres', 'dijรฒus', 'divendres', 'dissabte'], + 'weekdays_short' => ['dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds'], + 'weekdays_min' => ['dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds'], + 'ordinal' => function ($number, string $period = '') { + $ordinal = [1 => 'รจr', 2 => 'nd'][(int) $number] ?? 'en'; + + // feminine for year, week, hour, minute, second + if (preg_match('/^[yYwWhHgGis]$/', $period)) { + $ordinal .= 'a'; + } + + return $number.$ordinal; + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/oc_FR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/oc_FR.php new file mode 100644 index 0000000000..01eb5c1415 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/oc_FR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/oc.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/om.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/om.php new file mode 100644 index 0000000000..b8d5a0b01c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/om.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation & Sagalee Oromoo Publishing Co. Inc. locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'dd-MMM-YYYY', + 'LLL' => 'dd MMMM YYYY HH:mm', + 'LLLL' => 'dddd, MMMM D, YYYY HH:mm', + ], + 'months' => ['Amajjii', 'Guraandhala', 'Bitooteessa', 'Elba', 'Caamsa', 'Waxabajjii', 'Adooleessa', 'Hagayya', 'Fuulbana', 'Onkololeessa', 'Sadaasa', 'Muddee'], + 'months_short' => ['Ama', 'Gur', 'Bit', 'Elb', 'Cam', 'Wax', 'Ado', 'Hag', 'Ful', 'Onk', 'Sad', 'Mud'], + 'weekdays' => ['Dilbata', 'Wiixata', 'Qibxata', 'Roobii', 'Kamiisa', 'Jimaata', 'Sanbata'], + 'weekdays_short' => ['Dil', 'Wix', 'Qib', 'Rob', 'Kam', 'Jim', 'San'], + 'weekdays_min' => ['Dil', 'Wix', 'Qib', 'Rob', 'Kam', 'Jim', 'San'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['WD', 'WB'], + + 'year' => 'wggoota :count', + 'y' => 'wggoota :count', + 'a_year' => 'wggoota :count', + + 'month' => 'jiโ€™a :count', + 'm' => 'jiโ€™a :count', + 'a_month' => 'jiโ€™a :count', + + 'week' => 'torban :count', + 'w' => 'torban :count', + 'a_week' => 'torban :count', + + 'day' => 'guyyaa :count', + 'd' => 'guyyaa :count', + 'a_day' => 'guyyaa :count', + + 'hour' => 'saสผaatii :count', + 'h' => 'saสผaatii :count', + 'a_hour' => 'saสผaatii :count', + + 'minute' => 'daqiiqaa :count', + 'min' => 'daqiiqaa :count', + 'a_minute' => 'daqiiqaa :count', + + 'second' => 'sekoondii :count', + 's' => 'sekoondii :count', + 'a_second' => 'sekoondii :count', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/om_ET.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/om_ET.php new file mode 100644 index 0000000000..044760e359 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/om_ET.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/om.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/om_KE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/om_KE.php new file mode 100644 index 0000000000..f5a4d1c93d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/om_KE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/om.php', [ + 'day_of_first_week_of_year' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/or.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/or.php new file mode 100644 index 0000000000..3aa7173266 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/or.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/or_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/or_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/or_IN.php new file mode 100644 index 0000000000..57a89f5dfb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/or_IN.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM AP Linux Technology Center, Yamato Software Laboratory bug-glibc@gnu.org + */ +return [ + 'diff_now' => 'เฌฌเฌฐเญเฌคเญเฌคเฌฎเฌพเฌจ', + 'diff_yesterday' => 'เฌ—เฌคเฌ•เฌพเฌฒเฌฟ', + 'diff_tomorrow' => 'เฌ†เฌธเฌจเญเฌคเฌพเฌ•เฌพเฌฒเฌฟ', + 'formats' => [ + 'LT' => 'Oh:Om A', + 'LTS' => 'Oh:Om:Os A', + 'L' => 'OD-OM-OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY Oh:Om A', + 'LLLL' => 'dddd OD MMMM OY Oh:Om A', + ], + 'months' => ['เฌœเฌพเฌจเญเฌ†เฌฐเญ€', 'เฌซเญ‡เฌฌเญƒเฌ†เฌฐเญ€', 'เฌฎเฌพเฌฐเญเฌšเญเฌš', 'เฌ…เฌชเญเฌฐเญ‡เฌฒ', 'เฌฎเฌ‡', 'เฌœเญเฌจ', 'เฌœเญเฌฒเฌพเฌ‡', 'เฌ…เฌ—เฌทเญเฌŸ', 'เฌธเญ‡เฌชเญเฌŸเญ‡เฌฎเญเฌฌเฌฐ', 'เฌ…เฌ•เญเฌŸเญ‹เฌฌเฌฐ', 'เฌจเฌญเญ‡เฌฎเญเฌฌเฌฐ', 'เฌกเฌฟเฌธเญ‡เฌฎเญเฌฌเฌฐ'], + 'months_short' => ['เฌœเฌพเฌจเญเฌ†เฌฐเญ€', 'เฌซเญ‡เฌฌเญƒเฌ†เฌฐเญ€', 'เฌฎเฌพเฌฐเญเฌšเญเฌš', 'เฌ…เฌชเญเฌฐเญ‡เฌฒ', 'เฌฎเฌ‡', 'เฌœเญเฌจ', 'เฌœเญเฌฒเฌพเฌ‡', 'เฌ…เฌ—เฌทเญเฌŸ', 'เฌธเญ‡เฌชเญเฌŸเญ‡เฌฎเญเฌฌเฌฐ', 'เฌ…เฌ•เญเฌŸเญ‹เฌฌเฌฐ', 'เฌจเฌญเญ‡เฌฎเญเฌฌเฌฐ', 'เฌกเฌฟเฌธเญ‡เฌฎเญเฌฌเฌฐ'], + 'weekdays' => ['เฌฐเฌฌเฌฟเฌฌเฌพเฌฐ', 'เฌธเญ‹เฌฎเฌฌเฌพเฌฐ', 'เฌฎเฌ™เญเฌ—เฌณเฌฌเฌพเฌฐ', 'เฌฌเญเฌงเฌฌเฌพเฌฐ', 'เฌ—เญเฌฐเญเฌฌเฌพเฌฐ', 'เฌถเญเฌ•เญเฌฐเฌฌเฌพเฌฐ', 'เฌถเฌจเฌฟเฌฌเฌพเฌฐ'], + 'weekdays_short' => ['เฌฐเฌฌเฌฟ', 'เฌธเญ‹เฌฎ', 'เฌฎเฌ™เญเฌ—เฌณ', 'เฌฌเญเฌง', 'เฌ—เญเฌฐเญ', 'เฌถเญเฌ•เญเฌฐ', 'เฌถเฌจเฌฟ'], + 'weekdays_min' => ['เฌฐเฌฌเฌฟ', 'เฌธเญ‹เฌฎ', 'เฌฎเฌ™เญเฌ—เฌณ', 'เฌฌเญเฌง', 'เฌ—เญเฌฐเญ', 'เฌถเญเฌ•เญเฌฐ', 'เฌถเฌจเฌฟ'], + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['เญฆ', 'เญง', 'เญจ', 'เญฉ', 'เญช', 'เญซ', 'เญฌ', 'เญญ', 'เญฎ', 'เญฏ', 'เญงเญฆ', 'เญงเญง', 'เญงเญจ', 'เญงเญฉ', 'เญงเญช', 'เญงเญซ', 'เญงเญฌ', 'เญงเญญ', 'เญงเญฎ', 'เญงเญฏ', 'เญจเญฆ', 'เญจเญง', 'เญจเญจ', 'เญจเญฉ', 'เญจเญช', 'เญจเญซ', 'เญจเญฌ', 'เญจเญญ', 'เญจเญฎ', 'เญจเญฏ', 'เญฉเญฆ', 'เญฉเญง', 'เญฉเญจ', 'เญฉเญฉ', 'เญฉเญช', 'เญฉเญซ', 'เญฉเญฌ', 'เญฉเญญ', 'เญฉเญฎ', 'เญฉเญฏ', 'เญชเญฆ', 'เญชเญง', 'เญชเญจ', 'เญชเญฉ', 'เญชเญช', 'เญชเญซ', 'เญชเญฌ', 'เญชเญญ', 'เญชเญฎ', 'เญชเญฏ', 'เญซเญฆ', 'เญซเญง', 'เญซเญจ', 'เญซเญฉ', 'เญซเญช', 'เญซเญซ', 'เญซเญฌ', 'เญซเญญ', 'เญซเญฎ', 'เญซเญฏ', 'เญฌเญฆ', 'เญฌเญง', 'เญฌเญจ', 'เญฌเญฉ', 'เญฌเญช', 'เญฌเญซ', 'เญฌเญฌ', 'เญฌเญญ', 'เญฌเญฎ', 'เญฌเญฏ', 'เญญเญฆ', 'เญญเญง', 'เญญเญจ', 'เญญเญฉ', 'เญญเญช', 'เญญเญซ', 'เญญเญฌ', 'เญญเญญ', 'เญญเญฎ', 'เญญเญฏ', 'เญฎเญฆ', 'เญฎเญง', 'เญฎเญจ', 'เญฎเญฉ', 'เญฎเญช', 'เญฎเญซ', 'เญฎเญฌ', 'เญฎเญญ', 'เญฎเญฎ', 'เญฎเญฏ', 'เญฏเญฆ', 'เญฏเญง', 'เญฏเญจ', 'เญฏเญฉ', 'เญฏเญช', 'เญฏเญซ', 'เญฏเญฌ', 'เญฏเญญ', 'เญฏเญฎ', 'เญฏเญฏ'], + 'year' => ':count เฌฌเฌฐเญเฌท', + 'y' => ':count เฌฌ.', + 'month' => ':count เฌฎเฌพเฌธ', + 'm' => ':count เฌฎเฌพ.', + 'week' => ':count เฌธเฌชเญเฌคเฌพเฌน', + 'w' => ':count เฌธเฌชเญเฌคเฌพ.', + 'day' => ':count เฌฆเฌฟเฌจ', + 'd' => ':count เฌฆเฌฟเฌจ', + 'hour' => ':count เฌ˜เฌฃเญเฌค', + 'h' => ':count เฌ˜.', + 'minute' => ':count เฌฎเฌฟเฌจเฌŸ', + 'min' => ':count เฌฎเฌฟ.', + 'second' => ':count เฌธเญ‡เฌ•เฌฃเญเฌข', + 's' => ':count เฌธเญ‡.', + 'ago' => ':time เฌชเญ‚เฌฐเญเฌฌเญ‡', + 'from_now' => ':timeเฌฐเญ‡', +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/os.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/os.php new file mode 100644 index 0000000000..5f55e8a2d0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/os.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/os_RU.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/os_RU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/os_RU.php new file mode 100644 index 0000000000..9592d15d56 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/os_RU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['ัะฝะฒะฐั€ั‹', 'ั„ะตะฒั€ะฐะปั‹', 'ะผะฐั€ั‚ัŠะธะนั‹', 'ะฐะฟั€ะตะปั‹', 'ะผะฐะนั‹', 'ะธัŽะฝั‹', 'ะธัŽะปั‹', 'ะฐะฒะณัƒัั‚ั‹', 'ัะตะฝั‚ัะฑั€ั‹', 'ะพะบั‚ัะฑั€ั‹', 'ะฝะพัะฑั€ั‹', 'ะดะตะบะฐะฑั€ั‹'], + 'months_short' => ['ะฏะฝะฒ', 'ะคะตะฒ', 'ะœะฐั€', 'ะะฟั€', 'ะœะฐะน', 'ะ˜ัŽะฝ', 'ะ˜ัŽะป', 'ะะฒะณ', 'ะกะตะฝ', 'ะžะบั‚', 'ะะพั', 'ะ”ะตะบ'], + 'weekdays' => ['ะฅัƒั‹ั†ะฐัƒะฑะพะฝ', 'ะšัŠัƒั‹ั€ะธัรฆั€', 'ะ”ั‹ั†ั†รฆะณ', 'ร†ั€ั‚ั‹ั†ั†รฆะณ', 'ะฆั‹ะฟะฟรฆั€รฆะผ', 'ะœะฐะนั€รฆะผะฑะพะฝ', 'ะกะฐะฑะฐั‚'], + 'weekdays_short' => ['ะฅั†ะฑ', 'ะšั€ั', 'ะ”ั†ะณ', 'ร†ั€ั‚', 'ะฆะฟั€', 'ะœั€ะฑ', 'ะกะฑั‚'], + 'weekdays_min' => ['ะฅั†ะฑ', 'ะšั€ั', 'ะ”ั†ะณ', 'ร†ั€ั‚', 'ะฆะฟั€', 'ะœั€ะฑ', 'ะกะฑั‚'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'minute' => ':count ะณั‹ั†ั†ั‹ะป', // less reliable + 'min' => ':count ะณั‹ั†ั†ั‹ะป', // less reliable + 'a_minute' => ':count ะณั‹ั†ั†ั‹ะป', // less reliable + + 'second' => ':count รฆะฝะดรฆั€', // less reliable + 's' => ':count รฆะฝะดรฆั€', // less reliable + 'a_second' => ':count รฆะฝะดรฆั€', // less reliable + + 'year' => ':count ะฐะท', + 'y' => ':count ะฐะท', + 'a_year' => ':count ะฐะท', + + 'month' => ':count ะผำ•ะน', + 'm' => ':count ะผำ•ะน', + 'a_month' => ':count ะผำ•ะน', + + 'week' => ':count ะบัŠัƒั‹ั€ะธ', + 'w' => ':count ะบัŠัƒั‹ั€ะธ', + 'a_week' => ':count ะบัŠัƒั‹ั€ะธ', + + 'day' => ':count ะฑะพะฝ', + 'd' => ':count ะฑะพะฝ', + 'a_day' => ':count ะฑะพะฝ', + + 'hour' => ':count ัะฐั…ะฐั‚', + 'h' => ':count ัะฐั…ะฐั‚', + 'a_hour' => ':count ัะฐั…ะฐั‚', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa.php new file mode 100644 index 0000000000..48b203316b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - Punjab + */ +return [ + 'year' => 'เจ‡เฉฑเจ• เจธเจพเจฒ|:count เจธเจพเจฒ', + 'month' => 'เจ‡เฉฑเจ• เจฎเจนเฉ€เจจเจพ|:count เจฎเจนเฉ€เจจเฉ‡', + 'week' => 'เจนเจซเจคเจพ|:count เจนเจซเจผเจคเฉ‡', + 'day' => 'เจ‡เฉฑเจ• เจฆเจฟเจจ|:count เจฆเจฟเจจ', + 'hour' => 'เจ‡เฉฑเจ• เจ˜เฉฐเจŸเจพ|:count เจ˜เฉฐเจŸเฉ‡', + 'minute' => 'เจ‡เจ• เจฎเจฟเฉฐเจŸ|:count เจฎเจฟเฉฐเจŸ', + 'second' => 'เจ•เฉเจ เจธเจ•เจฟเฉฐเจŸ|:count เจธเจ•เจฟเฉฐเจŸ', + 'ago' => ':time เจชเจนเจฟเจฒเจพเจ‚', + 'from_now' => ':time เจตเจฟเฉฑเจš', + 'before' => ':time เจคเฉ‹เจ‚ เจชเจนเจฟเจฒเจพเจ‚', + 'after' => ':time เจคเฉ‹เจ‚ เจฌเจพเจ…เจฆ', + 'diff_now' => 'เจนเฉเจฃ', + 'diff_today' => 'เจ…เจœ', + 'diff_yesterday' => 'เจ•เจฒ', + 'diff_tomorrow' => 'เจ•เจฒ', + 'formats' => [ + 'LT' => 'A h:mm เจตเจœเฉ‡', + 'LTS' => 'A h:mm:ss เจตเจœเฉ‡', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm เจตเจœเฉ‡', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm เจตเจœเฉ‡', + ], + 'calendar' => [ + 'sameDay' => '[เจ…เจœ] LT', + 'nextDay' => '[เจ•เจฒ] LT', + 'nextWeek' => '[เจ…เจ—เจฒเจพ] dddd, LT', + 'lastDay' => '[เจ•เจฒ] LT', + 'lastWeek' => '[เจชเจฟเจ›เจฒเฉ‡] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'เจฐเจพเจค'; + } + if ($hour < 10) { + return 'เจธเจตเฉ‡เจฐ'; + } + if ($hour < 17) { + return 'เจฆเฉเจชเจนเจฟเจฐ'; + } + if ($hour < 20) { + return 'เจธเจผเจพเจฎ'; + } + + return 'เจฐเจพเจค'; + }, + 'months' => ['เจœเจจเจตเจฐเฉ€', 'เจซเจผเจฐเจตเจฐเฉ€', 'เจฎเจพเจฐเจš', 'เจ…เจชเฉเจฐเฉˆเจฒ', 'เจฎเจˆ', 'เจœเฉ‚เจจ', 'เจœเฉเจฒเจพเจˆ', 'เจ…เจ—เจธเจค', 'เจธเจคเฉฐเจฌเจฐ', 'เจ…เจ•เจคเฉ‚เจฌเจฐ', 'เจจเจตเฉฐเจฌเจฐ', 'เจฆเจธเฉฐเจฌเจฐ'], + 'months_short' => ['เจœเจจเจตเจฐเฉ€', 'เจซเจผเจฐเจตเจฐเฉ€', 'เจฎเจพเจฐเจš', 'เจ…เจชเฉเจฐเฉˆเจฒ', 'เจฎเจˆ', 'เจœเฉ‚เจจ', 'เจœเฉเจฒเจพเจˆ', 'เจ…เจ—เจธเจค', 'เจธเจคเฉฐเจฌเจฐ', 'เจ…เจ•เจคเฉ‚เจฌเจฐ', 'เจจเจตเฉฐเจฌเจฐ', 'เจฆเจธเฉฐเจฌเจฐ'], + 'weekdays' => ['เจเจคเจตเจพเจฐ', 'เจธเฉ‹เจฎเจตเจพเจฐ', 'เจฎเฉฐเจ—เจฒเจตเจพเจฐ', 'เจฌเฉเจงเจตเจพเจฐ', 'เจตเฉ€เจฐเจตเจพเจฐ', 'เจธเจผเฉเฉฑเจ•เจฐเจตเจพเจฐ', 'เจธเจผเจจเฉ€เจšเจฐเจตเจพเจฐ'], + 'weekdays_short' => ['เจเจค', 'เจธเฉ‹เจฎ', 'เจฎเฉฐเจ—เจฒ', 'เจฌเฉเจง', 'เจตเฉ€เจฐ', 'เจธเจผเฉเจ•เจฐ', 'เจธเจผเจจเฉ€'], + 'weekdays_min' => ['เจเจค', 'เจธเฉ‹เจฎ', 'เจฎเฉฐเจ—เจฒ', 'เจฌเฉเจง', 'เจตเฉ€เจฐ', 'เจธเจผเฉเจ•เจฐ', 'เจธเจผเจจเฉ€'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' เจ…เจคเฉ‡ '], + 'weekend' => [0, 0], + 'alt_numbers' => ['เฉฆ', 'เฉง', 'เฉจ', 'เฉฉ', 'เฉช', 'เฉซ', 'เฉฌ', 'เฉญ', 'เฉฎ', 'เฉฏ'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_Arab.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_Arab.php new file mode 100644 index 0000000000..39b06532df --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_Arab.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ur.php', [ + 'weekdays' => ['ุงุชูˆุงุฑ', 'ูพŒุฑ', 'ู…ู†ฺฏู„', 'ุจูุฏฺพ', 'ุฌู…ุนุฑุงุช', 'ุฌู…ุน', 'ูุช'], + 'weekdays_short' => ['ุงุชูˆุงุฑ', 'ูพŒุฑ', 'ู…ู†ฺฏู„', 'ุจูุฏฺพ', 'ุฌู…ุนุฑุงุช', 'ุฌู…ุน', 'ูุช'], + 'weekdays_min' => ['ุงุชูˆุงุฑ', 'ูพŒุฑ', 'ู…ู†ฺฏู„', 'ุจูุฏฺพ', 'ุฌู…ุนุฑุงุช', 'ุฌู…ุน', 'ูุช'], + 'months' => ['ุฌู†ูˆุฑŒ', 'ูุฑูˆุฑŒ', 'ู…ุงุฑฺ†', 'ุงูพุฑŒู„', 'ู…ุฆ', 'ุฌูˆู†', 'ุฌูˆู„ุงุฆŒ', 'ุงฺฏุณุช', 'ุณุชู…ุจุฑ', 'ุงฺฉุชูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'months_short' => ['ุฌู†ูˆุฑŒ', 'ูุฑูˆุฑŒ', 'ู…ุงุฑฺ†', 'ุงูพุฑŒู„', 'ู…ุฆ', 'ุฌูˆู†', 'ุฌูˆู„ุงุฆŒ', 'ุงฺฏุณุช', 'ุณุชู…ุจุฑ', 'ุงฺฉุชูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, DD MMMM YYYY h:mm a', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_Guru.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_Guru.php new file mode 100644 index 0000000000..7adff5c32e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_Guru.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/pa.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY, h:mm a', + ], + 'months' => ['เจœเจจเจตเจฐเฉ€', 'เจซเจผเจฐเจตเจฐเฉ€', 'เจฎเจพเจฐเจš', 'เจ…เจชเฉเจฐเฉˆเจฒ', 'เจฎเจˆ', 'เจœเฉ‚เจจ', 'เจœเฉเจฒเจพเจˆ', 'เจ…เจ—เจธเจค', 'เจธเจคเฉฐเจฌเจฐ', 'เจ…เจ•เจคเฉ‚เจฌเจฐ', 'เจจเจตเฉฐเจฌเจฐ', 'เจฆเจธเฉฐเจฌเจฐ'], + 'months_short' => ['เจœเจจ', 'เจซเจผเจฐ', 'เจฎเจพเจฐเจš', 'เจ…เจชเฉเจฐเฉˆ', 'เจฎเจˆ', 'เจœเฉ‚เจจ', 'เจœเฉเจฒเจพ', 'เจ…เจ—', 'เจธเจคเฉฐ', 'เจ…เจ•เจคเฉ‚', 'เจจเจตเฉฐ', 'เจฆเจธเฉฐ'], + 'weekdays' => ['เจเจคเจตเจพเจฐ', 'เจธเฉ‹เจฎเจตเจพเจฐ', 'เจฎเฉฐเจ—เจฒเจตเจพเจฐ', 'เจฌเฉเฉฑเจงเจตเจพเจฐ', 'เจตเฉ€เจฐเจตเจพเจฐ', 'เจธเจผเฉเฉฑเจ•เจฐเจตเจพเจฐ', 'เจธเจผเจจเจฟเฉฑเจšเจฐเจตเจพเจฐ'], + 'weekdays_short' => ['เจเจค', 'เจธเฉ‹เจฎ', 'เจฎเฉฐเจ—เจฒ', 'เจฌเฉเฉฑเจง', 'เจตเฉ€เจฐ', 'เจธเจผเฉเฉฑเจ•เจฐ', 'เจธเจผเจจเจฟเฉฑเจšเจฐ'], + 'weekdays_min' => ['เจเจค', 'เจธเฉ‹เจฎ', 'เจฎเฉฐเจ—', 'เจฌเฉเฉฑเจง', 'เจตเฉ€เจฐ', 'เจธเจผเฉเฉฑเจ•', 'เจธเจผเจจเจฟเฉฑ'], + 'weekend' => [0, 0], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_IN.php new file mode 100644 index 0000000000..ca67642a55 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_IN.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Guo Xiang Tan + * - Josh Soref + * - Ash + * - harpreetkhalsagtbit + */ +return require __DIR__.'/pa.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_PK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_PK.php new file mode 100644 index 0000000000..f9af11c60c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_PK.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ุฌู†ูˆุฑูŠ', 'ูุฑูˆุฑูŠ', 'ู…ุงุฑฺ†', 'ุงูพุฑูŠู„', 'ู…ู“Œ', 'ุฌูˆู†', 'ุฌูˆู„ุงูŠ', 'ุงฺฏุณุช', 'ุณุชู…ุจุฑ', 'ุงูƒุชูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'months_short' => ['ุฌู†ูˆุฑูŠ', 'ูุฑูˆุฑูŠ', 'ู…ุงุฑฺ†', 'ุงูพุฑูŠู„', 'ู…ู“Œ', 'ุฌูˆู†', 'ุฌูˆู„ุงูŠ', 'ุงฺฏุณุช', 'ุณุชู…ุจุฑ', 'ุงูƒุชูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'weekdays' => ['ุงุชูˆุงุฑ', 'ูพูŠุฑ', 'ู…ู†ฺฏู„', 'ุจุฏฺพ', 'ุฌู…ุนุฑุงุช', 'ุฌู…ุนู‡', 'ู‡ูุชู‡'], + 'weekdays_short' => ['ุงุชูˆุงุฑ', 'ูพูŠุฑ', 'ู…ู†ฺฏู„', 'ุจุฏฺพ', 'ุฌู…ุนุฑุงุช', 'ุฌู…ุนู‡', 'ู‡ูุชู‡'], + 'weekdays_min' => ['ุงุชูˆุงุฑ', 'ูพูŠุฑ', 'ู…ู†ฺฏู„', 'ุจุฏฺพ', 'ุฌู…ุนุฑุงุช', 'ุฌู…ุนู‡', 'ู‡ูุชู‡'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ุต', 'ุด'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pap.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pap.php new file mode 100644 index 0000000000..b4c1706f91 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pap.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return [ + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm:ss', + 'L' => 'DD-MM-YY', + 'LL' => 'MMMM [di] DD, YYYY', + 'LLL' => 'DD MMM HH.mm', + 'LLLL' => 'MMMM DD, YYYY HH.mm', + ], + 'months' => ['yanรผari', 'febrรผari', 'mart', 'aprel', 'mei', 'yรผni', 'yรผli', 'ougรนstรนs', 'sรจptรจmber', 'oktober', 'novรจmber', 'desรจmber'], + 'months_short' => ['yan', 'feb', 'mar', 'apr', 'mei', 'yรผn', 'yรผl', 'oug', 'sรจp', 'okt', 'nov', 'des'], + 'weekdays' => ['djadomingo', 'djaluna', 'djamars', 'djawebs', 'djarason', 'djabierne', 'djasabra'], + 'weekdays_short' => ['do', 'lu', 'ma', 'we', 'ra', 'bi', 'sa'], + 'weekdays_min' => ['do', 'lu', 'ma', 'we', 'ra', 'bi', 'sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'year' => ':count aรฑa', + 'month' => ':count luna', + 'week' => ':count siman', + 'day' => ':count dia', + 'hour' => ':count ora', + 'minute' => ':count minรผt', + 'second' => ':count sekรฒnde', + 'list' => [', ', ' i '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pap_AW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pap_AW.php new file mode 100644 index 0000000000..e9a48ffc36 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pap_AW.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from native speaker Pablo Saratxaga pablo@mandrakesoft.com + */ +return require __DIR__.'/pap.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pap_CW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pap_CW.php new file mode 100644 index 0000000000..e9a48ffc36 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pap_CW.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from native speaker Pablo Saratxaga pablo@mandrakesoft.com + */ +return require __DIR__.'/pap.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pl.php new file mode 100644 index 0000000000..b720535498 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pl.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Wacล‚aw Jacek + * - Franรงois B + * - Tim Fish + * - Serhan Apaydฤฑn + * - Massimiliano Caniparoli + * - JD Isaacks + * - Jakub Szwacz + * - Jan + * - Paul + * - damlys + * - Marek (marast78) + * - Peter (UnrulyNatives) + * - Qrzysio + * - Jan (aso824) + * - diverpl + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count rok|:count lata|:count lat', + 'a_year' => 'rok|:count lata|:count lat', + 'y' => ':count r|:count l|:count l', + 'month' => ':count miesiฤ…c|:count miesiฤ…ce|:count miesiฤ™cy', + 'a_month' => 'miesiฤ…c|:count miesiฤ…ce|:count miesiฤ™cy', + 'm' => ':count mies.', + 'week' => ':count tydzieล„|:count tygodnie|:count tygodni', + 'a_week' => 'tydzieล„|:count tygodnie|:count tygodni', + 'w' => ':count tyg.', + 'day' => ':count dzieล„|:count dni|:count dni', + 'a_day' => 'dzieล„|:count dni|:count dni', + 'd' => ':count d', + 'hour' => ':count godzina|:count godziny|:count godzin', + 'a_hour' => 'godzina|:count godziny|:count godzin', + 'h' => ':count godz.', + 'minute' => ':count minuta|:count minuty|:count minut', + 'a_minute' => 'minuta|:count minuty|:count minut', + 'min' => ':count min', + 'second' => ':count sekunda|:count sekundy|:count sekund', + 'a_second' => '{1}kilka sekund|:count sekunda|:count sekundy|:count sekund', + 's' => ':count sek.', + 'ago' => ':time temu', + 'from_now' => static function ($time) { + return 'za '.strtr($time, [ + 'godzina' => 'godzinฤ™', + 'minuta' => 'minutฤ™', + 'sekunda' => 'sekundฤ™', + ]); + }, + 'after' => ':time po', + 'before' => ':time przed', + 'diff_now' => 'teraz', + 'diff_today' => 'Dziล›', + 'diff_today_regexp' => 'Dziล›(?:\\s+o)?', + 'diff_yesterday' => 'wczoraj', + 'diff_yesterday_regexp' => 'Wczoraj(?:\\s+o)?', + 'diff_tomorrow' => 'jutro', + 'diff_tomorrow_regexp' => 'Jutro(?:\\s+o)?', + 'diff_before_yesterday' => 'przedwczoraj', + 'diff_after_tomorrow' => 'pojutrze', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Dziล› o] LT', + 'nextDay' => '[Jutro o] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[W niedzielฤ™ o] LT'; + case 2: + return '[We wtorek o] LT'; + case 3: + return '[W ล›rodฤ™ o] LT'; + case 6: + return '[W sobotฤ™ o] LT'; + default: + return '[W] dddd [o] LT'; + } + }, + 'lastDay' => '[Wczoraj o] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[W zeszล‚ฤ… niedzielฤ™ o] LT'; + case 3: + return '[W zeszล‚ฤ… ล›rodฤ™ o] LT'; + case 6: + return '[W zeszล‚ฤ… sobotฤ™ o] LT'; + default: + return '[W zeszล‚y] dddd [o] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['stycznia', 'lutego', 'marca', 'kwietnia', 'maja', 'czerwca', 'lipca', 'sierpnia', 'wrzeล›nia', 'paลบdziernika', 'listopada', 'grudnia'], + 'months_standalone' => ['styczeล„', 'luty', 'marzec', 'kwiecieล„', 'maj', 'czerwiec', 'lipiec', 'sierpieล„', 'wrzesieล„', 'paลบdziernik', 'listopad', 'grudzieล„'], + 'months_short' => ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paลบ', 'lis', 'gru'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['niedziela', 'poniedziaล‚ek', 'wtorek', 'ล›roda', 'czwartek', 'piฤ…tek', 'sobota'], + 'weekdays_short' => ['ndz', 'pon', 'wt', 'ล›r', 'czw', 'pt', 'sob'], + 'weekdays_min' => ['Nd', 'Pn', 'Wt', 'ลšr', 'Cz', 'Pt', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' i '], + 'meridiem' => ['przed poล‚udniem', 'po poล‚udniu'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pl_PL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pl_PL.php new file mode 100644 index 0000000000..222bcdb478 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pl_PL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pl.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/prg.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/prg.php new file mode 100644 index 0000000000..6e63f4ad78 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/prg.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'months_short' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => ':count meta', + 'y' => ':count meta', + 'a_year' => ':count meta', + + 'month' => ':count mฤ“niks', // less reliable + 'm' => ':count mฤ“niks', // less reliable + 'a_month' => ':count mฤ“niks', // less reliable + + 'week' => ':count sawaฤซtin', // less reliable + 'w' => ':count sawaฤซtin', // less reliable + 'a_week' => ':count sawaฤซtin', // less reliable + + 'day' => ':count di', + 'd' => ':count di', + 'a_day' => ':count di', + + 'hour' => ':count bruktฤ“t', // less reliable + 'h' => ':count bruktฤ“t', // less reliable + 'a_hour' => ':count bruktฤ“t', // less reliable + + 'minute' => ':count lฤซkuts', // less reliable + 'min' => ':count lฤซkuts', // less reliable + 'a_minute' => ':count lฤซkuts', // less reliable + + 'second' => ':count kitan', // less reliable + 's' => ':count kitan', // less reliable + 'a_second' => ':count kitan', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ps.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ps.php new file mode 100644 index 0000000000..a928b28e37 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ps.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Muhammad Nasir Rahimi + * - Nassim Nasibullah (spinzar) + */ +return [ + 'year' => ':count ฺฉุงู„|:count ฺฉุงู„ู‡', + 'y' => ':countฺฉุงู„|:countฺฉุงู„ู‡', + 'month' => ':count ู…ูŠุงุดุช|:count ู…ูŠุงุดุชูŠ', + 'm' => ':countู…ูŠุงุดุช|:countู…ูŠุงุดุชูŠ', + 'week' => ':count ุงูˆู†|:count ุงูˆู†', + 'w' => ':countุงูˆู†|:countุงูˆู†', + 'day' => ':count ูˆุฑฺ|:count ูˆุฑฺูŠ', + 'd' => ':countูˆุฑฺ|:countูˆุฑฺูŠ', + 'hour' => ':count ุณุงุนุช|:count ุณุงุนุชู‡', + 'h' => ':countุณุงุนุช|:countุณุงุนุชู‡', + 'minute' => ':count ุฏู‚ูŠู‚ู‡|:count ุฏู‚ูŠู‚', + 'min' => ':countุฏู‚ูŠู‚ู‡|:countุฏู‚ูŠู‚', + 'second' => ':count ุซุงู†ูŠู‡|:count ุซุงู†ูА', + 's' => ':countุซุงู†ูŠู‡|:countุซุงู†ูА', + 'ago' => ':time ุฏู…ุฎู‡', + 'from_now' => ':time ู„ู‡ ุงูˆุณ ฺ…ุฎู‡', + 'after' => ':time ูˆุฑูˆุณุชู‡', + 'before' => ':time ุฏู…ุฎู‡', + 'list' => ['ุŒ ', ' ุงูˆ '], + 'meridiem' => ['ุบ.ู….', 'ุบ.ูˆ.'], + 'weekdays' => ['ุงุชูˆุงุฑ', 'ฺซู„', 'ู†ู‡ู‡', 'ุดูˆุฑูˆ', 'ุฒูŠุงุฑุช', 'ุฌู…ุนู‡', 'ุฎุงู„ูŠ'], + 'weekdays_short' => ['ุง', 'ฺซ', 'ู†', 'ุด', 'ุฒ', 'ุฌ', 'ุฎ'], + 'weekdays_min' => ['ุง', 'ฺซ', 'ู†', 'ุด', 'ุฒ', 'ุฌ', 'ุฎ'], + 'months' => ['ุฌู†ูˆุฑูŠ', 'ูุจุฑูˆุฑูŠ', 'ู…ุงุฑฺ†', 'ุงูพุฑŒู„', 'ู…', 'ุฌูˆู†', 'ุฌูˆู„ุงŒ', 'ุงฺฏุณุช', 'ุณูพุชู…ุจุฑ', 'ุงฺฉุชูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'months_short' => ['ุฌู†ูˆุฑูŠ', 'ูุจุฑูˆุฑูŠ', 'ู…ุงุฑฺ†', 'ุงูพุฑŒู„', 'ู…', 'ุฌูˆู†', 'ุฌูˆู„ุงŒ', 'ุงฺฏุณุช', 'ุณูพุชู…ุจุฑ', 'ุงฺฉุชูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'months_standalone' => ['ุฌู†ูˆุฑูŠ', 'ูุจุฑูˆุฑูŠ', 'ู…ุงุฑฺ†', 'ุงูพุฑŒู„', 'ู…', 'ุฌูˆู†', 'ุฌูˆู„ุงŒ', 'ุงฺฏุณุช', 'ุณูพุชู…ุจุฑ', 'ุงฺฉุชูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'months_short_standalone' => ['ุฌู†ูˆุฑูŠ', 'ูุจุฑูˆุฑูŠ', 'ู…ุงุฑฺ†', 'ุงูพุฑŒู„', 'ู…', 'ุฌูˆู†', 'ุฌูˆู„ุงŒ', 'ุงฺฏุณุช', 'ุณูพุชู…ุจุฑ', 'ุงฺฉุชูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'first_day_of_week' => 6, + 'weekend' => [4, 5], + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'YYYY/M/d', + 'LL' => 'YYYY MMM D', + 'LLL' => 'ุฏ YYYY ุฏ MMMM D H:mm', + 'LLLL' => 'dddd ุฏ YYYY ุฏ MMMM D H:mm', + ], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ps_AF.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ps_AF.php new file mode 100644 index 0000000000..6ec51804be --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ps_AF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ps.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt.php new file mode 100644 index 0000000000..bb6359b149 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Cassiano Montanari + * - Matt Pope + * - Franรงois B + * - Prodis + * - JD Isaacks + * - Raphael Amorim + * - Joรฃo Magalhรฃes + * - victortobias + * - Paulo Freitas + * - Sebastian Thierer + * - Claudson Martins (claudsonm) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count ano|:count anos', + 'a_year' => 'um ano|:count anos', + 'y' => ':counta', + 'month' => ':count mรชs|:count meses', + 'a_month' => 'um mรชs|:count meses', + 'm' => ':countm', + 'week' => ':count semana|:count semanas', + 'a_week' => 'uma semana|:count semanas', + 'w' => ':countsem', + 'day' => ':count dia|:count dias', + 'a_day' => 'um dia|:count dias', + 'd' => ':countd', + 'hour' => ':count hora|:count horas', + 'a_hour' => 'uma hora|:count horas', + 'h' => ':counth', + 'minute' => ':count minuto|:count minutos', + 'a_minute' => 'um minuto|:count minutos', + 'min' => ':countmin', + 'second' => ':count segundo|:count segundos', + 'a_second' => 'alguns segundos|:count segundos', + 's' => ':counts', + 'millisecond' => ':count milissegundo|:count milissegundos', + 'a_millisecond' => 'um milissegundo|:count milissegundos', + 'ms' => ':countms', + 'microsecond' => ':count microssegundo|:count microssegundos', + 'a_microsecond' => 'um microssegundo|:count microssegundos', + 'ยตs' => ':countยตs', + 'ago' => 'hรก :time', + 'from_now' => 'em :time', + 'after' => ':time depois', + 'before' => ':time antes', + 'diff_now' => 'agora', + 'diff_today' => 'Hoje', + 'diff_today_regexp' => 'Hoje(?:\\s+ร s)?', + 'diff_yesterday' => 'ontem', + 'diff_yesterday_regexp' => 'Ontem(?:\\s+ร s)?', + 'diff_tomorrow' => 'amanhรฃ', + 'diff_tomorrow_regexp' => 'Amanhรฃ(?:\\s+ร s)?', + 'diff_before_yesterday' => 'anteontem', + 'diff_after_tomorrow' => 'depois de amanhรฃ', + 'period_recurrences' => 'uma vez|:count vezes', + 'period_interval' => 'cada :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'atรฉ :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [de] MMMM [de] YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY HH:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hoje ร s] LT', + 'nextDay' => '[Amanhรฃ ร s] LT', + 'nextWeek' => 'dddd [ร s] LT', + 'lastDay' => '[Ontem ร s] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + case 6: + return '[รšltimo] dddd [ร s] LT'; + default: + return '[รšltima] dddd [ร s] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberยบ', + 'months' => ['janeiro', 'fevereiro', 'marรงo', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'], + 'months_short' => ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'], + 'weekdays' => ['domingo', 'segunda-feira', 'terรงa-feira', 'quarta-feira', 'quinta-feira', 'sexta-feira', 'sรกbado'], + 'weekdays_short' => ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sรกb'], + 'weekdays_min' => ['Do', '2ยช', '3ยช', '4ยช', '5ยช', '6ยช', 'Sรก'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], + 'ordinal_words' => [ + 'of' => 'de', + 'first' => 'primeira', + 'second' => 'segunda', + 'third' => 'terceira', + 'fourth' => 'quarta', + 'fifth' => 'quinta', + 'last' => 'รบltima', + ], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_AO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_AO.php new file mode 100644 index 0000000000..22c01ec59f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_AO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php new file mode 100644 index 0000000000..e917c5cde3 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Cassiano Montanari + * - Eduardo Dalla Vecchia + * - David Rodrigues + * - Matt Pope + * - Franรงois B + * - Prodis + * - Marlon Maxwel + * - JD Isaacks + * - Raphael Amorim + * - Rafael Raupp + * - felipeleite1 + * - swalker + * - Lucas Macedo + * - Paulo Freitas + * - Sebastian Thierer + */ +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'period_recurrences' => 'uma|:count vez', + 'period_interval' => 'toda :interval', + 'formats' => [ + 'LLL' => 'D [de] MMMM [de] YYYY [ร s] HH:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY [ร s] HH:mm', + ], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_CH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_CH.php new file mode 100644 index 0000000000..22c01ec59f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_CH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_CV.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_CV.php new file mode 100644 index 0000000000..22c01ec59f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_CV.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_GQ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_GQ.php new file mode 100644 index 0000000000..22c01ec59f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_GQ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_GW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_GW.php new file mode 100644 index 0000000000..22c01ec59f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_GW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_LU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_LU.php new file mode 100644 index 0000000000..22c01ec59f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_LU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_MO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_MO.php new file mode 100644 index 0000000000..f2b5eab75c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_MO.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'LLL' => 'D [de] MMMM [de] YYYY, h:mm a', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY, h:mm a', + ], + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_MZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_MZ.php new file mode 100644 index 0000000000..fbc0c97b70 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_MZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_PT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_PT.php new file mode 100644 index 0000000000..2a76fc1fc2 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_PT.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['janeiro', 'fevereiro', 'marรงo', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'], + 'months_short' => ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'], + 'weekdays' => ['domingo', 'segunda', 'terรงa', 'quarta', 'quinta', 'sexta', 'sรกbado'], + 'weekdays_short' => ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sรกb'], + 'weekdays_min' => ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sรกb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_ST.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_ST.php new file mode 100644 index 0000000000..22c01ec59f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_ST.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_TL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_TL.php new file mode 100644 index 0000000000..22c01ec59f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt_TL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/qu.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/qu.php new file mode 100644 index 0000000000..65278cd110 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/qu.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es_UY.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM, YYYY HH:mm', + ], + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/qu_BO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/qu_BO.php new file mode 100644 index 0000000000..d5db6bf539 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/qu_BO.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/qu.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/qu_EC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/qu_EC.php new file mode 100644 index 0000000000..d5db6bf539 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/qu_EC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/qu.php', [ + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/quz.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/quz.php new file mode 100644 index 0000000000..1640c02fb2 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/quz.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/quz_PE.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/quz_PE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/quz_PE.php new file mode 100644 index 0000000000..d322918935 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/quz_PE.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sugar Labs // OLPC sugarlabs.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['iniru', 'phiwriru', 'marsu', 'awril', 'mayu', 'huniyu', 'huliyu', 'agustu', 'siptiyimri', 'uktuwri', 'nuwiyimri', 'tisiyimri'], + 'months_short' => ['ini', 'phi', 'mar', 'awr', 'may', 'hun', 'hul', 'agu', 'sip', 'ukt', 'nuw', 'tis'], + 'weekdays' => ['tuminku', 'lunis', 'martis', 'miyirkulis', 'juywis', 'wiyirnis', 'sawatu'], + 'weekdays_short' => ['tum', 'lun', 'mar', 'miy', 'juy', 'wiy', 'saw'], + 'weekdays_min' => ['tum', 'lun', 'mar', 'miy', 'juy', 'wiy', 'saw'], + 'day_of_first_week_of_year' => 1, + + 'minute' => ':count uchuy', // less reliable + 'min' => ':count uchuy', // less reliable + 'a_minute' => ':count uchuy', // less reliable + + 'year' => ':count wata', + 'y' => ':count wata', + 'a_year' => ':count wata', + + 'month' => ':count killa', + 'm' => ':count killa', + 'a_month' => ':count killa', + + 'week' => ':count simana', + 'w' => ':count simana', + 'a_week' => ':count simana', + + 'day' => ':count pสผunchaw', + 'd' => ':count pสผunchaw', + 'a_day' => ':count pสผunchaw', + + 'hour' => ':count ura', + 'h' => ':count ura', + 'a_hour' => ':count ura', + + 'second' => ':count iskay รฑiqin', + 's' => ':count iskay รฑiqin', + 'a_second' => ':count iskay รฑiqin', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/raj.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/raj.php new file mode 100644 index 0000000000..26138c9b28 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/raj.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/raj_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/raj_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/raj_IN.php new file mode 100644 index 0000000000..7b4589cd31 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/raj_IN.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - meghrajsuthar03@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅˆเคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเค‚เคฌเคฐ', 'เค…เค•เฅเคŸเฅ‚เคฌเคฐ', 'เคจเคตเค‚เคฌเคฐ', 'เคฆเคฟเคธเค‚เคฌเคฐ'], + 'months_short' => ['เคœเคจ', 'เคซเคฐ', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅˆ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒ', 'เค…เค—', 'เคธเคฟเคค', 'เค…เค•เฅเคŸเฅ‚', 'เคจเคต', 'เคฆเคฟเคธ'], + 'weekdays' => ['เคฐเคตเคฟเคตเคพเคฐ', 'เคธเฅ‹เคฎเคตเคพเคฐ', 'เคฎเค‚เค—เคฒเฅเคฒเคตเคพเคฐ', 'เคฌเฅเคงเคตเคพเคฐ', 'เคฌเฅƒเคนเคธเฅเคชเคคเคฟเคตเคพเคฐ', 'เคถเฅเค•เฅเคฐเคตเคพเคฐ', 'เคถเคจเคฟเคตเคพเคฐ'], + 'weekdays_short' => ['เคฐเคตเคฟ', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคง', 'เคฌเฅƒเคนเคธเฅเคชเคคเคฟ', 'เคถเฅเค•เฅเคฐ', 'เคถเคจเคฟ'], + 'weekdays_min' => ['เคฐเคตเคฟ', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคง', 'เคฌเฅƒเคนเคธเฅเคชเคคเคฟ', 'เคถเฅเค•เฅเคฐ', 'เคถเคจเคฟ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เคชเฅ‚เคฐเฅเคตเคพเคนเฅเคจ', 'เค…เคชเคฐเคพเคนเฅเคจ'], + + 'year' => ':count เค†เค‚เคนเฅ‚', // less reliable + 'y' => ':count เค†เค‚เคนเฅ‚', // less reliable + 'a_year' => ':count เค†เค‚เคนเฅ‚', // less reliable + + 'month' => ':count เคธเฅ‚เคฐเคœ', // less reliable + 'm' => ':count เคธเฅ‚เคฐเคœ', // less reliable + 'a_month' => ':count เคธเฅ‚เคฐเคœ', // less reliable + + 'week' => ':count เคจเคฟเคตเคพเคœ', // less reliable + 'w' => ':count เคจเคฟเคตเคพเคœ', // less reliable + 'a_week' => ':count เคจเคฟเคตเคพเคœ', // less reliable + + 'day' => ':count เค…เฅ‡เค•', // less reliable + 'd' => ':count เค…เฅ‡เค•', // less reliable + 'a_day' => ':count เค…เฅ‡เค•', // less reliable + + 'hour' => ':count เคฆเฅเคจเคฟเคฏเคพเค‚เคฃ', // less reliable + 'h' => ':count เคฆเฅเคจเคฟเคฏเคพเค‚เคฃ', // less reliable + 'a_hour' => ':count เคฆเฅเคจเคฟเคฏเคพเค‚เคฃ', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rm.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rm.php new file mode 100644 index 0000000000..1843f45630 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rm.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Tsutomu Kuroda + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolรกs Hock Isaza + * - sebastian de castelberg + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'Do MMMM YYYY', + 'LLL' => 'Do MMMM, HH:mm [Uhr]', + 'LLLL' => 'dddd, Do MMMM YYYY, HH:mm [Uhr]', + ], + 'year' => ':count onn|:count onns', + 'month' => ':count mais', + 'week' => ':count emna|:count emnas', + 'day' => ':count di|:count dis', + 'hour' => ':count oura|:count ouras', + 'minute' => ':count minuta|:count minutas', + 'second' => ':count secunda|:count secundas', + 'weekdays' => ['dumengia', 'glindesdi', 'mardi', 'mesemna', 'gievgia', 'venderdi', 'sonda'], + 'weekdays_short' => ['du', 'gli', 'ma', 'me', 'gie', 've', 'so'], + 'weekdays_min' => ['du', 'gli', 'ma', 'me', 'gie', 've', 'so'], + 'months' => ['schaner', 'favrer', 'mars', 'avrigl', 'matg', 'zercladur', 'fanadur', 'avust', 'settember', 'october', 'november', 'december'], + 'months_short' => ['schan', 'favr', 'mars', 'avr', 'matg', 'zercl', 'fan', 'avust', 'sett', 'oct', 'nov', 'dec'], + 'meridiem' => ['avantmezdi', 'suentermezdi'], + 'list' => [', ', ' e '], + 'first_day_of_week' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rn.php new file mode 100644 index 0000000000..8ab958ebe5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rn.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Z.MU.', 'Z.MW.'], + 'weekdays' => ['Ku wโ€™indwi', 'Ku wa mbere', 'Ku wa kabiri', 'Ku wa gatatu', 'Ku wa kane', 'Ku wa gatanu', 'Ku wa gatandatu'], + 'weekdays_short' => ['cu.', 'mbe.', 'kab.', 'gtu.', 'kan.', 'gnu.', 'gnd.'], + 'weekdays_min' => ['cu.', 'mbe.', 'kab.', 'gtu.', 'kan.', 'gnu.', 'gnd.'], + 'months' => ['Nzero', 'Ruhuhuma', 'Ntwarante', 'Ndamukiza', 'Rusama', 'Ruheshi', 'Mukakaro', 'Nyandagaro', 'Nyakanga', 'Gitugutu', 'Munyonyo', 'Kigarama'], + 'months_short' => ['Mut.', 'Gas.', 'Wer.', 'Mat.', 'Gic.', 'Kam.', 'Nya.', 'Kan.', 'Nze.', 'Ukw.', 'Ugu.', 'Uku.'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => 'imyaka :count', + 'y' => 'imyaka :count', + 'a_year' => 'imyaka :count', + + 'month' => 'amezi :count', + 'm' => 'amezi :count', + 'a_month' => 'amezi :count', + + 'week' => 'indwi :count', + 'w' => 'indwi :count', + 'a_week' => 'indwi :count', + + 'day' => 'imisi :count', + 'd' => 'imisi :count', + 'a_day' => 'imisi :count', + + 'hour' => 'amasaha :count', + 'h' => 'amasaha :count', + 'a_hour' => 'amasaha :count', + + 'minute' => 'iminuta :count', + 'min' => 'iminuta :count', + 'a_minute' => 'iminuta :count', + + 'second' => 'inguvu :count', // less reliable + 's' => 'inguvu :count', // less reliable + 'a_second' => 'inguvu :count', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ro.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ro.php new file mode 100644 index 0000000000..868a3279df --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ro.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + * - Cฤƒtฤƒlin Georgescu + * - Valentin IvaลŸcu (oriceon) + */ +return [ + 'year' => ':count an|:count ani|:count ani', + 'a_year' => 'un an|:count ani|:count ani', + 'y' => ':count a.', + 'month' => ':count lunฤƒ|:count luni|:count luni', + 'a_month' => 'o lunฤƒ|:count luni|:count luni', + 'm' => ':count l.', + 'week' => ':count sฤƒptฤƒmรขnฤƒ|:count sฤƒptฤƒmรขni|:count sฤƒptฤƒmรขni', + 'a_week' => 'o sฤƒptฤƒmรขnฤƒ|:count sฤƒptฤƒmรขni|:count sฤƒptฤƒmรขni', + 'w' => ':count sฤƒp.', + 'day' => ':count zi|:count zile|:count zile', + 'a_day' => 'o zi|:count zile|:count zile', + 'd' => ':count z.', + 'hour' => ':count orฤƒ|:count ore|:count ore', + 'a_hour' => 'o orฤƒ|:count ore|:count ore', + 'h' => ':count o.', + 'minute' => ':count minut|:count minute|:count minute', + 'a_minute' => 'un minut|:count minute|:count minute', + 'min' => ':count m.', + 'second' => ':count secundฤƒ|:count secunde|:count secunde', + 'a_second' => 'cรขteva secunde|:count secunde|:count secunde', + 's' => ':count sec.', + 'ago' => ':time รฎn urmฤƒ', + 'from_now' => 'peste :time', + 'after' => 'peste :time', + 'before' => 'acum :time', + 'diff_now' => 'acum', + 'diff_today' => 'azi', + 'diff_today_regexp' => 'azi(?:\\s+la)?', + 'diff_yesterday' => 'ieri', + 'diff_yesterday_regexp' => 'ieri(?:\\s+la)?', + 'diff_tomorrow' => 'mรขine', + 'diff_tomorrow_regexp' => 'mรขine(?:\\s+la)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY H:mm', + 'LLLL' => 'dddd, D MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[azi la] LT', + 'nextDay' => '[mรขine la] LT', + 'nextWeek' => 'dddd [la] LT', + 'lastDay' => '[ieri la] LT', + 'lastWeek' => '[fosta] dddd [la] LT', + 'sameElse' => 'L', + ], + 'months' => ['ianuarie', 'februarie', 'martie', 'aprilie', 'mai', 'iunie', 'iulie', 'august', 'septembrie', 'octombrie', 'noiembrie', 'decembrie'], + 'months_short' => ['ian.', 'feb.', 'mar.', 'apr.', 'mai', 'iun.', 'iul.', 'aug.', 'sept.', 'oct.', 'nov.', 'dec.'], + 'weekdays' => ['duminicฤƒ', 'luni', 'marศ›i', 'miercuri', 'joi', 'vineri', 'sรขmbฤƒtฤƒ'], + 'weekdays_short' => ['dum', 'lun', 'mar', 'mie', 'joi', 'vin', 'sรขm'], + 'weekdays_min' => ['du', 'lu', 'ma', 'mi', 'jo', 'vi', 'sรข'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ศ™i '], + 'meridiem' => ['a.m.', 'p.m.'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ro_MD.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ro_MD.php new file mode 100644 index 0000000000..ad1d2fa819 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ro_MD.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ro.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ro_RO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ro_RO.php new file mode 100644 index 0000000000..102afcde58 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ro_RO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ro.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rof.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rof.php new file mode 100644 index 0000000000..205fc2663e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rof.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['kangโ€™ama', 'kingoto'], + 'weekdays' => ['Ijumapili', 'Ijumatatu', 'Ijumanne', 'Ijumatano', 'Alhamisi', 'Ijumaa', 'Ijumamosi'], + 'weekdays_short' => ['Ijp', 'Ijt', 'Ijn', 'Ijtn', 'Alh', 'Iju', 'Ijm'], + 'weekdays_min' => ['Ijp', 'Ijt', 'Ijn', 'Ijtn', 'Alh', 'Iju', 'Ijm'], + 'months' => ['Mweri wa kwanza', 'Mweri wa kaili', 'Mweri wa katatu', 'Mweri wa kaana', 'Mweri wa tanu', 'Mweri wa sita', 'Mweri wa saba', 'Mweri wa nane', 'Mweri wa tisa', 'Mweri wa ikumi', 'Mweri wa ikumi na moja', 'Mweri wa ikumi na mbili'], + 'months_short' => ['M1', 'M2', 'M3', 'M4', 'M5', 'M6', 'M7', 'M8', 'M9', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru.php new file mode 100644 index 0000000000..673b043b2f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Bari Badamshin + * - Jรธrn ร˜lmheim + * - Franรงois B + * - Tim Fish + * - ะšะพั€ะตะฝะฑะตั€ะณ ะœะฐั€ะบ (imac) + * - Serhan Apaydฤฑn + * - RomeroMsk + * - vsn4ik + * - JD Isaacks + * - Bari Badamshin + * - Jรธrn ร˜lmheim + * - Franรงois B + * - ะšะพั€ะตะฝะฑะตั€ะณ ะœะฐั€ะบ (imac) + * - Serhan Apaydฤฑn + * - RomeroMsk + * - vsn4ik + * - JD Isaacks + * - Fellzo + * - andrey-helldar + * - Pavel Skripkin (psxx) + * - AlexWalkerson + * - Vladislav UnsealedOne + * - dima-bzz + */ + +use Carbon\CarbonInterface; + +$transformDiff = function ($input) { + return strtr($input, [ + 'ะฝะตะดะตะปั' => 'ะฝะตะดะตะปัŽ', + 'ัะตะบัƒะฝะดะฐ' => 'ัะตะบัƒะฝะดัƒ', + 'ะผะธะฝัƒั‚ะฐ' => 'ะผะธะฝัƒั‚ัƒ', + ]); +}; + +return [ + 'year' => ':count ะณะพะด|:count ะณะพะดะฐ|:count ะปะตั‚', + 'y' => ':count ะณ.|:count ะณ.|:count ะป.', + 'a_year' => '{1}ะณะพะด|:count ะณะพะด|:count ะณะพะดะฐ|:count ะปะตั‚', + 'month' => ':count ะผะตััั†|:count ะผะตััั†ะฐ|:count ะผะตััั†ะตะฒ', + 'm' => ':count ะผะตั.', + 'a_month' => '{1}ะผะตััั†|:count ะผะตััั†|:count ะผะตััั†ะฐ|:count ะผะตััั†ะตะฒ', + 'week' => ':count ะฝะตะดะตะปั|:count ะฝะตะดะตะปะธ|:count ะฝะตะดะตะปัŒ', + 'w' => ':count ะฝะตะด.', + 'a_week' => '{1}ะฝะตะดะตะปั|:count ะฝะตะดะตะปัŽ|:count ะฝะตะดะตะปะธ|:count ะฝะตะดะตะปัŒ', + 'day' => ':count ะดะตะฝัŒ|:count ะดะฝั|:count ะดะฝะตะน', + 'd' => ':count ะด.', + 'a_day' => '{1}ะดะตะฝัŒ|:count ะดะตะฝัŒ|:count ะดะฝั|:count ะดะฝะตะน', + 'hour' => ':count ั‡ะฐั|:count ั‡ะฐัะฐ|:count ั‡ะฐัะพะฒ', + 'h' => ':count ั‡.', + 'a_hour' => '{1}ั‡ะฐั|:count ั‡ะฐั|:count ั‡ะฐัะฐ|:count ั‡ะฐัะพะฒ', + 'minute' => ':count ะผะธะฝัƒั‚ะฐ|:count ะผะธะฝัƒั‚ั‹|:count ะผะธะฝัƒั‚', + 'min' => ':count ะผะธะฝ.', + 'a_minute' => '{1}ะผะธะฝัƒั‚ะฐ|:count ะผะธะฝัƒั‚ะฐ|:count ะผะธะฝัƒั‚ั‹|:count ะผะธะฝัƒั‚', + 'second' => ':count ัะตะบัƒะฝะดะฐ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + 's' => ':count ัะตะบ.', + 'a_second' => '{1}ะฝะตัะบะพะปัŒะบะพ ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดั‹|:count ัะตะบัƒะฝะด', + 'ago' => function ($time) use ($transformDiff) { + return $transformDiff($time).' ะฝะฐะทะฐะด'; + }, + 'from_now' => function ($time) use ($transformDiff) { + return 'ั‡ะตั€ะตะท '.$transformDiff($time); + }, + 'after' => function ($time) use ($transformDiff) { + return $transformDiff($time).' ะฟะพัะปะต'; + }, + 'before' => function ($time) use ($transformDiff) { + return $transformDiff($time).' ะดะพ'; + }, + 'diff_now' => 'ั‚ะพะปัŒะบะพ ั‡ั‚ะพ', + 'diff_today' => 'ะกะตะณะพะดะฝั,', + 'diff_today_regexp' => 'ะกะตะณะพะดะฝั,?(?:\\s+ะฒ)?', + 'diff_yesterday' => 'ะฒั‡ะตั€ะฐ', + 'diff_yesterday_regexp' => 'ะ’ั‡ะตั€ะฐ,?(?:\\s+ะฒ)?', + 'diff_tomorrow' => 'ะทะฐะฒั‚ั€ะฐ', + 'diff_tomorrow_regexp' => 'ะ—ะฐะฒั‚ั€ะฐ,?(?:\\s+ะฒ)?', + 'diff_before_yesterday' => 'ะฟะพะทะฐะฒั‡ะตั€ะฐ', + 'diff_after_tomorrow' => 'ะฟะพัะปะตะทะฐะฒั‚ั€ะฐ', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY ะณ.', + 'LLL' => 'D MMMM YYYY ะณ., H:mm', + 'LLLL' => 'dddd, D MMMM YYYY ะณ., H:mm', + ], + 'calendar' => [ + 'sameDay' => '[ะกะตะณะพะดะฝั, ะฒ] LT', + 'nextDay' => '[ะ—ะฐะฒั‚ั€ะฐ, ะฒ] LT', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($current->week !== $other->week) { + switch ($current->dayOfWeek) { + case 0: + return '[ะ’ ัะปะตะดัƒัŽั‰ะตะต] dddd, [ะฒ] LT'; + case 1: + case 2: + case 4: + return '[ะ’ ัะปะตะดัƒัŽั‰ะธะน] dddd, [ะฒ] LT'; + case 3: + case 5: + case 6: + return '[ะ’ ัะปะตะดัƒัŽั‰ัƒัŽ] dddd, [ะฒ] LT'; + } + } + + if ($current->dayOfWeek === 2) { + return '[ะ’ะพ] dddd, [ะฒ] LT'; + } + + return '[ะ’] dddd, [ะฒ] LT'; + }, + 'lastDay' => '[ะ’ั‡ะตั€ะฐ, ะฒ] LT', + 'lastWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($current->week !== $other->week) { + switch ($current->dayOfWeek) { + case 0: + return '[ะ’ ะฟั€ะพัˆะปะพะต] dddd, [ะฒ] LT'; + case 1: + case 2: + case 4: + return '[ะ’ ะฟั€ะพัˆะปั‹ะน] dddd, [ะฒ] LT'; + case 3: + case 5: + case 6: + return '[ะ’ ะฟั€ะพัˆะปัƒัŽ] dddd, [ะฒ] LT'; + } + } + + if ($current->dayOfWeek === 2) { + return '[ะ’ะพ] dddd, [ะฒ] LT'; + } + + return '[ะ’] dddd, [ะฒ] LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'M': + case 'd': + case 'DDD': + return $number.'-ะน'; + case 'D': + return $number.'-ะณะพ'; + case 'w': + case 'W': + return $number.'-ั'; + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ะฝะพั‡ะธ'; + } + if ($hour < 12) { + return 'ัƒั‚ั€ะฐ'; + } + if ($hour < 17) { + return 'ะดะฝั'; + } + + return 'ะฒะตั‡ะตั€ะฐ'; + }, + 'months' => ['ัะฝะฒะฐั€ั', 'ั„ะตะฒั€ะฐะปั', 'ะผะฐั€ั‚ะฐ', 'ะฐะฟั€ะตะปั', 'ะผะฐั', 'ะธัŽะฝั', 'ะธัŽะปั', 'ะฐะฒะณัƒัั‚ะฐ', 'ัะตะฝั‚ัะฑั€ั', 'ะพะบั‚ัะฑั€ั', 'ะฝะพัะฑั€ั', 'ะดะตะบะฐะฑั€ั'], + 'months_standalone' => ['ัะฝะฒะฐั€ัŒ', 'ั„ะตะฒั€ะฐะปัŒ', 'ะผะฐั€ั‚', 'ะฐะฟั€ะตะปัŒ', 'ะผะฐะน', 'ะธัŽะฝัŒ', 'ะธัŽะปัŒ', 'ะฐะฒะณัƒัั‚', 'ัะตะฝั‚ัะฑั€ัŒ', 'ะพะบั‚ัะฑั€ัŒ', 'ะฝะพัะฑั€ัŒ', 'ะดะตะบะฐะฑั€ัŒ'], + 'months_short' => ['ัะฝะฒ', 'ั„ะตะฒ', 'ะผะฐั€', 'ะฐะฟั€', 'ะผะฐั', 'ะธัŽะฝ', 'ะธัŽะป', 'ะฐะฒะณ', 'ัะตะฝ', 'ะพะบั‚', 'ะฝะพั', 'ะดะตะบ'], + 'months_short_standalone' => ['ัะฝะฒ', 'ั„ะตะฒ', 'ะผะฐั€', 'ะฐะฟั€', 'ะผะฐะน', 'ะธัŽะฝ', 'ะธัŽะป', 'ะฐะฒะณ', 'ัะตะฝ', 'ะพะบั‚', 'ะฝะพั', 'ะดะตะบ'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['ะฒะพัะบั€ะตัะตะฝัŒะต', 'ะฟะพะฝะตะดะตะปัŒะฝะธะบ', 'ะฒั‚ะพั€ะฝะธะบ', 'ัั€ะตะดัƒ', 'ั‡ะตั‚ะฒะตั€ะณ', 'ะฟัั‚ะฝะธั†ัƒ', 'ััƒะฑะฑะพั‚ัƒ'], + 'weekdays_standalone' => ['ะฒะพัะบั€ะตัะตะฝัŒะต', 'ะฟะพะฝะตะดะตะปัŒะฝะธะบ', 'ะฒั‚ะพั€ะฝะธะบ', 'ัั€ะตะดะฐ', 'ั‡ะตั‚ะฒะตั€ะณ', 'ะฟัั‚ะฝะธั†ะฐ', 'ััƒะฑะฑะพั‚ะฐ'], + 'weekdays_short' => ['ะฒัะบ', 'ะฟะฝะด', 'ะฒั‚ั€', 'ัั€ะด', 'ั‡ั‚ะฒ', 'ะฟั‚ะฝ', 'ัะฑั‚'], + 'weekdays_min' => ['ะฒั', 'ะฟะฝ', 'ะฒั‚', 'ัั€', 'ั‡ั‚', 'ะฟั‚', 'ัะฑ'], + 'weekdays_regexp' => '/\[\s*(ะ’|ะฒ)\s*((?:ะฟั€ะพัˆะปัƒัŽ|ัะปะตะดัƒัŽั‰ัƒัŽ|ัั‚ัƒ)\s*)?\]\s*dddd/', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ะธ '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_BY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_BY.php new file mode 100644 index 0000000000..8ca7df3443 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_BY.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_KG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_KG.php new file mode 100644 index 0000000000..8ca7df3443 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_KG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_KZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_KZ.php new file mode 100644 index 0000000000..8ca7df3443 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_KZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_MD.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_MD.php new file mode 100644 index 0000000000..8ca7df3443 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_MD.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_RU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_RU.php new file mode 100644 index 0000000000..8ca7df3443 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_RU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_UA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_UA.php new file mode 100644 index 0000000000..db958d6868 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru_UA.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RFC 2319 bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ru.php', [ + 'weekdays' => ['ะฒะพัะบั€ะตัะตะฝัŒะต', 'ะฟะพะฝะตะดะตะปัŒะฝะธะบ', 'ะฒั‚ะพั€ะฝะธะบ', 'ัั€ะตะดะฐ', 'ั‡ะตั‚ะฒะตั€ะณ', 'ะฟัั‚ะฝะธั†ะฐ', 'ััƒะฑะฑะพั‚ะฐ'], + 'weekdays_short' => ['ะฒัะบ', 'ะฟะฝะด', 'ะฒั‚ะพ', 'ัั€ะด', 'ั‡ั‚ะฒ', 'ะฟั‚ะฝ', 'ััƒะฑ'], + 'weekdays_min' => ['ะฒั', 'ะฟะฝ', 'ะฒั‚', 'ัั€', 'ั‡ั‚', 'ะฟั‚', 'ััƒ'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rw.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rw.php new file mode 100644 index 0000000000..bc4a347f0a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/rw_RW.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rw_RW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rw_RW.php new file mode 100644 index 0000000000..9b3e06825f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rw_RW.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rwanda Steve Murphy murf@e-tools.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Mutarama', 'Gashyantare', 'Werurwe', 'Mata', 'Gicuransi', 'Kamena', 'Nyakanga', 'Kanama', 'Nzeli', 'Ukwakira', 'Ugushyingo', 'Ukuboza'], + 'months_short' => ['Mut', 'Gas', 'Wer', 'Mat', 'Gic', 'Kam', 'Nya', 'Kan', 'Nze', 'Ukw', 'Ugu', 'Uku'], + 'weekdays' => ['Ku cyumweru', 'Kuwa mbere', 'Kuwa kabiri', 'Kuwa gatatu', 'Kuwa kane', 'Kuwa gatanu', 'Kuwa gatandatu'], + 'weekdays_short' => ['Mwe', 'Mbe', 'Kab', 'Gtu', 'Kan', 'Gnu', 'Gnd'], + 'weekdays_min' => ['Mwe', 'Mbe', 'Kab', 'Gtu', 'Kan', 'Gnu', 'Gnd'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'second' => ':count vuna', // less reliable + 's' => ':count vuna', // less reliable + 'a_second' => ':count vuna', // less reliable + + 'year' => 'aka :count', + 'y' => 'aka :count', + 'a_year' => 'aka :count', + + 'month' => 'ezi :count', + 'm' => 'ezi :count', + 'a_month' => 'ezi :count', + + 'week' => ':count icyumweru', + 'w' => ':count icyumweru', + 'a_week' => ':count icyumweru', + + 'day' => ':count nsi', + 'd' => ':count nsi', + 'a_day' => ':count nsi', + + 'hour' => 'saha :count', + 'h' => 'saha :count', + 'a_hour' => 'saha :count', + + 'minute' => ':count -nzinya', + 'min' => ':count -nzinya', + 'a_minute' => ':count -nzinya', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rwk.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rwk.php new file mode 100644 index 0000000000..ed92e8e70e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/rwk.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['utuko', 'kyiukonyi'], + 'weekdays' => ['Jumapilyi', 'Jumatatuu', 'Jumanne', 'Jumatanu', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprilyi', 'Mei', 'Junyi', 'Julyai', 'Agusti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sa.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sa.php new file mode 100644 index 0000000000..1357c0305d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sa.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sa_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sa_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sa_IN.php new file mode 100644 index 0000000000..cfda9a6367 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sa_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - The Debian project Christian Perrier bubulle@debian.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D-MM-YY', + ], + 'months' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคผเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅ‡เคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเคฎเฅเคฌเคฐ', 'เค…เค•เฅเคŸเฅ‚เคฌเคฐ', 'เคจเคตเคฎเฅเคฌเคฐ', 'เคฆเคฟเคธเคฎเฅเคฌเคฐ'], + 'months_short' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคผเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅ‡เคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเคฎเฅเคฌเคฐ', 'เค…เค•เฅเคŸเฅ‚เคฌเคฐ', 'เคจเคตเคฎเฅเคฌเคฐ', 'เคฆเคฟเคธเคฎเฅเคฌเคฐ'], + 'weekdays' => ['เคฐเคตเคฟเคตเคพเคธเคฐ:', 'เคธเฅ‹เคฎเคตเคพเคธเคฐ:', 'เคฎเค‚เค—เคฒเคตเคพเคธเคฐ:', 'เคฌเฅเคงเคตเคพเคธเคฐ:', 'เคฌเฅƒเคนเคธเฅเคชเคคเคฟเคตเคพเคธเคฐเคƒ', 'เคถเฅเค•เฅเคฐเคตเคพเคธเคฐ', 'เคถเคจเคฟเคตเคพเคธเคฐ:'], + 'weekdays_short' => ['เคฐเคตเคฟเคƒ', 'เคธเฅ‹เคฎ:', 'เคฎเค‚เค—เคฒ:', 'เคฌเฅเคง:', 'เคฌเฅƒเคนเคธเฅเคชเคคเคฟเคƒ', 'เคถเฅเค•เฅเคฐ', 'เคถเคจเคฟ:'], + 'weekdays_min' => ['เคฐเคตเคฟเคƒ', 'เคธเฅ‹เคฎ:', 'เคฎเค‚เค—เคฒ:', 'เคฌเฅเคง:', 'เคฌเฅƒเคนเคธเฅเคชเคคเคฟเคƒ', 'เคถเฅเค•เฅเคฐ', 'เคถเคจเคฟ:'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เคชเฅ‚เคฐเฅเคตเคพเคนเฅเคจ', 'เค…เคชเคฐเคพเคนเฅเคจ'], + + 'minute' => ':count เคนเฅ‹เคฐเคพ', // less reliable + 'min' => ':count เคนเฅ‹เคฐเคพ', // less reliable + 'a_minute' => ':count เคนเฅ‹เคฐเคพ', // less reliable + + 'year' => ':count เคตเคฐเฅเคท', + 'y' => ':count เคตเคฐเฅเคท', + 'a_year' => ':count เคตเคฐเฅเคท', + + 'month' => ':count เคฎเคพเคธ', + 'm' => ':count เคฎเคพเคธ', + 'a_month' => ':count เคฎเคพเคธ', + + 'week' => ':count เคธเคชเฅเคคเคพเคนเคƒ saptahaฤฅ', + 'w' => ':count เคธเคชเฅเคคเคพเคนเคƒ saptahaฤฅ', + 'a_week' => ':count เคธเคชเฅเคคเคพเคนเคƒ saptahaฤฅ', + + 'day' => ':count เคฆเคฟเคจ', + 'd' => ':count เคฆเคฟเคจ', + 'a_day' => ':count เคฆเคฟเคจ', + + 'hour' => ':count เค˜เคฃเฅเคŸเคพ', + 'h' => ':count เค˜เคฃเฅเคŸเคพ', + 'a_hour' => ':count เค˜เคฃเฅเคŸเคพ', + + 'second' => ':count เคฆเฅเคตเคฟเคคเฅ€เคฏเคƒ', + 's' => ':count เคฆเฅเคตเคฟเคคเฅ€เคฏเคƒ', + 'a_second' => ':count เคฆเฅเคตเคฟเคคเฅ€เคฏเคƒ', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sah.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sah.php new file mode 100644 index 0000000000..b828824235 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sah.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sah_RU.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sah_RU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sah_RU.php new file mode 100644 index 0000000000..94cc0cb026 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sah_RU.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Valery Timiriliyev Valery Timiriliyev timiriliyev@gmail.com + */ +return array_replace_recursive(require __DIR__.'/ru.php', [ + 'formats' => [ + 'L' => 'YYYY.MM.DD', + ], + 'months' => ['ั‚ะพั…ััƒะฝะฝัŒัƒ', 'ะพะปัƒะฝะฝัŒัƒ', 'ะบัƒะปัƒะฝ ั‚ัƒั‚ะฐั€', 'ะผัƒัƒั ัƒัั‚ะฐั€', 'ั‹ะฐะผ ั‹ะนั‹ะฝ', 'ะฑัั ั‹ะนั‹ะฝ', 'ะพั‚ ั‹ะนั‹ะฝ', 'ะฐั‚ั‹ั€ะดัŒะฐั… ั‹ะนั‹ะฝ', 'ะฑะฐะปะฐา•ะฐะฝ ั‹ะนั‹ะฝ', 'ะฐะปั‚ั‹ะฝะฝัŒั‹', 'ััั‚ะธะฝะฝัŒะธ', 'ะฐั…ัั‹ะฝะฝัŒั‹'], + 'months_short' => ['ั‚ะพั…ั', 'ะพะปัƒะฝ', 'ะบัƒะป', 'ะผัƒัƒั', 'ั‹ะฐะผ', 'ะฑัั', 'ะพั‚', 'ะฐั‚ั‹ั€', 'ะฑะฐะป', 'ะฐะปั‚', 'ััั‚', 'ะฐั…ั'], + 'weekdays' => ['ะฑะฐัะบั‹าปั‹ะฐะฝะฝัŒะฐ', 'ะฑัะฝะธะดะธัะฝะฝัŒะธะบ', 'ะพะฟั‚ัƒะพั€ัƒะฝะฝัŒัƒะบ', 'ััั€ัะดั', 'ั‡ัะฟะฟะธัั€', 'ะฑััั‚ะธะฝัั', 'ััƒะฑัƒะพั‚ะฐ'], + 'weekdays_short' => ['ะฑั', 'ะฑะฝ', 'ะพะฟ', 'ัั€', 'ั‡ะฟ', 'ะฑั‚', 'ัะฑ'], + 'weekdays_min' => ['ะฑั', 'ะฑะฝ', 'ะพะฟ', 'ัั€', 'ั‡ะฟ', 'ะฑั‚', 'ัะฑ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/saq.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/saq.php new file mode 100644 index 0000000000..ff8bf6044d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/saq.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Tesiran', 'Teipa'], + 'weekdays' => ['Mderot ee are', 'Mderot ee kuni', 'Mderot ee ongโ€™wan', 'Mderot ee inet', 'Mderot ee ile', 'Mderot ee sapa', 'Mderot ee kwe'], + 'weekdays_short' => ['Are', 'Kun', 'Ong', 'Ine', 'Ile', 'Sap', 'Kwe'], + 'weekdays_min' => ['Are', 'Kun', 'Ong', 'Ine', 'Ile', 'Sap', 'Kwe'], + 'months' => ['Lapa le obo', 'Lapa le waare', 'Lapa le okuni', 'Lapa le ongโ€™wan', 'Lapa le imet', 'Lapa le ile', 'Lapa le sapa', 'Lapa le isiet', 'Lapa le saal', 'Lapa le tomon', 'Lapa le tomon obo', 'Lapa le tomon waare'], + 'months_short' => ['Obo', 'Waa', 'Oku', 'Ong', 'Ime', 'Ile', 'Sap', 'Isi', 'Saa', 'Tom', 'Tob', 'Tow'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sat.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sat.php new file mode 100644 index 0000000000..c9914c6685 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sat.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sat_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sat_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sat_IN.php new file mode 100644 index 0000000000..632b1af6bc --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sat_IN.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅ‡เคฒ', 'เคฎเคˆ', 'เคœเฅเคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเคฎเฅเคฌเคฐ', 'เค…เค–เคฅเคฌเคฐ', 'เคจเคตเคฎเฅเคฌเคฐ', 'เคฆเคฟเคธเคฎเฅเคฌเคฐ'], + 'months_short' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅ‡เคฒ', 'เคฎเคˆ', 'เคœเฅเคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเคฎเฅเคฌเคฐ', 'เค…เค–เคฅเคฌเคฐ', 'เคจเคตเคฎเฅเคฌเคฐ', 'เคฆเคฟเคธเคฎเฅเคฌเคฐ'], + 'weekdays' => ['เคธเคฟเค‚เค—เฅ‡เคฎเคพเคเคนเคพเค', 'เค“เคคเฅ‡เคฎเคพเคเคนเคพเค', 'เคฌเคพเคฒเฅ‡เคฎเคพเคเคนเคพเค', 'เคธเคพเค—เฅเคจเคฎเคพเคเคนเคพเค', 'เคธเคพเคฐเคฆเฅ€เคฎเคพเคเคนเคพเค', 'เคœเคพเคฐเฅเคฎเคฎเคพเคเคนเคพเค', 'เคžเฅเคนเฅเคฎเคฎเคพเคเคนเคพเค'], + 'weekdays_short' => ['เคธเคฟเค‚เค—เฅ‡', 'เค“เคคเฅ‡', 'เคฌเคพเคฒเฅ‡', 'เคธเคพเค—เฅเคจ', 'เคธเคพเคฐเคฆเฅ€', 'เคœเคพเคฐเฅเคฎ', 'เคžเฅเคนเฅเคฎ'], + 'weekdays_min' => ['เคธเคฟเค‚เค—เฅ‡', 'เค“เคคเฅ‡', 'เคฌเคพเคฒเฅ‡', 'เคธเคพเค—เฅเคจ', 'เคธเคพเคฐเคฆเฅ€', 'เคœเคพเคฐเฅเคฎ', 'เคžเฅเคนเฅเคฎ'], + 'day_of_first_week_of_year' => 1, + + 'month' => ':count ล„indแบก cando', // less reliable + 'm' => ':count ล„indแบก cando', // less reliable + 'a_month' => ':count ล„indแบก cando', // less reliable + + 'week' => ':count mรฃhรฃ', // less reliable + 'w' => ':count mรฃhรฃ', // less reliable + 'a_week' => ':count mรฃhรฃ', // less reliable + + 'hour' => ':count แฑฅแฑณแฑฑแฑš', // less reliable + 'h' => ':count แฑฅแฑณแฑฑแฑš', // less reliable + 'a_hour' => ':count แฑฅแฑณแฑฑแฑš', // less reliable + + 'minute' => ':count แฑฏแฑคแฑžแฑชแฑฉ', // less reliable + 'min' => ':count แฑฏแฑคแฑžแฑชแฑฉ', // less reliable + 'a_minute' => ':count แฑฏแฑคแฑžแฑชแฑฉ', // less reliable + + 'second' => ':count ar', // less reliable + 's' => ':count ar', // less reliable + 'a_second' => ':count ar', // less reliable + + 'year' => ':count neฬฒs', + 'y' => ':count neฬฒs', + 'a_year' => ':count neฬฒs', + + 'day' => ':count แฑซแฑคแฑฑ', + 'd' => ':count แฑซแฑคแฑฑ', + 'a_day' => ':count แฑซแฑคแฑฑ', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sbp.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sbp.php new file mode 100644 index 0000000000..e29ca379f7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sbp.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Lwamilawu', 'Pashamihe'], + 'weekdays' => ['Mulungu', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alahamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Mul', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Mul', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Mupalangulwa', 'Mwitope', 'Mushende', 'Munyi', 'Mushende Magali', 'Mujimbi', 'Mushipepo', 'Mupuguto', 'Munyense', 'Mokhu', 'Musongandembwe', 'Muhaano'], + 'months_short' => ['Mup', 'Mwi', 'Msh', 'Mun', 'Mag', 'Muj', 'Msp', 'Mpg', 'Mye', 'Mok', 'Mus', 'Muh'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sc.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sc.php new file mode 100644 index 0000000000..7178cf4f16 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sc.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sc_IT.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sc_IT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sc_IT.php new file mode 100644 index 0000000000..5d1e4cecc6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sc_IT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sardinian Translators Team Massimeddu Cireddu massimeddu@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD. MM. YY', + ], + 'months' => ['Ghennร rgiu', 'Freร rgiu', 'Martzu', 'Abrile', 'Maju', 'Lร mpadas', 'Argiolas//Trรฌulas', 'Austu', 'Cabudanni', 'Santugaine//Ladร mine', 'Onniasantu//Santandria', 'Nadale//Idas'], + 'months_short' => ['Ghe', 'Fre', 'Mar', 'Abr', 'Maj', 'Lร m', 'Arg', 'Aus', 'Cab', 'Lad', 'Onn', 'Nad'], + 'weekdays' => ['Domรฌnigu', 'Lunis', 'Martis', 'Mรจrcuris', 'Giรฒbia', 'Chenร bura', 'Sร badu'], + 'weekdays_short' => ['Dom', 'Lun', 'Mar', 'Mรจr', 'Giรฒ', 'Che', 'Sร b'], + 'weekdays_min' => ['Dom', 'Lun', 'Mar', 'Mรจr', 'Giรฒ', 'Che', 'Sร b'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'minute' => ':count mementu', // less reliable + 'min' => ':count mementu', // less reliable + 'a_minute' => ':count mementu', // less reliable + + 'year' => ':count annu', + 'y' => ':count annu', + 'a_year' => ':count annu', + + 'month' => ':count mese', + 'm' => ':count mese', + 'a_month' => ':count mese', + + 'week' => ':count chida', + 'w' => ':count chida', + 'a_week' => ':count chida', + + 'day' => ':count dรญ', + 'd' => ':count dรญ', + 'a_day' => ':count dรญ', + + 'hour' => ':count ora', + 'h' => ':count ora', + 'a_hour' => ':count ora', + + 'second' => ':count secundu', + 's' => ':count secundu', + 'a_second' => ':count secundu', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sd.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sd.php new file mode 100644 index 0000000000..0022c5a9cf --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sd.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$months = [ + 'ุฌู†ูˆุฑูŠ', + 'ููŠุจุฑูˆุฑูŠ', + 'ู…ุงุฑฺ†', + 'ุงูพุฑูŠู„', + 'ู…ุฆูŠ', + 'ุฌูˆู†', + 'ุฌูˆู„ุงุกู', + 'ุขฺฏุณูฝ', + 'ุณูŠูพูฝู…ุจุฑ', + 'ุขฺชูฝูˆุจุฑ', + 'ู†ูˆู…ุจุฑ', + 'ฺŠุณู…ุจุฑ', +]; + +$weekdays = [ + 'ุขฺ†ุฑ', + 'ุณูˆู…ุฑ', + 'ุงฺฑุงุฑูˆ', + 'ุงุฑุจุน', + 'ุฎู…ูŠุณ', + 'ุฌู…ุน', + 'ฺ‡ู†ฺ‡ุฑ', +]; + +/* + * Authors: + * - Narain Sagar + * - Sawood Alam + * - Narain Sagar + */ +return [ + 'year' => '{1}'.'ู‡ฺช ุณุงู„'.'|:count '.'ุณุงู„', + 'month' => '{1}'.'ู‡ฺช ู…ู‡ูŠู†ูˆ'.'|:count '.'ู…ู‡ูŠู†ุง', + 'week' => '{1}'.'ฺพฺช ฺพูุชูˆ'.'|:count '.'ู‡ูุชุง', + 'day' => '{1}'.'ู‡ฺช ฺูŠู†ู‡ู†'.'|:count '.'ฺูŠู†ู‡ู†', + 'hour' => '{1}'.'ู‡ฺช ฺชู„ุงฺช'.'|:count '.'ฺชู„ุงฺช', + 'minute' => '{1}'.'ู‡ฺช ู…ู†ูฝ'.'|:count '.'ู…ู†ูฝ', + 'second' => '{1}'.'ฺ†ู†ุฏ ุณูŠฺชู†ฺŠ'.'|:count '.'ุณูŠฺชู†ฺŠ', + 'ago' => ':time ุงฺณ', + 'from_now' => ':time ูพูˆุก', + 'diff_yesterday' => 'ฺชุงู„ู‡ู‡', + 'diff_today' => 'ุงฺ„', + 'diff_tomorrow' => 'ุณฺ€ุงฺปูŠ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'ddddุŒ D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ุงฺ„] LT', + 'nextDay' => '[ุณฺ€ุงฺปูŠ] LT', + 'nextWeek' => 'dddd [ุงฺณูŠู† ู‡ูุชูŠ ุชูŠ] LT', + 'lastDay' => '[ฺชุงู„ู‡ู‡] LT', + 'lastWeek' => '[ฺฏุฒุฑูŠู„ ู‡ูุชูŠ] dddd [ุชูŠ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ุตุจุญ', 'ุดุงู…'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => $weekdays, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => ['ุŒ ', ' ฝ '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN.php new file mode 100644 index 0000000000..de1dad0519 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/sd.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['ุฌู†ูˆุฑูŠ', 'ูุจุฑูˆุฑูŠ', 'ู…ุงุฑฺ†', 'ุงูพุฑูŠู„', 'ู…ูŠ', 'ุฌูˆู†', 'ุฌูˆู„ุงุกู', 'ุขฺฏุณูฝ', 'ุณูŠูพูฝูŠู…ุจุฑ', 'ุขฺชูฝูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ฺŠุณู…ุจุฑ'], + 'months_short' => ['ุฌู†ูˆุฑูŠ', 'ูุจุฑูˆุฑูŠ', 'ู…ุงุฑฺ†', 'ุงูพุฑูŠู„', 'ู…ูŠ', 'ุฌูˆู†', 'ุฌูˆู„ุงุกู', 'ุขฺฏุณูฝ', 'ุณูŠูพูฝูŠู…ุจุฑ', 'ุขฺชูฝูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ฺŠุณู…ุจุฑ'], + 'weekdays' => ['ุขุฑุชูˆุงุฑู', 'ุณูˆู…ุฑู', 'ู…ู†ฺฏู„ู', 'ูปูฺŒุฑู', 'ูˆุณูพุช', 'ุฌูู…ูˆ', 'ฺ‡ู†ฺ‡ุฑ'], + 'weekdays_short' => ['ุขุฑุชูˆุงุฑู', 'ุณูˆู…ุฑู', 'ู…ู†ฺฏู„ู', 'ูปูฺŒุฑู', 'ูˆุณูพุช', 'ุฌูู…ูˆ', 'ฺ‡ู†ฺ‡ุฑ'], + 'weekdays_min' => ['ุขุฑุชูˆุงุฑู', 'ุณูˆู…ุฑู', 'ู…ู†ฺฏู„ู', 'ูปูฺŒุฑู', 'ูˆุณูพุช', 'ุฌูู…ูˆ', 'ฺ‡ู†ฺ‡ุฑ'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN@devanagari.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN@devanagari.php new file mode 100644 index 0000000000..061fcc1628 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN@devanagari.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/sd.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคฌเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคšเคฟ', 'เค…เคชเฅเคฐเฅ‡เคฒ', 'เคฎเฅ‡', 'เคœเฅ‚เคจเคฟ', 'เคœเฅ‚เคฒเคพเค‡', 'เค†เค—เคธเฅเคŸเฅ', 'เคธเฅ‡เคชเฅเคŸเฅ‡เค‚เคฌเคฐเฅ‚', 'เค†เค•เฅเคŸเฅ‚เคฌเคฐเฅ‚', 'เคจเคตเค‚เคฌเคฐเฅ‚', 'เฅพเคฟเคธเค‚เคฌเคฐเฅ‚'], + 'months_short' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคฌเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคšเคฟ', 'เค…เคชเฅเคฐเฅ‡เคฒ', 'เคฎเฅ‡', 'เคœเฅ‚เคจเคฟ', 'เคœเฅ‚เคฒเคพเค‡', 'เค†เค—เคธเฅเคŸเฅ', 'เคธเฅ‡เคชเฅเคŸเฅ‡เค‚เคฌเคฐเฅ‚', 'เค†เค•เฅเคŸเฅ‚เคฌเคฐเฅ‚', 'เคจเคตเค‚เคฌเคฐเฅ‚', 'เฅพเคฟเคธเค‚เคฌเคฐเฅ‚'], + 'weekdays' => ['เค†เคฐเฅเคคเคตเคพเคฐเฅ‚', 'เคธเฅ‚เคฎเคฐเฅ‚', 'เคฎเค‚เค—เคฒเฅ‚', 'เฅฟเฅเคงเคฐเฅ‚', 'เคตเคฟเคธเฅเคชเคคเคฟ', 'เคœเฅเคฎเฅ‹', 'เค›เค‚เค›เคธ'], + 'weekdays_short' => ['เค†เคฐเฅเคคเคตเคพเคฐเฅ‚', 'เคธเฅ‚เคฎเคฐเฅ‚', 'เคฎเค‚เค—เคฒเฅ‚', 'เฅฟเฅเคงเคฐเฅ‚', 'เคตเคฟเคธเฅเคชเคคเคฟ', 'เคœเฅเคฎเฅ‹', 'เค›เค‚เค›เคธ'], + 'weekdays_min' => ['เค†เคฐเฅเคคเคตเคพเคฐเฅ‚', 'เคธเฅ‚เคฎเคฐเฅ‚', 'เคฎเค‚เค—เคฒเฅ‚', 'เฅฟเฅเคงเคฐเฅ‚', 'เคตเคฟเคธเฅเคชเคคเคฟ', 'เคœเฅเคฎเฅ‹', 'เค›เค‚เค›เคธ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เคฎ.เคชเฅ‚.', 'เคฎ.เคจเค‚.'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/se.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/se.php new file mode 100644 index 0000000000..7c4b92a55d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/se.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Karamell + */ +return [ + 'year' => '{1}:count jahki|:count jagit', + 'a_year' => '{1}okta jahki|:count jagit', + 'y' => ':count j.', + 'month' => '{1}:count mรกnnu|:count mรกnut', + 'a_month' => '{1}okta mรกnnu|:count mรกnut', + 'm' => ':count mรกn.', + 'week' => '{1}:count vahkku|:count vahkku', + 'a_week' => '{1}okta vahkku|:count vahkku', + 'w' => ':count v.', + 'day' => '{1}:count beaivi|:count beaivvit', + 'a_day' => '{1}okta beaivi|:count beaivvit', + 'd' => ':count b.', + 'hour' => '{1}:count diimmu|:count diimmut', + 'a_hour' => '{1}okta diimmu|:count diimmut', + 'h' => ':count d.', + 'minute' => '{1}:count minuhta|:count minuhtat', + 'a_minute' => '{1}okta minuhta|:count minuhtat', + 'min' => ':count min.', + 'second' => '{1}:count sekunddat|:count sekunddat', + 'a_second' => '{1}moadde sekunddat|:count sekunddat', + 's' => ':count s.', + 'ago' => 'maล‹it :time', + 'from_now' => ':time geaลพes', + 'diff_yesterday' => 'ikte', + 'diff_yesterday_regexp' => 'ikte(?:\\s+ti)?', + 'diff_today' => 'otne', + 'diff_today_regexp' => 'otne(?:\\s+ti)?', + 'diff_tomorrow' => 'ihttin', + 'diff_tomorrow_regexp' => 'ihttin(?:\\s+ti)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'MMMM D. [b.] YYYY', + 'LLL' => 'MMMM D. [b.] YYYY [ti.] HH:mm', + 'LLLL' => 'dddd, MMMM D. [b.] YYYY [ti.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[otne ti] LT', + 'nextDay' => '[ihttin ti] LT', + 'nextWeek' => 'dddd [ti] LT', + 'lastDay' => '[ikte ti] LT', + 'lastWeek' => '[ovddit] dddd [ti] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['oฤ‘ฤ‘ajagemรกnnu', 'guovvamรกnnu', 'njukฤamรกnnu', 'cuoล‹omรกnnu', 'miessemรกnnu', 'geassemรกnnu', 'suoidnemรกnnu', 'borgemรกnnu', 'ฤakฤamรกnnu', 'golggotmรกnnu', 'skรกbmamรกnnu', 'juovlamรกnnu'], + 'months_short' => ['oฤ‘ฤ‘j', 'guov', 'njuk', 'cuo', 'mies', 'geas', 'suoi', 'borg', 'ฤakฤ', 'golg', 'skรกb', 'juov'], + 'weekdays' => ['sotnabeaivi', 'vuossรกrga', 'maล‹ล‹ebรกrga', 'gaskavahkku', 'duorastat', 'bearjadat', 'lรกvvardat'], + 'weekdays_short' => ['sotn', 'vuos', 'maล‹', 'gask', 'duor', 'bear', 'lรกv'], + 'weekdays_min' => ['s', 'v', 'm', 'g', 'd', 'b', 'L'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ja '], + 'meridiem' => ['i.b.', 'e.b.'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/se_FI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/se_FI.php new file mode 100644 index 0000000000..cf01805def --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/se_FI.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/se.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'months' => ['oฤ‘ฤ‘ajagemรกnnu', 'guovvamรกnnu', 'njukฤamรกnnu', 'cuoล‹omรกnnu', 'miessemรกnnu', 'geassemรกnnu', 'suoidnemรกnnu', 'borgemรกnnu', 'ฤakฤamรกnnu', 'golggotmรกnnu', 'skรกbmamรกnnu', 'juovlamรกnnu'], + 'months_short' => ['oฤ‘ฤ‘j', 'guov', 'njuk', 'cuoล‹', 'mies', 'geas', 'suoi', 'borg', 'ฤakฤ', 'golg', 'skรกb', 'juov'], + 'weekdays' => ['sotnabeaivi', 'mรกnnodat', 'disdat', 'gaskavahkku', 'duorastat', 'bearjadat', 'lรกvvordat'], + 'weekdays_short' => ['so', 'mรก', 'di', 'ga', 'du', 'be', 'lรก'], + 'weekdays_min' => ['so', 'mรก', 'di', 'ga', 'du', 'be', 'lรก'], + 'meridiem' => ['i', 'e'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/se_NO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/se_NO.php new file mode 100644 index 0000000000..177c7e94ee --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/se_NO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/se.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/se_SE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/se_SE.php new file mode 100644 index 0000000000..177c7e94ee --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/se_SE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/se.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/seh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/seh.php new file mode 100644 index 0000000000..babf9afb02 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/seh.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Dimingu', 'Chiposi', 'Chipiri', 'Chitatu', 'Chinai', 'Chishanu', 'Sabudu'], + 'weekdays_short' => ['Dim', 'Pos', 'Pir', 'Tat', 'Nai', 'Sha', 'Sab'], + 'weekdays_min' => ['Dim', 'Pos', 'Pir', 'Tat', 'Nai', 'Sha', 'Sab'], + 'months' => ['Janeiro', 'Fevreiro', 'Marco', 'Abril', 'Maio', 'Junho', 'Julho', 'Augusto', 'Setembro', 'Otubro', 'Novembro', 'Decembro'], + 'months_short' => ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Aug', 'Set', 'Otu', 'Nov', 'Dec'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'd [de] MMM [de] YYYY', + 'LLL' => 'd [de] MMMM [de] YYYY HH:mm', + 'LLLL' => 'dddd, d [de] MMMM [de] YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ses.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ses.php new file mode 100644 index 0000000000..e1099e65a0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ses.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Adduha', 'Aluula'], + 'weekdays' => ['Alhadi', 'Atinni', 'Atalaata', 'Alarba', 'Alhamiisa', 'Alzuma', 'Asibti'], + 'weekdays_short' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'weekdays_min' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'months' => ['ลฝanwiye', 'Feewiriye', 'Marsi', 'Awiril', 'Me', 'ลฝuweล‹', 'ลฝuyye', 'Ut', 'Sektanbur', 'Oktoobur', 'Noowanbur', 'Deesanbur'], + 'months_short' => ['ลฝan', 'Fee', 'Mar', 'Awi', 'Me', 'ลฝuw', 'ลฝuy', 'Ut', 'Sek', 'Okt', 'Noo', 'Dee'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'month' => ':count alaada', // less reliable + 'm' => ':count alaada', // less reliable + 'a_month' => ':count alaada', // less reliable + + 'hour' => ':count ษฒaajin', // less reliable + 'h' => ':count ษฒaajin', // less reliable + 'a_hour' => ':count ษฒaajin', // less reliable + + 'minute' => ':count zarbu', // less reliable + 'min' => ':count zarbu', // less reliable + 'a_minute' => ':count zarbu', // less reliable + + 'year' => ':count jiiri', + 'y' => ':count jiiri', + 'a_year' => ':count jiiri', + + 'week' => ':count jirbiiyye', + 'w' => ':count jirbiiyye', + 'a_week' => ':count jirbiiyye', + + 'day' => ':count zaari', + 'd' => ':count zaari', + 'a_day' => ':count zaari', + + 'second' => ':count ihinkante', + 's' => ':count ihinkante', + 'a_second' => ':count ihinkante', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sg.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sg.php new file mode 100644 index 0000000000..9264e89399 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sg.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ND', 'LK'], + 'weekdays' => ['Bikua-รดko', 'Bรฏkua-รปse', 'Bรฏkua-ptรข', 'Bรฏkua-usรฏรถ', 'Bรฏkua-okรผ', 'Lรขpรดsรถ', 'Lรขyenga'], + 'weekdays_short' => ['Bk1', 'Bk2', 'Bk3', 'Bk4', 'Bk5', 'Lรขp', 'Lรขy'], + 'weekdays_min' => ['Bk1', 'Bk2', 'Bk3', 'Bk4', 'Bk5', 'Lรขp', 'Lรขy'], + 'months' => ['Nyenye', 'Fulundรฏgi', 'Mbรคngรผ', 'Ngubรนe', 'Bรชlรคwรผ', 'Fรถndo', 'Lengua', 'Kรผkรผrรผ', 'Mvuka', 'Ngberere', 'Nabรคndรผru', 'Kakauka'], + 'months_short' => ['Nye', 'Ful', 'Mbรค', 'Ngu', 'Bรชl', 'Fรถn', 'Len', 'Kรผk', 'Mvu', 'Ngb', 'Nab', 'Kak'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count dฤ', // less reliable + 'y' => ':count dฤ', // less reliable + 'a_year' => ':count dฤ', // less reliable + + 'week' => ':count bรฏkua-okรผ', // less reliable + 'w' => ':count bรฏkua-okรผ', // less reliable + 'a_week' => ':count bรฏkua-okรผ', // less reliable + + 'day' => ':count ziggawรข', // less reliable + 'd' => ':count ziggawรข', // less reliable + 'a_day' => ':count ziggawรข', // less reliable + + 'hour' => ':count yรขngรขkรถdรถrรถ', // less reliable + 'h' => ':count yรขngรขkรถdรถrรถ', // less reliable + 'a_hour' => ':count yรขngรขkรถdรถrรถ', // less reliable + + 'second' => ':count bรฏkua-รดko', // less reliable + 's' => ':count bรฏkua-รดko', // less reliable + 'a_second' => ':count bรฏkua-รดko', // less reliable + + 'month' => ':count Nze tรฎ ngu', + 'm' => ':count Nze tรฎ ngu', + 'a_month' => ':count Nze tรฎ ngu', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sgs.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sgs.php new file mode 100644 index 0000000000..864b9892c6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sgs.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sgs_LT.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sgs_LT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sgs_LT.php new file mode 100644 index 0000000000..aa9e942e48 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sgs_LT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Arnas Udoviฤius bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY.MM.DD', + ], + 'months' => ['sausฤ—', 'vasarฤ—', 'kuova', 'balondฤ—', 'gegoลพฤ—s', 'bฤ—rลพelฤ—', 'lรซpas', 'rogpjลซtฤ—', 'siejฤ—s', 'spalฤ—', 'lapkrฤ—stฤ—', 'grลฏdฤ—'], + 'months_short' => ['Sau', 'Vas', 'Kuo', 'Bal', 'Geg', 'Bฤ—r', 'Lรซp', 'Rgp', 'Sie', 'Spa', 'Lap', 'Grd'], + 'weekdays' => ['nedielฤ—s dรซna', 'panedielis', 'oterninks', 'sereda', 'ฤetvergs', 'petnฤซฤฤ—', 'sobata'], + 'weekdays_short' => ['Nd', 'Pn', 'Ot', 'Sr', 'ฤŒt', 'Pt', 'Sb'], + 'weekdays_min' => ['Nd', 'Pn', 'Ot', 'Sr', 'ฤŒt', 'Pt', 'Sb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'minute' => ':count maลพos', // less reliable + 'min' => ':count maลพos', // less reliable + 'a_minute' => ':count maลพos', // less reliable + + 'year' => ':count metฤ', + 'y' => ':count metฤ', + 'a_year' => ':count metฤ', + + 'month' => ':count mienou', + 'm' => ':count mienou', + 'a_month' => ':count mienou', + + 'week' => ':count nedielฤ—', + 'w' => ':count nedielฤ—', + 'a_week' => ':count nedielฤ—', + + 'day' => ':count dฤซna', + 'd' => ':count dฤซna', + 'a_day' => ':count dฤซna', + + 'hour' => ':count adฤซna', + 'h' => ':count adฤซna', + 'a_hour' => ':count adฤซna', + + 'second' => ':count Sekondฤ—', + 's' => ':count Sekondฤ—', + 'a_second' => ':count Sekondฤ—', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sh.php new file mode 100644 index 0000000000..e03b506752 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sh.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +// @codeCoverageIgnoreStart +use Symfony\Component\Translation\PluralizationRules; + +if (class_exists('Symfony\\Component\\Translation\\PluralizationRules')) { + PluralizationRules::set(static function ($number) { + return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + }, 'sh'); +} +// @codeCoverageIgnoreEnd + +/* + * Authors: + * - ะขะพะผะธั†ะฐ ะšะพั€ะฐั› + * - Enrique Vidal + * - Christopher Dell + * - dmilisic + * - danijel + * - Miroslav Matkovic (mikki021) + */ +return [ + 'diff_now' => 'sada', + 'diff_yesterday' => 'juฤe', + 'diff_tomorrow' => 'sutra', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'MMMM D, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'year' => ':count godina|:count godine|:count godina', + 'y' => ':count g.', + 'month' => ':count mesec|:count meseca|:count meseci', + 'm' => ':count m.', + 'week' => ':count nedelja|:count nedelje|:count nedelja', + 'w' => ':count n.', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count d.', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count ฤ.', + 'minute' => ':count minut|:count minuta|:count minuta', + 'min' => ':count min.', + 'second' => ':count sekund|:count sekunde|:count sekundi', + 's' => ':count s.', + 'ago' => 'pre :time', + 'from_now' => 'za :time', + 'after' => 'nakon :time', + 'before' => ':time raniั˜e', + 'weekdays' => ['Nedelja', 'Ponedeljak', 'Utorak', 'Sreda', 'ฤŒetvrtak', 'Petak', 'Subota'], + 'weekdays_short' => ['Ned', 'Pon', 'Uto', 'Sre', 'ฤŒet', 'Pet', 'Sub'], + 'weekdays_min' => ['Ned', 'Pon', 'Uto', 'Sre', 'ฤŒet', 'Pet', 'Sub'], + 'months' => ['Januar', 'Februar', 'Mart', 'April', 'Maj', 'Jun', 'Jul', 'Avgust', 'Septembar', 'Oktobar', 'Novembar', 'Decembar'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'], + 'list' => [', ', ' i '], + 'meridiem' => ['pre podne', 'po podne'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shi.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shi.php new file mode 100644 index 0000000000..781518694e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shi.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['โตœโต‰โดผโดฐโตกโตœ', 'โตœโดฐโดทโดณโดณโตฏโดฐโตœ'], + 'weekdays' => ['โดฐโต™โดฐโตŽโดฐโต™', 'โดฐโตขโตโดฐโต™', 'โดฐโต™โต‰โตโดฐโต™', 'โดฐโดฝโต•โดฐโต™', 'โดฐโดฝโตกโดฐโต™', 'โต™โต‰โตŽโตกโดฐโต™', 'โดฐโต™โต‰โดนโตขโดฐโต™'], + 'weekdays_short' => ['โดฐโต™โดฐ', 'โดฐโตขโต', 'โดฐโต™โต‰', 'โดฐโดฝโต•', 'โดฐโดฝโตก', 'โดฐโต™โต‰โตŽ', 'โดฐโต™โต‰โดน'], + 'weekdays_min' => ['โดฐโต™โดฐ', 'โดฐโตขโต', 'โดฐโต™โต‰', 'โดฐโดฝโต•', 'โดฐโดฝโตก', 'โดฐโต™โต‰โตŽ', 'โดฐโต™โต‰โดน'], + 'months' => ['โต‰โตโตโดฐโตขโต”', 'โดฑโต•โดฐโตขโต•', 'โตŽโดฐโต•โตš', 'โต‰โดฑโต”โต‰โต”', 'โตŽโดฐโตขโตขโต“', 'โตขโต“โตโตขโต“', 'โตขโต“โตโตขโต“โตฃ', 'โต–โต“โต›โตœ', 'โต›โต“โตœโดฐโตโดฑโต‰โต”', 'โดฝโตœโต“โดฑโต”', 'โตโต“โตกโดฐโตโดฑโต‰โต”', 'โดทโต“โตŠโดฐโตโดฑโต‰โต”'], + 'months_short' => ['โต‰โตโต', 'โดฑโต•โดฐ', 'โตŽโดฐโต•', 'โต‰โดฑโต”', 'โตŽโดฐโตข', 'โตขโต“โต', 'โตขโต“โต', 'โต–โต“โต›', 'โต›โต“โตœ', 'โดฝโตœโต“', 'โตโต“โตก', 'โดทโต“โตŠ'], + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count aseggwas', + 'y' => ':count aseggwas', + 'a_year' => ':count aseggwas', + + 'month' => ':count ayyur', + 'm' => ':count ayyur', + 'a_month' => ':count ayyur', + + 'week' => ':count imalass', + 'w' => ':count imalass', + 'a_week' => ':count imalass', + + 'day' => ':count ass', + 'd' => ':count ass', + 'a_day' => ':count ass', + + 'hour' => ':count urษฃ', // less reliable + 'h' => ':count urษฃ', // less reliable + 'a_hour' => ':count urษฃ', // less reliable + + 'minute' => ':count โดฐโตŽโตฅโต‰', // less reliable + 'min' => ':count โดฐโตŽโตฅโต‰', // less reliable + 'a_minute' => ':count โดฐโตŽโตฅโต‰', // less reliable + + 'second' => ':count sin', // less reliable + 's' => ':count sin', // less reliable + 'a_second' => ':count sin', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shi_Latn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shi_Latn.php new file mode 100644 index 0000000000..cddfb242f0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shi_Latn.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/shi.php', [ + 'meridiem' => ['tifawt', 'tadggสทat'], + 'weekdays' => ['asamas', 'aynas', 'asinas', 'akแน›as', 'akwas', 'asimwas', 'asiแธyas'], + 'weekdays_short' => ['asa', 'ayn', 'asi', 'akแน›', 'akw', 'asim', 'asiแธ'], + 'weekdays_min' => ['asa', 'ayn', 'asi', 'akแน›', 'akw', 'asim', 'asiแธ'], + 'months' => ['innayr', 'bแน›ayแน›', 'maแน›แนฃ', 'ibrir', 'mayyu', 'yunyu', 'yulyuz', 'ษฃuct', 'cutanbir', 'ktubr', 'nuwanbir', 'dujanbir'], + 'months_short' => ['inn', 'bแน›a', 'maแน›', 'ibr', 'may', 'yun', 'yul', 'ษฃuc', 'cut', 'ktu', 'nuw', 'duj'], + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'minute' => ':count agur', // less reliable + 'min' => ':count agur', // less reliable + 'a_minute' => ':count agur', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shi_Tfng.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shi_Tfng.php new file mode 100644 index 0000000000..f3df1f2cc9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shi_Tfng.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/shi.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shn.php new file mode 100644 index 0000000000..fe7b1ea556 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/shn_MM.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shn_MM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shn_MM.php new file mode 100644 index 0000000000..f399acf0d3 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shn_MM.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ubuntu Myanmar LoCo Team https://ubuntu-mm.net Bone Pyae Sone bone.burma@mail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'OY MMM OD dddd', + ], + 'months' => ['แ€œแ€ญแ€ฐแผแ€บแตแ€™แ€บ', 'แ€œแ€ญแ€ฐแผแ€บแ€žแขแ€™แ€บ', 'แ€œแ€ญแ€ฐแผแ€บแ€žแ€ฎ', 'แ€œแ€ญแ€ฐแผแ€บแ‚แ‚ƒแ‚ˆ', 'แ€œแ€ญแ€ฐแผแ€บแ‚แ€ฐแตแ€บแ€ธ', 'แ€œแ€ญแ€ฐแผแ€บแธแ€ตแ€แ€บแ€ธ', 'แ€œแ€ญแ€ฐแผแ€บแ€•แ‚…แ€แ€บแ‚‡', 'แ€œแ€ญแ€ฐแผแ€บแตแ€แ€บแ‚ˆ', 'แ€œแ€ญแ€ฐแผแ€บแ€žแ€ญแ€•แ€บแ€ธ', 'แ€œแ€ญแ€ฐแผแ€บแ€žแ€ญแ€•แ€บแ€ธแ€ขแ€ญแ€แ€บแ€ธ', 'แ€œแ€ญแ€ฐแผแ€บแ€žแ€ญแ€•แ€บแ€ธแ€ขแ€ญแ€แ€บแ€ธแ€žแ€ฝแ€„แ€บ', 'แ€œแ€ญแ€ฐแผแ€บแธแ€ตแ€„แ€บ'], + 'months_short' => ['แ€œแ€ญแ€ฐแผแ€บแตแ€™แ€บ', 'แ€œแ€ญแ€ฐแผแ€บแ€žแขแ€™แ€บ', 'แ€œแ€ญแ€ฐแผแ€บแ€žแ€ฎ', 'แ€œแ€ญแ€ฐแผแ€บแ‚แ‚ƒแ‚ˆ', 'แ€œแ€ญแ€ฐแผแ€บแ‚แ€ฐแตแ€บแ€ธ', 'แ€œแ€ญแ€ฐแผแ€บแธแ€ตแ€แ€บแ€ธ', 'แ€œแ€ญแ€ฐแผแ€บแ€•แ‚…แ€แ€บแ‚‡', 'แ€œแ€ญแ€ฐแผแ€บแตแ€แ€บแ‚ˆ', 'แ€œแ€ญแ€ฐแผแ€บแ€žแ€ญแ€•แ€บแ€ธ', 'แ€œแ€ญแ€ฐแผแ€บแ€žแ€ญแ€•แ€บแ€ธแ€ขแ€ญแ€แ€บแ€ธ', 'แ€œแ€ญแ€ฐแผแ€บแ€žแ€ญแ€•แ€บแ€ธแ€ขแ€ญแ€แ€บแ€ธแ€žแ€ฝแ€„แ€บ', 'แ€œแ€ญแ€ฐแผแ€บแธแ€ตแ€„แ€บ'], + 'weekdays' => ['แ€แผแ€บแ€ธแ€ขแ‚ƒแ€ธแ€แ€ญแ€แ€บแ‚‰', 'แ€แผแ€บแ€ธแธแผแ€บ', 'แ€แผแ€บแ€ธโ€‹แ€ขแขแ€„แ€บแ€ธโ€‹แตแขแผแ€บแ€ธ', 'แ€แผแ€บแ€ธแ€•แ€ฐแ€แ€บแ‚‰', 'แ€แผแ€บแ€ธแฝแ€แ€บแ€ธ', 'แ€แผแ€บแ€ธแ€žแ€ฏแตแ€บแ€ธ', 'แ€แผแ€บแ€ธแ€žแ€แ€บ'], + 'weekdays_short' => ['แ€แ€ญแ€แ€ทแ€บ', 'แธแผแ€บ', 'แตแขแผแ€บแ€ธ', 'แ€•แ€ฏแ€แ€ทแ€บ', 'แฝแ€แ€บแ€ธ', 'แ€žแ€ฏแตแ€บแ€ธ', 'แ€žแ€แ€บ'], + 'weekdays_min' => ['แ€แ€ญแ€แ€ทแ€บ', 'แธแผแ€บ', 'แตแขแผแ€บแ€ธ', 'แ€•แ€ฏแ€แ€ทแ€บ', 'แฝแ€แ€บแ€ธ', 'แ€žแ€ฏแตแ€บแ€ธ', 'แ€žแ€แ€บ'], + 'alt_numbers' => ['แ‚แ‚', 'แ‚แ‚‘', 'แ‚แ‚’', 'แ‚แ‚“', 'แ‚แ‚”', 'แ‚แ‚•', 'แ‚แ‚–', 'แ‚แ‚—', 'แ‚แ‚˜', 'แ‚แ‚™', 'แ‚‘แ‚', 'แ‚‘แ‚‘', 'แ‚‘แ‚’', 'แ‚‘แ‚“', 'แ‚‘แ‚”', 'แ‚‘แ‚•', 'แ‚‘แ‚–', 'แ‚‘แ‚—', 'แ‚‘แ‚˜', 'แ‚‘แ‚™', 'แ‚’แ‚', 'แ‚’แ‚‘', 'แ‚’แ‚’', 'แ‚’แ‚“', 'แ‚’แ‚”', 'แ‚’แ‚•', 'แ‚’แ‚–', 'แ‚’แ‚—', 'แ‚’แ‚˜', 'แ‚’แ‚™', 'แ‚“แ‚', 'แ‚“แ‚‘', 'แ‚“แ‚’', 'แ‚“แ‚“', 'แ‚“แ‚”', 'แ‚“แ‚•', 'แ‚“แ‚–', 'แ‚“แ‚—', 'แ‚“แ‚˜', 'แ‚“แ‚™', 'แ‚”แ‚', 'แ‚”แ‚‘', 'แ‚”แ‚’', 'แ‚”แ‚“', 'แ‚”แ‚”', 'แ‚”แ‚•', 'แ‚”แ‚–', 'แ‚”แ‚—', 'แ‚”แ‚˜', 'แ‚”แ‚™', 'แ‚•แ‚', 'แ‚•แ‚‘', 'แ‚•แ‚’', 'แ‚•แ‚“', 'แ‚•แ‚”', 'แ‚•แ‚•', 'แ‚•แ‚–', 'แ‚•แ‚—', 'แ‚•แ‚˜', 'แ‚•แ‚™', 'แ‚–แ‚', 'แ‚–แ‚‘', 'แ‚–แ‚’', 'แ‚–แ‚“', 'แ‚–แ‚”', 'แ‚–แ‚•', 'แ‚–แ‚–', 'แ‚–แ‚—', 'แ‚–แ‚˜', 'แ‚–แ‚™', 'แ‚—แ‚', 'แ‚—แ‚‘', 'แ‚—แ‚’', 'แ‚—แ‚“', 'แ‚—แ‚”', 'แ‚—แ‚•', 'แ‚—แ‚–', 'แ‚—แ‚—', 'แ‚—แ‚˜', 'แ‚—แ‚™', 'แ‚˜แ‚', 'แ‚˜แ‚‘', 'แ‚˜แ‚’', 'แ‚˜แ‚“', 'แ‚˜แ‚”', 'แ‚˜แ‚•', 'แ‚˜แ‚–', 'แ‚˜แ‚—', 'แ‚˜แ‚˜', 'แ‚˜แ‚™', 'แ‚™แ‚', 'แ‚™แ‚‘', 'แ‚™แ‚’', 'แ‚™แ‚“', 'แ‚™แ‚”', 'แ‚™แ‚•', 'แ‚™แ‚–', 'แ‚™แ‚—', 'แ‚™แ‚˜', 'แ‚™แ‚™'], + 'meridiem' => ['แตแขแ€„แ€บแผแ‚‚แ€บ', 'แ€แขแ€™แ€บแ€ธแถแ€™แ€บแ‚ˆ'], + + 'month' => ':count แ€œแ€ญแ€ฐแผแ€บ', // less reliable + 'm' => ':count แ€œแ€ญแ€ฐแผแ€บ', // less reliable + 'a_month' => ':count แ€œแ€ญแ€ฐแผแ€บ', // less reliable + + 'week' => ':count แ€แผแ€บแ€ธ', // less reliable + 'w' => ':count แ€แผแ€บแ€ธ', // less reliable + 'a_week' => ':count แ€แผแ€บแ€ธ', // less reliable + + 'hour' => ':count เบ•เบต', // less reliable + 'h' => ':count เบ•เบต', // less reliable + 'a_hour' => ':count เบ•เบต', // less reliable + + 'minute' => ':count เป€เบ”เบฑเบ', // less reliable + 'min' => ':count เป€เบ”เบฑเบ', // less reliable + 'a_minute' => ':count เป€เบ”เบฑเบ', // less reliable + + 'second' => ':count แ€ขแ€ญแ€ฏแผแ€บแ‚‡', // less reliable + 's' => ':count แ€ขแ€ญแ€ฏแผแ€บแ‚‡', // less reliable + 'a_second' => ':count แ€ขแ€ญแ€ฏแผแ€บแ‚‡', // less reliable + + 'year' => ':count แ€•แ€ฎ', + 'y' => ':count แ€•แ€ฎ', + 'a_year' => ':count แ€•แ€ฎ', + + 'day' => ':count เบเบฒเบ‡เบงเบฑเบ™', + 'd' => ':count เบเบฒเบ‡เบงเบฑเบ™', + 'a_day' => ':count เบเบฒเบ‡เบงเบฑเบ™', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shs.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shs.php new file mode 100644 index 0000000000..8d2e1d7d58 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shs.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/shs_CA.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shs_CA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shs_CA.php new file mode 100644 index 0000000000..08d385e660 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shs_CA.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Neskie Manuel bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Pellkwetฬ“min', 'Pelctsipwenฬ“ten', 'Pellsqรฉpts', 'Pesllรฉwten', 'Pell7ell7รฉ7llqten', 'Pelltspรฉntsk', 'Pelltqwelqฬ“wรฉlฬ“t', 'Pellctฬ“รฉxelฬ“cten', 'Pesqelqlรฉlten', 'Pesllwรฉlsten', 'Pellc7ell7รฉ7llcwtenฬ“', 'Pelltetรฉtqฬ“em'], + 'months_short' => ['Kwe', 'Tsi', 'Sqe', 'ร‰wt', 'Ell', 'Tsp', 'Tqw', 'Ctฬ“รฉ', 'Qel', 'Wรฉl', 'U7l', 'Tet'], + 'weekdays' => ['Sxetspesqฬ“t', 'Spetkesqฬ“t', 'Selesqฬ“t', 'Skellesqฬ“t', 'Smesesqฬ“t', 'Stselkstesqฬ“t', 'Stqmekstesqฬ“t'], + 'weekdays_short' => ['Sxe', 'Spe', 'Sel', 'Ske', 'Sme', 'Sts', 'Stq'], + 'weekdays_min' => ['Sxe', 'Spe', 'Sel', 'Ske', 'Sme', 'Sts', 'Stq'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count sqlรฉlten', // less reliable + 'y' => ':count sqlรฉlten', // less reliable + 'a_year' => ':count sqlรฉlten', // less reliable + + 'month' => ':count swewll', // less reliable + 'm' => ':count swewll', // less reliable + 'a_month' => ':count swewll', // less reliable + + 'hour' => ':count seqwlรบt', // less reliable + 'h' => ':count seqwlรบt', // less reliable + 'a_hour' => ':count seqwlรบt', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/si.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/si.php new file mode 100644 index 0000000000..636bf69146 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/si.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Serhan Apaydฤฑn + * - JD Isaacks + * - Malinda Weerasinghe (MalindaWMD) + */ +return [ + 'year' => '{1}เท€เทƒเถป 1|เท€เทƒเถป :count', + 'a_year' => '{1}เท€เทƒเถปเถšเทŠ|เท€เทƒเถป :count', + 'month' => '{1}เถธเทเทƒ 1|เถธเทเทƒ :count', + 'a_month' => '{1}เถธเทเทƒเถบ|เถธเทเทƒ :count', + 'week' => '{1}เทƒเถญเท’ 1|เทƒเถญเท’ :count', + 'a_week' => '{1}เทƒเถญเท’เถบเถšเทŠ|เทƒเถญเท’ :count', + 'day' => '{1}เถฏเท’เถฑ 1|เถฏเท’เถฑ :count', + 'a_day' => '{1}เถฏเท’เถฑเถšเทŠ|เถฏเท’เถฑ :count', + 'hour' => '{1}เถดเทเถบ 1|เถดเทเถบ :count', + 'a_hour' => '{1}เถดเทเถบเถšเทŠ|เถดเทเถบ :count', + 'minute' => '{1}เถธเท’เถฑเท’เถญเทŠเถญเท” 1|เถธเท’เถฑเท’เถญเทŠเถญเท” :count', + 'a_minute' => '{1}เถธเท’เถฑเท’เถญเทŠเถญเท”เท€เถšเทŠ|เถธเท’เถฑเท’เถญเทŠเถญเท” :count', + 'second' => '{1}เถญเถญเทŠเถดเถป 1|เถญเถญเทŠเถดเถป :count', + 'a_second' => '{1}เถญเถญเทŠเถดเถป เถšเท’เท„เท’เถดเถบเถšเถง|เถญเถญเทŠเถดเถป :count', + 'ago' => ':time เถšเถง เถดเท™เถป', + 'from_now' => function ($time) { + if (preg_match('/เถฏเท’เถฑ \d/u', $time)) { + return $time.' เถฑเทŠ'; + } + + return $time.' เถšเท’เถฑเทŠ'; + }, + 'before' => ':time เถšเถง เถดเท™เถป', + 'after' => function ($time) { + if (preg_match('/เถฏเท’เถฑ \d/u', $time)) { + return $time.' เถฑเทŠ'; + } + + return $time.' เถšเท’เถฑเทŠ'; + }, + 'diff_now' => 'เถฏเทเถฑเทŠ', + 'diff_today' => 'เถ…เถฏ', + 'diff_yesterday' => 'เถŠเถบเทš', + 'diff_tomorrow' => 'เท„เท™เถง', + 'formats' => [ + 'LT' => 'a h:mm', + 'LTS' => 'a h:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY MMMM D', + 'LLL' => 'YYYY MMMM D, a h:mm', + 'LLLL' => 'YYYY MMMM D [เท€เทเถฑเท’] dddd, a h:mm:ss', + ], + 'calendar' => [ + 'sameDay' => '[เถ…เถฏ] LT[เถง]', + 'nextDay' => '[เท„เท™เถง] LT[เถง]', + 'nextWeek' => 'dddd LT[เถง]', + 'lastDay' => '[เถŠเถบเทš] LT[เถง]', + 'lastWeek' => '[เถดเทƒเท”เถœเท’เถบ] dddd LT[เถง]', + 'sameElse' => 'L', + ], + 'ordinal' => ':number เท€เทเถฑเท’', + 'meridiem' => ['เถดเท™เถป เท€เถปเท”', 'เถดเทƒเทŠ เท€เถปเท”', 'เถดเท™.เท€.', 'เถด.เท€.'], + 'months' => ['เถขเถฑเท€เทเถปเท’', 'เถดเท™เถถเถปเท€เทเถปเท’', 'เถธเทเถปเทŠเถญเท”', 'เถ…เถดเทŠโ€เถปเทšเถฝเทŠ', 'เถธเทเถบเท’', 'เถขเท–เถฑเท’', 'เถขเท–เถฝเท’', 'เถ…เถœเทเทƒเทŠเถญเท”', 'เทƒเทเถดเทŠเถญเทเถธเทŠเถถเถปเทŠ', 'เถ”เถšเทŠเถญเทเถถเถปเทŠ', 'เถฑเทœเท€เทเถธเทŠเถถเถปเทŠ', 'เถฏเท™เทƒเทเถธเทŠเถถเถปเทŠ'], + 'months_short' => ['เถขเถฑ', 'เถดเท™เถถ', 'เถธเทเถปเทŠ', 'เถ…เถดเทŠ', 'เถธเทเถบเท’', 'เถขเท–เถฑเท’', 'เถขเท–เถฝเท’', 'เถ…เถœเท', 'เทƒเทเถดเทŠ', 'เถ”เถšเทŠ', 'เถฑเทœเท€เท', 'เถฏเท™เทƒเท'], + 'weekdays' => ['เถ‰เถปเท’เถฏเท', 'เทƒเถณเท”เถฏเท', 'เถ…เถŸเท„เถปเท”เท€เทเถฏเท', 'เถถเถฏเทเถฏเท', 'เถถเทŠโ€เถปเท„เทƒเทŠเถดเถญเท’เถฑเทŠเถฏเท', 'เทƒเท’เถšเท”เถปเทเถฏเท', 'เทƒเท™เถฑเทƒเท”เถปเทเถฏเท'], + 'weekdays_short' => ['เถ‰เถปเท’', 'เทƒเถณเท”', 'เถ…เถŸ', 'เถถเถฏเท', 'เถถเทŠโ€เถปเท„', 'เทƒเท’เถšเท”', 'เทƒเท™เถฑ'], + 'weekdays_min' => ['เถ‰', 'เทƒ', 'เถ…', 'เถถ', 'เถถเทŠโ€เถป', 'เทƒเท’', 'เทƒเท™'], + 'first_day_of_week' => 1, +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/si_LK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/si_LK.php new file mode 100644 index 0000000000..81c44e0e14 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/si_LK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/si.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sid.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sid.php new file mode 100644 index 0000000000..b1c6521830 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sid.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sid_ET.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sid_ET.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sid_ET.php new file mode 100644 index 0000000000..1296f9be70 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sid_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Sambata', 'Sanyo', 'Maakisanyo', 'Roowe', 'Hamuse', 'Arbe', 'Qidaame'], + 'weekdays_short' => ['Sam', 'San', 'Mak', 'Row', 'Ham', 'Arb', 'Qid'], + 'weekdays_min' => ['Sam', 'San', 'Mak', 'Row', 'Ham', 'Arb', 'Qid'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['soodo', 'hawwaro'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sk.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sk.php new file mode 100644 index 0000000000..f9702e960c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sk.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Martin Suja + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Ivan Stana + * - Akira Matsuda + * - Christopher Dell + * - James McKinney + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Jozef Fulop + * - Nicolรกs Hock Isaza + * - Tom Hughes + * - Simon Hรผrlimann (CyT) + * - jofi + * - Jakub ADAMEC + * - Marek Adamickรฝ + * - AlterwebStudio + */ + +use Carbon\CarbonInterface; + +$fromNow = function ($time) { + return 'o '.strtr($time, [ + 'hodina' => 'hodinu', + 'minรบta' => 'minรบtu', + 'sekunda' => 'sekundu', + ]); +}; + +$ago = function ($time) { + $replacements = [ + '/\bhodina\b/' => 'hodinou', + '/\bminรบta\b/' => 'minรบtou', + '/\bsekunda\b/' => 'sekundou', + '/\bdeลˆ\b/u' => 'dลˆom', + '/\btรฝลพdeลˆ\b/u' => 'tรฝลพdลˆom', + '/\bmesiac\b/' => 'mesiacom', + '/\brok\b/' => 'rokom', + ]; + + $replacementsPlural = [ + '/\bhodiny\b/' => 'hodinami', + '/\bminรบty\b/' => 'minรบtami', + '/\bsekundy\b/' => 'sekundami', + '/\bdni\b/' => 'dลˆami', + '/\btรฝลพdne\b/' => 'tรฝลพdลˆami', + '/\bmesiace\b/' => 'mesiacmi', + '/\broky\b/' => 'rokmi', + ]; + + foreach ($replacements + $replacementsPlural as $pattern => $replacement) { + $time = preg_replace($pattern, $replacement, $time); + } + + return "pred $time"; +}; + +return [ + 'year' => ':count rok|:count roky|:count rokov', + 'a_year' => 'rok|:count roky|:count rokov', + 'y' => ':count r', + 'month' => ':count mesiac|:count mesiace|:count mesiacov', + 'a_month' => 'mesiac|:count mesiace|:count mesiacov', + 'm' => ':count m', + 'week' => ':count tรฝลพdeลˆ|:count tรฝลพdne|:count tรฝลพdลˆov', + 'a_week' => 'tรฝลพdeลˆ|:count tรฝลพdne|:count tรฝลพdลˆov', + 'w' => ':count t', + 'day' => ':count deลˆ|:count dni|:count dnรญ', + 'a_day' => 'deลˆ|:count dni|:count dnรญ', + 'd' => ':count d', + 'hour' => ':count hodina|:count hodiny|:count hodรญn', + 'a_hour' => 'hodina|:count hodiny|:count hodรญn', + 'h' => ':count h', + 'minute' => ':count minรบta|:count minรบty|:count minรบt', + 'a_minute' => 'minรบta|:count minรบty|:count minรบt', + 'min' => ':count min', + 'second' => ':count sekunda|:count sekundy|:count sekรบnd', + 'a_second' => 'sekunda|:count sekundy|:count sekรบnd', + 's' => ':count s', + 'millisecond' => ':count milisekunda|:count milisekundy|:count milisekรบnd', + 'a_millisecond' => 'milisekunda|:count milisekundy|:count milisekรบnd', + 'ms' => ':count ms', + 'microsecond' => ':count mikrosekunda|:count mikrosekundy|:count mikrosekรบnd', + 'a_microsecond' => 'mikrosekunda|:count mikrosekundy|:count mikrosekรบnd', + 'ยตs' => ':count ยตs', + + 'ago' => $ago, + 'from_now' => $fromNow, + 'before' => ':time pred', + 'after' => ':time po', + + 'hour_after' => ':count hodinu|:count hodiny|:count hodรญn', + 'minute_after' => ':count minรบtu|:count minรบty|:count minรบt', + 'second_after' => ':count sekundu|:count sekundy|:count sekรบnd', + + 'hour_before' => ':count hodinu|:count hodiny|:count hodรญn', + 'minute_before' => ':count minรบtu|:count minรบty|:count minรบt', + 'second_before' => ':count sekundu|:count sekundy|:count sekรบnd', + + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' a '], + 'diff_now' => 'teraz', + 'diff_yesterday' => 'vฤera', + 'diff_tomorrow' => 'zajtra', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'DD. MMMM YYYY', + 'LLL' => 'D. M. HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[dnes o] LT', + 'nextDay' => '[zajtra o] LT', + 'lastDay' => '[vฤera o] LT', + 'nextWeek' => 'dddd [o] LT', + 'lastWeek' => static function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 1: + case 2: + case 4: + case 5: + return '[minulรฝ] dddd [o] LT'; //pondelok/utorok/ลกtvrtok/piatok + default: + return '[minulรก] dddd [o] LT'; + } + }, + 'sameElse' => 'L', + ], + 'weekdays' => ['nedeฤพa', 'pondelok', 'utorok', 'streda', 'ลกtvrtok', 'piatok', 'sobota'], + 'weekdays_short' => ['ned', 'pon', 'uto', 'str', 'ลกtv', 'pia', 'sob'], + 'weekdays_min' => ['ne', 'po', 'ut', 'st', 'ลกt', 'pi', 'so'], + 'months' => ['januรกr', 'februรกr', 'marec', 'aprรญl', 'mรกj', 'jรบn', 'jรบl', 'august', 'september', 'oktรณber', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mรกj', 'jรบn', 'jรบl', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'meridiem' => ['dopoludnia', 'popoludnรญ'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sk_SK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sk_SK.php new file mode 100644 index 0000000000..0515601a1a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sk_SK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sk.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sl.php new file mode 100644 index 0000000000..1f1d1b3381 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sl.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolรกs Hock Isaza + * - Miha Rebernik + * - Gal Jakiฤ (morpheus7CS) + * - Glaviฤ‡ + * - Anลพe ฤŒasar + * - Lovro Tramลกek (Lovro1107) + * - burut13 + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count leto|:count leti|:count leta|:count let', + 'y' => ':count leto|:count leti|:count leta|:count let', + 'month' => ':count mesec|:count meseca|:count mesece|:count mesecev', + 'm' => ':count mes.', + 'week' => ':count teden|:count tedna|:count tedne|:count tednov', + 'w' => ':count ted.', + 'day' => ':count dan|:count dni|:count dni|:count dni', + 'd' => ':count dan|:count dni|:count dni|:count dni', + 'hour' => ':count ura|:count uri|:count ure|:count ur', + 'h' => ':count h', + 'minute' => ':count minuta|:count minuti|:count minute|:count minut', + 'min' => ':count min.', + 'second' => ':count sekunda|:count sekundi|:count sekunde|:count sekund', + 'a_second' => '{1}nekaj sekund|:count sekunda|:count sekundi|:count sekunde|:count sekund', + 's' => ':count s', + + 'year_ago' => ':count letom|:count letoma|:count leti|:count leti', + 'y_ago' => ':count letom|:count letoma|:count leti|:count leti', + 'month_ago' => ':count mesecem|:count mesecema|:count meseci|:count meseci', + 'week_ago' => ':count tednom|:count tednoma|:count tedni|:count tedni', + 'day_ago' => ':count dnem|:count dnevoma|:count dnevi|:count dnevi', + 'd_ago' => ':count dnem|:count dnevoma|:count dnevi|:count dnevi', + 'hour_ago' => ':count uro|:count urama|:count urami|:count urami', + 'minute_ago' => ':count minuto|:count minutama|:count minutami|:count minutami', + 'second_ago' => ':count sekundo|:count sekundama|:count sekundami|:count sekundami', + + 'day_from_now' => ':count dan|:count dneva|:count dni|:count dni', + 'd_from_now' => ':count dan|:count dneva|:count dni|:count dni', + 'hour_from_now' => ':count uro|:count uri|:count ure|:count ur', + 'minute_from_now' => ':count minuto|:count minuti|:count minute|:count minut', + 'second_from_now' => ':count sekundo|:count sekundi|:count sekunde|:count sekund', + + 'ago' => 'pred :time', + 'from_now' => 'ฤez :time', + 'after' => ':time kasneje', + 'before' => ':time prej', + + 'diff_now' => 'ravnokar', + 'diff_today' => 'danes', + 'diff_today_regexp' => 'danes(?:\\s+ob)?', + 'diff_yesterday' => 'vฤeraj', + 'diff_yesterday_regexp' => 'vฤeraj(?:\\s+ob)?', + 'diff_tomorrow' => 'jutri', + 'diff_tomorrow_regexp' => 'jutri(?:\\s+ob)?', + 'diff_before_yesterday' => 'predvฤerajลกnjim', + 'diff_after_tomorrow' => 'pojutriลกnjem', + + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'period_start_date' => 'od :date', + 'period_end_date' => 'do :date', + + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danes ob] LT', + 'nextDay' => '[jutri ob] LT', + 'nextWeek' => 'dddd [ob] LT', + 'lastDay' => '[vฤeraj ob] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[preteklo] [nedeljo] [ob] LT'; + case 1: + return '[pretekli] [ponedeljek] [ob] LT'; + case 2: + return '[pretekli] [torek] [ob] LT'; + case 3: + return '[preteklo] [sredo] [ob] LT'; + case 4: + return '[pretekli] [ฤetrtek] [ob] LT'; + case 5: + return '[pretekli] [petek] [ob] LT'; + case 6: + return '[preteklo] [soboto] [ob] LT'; + } + }, + 'sameElse' => 'L', + ], + 'months' => ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['nedelja', 'ponedeljek', 'torek', 'sreda', 'ฤetrtek', 'petek', 'sobota'], + 'weekdays_short' => ['ned', 'pon', 'tor', 'sre', 'ฤet', 'pet', 'sob'], + 'weekdays_min' => ['ne', 'po', 'to', 'sr', 'ฤe', 'pe', 'so'], + 'list' => [', ', ' in '], + 'meridiem' => ['dopoldan', 'popoldan'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sl_SI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sl_SI.php new file mode 100644 index 0000000000..5dad8c8160 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sl_SI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sl.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sm.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sm.php new file mode 100644 index 0000000000..e8c118acfc --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sm.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sm_WS.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sm_WS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sm_WS.php new file mode 100644 index 0000000000..f066068715 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sm_WS.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Ianuari', 'Fepuari', 'Mati', 'Aperila', 'Me', 'Iuni', 'Iulai', 'Auguso', 'Setema', 'Oketopa', 'Novema', 'Tesema'], + 'months_short' => ['Ian', 'Fep', 'Mat', 'Ape', 'Me', 'Iun', 'Iul', 'Aug', 'Set', 'Oke', 'Nov', 'Tes'], + 'weekdays' => ['Aso Sa', 'Aso Gafua', 'Aso Lua', 'Aso Lulu', 'Aso Tofi', 'Aso Farail', 'Aso To\'ana\'i'], + 'weekdays_short' => ['Aso Sa', 'Aso Gaf', 'Aso Lua', 'Aso Lul', 'Aso Tof', 'Aso Far', 'Aso To\''], + 'weekdays_min' => ['Aso Sa', 'Aso Gaf', 'Aso Lua', 'Aso Lul', 'Aso Tof', 'Aso Far', 'Aso To\''], + + 'hour' => ':count uati', // less reliable + 'h' => ':count uati', // less reliable + 'a_hour' => ':count uati', // less reliable + + 'minute' => ':count itiiti', // less reliable + 'min' => ':count itiiti', // less reliable + 'a_minute' => ':count itiiti', // less reliable + + 'second' => ':count lua', // less reliable + 's' => ':count lua', // less reliable + 'a_second' => ':count lua', // less reliable + + 'year' => ':count tausaga', + 'y' => ':count tausaga', + 'a_year' => ':count tausaga', + + 'month' => ':count mฤsina', + 'm' => ':count mฤsina', + 'a_month' => ':count mฤsina', + + 'week' => ':count vaiaso', + 'w' => ':count vaiaso', + 'a_week' => ':count vaiaso', + + 'day' => ':count aso', + 'd' => ':count aso', + 'a_day' => ':count aso', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/smn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/smn.php new file mode 100644 index 0000000000..20add02395 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/smn.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ip.', 'ep.'], + 'weekdays' => ['pasepeeivi', 'vuossaargรข', 'majebaargรข', 'koskoho', 'tuorรขstuv', 'vรกstuppeeivi', 'lรกvurduv'], + 'weekdays_short' => ['pas', 'vuo', 'maj', 'kos', 'tuo', 'vรกs', 'lรกv'], + 'weekdays_min' => ['pa', 'vu', 'ma', 'ko', 'tu', 'vรก', 'lรก'], + 'weekdays_standalone' => ['pasepeivi', 'vuossargรข', 'majebargรข', 'koskokko', 'tuorรขstรขh', 'vรกstuppeivi', 'lรกvurdรขh'], + 'months' => ['uฤ‘ฤ‘รขivemรกรกnu', 'kuovรขmรกรกnu', 'njuhฤรขmรกรกnu', 'cuรกล‹uimรกรกnu', 'vyesimรกรกnu', 'kesimรกรกnu', 'syeinimรกรกnu', 'porgemรกรกnu', 'ฤohฤรขmรกรกnu', 'roovvรขdmรกรกnu', 'skammรขmรกรกnu', 'juovlรขmรกรกnu'], + 'months_short' => ['uฤ‘iv', 'kuovรข', 'njuhฤรข', 'cuรกล‹ui', 'vyesi', 'kesi', 'syeini', 'porge', 'ฤohฤรข', 'roovvรขd', 'skammรข', 'juovlรข'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'H.mm', + 'LTS' => 'H.mm.ss', + 'L' => 'D.M.YYYY', + 'LL' => 'MMM D. YYYY', + 'LLL' => 'MMMM D. YYYY H.mm', + 'LLLL' => 'dddd, MMMM D. YYYY H.mm', + ], + + 'hour' => ':count รคigi', // less reliable + 'h' => ':count รคigi', // less reliable + 'a_hour' => ':count รคigi', // less reliable + + 'year' => ':count ihe', + 'y' => ':count ihe', + 'a_year' => ':count ihe', + + 'month' => ':count mรกnuppaje', + 'm' => ':count mรกnuppaje', + 'a_month' => ':count mรกnuppaje', + + 'week' => ':count okko', + 'w' => ':count okko', + 'a_week' => ':count okko', + + 'day' => ':count peivi', + 'd' => ':count peivi', + 'a_day' => ':count peivi', + + 'minute' => ':count miinut', + 'min' => ':count miinut', + 'a_minute' => ':count miinut', + + 'second' => ':count nubbe', + 's' => ':count nubbe', + 'a_second' => ':count nubbe', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sn.php new file mode 100644 index 0000000000..4f25028a3a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sn.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['a', 'p'], + 'weekdays' => ['Svondo', 'Muvhuro', 'Chipiri', 'Chitatu', 'China', 'Chishanu', 'Mugovera'], + 'weekdays_short' => ['Svo', 'Muv', 'Chp', 'Cht', 'Chn', 'Chs', 'Mug'], + 'weekdays_min' => ['Sv', 'Mu', 'Cp', 'Ct', 'Cn', 'Cs', 'Mg'], + 'months' => ['Ndira', 'Kukadzi', 'Kurume', 'Kubvumbi', 'Chivabvu', 'Chikumi', 'Chikunguru', 'Nyamavhuvhu', 'Gunyana', 'Gumiguru', 'Mbudzi', 'Zvita'], + 'months_short' => ['Ndi', 'Kuk', 'Kur', 'Kub', 'Chv', 'Chk', 'Chg', 'Nya', 'Gun', 'Gum', 'Mbu', 'Zvi'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => 'makore :count', + 'y' => 'makore :count', + 'a_year' => 'makore :count', + + 'month' => 'mwedzi :count', + 'm' => 'mwedzi :count', + 'a_month' => 'mwedzi :count', + + 'week' => 'vhiki :count', + 'w' => 'vhiki :count', + 'a_week' => 'vhiki :count', + + 'day' => 'mazuva :count', + 'd' => 'mazuva :count', + 'a_day' => 'mazuva :count', + + 'hour' => 'maawa :count', + 'h' => 'maawa :count', + 'a_hour' => 'maawa :count', + + 'minute' => 'minitsi :count', + 'min' => 'minitsi :count', + 'a_minute' => 'minitsi :count', + + 'second' => 'sekonzi :count', + 's' => 'sekonzi :count', + 'a_second' => 'sekonzi :count', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so.php new file mode 100644 index 0000000000..5785271977 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Author: + * - Abdifatah Abdilahi(@abdifatahz) + */ +return [ + 'year' => ':count sanad|:count sanadood', + 'a_year' => 'sanad|:count sanadood', + 'y' => '{1}:countsn|{0}:countsns|]1,Inf[:countsn', + 'month' => ':count bil|:count bilood', + 'a_month' => 'bil|:count bilood', + 'm' => ':countbil', + 'week' => ':count isbuuc', + 'a_week' => 'isbuuc|:count isbuuc', + 'w' => ':countis', + 'day' => ':count maalin|:count maalmood', + 'a_day' => 'maalin|:count maalmood', + 'd' => ':countml', + 'hour' => ':count saac', + 'a_hour' => 'saacad|:count saac', + 'h' => ':countsc', + 'minute' => ':count daqiiqo', + 'a_minute' => 'daqiiqo|:count daqiiqo', + 'min' => ':countdq', + 'second' => ':count ilbidhiqsi', + 'a_second' => 'xooga ilbidhiqsiyo|:count ilbidhiqsi', + 's' => ':countil', + 'ago' => ':time kahor', + 'from_now' => ':time gudahood', + 'after' => ':time kedib', + 'before' => ':time kahor', + 'diff_now' => 'hada', + 'diff_today' => 'maanta', + 'diff_today_regexp' => 'maanta(?:\s+markay\s+(?:tahay|ahayd))?', + 'diff_yesterday' => 'shalayto', + 'diff_yesterday_regexp' => 'shalayto(?:\s+markay\s+ahayd)?', + 'diff_tomorrow' => 'beri', + 'diff_tomorrow_regexp' => 'beri(?:\s+markay\s+tahay)?', + 'diff_before_yesterday' => 'doraato', + 'diff_after_tomorrow' => 'saadanbe', + 'period_recurrences' => 'mar|:count jeer', + 'period_interval' => ':interval kasta', + 'period_start_date' => 'laga bilaabo :date', + 'period_end_date' => 'ilaa :date', + 'months' => ['Janaayo', 'Febraayo', 'Abriil', 'Maajo', 'Juun', 'Luuliyo', 'Agoosto', 'Sebteembar', 'Oktoobar', 'Nofeembar', 'Diseembar'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Abr', 'Mjo', 'Jun', 'Lyo', 'Agt', 'Seb', 'Okt', 'Nof', 'Dis'], + 'weekdays' => ['Axad', 'Isniin', 'Talaada', 'Arbaca', 'Khamiis', 'Jimce', 'Sabti'], + 'weekdays_short' => ['Axd', 'Isn', 'Tal', 'Arb', 'Kha', 'Jim', 'Sbt'], + 'weekdays_min' => ['Ax', 'Is', 'Ta', 'Ar', 'Kh', 'Ji', 'Sa'], + 'list' => [', ', ' and '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'calendar' => [ + 'sameDay' => '[Maanta markay tahay] LT', + 'nextDay' => '[Beri markay tahay] LT', + 'nextWeek' => 'dddd [markay tahay] LT', + 'lastDay' => '[Shalay markay ahayd] LT', + 'lastWeek' => '[Hore] dddd [Markay ahayd] LT', + 'sameElse' => 'L', + ], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so_DJ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so_DJ.php new file mode 100644 index 0000000000..273dda8dec --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so_DJ.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/so.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so_ET.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so_ET.php new file mode 100644 index 0000000000..7b699715b9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so_ET.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return require __DIR__.'/so.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so_KE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so_KE.php new file mode 100644 index 0000000000..7b699715b9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so_KE.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return require __DIR__.'/so.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so_SO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so_SO.php new file mode 100644 index 0000000000..7b699715b9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so_SO.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return require __DIR__.'/so.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sq.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sq.php new file mode 100644 index 0000000000..ffa592ecf9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sq.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - JD Isaacks + * - Fadion Dashi + */ +return [ + 'year' => ':count vit|:count vjet', + 'a_year' => 'njรซ vit|:count vite', + 'y' => ':count v.', + 'month' => ':count muaj', + 'a_month' => 'njรซ muaj|:count muaj', + 'm' => ':count muaj', + 'week' => ':count javรซ', + 'a_week' => ':count javรซ|:count javรซ', + 'w' => ':count j.', + 'day' => ':count ditรซ', + 'a_day' => 'njรซ ditรซ|:count ditรซ', + 'd' => ':count d.', + 'hour' => ':count orรซ', + 'a_hour' => 'njรซ orรซ|:count orรซ', + 'h' => ':count o.', + 'minute' => ':count minutรซ|:count minuta', + 'a_minute' => 'njรซ minutรซ|:count minuta', + 'min' => ':count min.', + 'second' => ':count sekondรซ|:count sekonda', + 'a_second' => 'disa sekonda|:count sekonda', + 's' => ':count s.', + 'ago' => ':time mรซ parรซ', + 'from_now' => 'nรซ :time', + 'after' => ':time pas', + 'before' => ':time para', + 'diff_now' => 'tani', + 'diff_today' => 'Sot', + 'diff_today_regexp' => 'Sot(?:\\s+nรซ)?', + 'diff_yesterday' => 'dje', + 'diff_yesterday_regexp' => 'Dje(?:\\s+nรซ)?', + 'diff_tomorrow' => 'nesรซr', + 'diff_tomorrow_regexp' => 'Nesรซr(?:\\s+nรซ)?', + 'diff_before_yesterday' => 'pardje', + 'diff_after_tomorrow' => 'pasnesรซr', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Sot nรซ] LT', + 'nextDay' => '[Nesรซr nรซ] LT', + 'nextWeek' => 'dddd [nรซ] LT', + 'lastDay' => '[Dje nรซ] LT', + 'lastWeek' => 'dddd [e kaluar nรซ] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'meridiem' => ['PD', 'MD'], + 'months' => ['janar', 'shkurt', 'mars', 'prill', 'maj', 'qershor', 'korrik', 'gusht', 'shtator', 'tetor', 'nรซntor', 'dhjetor'], + 'months_short' => ['jan', 'shk', 'mar', 'pri', 'maj', 'qer', 'kor', 'gus', 'sht', 'tet', 'nรซn', 'dhj'], + 'weekdays' => ['e diel', 'e hรซnรซ', 'e martรซ', 'e mรซrkurรซ', 'e enjte', 'e premte', 'e shtunรซ'], + 'weekdays_short' => ['die', 'hรซn', 'mar', 'mรซr', 'enj', 'pre', 'sht'], + 'weekdays_min' => ['d', 'h', 'ma', 'mรซ', 'e', 'p', 'sh'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' dhe '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sq_AL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sq_AL.php new file mode 100644 index 0000000000..ea5df3f26f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sq_AL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sq.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sq_MK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sq_MK.php new file mode 100644 index 0000000000..62f752c44b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sq_MK.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sq.php', [ + 'formats' => [ + 'L' => 'D.M.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sq_XK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sq_XK.php new file mode 100644 index 0000000000..62f752c44b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sq_XK.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sq.php', [ + 'formats' => [ + 'L' => 'D.M.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr.php new file mode 100644 index 0000000000..68ba663aa0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Franรงois B + * - shaishavgandhi05 + * - Serhan Apaydฤฑn + * - JD Isaacks + * - Glaviฤ‡ + * - Milos Sakovic + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count godina|:count godine|:count godina', + 'y' => ':count g.', + 'month' => ':count mesec|:count meseca|:count meseci', + 'm' => ':count mj.', + 'week' => ':count nedelja|:count nedelje|:count nedelja', + 'w' => ':count ned.', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count d.', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count ฤ.', + 'minute' => ':count minut|:count minuta|:count minuta', + 'min' => ':count min.', + 'second' => ':count sekundu|:count sekunde|:count sekundi', + 's' => ':count sek.', + 'ago' => 'pre :time', + 'from_now' => 'za :time', + 'after' => 'nakon :time', + 'before' => 'pre :time', + + 'year_from_now' => ':count godinu|:count godine|:count godina', + 'year_ago' => ':count godinu|:count godine|:count godina', + 'week_from_now' => ':count nedelju|:count nedelje|:count nedelja', + 'week_ago' => ':count nedelju|:count nedelje|:count nedelja', + + 'diff_now' => 'upravo sada', + 'diff_today' => 'danas', + 'diff_today_regexp' => 'danas(?:\\s+u)?', + 'diff_yesterday' => 'juฤe', + 'diff_yesterday_regexp' => 'juฤe(?:\\s+u)?', + 'diff_tomorrow' => 'sutra', + 'diff_tomorrow_regexp' => 'sutra(?:\\s+u)?', + 'diff_before_yesterday' => 'prekjuฤe', + 'diff_after_tomorrow' => 'preksutra', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danas u] LT', + 'nextDay' => '[sutra u] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[u nedelju u] LT'; + case 3: + return '[u sredu u] LT'; + case 6: + return '[u subotu u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastDay' => '[juฤe u] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[proลกle nedelje u] LT'; + case 1: + return '[proลกlog ponedeljka u] LT'; + case 2: + return '[proลกlog utorka u] LT'; + case 3: + return '[proลกle srede u] LT'; + case 4: + return '[proลกlog ฤetvrtka u] LT'; + case 5: + return '[proลกlog petka u] LT'; + default: + return '[proลกle subote u] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mart', 'april', 'maj', 'jun', 'jul', 'avgust', 'septembar', 'oktobar', 'novembar', 'decembar'], + 'months_short' => ['jan.', 'feb.', 'mar.', 'apr.', 'maj', 'jun', 'jul', 'avg.', 'sep.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['nedelja', 'ponedeljak', 'utorak', 'sreda', 'ฤetvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sre.', 'ฤet.', 'pet.', 'sub.'], + 'weekdays_min' => ['ne', 'po', 'ut', 'sr', 'ฤe', 'pe', 'su'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php new file mode 100644 index 0000000000..8becbc5761 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Franรงois B + * - shaishavgandhi05 + * - Serhan Apaydฤฑn + * - JD Isaacks + * - Glaviฤ‡ + * - Nikola Zeravcic + * - Milos Sakovic + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count ะณะพะดะธะฝะฐ|:count ะณะพะดะธะฝะต|:count ะณะพะดะธะฝะฐ', + 'y' => ':count ะณ.', + 'month' => ':count ะผะตัะตั†|:count ะผะตัะตั†ะฐ|:count ะผะตัะตั†ะธ', + 'm' => ':count ะผ.', + 'week' => ':count ะฝะตะดะตั™ะฐ|:count ะฝะตะดะตั™ะต|:count ะฝะตะดะตั™ะฐ', + 'w' => ':count ะฝะตะด.', + 'day' => ':count ะดะฐะฝ|:count ะดะฐะฝะฐ|:count ะดะฐะฝะฐ', + 'd' => ':count ะด.', + 'hour' => ':count ัะฐั‚|:count ัะฐั‚ะฐ|:count ัะฐั‚ะธ', + 'h' => ':count ั‡.', + 'minute' => ':count ะผะธะฝัƒั‚|:count ะผะธะฝัƒั‚ะฐ|:count ะผะธะฝัƒั‚ะฐ', + 'min' => ':count ะผะธะฝ.', + 'second' => ':count ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะดะต|:count ัะตะบัƒะฝะดะธ', + 's' => ':count ัะตะบ.', + 'ago' => 'ะฟั€ะต :time', + 'from_now' => 'ะทะฐ :time', + 'after' => ':time ะฝะฐะบะพะฝ', + 'before' => ':time ะฟั€ะต', + 'year_from_now' => ':count ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝะต|:count ะณะพะดะธะฝะฐ', + 'year_ago' => ':count ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝะต|:count ะณะพะดะธะฝะฐ', + 'week_from_now' => ':count ะฝะตะดะตั™ัƒ|:count ะฝะตะดะตั™ะต|:count ะฝะตะดะตั™ะฐ', + 'week_ago' => ':count ะฝะตะดะตั™ัƒ|:count ะฝะตะดะตั™ะต|:count ะฝะตะดะตั™ะฐ', + 'diff_now' => 'ัƒะฟั€ะฐะฒะพ ัะฐะดะฐ', + 'diff_today' => 'ะดะฐะฝะฐั', + 'diff_today_regexp' => 'ะดะฐะฝะฐั(?:\\s+ัƒ)?', + 'diff_yesterday' => 'ั˜ัƒั‡ะต', + 'diff_yesterday_regexp' => 'ั˜ัƒั‡ะต(?:\\s+ัƒ)?', + 'diff_tomorrow' => 'ััƒั‚ั€ะฐ', + 'diff_tomorrow_regexp' => 'ััƒั‚ั€ะฐ(?:\\s+ัƒ)?', + 'diff_before_yesterday' => 'ะฟั€ะตะบั˜ัƒั‡ะต', + 'diff_after_tomorrow' => 'ะฟั€ะตะบะพััƒั‚ั€ะฐ', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[ะดะฐะฝะฐั ัƒ] LT', + 'nextDay' => '[ััƒั‚ั€ะฐ ัƒ] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[ัƒ ะฝะตะดะตั™ัƒ ัƒ] LT'; + case 3: + return '[ัƒ ัั€ะตะดัƒ ัƒ] LT'; + case 6: + return '[ัƒ ััƒะฑะพั‚ัƒ ัƒ] LT'; + default: + return '[ัƒ] dddd [ัƒ] LT'; + } + }, + 'lastDay' => '[ั˜ัƒั‡ะต ัƒ] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[ะฟั€ะพัˆะปะต ะฝะตะดะตั™ะต ัƒ] LT'; + case 1: + return '[ะฟั€ะพัˆะปะพะณ ะฟะพะฝะตะดะตั™ะบะฐ ัƒ] LT'; + case 2: + return '[ะฟั€ะพัˆะปะพะณ ัƒั‚ะพั€ะบะฐ ัƒ] LT'; + case 3: + return '[ะฟั€ะพัˆะปะต ัั€ะตะดะต ัƒ] LT'; + case 4: + return '[ะฟั€ะพัˆะปะพะณ ั‡ะตั‚ะฒั€ั‚ะบะฐ ัƒ] LT'; + case 5: + return '[ะฟั€ะพัˆะปะพะณ ะฟะตั‚ะบะฐ ัƒ] LT'; + default: + return '[ะฟั€ะพัˆะปะต ััƒะฑะพั‚ะต ัƒ] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['ั˜ะฐะฝัƒะฐั€', 'ั„ะตะฑั€ัƒะฐั€', 'ะผะฐั€ั‚', 'ะฐะฟั€ะธะป', 'ะผะฐั˜', 'ั˜ัƒะฝ', 'ั˜ัƒะป', 'ะฐะฒะณัƒัั‚', 'ัะตะฟั‚ะตะผะฑะฐั€', 'ะพะบั‚ะพะฑะฐั€', 'ะฝะพะฒะตะผะฑะฐั€', 'ะดะตั†ะตะผะฑะฐั€'], + 'months_short' => ['ั˜ะฐะฝ.', 'ั„ะตะฑ.', 'ะผะฐั€.', 'ะฐะฟั€.', 'ะผะฐั˜', 'ั˜ัƒะฝ', 'ั˜ัƒะป', 'ะฐะฒะณ.', 'ัะตะฟ.', 'ะพะบั‚.', 'ะฝะพะฒ.', 'ะดะตั†.'], + 'weekdays' => ['ะฝะตะดะตั™ะฐ', 'ะฟะพะฝะตะดะตั™ะฐะบ', 'ัƒั‚ะพั€ะฐะบ', 'ัั€ะตะดะฐ', 'ั‡ะตั‚ะฒั€ั‚ะฐะบ', 'ะฟะตั‚ะฐะบ', 'ััƒะฑะพั‚ะฐ'], + 'weekdays_short' => ['ะฝะตะด.', 'ะฟะพะฝ.', 'ัƒั‚ะพ.', 'ัั€ะต.', 'ั‡ะตั‚.', 'ะฟะตั‚.', 'ััƒะฑ.'], + 'weekdays_min' => ['ะฝะต', 'ะฟะพ', 'ัƒั‚', 'ัั€', 'ั‡ะต', 'ะฟะต', 'ััƒ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ะธ '], + 'meridiem' => ['ะะœ', 'ะŸะœ'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_BA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_BA.php new file mode 100644 index 0000000000..4b29a45c71 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_BA.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Cyrl_BA'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr_Cyrl.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D.M.yy.', + 'LL' => 'DD.MM.YYYY.', + 'LLL' => 'DD. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, DD. MMMM YYYY. HH:mm', + ], + 'weekdays' => ['ะฝะตะดั˜ะตั™ะฐ', 'ะฟะพะฝะตะดะตั™ะฐะบ', 'ัƒั‚ะพั€ะฐะบ', 'ัั€ะธั˜ะตะดะฐ', 'ั‡ะตั‚ะฒั€ั‚ะฐะบ', 'ะฟะตั‚ะฐะบ', 'ััƒะฑะพั‚ะฐ'], + 'weekdays_short' => ['ะฝะตะด.', 'ะฟะพะฝ.', 'ัƒั‚.', 'ัั€.', 'ั‡ะตั‚.', 'ะฟะตั‚.', 'ััƒะฑ.'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php new file mode 100644 index 0000000000..28d22fd2c7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Glaviฤ‡ + * - Milos Sakovic + */ + +use Carbon\CarbonInterface; +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Cyrl_ME'); +} +// @codeCoverageIgnoreEnd + +return [ + 'year' => ':count ะณะพะดะธะฝะฐ|:count ะณะพะดะธะฝะต|:count ะณะพะดะธะฝะฐ', + 'y' => ':count ะณ.', + 'month' => ':count ะผั˜ะตัะตั†|:count ะผั˜ะตัะตั†ะฐ|:count ะผั˜ะตัะตั†ะธ', + 'm' => ':count ะผั˜.', + 'week' => ':count ะฝะตะดั˜ะตั™ะฐ|:count ะฝะตะดั˜ะตั™ะต|:count ะฝะตะดั˜ะตั™ะฐ', + 'w' => ':count ะฝะตะด.', + 'day' => ':count ะดะฐะฝ|:count ะดะฐะฝะฐ|:count ะดะฐะฝะฐ', + 'd' => ':count ะด.', + 'hour' => ':count ัะฐั‚|:count ัะฐั‚ะฐ|:count ัะฐั‚ะธ', + 'h' => ':count ั‡.', + 'minute' => ':count ะผะธะฝัƒั‚|:count ะผะธะฝัƒั‚ะฐ|:count ะผะธะฝัƒั‚ะฐ', + 'min' => ':count ะผะธะฝ.', + 'second' => ':count ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะดะต|:count ัะตะบัƒะฝะดะธ', + 's' => ':count ัะตะบ.', + 'ago' => 'ะฟั€ะธั˜ะต :time', + 'from_now' => 'ะทะฐ :time', + 'after' => ':time ะฝะฐะบะพะฝ', + 'before' => ':time ะฟั€ะธั˜ะต', + + 'year_from_now' => ':count ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝะต|:count ะณะพะดะธะฝะฐ', + 'year_ago' => ':count ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝะต|:count ะณะพะดะธะฝะฐ', + + 'week_from_now' => ':count ะฝะตะดั˜ะตั™ัƒ|:count ะฝะตะดั˜ะตั™ะต|:count ะฝะตะดั˜ะตั™ะฐ', + 'week_ago' => ':count ะฝะตะดั˜ะตั™ัƒ|:count ะฝะตะดั˜ะตั™ะต|:count ะฝะตะดั˜ะตั™ะฐ', + + 'diff_now' => 'ัƒะฟั€ะฐะฒะพ ัะฐะดะฐ', + 'diff_today' => 'ะดะฐะฝะฐั', + 'diff_today_regexp' => 'ะดะฐะฝะฐั(?:\\s+ัƒ)?', + 'diff_yesterday' => 'ั˜ัƒั‡ะต', + 'diff_yesterday_regexp' => 'ั˜ัƒั‡ะต(?:\\s+ัƒ)?', + 'diff_tomorrow' => 'ััƒั‚ั€ะฐ', + 'diff_tomorrow_regexp' => 'ััƒั‚ั€ะฐ(?:\\s+ัƒ)?', + 'diff_before_yesterday' => 'ะฟั€ะตะบั˜ัƒั‡ะต', + 'diff_after_tomorrow' => 'ะฟั€ะตะบะพัั˜ัƒั‚ั€ะฐ', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[ะดะฐะฝะฐั ัƒ] LT', + 'nextDay' => '[ััƒั‚ั€ะฐ ัƒ] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[ัƒ ะฝะตะดะตั™ัƒ ัƒ] LT'; + case 3: + return '[ัƒ ัั€ะตะดัƒ ัƒ] LT'; + case 6: + return '[ัƒ ััƒะฑะพั‚ัƒ ัƒ] LT'; + default: + return '[ัƒ] dddd [ัƒ] LT'; + } + }, + 'lastDay' => '[ั˜ัƒั‡ะต ัƒ] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[ะฟั€ะพัˆะปะต ะฝะตะดะตั™ะต ัƒ] LT'; + case 1: + return '[ะฟั€ะพัˆะปะพะณ ะฟะพะฝะตะดะตั™ะบะฐ ัƒ] LT'; + case 2: + return '[ะฟั€ะพัˆะปะพะณ ัƒั‚ะพั€ะบะฐ ัƒ] LT'; + case 3: + return '[ะฟั€ะพัˆะปะต ัั€ะตะดะต ัƒ] LT'; + case 4: + return '[ะฟั€ะพัˆะปะพะณ ั‡ะตั‚ะฒั€ั‚ะบะฐ ัƒ] LT'; + case 5: + return '[ะฟั€ะพัˆะปะพะณ ะฟะตั‚ะบะฐ ัƒ] LT'; + default: + return '[ะฟั€ะพัˆะปะต ััƒะฑะพั‚ะต ัƒ] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['ั˜ะฐะฝัƒะฐั€', 'ั„ะตะฑั€ัƒะฐั€', 'ะผะฐั€ั‚', 'ะฐะฟั€ะธะป', 'ะผะฐั˜', 'ั˜ัƒะฝ', 'ั˜ัƒะป', 'ะฐะฒะณัƒัั‚', 'ัะตะฟั‚ะตะผะฑะฐั€', 'ะพะบั‚ะพะฑะฐั€', 'ะฝะพะฒะตะผะฑะฐั€', 'ะดะตั†ะตะผะฑะฐั€'], + 'months_short' => ['ั˜ะฐะฝ.', 'ั„ะตะฑ.', 'ะผะฐั€.', 'ะฐะฟั€.', 'ะผะฐั˜', 'ั˜ัƒะฝ', 'ั˜ัƒะป', 'ะฐะฒะณ.', 'ัะตะฟ.', 'ะพะบั‚.', 'ะฝะพะฒ.', 'ะดะตั†.'], + 'weekdays' => ['ะฝะตะดะตั™ะฐ', 'ะฟะพะฝะตะดะตั™ะฐะบ', 'ัƒั‚ะพั€ะฐะบ', 'ัั€ะตะดะฐ', 'ั‡ะตั‚ะฒั€ั‚ะฐะบ', 'ะฟะตั‚ะฐะบ', 'ััƒะฑะพั‚ะฐ'], + 'weekdays_short' => ['ะฝะตะด.', 'ะฟะพะฝ.', 'ัƒั‚ะพ.', 'ัั€ะต.', 'ั‡ะตั‚.', 'ะฟะตั‚.', 'ััƒะฑ.'], + 'weekdays_min' => ['ะฝะต', 'ะฟะพ', 'ัƒั‚', 'ัั€', 'ั‡ะต', 'ะฟะต', 'ััƒ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ะธ '], + 'meridiem' => ['ะะœ', 'ะŸะœ'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_XK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_XK.php new file mode 100644 index 0000000000..d6e29b86bd --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_XK.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Cyrl_XK'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr_Cyrl_BA.php', [ + 'weekdays' => ['ะฝะตะดะตั™ะฐ', 'ะฟะพะฝะตะดะตั™ะฐะบ', 'ัƒั‚ะพั€ะฐะบ', 'ัั€ะตะดะฐ', 'ั‡ะตั‚ะฒั€ั‚ะฐะบ', 'ะฟะตั‚ะฐะบ', 'ััƒะฑะพั‚ะฐ'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn.php new file mode 100644 index 0000000000..9971674708 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_BA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_BA.php new file mode 100644 index 0000000000..95b2770db4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_BA.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Latn_BA'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr_Latn.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D.M.yy.', + 'LL' => 'DD.MM.YYYY.', + 'LLL' => 'DD. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, DD. MMMM YYYY. HH:mm', + ], + 'weekdays' => ['nedjelja', 'ponedeljak', 'utorak', 'srijeda', 'ฤetvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'ut.', 'sr.', 'ฤet.', 'pet.', 'sub.'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php new file mode 100644 index 0000000000..5b8f2d062d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Glaviฤ‡ + * - Milos Sakovic + */ + +use Carbon\CarbonInterface; +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Latn_ME'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr.php', [ + 'month' => ':count mjesec|:count mjeseca|:count mjeseci', + 'week' => ':count nedjelja|:count nedjelje|:count nedjelja', + 'second' => ':count sekund|:count sekunde|:count sekundi', + 'ago' => 'prije :time', + 'from_now' => 'za :time', + 'after' => ':time nakon', + 'before' => ':time prije', + 'week_from_now' => ':count nedjelju|:count nedjelje|:count nedjelja', + 'week_ago' => ':count nedjelju|:count nedjelje|:count nedjelja', + 'second_ago' => ':count sekund|:count sekunde|:count sekundi', + 'diff_tomorrow' => 'sjutra', + 'calendar' => [ + 'nextDay' => '[sjutra u] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[u nedjelju u] LT'; + case 3: + return '[u srijedu u] LT'; + case 6: + return '[u subotu u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[proลกle nedjelje u] LT'; + case 1: + return '[proลกle nedjelje u] LT'; + case 2: + return '[proลกlog utorka u] LT'; + case 3: + return '[proลกle srijede u] LT'; + case 4: + return '[proลกlog ฤetvrtka u] LT'; + case 5: + return '[proลกlog petka u] LT'; + default: + return '[proลกle subote u] LT'; + } + }, + ], + 'weekdays' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'ฤetvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sri.', 'ฤet.', 'pet.', 'sub.'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_XK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_XK.php new file mode 100644 index 0000000000..5278e2e5a2 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_XK.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Translation\PluralizationRules; + +// @codeCoverageIgnoreStart +if (class_exists(PluralizationRules::class)) { + PluralizationRules::set(static function ($number) { + return PluralizationRules::get($number, 'sr'); + }, 'sr_Latn_XK'); +} +// @codeCoverageIgnoreEnd + +return array_replace_recursive(require __DIR__.'/sr_Latn_BA.php', [ + 'weekdays' => ['nedelja', 'ponedeljak', 'utorak', 'sreda', 'ฤetvrtak', 'petak', 'subota'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php new file mode 100644 index 0000000000..d7c65b91a7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sr_Latn_ME.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS.php new file mode 100644 index 0000000000..bc5e04bf89 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - sr_YU, sr_CS locale Danilo Segan bug-glibc-locales@gnu.org + */ +return require __DIR__.'/sr_Cyrl.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS@latin.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS@latin.php new file mode 100644 index 0000000000..9971674708 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS@latin.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ss.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ss.php new file mode 100644 index 0000000000..1c52c9bf63 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ss.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Nicolai Davies + */ +return [ + 'year' => '{1}umnyaka|:count iminyaka', + 'month' => '{1}inyanga|:count tinyanga', + 'week' => '{1}:count liviki|:count emaviki', + 'day' => '{1}lilanga|:count emalanga', + 'hour' => '{1}lihora|:count emahora', + 'minute' => '{1}umzuzu|:count emizuzu', + 'second' => '{1}emizuzwana lomcane|:count mzuzwana', + 'ago' => 'wenteka nga :time', + 'from_now' => 'nga :time', + 'diff_yesterday' => 'Itolo', + 'diff_yesterday_regexp' => 'Itolo(?:\\s+nga)?', + 'diff_today' => 'Namuhla', + 'diff_today_regexp' => 'Namuhla(?:\\s+nga)?', + 'diff_tomorrow' => 'Kusasa', + 'diff_tomorrow_regexp' => 'Kusasa(?:\\s+nga)?', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'calendar' => [ + 'sameDay' => '[Namuhla nga] LT', + 'nextDay' => '[Kusasa nga] LT', + 'nextWeek' => 'dddd [nga] LT', + 'lastDay' => '[Itolo nga] LT', + 'lastWeek' => 'dddd [leliphelile] [nga] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + + return $number.( + ((int) ($number % 100 / 10) === 1) ? 'e' : ( + ($lastDigit === 1 || $lastDigit === 2) ? 'a' : 'e' + ) + ); + }, + 'meridiem' => function ($hour) { + if ($hour < 11) { + return 'ekuseni'; + } + if ($hour < 15) { + return 'emini'; + } + if ($hour < 19) { + return 'entsambama'; + } + + return 'ebusuku'; + }, + 'months' => ['Bhimbidvwane', 'Indlovana', 'Indlov\'lenkhulu', 'Mabasa', 'Inkhwekhweti', 'Inhlaba', 'Kholwane', 'Ingci', 'Inyoni', 'Imphala', 'Lweti', 'Ingongoni'], + 'months_short' => ['Bhi', 'Ina', 'Inu', 'Mab', 'Ink', 'Inh', 'Kho', 'Igc', 'Iny', 'Imp', 'Lwe', 'Igo'], + 'weekdays' => ['Lisontfo', 'Umsombuluko', 'Lesibili', 'Lesitsatfu', 'Lesine', 'Lesihlanu', 'Umgcibelo'], + 'weekdays_short' => ['Lis', 'Umb', 'Lsb', 'Les', 'Lsi', 'Lsh', 'Umg'], + 'weekdays_min' => ['Li', 'Us', 'Lb', 'Lt', 'Ls', 'Lh', 'Ug'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ss_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ss_ZA.php new file mode 100644 index 0000000000..ba89527c4b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ss_ZA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ss.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/st.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/st.php new file mode 100644 index 0000000000..b065445b56 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/st.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/st_ZA.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/st_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/st_ZA.php new file mode 100644 index 0000000000..5bce7f20f6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/st_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Pherekgong', 'Hlakola', 'Tlhakubele', 'Mmese', 'Motsheanong', 'Phupjane', 'Phupu', 'Phato', 'Leotse', 'Mphalane', 'Pudungwana', 'Tshitwe'], + 'months_short' => ['Phe', 'Hla', 'TlH', 'Mme', 'Mot', 'Jan', 'Upu', 'Pha', 'Leo', 'Mph', 'Pud', 'Tsh'], + 'weekdays' => ['Sontaha', 'Mantaha', 'Labobedi', 'Laboraro', 'Labone', 'Labohlano', 'Moqebelo'], + 'weekdays_short' => ['Son', 'Mma', 'Bed', 'Rar', 'Ne', 'Hla', 'Moq'], + 'weekdays_min' => ['Son', 'Mma', 'Bed', 'Rar', 'Ne', 'Hla', 'Moq'], + 'day_of_first_week_of_year' => 1, + + 'week' => ':count Sontaha', // less reliable + 'w' => ':count Sontaha', // less reliable + 'a_week' => ':count Sontaha', // less reliable + + 'day' => ':count letsatsi', // less reliable + 'd' => ':count letsatsi', // less reliable + 'a_day' => ':count letsatsi', // less reliable + + 'hour' => ':count seลกupanako', // less reliable + 'h' => ':count seลกupanako', // less reliable + 'a_hour' => ':count seลกupanako', // less reliable + + 'minute' => ':count menyane', // less reliable + 'min' => ':count menyane', // less reliable + 'a_minute' => ':count menyane', // less reliable + + 'second' => ':count thusa', // less reliable + 's' => ':count thusa', // less reliable + 'a_second' => ':count thusa', // less reliable + + 'year' => ':count selemo', + 'y' => ':count selemo', + 'a_year' => ':count selemo', + + 'month' => ':count kgwedi', + 'm' => ':count kgwedi', + 'a_month' => ':count kgwedi', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv.php new file mode 100644 index 0000000000..1706c71913 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Kristoffer Snabb + * - JD Isaacks + * - Jens Herlevsen + * - Nightpine + * - Anders Nygren (litemerafrukt) + */ +return [ + 'year' => ':count รฅr', + 'a_year' => 'ett รฅr|:count รฅr', + 'y' => ':count รฅr', + 'month' => ':count mรฅnad|:count mรฅnader', + 'a_month' => 'en mรฅnad|:count mรฅnader', + 'm' => ':count mรฅn', + 'week' => ':count vecka|:count veckor', + 'a_week' => 'en vecka|:count veckor', + 'w' => ':count v', + 'day' => ':count dag|:count dagar', + 'a_day' => 'en dag|:count dagar', + 'd' => ':count dgr', + 'hour' => ':count timme|:count timmar', + 'a_hour' => 'en timme|:count timmar', + 'h' => ':count tim', + 'minute' => ':count minut|:count minuter', + 'a_minute' => 'en minut|:count minuter', + 'min' => ':count min', + 'second' => ':count sekund|:count sekunder', + 'a_second' => 'nรฅgra sekunder|:count sekunder', + 's' => ':count s', + 'ago' => 'fรถr :time sedan', + 'from_now' => 'om :time', + 'after' => ':time efter', + 'before' => ':time fรถre', + 'diff_now' => 'nu', + 'diff_today' => 'I dag', + 'diff_yesterday' => 'i gรฅr', + 'diff_yesterday_regexp' => 'I gรฅr', + 'diff_tomorrow' => 'i morgon', + 'diff_tomorrow_regexp' => 'I morgon', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [kl.] HH:mm', + 'LLLL' => 'dddd D MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[I dag] LT', + 'nextDay' => '[I morgon] LT', + 'nextWeek' => '[Pรฅ] dddd LT', + 'lastDay' => '[I gรฅr] LT', + 'lastWeek' => '[I] dddd[s] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + + return $number.( + ((int) ($number % 100 / 10) === 1) ? 'e' : ( + ($lastDigit === 1 || $lastDigit === 2) ? 'a' : 'e' + ) + ); + }, + 'months' => ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['sรถndag', 'mรฅndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lรถrdag'], + 'weekdays_short' => ['sรถn', 'mรฅn', 'tis', 'ons', 'tors', 'fre', 'lรถr'], + 'weekdays_min' => ['sรถ', 'mรฅ', 'ti', 'on', 'to', 'fr', 'lรถ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' och '], + 'meridiem' => ['fm', 'em'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv_AX.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv_AX.php new file mode 100644 index 0000000000..70cc55853a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv_AX.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sv.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-dd', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv_FI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv_FI.php new file mode 100644 index 0000000000..d7182c83cc --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv_FI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sv.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv_SE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv_SE.php new file mode 100644 index 0000000000..d7182c83cc --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv_SE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sv.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw.php new file mode 100644 index 0000000000..f8630d53b5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - leyluj + * - Josh Soref + * - ryanhart2 + */ +return [ + 'year' => 'mwaka :count|miaka :count', + 'a_year' => 'mwaka mmoja|miaka :count', + 'y' => 'mwaka :count|miaka :count', + 'month' => 'mwezi :count|miezi :count', + 'a_month' => 'mwezi mmoja|miezi :count', + 'm' => 'mwezi :count|miezi :count', + 'week' => 'wiki :count', + 'a_week' => 'wiki mmoja|wiki :count', + 'w' => 'w. :count', + 'day' => 'siku :count', + 'a_day' => 'siku moja|masiku :count', + 'd' => 'si. :count', + 'hour' => 'saa :count|masaa :count', + 'a_hour' => 'saa limoja|masaa :count', + 'h' => 'saa :count|masaa :count', + 'minute' => 'dakika :count', + 'a_minute' => 'dakika moja|dakika :count', + 'min' => 'd. :count', + 'second' => 'sekunde :count', + 'a_second' => 'hivi punde|sekunde :count', + 's' => 'se. :count', + 'ago' => 'tokea :time', + 'from_now' => ':time baadaye', + 'after' => ':time baada', + 'before' => ':time kabla', + 'diff_now' => 'sasa hivi', + 'diff_today' => 'leo', + 'diff_today_regexp' => 'leo(?:\\s+saa)?', + 'diff_yesterday' => 'jana', + 'diff_tomorrow' => 'kesho', + 'diff_tomorrow_regexp' => 'kesho(?:\\s+saa)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[leo saa] LT', + 'nextDay' => '[kesho saa] LT', + 'nextWeek' => '[wiki ijayo] dddd [saat] LT', + 'lastDay' => '[jana] LT', + 'lastWeek' => '[wiki iliyopita] dddd [saat] LT', + 'sameElse' => 'L', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpl', 'Jtat', 'Jnne', 'Jtan', 'Alh', 'Ijm', 'Jmos'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Al', 'Ij', 'J1'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' na '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw_CD.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw_CD.php new file mode 100644 index 0000000000..ec9117b53d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw_CD.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw_KE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw_KE.php new file mode 100644 index 0000000000..2ace0db299 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw_KE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kamusi Project Martin Benjamin locales@kamusi.org + */ +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['asubuhi', 'alasiri'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw_TZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw_TZ.php new file mode 100644 index 0000000000..fab3cd683f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw_TZ.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kamusi Project Martin Benjamin locales@kamusi.org + */ +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['asubuhi', 'alasiri'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw_UG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw_UG.php new file mode 100644 index 0000000000..ec9117b53d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sw_UG.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/szl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/szl.php new file mode 100644 index 0000000000..4429c4f568 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/szl.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/szl_PL.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/szl_PL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/szl_PL.php new file mode 100644 index 0000000000..9adddcf87c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/szl_PL.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - szl_PL locale Przemyslaw Buczkowski libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['styczyล„', 'luty', 'merc', 'kwjeciyล„', 'moj', 'czyrwjyล„', 'lipjyล„', 'siyrpjyล„', 'wrzesiyล„', 'paลบdziernik', 'listopad', 'grudziyล„'], + 'months_short' => ['sty', 'lut', 'mer', 'kwj', 'moj', 'czy', 'lip', 'siy', 'wrz', 'paลบ', 'lis', 'gru'], + 'weekdays' => ['niydziela', 'pyล„dziลek', 'wtลrek', 'strzลda', 'sztwortek', 'pjลntek', 'sobลta'], + 'weekdays_short' => ['niy', 'pyล„', 'wtล', 'str', 'szt', 'pjล', 'sob'], + 'weekdays_min' => ['niy', 'pyล„', 'wtล', 'str', 'szt', 'pjล', 'sob'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count rok', + 'y' => ':count rok', + 'a_year' => ':count rok', + + 'month' => ':count mjeล›ลฏnc', + 'm' => ':count mjeล›ลฏnc', + 'a_month' => ':count mjeล›ลฏnc', + + 'week' => ':count tydลบyล„', + 'w' => ':count tydลบyล„', + 'a_week' => ':count tydลบyล„', + + 'day' => ':count dลบyล„', + 'd' => ':count dลบyล„', + 'a_day' => ':count dลบyล„', + + 'hour' => ':count godzina', + 'h' => ':count godzina', + 'a_hour' => ':count godzina', + + 'minute' => ':count minuta', + 'min' => ':count minuta', + 'a_minute' => ':count minuta', + + 'second' => ':count sekลฏnda', + 's' => ':count sekลฏnda', + 'a_second' => ':count sekลฏnda', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta.php new file mode 100644 index 0000000000..c1d89cbb32 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Franรงois B + * - JD Isaacks + * - Satheez + */ +return [ + 'year' => ':count เฎตเฎฐเฏเฎŸเฎฎเฏ|:count เฎ†เฎฃเฏเฎŸเฏเฎ•เฎณเฏ', + 'a_year' => 'เฎ’เฎฐเฏ เฎตเฎฐเฏเฎŸเฎฎเฏ|:count เฎ†เฎฃเฏเฎŸเฏเฎ•เฎณเฏ', + 'y' => ':count เฎตเฎฐเฏเฎŸ.|:count เฎ†เฎฃเฏ.', + 'month' => ':count เฎฎเฎพเฎคเฎฎเฏ|:count เฎฎเฎพเฎคเฎ™เฏเฎ•เฎณเฏ', + 'a_month' => 'เฎ’เฎฐเฏ เฎฎเฎพเฎคเฎฎเฏ|:count เฎฎเฎพเฎคเฎ™เฏเฎ•เฎณเฏ', + 'm' => ':count เฎฎเฎพเฎค.', + 'week' => ':count เฎตเฎพเฎฐเฎฎเฏ|:count เฎตเฎพเฎฐเฎ™เฏเฎ•เฎณเฏ', + 'a_week' => 'เฎ’เฎฐเฏ เฎตเฎพเฎฐเฎฎเฏ|:count เฎตเฎพเฎฐเฎ™เฏเฎ•เฎณเฏ', + 'w' => ':count เฎตเฎพเฎฐ.', + 'day' => ':count เฎจเฎพเฎณเฏ|:count เฎจเฎพเฎŸเฏเฎ•เฎณเฏ', + 'a_day' => 'เฎ’เฎฐเฏ เฎจเฎพเฎณเฏ|:count เฎจเฎพเฎŸเฏเฎ•เฎณเฏ', + 'd' => ':count เฎจเฎพเฎณเฏ|:count เฎจเฎพเฎŸเฏ.', + 'hour' => ':count เฎฎเฎฃเฎฟ เฎจเฏ‡เฎฐเฎฎเฏ|:count เฎฎเฎฃเฎฟ เฎจเฏ‡เฎฐเฎฎเฏ', + 'a_hour' => 'เฎ’เฎฐเฏ เฎฎเฎฃเฎฟ เฎจเฏ‡เฎฐเฎฎเฏ|:count เฎฎเฎฃเฎฟ เฎจเฏ‡เฎฐเฎฎเฏ', + 'h' => ':count เฎฎเฎฃเฎฟ.', + 'minute' => ':count เฎจเฎฟเฎฎเฎฟเฎŸเฎฎเฏ|:count เฎจเฎฟเฎฎเฎฟเฎŸเฎ™เฏเฎ•เฎณเฏ', + 'a_minute' => 'เฎ’เฎฐเฏ เฎจเฎฟเฎฎเฎฟเฎŸเฎฎเฏ|:count เฎจเฎฟเฎฎเฎฟเฎŸเฎ™เฏเฎ•เฎณเฏ', + 'min' => ':count เฎจเฎฟเฎฎเฎฟ.', + 'second' => ':count เฎšเฎฟเฎฒ เฎตเฎฟเฎจเฎพเฎŸเฎฟเฎ•เฎณเฏ|:count เฎตเฎฟเฎจเฎพเฎŸเฎฟเฎ•เฎณเฏ', + 'a_second' => 'เฎ’เฎฐเฏ เฎšเฎฟเฎฒ เฎตเฎฟเฎจเฎพเฎŸเฎฟเฎ•เฎณเฏ|:count เฎตเฎฟเฎจเฎพเฎŸเฎฟเฎ•เฎณเฏ', + 's' => ':count เฎตเฎฟเฎจเฎพ.', + 'ago' => ':time เฎฎเฏเฎฉเฏ', + 'from_now' => ':time เฎ‡เฎฒเฏ', + 'before' => ':time เฎฎเฏเฎฉเฏ', + 'after' => ':time เฎชเฎฟเฎฉเฏ', + 'diff_now' => 'เฎ‡เฎชเฏเฎชเฏ‹เฎคเฏ', + 'diff_today' => 'เฎ‡เฎฉเฏเฎฑเฏ', + 'diff_yesterday' => 'เฎจเฏ‡เฎฑเฏเฎฑเฏ', + 'diff_tomorrow' => 'เฎจเฎพเฎณเฏˆ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[เฎ‡เฎฉเฏเฎฑเฏ] LT', + 'nextDay' => '[เฎจเฎพเฎณเฏˆ] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[เฎจเฏ‡เฎฑเฏเฎฑเฏ] LT', + 'lastWeek' => '[เฎ•เฎŸเฎจเฏเฎค เฎตเฎพเฎฐเฎฎเฏ] dddd, LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberเฎตเฎคเฏ', + 'meridiem' => function ($hour) { + if ($hour < 2) { + return ' เฎฏเฎพเฎฎเฎฎเฏ'; + } + if ($hour < 6) { + return ' เฎตเฏˆเฎ•เฎฑเฏˆ'; + } + if ($hour < 10) { + return ' เฎ•เฎพเฎฒเฏˆ'; + } + if ($hour < 14) { + return ' เฎจเฎฃเฏเฎชเฎ•เฎฒเฏ'; + } + if ($hour < 18) { + return ' เฎŽเฎฑเฏเฎชเฎพเฎŸเฏ'; + } + if ($hour < 22) { + return ' เฎฎเฎพเฎฒเฏˆ'; + } + + return ' เฎฏเฎพเฎฎเฎฎเฏ'; + }, + 'months' => ['เฎœเฎฉเฎตเฎฐเฎฟ', 'เฎชเฎฟเฎชเฏเฎฐเฎตเฎฐเฎฟ', 'เฎฎเฎพเฎฐเฏเฎšเฏ', 'เฎเฎชเฏเฎฐเฎฒเฏ', 'เฎฎเฏ‡', 'เฎœเฏ‚เฎฉเฏ', 'เฎœเฏ‚เฎฒเฏˆ', 'เฎ†เฎ•เฎธเฏเฎŸเฏ', 'เฎšเฏ†เฎชเฏเฎŸเฏ†เฎฎเฏเฎชเฎฐเฏ', 'เฎ…เฎ•เฏเฎŸเฏ‡เฎพเฎชเฎฐเฏ', 'เฎจเฎตเฎฎเฏเฎชเฎฐเฏ', 'เฎŸเฎฟเฎšเฎฎเฏเฎชเฎฐเฏ'], + 'months_short' => ['เฎœเฎฉเฎตเฎฐเฎฟ', 'เฎชเฎฟเฎชเฏเฎฐเฎตเฎฐเฎฟ', 'เฎฎเฎพเฎฐเฏเฎšเฏ', 'เฎเฎชเฏเฎฐเฎฒเฏ', 'เฎฎเฏ‡', 'เฎœเฏ‚เฎฉเฏ', 'เฎœเฏ‚เฎฒเฏˆ', 'เฎ†เฎ•เฎธเฏเฎŸเฏ', 'เฎšเฏ†เฎชเฏเฎŸเฏ†เฎฎเฏเฎชเฎฐเฏ', 'เฎ…เฎ•เฏเฎŸเฏ‡เฎพเฎชเฎฐเฏ', 'เฎจเฎตเฎฎเฏเฎชเฎฐเฏ', 'เฎŸเฎฟเฎšเฎฎเฏเฎชเฎฐเฏ'], + 'weekdays' => ['เฎžเฎพเฎฏเฎฟเฎฑเฏเฎฑเฏเฎ•เฏเฎ•เฎฟเฎดเฎฎเฏˆ', 'เฎคเฎฟเฎ™เฏเฎ•เฎŸเฏเฎ•เฎฟเฎดเฎฎเฏˆ', 'เฎšเฏ†เฎตเฏเฎตเฎพเฎฏเฏเฎ•เฎฟเฎดเฎฎเฏˆ', 'เฎชเฏเฎคเฎฉเฏเฎ•เฎฟเฎดเฎฎเฏˆ', 'เฎตเฎฟเฎฏเฎพเฎดเฎ•เฏเฎ•เฎฟเฎดเฎฎเฏˆ', 'เฎตเฏ†เฎณเฏเฎณเฎฟเฎ•เฏเฎ•เฎฟเฎดเฎฎเฏˆ', 'เฎšเฎฉเฎฟเฎ•เฏเฎ•เฎฟเฎดเฎฎเฏˆ'], + 'weekdays_short' => ['เฎžเฎพเฎฏเฎฟเฎฑเฏ', 'เฎคเฎฟเฎ™เฏเฎ•เฎณเฏ', 'เฎšเฏ†เฎตเฏเฎตเฎพเฎฏเฏ', 'เฎชเฏเฎคเฎฉเฏ', 'เฎตเฎฟเฎฏเฎพเฎดเฎฉเฏ', 'เฎตเฏ†เฎณเฏเฎณเฎฟ', 'เฎšเฎฉเฎฟ'], + 'weekdays_min' => ['เฎžเฎพ', 'เฎคเฎฟ', 'เฎšเฏ†', 'เฎชเฏ', 'เฎตเฎฟ', 'เฎตเฏ†', 'เฎš'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' เฎฎเฎฑเฏเฎฑเฏเฎฎเฏ '], + 'weekend' => [0, 0], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_IN.php new file mode 100644 index 0000000000..492d4c56db --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เฎœเฎฉเฎตเฎฐเฎฟ', 'เฎชเฎฟเฎชเฏเฎฐเฎตเฎฐเฎฟ', 'เฎฎเฎพเฎฐเฏเฎšเฏ', 'เฎเฎชเฏเฎฐเฎฒเฏ', 'เฎฎเฏ‡', 'เฎœเฏ‚เฎฉเฏ', 'เฎœเฏ‚เฎฒเฏˆ', 'เฎ†เฎ•เฎธเฏเฎŸเฏ', 'เฎšเฏ†เฎชเฏเฎŸเฎฎเฏเฎชเฎฐเฏ', 'เฎ…เฎ•เฏเฎŸเฏ‹เฎชเฎฐเฏ', 'เฎจเฎตเฎฎเฏเฎชเฎฐเฏ', 'เฎŸเฎฟเฎšเฎฎเฏเฎชเฎฐเฏ'], + 'months_short' => ['เฎœเฎฉ.', 'เฎชเฎฟเฎชเฏ.', 'เฎฎเฎพเฎฐเฏ.', 'เฎเฎชเฏ.', 'เฎฎเฏ‡', 'เฎœเฏ‚เฎฉเฏ', 'เฎœเฏ‚เฎฒเฏˆ', 'เฎ†เฎ•.', 'เฎšเฏ†เฎชเฏ.', 'เฎ…เฎ•เฏ.', 'เฎจเฎต.', 'เฎŸเฎฟเฎš.'], + 'weekdays' => ['เฎžเฎพเฎฏเฎฟเฎฑเฏ', 'เฎคเฎฟเฎ™เฏเฎ•เฎณเฏ', 'เฎšเฏ†เฎตเฏเฎตเฎพเฎฏเฏ', 'เฎชเฏเฎคเฎฉเฏ', 'เฎตเฎฟเฎฏเฎพเฎดเฎฉเฏ', 'เฎตเฏ†เฎณเฏเฎณเฎฟ', 'เฎšเฎฉเฎฟ'], + 'weekdays_short' => ['เฎžเฎพ', 'เฎคเฎฟ', 'เฎšเฏ†', 'เฎชเฏ', 'เฎตเฎฟ', 'เฎตเฏ†', 'เฎš'], + 'weekdays_min' => ['เฎžเฎพ', 'เฎคเฎฟ', 'เฎšเฏ†', 'เฎชเฏ', 'เฎตเฎฟ', 'เฎตเฏ†', 'เฎš'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เฎ•เฎพเฎฒเฏˆ', 'เฎฎเฎพเฎฒเฏˆ'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_LK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_LK.php new file mode 100644 index 0000000000..8e2afbf635 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_LK.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - J.Yogaraj 94-777-315206 yogaraj.ubuntu@gmail.com + */ +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เฎœเฎฉเฎตเฎฐเฎฟ', 'เฎชเฎฟเฎชเฏเฎฐเฎตเฎฐเฎฟ', 'เฎฎเฎพเฎฐเฏเฎšเฏ', 'เฎเฎชเฏเฎฐเฎฒเฏ', 'เฎฎเฏ‡', 'เฎœเฏ‚เฎฉเฏ', 'เฎœเฏ‚เฎฒเฏˆ', 'เฎ†เฎ•เฎธเฏเฎŸเฏ', 'เฎšเฏ†เฎชเฏเฎŸเฎฎเฏเฎชเฎฐเฏ', 'เฎ…เฎ•เฏเฎŸเฏ‹เฎชเฎฐเฏ', 'เฎจเฎตเฎฎเฏเฎชเฎฐเฏ', 'เฎŸเฎฟเฎšเฎฎเฏเฎชเฎฐเฏ'], + 'months_short' => ['เฎœเฎฉ', 'เฎชเฎฟเฎชเฏ', 'เฎฎเฎพเฎฐเฏ', 'เฎเฎชเฏ', 'เฎฎเฏ‡', 'เฎœเฏ‚เฎฉเฏ', 'เฎœเฏ‚เฎฒเฏˆ', 'เฎ†เฎ•', 'เฎšเฏ†เฎชเฏ', 'เฎ…เฎ•เฏ', 'เฎจเฎต', 'เฎŸเฎฟเฎš'], + 'weekdays' => ['เฎžเฎพเฎฏเฎฟเฎฑเฏ', 'เฎคเฎฟเฎ™เฏเฎ•เฎณเฏ', 'เฎšเฏ†เฎตเฏเฎตเฎพเฎฏเฏ', 'เฎชเฏเฎคเฎฉเฏ', 'เฎตเฎฟเฎฏเฎพเฎดเฎฉเฏ', 'เฎตเฏ†เฎณเฏเฎณเฎฟ', 'เฎšเฎฉเฎฟ'], + 'weekdays_short' => ['เฎžเฎพ', 'เฎคเฎฟ', 'เฎšเฏ†', 'เฎชเฏ', 'เฎตเฎฟ', 'เฎตเฏ†', 'เฎš'], + 'weekdays_min' => ['เฎžเฎพ', 'เฎคเฎฟ', 'เฎšเฏ†', 'เฎชเฏ', 'เฎตเฎฟ', 'เฎตเฏ†', 'เฎš'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เฎ•เฎพเฎฒเฏˆ', 'เฎฎเฎพเฎฒเฏˆ'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_MY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_MY.php new file mode 100644 index 0000000000..a6cd8b517b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_MY.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'LT' => 'a h:mm', + 'LTS' => 'a h:mm:ss', + 'L' => 'D/M/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY, a h:mm', + 'LLLL' => 'dddd, D MMMM, YYYY, a h:mm', + ], + 'months' => ['เฎœเฎฉเฎตเฎฐเฎฟ', 'เฎชเฎฟเฎชเฏเฎฐเฎตเฎฐเฎฟ', 'เฎฎเฎพเฎฐเฏเฎšเฏ', 'เฎเฎชเฏเฎฐเฎฒเฏ', 'เฎฎเฏ‡', 'เฎœเฏ‚เฎฉเฏ', 'เฎœเฏ‚เฎฒเฏˆ', 'เฎ†เฎ•เฎธเฏเฎŸเฏ', 'เฎšเฏ†เฎชเฏเฎŸเฎฎเฏเฎชเฎฐเฏ', 'เฎ…เฎ•เฏเฎŸเฏ‹เฎชเฎฐเฏ', 'เฎจเฎตเฎฎเฏเฎชเฎฐเฏ', 'เฎŸเฎฟเฎšเฎฎเฏเฎชเฎฐเฏ'], + 'months_short' => ['เฎœเฎฉ.', 'เฎชเฎฟเฎชเฏ.', 'เฎฎเฎพเฎฐเฏ.', 'เฎเฎชเฏ.', 'เฎฎเฏ‡', 'เฎœเฏ‚เฎฉเฏ', 'เฎœเฏ‚เฎฒเฏˆ', 'เฎ†เฎ•.', 'เฎšเฏ†เฎชเฏ.', 'เฎ…เฎ•เฏ.', 'เฎจเฎต.', 'เฎŸเฎฟเฎš.'], + 'weekdays' => ['เฎžเฎพเฎฏเฎฟเฎฑเฏ', 'เฎคเฎฟเฎ™เฏเฎ•เฎณเฏ', 'เฎšเฏ†เฎตเฏเฎตเฎพเฎฏเฏ', 'เฎชเฏเฎคเฎฉเฏ', 'เฎตเฎฟเฎฏเฎพเฎดเฎฉเฏ', 'เฎตเฏ†เฎณเฏเฎณเฎฟ', 'เฎšเฎฉเฎฟ'], + 'weekdays_short' => ['เฎžเฎพเฎฏเฎฟ.', 'เฎคเฎฟเฎ™เฏ.', 'เฎšเฏ†เฎตเฏ.', 'เฎชเฏเฎค.', 'เฎตเฎฟเฎฏเฎพ.', 'เฎตเฏ†เฎณเฏ.', 'เฎšเฎฉเฎฟ'], + 'weekdays_min' => ['เฎžเฎพ', 'เฎคเฎฟ', 'เฎšเฏ†', 'เฎชเฏ', 'เฎตเฎฟ', 'เฎตเฏ†', 'เฎš'], + 'first_day_of_week' => 1, + 'meridiem' => ['เฎฎเฏ.เฎช', 'เฎชเฎฟ.เฎช'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_SG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_SG.php new file mode 100644 index 0000000000..7dbedeee8b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_SG.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'LT' => 'a h:mm', + 'LTS' => 'a h:mm:ss', + 'L' => 'D/M/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY, a h:mm', + 'LLLL' => 'dddd, D MMMM, YYYY, a h:mm', + ], + 'months' => ['เฎœเฎฉเฎตเฎฐเฎฟ', 'เฎชเฎฟเฎชเฏเฎฐเฎตเฎฐเฎฟ', 'เฎฎเฎพเฎฐเฏเฎšเฏ', 'เฎเฎชเฏเฎฐเฎฒเฏ', 'เฎฎเฏ‡', 'เฎœเฏ‚เฎฉเฏ', 'เฎœเฏ‚เฎฒเฏˆ', 'เฎ†เฎ•เฎธเฏเฎŸเฏ', 'เฎšเฏ†เฎชเฏเฎŸเฎฎเฏเฎชเฎฐเฏ', 'เฎ…เฎ•เฏเฎŸเฏ‹เฎชเฎฐเฏ', 'เฎจเฎตเฎฎเฏเฎชเฎฐเฏ', 'เฎŸเฎฟเฎšเฎฎเฏเฎชเฎฐเฏ'], + 'months_short' => ['เฎœเฎฉ.', 'เฎชเฎฟเฎชเฏ.', 'เฎฎเฎพเฎฐเฏ.', 'เฎเฎชเฏ.', 'เฎฎเฏ‡', 'เฎœเฏ‚เฎฉเฏ', 'เฎœเฏ‚เฎฒเฏˆ', 'เฎ†เฎ•.', 'เฎšเฏ†เฎชเฏ.', 'เฎ…เฎ•เฏ.', 'เฎจเฎต.', 'เฎŸเฎฟเฎš.'], + 'weekdays' => ['เฎžเฎพเฎฏเฎฟเฎฑเฏ', 'เฎคเฎฟเฎ™เฏเฎ•เฎณเฏ', 'เฎšเฏ†เฎตเฏเฎตเฎพเฎฏเฏ', 'เฎชเฏเฎคเฎฉเฏ', 'เฎตเฎฟเฎฏเฎพเฎดเฎฉเฏ', 'เฎตเฏ†เฎณเฏเฎณเฎฟ', 'เฎšเฎฉเฎฟ'], + 'weekdays_short' => ['เฎžเฎพเฎฏเฎฟ.', 'เฎคเฎฟเฎ™เฏ.', 'เฎšเฏ†เฎตเฏ.', 'เฎชเฏเฎค.', 'เฎตเฎฟเฎฏเฎพ.', 'เฎตเฏ†เฎณเฏ.', 'เฎšเฎฉเฎฟ'], + 'weekdays_min' => ['เฎžเฎพ', 'เฎคเฎฟ', 'เฎšเฏ†', 'เฎชเฏ', 'เฎตเฎฟ', 'เฎตเฏ†', 'เฎš'], + 'meridiem' => ['เฎฎเฏ.เฎช', 'เฎชเฎฟ.เฎช'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tcy.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tcy.php new file mode 100644 index 0000000000..2eb99057cd --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tcy.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tcy_IN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tcy_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tcy_IN.php new file mode 100644 index 0000000000..2ff20e0e66 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tcy_IN.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IndLinux.org, Samsung Electronics Co., Ltd. alexey.merzlyakov@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['เฒœเฒจเฒตเฒฐเฒฟ', 'เฒซเณ†เฒฌเณเฒฐเณเฒตเฒฐเฒฟ', 'เฒฎเฒพเฒฐเณเฒšเณ', 'เฒเฒชเณเฒฐเฒฟเฒฒเณโ€Œโ€Œ', 'เฒฎเณ‡', 'เฒœเณ‚เฒจเณ', 'เฒœเณเฒฒเณˆ', 'เฒ†เฒ—เฒธเณเฒŸเณ', 'เฒธเณ†เฒชเณเฒŸเณ†เฒ‚เฒฌเฒฐเณโ€Œ', 'เฒ…เฒ•เณเฒŸเณ‹เฒฌเฒฐเณ', 'เฒจเฒตเณ†เฒ‚เฒฌเฒฐเณ', 'เฒกเฒฟเฒธเณ†เฒ‚เฒฌเฒฐเณ'], + 'months_short' => ['เฒœ', 'เฒซเณ†', 'เฒฎเฒพ', 'เฒ', 'เฒฎเณ‡', 'เฒœเณ‚', 'เฒœเณ', 'เฒ†', 'เฒธเณ†', 'เฒ…', 'เฒจ', 'เฒกเฒฟ'], + 'weekdays' => ['เฒเฒฅเฒพเฒฐ', 'เฒธเณ‹เฒฎเฒพเฒฐ', 'เฒ…เฒ‚เฒ—เฒฐเณ†', 'เฒฌเณเฒงเฒพเฒฐ', 'เฒ—เณเฒฐเณเฒตเฒพเฒฐ', 'เฒถเณเฒ•เณเฒฐเฒฐ', 'เฒถเฒจเฒฟเฒตเฒพเฒฐ'], + 'weekdays_short' => ['เฒ', 'เฒธเณ‹', 'เฒ…เฒ‚', 'เฒฌเณ', 'เฒ—เณ', 'เฒถเณ', 'เฒถ'], + 'weekdays_min' => ['เฒ', 'เฒธเณ‹', 'เฒ…เฒ‚', 'เฒฌเณ', 'เฒ—เณ', 'เฒถเณ', 'เฒถ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เฒ•เฒพเฒ‚เฒกเณ†', 'เฒฌเฒฏเณเฒฏ'], + + 'year' => ':count เฒจเณ€เฒฐเณ', // less reliable + 'y' => ':count เฒจเณ€เฒฐเณ', // less reliable + 'a_year' => ':count เฒจเณ€เฒฐเณ', // less reliable + + 'month' => ':count เฒฎเณ€เฒจเณ', // less reliable + 'm' => ':count เฒฎเณ€เฒจเณ', // less reliable + 'a_month' => ':count เฒฎเณ€เฒจเณ', // less reliable + + 'day' => ':count เฒธเณเฒ—เณเฒ—เฒฟ', // less reliable + 'd' => ':count เฒธเณเฒ—เณเฒ—เฒฟ', // less reliable + 'a_day' => ':count เฒธเณเฒ—เณเฒ—เฒฟ', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/te.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/te.php new file mode 100644 index 0000000000..ac38218f33 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/te.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - Josh Soref + * - Franรงois B + * - kc + */ +return [ + 'year' => ':count เฐธเฐ‚เฐตเฐคเฑเฐธเฐฐเฐ‚|:count เฐธเฐ‚เฐตเฐคเฑเฐธเฐฐเฐพเฐฒเฑ', + 'a_year' => 'เฐ’เฐ• เฐธเฐ‚เฐตเฐคเฑเฐธเฐฐเฐ‚|:count เฐธเฐ‚เฐตเฐคเฑเฐธเฐฐเฐพเฐฒเฑ', + 'y' => ':count เฐธเฐ‚.', + 'month' => ':count เฐจเฑ†เฐฒ|:count เฐจเฑ†เฐฒเฐฒเฑ', + 'a_month' => 'เฐ’เฐ• เฐจเฑ†เฐฒ|:count เฐจเฑ†เฐฒเฐฒเฑ', + 'm' => ':count เฐจเฑ†เฐฒ|:count เฐจเฑ†เฐฒ.', + 'week' => ':count เฐตเฐพเฐฐเฐ‚|:count เฐตเฐพเฐฐเฐพเฐฒเฑ', + 'a_week' => 'เฐ’เฐ• เฐตเฐพเฐฐเฐ‚|:count เฐตเฐพเฐฐเฐพเฐฒเฑ', + 'w' => ':count เฐตเฐพเฐฐ.|:count เฐตเฐพเฐฐเฐพ.', + 'day' => ':count เฐฐเฑ‹เฐœเฑ|:count เฐฐเฑ‹เฐœเฑเฐฒเฑ', + 'a_day' => 'เฐ’เฐ• เฐฐเฑ‹เฐœเฑ|:count เฐฐเฑ‹เฐœเฑเฐฒเฑ', + 'd' => ':count เฐฐเฑ‹เฐœเฑ|:count เฐฐเฑ‹เฐœเฑ.', + 'hour' => ':count เฐ—เฐ‚เฐŸ|:count เฐ—เฐ‚เฐŸเฐฒเฑ', + 'a_hour' => 'เฐ’เฐ• เฐ—เฐ‚เฐŸ|:count เฐ—เฐ‚เฐŸเฐฒเฑ', + 'h' => ':count เฐ—เฐ‚.', + 'minute' => ':count เฐจเฐฟเฐฎเฐฟเฐทเฐ‚|:count เฐจเฐฟเฐฎเฐฟเฐทเฐพเฐฒเฑ', + 'a_minute' => 'เฐ’เฐ• เฐจเฐฟเฐฎเฐฟเฐทเฐ‚|:count เฐจเฐฟเฐฎเฐฟเฐทเฐพเฐฒเฑ', + 'min' => ':count เฐจเฐฟเฐฎเฐฟ.', + 'second' => ':count เฐธเฑ†เฐ•เฐจเฑ|:count เฐธเฑ†เฐ•เฐจเฑเฐฒเฑ', + 'a_second' => 'เฐ•เฑŠเฐจเฑเฐจเฐฟ เฐ•เฑเฐทเฐฃเฐพเฐฒเฑ|:count เฐธเฑ†เฐ•เฐจเฑเฐฒเฑ', + 's' => ':count เฐธเฑ†เฐ•.', + 'ago' => ':time เฐ•เฑเฐฐเฐฟเฐคเฐ‚', + 'from_now' => ':time เฐฒเฑ‹', + 'diff_now' => 'เฐชเฑเฐฐเฐธเฑเฐคเฑเฐคเฐ‚', + 'diff_today' => 'เฐจเฑ‡เฐกเฑ', + 'diff_yesterday' => 'เฐจเฐฟเฐจเฑเฐจ', + 'diff_tomorrow' => 'เฐฐเฑ‡เฐชเฑ', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm', + ], + 'calendar' => [ + 'sameDay' => '[เฐจเฑ‡เฐกเฑ] LT', + 'nextDay' => '[เฐฐเฑ‡เฐชเฑ] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[เฐจเฐฟเฐจเฑเฐจ] LT', + 'lastWeek' => '[เฐ—เฐค] dddd, LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberเฐต', + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'เฐฐเฐพเฐคเฑเฐฐเฐฟ'; + } + if ($hour < 10) { + return 'เฐ‰เฐฆเฐฏเฐ‚'; + } + if ($hour < 17) { + return 'เฐฎเฐงเฑเฐฏเฐพเฐนเฑเฐจเฐ‚'; + } + if ($hour < 20) { + return 'เฐธเฐพเฐฏเฐ‚เฐคเฑเฐฐเฐ‚'; + } + + return ' เฐฐเฐพเฐคเฑเฐฐเฐฟ'; + }, + 'months' => ['เฐœเฐจเฐตเฐฐเฐฟ', 'เฐซเฐฟเฐฌเฑเฐฐเฐตเฐฐเฐฟ', 'เฐฎเฐพเฐฐเฑเฐšเฐฟ', 'เฐเฐชเฑเฐฐเฐฟเฐฒเฑ', 'เฐฎเฑ‡', 'เฐœเฑ‚เฐจเฑ', 'เฐœเฑ‚เฐฒเฑ†เฑ–', 'เฐ†เฐ—เฐธเฑเฐŸเฑ', 'เฐธเฑ†เฐชเฑเฐŸเฑ†เฐ‚เฐฌเฐฐเฑ', 'เฐ…เฐ•เฑเฐŸเฑ‹เฐฌเฐฐเฑ', 'เฐจเฐตเฐ‚เฐฌเฐฐเฑ', 'เฐกเฐฟเฐธเฑ†เฐ‚เฐฌเฐฐเฑ'], + 'months_short' => ['เฐœเฐจ.', 'เฐซเฐฟเฐฌเฑเฐฐ.', 'เฐฎเฐพเฐฐเฑเฐšเฐฟ', 'เฐเฐชเฑเฐฐเฐฟ.', 'เฐฎเฑ‡', 'เฐœเฑ‚เฐจเฑ', 'เฐœเฑ‚เฐฒเฑ†เฑ–', 'เฐ†เฐ—.', 'เฐธเฑ†เฐชเฑ.', 'เฐ…เฐ•เฑเฐŸเฑ‹.', 'เฐจเฐต.', 'เฐกเฐฟเฐธเฑ†.'], + 'weekdays' => ['เฐ†เฐฆเฐฟเฐตเฐพเฐฐเฐ‚', 'เฐธเฑ‹เฐฎเฐตเฐพเฐฐเฐ‚', 'เฐฎเฐ‚เฐ—เฐณเฐตเฐพเฐฐเฐ‚', 'เฐฌเฑเฐงเฐตเฐพเฐฐเฐ‚', 'เฐ—เฑเฐฐเฑเฐตเฐพเฐฐเฐ‚', 'เฐถเฑเฐ•เฑเฐฐเฐตเฐพเฐฐเฐ‚', 'เฐถเฐจเฐฟเฐตเฐพเฐฐเฐ‚'], + 'weekdays_short' => ['เฐ†เฐฆเฐฟ', 'เฐธเฑ‹เฐฎ', 'เฐฎเฐ‚เฐ—เฐณ', 'เฐฌเฑเฐง', 'เฐ—เฑเฐฐเฑ', 'เฐถเฑเฐ•เฑเฐฐ', 'เฐถเฐจเฐฟ'], + 'weekdays_min' => ['เฐ†', 'เฐธเฑ‹', 'เฐฎเฐ‚', 'เฐฌเฑ', 'เฐ—เฑ', 'เฐถเฑ', 'เฐถ'], + 'list' => ', ', + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/te_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/te_IN.php new file mode 100644 index 0000000000..3963f8d508 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/te_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/te.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/teo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/teo.php new file mode 100644 index 0000000000..ca30c37d7b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/teo.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'meridiem' => ['Taparachu', 'Ebongi'], + 'weekdays' => ['Nakaejuma', 'Nakaebarasa', 'Nakaare', 'Nakauni', 'Nakaungโ€™on', 'Nakakany', 'Nakasabiti'], + 'weekdays_short' => ['Jum', 'Bar', 'Aar', 'Uni', 'Ung', 'Kan', 'Sab'], + 'weekdays_min' => ['Jum', 'Bar', 'Aar', 'Uni', 'Ung', 'Kan', 'Sab'], + 'months' => ['Orara', 'Omuk', 'Okwamgโ€™', 'Odungโ€™el', 'Omaruk', 'Omodokโ€™kingโ€™ol', 'Ojola', 'Opedel', 'Osokosokoma', 'Otibar', 'Olabor', 'Opoo'], + 'months_short' => ['Rar', 'Muk', 'Kwa', 'Dun', 'Mar', 'Mod', 'Jol', 'Ped', 'Sok', 'Tib', 'Lab', 'Poo'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/teo_KE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/teo_KE.php new file mode 100644 index 0000000000..010a04f54c --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/teo_KE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/teo.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tet.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tet.php new file mode 100644 index 0000000000..d0544d4e41 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tet.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Joshua Brooks + * - Franรงois B + */ +return [ + 'year' => 'tinan :count', + 'a_year' => '{1}tinan ida|tinan :count', + 'month' => 'fulan :count', + 'a_month' => '{1}fulan ida|fulan :count', + 'week' => 'semana :count', + 'a_week' => '{1}semana ida|semana :count', + 'day' => 'loron :count', + 'a_day' => '{1}loron ida|loron :count', + 'hour' => 'oras :count', + 'a_hour' => '{1}oras ida|oras :count', + 'minute' => 'minutu :count', + 'a_minute' => '{1}minutu ida|minutu :count', + 'second' => 'segundu :count', + 'a_second' => '{1}segundu balun|segundu :count', + 'ago' => ':time liuba', + 'from_now' => 'iha :time', + 'diff_yesterday' => 'Horiseik', + 'diff_yesterday_regexp' => 'Horiseik(?:\\s+iha)?', + 'diff_today' => 'Ohin', + 'diff_today_regexp' => 'Ohin(?:\\s+iha)?', + 'diff_tomorrow' => 'Aban', + 'diff_tomorrow_regexp' => 'Aban(?:\\s+iha)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Ohin iha] LT', + 'nextDay' => '[Aban iha] LT', + 'nextWeek' => 'dddd [iha] LT', + 'lastDay' => '[Horiseik iha] LT', + 'lastWeek' => 'dddd [semana kotuk] [iha] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberยบ', + 'months' => ['Janeiru', 'Fevereiru', 'Marsu', 'Abril', 'Maiu', 'Juรฑu', 'Jullu', 'Agustu', 'Setembru', 'Outubru', 'Novembru', 'Dezembru'], + 'months_short' => ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'], + 'weekdays' => ['Domingu', 'Segunda', 'Tersa', 'Kuarta', 'Kinta', 'Sesta', 'Sabadu'], + 'weekdays_short' => ['Dom', 'Seg', 'Ters', 'Kua', 'Kint', 'Sest', 'Sab'], + 'weekdays_min' => ['Do', 'Seg', 'Te', 'Ku', 'Ki', 'Ses', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tg.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tg.php new file mode 100644 index 0000000000..b7df893c13 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tg.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Orif N. Jr + */ +return [ + 'year' => '{1}ัะบ ัะพะป|:count ัะพะป', + 'month' => '{1}ัะบ ะผะพาณ|:count ะผะพาณ', + 'week' => '{1}ัะบ าณะฐั„ั‚ะฐ|:count าณะฐั„ั‚ะฐ', + 'day' => '{1}ัะบ ั€ำฏะท|:count ั€ำฏะท', + 'hour' => '{1}ัะบ ัะพะฐั‚|:count ัะพะฐั‚', + 'minute' => '{1}ัะบ ะดะฐา›ะธา›ะฐ|:count ะดะฐา›ะธา›ะฐ', + 'second' => '{1}ัะบั‡ะฐะฝะด ัะพะฝะธั|:count ัะพะฝะธั', + 'ago' => ':time ะฟะตัˆ', + 'from_now' => 'ะฑะฐัŠะดะธ :time', + 'diff_today' => 'ะ˜ะผั€ำฏะท', + 'diff_yesterday' => 'ะ”ะธั€ำฏะท', + 'diff_yesterday_regexp' => 'ะ”ะธั€ำฏะท(?:\\s+ัะพะฐั‚ะธ)?', + 'diff_tomorrow' => 'ะŸะฐะณะพาณ', + 'diff_tomorrow_regexp' => 'ะŸะฐะณะพาณ(?:\\s+ัะพะฐั‚ะธ)?', + 'diff_today_regexp' => 'ะ˜ะผั€ำฏะท(?:\\s+ัะพะฐั‚ะธ)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ะ˜ะผั€ำฏะท ัะพะฐั‚ะธ] LT', + 'nextDay' => '[ะŸะฐะณะพาณ ัะพะฐั‚ะธ] LT', + 'nextWeek' => 'dddd[ะธ] [าณะฐั„ั‚ะฐะธ ะพัะฝะดะฐ ัะพะฐั‚ะธ] LT', + 'lastDay' => '[ะ”ะธั€ำฏะท ัะพะฐั‚ะธ] LT', + 'lastWeek' => 'dddd[ะธ] [าณะฐั„ั‚ะฐะธ ะณัƒะทะฐัˆั‚ะฐ ัะพะฐั‚ะธ] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + if ($number === 0) { // special case for zero + return "$number-ฤฑncฤฑ"; + } + + static $suffixes = [ + 0 => '-ัƒะผ', + 1 => '-ัƒะผ', + 2 => '-ัŽะผ', + 3 => '-ัŽะผ', + 4 => '-ัƒะผ', + 5 => '-ัƒะผ', + 6 => '-ัƒะผ', + 7 => '-ัƒะผ', + 8 => '-ัƒะผ', + 9 => '-ัƒะผ', + 10 => '-ัƒะผ', + 12 => '-ัƒะผ', + 13 => '-ัƒะผ', + 20 => '-ัƒะผ', + 30 => '-ัŽะผ', + 40 => '-ัƒะผ', + 50 => '-ัƒะผ', + 60 => '-ัƒะผ', + 70 => '-ัƒะผ', + 80 => '-ัƒะผ', + 90 => '-ัƒะผ', + 100 => '-ัƒะผ', + ]; + + return $number.($suffixes[$number] ?? $suffixes[$number % 10] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ัˆะฐะฑ'; + } + if ($hour < 11) { + return 'ััƒะฑาณ'; + } + if ($hour < 16) { + return 'ั€ำฏะท'; + } + if ($hour < 19) { + return 'ะฑะตะณะพาณ'; + } + + return 'ัˆะฐะฑ'; + }, + 'months' => ['ัะฝะฒะฐั€', 'ั„ะตะฒั€ะฐะป', 'ะผะฐั€ั‚', 'ะฐะฟั€ะตะป', 'ะผะฐะน', 'ะธัŽะฝ', 'ะธัŽะป', 'ะฐะฒะณัƒัั‚', 'ัะตะฝั‚ัะฑั€', 'ะพะบั‚ัะฑั€', 'ะฝะพัะฑั€', 'ะดะตะบะฐะฑั€'], + 'months_short' => ['ัะฝะฒ', 'ั„ะตะฒ', 'ะผะฐั€', 'ะฐะฟั€', 'ะผะฐะน', 'ะธัŽะฝ', 'ะธัŽะป', 'ะฐะฒะณ', 'ัะตะฝ', 'ะพะบั‚', 'ะฝะพั', 'ะดะตะบ'], + 'weekdays' => ['ัะบัˆะฐะฝะฑะต', 'ะดัƒัˆะฐะฝะฑะต', 'ัะตัˆะฐะฝะฑะต', 'ั‡ะพั€ัˆะฐะฝะฑะต', 'ะฟะฐะฝาทัˆะฐะฝะฑะต', 'าทัƒะผัŠะฐ', 'ัˆะฐะฝะฑะต'], + 'weekdays_short' => ['ััˆะฑ', 'ะดัˆะฑ', 'ััˆะฑ', 'ั‡ัˆะฑ', 'ะฟัˆะฑ', 'าทัƒะผ', 'ัˆะฝะฑ'], + 'weekdays_min' => ['ััˆ', 'ะดัˆ', 'ััˆ', 'ั‡ัˆ', 'ะฟัˆ', 'าทะผ', 'ัˆะฑ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ะฒะฐ '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tg_TJ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tg_TJ.php new file mode 100644 index 0000000000..badc7d1f2b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tg_TJ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/tg.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/th.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/th.php new file mode 100644 index 0000000000..6397f6e46e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/th.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Nate Whittaker + * - John MacAslan + * - Chanintorn Asavavichairoj + * - JD Isaacks + * - ROKAISAKKON + * - RO'KAISAKKON + * - Andreas Moฬˆller + * - nithisa + */ +return [ + 'year' => ':count เธ›เธต', + 'y' => ':count เธ›เธต', + 'month' => ':count เน€เธ”เธทเธญเธ™', + 'm' => ':count เน€เธ”เธทเธญเธ™', + 'week' => ':count เธชเธฑเธ›เธ”เธฒเธซเนŒ', + 'w' => ':count เธชเธฑเธ›เธ”เธฒเธซเนŒ', + 'day' => ':count เธงเธฑเธ™', + 'd' => ':count เธงเธฑเธ™', + 'hour' => ':count เธŠเธฑเนˆเธงเน‚เธกเธ‡', + 'h' => ':count เธŠเธฑเนˆเธงเน‚เธกเธ‡', + 'minute' => ':count เธ™เธฒเธ—เธต', + 'min' => ':count เธ™เธฒเธ—เธต', + 'second' => ':count เธงเธดเธ™เธฒเธ—เธต', + 'a_second' => '{1}เน„เธกเนˆเธเธตเนˆเธงเธดเธ™เธฒเธ—เธต|]1,Inf[:count เธงเธดเธ™เธฒเธ—เธต', + 's' => ':count เธงเธดเธ™เธฒเธ—เธต', + 'ago' => ':timeเธ—เธตเนˆเนเธฅเน‰เธง', + 'from_now' => 'เธญเธตเธ :time', + 'after' => ':timeเธซเธฅเธฑเธ‡เธˆเธฒเธเธ™เธตเน‰', + 'before' => ':timeเธเนˆเธญเธ™', + 'diff_now' => 'เธ‚เธ“เธฐเธ™เธตเน‰', + 'diff_today' => 'เธงเธฑเธ™เธ™เธตเน‰', + 'diff_today_regexp' => 'เธงเธฑเธ™เธ™เธตเน‰(?:\\s+เน€เธงเธฅเธฒ)?', + 'diff_yesterday' => 'เน€เธกเธทเนˆเธญเธงเธฒเธ™', + 'diff_yesterday_regexp' => 'เน€เธกเธทเนˆเธญเธงเธฒเธ™เธ™เธตเน‰(?:\\s+เน€เธงเธฅเธฒ)?', + 'diff_tomorrow' => 'เธžเธฃเธธเนˆเธ‡เธ™เธตเน‰', + 'diff_tomorrow_regexp' => 'เธžเธฃเธธเนˆเธ‡เธ™เธตเน‰(?:\\s+เน€เธงเธฅเธฒ)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY เน€เธงเธฅเธฒ H:mm', + 'LLLL' => 'เธงเธฑเธ™ddddเธ—เธตเนˆ D MMMM YYYY เน€เธงเธฅเธฒ H:mm', + ], + 'calendar' => [ + 'sameDay' => '[เธงเธฑเธ™เธ™เธตเน‰ เน€เธงเธฅเธฒ] LT', + 'nextDay' => '[เธžเธฃเธธเนˆเธ‡เธ™เธตเน‰ เน€เธงเธฅเธฒ] LT', + 'nextWeek' => 'dddd[เธซเธ™เน‰เธฒ เน€เธงเธฅเธฒ] LT', + 'lastDay' => '[เน€เธกเธทเนˆเธญเธงเธฒเธ™เธ™เธตเน‰ เน€เธงเธฅเธฒ] LT', + 'lastWeek' => '[เธงเธฑเธ™]dddd[เธ—เธตเนˆเนเธฅเน‰เธง เน€เธงเธฅเธฒ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['เธเนˆเธญเธ™เน€เธ—เธตเนˆเธขเธ‡', 'เธซเธฅเธฑเธ‡เน€เธ—เธตเนˆเธขเธ‡'], + 'months' => ['เธกเธเธฃเธฒเธ„เธก', 'เธเธธเธกเธ เธฒเธžเธฑเธ™เธ˜เนŒ', 'เธกเธตเธ™เธฒเธ„เธก', 'เน€เธกเธฉเธฒเธขเธ™', 'เธžเธคเธฉเธ เธฒเธ„เธก', 'เธกเธดเธ–เธธเธ™เธฒเธขเธ™', 'เธเธฃเธเธŽเธฒเธ„เธก', 'เธชเธดเธ‡เธซเธฒเธ„เธก', 'เธเธฑเธ™เธขเธฒเธขเธ™', 'เธ•เธธเธฅเธฒเธ„เธก', 'เธžเธคเธจเธˆเธดเธเธฒเธขเธ™', 'เธ˜เธฑเธ™เธงเธฒเธ„เธก'], + 'months_short' => ['เธก.เธ„.', 'เธ.เธž.', 'เธกเธต.เธ„.', 'เน€เธก.เธข.', 'เธž.เธ„.', 'เธกเธด.เธข.', 'เธ.เธ„.', 'เธช.เธ„.', 'เธ.เธข.', 'เธ•.เธ„.', 'เธž.เธข.', 'เธ˜.เธ„.'], + 'weekdays' => ['เธญเธฒเธ—เธดเธ•เธขเนŒ', 'เธˆเธฑเธ™เธ—เธฃเนŒ', 'เธญเธฑเธ‡เธ„เธฒเธฃ', 'เธžเธธเธ˜', 'เธžเธคเธซเธฑเธชเธšเธ”เธต', 'เธจเธธเธเธฃเนŒ', 'เน€เธชเธฒเธฃเนŒ'], + 'weekdays_short' => ['เธญเธฒเธ—เธดเธ•เธขเนŒ', 'เธˆเธฑเธ™เธ—เธฃเนŒ', 'เธญเธฑเธ‡เธ„เธฒเธฃ', 'เธžเธธเธ˜', 'เธžเธคเธซเธฑเธช', 'เธจเธธเธเธฃเนŒ', 'เน€เธชเธฒเธฃเนŒ'], + 'weekdays_min' => ['เธญเธฒ.', 'เธˆ.', 'เธญ.', 'เธž.', 'เธžเธค.', 'เธจ.', 'เธช.'], + 'list' => [', ', ' เนเธฅเธฐ '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/th_TH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/th_TH.php new file mode 100644 index 0000000000..b9f94b2dd6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/th_TH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/th.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/the.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/the.php new file mode 100644 index 0000000000..85f8333b8d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/the.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/the_NP.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/the_NP.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/the_NP.php new file mode 100644 index 0000000000..34da162789 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/the_NP.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Chitwanix OS Development info@chitwanix.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคผเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅ‡เคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเคฎเฅเคฌเคฐ', 'เค…เค•เฅเคŸเฅ‚เคฌเคฐ', 'เคจเคตเคฎเฅเคฌเคฐ', 'เคฆเคฟเคธเคฎเฅเคฌเคฐ'], + 'months_short' => ['เคœเคจเคตเคฐเฅ€', 'เคซเคผเคฐเคตเคฐเฅ€', 'เคฎเคพเคฐเฅเคš', 'เค…เคชเฅเคฐเฅ‡เคฒ', 'เคฎเคˆ', 'เคœเฅ‚เคจ', 'เคœเฅเคฒเคพเคˆ', 'เค…เค—เคธเฅเคค', 'เคธเคฟเคคเคฎเฅเคฌเคฐ', 'เค…เค•เฅเคŸเฅ‚เคฌเคฐ', 'เคจเคตเคฎเฅเคฌเคฐ', 'เคฆเคฟเคธเคฎเฅเคฌเคฐ'], + 'weekdays' => ['เค†เค‡เคคเคฌเคพเคฐ', 'เคธเฅ‹เคฎเคฌเคพเคฐ', 'เคฎเค‚เค—เคฒเคฌเคพเคฐ', 'เคฌเฅเคงเคฌเคพเคฐ', 'เคฌเคฟเคนเคฟเคฌเคพเคฐ', 'เคถเฅเค•เฅเคฐเคฌเคพเคฐ', 'เคถเคจเคฟเคฌเคพเคฐ'], + 'weekdays_short' => ['เค†เค‡เคค', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคง', 'เคฌเคฟเคนเคฟ', 'เคถเฅเค•เฅเคฐ', 'เคถเคจเคฟ'], + 'weekdays_min' => ['เค†เค‡เคค', 'เคธเฅ‹เคฎ', 'เคฎเค‚เค—เคฒ', 'เคฌเฅเคง', 'เคฌเคฟเคนเคฟ', 'เคถเฅเค•เฅเคฐ', 'เคถเคจเคฟ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['เคชเฅ‚เคฐเฅเคตเคพเคนเฅเคจ', 'เค…เคชเคฐเคพเคนเฅเคจ'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ti.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ti.php new file mode 100644 index 0000000000..ffd3236946 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ti.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ti_ER.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ti_ER.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ti_ER.php new file mode 100644 index 0000000000..310c51ccea --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ti_ER.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['แŒฅแˆช', 'แˆˆแŠซแ‰ฒแ‰ต', 'แˆ˜แŒ‹แ‰ขแ‰ต', 'แˆšแ‹ซแ‹แ‹ซ', 'แŒแŠ•แ‰ฆแ‰ต', 'แˆฐแА', 'แˆ“แˆแˆˆ', 'แАแˆ“แˆฐ', 'แˆ˜แˆตแŠจแˆจแˆ', 'แŒฅแ‰…แˆแ‰ฒ', 'แˆ•แ‹ณแˆญ', 'แ‰ณแˆ•แˆณแˆต'], + 'months_short' => ['แŒฅแˆช ', 'แˆˆแŠซแ‰ฒ', 'แˆ˜แŒ‹แ‰ข', 'แˆšแ‹ซแ‹', 'แŒแŠ•แ‰ฆ', 'แˆฐแА ', 'แˆ“แˆแˆˆ', 'แАแˆ“แˆฐ', 'แˆ˜แˆตแŠจ', 'แŒฅแ‰…แˆ', 'แˆ•แ‹ณแˆญ', 'แ‰ณแˆ•แˆณ'], + 'weekdays' => ['แˆฐแŠ•แ‰ แ‰ต', 'แˆฐแŠ‘แ‹ญ', 'แˆฐแˆ‰แˆต', 'แˆจแ‰กแ‹•', 'แˆ“แˆ™แˆต', 'แ‹“แˆญแ‰ข', 'แ‰€แ‹ณแˆ'], + 'weekdays_short' => ['แˆฐแŠ•แ‰ ', 'แˆฐแŠ‘แ‹ญ', 'แˆฐแˆ‰แˆต', 'แˆจแ‰กแ‹•', 'แˆ“แˆ™แˆต', 'แ‹“แˆญแ‰ข', 'แ‰€แ‹ณแˆ'], + 'weekdays_min' => ['แˆฐแŠ•แ‰ ', 'แˆฐแŠ‘แ‹ญ', 'แˆฐแˆ‰แˆต', 'แˆจแ‰กแ‹•', 'แˆ“แˆ™แˆต', 'แ‹“แˆญแ‰ข', 'แ‰€แ‹ณแˆ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['แŠ•แŒ‰แˆ† แˆฐแ‹“แ‰ฐ', 'แ‹ตแˆ•แˆญ แˆฐแ‹“แ‰ต'], + + 'year' => ':count แ‹“แˆ˜แ‰ต', + 'y' => ':count แ‹“แˆ˜แ‰ต', + 'a_year' => ':count แ‹“แˆ˜แ‰ต', + + 'month' => 'แ‹ˆแˆญแˆ’ :count', + 'm' => 'แ‹ˆแˆญแˆ’ :count', + 'a_month' => 'แ‹ˆแˆญแˆ’ :count', + + 'week' => ':count แˆฐแˆ™แŠ•', + 'w' => ':count แˆฐแˆ™แŠ•', + 'a_week' => ':count แˆฐแˆ™แŠ•', + + 'day' => ':count แˆ˜แ‹“แˆแ‰ฒ', + 'd' => ':count แˆ˜แ‹“แˆแ‰ฒ', + 'a_day' => ':count แˆ˜แ‹“แˆแ‰ฒ', + + 'hour' => ':count แˆฐแ‹“แ‰ต', + 'h' => ':count แˆฐแ‹“แ‰ต', + 'a_hour' => ':count แˆฐแ‹“แ‰ต', + + 'minute' => ':count แ‹ฐแ‰’แ‰•', + 'min' => ':count แ‹ฐแ‰’แ‰•', + 'a_minute' => ':count แ‹ฐแ‰’แ‰•', + + 'second' => ':count แˆฐแŠจแŠ•แ‹ต', + 's' => ':count แˆฐแŠจแŠ•แ‹ต', + 'a_second' => ':count แˆฐแŠจแŠ•แ‹ต', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ti_ET.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ti_ET.php new file mode 100644 index 0000000000..024217f2fd --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ti_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['แŒƒแŠ•แ‹ฉแ‹ˆแˆช', 'แŒแ‰ฅแˆฉแ‹ˆแˆช', 'แˆ›แˆญแ‰ฝ', 'แŠคแ•แˆจแˆ', 'แˆœแ‹ญ', 'แŒแŠ•', 'แŒแˆ‹แ‹ญ', 'แŠฆแŒˆแˆตแ‰ต', 'แˆดแ•แ‰ดแˆแ‰ แˆญ', 'แŠฆแŠญแ‰ฐแ‹แ‰ แˆญ', 'แŠ–แ‰ฌแˆแ‰ แˆญ', 'แ‹ฒแˆดแˆแ‰ แˆญ'], + 'months_short' => ['แŒƒแŠ•แ‹ฉ', 'แŒแ‰ฅแˆฉ', 'แˆ›แˆญแ‰ฝ', 'แŠคแ•แˆจ', 'แˆœแ‹ญ ', 'แŒแŠ• ', 'แŒแˆ‹แ‹ญ', 'แŠฆแŒˆแˆต', 'แˆดแ•แ‰ด', 'แŠฆแŠญแ‰ฐ', 'แŠ–แ‰ฌแˆ', 'แ‹ฒแˆดแˆ'], + 'weekdays' => ['แˆฐแŠ•แ‰ แ‰ต', 'แˆฐแŠ‘แ‹ญ', 'แˆฐแˆ‰แˆต', 'แˆจแ‰กแ‹•', 'แˆ“แˆ™แˆต', 'แ‹“แˆญแ‰ข', 'แ‰€แ‹ณแˆ'], + 'weekdays_short' => ['แˆฐแŠ•แ‰ ', 'แˆฐแŠ‘แ‹ญ', 'แˆฐแˆ‰แˆต', 'แˆจแ‰กแ‹•', 'แˆ“แˆ™แˆต', 'แ‹“แˆญแ‰ข', 'แ‰€แ‹ณแˆ'], + 'weekdays_min' => ['แˆฐแŠ•แ‰ ', 'แˆฐแŠ‘แ‹ญ', 'แˆฐแˆ‰แˆต', 'แˆจแ‰กแ‹•', 'แˆ“แˆ™แˆต', 'แ‹“แˆญแ‰ข', 'แ‰€แ‹ณแˆ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['แŠ•แŒ‰แˆ† แˆฐแ‹“แ‰ฐ', 'แ‹ตแˆ•แˆญ แˆฐแ‹“แ‰ต'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tig.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tig.php new file mode 100644 index 0000000000..186fe713c0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tig.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tig_ER.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tig_ER.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tig_ER.php new file mode 100644 index 0000000000..46887b05d3 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tig_ER.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['แŒฅแˆช', 'แˆˆแŠซแ‰ฒแ‰ต', 'แˆ˜แŒ‹แ‰ขแ‰ต', 'แˆšแ‹ซแ‹แ‹ซ', 'แŒแŠ•แ‰ฆแ‰ต', 'แˆฐแА', 'แˆ“แˆแˆˆ', 'แАแˆ“แˆฐ', 'แˆ˜แˆตแŠจแˆจแˆ', 'แŒฅแ‰…แˆแ‰ฒ', 'แˆ•แ‹ณแˆญ', 'แ‰ณแˆ•แˆณแˆต'], + 'months_short' => ['แŒฅแˆช ', 'แˆˆแŠซแ‰ฒ', 'แˆ˜แŒ‹แ‰ข', 'แˆšแ‹ซแ‹', 'แŒแŠ•แ‰ฆ', 'แˆฐแА ', 'แˆ“แˆแˆˆ', 'แАแˆ“แˆฐ', 'แˆ˜แˆตแŠจ', 'แŒฅแ‰…แˆ', 'แˆ•แ‹ณแˆญ', 'แ‰ณแˆ•แˆณ'], + 'weekdays' => ['แˆฐแŠ•แ‰ แ‰ต แ‹“แ‰ฃแ‹ญ', 'แˆฐแŠ–', 'แ‰ณแˆ‹แˆธแŠ–', 'แŠฃแˆจแˆญแ‰ฃแ‹“', 'แŠจแˆšแˆฝ', 'แŒ…แˆแ‹“แ‰ต', 'แˆฐแŠ•แ‰ แ‰ต แŠ•แŠขแˆฝ'], + 'weekdays_short' => ['แˆฐ//แ‹“', 'แˆฐแŠ– ', 'แ‰ณแˆ‹แˆธ', 'แŠฃแˆจแˆญ', 'แŠจแˆšแˆฝ', 'แŒ…แˆแ‹“', 'แˆฐ//แŠ•'], + 'weekdays_min' => ['แˆฐ//แ‹“', 'แˆฐแŠ– ', 'แ‰ณแˆ‹แˆธ', 'แŠฃแˆจแˆญ', 'แŠจแˆšแˆฝ', 'แŒ…แˆแ‹“', 'แˆฐ//แŠ•'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['แ‰€แ‹ฐแˆ แˆฐแˆญ แˆแ‹•แˆ', 'แˆ“แ‰† แˆฐแˆญ แˆแ‹•แˆ'], + + 'year' => ':count แˆ›แ‹ญ', // less reliable + 'y' => ':count แˆ›แ‹ญ', // less reliable + 'a_year' => ':count แˆ›แ‹ญ', // less reliable + + 'month' => ':count แˆธแˆแˆฝ', // less reliable + 'm' => ':count แˆธแˆแˆฝ', // less reliable + 'a_month' => ':count แˆธแˆแˆฝ', // less reliable + + 'week' => ':count แˆฐแ‰กแ‹•', // less reliable + 'w' => ':count แˆฐแ‰กแ‹•', // less reliable + 'a_week' => ':count แˆฐแ‰กแ‹•', // less reliable + + 'day' => ':count แ‹Žแˆฎ', // less reliable + 'd' => ':count แ‹Žแˆฎ', // less reliable + 'a_day' => ':count แ‹Žแˆฎ', // less reliable + + 'hour' => ':count แˆฐแ‹“แ‰ต', // less reliable + 'h' => ':count แˆฐแ‹“แ‰ต', // less reliable + 'a_hour' => ':count แˆฐแ‹“แ‰ต', // less reliable + + 'minute' => ':count แŠซแˆแŠฃแ‹ญแ‰ต', // less reliable + 'min' => ':count แŠซแˆแŠฃแ‹ญแ‰ต', // less reliable + 'a_minute' => ':count แŠซแˆแŠฃแ‹ญแ‰ต', // less reliable + + 'second' => ':count แŠซแˆแŠฃแ‹ญ', + 's' => ':count แŠซแˆแŠฃแ‹ญ', + 'a_second' => ':count แŠซแˆแŠฃแ‹ญ', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tk.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tk.php new file mode 100644 index 0000000000..d8f7d19d68 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tk.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tk_TM.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tk_TM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tk_TM.php new file mode 100644 index 0000000000..f949a43000 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tk_TM.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Ghorban M. Tavakoly Pablo Saratxaga & Ghorban M. Tavakoly pablo@walon.org & gmt314@yahoo.com + * - SuperManPHP + * - Maksat Meredow (isadma) + */ +$transformDiff = function ($input) { + return strtr($input, [ + 'sekunt' => 'sekunt', + 'hepde' => 'hepde', + ]); +}; + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['รanwar', 'Fewral', 'Mart', 'Aprel', 'Maรฝ', 'Iรฝun', 'Iรฝul', 'Awgust', 'Sentรฝabr', 'Oktรฝabr', 'Noรฝabr', 'Dekabr'], + 'months_short' => ['รan', 'Few', 'Mar', 'Apr', 'Maรฝ', 'Iรฝn', 'Iรฝl', 'Awg', 'Sen', 'Okt', 'Noรฝ', 'Dek'], + 'weekdays' => ['DuลŸenbe', 'SiลŸenbe', 'ร‡arลŸenbe', 'PenลŸenbe', 'Anna', 'ลženbe', 'รekลŸenbe'], + 'weekdays_short' => ['DuลŸ', 'SiลŸ', 'ร‡ar', 'Pen', 'Ann', 'ลžen', 'รek'], + 'weekdays_min' => ['Du', 'Si', 'ร‡a', 'Pe', 'An', 'ลže', 'รe'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count รฝyl', + 'y' => ':count รฝyl', + 'a_year' => ':count รฝyl', + + 'month' => ':count aรฝ', + 'm' => ':count aรฝ', + 'a_month' => ':count aรฝ', + + 'week' => ':count hepde', + 'w' => ':count hepde', + 'a_week' => ':count hepde', + + 'day' => ':count gรผn', + 'd' => ':count gรผn', + 'a_day' => ':count gรผn', + + 'hour' => ':count sagat', + 'h' => ':count sagat', + 'a_hour' => ':count sagat', + + 'minute' => ':count minut', + 'min' => ':count minut', + 'a_minute' => ':count minut', + + 'second' => ':count sekunt', + 's' => ':count sekunt', + 'a_second' => ':count sekunt', + + 'ago' => function ($time) use ($transformDiff) { + return $transformDiff($time).' ozal'; + }, + 'from_now' => function ($time) use ($transformDiff) { + return $transformDiff($time).' soลˆra'; + }, + 'after' => function ($time) use ($transformDiff) { + return $transformDiff($time).' soลˆ'; + }, + 'before' => function ($time) use ($transformDiff) { + return $transformDiff($time).' รถลˆ'; + }, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tl.php new file mode 100644 index 0000000000..410a2660f1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tl.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'year' => ':count taon', + 'a_year' => '{1}isang taon|:count taon', + 'month' => ':count buwan', + 'a_month' => '{1}isang buwan|:count buwan', + 'week' => ':count linggo', + 'a_week' => '{1}isang linggo|:count linggo', + 'day' => ':count araw', + 'a_day' => '{1}isang araw|:count araw', + 'hour' => ':count oras', + 'a_hour' => '{1}isang oras|:count oras', + 'minute' => ':count minuto', + 'a_minute' => '{1}isang minuto|:count minuto', + 'min' => ':count min.', + 'second' => ':count segundo', + 'a_second' => '{1}ilang segundo|:count segundo', + 's' => ':count seg.', + 'ago' => ':time ang nakalipas', + 'from_now' => 'sa loob ng :time', + 'diff_now' => 'ngayon', + 'diff_today' => 'ngayong', + 'diff_today_regexp' => 'ngayong(?:\\s+araw)?', + 'diff_yesterday' => 'kahapon', + 'diff_tomorrow' => 'bukas', + 'diff_tomorrow_regexp' => 'Bukas(?:\\s+ng)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'MM/D/YYYY', + 'LL' => 'MMMM D, YYYY', + 'LLL' => 'MMMM D, YYYY HH:mm', + 'LLLL' => 'dddd, MMMM DD, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => 'LT [ngayong araw]', + 'nextDay' => '[Bukas ng] LT', + 'nextWeek' => 'LT [sa susunod na] dddd', + 'lastDay' => 'LT [kahapon]', + 'lastWeek' => 'LT [noong nakaraang] dddd', + 'sameElse' => 'L', + ], + 'months' => ['Enero', 'Pebrero', 'Marso', 'Abril', 'Mayo', 'Hunyo', 'Hulyo', 'Agosto', 'Setyembre', 'Oktubre', 'Nobyembre', 'Disyembre'], + 'months_short' => ['Ene', 'Peb', 'Mar', 'Abr', 'May', 'Hun', 'Hul', 'Ago', 'Set', 'Okt', 'Nob', 'Dis'], + 'weekdays' => ['Linggo', 'Lunes', 'Martes', 'Miyerkules', 'Huwebes', 'Biyernes', 'Sabado'], + 'weekdays_short' => ['Lin', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'], + 'weekdays_min' => ['Li', 'Lu', 'Ma', 'Mi', 'Hu', 'Bi', 'Sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' at '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tl_PH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tl_PH.php new file mode 100644 index 0000000000..95f508c344 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tl_PH.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Ian De La Cruz + * - JD Isaacks + */ +return require __DIR__.'/tl.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tlh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tlh.php new file mode 100644 index 0000000000..fbf9e6f722 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tlh.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Serhan Apaydฤฑn + * - Dominika + */ +return [ + 'year' => '{1}waโ€™ DIS|:count DIS', + 'month' => '{1}waโ€™ jar|:count jar', + 'week' => '{1}waโ€™ hogh|:count hogh', + 'day' => '{1}waโ€™ jaj|:count jaj', + 'hour' => '{1}waโ€™ rep|:count rep', + 'minute' => '{1}waโ€™ tup|:count tup', + 'second' => '{1}puS lup|:count lup', + 'ago' => function ($time) { + $output = strtr($time, [ + 'jaj' => 'Huโ€™', + 'jar' => 'wen', + 'DIS' => 'ben', + ]); + + return $output === $time ? "$time ret" : $output; + }, + 'from_now' => function ($time) { + $output = strtr($time, [ + 'jaj' => 'leS', + 'jar' => 'waQ', + 'DIS' => 'nem', + ]); + + return $output === $time ? "$time pIq" : $output; + }, + 'diff_yesterday' => 'waโ€™Huโ€™', + 'diff_today' => 'DaHjaj', + 'diff_tomorrow' => 'waโ€™leS', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[DaHjaj] LT', + 'nextDay' => '[waโ€™leS] LT', + 'nextWeek' => 'LLL', + 'lastDay' => '[waโ€™Huโ€™] LT', + 'lastWeek' => 'LLL', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['teraโ€™ jar waโ€™', 'teraโ€™ jar chaโ€™', 'teraโ€™ jar wej', 'teraโ€™ jar loS', 'teraโ€™ jar vagh', 'teraโ€™ jar jav', 'teraโ€™ jar Soch', 'teraโ€™ jar chorgh', 'teraโ€™ jar Hut', 'teraโ€™ jar waโ€™maH', 'teraโ€™ jar waโ€™maH waโ€™', 'teraโ€™ jar waโ€™maH chaโ€™'], + 'months_short' => ['jar waโ€™', 'jar chaโ€™', 'jar wej', 'jar loS', 'jar vagh', 'jar jav', 'jar Soch', 'jar chorgh', 'jar Hut', 'jar waโ€™maH', 'jar waโ€™maH waโ€™', 'jar waโ€™maH chaโ€™'], + 'weekdays' => ['lojmItjaj', 'DaSjaj', 'povjaj', 'ghItlhjaj', 'loghjaj', 'buqjaj', 'ghInjaj'], + 'weekdays_short' => ['lojmItjaj', 'DaSjaj', 'povjaj', 'ghItlhjaj', 'loghjaj', 'buqjaj', 'ghInjaj'], + 'weekdays_min' => ['lojmItjaj', 'DaSjaj', 'povjaj', 'ghItlhjaj', 'loghjaj', 'buqjaj', 'ghInjaj'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' โ€™ej '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tn.php new file mode 100644 index 0000000000..f29bdf680d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tn_ZA.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tn_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tn_ZA.php new file mode 100644 index 0000000000..aada7db58a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tn_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Ferikgong', 'Tlhakole', 'Mopitlwe', 'Moranang', 'Motsheganong', 'Seetebosigo', 'Phukwi', 'Phatwe', 'Lwetse', 'Diphalane', 'Ngwanatsele', 'Sedimonthole'], + 'months_short' => ['Fer', 'Tlh', 'Mop', 'Mor', 'Mot', 'See', 'Phu', 'Pha', 'Lwe', 'Dip', 'Ngw', 'Sed'], + 'weekdays' => ['laTshipi', 'Mosupologo', 'Labobedi', 'Laboraro', 'Labone', 'Labotlhano', 'Lamatlhatso'], + 'weekdays_short' => ['Tsh', 'Mos', 'Bed', 'Rar', 'Ne', 'Tlh', 'Mat'], + 'weekdays_min' => ['Tsh', 'Mos', 'Bed', 'Rar', 'Ne', 'Tlh', 'Mat'], + 'day_of_first_week_of_year' => 1, + + 'year' => 'dingwaga di le :count', + 'y' => 'dingwaga di le :count', + 'a_year' => 'dingwaga di le :count', + + 'month' => 'dikgwedi di le :count', + 'm' => 'dikgwedi di le :count', + 'a_month' => 'dikgwedi di le :count', + + 'week' => 'dibeke di le :count', + 'w' => 'dibeke di le :count', + 'a_week' => 'dibeke di le :count', + + 'day' => 'malatsi :count', + 'd' => 'malatsi :count', + 'a_day' => 'malatsi :count', + + 'hour' => 'diura di le :count', + 'h' => 'diura di le :count', + 'a_hour' => 'diura di le :count', + + 'minute' => 'metsotso e le :count', + 'min' => 'metsotso e le :count', + 'a_minute' => 'metsotso e le :count', + + 'second' => 'metsotswana e le :count', + 's' => 'metsotswana e le :count', + 'a_second' => 'metsotswana e le :count', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/to.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/to.php new file mode 100644 index 0000000000..20581bbacf --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/to.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/to_TO.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/to_TO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/to_TO.php new file mode 100644 index 0000000000..335c69a859 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/to_TO.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - International Components for Unicode akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['Sฤnuali', 'Fฤ“pueli', 'Maสปasi', 'สปEpeleli', 'Mฤ“', 'Sune', 'Siulai', 'สปAokosi', 'Sepitema', 'สปOkatopa', 'Nลvema', 'Tฤซsema'], + 'months_short' => ['Sฤn', 'Fฤ“p', 'Maสปa', 'สปEpe', 'Mฤ“', 'Sun', 'Siu', 'สปAok', 'Sep', 'สปOka', 'Nลv', 'Tฤซs'], + 'weekdays' => ['Sฤpate', 'Mลnite', 'Tลซsite', 'Pulelulu', 'Tuสปapulelulu', 'Falaite', 'Tokonaki'], + 'weekdays_short' => ['Sฤp', 'Mลn', 'Tลซs', 'Pul', 'Tuสปa', 'Fal', 'Tok'], + 'weekdays_min' => ['Sฤp', 'Mลn', 'Tลซs', 'Pul', 'Tuสปa', 'Fal', 'Tok'], + 'meridiem' => ['hengihengi', 'efiafi'], + + 'year' => ':count fitu', // less reliable + 'y' => ':count fitu', // less reliable + 'a_year' => ':count fitu', // less reliable + + 'month' => ':count mahina', // less reliable + 'm' => ':count mahina', // less reliable + 'a_month' => ':count mahina', // less reliable + + 'week' => ':count Sapate', // less reliable + 'w' => ':count Sapate', // less reliable + 'a_week' => ':count Sapate', // less reliable + + 'day' => ':count สปaho', // less reliable + 'd' => ':count สปaho', // less reliable + 'a_day' => ':count สปaho', // less reliable + + 'hour' => ':count houa', + 'h' => ':count houa', + 'a_hour' => ':count houa', + + 'minute' => ':count miniti', + 'min' => ':count miniti', + 'a_minute' => ':count miniti', + + 'second' => ':count sekoni', + 's' => ':count sekoni', + 'a_second' => ':count sekoni', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tpi.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tpi.php new file mode 100644 index 0000000000..7d38daed74 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tpi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tpi_PG.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tpi_PG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tpi_PG.php new file mode 100644 index 0000000000..5f58c44c0b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tpi_PG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Janueri', 'Februeri', 'Mas', 'Epril', 'Me', 'Jun', 'Julai', 'Ogas', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mas', 'Epr', 'Me', 'Jun', 'Jul', 'Oga', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Sande', 'Mande', 'Tunde', 'Trinde', 'Fonde', 'Fraide', 'Sarere'], + 'weekdays_short' => ['San', 'Man', 'Tun', 'Tri', 'Fon', 'Fra', 'Sar'], + 'weekdays_min' => ['San', 'Man', 'Tun', 'Tri', 'Fon', 'Fra', 'Sar'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['biknait', 'apinun'], + + 'year' => 'yia :count', + 'y' => 'yia :count', + 'a_year' => 'yia :count', + + 'month' => ':count mun', + 'm' => ':count mun', + 'a_month' => ':count mun', + + 'week' => ':count wik', + 'w' => ':count wik', + 'a_week' => ':count wik', + + 'day' => ':count de', + 'd' => ':count de', + 'a_day' => ':count de', + + 'hour' => ':count aua', + 'h' => ':count aua', + 'a_hour' => ':count aua', + + 'minute' => ':count minit', + 'min' => ':count minit', + 'a_minute' => ':count minit', + + 'second' => ':count namba tu', + 's' => ':count namba tu', + 'a_second' => ':count namba tu', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tr.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tr.php new file mode 100644 index 0000000000..f5d9f4cc59 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tr.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Alan Agius + * - Erhan Gundogan + * - Franรงois B + * - JD Isaacks + * - Murat Yรผksel + * - Baran ลžengรผl + * - Selami (selamialtin) + * - TeomanBey + */ +return [ + 'year' => ':count yฤฑl', + 'a_year' => '{1}bir yฤฑl|]1,Inf[:count yฤฑl', + 'y' => ':county', + 'month' => ':count ay', + 'a_month' => '{1}bir ay|]1,Inf[:count ay', + 'm' => ':countay', + 'week' => ':count hafta', + 'a_week' => '{1}bir hafta|]1,Inf[:count hafta', + 'w' => ':counth', + 'day' => ':count gรผn', + 'a_day' => '{1}bir gรผn|]1,Inf[:count gรผn', + 'd' => ':countg', + 'hour' => ':count saat', + 'a_hour' => '{1}bir saat|]1,Inf[:count saat', + 'h' => ':countsa', + 'minute' => ':count dakika', + 'a_minute' => '{1}bir dakika|]1,Inf[:count dakika', + 'min' => ':countdk', + 'second' => ':count saniye', + 'a_second' => '{1}birkaรง saniye|]1,Inf[:count saniye', + 's' => ':countsn', + 'ago' => ':time รถnce', + 'from_now' => ':time sonra', + 'after' => ':time sonra', + 'before' => ':time รถnce', + 'diff_now' => 'ลŸimdi', + 'diff_today' => 'bugรผn', + 'diff_today_regexp' => 'bugรผn(?:\\s+saat)?', + 'diff_yesterday' => 'dรผn', + 'diff_tomorrow' => 'yarฤฑn', + 'diff_tomorrow_regexp' => 'yarฤฑn(?:\\s+saat)?', + 'diff_before_yesterday' => 'evvelsi gรผn', + 'diff_after_tomorrow' => 'รถbรผr gรผn', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[bugรผn saat] LT', + 'nextDay' => '[yarฤฑn saat] LT', + 'nextWeek' => '[gelecek] dddd [saat] LT', + 'lastDay' => '[dรผn] LT', + 'lastWeek' => '[geรงen] dddd [saat] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'Do': + case 'DD': + return $number; + default: + if ($number === 0) { // special case for zero + return "$number'ฤฑncฤฑ"; + } + + static $suffixes = [ + 1 => '\'inci', + 5 => '\'inci', + 8 => '\'inci', + 70 => '\'inci', + 80 => '\'inci', + 2 => '\'nci', + 7 => '\'nci', + 20 => '\'nci', + 50 => '\'nci', + 3 => '\'รผncรผ', + 4 => '\'รผncรผ', + 100 => '\'รผncรผ', + 6 => '\'ncฤฑ', + 9 => '\'uncu', + 10 => '\'uncu', + 30 => '\'uncu', + 60 => '\'ฤฑncฤฑ', + 90 => '\'ฤฑncฤฑ', + ]; + + $lastDigit = $number % 10; + + return $number.($suffixes[$lastDigit] ?? $suffixes[$number % 100 - $lastDigit] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + } + }, + 'meridiem' => ['ร–ร–', 'ร–S', 'รถรถ', 'รถs'], + 'months' => ['Ocak', 'ลžubat', 'Mart', 'Nisan', 'Mayฤฑs', 'Haziran', 'Temmuz', 'AฤŸustos', 'Eylรผl', 'Ekim', 'Kasฤฑm', 'Aralฤฑk'], + 'months_short' => ['Oca', 'ลžub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'AฤŸu', 'Eyl', 'Eki', 'Kas', 'Ara'], + 'weekdays' => ['Pazar', 'Pazartesi', 'Salฤฑ', 'ร‡arลŸamba', 'PerลŸembe', 'Cuma', 'Cumartesi'], + 'weekdays_short' => ['Paz', 'Pts', 'Sal', 'ร‡ar', 'Per', 'Cum', 'Cts'], + 'weekdays_min' => ['Pz', 'Pt', 'Sa', 'ร‡a', 'Pe', 'Cu', 'Ct'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ve '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tr_CY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tr_CY.php new file mode 100644 index 0000000000..23f1144915 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tr_CY.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/tr.php', [ + 'weekdays_short' => ['Paz', 'Pzt', 'Sal', 'ร‡ar', 'Per', 'Cum', 'Cmt'], + 'weekdays_min' => ['Pa', 'Pt', 'Sa', 'ร‡a', 'Pe', 'Cu', 'Ct'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'D MMMM YYYY dddd h:mm a', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tr_TR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tr_TR.php new file mode 100644 index 0000000000..9e99482484 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tr_TR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/tr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ts.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ts.php new file mode 100644 index 0000000000..525736bfeb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ts.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ts_ZA.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ts_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ts_ZA.php new file mode 100644 index 0000000000..37a24ec49b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ts_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Sunguti', 'Nyenyenyani', 'Nyenyankulu', 'Dzivamisoko', 'Mudyaxihi', 'Khotavuxika', 'Mawuwani', 'Mhawuri', 'Ndzhati', 'Nhlangula', 'Hukuri', 'N\'wendzamhala'], + 'months_short' => ['Sun', 'Yan', 'Kul', 'Dzi', 'Mud', 'Kho', 'Maw', 'Mha', 'Ndz', 'Nhl', 'Huk', 'N\'w'], + 'weekdays' => ['Sonto', 'Musumbhunuku', 'Ravumbirhi', 'Ravunharhu', 'Ravumune', 'Ravuntlhanu', 'Mugqivela'], + 'weekdays_short' => ['Son', 'Mus', 'Bir', 'Har', 'Ne', 'Tlh', 'Mug'], + 'weekdays_min' => ['Son', 'Mus', 'Bir', 'Har', 'Ne', 'Tlh', 'Mug'], + 'day_of_first_week_of_year' => 1, + + 'year' => 'malembe ya :count', + 'y' => 'malembe ya :count', + 'a_year' => 'malembe ya :count', + + 'month' => 'tinโ€™hweti ta :count', + 'm' => 'tinโ€™hweti ta :count', + 'a_month' => 'tinโ€™hweti ta :count', + + 'week' => 'mavhiki ya :count', + 'w' => 'mavhiki ya :count', + 'a_week' => 'mavhiki ya :count', + + 'day' => 'masiku :count', + 'd' => 'masiku :count', + 'a_day' => 'masiku :count', + + 'hour' => 'tiawara ta :count', + 'h' => 'tiawara ta :count', + 'a_hour' => 'tiawara ta :count', + + 'minute' => 'timinete ta :count', + 'min' => 'timinete ta :count', + 'a_minute' => 'timinete ta :count', + + 'second' => 'tisekoni ta :count', + 's' => 'tisekoni ta :count', + 'a_second' => 'tisekoni ta :count', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tt.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tt.php new file mode 100644 index 0000000000..d67d896ec1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tt.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tt_RU.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU.php new file mode 100644 index 0000000000..38e42d0545 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rinat Norkin Pablo Saratxaga, Rinat Norkin pablo@mandrakesoft.com, rinat@taif.ru + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'DD MMM, HH:mm', + 'LLLL' => 'DD MMMM YYYY, HH:mm', + ], + 'months' => ['ัะฝะฒะฐั€ั', 'ั„ะตะฒั€ะฐะปั', 'ะผะฐั€ั‚ะฐ', 'ะฐะฟั€ะตะปั', 'ะผะฐั', 'ะธัŽะฝั', 'ะธัŽะปั', 'ะฐะฒะณัƒัั‚ะฐ', 'ัะตะฝั‚ัะฑั€ั', 'ะพะบั‚ัะฑั€ั', 'ะฝะพัะฑั€ั', 'ะดะตะบะฐะฑั€ั'], + 'months_short' => ['ัะฝะฒ', 'ั„ะตะฒ', 'ะผะฐั€', 'ะฐะฟั€', 'ะผะฐะน', 'ะธัŽะฝ', 'ะธัŽะป', 'ะฐะฒะณ', 'ัะตะฝ', 'ะพะบั‚', 'ะฝะพั', 'ะดะตะบ'], + 'weekdays' => ['ัะบัˆำ™ะผะฑะต', 'ะดั‹ัˆำ™ะผะฑะต', 'ัะธัˆำ™ะผะฑะต', 'ั‡ำ™ั€ัˆำ™ำ™ะผะฑะต', 'ะฟำ™ะฝา—ะตัˆะผะฑะต', 'า—ะพะผะณะฐ', 'ัˆะธะผะฑำ™'], + 'weekdays_short' => ['ัะบัˆ', 'ะดั‹ัˆ', 'ัะธัˆ', 'ั‡ำ™ั€ัˆ', 'ะฟำ™ะฝา—', 'า—ะพะผ', 'ัˆะธะผ'], + 'weekdays_min' => ['ัะบัˆ', 'ะดั‹ัˆ', 'ัะธัˆ', 'ั‡ำ™ั€ัˆ', 'ะฟำ™ะฝา—', 'า—ะพะผ', 'ัˆะธะผ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'year' => ':count ะตะป', + 'month' => ':count ะฐะน', + 'week' => ':count ะฐั‚ะฝะฐ', + 'day' => ':count ะบำฉะฝ', + 'hour' => ':count ัำ™ะณะฐั‚ัŒ', + 'minute' => ':count ะผะธะฝัƒั‚', + 'second' => ':count ัะตะบัƒะฝะด', +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU@iqtelif.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU@iqtelif.php new file mode 100644 index 0000000000..16b8efb1f7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU@iqtelif.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Reshat Sabiq tatar.iqtelif.i18n@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['ฤžฤฑnwar', 'Fiwral\'', 'Mart', 'April', 'May', 'Yรผn', 'Yรผl', 'Awgust', 'Sintebรญr', 'รœktebรญr', 'Noyebรญr', 'Dikebรญr'], + 'months_short' => ['ฤžฤฑn', 'Fiw', 'Mar', 'Apr', 'May', 'Yรผn', 'Yรผl', 'Awg', 'Sin', 'รœkt', 'Noy', 'Dik'], + 'weekdays' => ['YekลŸembรญ', 'DรผลŸembรญ', 'SiลŸembรญ', 'ร‡erลŸembรญ', 'PencรญลŸembรญ', 'ComฤŸa', 'ลžimbe'], + 'weekdays_short' => ['Yek', 'DรผลŸ', 'SiลŸ', 'ร‡er', 'Pen', 'Com', 'ลžim'], + 'weekdays_min' => ['Yek', 'DรผลŸ', 'SiลŸ', 'ร‡er', 'Pen', 'Com', 'ลžim'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ร–A', 'ร–S'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/twq.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/twq.php new file mode 100644 index 0000000000..5cbb46e011 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/twq.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ses.php', [ + 'meridiem' => ['Subbaahi', 'Zaarikay b'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tzl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tzl.php new file mode 100644 index 0000000000..50bf26d269 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tzl.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'year' => '[0,1]:count ar|:count ars', + 'y' => '[0,1]:count ar|:count ars', + 'month' => '[0,1]:count mes|:count mesen', + 'm' => '[0,1]:count mes|:count mesen', + 'week' => '[0,1]:count seifetziua|:count seifetziuas', + 'w' => '[0,1]:count seifetziua|:count seifetziuas', + 'day' => '[0,1]:count ziua|:count ziuas', + 'd' => '[0,1]:count ziua|:count ziuas', + 'hour' => '[0,1]:count รพora|:count รพoras', + 'h' => '[0,1]:count รพora|:count รพoras', + 'minute' => '[0,1]:count mรญut|:count mรญuts', + 'min' => '[0,1]:count mรญut|:count mรญuts', + 'second' => ':count secunds', + 's' => ':count secunds', + + 'ago' => 'ja :time', + 'from_now' => 'osprei :time', + + 'diff_yesterday' => 'ieiri', + 'diff_yesterday_regexp' => 'ieiri(?:\\s+ร )?', + 'diff_today' => 'oxhi', + 'diff_today_regexp' => 'oxhi(?:\\s+ร )?', + 'diff_tomorrow' => 'demร ', + 'diff_tomorrow_regexp' => 'demร (?:\\s+ร )?', + + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM [dallas] YYYY', + 'LLL' => 'D. MMMM [dallas] YYYY HH.mm', + 'LLLL' => 'dddd, [li] D. MMMM [dallas] YYYY HH.mm', + ], + + 'calendar' => [ + 'sameDay' => '[oxhi ร ] LT', + 'nextDay' => '[demร  ร ] LT', + 'nextWeek' => 'dddd [ร ] LT', + 'lastDay' => '[ieiri ร ] LT', + 'lastWeek' => '[sรผr el] dddd [lasteu ร ] LT', + 'sameElse' => 'L', + ], + + 'meridiem' => ["D'A", "D'O"], + 'months' => ['Januar', 'Fevraglh', 'Marรง', 'Avrรฏu', 'Mai', 'Gรผn', 'Julia', 'Guscht', 'Setemvar', 'Listopรคts', 'Noemvar', 'Zecemvar'], + 'months_short' => ['Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Gรผn', 'Jul', 'Gus', 'Set', 'Lis', 'Noe', 'Zec'], + 'weekdays' => ['Sรบladi', 'Lรบneรงi', 'Maitzi', 'Mรกrcuri', 'Xhรบadi', 'Viรฉnerรงi', 'Sรกturi'], + 'weekdays_short' => ['Sรบl', 'Lรบn', 'Mai', 'Mรกr', 'Xhรบ', 'Viรฉ', 'Sรกt'], + 'weekdays_min' => ['Sรบ', 'Lรบ', 'Ma', 'Mรก', 'Xh', 'Vi', 'Sรก'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tzm.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tzm.php new file mode 100644 index 0000000000..2a1a0f2bca --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tzm.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => '{1}โดฐโต™โดณโดฐโต™|:count โต‰โต™โดณโดฐโต™โต', + 'month' => '{1}โดฐโตขoโต“โต”|:count โต‰โตขโตขโต‰โต”โต', + 'week' => ':count โต‰โตŽโดฐโตโดฐโต™โต™', + 'day' => '{1}โดฐโต™โต™|:count oโต™โต™โดฐโต', + 'hour' => '{1}โต™โดฐโต„โดฐ|:count โตœโดฐโต™โต™โดฐโต„โต‰โต', + 'minute' => '{1}โตŽโต‰โตโต“โดบ|:count โตŽโต‰โตโต“โดบ', + 'second' => '{1}โต‰โตŽโต‰โดฝ|:count โต‰โตŽโต‰โดฝ', + 'ago' => 'โตขโดฐโต :time', + 'from_now' => 'โดทโดฐโดทโต… โต™ โตขโดฐโต :time', + 'diff_today' => 'โดฐโต™โดทโต…', + 'diff_yesterday' => 'โดฐโตšโดฐโตโตœ', + 'diff_yesterday_regexp' => 'โดฐโตšโดฐโตโตœ(?:\\s+โดด)?', + 'diff_tomorrow' => 'โดฐโต™โดฝโดฐ', + 'diff_tomorrow_regexp' => 'โดฐโต™โดฝโดฐ(?:\\s+โดด)?', + 'diff_today_regexp' => 'โดฐโต™โดทโต…(?:\\s+โดด)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[โดฐโต™โดทโต… โดด] LT', + 'nextDay' => '[โดฐโต™โดฝโดฐ โดด] LT', + 'nextWeek' => 'dddd [โดด] LT', + 'lastDay' => '[โดฐโตšโดฐโตโตœ โดด] LT', + 'lastWeek' => 'dddd [โดด] LT', + 'sameElse' => 'L', + ], + 'months' => ['โต‰โตโตโดฐโตขโต”', 'โดฑโต•โดฐโตขโต•', 'โตŽโดฐโต•โตš', 'โต‰โดฑโต”โต‰โต”', 'โตŽโดฐโตขโตขโต“', 'โตขโต“โตโตขโต“', 'โตขโต“โตโตขโต“โตฃ', 'โต–โต“โต›โตœ', 'โต›โต“โตœโดฐโตโดฑโต‰โต”', 'โดฝโตŸโต“โดฑโต•', 'โตโต“โตกโดฐโตโดฑโต‰โต”', 'โดทโต“โตŠโตโดฑโต‰โต”'], + 'months_short' => ['โต‰โตโตโดฐโตขโต”', 'โดฑโต•โดฐโตขโต•', 'โตŽโดฐโต•โตš', 'โต‰โดฑโต”โต‰โต”', 'โตŽโดฐโตขโตขโต“', 'โตขโต“โตโตขโต“', 'โตขโต“โตโตขโต“โตฃ', 'โต–โต“โต›โตœ', 'โต›โต“โตœโดฐโตโดฑโต‰โต”', 'โดฝโตŸโต“โดฑโต•', 'โตโต“โตกโดฐโตโดฑโต‰โต”', 'โดทโต“โตŠโตโดฑโต‰โต”'], + 'weekdays' => ['โดฐโต™โดฐโตŽโดฐโต™', 'โดฐโตขโตโดฐโต™', 'โดฐโต™โต‰โตโดฐโต™', 'โดฐโดฝโต”โดฐโต™', 'โดฐโดฝโตกโดฐโต™', 'โดฐโต™โต‰โตŽโตกโดฐโต™', 'โดฐโต™โต‰โดนโตขโดฐโต™'], + 'weekdays_short' => ['โดฐโต™โดฐโตŽโดฐโต™', 'โดฐโตขโตโดฐโต™', 'โดฐโต™โต‰โตโดฐโต™', 'โดฐโดฝโต”โดฐโต™', 'โดฐโดฝโตกโดฐโต™', 'โดฐโต™โต‰โตŽโตกโดฐโต™', 'โดฐโต™โต‰โดนโตขโดฐโต™'], + 'weekdays_min' => ['โดฐโต™โดฐโตŽโดฐโต™', 'โดฐโตขโตโดฐโต™', 'โดฐโต™โต‰โตโดฐโต™', 'โดฐโดฝโต”โดฐโต™', 'โดฐโดฝโตกโดฐโต™', 'โดฐโต™โต‰โตŽโตกโดฐโต™', 'โดฐโต™โต‰โดนโตขโดฐโต™'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'weekend' => [5, 6], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tzm_Latn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tzm_Latn.php new file mode 100644 index 0000000000..5840d20920 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tzm_Latn.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => '{1}:count asgas|:count isgasn', + 'a_year' => 'asgas|:count isgasn', + 'month' => '{1}:count ayowr|:count iyyirn', + 'a_month' => 'ayowr|:count iyyirn', + 'week' => ':count imalass', + 'a_week' => ':imalass', + 'day' => '{1}:count ass|:count ossan', + 'a_day' => 'ass|:count ossan', + 'hour' => '{1}:count saษ›a|:count tassaษ›in', + 'a_hour' => '{1}saษ›a|:count tassaษ›in', + 'minute' => ':count minuแธ', + 'a_minute' => '{1}minuแธ|:count minuแธ', + 'second' => ':count imik', + 'a_second' => '{1}imik|:count imik', + 'ago' => 'yan :time', + 'from_now' => 'dadkh s yan :time', + 'diff_yesterday' => 'assant', + 'diff_yesterday_regexp' => 'assant(?:\\s+g)?', + 'diff_today' => 'asdkh', + 'diff_today_regexp' => 'asdkh(?:\\s+g)?', + 'diff_tomorrow' => 'aska', + 'diff_tomorrow_regexp' => 'aska(?:\\s+g)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[asdkh g] LT', + 'nextDay' => '[aska g] LT', + 'nextWeek' => 'dddd [g] LT', + 'lastDay' => '[assant g] LT', + 'lastWeek' => 'dddd [g] LT', + 'sameElse' => 'L', + ], + 'months' => ['innayr', 'brหคayrหค', 'marหคsหค', 'ibrir', 'mayyw', 'ywnyw', 'ywlywz', 'ษฃwลกt', 'ลกwtanbir', 'ktหคwbrหค', 'nwwanbir', 'dwjnbir'], + 'months_short' => ['innayr', 'brหคayrหค', 'marหคsหค', 'ibrir', 'mayyw', 'ywnyw', 'ywlywz', 'ษฃwลกt', 'ลกwtanbir', 'ktหคwbrหค', 'nwwanbir', 'dwjnbir'], + 'weekdays' => ['asamas', 'aynas', 'asinas', 'akras', 'akwas', 'asimwas', 'asiแธyas'], + 'weekdays_short' => ['asamas', 'aynas', 'asinas', 'akras', 'akwas', 'asimwas', 'asiแธyas'], + 'weekdays_min' => ['asamas', 'aynas', 'asinas', 'akras', 'akwas', 'asimwas', 'asiแธyas'], + 'meridiem' => ['Zdat azal', 'แธŒeffir aza'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ug.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ug.php new file mode 100644 index 0000000000..259b99a483 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ug.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - yasinn + */ +return [ + 'year' => '{1}'.'ุจู‰ุฑ ูŠู‰ู„'.'|:count '.'ูŠู‰ู„', + 'month' => '{1}'.'ุจู‰ุฑ ุฆุงูŠ'.'|:count '.'ุฆุงูŠ', + 'week' => '{1}'.'ุจู‰ุฑ ฺพ•ูพุช•'.'|:count '.'ฺพ•ูพุช•', + 'day' => '{1}'.'ุจู‰ุฑ ูƒˆู†'.'|:count '.'ูƒˆู†', + 'hour' => '{1}'.'ุจู‰ุฑ ุณุงุฆ•ุช'.'|:count '.'ุณุงุฆ•ุช', + 'minute' => '{1}'.'ุจู‰ุฑ ู…ู‰ู†‡ุช'.'|:count '.'ู…ู‰ู†‡ุช', + 'second' => '{1}'.'ู†•ฺ†ฺ†• ุณูƒูˆู†ุช'.'|:count '.'ุณูƒูˆู†ุช', + 'ago' => ':time ุจ‡ุฑ‡ู†', + 'from_now' => ':time ูƒูŠู‰ู†', + 'diff_today' => 'ุจˆฺฏˆู†', + 'diff_yesterday' => 'ุช†ู†ˆฺฏˆู†', + 'diff_tomorrow' => 'ุฆ•ุช•', + 'diff_tomorrow_regexp' => 'ุฆ•ุช•(?:\\s+ุณุงุฆ•ุช)?', + 'diff_today_regexp' => 'ุจˆฺฏˆู†(?:\\s+ุณุงุฆ•ุช)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY-ูŠู‰ู„ู‰M-ุฆุงูŠู†ู‰ฺญD-ูƒˆู†ู‰', + 'LLL' => 'YYYY-ูŠู‰ู„ู‰M-ุฆุงูŠู†ู‰ฺญD-ูƒˆู†ู‰ุŒ HH:mm', + 'LLLL' => 'ddddุŒ YYYY-ูŠู‰ู„ู‰M-ุฆุงูŠู†ู‰ฺญD-ูƒˆู†ู‰ุŒ HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ุจˆฺฏˆู† ุณุงุฆ•ุช] LT', + 'nextDay' => '[ุฆ•ุช• ุณุงุฆ•ุช] LT', + 'nextWeek' => '[ูƒู„•ุฑูƒู‰] dddd [ุณุงุฆ•ุช] LT', + 'lastDay' => '[ุช†ู†ˆฺฏˆู†] LT', + 'lastWeek' => '[ุฆุงู„ุฏู‰ู†ู‚ู‰] dddd [ุณุงุฆ•ุช] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'-ูƒˆู†ู‰'; + case 'w': + case 'W': + return $number.'-ฺพ•ูพุช•'; + default: + return $number; + } + }, + 'meridiem' => function ($hour, $minute) { + $time = $hour * 100 + $minute; + if ($time < 600) { + return 'ูАุฑู‰ู… ูƒฺ†•'; + } + if ($time < 900) { + return 'ุณ•ฺพ•ุฑ'; + } + if ($time < 1130) { + return 'ฺ†ˆุดุชู‰ู† ุจ‡ุฑ‡ู†'; + } + if ($time < 1230) { + return 'ฺ†ˆุด'; + } + if ($time < 1800) { + return 'ฺ†ˆุดุชู‰ู† ูƒูŠู‰ู†'; + } + + return 'ูƒ•ฺ†'; + }, + 'months' => ['ูŠุงู†‹ุงุฑ', 'ู‹ุฑุงู„', 'ู…ุงุฑุช', 'ุฆุงูพุฑู„', 'ู…ุงูŠ', 'ุฆู‰ูЇู†', 'ุฆู‰ูЇู„', 'ุฆุง‹ุบ‡ุณุช', 'ุณู†ุช•ุจู‰ุฑ', 'ุฆ†ูƒุช•ุจู‰ุฑ', 'ู†ูˆูŠุงุจู‰ุฑ', 'ุฏูƒุงุจู‰ุฑ'], + 'months_short' => ['ูŠุงู†‹ุงุฑ', 'ู‹ุฑุงู„', 'ู…ุงุฑุช', 'ุฆุงูพุฑู„', 'ู…ุงูŠ', 'ุฆู‰ูЇู†', 'ุฆู‰ูЇู„', 'ุฆุง‹ุบ‡ุณุช', 'ุณู†ุช•ุจู‰ุฑ', 'ุฆ†ูƒุช•ุจู‰ุฑ', 'ู†ูˆูŠุงุจู‰ุฑ', 'ุฏูƒุงุจู‰ุฑ'], + 'weekdays' => ['ูŠ•ูƒุด•ู†ุจ•', 'ุฏˆุด•ู†ุจ•', 'ุณ•ูŠุด•ู†ุจ•', 'ฺ†ุงุฑุด•ู†ุจ•', 'ูพ•ูŠุด•ู†ุจ•', 'ุฌˆู…•', 'ุด•ู†ุจ•'], + 'weekdays_short' => ['ูŠ•', 'ุฏˆ', 'ุณ•', 'ฺ†ุง', 'ูพ•', 'ุฌˆ', 'ุด•'], + 'weekdays_min' => ['ูŠ•', 'ุฏˆ', 'ุณ•', 'ฺ†ุง', 'ูพ•', 'ุฌˆ', 'ุด•'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ‹• '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ug_CN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ug_CN.php new file mode 100644 index 0000000000..deb828c503 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ug_CN.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - Alim Boyaq + */ +return require __DIR__.'/ug.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uk.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uk.php new file mode 100644 index 0000000000..4217d16edb --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uk.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\CarbonInterface; + +$processHoursFunction = function (CarbonInterface $date, string $format) { + return $format.'ะพ'.($date->hour === 11 ? 'ะฑ' : '').'] LT'; +}; + +/* + * Authors: + * - Kunal Marwaha + * - Josh Soref + * - Franรงois B + * - Tim Fish + * - Serhan Apaydฤฑn + * - Max Mykhailenko + * - JD Isaacks + * - Max Kovpak + * - AucT + * - Philippe Vaucher + * - Ilya Shaplyko + * - Vadym Ievsieiev + * - Denys Kurets + * - Igor Kasyanchuk + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Oleh + * - epaminond + * - Juanito Fatas + * - Vitalii Khustochka + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Andriy Tyurnikov + * - Nicolรกs Hock Isaza + * - Iwakura Taro + * - Andrii Ponomarov + * - alecrabbit + * - vystepanenko + * - AlexWalkerson + * - Andre Havryliuk (Andrend) + * - Max Datsenko (datsenko-md) + */ +return [ + 'year' => ':count ั€ั–ะบ|:count ั€ะพะบะธ|:count ั€ะพะบั–ะฒ', + 'y' => ':countั€|:countั€ั€|:countั€ั€', + 'a_year' => '{1}ั€ั–ะบ|:count ั€ั–ะบ|:count ั€ะพะบะธ|:count ั€ะพะบั–ะฒ', + 'month' => ':count ะผั–ััั†ัŒ|:count ะผั–ััั†ั–|:count ะผั–ััั†ั–ะฒ', + 'm' => ':countะผ', + 'a_month' => '{1}ะผั–ััั†ัŒ|:count ะผั–ััั†ัŒ|:count ะผั–ััั†ั–|:count ะผั–ััั†ั–ะฒ', + 'week' => ':count ั‚ะธะถะดะตะฝัŒ|:count ั‚ะธะถะฝั–|:count ั‚ะธะถะฝั–ะฒ', + 'w' => ':countั‚', + 'a_week' => '{1}ั‚ะธะถะดะตะฝัŒ|:count ั‚ะธะถะดะตะฝัŒ|:count ั‚ะธะถะฝั–|:count ั‚ะธะถะฝั–ะฒ', + 'day' => ':count ะดะตะฝัŒ|:count ะดะฝั–|:count ะดะฝั–ะฒ', + 'd' => ':countะด', + 'a_day' => '{1}ะดะตะฝัŒ|:count ะดะตะฝัŒ|:count ะดะฝั–|:count ะดะฝั–ะฒ', + 'hour' => ':count ะณะพะดะธะฝะฐ|:count ะณะพะดะธะฝะธ|:count ะณะพะดะธะฝ', + 'h' => ':countะณ', + 'a_hour' => '{1}ะณะพะดะธะฝะฐ|:count ะณะพะดะธะฝะฐ|:count ะณะพะดะธะฝะธ|:count ะณะพะดะธะฝ', + 'minute' => ':count ั…ะฒะธะปะธะฝะฐ|:count ั…ะฒะธะปะธะฝะธ|:count ั…ะฒะธะปะธะฝ', + 'min' => ':countั…ะฒ', + 'a_minute' => '{1}ั…ะฒะธะปะธะฝะฐ|:count ั…ะฒะธะปะธะฝะฐ|:count ั…ะฒะธะปะธะฝะธ|:count ั…ะฒะธะปะธะฝ', + 'second' => ':count ัะตะบัƒะฝะดะฐ|:count ัะตะบัƒะฝะดะธ|:count ัะตะบัƒะฝะด', + 's' => ':countัะตะบ', + 'a_second' => '{1}ะดะตะบั–ะปัŒะบะฐ ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะดะฐ|:count ัะตะบัƒะฝะดะธ|:count ัะตะบัƒะฝะด', + + 'hour_ago' => ':count ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝะธ|:count ะณะพะดะธะฝ', + 'a_hour_ago' => '{1}ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝะธ|:count ะณะพะดะธะฝ', + 'minute_ago' => ':count ั…ะฒะธะปะธะฝัƒ|:count ั…ะฒะธะปะธะฝะธ|:count ั…ะฒะธะปะธะฝ', + 'a_minute_ago' => '{1}ั…ะฒะธะปะธะฝัƒ|:count ั…ะฒะธะปะธะฝัƒ|:count ั…ะฒะธะปะธะฝะธ|:count ั…ะฒะธะปะธะฝ', + 'second_ago' => ':count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดะธ|:count ัะตะบัƒะฝะด', + 'a_second_ago' => '{1}ะดะตะบั–ะปัŒะบะฐ ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดะธ|:count ัะตะบัƒะฝะด', + + 'hour_from_now' => ':count ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝะธ|:count ะณะพะดะธะฝ', + 'a_hour_from_now' => '{1}ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝะธ|:count ะณะพะดะธะฝ', + 'minute_from_now' => ':count ั…ะฒะธะปะธะฝัƒ|:count ั…ะฒะธะปะธะฝะธ|:count ั…ะฒะธะปะธะฝ', + 'a_minute_from_now' => '{1}ั…ะฒะธะปะธะฝัƒ|:count ั…ะฒะธะปะธะฝัƒ|:count ั…ะฒะธะปะธะฝะธ|:count ั…ะฒะธะปะธะฝ', + 'second_from_now' => ':count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดะธ|:count ัะตะบัƒะฝะด', + 'a_second_from_now' => '{1}ะดะตะบั–ะปัŒะบะฐ ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดะธ|:count ัะตะบัƒะฝะด', + + 'hour_after' => ':count ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝะธ|:count ะณะพะดะธะฝ', + 'a_hour_after' => '{1}ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝะธ|:count ะณะพะดะธะฝ', + 'minute_after' => ':count ั…ะฒะธะปะธะฝัƒ|:count ั…ะฒะธะปะธะฝะธ|:count ั…ะฒะธะปะธะฝ', + 'a_minute_after' => '{1}ั…ะฒะธะปะธะฝัƒ|:count ั…ะฒะธะปะธะฝัƒ|:count ั…ะฒะธะปะธะฝะธ|:count ั…ะฒะธะปะธะฝ', + 'second_after' => ':count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดะธ|:count ัะตะบัƒะฝะด', + 'a_second_after' => '{1}ะดะตะบั–ะปัŒะบะฐ ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดะธ|:count ัะตะบัƒะฝะด', + + 'hour_before' => ':count ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝะธ|:count ะณะพะดะธะฝ', + 'a_hour_before' => '{1}ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝัƒ|:count ะณะพะดะธะฝะธ|:count ะณะพะดะธะฝ', + 'minute_before' => ':count ั…ะฒะธะปะธะฝัƒ|:count ั…ะฒะธะปะธะฝะธ|:count ั…ะฒะธะปะธะฝ', + 'a_minute_before' => '{1}ั…ะฒะธะปะธะฝัƒ|:count ั…ะฒะธะปะธะฝัƒ|:count ั…ะฒะธะปะธะฝะธ|:count ั…ะฒะธะปะธะฝ', + 'second_before' => ':count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดะธ|:count ัะตะบัƒะฝะด', + 'a_second_before' => '{1}ะดะตะบั–ะปัŒะบะฐ ัะตะบัƒะฝะด|:count ัะตะบัƒะฝะดัƒ|:count ัะตะบัƒะฝะดะธ|:count ัะตะบัƒะฝะด', + + 'ago' => ':time ั‚ะพะผัƒ', + 'from_now' => 'ะทะฐ :time', + 'after' => ':time ะฟั–ัะปั', + 'before' => ':time ะดะพ', + 'diff_now' => 'ั‰ะพะนะฝะพ', + 'diff_today' => 'ะกัŒะพะณะพะดะฝั–', + 'diff_today_regexp' => 'ะกัŒะพะณะพะดะฝั–(?:\\s+ะพ)?', + 'diff_yesterday' => 'ะฒั‡ะพั€ะฐ', + 'diff_yesterday_regexp' => 'ะ’ั‡ะพั€ะฐ(?:\\s+ะพ)?', + 'diff_tomorrow' => 'ะทะฐะฒั‚ั€ะฐ', + 'diff_tomorrow_regexp' => 'ะ—ะฐะฒั‚ั€ะฐ(?:\\s+ะพ)?', + 'diff_before_yesterday' => 'ะฟะพะทะฐะฒั‡ะพั€ะฐ', + 'diff_after_tomorrow' => 'ะฟั–ัะปัะทะฐะฒั‚ั€ะฐ', + 'period_recurrences' => 'ะพะดะธะฝ ั€ะฐะท|:count ั€ะฐะทะธ|:count ั€ะฐะทั–ะฒ', + 'period_interval' => 'ะบะพะถะฝั– :interval', + 'period_start_date' => 'ะท :date', + 'period_end_date' => 'ะดะพ :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[ะกัŒะพะณะพะดะฝั– '); + }, + 'nextDay' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[ะ—ะฐะฒั‚ั€ะฐ '); + }, + 'nextWeek' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[ะฃ] dddd ['); + }, + 'lastDay' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[ะ’ั‡ะพั€ะฐ '); + }, + 'lastWeek' => function (CarbonInterface $date) use ($processHoursFunction) { + switch ($date->dayOfWeek) { + case 0: + case 3: + case 5: + case 6: + return $processHoursFunction($date, '[ะœะธะฝัƒะปะพั—] dddd ['); + default: + return $processHoursFunction($date, '[ะœะธะฝัƒะปะพะณะพ] dddd ['); + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'M': + case 'd': + case 'DDD': + case 'w': + case 'W': + return $number.'-ะน'; + case 'D': + return $number.'-ะณะพ'; + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ะฝะพั‡ั–'; + } + if ($hour < 12) { + return 'ั€ะฐะฝะบัƒ'; + } + if ($hour < 17) { + return 'ะดะฝั'; + } + + return 'ะฒะตั‡ะพั€ะฐ'; + }, + 'months' => ['ัั–ั‡ะฝั', 'ะปัŽั‚ะพะณะพ', 'ะฑะตั€ะตะทะฝั', 'ะบะฒั–ั‚ะฝั', 'ั‚ั€ะฐะฒะฝั', 'ั‡ะตั€ะฒะฝั', 'ะปะธะฟะฝั', 'ัะตั€ะฟะฝั', 'ะฒะตั€ะตัะฝั', 'ะถะพะฒั‚ะฝั', 'ะปะธัั‚ะพะฟะฐะดะฐ', 'ะณั€ัƒะดะฝั'], + 'months_standalone' => ['ัั–ั‡ะตะฝัŒ', 'ะปัŽั‚ะธะน', 'ะฑะตั€ะตะทะตะฝัŒ', 'ะบะฒั–ั‚ะตะฝัŒ', 'ั‚ั€ะฐะฒะตะฝัŒ', 'ั‡ะตั€ะฒะตะฝัŒ', 'ะปะธะฟะตะฝัŒ', 'ัะตั€ะฟะตะฝัŒ', 'ะฒะตั€ะตัะตะฝัŒ', 'ะถะพะฒั‚ะตะฝัŒ', 'ะปะธัั‚ะพะฟะฐะด', 'ะณั€ัƒะดะตะฝัŒ'], + 'months_short' => ['ัั–ั‡', 'ะปัŽั‚', 'ะฑะตั€', 'ะบะฒั–', 'ั‚ั€ะฐ', 'ั‡ะตั€', 'ะปะธะฟ', 'ัะตั€', 'ะฒะตั€', 'ะถะพะฒ', 'ะปะธั', 'ะณั€ัƒ'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => function (CarbonInterface $date, $format, $index) { + static $words = [ + 'nominative' => ['ะฝะตะดั–ะปั', 'ะฟะพะฝะตะดั–ะปะพะบ', 'ะฒั–ะฒั‚ะพั€ะพะบ', 'ัะตั€ะตะดะฐ', 'ั‡ะตั‚ะฒะตั€', 'ะฟโ€™ัั‚ะฝะธั†ั', 'ััƒะฑะพั‚ะฐ'], + 'accusative' => ['ะฝะตะดั–ะปัŽ', 'ะฟะพะฝะตะดั–ะปะพะบ', 'ะฒั–ะฒั‚ะพั€ะพะบ', 'ัะตั€ะตะดัƒ', 'ั‡ะตั‚ะฒะตั€', 'ะฟโ€™ัั‚ะฝะธั†ัŽ', 'ััƒะฑะพั‚ัƒ'], + 'genitive' => ['ะฝะตะดั–ะปั–', 'ะฟะพะฝะตะดั–ะปะบะฐ', 'ะฒั–ะฒั‚ะพั€ะบะฐ', 'ัะตั€ะตะดะธ', 'ั‡ะตั‚ะฒะตั€ะณะฐ', 'ะฟโ€™ัั‚ะฝะธั†ั–', 'ััƒะฑะพั‚ะธ'], + ]; + + $format = $format ?? ''; + $nounCase = preg_match('/(\[(ะ’|ะฒ|ะฃ|ัƒ)\])\s+dddd/u', $format) + ? 'accusative' + : ( + preg_match('/\[?(?:ะผะธะฝัƒะปะพั—|ะฝะฐัั‚ัƒะฟะฝะพั—)?\s*\]\s+dddd/u', $format) + ? 'genitive' + : 'nominative' + ); + + return $words[$nounCase][$index] ?? null; + }, + 'weekdays_short' => ['ะฝะด', 'ะฟะฝ', 'ะฒั‚', 'ัั€', 'ั‡ั‚', 'ะฟั‚', 'ัะฑ'], + 'weekdays_min' => ['ะฝะด', 'ะฟะฝ', 'ะฒั‚', 'ัั€', 'ั‡ั‚', 'ะฟั‚', 'ัะฑ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uk_UA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uk_UA.php new file mode 100644 index 0000000000..bd11d86e3d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uk_UA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/uk.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/unm.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/unm.php new file mode 100644 index 0000000000..d3f19f069a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/unm.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/unm_US.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/unm_US.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/unm_US.php new file mode 100644 index 0000000000..fa5c374e3d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/unm_US.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['enikwsi', 'chkwali', 'xamokhwite', 'kwetayoxe', 'tainipen', 'kichinipen', 'lainipen', 'winaminke', 'kichitahkok', 'puksit', 'wini', 'muxkotae'], + 'months_short' => ['eni', 'chk', 'xam', 'kwe', 'tai', 'nip', 'lai', 'win', 'tah', 'puk', 'kun', 'mux'], + 'weekdays' => ['kentuwei', 'manteke', 'tusteke', 'lelai', 'tasteke', 'pelaiteke', 'sateteke'], + 'weekdays_short' => ['ken', 'man', 'tus', 'lel', 'tas', 'pel', 'sat'], + 'weekdays_min' => ['ken', 'man', 'tus', 'lel', 'tas', 'pel', 'sat'], + 'day_of_first_week_of_year' => 1, + + // Too unreliable + /* + 'year' => ':count kaxtรซne', + 'y' => ':count kaxtรซne', + 'a_year' => ':count kaxtรซne', + + 'month' => ':count piskewรซni kishux', // less reliable + 'm' => ':count piskewรซni kishux', // less reliable + 'a_month' => ':count piskewรซni kishux', // less reliable + + 'week' => ':count kishku', // less reliable + 'w' => ':count kishku', // less reliable + 'a_week' => ':count kishku', // less reliable + + 'day' => ':count kishku', + 'd' => ':count kishku', + 'a_day' => ':count kishku', + + 'hour' => ':count xkuk', // less reliable + 'h' => ':count xkuk', // less reliable + 'a_hour' => ':count xkuk', // less reliable + + 'minute' => ':count txituwร k', // less reliable + 'min' => ':count txituwร k', // less reliable + 'a_minute' => ':count txituwร k', // less reliable + + 'second' => ':count nisha', // less reliable + 's' => ':count nisha', // less reliable + 'a_second' => ':count nisha', // less reliable + */ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ur.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ur.php new file mode 100644 index 0000000000..dc16c2c35b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ur.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$months = [ + 'ุฌู†ูˆุฑŒ', + 'ูุฑูˆุฑŒ', + 'ู…ุงุฑฺ†', + 'ุงูพุฑŒู„', + 'ู…ุฆŒ', + 'ุฌูˆู†', + 'ุฌูˆู„ุงุฆŒ', + 'ุงฺฏุณุช', + 'ุณุชู…ุจุฑ', + 'ุงฺฉุชูˆุจุฑ', + 'ู†ูˆู…ุจุฑ', + 'ุฏุณู…ุจุฑ', +]; + +$weekdays = [ + 'ุงุชูˆุงุฑ', + 'ูพŒุฑ', + 'ู…ู†ฺฏู„', + 'ุจุฏฺพ', + 'ุฌู…ุนุฑุงุช', + 'ุฌู…ุน', + 'ูุช', +]; + +/* + * Authors: + * - Sawood Alam + * - Mehshan + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - Zaid Akram + * - Max Melentiev + * - hafezdivandari + * - Hossein Jabbari + * - nimamo + */ +return [ + 'year' => 'ุงŒฺฉ ุณุงู„|:count ุณุงู„', + 'month' => 'ุงŒฺฉ ู…ุง|:count ู…ุง', + 'week' => ':count ูุช’', + 'day' => 'ุงŒฺฉ ุฏู†|:count ุฏู†', + 'hour' => 'ุงŒฺฉ ฺฏฺพู†ูน|:count ฺฏฺพู†ูน’', + 'minute' => 'ุงŒฺฉ ู…ู†ูน|:count ู…ู†ูน', + 'second' => 'ฺ†ู†ุฏ ุณŒฺฉู†ฺˆ|:count ุณŒฺฉู†ฺˆ', + 'ago' => ':time ู‚ุจู„', + 'from_now' => ':time ุจุนุฏ', + 'after' => ':time ุจุนุฏ', + 'before' => ':time ูพู„’', + 'diff_now' => 'ุงุจ', + 'diff_today' => 'ุขุฌ', + 'diff_today_regexp' => 'ุขุฌ(?:\\s+ุจูˆู‚ุช)?', + 'diff_yesterday' => 'ฺฏุฒุดุช ฺฉู„', + 'diff_yesterday_regexp' => 'ฺฏุฐุดุช(?:\\s+ุฑูˆุฒ)?(?:\\s+ุจูˆู‚ุช)?', + 'diff_tomorrow' => 'ุขุฆู†ุฏ ฺฉู„', + 'diff_tomorrow_regexp' => 'ฺฉู„(?:\\s+ุจูˆู‚ุช)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'ddddุŒ D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ุขุฌ ุจูˆู‚ุช] LT', + 'nextDay' => '[ฺฉู„ ุจูˆู‚ุช] LT', + 'nextWeek' => 'dddd [ุจูˆู‚ุช] LT', + 'lastDay' => '[ฺฏุฐุดุช ุฑูˆุฒ ุจูˆู‚ุช] LT', + 'lastWeek' => '[ฺฏุฐุดุช] dddd [ุจูˆู‚ุช] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ุตุจุญ', 'ุดุงู…'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => $weekdays, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => ['ุŒ ', ' ุงูˆุฑ '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ur_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ur_IN.php new file mode 100644 index 0000000000..f81c84d39f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ur_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ur.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['ุฌู†ูˆุฑŒ', 'ูุฑูˆุฑŒ', 'ู…ุงุฑฺ†', 'ุงูพุฑŒู„', 'ู…ุฆŒ', 'ุฌูˆู†', 'ุฌูˆู„ุงุฆŒ', 'ุงฺฏุณุช', 'ุณุชู…ุจุฑ', 'ุงฺฉุชูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'months_short' => ['ุฌู†ูˆุฑŒ', 'ูุฑูˆุฑŒ', 'ู…ุงุฑฺ†', 'ุงูพุฑŒู„', 'ู…ุฆŒ', 'ุฌูˆู†', 'ุฌูˆู„ุงุฆŒ', 'ุงฺฏุณุช', 'ุณุชู…ุจุฑ', 'ุงฺฉุชูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'weekdays' => ['ุงุชูˆุงุฑ', 'ูพŒุฑ', 'ู…ู†ฺฏู„', 'ุจุฏฺพ', 'ุฌู…ุนุฑุงุช', 'ุฌู…ุน', 'ุณู†Œฺ†ุฑ'], + 'weekdays_short' => ['ุงุชูˆุงุฑ', 'ูพŒุฑ', 'ู…ู†ฺฏู„', 'ุจุฏฺพ', 'ุฌู…ุนุฑุงุช', 'ุฌู…ุน', 'ุณู†Œฺ†ุฑ'], + 'weekdays_min' => ['ุงุชูˆุงุฑ', 'ูพŒุฑ', 'ู…ู†ฺฏู„', 'ุจุฏฺพ', 'ุฌู…ุนุฑุงุช', 'ุฌู…ุน', 'ุณู†Œฺ†ุฑ'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ur_PK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ur_PK.php new file mode 100644 index 0000000000..8cd593db68 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ur_PK.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ur.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ุฌู†ูˆุฑŒ', 'ูุฑูˆุฑŒ', 'ู…ุงุฑฺ†', 'ุงูพุฑŒู„', 'ู…ุฆŒ', 'ุฌูˆู†', 'ุฌูˆู„ุงุฆŒ', 'ุงฺฏุณุช', 'ุณุชู…ุจุฑ', 'ุงฺฉุชูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'months_short' => ['ุฌู†ูˆุฑŒ', 'ูุฑูˆุฑŒ', 'ู…ุงุฑฺ†', 'ุงูพุฑŒู„', 'ู…ุฆŒ', 'ุฌูˆู†', 'ุฌูˆู„ุงุฆŒ', 'ุงฺฏุณุช', 'ุณุชู…ุจุฑ', 'ุงฺฉุชูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'weekdays' => ['ุงุชูˆุงุฑ', 'ูพูŠุฑ', 'ู…ู†ฺฏู„', 'ุจุฏฺพ', 'ุฌู…ุนุฑุงุช', 'ุฌู…ุนู‡', 'ู‡ูุชู‡'], + 'weekdays_short' => ['ุงุชูˆุงุฑ', 'ูพูŠุฑ', 'ู…ู†ฺฏู„', 'ุจุฏฺพ', 'ุฌู…ุนุฑุงุช', 'ุฌู…ุนู‡', 'ู‡ูุชู‡'], + 'weekdays_min' => ['ุงุชูˆุงุฑ', 'ูพูŠุฑ', 'ู…ู†ฺฏู„', 'ุจุฏฺพ', 'ุฌู…ุนุฑุงุช', 'ุฌู…ุนู‡', 'ู‡ูุชู‡'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ุต', 'ุด'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz.php new file mode 100644 index 0000000000..61f3b64b3b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dmitriy Shabanov + * - JD Isaacks + * - Inoyatulloh + * - Jamshid + * - aarkhipov + * - Philippe Vaucher + * - felixthemagnificent + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Alisher Ulugbekov + * - Ergashev Adizbek + */ +return [ + 'year' => ':count ะนะธะป', + 'a_year' => '{1}ะฑะธั€ ะนะธะป|:count ะนะธะป', + 'y' => ':count ะน', + 'month' => ':count ะพะน', + 'a_month' => '{1}ะฑะธั€ ะพะน|:count ะพะน', + 'm' => ':count ะพ', + 'week' => ':count าณะฐั„ั‚ะฐ', + 'a_week' => '{1}ะฑะธั€ าณะฐั„ั‚ะฐ|:count าณะฐั„ั‚ะฐ', + 'w' => ':count าณ', + 'day' => ':count ะบัƒะฝ', + 'a_day' => '{1}ะฑะธั€ ะบัƒะฝ|:count ะบัƒะฝ', + 'd' => ':count ะบ', + 'hour' => ':count ัะพะฐั‚', + 'a_hour' => '{1}ะฑะธั€ ัะพะฐั‚|:count ัะพะฐั‚', + 'h' => ':count ั', + 'minute' => ':count ะดะฐา›ะธา›ะฐ', + 'a_minute' => '{1}ะฑะธั€ ะดะฐา›ะธา›ะฐ|:count ะดะฐา›ะธา›ะฐ', + 'min' => ':count ะด', + 'second' => ':count ัะพะฝะธั', + 'a_second' => '{1}ัะพะฝะธั|:count ัะพะฝะธั', + 's' => ':count ั', + 'ago' => ':time ะฐะฒะฒะฐะป', + 'from_now' => 'ะฏะบะธะฝ :time ะธั‡ะธะดะฐ', + 'after' => ':timeะดะฐะฝ ะบะตะนะธะฝ', + 'before' => ':time ะพะปะดะธะฝ', + 'diff_now' => 'าณะพะทะธั€', + 'diff_today' => 'ะ‘ัƒะณัƒะฝ', + 'diff_today_regexp' => 'ะ‘ัƒะณัƒะฝ(?:\\s+ัะพะฐั‚)?', + 'diff_yesterday' => 'ะšะตั‡ะฐ', + 'diff_yesterday_regexp' => 'ะšะตั‡ะฐ(?:\\s+ัะพะฐั‚)?', + 'diff_tomorrow' => 'ะญั€ั‚ะฐะณะฐ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'D MMMM YYYY, dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ะ‘ัƒะณัƒะฝ ัะพะฐั‚] LT [ะดะฐ]', + 'nextDay' => '[ะญั€ั‚ะฐะณะฐ] LT [ะดะฐ]', + 'nextWeek' => 'dddd [ะบัƒะฝะธ ัะพะฐั‚] LT [ะดะฐ]', + 'lastDay' => '[ะšะตั‡ะฐ ัะพะฐั‚] LT [ะดะฐ]', + 'lastWeek' => '[ะฃั‚ะณะฐะฝ] dddd [ะบัƒะฝะธ ัะพะฐั‚] LT [ะดะฐ]', + 'sameElse' => 'L', + ], + 'months' => ['ัะฝะฒะฐั€', 'ั„ะตะฒั€ะฐะป', 'ะผะฐั€ั‚', 'ะฐะฟั€ะตะป', 'ะผะฐะน', 'ะธัŽะฝ', 'ะธัŽะป', 'ะฐะฒะณัƒัั‚', 'ัะตะฝั‚ัะฑั€', 'ะพะบั‚ัะฑั€', 'ะฝะพัะฑั€', 'ะดะตะบะฐะฑั€'], + 'months_short' => ['ัะฝะฒ', 'ั„ะตะฒ', 'ะผะฐั€', 'ะฐะฟั€', 'ะผะฐะน', 'ะธัŽะฝ', 'ะธัŽะป', 'ะฐะฒะณ', 'ัะตะฝ', 'ะพะบั‚', 'ะฝะพั', 'ะดะตะบ'], + 'weekdays' => ['ัะบัˆะฐะฝะฑะฐ', 'ะดัƒัˆะฐะฝะฑะฐ', 'ัะตัˆะฐะฝะฑะฐ', 'ั‡ะพั€ัˆะฐะฝะฑะฐ', 'ะฟะฐะนัˆะฐะฝะฑะฐ', 'ะถัƒะผะฐ', 'ัˆะฐะฝะฑะฐ'], + 'weekdays_short' => ['ัะบัˆ', 'ะดัƒัˆ', 'ัะตัˆ', 'ั‡ะพั€', 'ะฟะฐะน', 'ะถัƒะผ', 'ัˆะฐะฝ'], + 'weekdays_min' => ['ัะบ', 'ะดัƒ', 'ัะต', 'ั‡ะพ', 'ะฟะฐ', 'ะถัƒ', 'ัˆะฐ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ัั€ั‚ะฐะปะฐะฑ', 'ะบะตั‡ะฐัะธ'], + 'list' => [', ', ' ะฒะฐ '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_Arab.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_Arab.php new file mode 100644 index 0000000000..ffb51319d8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_Arab.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fa.php', [ + 'weekdays' => ['Œฺฉุดู†ุจู‡', 'ุฏูˆุดู†ุจู‡', 'ุณู‡โ€Œุดู†ุจู‡', 'ฺ†ู‡ุงุฑุดู†ุจู‡', 'ูพู†ุฌุดู†ุจู‡', 'ุฌู…ุนู‡', 'ุดู†ุจู‡'], + 'weekdays_short' => ['Œ.', 'ุฏ.', 'ุณ.', 'ฺ†.', 'ูพ.', 'ุฌ.', 'ุด.'], + 'weekdays_min' => ['Œ.', 'ุฏ.', 'ุณ.', 'ฺ†.', 'ูพ.', 'ุฌ.', 'ุด.'], + 'months' => ['ุฌู†ูˆุฑŒ', 'ูุจุฑูˆุฑŒ', 'ู…ุงุฑฺ†', 'ุงูพุฑŒู„', 'ู…Œ', 'ุฌูˆู†', 'ุฌูˆู„ุงŒ', 'ุงฺฏุณุช', 'ุณูพุชู…ุจุฑ', 'ุงฺฉุชูˆุจุฑ', 'ู†ูˆู…ุจุฑ', 'ุฏุณู…ุจุฑ'], + 'months_short' => ['ุฌู†ูˆ', 'ูุจุฑ', 'ู…ุงุฑ', 'ุงูพุฑ', 'ู…Œ', 'ุฌูˆู†', 'ุฌูˆู„', 'ุงฺฏุณ', 'ุณูพุช', 'ุงฺฉุช', 'ู†ูˆู…', 'ุฏุณู…'], + 'first_day_of_week' => 6, + 'weekend' => [4, 5], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_Cyrl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_Cyrl.php new file mode 100644 index 0000000000..89e9971814 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_Cyrl.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/uz.php', [ + 'formats' => [ + 'L' => 'DD/MM/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY HH:mm', + 'LLLL' => 'dddd, DD MMMM, YYYY HH:mm', + ], + 'meridiem' => ['ะขะž', 'ะขะš'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_Latn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_Latn.php new file mode 100644 index 0000000000..ecceeaa3dc --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_Latn.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Rasulbek + * - Ilyosjon Kamoldinov (ilyosjon09) + */ +return [ + 'year' => ':count yil', + 'a_year' => '{1}bir yil|:count yil', + 'y' => ':count y', + 'month' => ':count oy', + 'a_month' => '{1}bir oy|:count oy', + 'm' => ':count o', + 'week' => ':count hafta', + 'a_week' => '{1}bir hafta|:count hafta', + 'w' => ':count h', + 'day' => ':count kun', + 'a_day' => '{1}bir kun|:count kun', + 'd' => ':count k', + 'hour' => ':count soat', + 'a_hour' => '{1}bir soat|:count soat', + 'h' => ':count soat', + 'minute' => ':count daqiqa', + 'a_minute' => '{1}bir daqiqa|:count daqiqa', + 'min' => ':count d', + 'second' => ':count soniya', + 'a_second' => '{1}soniya|:count soniya', + 's' => ':count son.', + 'ago' => ':time avval', + 'from_now' => 'Yaqin :time ichida', + 'after' => ':timedan keyin', + 'before' => ':time oldin', + 'diff_yesterday' => 'Kecha', + 'diff_yesterday_regexp' => 'Kecha(?:\\s+soat)?', + 'diff_today' => 'Bugun', + 'diff_today_regexp' => 'Bugun(?:\\s+soat)?', + 'diff_tomorrow' => 'Ertaga', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'D MMMM YYYY, dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Bugun soat] LT [da]', + 'nextDay' => '[Ertaga] LT [da]', + 'nextWeek' => 'dddd [kuni soat] LT [da]', + 'lastDay' => '[Kecha soat] LT [da]', + 'lastWeek' => '[O\'tgan] dddd [kuni soat] LT [da]', + 'sameElse' => 'L', + ], + 'months' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'Iyun', 'Iyul', 'Avgust', 'Sentabr', 'Oktabr', 'Noyabr', 'Dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'Iyun', 'Iyul', 'Avg', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['Yakshanba', 'Dushanba', 'Seshanba', 'Chorshanba', 'Payshanba', 'Juma', 'Shanba'], + 'weekdays_short' => ['Yak', 'Dush', 'Sesh', 'Chor', 'Pay', 'Jum', 'Shan'], + 'weekdays_min' => ['Ya', 'Du', 'Se', 'Cho', 'Pa', 'Ju', 'Sha'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' va '], + 'meridiem' => ['TO', 'TK'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ.php new file mode 100644 index 0000000000..d41bfee270 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Bobir Ismailov Bobir Ismailov, Pablo Saratxaga, Mashrab Kuvatov bobir_is@yahoo.com, pablo@mandrakesoft.com, kmashrab@uni-bremen.de + */ +return array_replace_recursive(require __DIR__.'/uz_Latn.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'Iyun', 'Iyul', 'Avgust', 'Sentabr', 'Oktabr', 'Noyabr', 'Dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'Iyn', 'Iyl', 'Avg', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['Yakshanba', 'Dushanba', 'Seshanba', 'Chorshanba', 'Payshanba', 'Juma', 'Shanba'], + 'weekdays_short' => ['Yak', 'Du', 'Se', 'Cho', 'Pay', 'Ju', 'Sha'], + 'weekdays_min' => ['Yak', 'Du', 'Se', 'Cho', 'Pay', 'Ju', 'Sha'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ@cyrillic.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ@cyrillic.php new file mode 100644 index 0000000000..2fa967c94a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ@cyrillic.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Mashrab Kuvatov Mashrab Kuvatov, Pablo Saratxaga kmashrab@uni-bremen.de, pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/uz.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['ะฏะฝะฒะฐั€', 'ะคะตะฒั€ะฐะป', 'ะœะฐั€ั‚', 'ะะฟั€ะตะป', 'ะœะฐะน', 'ะ˜ัŽะฝ', 'ะ˜ัŽะป', 'ะะฒะณัƒัั‚', 'ะกะตะฝั‚ัะฑั€', 'ะžะบั‚ัะฑั€', 'ะะพัะฑั€', 'ะ”ะตะบะฐะฑั€'], + 'months_short' => ['ะฏะฝะฒ', 'ะคะตะฒ', 'ะœะฐั€', 'ะะฟั€', 'ะœะฐะน', 'ะ˜ัŽะฝ', 'ะ˜ัŽะป', 'ะะฒะณ', 'ะกะตะฝ', 'ะžะบั‚', 'ะะพั', 'ะ”ะตะบ'], + 'weekdays' => ['ะฏะบัˆะฐะฝะฑะฐ', 'ะ”ัƒัˆะฐะฝะฑะฐ', 'ะกะตัˆะฐะฝะฑะฐ', 'ะงะพั€ัˆะฐะฝะฑะฐ', 'ะŸะฐะนัˆะฐะฝะฑะฐ', 'ะ–ัƒะผะฐ', 'ะจะฐะฝะฑะฐ'], + 'weekdays_short' => ['ะฏะบัˆ', 'ะ”ัƒัˆ', 'ะกะตัˆ', 'ะงะพั€', 'ะŸะฐะน', 'ะ–ัƒะผ', 'ะจะฐะฝ'], + 'weekdays_min' => ['ะฏะบัˆ', 'ะ”ัƒัˆ', 'ะกะตัˆ', 'ะงะพั€', 'ะŸะฐะน', 'ะ–ัƒะผ', 'ะจะฐะฝ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vai.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vai.php new file mode 100644 index 0000000000..3c378dfba6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vai.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['๊•ž๊•Œ๊”ต', '๊—ณ๊—ก๊˜‰', '๊•š๊•ž๊•š', '๊•‰๊•ž๊•’', '๊•‰๊”ค๊•†๊•ข', '๊•‰๊”ค๊•€๊•ฎ', '๊”ป๊”ฌ๊”ณ'], + 'weekdays_short' => ['๊•ž๊•Œ๊”ต', '๊—ณ๊—ก๊˜‰', '๊•š๊•ž๊•š', '๊•‰๊•ž๊•’', '๊•‰๊”ค๊•†๊•ข', '๊•‰๊”ค๊•€๊•ฎ', '๊”ป๊”ฌ๊”ณ'], + 'weekdays_min' => ['๊•ž๊•Œ๊”ต', '๊—ณ๊—ก๊˜‰', '๊•š๊•ž๊•š', '๊•‰๊•ž๊•’', '๊•‰๊”ค๊•†๊•ข', '๊•‰๊”ค๊•€๊•ฎ', '๊”ป๊”ฌ๊”ณ'], + 'months' => ['๊–จ๊–• ๊•ช๊•ด ๊”ž๊”€๊•ฎ๊•Š', '๊•’๊•ก๊–๊–•', '๊•พ๊–บ', '๊–ข๊–•', '๊–‘๊•ฑ', '๊–ฑ๊˜‹', '๊–ฑ๊•ž๊”ค', '๊—›๊”•', '๊•ข๊•Œ', '๊•ญ๊–ƒ', '๊”ž๊˜‹๊•”๊•ฟ ๊•ธ๊–ƒ๊—', '๊–จ๊–• ๊•ช๊•ด ๊—๊–บ๊•ฎ๊•Š'], + 'months_short' => ['๊–จ๊–•๊”ž', '๊•’๊•ก', '๊•พ๊–บ', '๊–ข๊–•', '๊–‘๊•ฑ', '๊–ฑ๊˜‹', '๊–ฑ๊•ž', '๊—›๊”•', '๊•ข๊•Œ', '๊•ญ๊–ƒ', '๊”ž๊˜‹', '๊–จ๊–•๊—'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], + + 'year' => ':count ๊•€', // less reliable + 'y' => ':count ๊•€', // less reliable + 'a_year' => ':count ๊•€', // less reliable + + 'second' => ':count ๊—ฑ๊•ž๊•ฏ๊•Š', // less reliable + 's' => ':count ๊—ฑ๊•ž๊•ฏ๊•Š', // less reliable + 'a_second' => ':count ๊—ฑ๊•ž๊•ฏ๊•Š', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vai_Latn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vai_Latn.php new file mode 100644 index 0000000000..51e83cc5f2 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vai_Latn.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['lahadi', 'tษ›ษ›nษ›ษ›', 'talata', 'alaba', 'aimisa', 'aijima', 'siษ“iti'], + 'weekdays_short' => ['lahadi', 'tษ›ษ›nษ›ษ›', 'talata', 'alaba', 'aimisa', 'aijima', 'siษ“iti'], + 'weekdays_min' => ['lahadi', 'tษ›ษ›nษ›ษ›', 'talata', 'alaba', 'aimisa', 'aijima', 'siษ“iti'], + 'months' => ['luukao kemรฃ', 'ษ“andaษ“u', 'vษ”ษ”', 'fulu', 'goo', '6', '7', 'kษ”nde', 'saah', 'galo', 'kenpkato ษ“ololษ”', 'luukao lษ”ma'], + 'months_short' => ['luukao kemรฃ', 'ษ“andaษ“u', 'vษ”ษ”', 'fulu', 'goo', '6', '7', 'kษ”nde', 'saah', 'galo', 'kenpkato ษ“ololษ”', 'luukao lษ”ma'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vai_Vaii.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vai_Vaii.php new file mode 100644 index 0000000000..b4bb533f21 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vai_Vaii.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/vai.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ve.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ve.php new file mode 100644 index 0000000000..7f10aeb95f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ve.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ve_ZA.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ve_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ve_ZA.php new file mode 100644 index 0000000000..5eb2b91285 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ve_ZA.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Phando', 'Luhuhi', 'แนฐhafamuhwe', 'Lambamai', 'Shundunthule', 'Fulwi', 'Fulwana', 'แนฐhangule', 'Khubvumedzi', 'Tshimedzi', 'แธผara', 'Nyendavhusiku'], + 'months_short' => ['Pha', 'Luh', 'Fam', 'Lam', 'Shu', 'Lwi', 'Lwa', 'Ngu', 'Khu', 'Tsh', 'แธผar', 'Nye'], + 'weekdays' => ['Swondaha', 'Musumbuluwo', 'แธผavhuvhili', 'แธผavhuraru', 'แธผavhuแน‹a', 'แธผavhuแนฑanu', 'Mugivhela'], + 'weekdays_short' => ['Swo', 'Mus', 'Vhi', 'Rar', 'แน‹a', 'แนฐan', 'Mug'], + 'weekdays_min' => ['Swo', 'Mus', 'Vhi', 'Rar', 'แน‹a', 'แนฐan', 'Mug'], + 'day_of_first_week_of_year' => 1, + + // Too unreliable + /* + 'day' => ':count vhege', // less reliable + 'd' => ':count vhege', // less reliable + 'a_day' => ':count vhege', // less reliable + + 'hour' => ':count watshi', // less reliable + 'h' => ':count watshi', // less reliable + 'a_hour' => ':count watshi', // less reliable + + 'minute' => ':count watshi', // less reliable + 'min' => ':count watshi', // less reliable + 'a_minute' => ':count watshi', // less reliable + + 'second' => ':count Mu', // less reliable + 's' => ':count Mu', // less reliable + 'a_second' => ':count Mu', // less reliable + + 'week' => ':count vhege', + 'w' => ':count vhege', + 'a_week' => ':count vhege', + */ +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vi.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vi.php new file mode 100644 index 0000000000..73e2852ebd --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vi.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Andre Polykanine A.K.A. Menelion Elensรบlรซ + * - JD Isaacks + */ +return [ + 'year' => ':count nฤƒm', + 'a_year' => '{1}mแป™t nฤƒm|]1, Inf[:count nฤƒm', + 'y' => ':count nฤƒm', + 'month' => ':count thรกng', + 'a_month' => '{1}mแป™t thรกng|]1, Inf[:count thรกng', + 'm' => ':count thรกng', + 'week' => ':count tuแบงn', + 'a_week' => '{1}mแป™t tuแบงn|]1, Inf[:count tuแบงn', + 'w' => ':count tuแบงn', + 'day' => ':count ngร y', + 'a_day' => '{1}mแป™t ngร y|]1, Inf[:count ngร y', + 'd' => ':count ngร y', + 'hour' => ':count giแป', + 'a_hour' => '{1}mแป™t giแป|]1, Inf[:count giแป', + 'h' => ':count giแป', + 'minute' => ':count phรบt', + 'a_minute' => '{1}mแป™t phรบt|]1, Inf[:count phรบt', + 'min' => ':count phรบt', + 'second' => ':count giรขy', + 'a_second' => '{1}vร i giรขy|]1, Inf[:count giรขy', + 's' => ':count giรขy', + 'ago' => ':time trฦฐแป›c', + 'from_now' => ':time tแป›i', + 'after' => ':time sau', + 'before' => ':time trฦฐแป›c', + 'diff_now' => 'bรขy giแป', + 'diff_today' => 'Hรดm', + 'diff_today_regexp' => 'Hรดm(?:\\s+nay)?(?:\\s+lรบc)?', + 'diff_yesterday' => 'Hรดm qua', + 'diff_yesterday_regexp' => 'Hรดm(?:\\s+qua)?(?:\\s+lรบc)?', + 'diff_tomorrow' => 'Ngร y mai', + 'diff_tomorrow_regexp' => 'Ngร y(?:\\s+mai)?(?:\\s+lรบc)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM [nฤƒm] YYYY', + 'LLL' => 'D MMMM [nฤƒm] YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM [nฤƒm] YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hรดm nay lรบc] LT', + 'nextDay' => '[Ngร y mai lรบc] LT', + 'nextWeek' => 'dddd [tuแบงn tแป›i lรบc] LT', + 'lastDay' => '[Hรดm qua lรบc] LT', + 'lastWeek' => 'dddd [tuแบงn trฦฐแป›c lรบc] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['SA', 'CH'], + 'months' => ['thรกng 1', 'thรกng 2', 'thรกng 3', 'thรกng 4', 'thรกng 5', 'thรกng 6', 'thรกng 7', 'thรกng 8', 'thรกng 9', 'thรกng 10', 'thรกng 11', 'thรกng 12'], + 'months_short' => ['Th01', 'Th02', 'Th03', 'Th04', 'Th05', 'Th06', 'Th07', 'Th08', 'Th09', 'Th10', 'Th11', 'Th12'], + 'weekdays' => ['chแปง nhแบญt', 'thแปฉ hai', 'thแปฉ ba', 'thแปฉ tฦฐ', 'thแปฉ nฤƒm', 'thแปฉ sรกu', 'thแปฉ bแบฃy'], + 'weekdays_short' => ['CN', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7'], + 'weekdays_min' => ['CN', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' vร  '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vi_VN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vi_VN.php new file mode 100644 index 0000000000..18d8987696 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vi_VN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/vi.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vo.php new file mode 100644 index 0000000000..e273033fa6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vo.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'months_short' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => ':count yel', + 'y' => ':count yel', + 'a_year' => ':count yel', + + 'month' => ':count mul', + 'm' => ':count mul', + 'a_month' => ':count mul', + + 'week' => ':count vig', + 'w' => ':count vig', + 'a_week' => ':count vig', + + 'day' => ':count del', + 'd' => ':count del', + 'a_day' => ':count del', + + 'hour' => ':count dรผp', + 'h' => ':count dรผp', + 'a_hour' => ':count dรผp', + + 'minute' => ':count minut', + 'min' => ':count minut', + 'a_minute' => ':count minut', + + 'second' => ':count sekun', + 's' => ':count sekun', + 'a_second' => ':count sekun', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vun.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vun.php new file mode 100644 index 0000000000..ed92e8e70e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/vun.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['utuko', 'kyiukonyi'], + 'weekdays' => ['Jumapilyi', 'Jumatatuu', 'Jumanne', 'Jumatanu', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprilyi', 'Mei', 'Junyi', 'Julyai', 'Agusti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wa.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wa.php new file mode 100644 index 0000000000..f6dc4ccd3b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wa.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wa_BE.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wa_BE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wa_BE.php new file mode 100644 index 0000000000..a76d80d939 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wa_BE.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Djan SACRE Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['di djanvรฎ', 'di fevrรฎ', 'di mรฅss', 'dโ€™ย avri', 'di may', 'di djun', 'di djulete', 'dโ€™ย awousse', 'di setimbe', 'dโ€™ย octรดbe', 'di nรดvimbe', 'di decimbe'], + 'months_short' => ['dja', 'fev', 'mรฅs', 'avr', 'may', 'djn', 'djl', 'awo', 'set', 'oct', 'nรดv', 'dec'], + 'weekdays' => ['dimegne', 'londi', 'mรฅrdi', 'mierkidi', 'djudi', 'vรฉnrdi', 'semdi'], + 'weekdays_short' => ['dim', 'lon', 'mรฅr', 'mie', 'dju', 'vรฉn', 'sem'], + 'weekdays_min' => ['dim', 'lon', 'mรฅr', 'mie', 'dju', 'vรฉn', 'sem'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count anรชye', + 'y' => ':count anรชye', + 'a_year' => ':count anรชye', + + 'month' => ':count meรปs', + 'm' => ':count meรปs', + 'a_month' => ':count meรปs', + + 'week' => ':count samwinne', + 'w' => ':count samwinne', + 'a_week' => ':count samwinne', + + 'day' => ':count djoรป', + 'd' => ':count djoรป', + 'a_day' => ':count djoรป', + + 'hour' => ':count eure', + 'h' => ':count eure', + 'a_hour' => ':count eure', + + 'minute' => ':count munute', + 'min' => ':count munute', + 'a_minute' => ':count munute', + + 'second' => ':count Sigonde', + 's' => ':count Sigonde', + 'a_second' => ':count Sigonde', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wae.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wae.php new file mode 100644 index 0000000000..bf57f23e69 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wae.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wae_CH.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wae_CH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wae_CH.php new file mode 100644 index 0000000000..2af50b4b2d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wae_CH.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Walser Translation Team ml@translate-wae.ch + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'months' => ['Jenner', 'Hornig', 'Mรคrze', 'Abrille', 'Meije', 'Brรกฤet', 'Heiwet', 'ร–igลกte', 'Herbลกtmรกnet', 'Wรญmรกnet', 'Wintermรกnet', 'Chriลกtmรกnet'], + 'months_short' => ['Jen', 'Hor', 'Mรคr', 'Abr', 'Mei', 'Brรก', 'Hei', 'ร–ig', 'Her', 'Wรญm', 'Win', 'Chr'], + 'weekdays' => ['Suntag', 'Mรคntag', 'Zischtag', 'Mittwuch', 'Frontag', 'Fritag', 'Samschtag'], + 'weekdays_short' => ['Sun', 'Mรคn', 'Zis', 'Mit', 'Fro', 'Fri', 'Sam'], + 'weekdays_min' => ['Sun', 'Mรคn', 'Zis', 'Mit', 'Fro', 'Fri', 'Sam'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'month' => ':count Maano', // less reliable + 'm' => ':count Maano', // less reliable + 'a_month' => ':count Maano', // less reliable +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wal.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wal.php new file mode 100644 index 0000000000..e8ec40ff62 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wal.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wal_ET.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wal_ET.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wal_ET.php new file mode 100644 index 0000000000..a4e619a873 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wal_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['แŒƒแŠ•แ‹ฉแ‹ˆแˆช', 'แŒแ‰ฅแˆฉแ‹ˆแˆช', 'แˆ›แˆญแ‰ฝ', 'แŠคแ•แˆจแˆ', 'แˆœแ‹ญ', 'แŒแŠ•', 'แŒแˆ‹แ‹ญ', 'แŠฆแŒˆแˆตแ‰ต', 'แˆดแ•แ‰ดแˆแ‰ แˆญ', 'แŠฆแŠญแ‰ฐแ‹แ‰ แˆญ', 'แŠ–แ‰ฌแˆแ‰ แˆญ', 'แ‹ฒแˆดแˆแ‰ แˆญ'], + 'months_short' => ['แŒƒแŠ•แ‹ฉ', 'แŒแ‰ฅแˆฉ', 'แˆ›แˆญแ‰ฝ', 'แŠคแ•แˆจ', 'แˆœแ‹ญ ', 'แŒแŠ• ', 'แŒแˆ‹แ‹ญ', 'แŠฆแŒˆแˆต', 'แˆดแ•แ‰ด', 'แŠฆแŠญแ‰ฐ', 'แŠ–แ‰ฌแˆ', 'แ‹ฒแˆดแˆ'], + 'weekdays' => ['แ‹ˆแŒ‹', 'แˆณแ‹ญแŠ–', 'แˆ›แ‰†แˆณแŠ›', 'แŠ แˆฉแ‹‹', 'แˆƒแˆ™แˆณ', 'แŠ แˆญแ‰ฃ', 'แ‰„แˆซ'], + 'weekdays_short' => ['แ‹ˆแŒ‹ ', 'แˆณแ‹ญแŠ–', 'แˆ›แ‰†แˆณ', 'แŠ แˆฉแ‹‹', 'แˆƒแˆ™แˆณ', 'แŠ แˆญแ‰ฃ', 'แ‰„แˆซ '], + 'weekdays_min' => ['แ‹ˆแŒ‹ ', 'แˆณแ‹ญแŠ–', 'แˆ›แ‰†แˆณ', 'แŠ แˆฉแ‹‹', 'แˆƒแˆ™แˆณ', 'แŠ แˆญแ‰ฃ', 'แ‰„แˆซ '], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['แˆ›แˆˆแ‹ถ', 'แ‰ƒแˆ›'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wo.php new file mode 100644 index 0000000000..74b95df0a1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wo.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wo_SN.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wo_SN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wo_SN.php new file mode 100644 index 0000000000..f8a85b3e4e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wo_SN.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - The Debian Project Christian Perrier bubulle@debian.org + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'months' => ['sanwiy\'e', 'feebriy\'e', 'mars', 'awril', 'me', 'suwen', 'sulet', 'uut', 'septaambar', 'oktoobar', 'nowaambar', 'desaambar'], + 'months_short' => ['san', 'fee', 'mar', 'awr', 'me ', 'suw', 'sul', 'uut', 'sep', 'okt', 'now', 'des'], + 'weekdays' => ['dib\'eer', 'altine', 'talaata', 'allarba', 'alxames', 'ajjuma', 'gaawu'], + 'weekdays_short' => ['dib', 'alt', 'tal', 'all', 'alx', 'ajj', 'gaa'], + 'weekdays_min' => ['dib', 'alt', 'tal', 'all', 'alx', 'ajj', 'gaa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'year' => ':count at', + 'month' => ':count wรจr', + 'week' => ':count ayubรฉs', + 'day' => ':count bรฉs', + 'hour' => ':count waxtu', + 'minute' => ':count simili', + 'second' => ':count saa', +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/xh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/xh.php new file mode 100644 index 0000000000..e88c78d9d4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/xh.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/xh_ZA.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/xh_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/xh_ZA.php new file mode 100644 index 0000000000..910f8311e6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/xh_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['eyoMqungu', 'eyoMdumba', 'eyoKwindla', 'uTshazimpuzi', 'uCanzibe', 'eyeSilimela', 'eyeKhala', 'eyeThupa', 'eyoMsintsi', 'eyeDwarha', 'eyeNkanga', 'eyoMnga'], + 'months_short' => ['Mqu', 'Mdu', 'Kwi', 'Tsh', 'Can', 'Sil', 'Kha', 'Thu', 'Msi', 'Dwa', 'Nka', 'Mng'], + 'weekdays' => ['iCawa', 'uMvulo', 'lwesiBini', 'lwesiThathu', 'ulweSine', 'lwesiHlanu', 'uMgqibelo'], + 'weekdays_short' => ['Caw', 'Mvu', 'Bin', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'weekdays_min' => ['Caw', 'Mvu', 'Bin', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ihlobo', // less reliable + 'y' => ':count ihlobo', // less reliable + 'a_year' => ':count ihlobo', // less reliable + + 'hour' => ':count iwotshi', // less reliable + 'h' => ':count iwotshi', // less reliable + 'a_hour' => ':count iwotshi', // less reliable + + 'minute' => ':count ingqalelo', // less reliable + 'min' => ':count ingqalelo', // less reliable + 'a_minute' => ':count ingqalelo', // less reliable + + 'second' => ':count nceda', // less reliable + 's' => ':count nceda', // less reliable + 'a_second' => ':count nceda', // less reliable + + 'month' => ':count inyanga', + 'm' => ':count inyanga', + 'a_month' => ':count inyanga', + + 'week' => ':count veki', + 'w' => ':count veki', + 'a_week' => ':count veki', + + 'day' => ':count imini', + 'd' => ':count imini', + 'a_day' => ':count imini', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/xog.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/xog.php new file mode 100644 index 0000000000..eb55b4ab85 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/xog.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Munkyo', 'Eigulo'], + 'weekdays' => ['Sabiiti', 'Balaza', 'Owokubili', 'Owokusatu', 'Olokuna', 'Olokutaanu', 'Olomukaaga'], + 'weekdays_short' => ['Sabi', 'Bala', 'Kubi', 'Kusa', 'Kuna', 'Kuta', 'Muka'], + 'weekdays_min' => ['Sabi', 'Bala', 'Kubi', 'Kusa', 'Kuna', 'Kuta', 'Muka'], + 'months' => ['Janwaliyo', 'Febwaliyo', 'Marisi', 'Apuli', 'Maayi', 'Juuni', 'Julaayi', 'Agusito', 'Sebuttemba', 'Okitobba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apu', 'Maa', 'Juu', 'Jul', 'Agu', 'Seb', 'Oki', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yav.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yav.php new file mode 100644 index 0000000000..225a20d8ff --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yav.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['kiษ›mษ›ฬษ›m', 'kisษ›ฬndษ›'], + 'weekdays' => ['sษ”ฬndiษ›', 'mรณndie', 'muรกnyรกล‹mรณndie', 'metรบkpรญรกpษ›', 'kรบpรฉlimetรบkpiapษ›', 'felรฉte', 'sรฉselรฉ'], + 'weekdays_short' => ['sd', 'md', 'mw', 'et', 'kl', 'fl', 'ss'], + 'weekdays_min' => ['sd', 'md', 'mw', 'et', 'kl', 'fl', 'ss'], + 'months' => ['pikรญtรญkรญtie, oรณlรญ รบ kutรบan', 'siษ›yษ›ฬ, oรณli รบ kรกndรญษ›', 'ษ”nsรบmbษ”l, oรณli รบ kรกtรกtรบษ›', 'mesiล‹, oรณli รบ kรฉnie', 'ensil, oรณli รบ kรกtรกnuษ›', 'ษ”sษ”n', 'efute', 'pisuyรบ', 'imษ›ล‹ i puษ”s', 'imษ›ล‹ i putรบk,oรณli รบ kรกtรญษ›', 'makandikษ›', 'pilษ”ndษ”ฬ'], + 'months_short' => ['o.1', 'o.2', 'o.3', 'o.4', 'o.5', 'o.6', 'o.7', 'o.8', 'o.9', 'o.10', 'o.11', 'o.12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yi.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yi.php new file mode 100644 index 0000000000..8f32022946 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/yi_US.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yi_US.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yi_US.php new file mode 100644 index 0000000000..f764d36f67 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yi_US.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - http://www.uyip.org/ Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['ื™ืึทื ื•ืึทืจ', 'ืคึฟืขื‘ืจื•ืึทืจ', 'ืžืขืจืฅ', 'ืึทืคึผืจื™ืœ', 'ืžื™ื™', 'ื™ื•ื ื™', 'ื™ื•ืœื™', 'ืื•ื™ื’ื•ืกื˜', 'ืกืขืคึผื˜ืขืžื‘ืขืจ', 'ืืงื˜ืื‘ืขืจ', 'ื ืื•ื•ืขืžื‘ืขืจ', 'ื“ืขืฆืขืžื‘ืขืจ'], + 'months_short' => ['ื™ืึทื ', 'ืคึฟืขื‘', 'ืžืึทืจ', 'ืึทืคึผืจ', 'ืžืฒึท ', 'ื™ื•ื ', 'ื™ื•ืœ', 'ืืฑื’', 'ืกืขืคึผ', 'ืึธืงื˜', 'ื ืึธืฐ', 'ื“ืขืฆ'], + 'weekdays' => ['ื–ื•ื ื˜ื™ืง', 'ืžืึธื ื˜ื™ืง', 'ื“ื™ื ืกื˜ื™ืง', 'ืžื™ื˜ืฐืึธืš', 'ื“ืึธื ืขืจืฉื˜ื™ืง', 'ืคึฟืจืฒึทื˜ื™ืง', 'ืฉื‘ืช'], + 'weekdays_short' => ['ื–ื•ื \'', 'ืžืึธื \'', 'ื“ื™ื \'', 'ืžื™ื˜\'', 'ื“ืึธื \'', 'ืคึฟืจืฒึท\'', 'ืฉื‘ืช'], + 'weekdays_min' => ['ื–ื•ื \'', 'ืžืึธื \'', 'ื“ื™ื \'', 'ืžื™ื˜\'', 'ื“ืึธื \'', 'ืคึฟืจืฒึท\'', 'ืฉื‘ืช'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ื™ืืจ', + 'y' => ':count ื™ืืจ', + 'a_year' => ':count ื™ืืจ', + + 'month' => ':count ื—ื•ื“ืฉ', + 'm' => ':count ื—ื•ื“ืฉ', + 'a_month' => ':count ื—ื•ื“ืฉ', + + 'week' => ':count ื•ื•ืึธืš', + 'w' => ':count ื•ื•ืึธืš', + 'a_week' => ':count ื•ื•ืึธืš', + + 'day' => ':count ื˜ืึธื’', + 'd' => ':count ื˜ืึธื’', + 'a_day' => ':count ื˜ืึธื’', + + 'hour' => ':count ืฉืขื”', + 'h' => ':count ืฉืขื”', + 'a_hour' => ':count ืฉืขื”', + + 'minute' => ':count ืžื™ื ื•ื˜', + 'min' => ':count ืžื™ื ื•ื˜', + 'a_minute' => ':count ืžื™ื ื•ื˜', + + 'second' => ':count ืกืขืงื•ื ื“ืข', + 's' => ':count ืกืขืงื•ื ื“ืข', + 'a_second' => ':count ืกืขืงื•ื ื“ืข', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yo.php new file mode 100644 index 0000000000..0a829810a9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yo.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Franรงois B + * - Atolagbe Abisoye + */ +return [ + 'year' => 'แปduฬn :count', + 'a_year' => '{1}แปduฬn kan|แปduฬn :count', + 'month' => 'osuฬ€ :count', + 'a_month' => '{1}osuฬ€ kan|osuฬ€ :count', + 'week' => 'แปsแบน :count', + 'a_week' => '{1}แปsแบน kan|แปsแบน :count', + 'day' => 'แปjแปฬ :count', + 'a_day' => '{1}แปjแปฬ kan|แปjแปฬ :count', + 'hour' => 'waฬkati :count', + 'a_hour' => '{1}waฬkati kan|waฬkati :count', + 'minute' => 'iฬ€sแบนjuฬ :count', + 'a_minute' => '{1}iฬ€sแบนjuฬ kan|iฬ€sแบนjuฬ :count', + 'second' => 'iaayaฬ :count', + 'a_second' => '{1}iฬ€sแบนjuฬ aayaฬ die|aayaฬ :count', + 'ago' => ':time kแปjaฬ', + 'from_now' => 'niฬ :time', + 'diff_yesterday' => 'Aฬ€na', + 'diff_yesterday_regexp' => 'Aฬ€na(?:\\s+ni)?', + 'diff_today' => 'Oฬ€niฬ€', + 'diff_today_regexp' => 'Oฬ€niฬ€(?:\\s+ni)?', + 'diff_tomorrow' => 'แปŒฬ€la', + 'diff_tomorrow_regexp' => 'แปŒฬ€la(?:\\s+ni)?', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'calendar' => [ + 'sameDay' => '[Oฬ€niฬ€ ni] LT', + 'nextDay' => '[แปŒฬ€la ni] LT', + 'nextWeek' => 'dddd [แปŒsแบนฬ€ toฬn\'bแป] [ni] LT', + 'lastDay' => '[Aฬ€na ni] LT', + 'lastWeek' => 'dddd [แปŒsแบนฬ€ toฬlแปฬ] [ni] LT', + 'sameElse' => 'L', + ], + 'ordinal' => 'แปjแปฬ :number', + 'months' => ['Sแบนฬrแบนฬ', 'Eฬ€reฬ€leฬ€', 'แบธrแบนฬ€naฬ€', 'Iฬ€gbeฬ', 'Eฬ€bibi', 'Oฬ€kuฬ€du', 'Agแบนmo', 'Oฬ€guฬn', 'Owewe', 'แปŒฬ€waฬ€raฬ€', 'Beฬluฬ', 'แปŒฬ€pแบนฬ€ฬ€'], + 'months_short' => ['Sแบนฬr', 'Eฬ€rl', 'แบธrn', 'Iฬ€gb', 'Eฬ€bi', 'Oฬ€kuฬ€', 'Agแบน', 'Oฬ€guฬ', 'Owe', 'แปŒฬ€waฬ€', 'Beฬl', 'แปŒฬ€pแบนฬ€ฬ€'], + 'weekdays' => ['Aฬ€iฬ€kuฬ', 'Ajeฬ', 'Iฬ€sแบนฬgun', 'แปŒjแปฬruฬ', 'แปŒjแปฬbแป', 'แบธtiฬ€', 'Aฬ€baฬmแบนฬta'], + 'weekdays_short' => ['Aฬ€iฬ€k', 'Ajeฬ', 'Iฬ€sแบนฬ', 'แปŒjr', 'แปŒjb', 'แบธtiฬ€', 'Aฬ€baฬ'], + 'weekdays_min' => ['Aฬ€iฬ€', 'Aj', 'Iฬ€s', 'แปŒr', 'แปŒb', 'แบธt', 'Aฬ€b'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'meridiem' => ['ร€รกrแปฬ€', 'แปŒฬ€sรกn'], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yo_BJ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yo_BJ.php new file mode 100644 index 0000000000..12b9e815c2 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yo_BJ.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/yo.php', [ + 'meridiem' => ['ร€รกrษ”ฬ€', 'ฦ†ฬ€sรกn'], + 'weekdays' => ['ฦ†jษ”ฬ ร€รฌkรบ', 'ฦ†jษ”ฬ Ajรฉ', 'ฦ†jษ”ฬ รŒsษ›ฬgun', 'ฦ†jษ”ฬrรบ', 'ฦ†jษ”ฬbษ”', 'ฦ†jษ”ฬ ฦtรฌ', 'ฦ†jษ”ฬ ร€bรกmษ›ฬta'], + 'weekdays_short' => ['ร€รฌkรบ', 'Ajรฉ', 'รŒsษ›ฬgun', 'ฦ†jษ”ฬrรบ', 'ฦ†jษ”ฬbษ”', 'ฦtรฌ', 'ร€bรกmษ›ฬta'], + 'weekdays_min' => ['ร€รฌkรบ', 'Ajรฉ', 'รŒsษ›ฬgun', 'ฦ†jษ”ฬrรบ', 'ฦ†jษ”ฬbษ”', 'ฦtรฌ', 'ร€bรกmษ›ฬta'], + 'months' => ['Oshรน Shษ›ฬrษ›ฬ', 'Oshรน รˆrรจlรจ', 'Oshรน ฦrษ›ฬ€nร ', 'Oshรน รŒgbรฉ', 'Oshรน ฦฬ€bibi', 'Oshรน ร’kรบdu', 'Oshรน Agษ›mษ”', 'Oshรน ร’gรบn', 'Oshรน Owewe', 'Oshรน ฦ†ฬ€wร rร ', 'Oshรน Bรฉlรบ', 'Oshรน ฦ†ฬ€pษ›ฬ€'], + 'months_short' => ['Shษ›ฬrษ›ฬ', 'รˆrรจlรจ', 'ฦrษ›ฬ€nร ', 'รŒgbรฉ', 'ฦฬ€bibi', 'ร’kรบdu', 'Agษ›mษ”', 'ร’gรบn', 'Owewe', 'ฦ†ฬ€wร rร ', 'Bรฉlรบ', 'ฦ†ฬ€pษ›ฬ€'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yo_NG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yo_NG.php new file mode 100644 index 0000000000..6860bc1a95 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yo_NG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/yo.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yue.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yue.php new file mode 100644 index 0000000000..ce233a4f87 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yue.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/yue_HK.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yue_HK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yue_HK.php new file mode 100644 index 0000000000..4e7d5c368b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yue_HK.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/zh_HK.php', [ + 'formats' => [ + 'L' => 'YYYYๅนดMMๆœˆDDๆ—ฅ dddd', + ], + 'months' => ['1ๆœˆ', '2ๆœˆ', '3ๆœˆ', '4ๆœˆ', '5ๆœˆ', '6ๆœˆ', '7ๆœˆ', '8ๆœˆ', '9ๆœˆ', '10ๆœˆ', '11ๆœˆ', '12ๆœˆ'], + 'months_short' => ['1ๆœˆ', '2ๆœˆ', '3ๆœˆ', '4ๆœˆ', '5ๆœˆ', '6ๆœˆ', '7ๆœˆ', '8ๆœˆ', '9ๆœˆ', '10ๆœˆ', '11ๆœˆ', '12ๆœˆ'], + 'weekdays' => ['ๆ˜ŸๆœŸๆ—ฅ', 'ๆ˜ŸๆœŸไธ€', 'ๆ˜ŸๆœŸไบŒ', 'ๆ˜ŸๆœŸไธ‰', 'ๆ˜ŸๆœŸๅ››', 'ๆ˜ŸๆœŸไบ”', 'ๆ˜ŸๆœŸๅ…ญ'], + 'weekdays_short' => ['ๆ—ฅ', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ'], + 'weekdays_min' => ['ๆ—ฅ', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ไธŠๅˆ', 'ไธ‹ๅˆ'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hans.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hans.php new file mode 100644 index 0000000000..db913caae8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hans.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hans.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hant.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hant.php new file mode 100644 index 0000000000..e2526f1315 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hant.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yuw.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yuw.php new file mode 100644 index 0000000000..8efdc93783 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yuw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/yuw_PG.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yuw_PG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yuw_PG.php new file mode 100644 index 0000000000..b99ad2e8e8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yuw_PG.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Information from native speakers Hannah Sarvasy nungon.localization@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['jenuari', 'febuari', 'mas', 'epril', 'mei', 'jun', 'julai', 'รถgus', 'septemba', 'รถktoba', 'nรถwemba', 'diksemba'], + 'months_short' => ['jen', 'feb', 'mas', 'epr', 'mei', 'jun', 'jul', 'รถgu', 'sep', 'รถkt', 'nรถw', 'dis'], + 'weekdays' => ['sรถnda', 'mรถnda', 'sinda', 'mitiwรถ', 'sogipbono', 'nenggo', 'sรถndanggie'], + 'weekdays_short' => ['sรถn', 'mรถn', 'sin', 'mit', 'soi', 'nen', 'sab'], + 'weekdays_min' => ['sรถn', 'mรถn', 'sin', 'mit', 'soi', 'nen', 'sab'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zgh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zgh.php new file mode 100644 index 0000000000..4d2c3b3769 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zgh.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - BAKTETE Miloud + */ +return [ + 'year' => ':count โต“โต™โดณโดณโตฏโดฐโต™|:count โต‰โต™โดณโดณโต“โต™โดฐ', + 'a_year' => 'โต“โต™โดณโดณโตฏโดฐโต™|:count โต‰โต™โดณโดณโต“โต™โดฐ', + 'y' => ':count โต“โต™โดณโดณโตฏโดฐโต™|:count โต‰โต™โดณโดณโต“โต™โดฐ', + 'month' => ':count โตกโดฐโตขโตขโต“โต”|:count โดฐโตขโตขโต“โต”โต', + 'a_month' => 'โต‰โดทโตŠ โตกโดฐโตขโตขโต“โต”|:count โดฐโตขโตขโต“โต”โต', + 'm' => ':count โดฐโตขโตขโต“โต”โต', + 'week' => ':count โต‰โตŽโดฐโตโดฐโต™โต™|:count โต‰โตŽโดฐโตโดฐโต™โต™โต', + 'a_week' => 'โต‰โต›โตœ โต‰โตŽโดฐโตโดฐโต™โต™|:count โต‰โตŽโดฐโตโดฐโต™โต™โต', + 'w' => ':count โต‰โตŽโดฐโตโดฐโต™โต™.', + 'day' => ':count โตกโดฐโต™โต™|:count โต“โต™โต™โดฐโต', + 'a_day' => 'โต‰โดทโตŠ โตกโดฐโต™โต™|:count โต“โต™โต™โดฐโต', + 'd' => ':count โต“', + 'hour' => ':count โตœโต™โต”โดฐโดณโตœ|:count โตœโต‰โต™โต”โดฐโดณโต‰โต', + 'a_hour' => 'โต‰โต›โตœ โตœโต™โต”โดฐโดณโตœ|:count โตœโต‰โต™โต”โดฐโดณโต‰โต', + 'h' => ':count โตœ', + 'minute' => ':count โตœโต“โต™โดทโต‰โดทโตœ|:count โตœโต“โต™โดทโต‰โดทโต‰โต', + 'a_minute' => 'โต‰โต›โตœ โตœโต“โต™โดทโต‰โดทโตœ|:count โตœโต“โต™โดทโต‰โดทโต‰โต', + 'min' => ':count โตœโต“โต™', + 'second' => ':count โตœโต™โต‰โตโตœ|:count โตœโต‰โต™โต‰โตโดฐ', + 'a_second' => 'โดฝโต”โดฐ โตœโต‰โต™โต‰โตโดฐ|:count โตœโต‰โต™โต‰โตโดฐ', + 's' => ':count โตœ', + 'ago' => 'โตฃโดณ :time', + 'from_now' => 'โดทโดณ :time', + 'after' => ':time โดฐโตกโดฐโต”', + 'before' => ':time โดทโดฐโตœ', + 'diff_now' => 'โดฐโดทโตกโดฐโตโต‰', + 'diff_today' => 'โดฐโต™โต™', + 'diff_today_regexp' => 'โดฐโต™โต™(?:\\s+โดฐ/โดฐโดท)?(?:\\s+โดณ)?', + 'diff_yesterday' => 'โดฐโต™โต™โตโตโดฐโตŸ', + 'diff_yesterday_regexp' => 'โดฐโต™โต™โตโตโดฐโตŸ(?:\\s+โดณ)?', + 'diff_tomorrow' => 'โดฐโต™โดฝโดฝโดฐ', + 'diff_tomorrow_regexp' => 'โดฐโต™โดฝโดฝโดฐ(?:\\s+โดณ)?', + 'diff_before_yesterday' => 'โดผโต” โต‰โดนโตโตโดฐโตŸ', + 'diff_after_tomorrow' => 'โตโดฐโดผ โต“โต™โดฝโดฝโดฐ', + 'period_recurrences' => ':count โตœโต‰โดฝโดฝโดฐโต', + 'period_interval' => 'โดฝโต“ :interval', + 'period_start_date' => 'โดณ :date', + 'period_end_date' => 'โต‰ :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[โดฐโต™โต™ โดฐ/โดฐโดท โดณ] LT', + 'nextDay' => '[โดฐโต™โดฝโดฝโดฐ โดณ] LT', + 'nextWeek' => 'dddd [โดณ] LT', + 'lastDay' => '[โดฐโต™โต™โตโตโดฐโตŸ โดณ] LT', + 'lastWeek' => 'dddd [โดฐโตŽโดณโดณโดฐโต”โต“ โดณ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['โตœโต‰โดผโดฐโตกโตœ', 'โตœโดฐโดทโดณโดณโตฏโดฐโตœ'], + 'months' => ['โต‰โตโตโดฐโตขโต”', 'โดฑโต•โดฐโตขโต•', 'โตŽโดฐโต•โตš', 'โต‰โดฑโต”โต‰โต”', 'โตŽโดฐโตขโตขโต“', 'โตขโต“โตโตขโต“', 'โตขโต“โตโตขโต“โตฃ', 'โต–โต“โต›โตœ', 'โต›โต“โตœโดฐโตโดฑโต‰โต”', 'โดฝโตŸโต“โดฑโต•', 'โตโต“โตกโดฐโตโดฑโต‰โต”', 'โดทโต“โตŠโดฐโตโดฑโต‰โต”'], + 'months_short' => ['โต‰โตโต', 'โดฑโต•โดฐ', 'โตŽโดฐโต•', 'โต‰โดฑโต”', 'โตŽโดฐโตข', 'โตขโต“โต', 'โตขโต“โต', 'โต–โต“โต›', 'โต›โต“โตœ', 'โดฝโตŸโต“', 'โตโต“โตก', 'โดทโต“โตŠ'], + 'weekdays' => ['โต“โต™โดฐโตŽโดฐโต™', 'โตกโดฐโตขโตโดฐโต™', 'โต“โต™โต‰โตโดฐโต™', 'โตกโดฐโดฝโต•โดฐโต™', 'โต“โดฝโตกโดฐโต™', 'โต“โต™โต‰โตŽโตกโดฐโต™', 'โต“โต™โต‰โดนโตขโดฐโต™'], + 'weekdays_short' => ['โต“โต™โดฐ', 'โตกโดฐโตข', 'โต“โต™โต‰', 'โตกโดฐโดฝ', 'โต“โดฝโตก', 'โต“โต™โต‰โตŽ', 'โต“โต™โต‰โดน'], + 'weekdays_min' => ['โต“โต™โดฐ', 'โตกโดฐโตข', 'โต“โต™โต‰', 'โตกโดฐโดฝ', 'โต“โดฝโตก', 'โต“โต™โต‰โตŽ', 'โต“โต™โต‰โดน'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' โดท '], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh.php new file mode 100644 index 0000000000..1187c3d77f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - xuri + * - sycuato + * - bokideckonja + * - Luo Ning + * - William Yang (williamyang233) + */ +return array_merge(require __DIR__.'/zh_Hans.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYYๅนดMๆœˆDๆ—ฅ', + 'LLL' => 'YYYYๅนดMๆœˆDๆ—ฅ A h็‚นmmๅˆ†', + 'LLLL' => 'YYYYๅนดMๆœˆDๆ—ฅdddd A h็‚นmmๅˆ†', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_CN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_CN.php new file mode 100644 index 0000000000..9c05d5a8d9 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_CN.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - monkeycon + * - Franรงois B + * - Jason Katz-Brown + * - Serhan Apaydฤฑn + * - Matt Johnson + * - JD Isaacks + * - Zeno Zeng + * - Chris Hemp + * - shankesgk2 + */ +return array_merge(require __DIR__.'/zh.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYYๅนดMๆœˆDๆ—ฅ', + 'LLL' => 'YYYYๅนดMๆœˆDๆ—ฅAh็‚นmmๅˆ†', + 'LLLL' => 'YYYYๅนดMๆœˆDๆ—ฅddddAh็‚นmmๅˆ†', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_HK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_HK.php new file mode 100644 index 0000000000..c3ee9fcb2f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_HK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant_HK.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans.php new file mode 100644 index 0000000000..9b91785e10 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - monkeycon + * - Franรงois B + * - Jason Katz-Brown + * - Konstantin Konev + * - Chris Lam + * - Serhan Apaydฤฑn + * - Gary Lo + * - JD Isaacks + * - Chris Hemp + * - shankesgk2 + * - Daniel Cheung (danvim) + */ +return [ + 'year' => ':count:optional-spaceๅนด', + 'y' => ':count:optional-spaceๅนด', + 'month' => ':count:optional-spaceไธชๆœˆ', + 'm' => ':count:optional-spaceไธชๆœˆ', + 'week' => ':count:optional-spaceๅ‘จ', + 'w' => ':count:optional-spaceๅ‘จ', + 'day' => ':count:optional-spaceๅคฉ', + 'd' => ':count:optional-spaceๅคฉ', + 'hour' => ':count:optional-spaceๅฐๆ—ถ', + 'h' => ':count:optional-spaceๅฐๆ—ถ', + 'minute' => ':count:optional-spaceๅˆ†้’Ÿ', + 'min' => ':count:optional-spaceๅˆ†้’Ÿ', + 'second' => ':count:optional-space็ง’', + 'a_second' => '{1}ๅ‡ ็ง’|]1,Inf[:count:optional-space็ง’', + 's' => ':count:optional-space็ง’', + 'ago' => ':timeๅ‰', + 'from_now' => ':timeๅŽ', + 'after' => ':timeๅŽ', + 'before' => ':timeๅ‰', + 'diff_now' => '็Žฐๅœจ', + 'diff_today' => 'ไปŠๅคฉ', + 'diff_yesterday' => 'ๆ˜จๅคฉ', + 'diff_tomorrow' => 'ๆ˜Žๅคฉ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYYๅนดMๆœˆDๆ—ฅ', + 'LLL' => 'YYYYๅนดMๆœˆDๆ—ฅ HH:mm', + 'LLLL' => 'YYYYๅนดMๆœˆDๆ—ฅdddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ไปŠๅคฉ]LT', + 'nextDay' => '[ๆ˜Žๅคฉ]LT', + 'nextWeek' => '[ไธ‹]ddddLT', + 'lastDay' => '[ๆ˜จๅคฉ]LT', + 'lastWeek' => '[ไธŠ]ddddLT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'ๆ—ฅ'; + case 'M': + return $number.'ๆœˆ'; + case 'w': + case 'W': + return $number.'ๅ‘จ'; + default: + return $number; + } + }, + 'meridiem' => function ($hour, $minute) { + $time = $hour * 100 + $minute; + if ($time < 600) { + return 'ๅ‡Œๆ™จ'; + } + if ($time < 900) { + return 'ๆ—ฉไธŠ'; + } + if ($time < 1130) { + return 'ไธŠๅˆ'; + } + if ($time < 1230) { + return 'ไธญๅˆ'; + } + if ($time < 1800) { + return 'ไธ‹ๅˆ'; + } + + return 'ๆ™šไธŠ'; + }, + 'months' => ['ไธ€ๆœˆ', 'ไบŒๆœˆ', 'ไธ‰ๆœˆ', 'ๅ››ๆœˆ', 'ไบ”ๆœˆ', 'ๅ…ญๆœˆ', 'ไธƒๆœˆ', 'ๅ…ซๆœˆ', 'ไนๆœˆ', 'ๅๆœˆ', 'ๅไธ€ๆœˆ', 'ๅไบŒๆœˆ'], + 'months_short' => ['1ๆœˆ', '2ๆœˆ', '3ๆœˆ', '4ๆœˆ', '5ๆœˆ', '6ๆœˆ', '7ๆœˆ', '8ๆœˆ', '9ๆœˆ', '10ๆœˆ', '11ๆœˆ', '12ๆœˆ'], + 'weekdays' => ['ๆ˜ŸๆœŸๆ—ฅ', 'ๆ˜ŸๆœŸไธ€', 'ๆ˜ŸๆœŸไบŒ', 'ๆ˜ŸๆœŸไธ‰', 'ๆ˜ŸๆœŸๅ››', 'ๆ˜ŸๆœŸไบ”', 'ๆ˜ŸๆœŸๅ…ญ'], + 'weekdays_short' => ['ๅ‘จๆ—ฅ', 'ๅ‘จไธ€', 'ๅ‘จไบŒ', 'ๅ‘จไธ‰', 'ๅ‘จๅ››', 'ๅ‘จไบ”', 'ๅ‘จๅ…ญ'], + 'weekdays_min' => ['ๆ—ฅ', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => '', +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_HK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_HK.php new file mode 100644 index 0000000000..db913caae8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_HK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hans.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_MO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_MO.php new file mode 100644 index 0000000000..db913caae8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_MO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hans.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_SG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_SG.php new file mode 100644 index 0000000000..db913caae8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_SG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hans.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant.php new file mode 100644 index 0000000000..a27b610939 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Adam + * - monkeycon + * - Franรงois B + * - Jason Katz-Brown + * - Chris Lam + * - Serhan Apaydฤฑn + * - Gary Lo + * - JD Isaacks + * - Chris Hemp + * - Eddie + * - KID + * - shankesgk2 + * - Daniel Cheung (danvim) + */ +return [ + 'year' => ':count:optional-spaceๅนด', + 'y' => ':count:optional-spaceๅนด', + 'month' => ':count:optional-spaceๅ€‹ๆœˆ', + 'm' => ':count:optional-spaceๆœˆ', + 'week' => ':count:optional-space้€ฑ', + 'w' => ':count:optional-space้€ฑ', + 'day' => ':count:optional-spaceๅคฉ', + 'd' => ':count:optional-spaceๅคฉ', + 'hour' => ':count:optional-spaceๅฐๆ™‚', + 'h' => ':count:optional-spaceๅฐๆ™‚', + 'minute' => ':count:optional-spaceๅˆ†้˜', + 'min' => ':count:optional-spaceๅˆ†้˜', + 'second' => ':count:optional-space็ง’', + 'a_second' => '{1}ๅนพ็ง’|]1,Inf[:count:optional-space็ง’', + 's' => ':count:optional-space็ง’', + 'ago' => ':timeๅ‰', + 'from_now' => ':timeๅพŒ', + 'after' => ':timeๅพŒ', + 'before' => ':timeๅ‰', + 'diff_now' => '็พๅœจ', + 'diff_today' => 'ไปŠๅคฉ', + 'diff_yesterday' => 'ๆ˜จๅคฉ', + 'diff_tomorrow' => 'ๆ˜Žๅคฉ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYYๅนดMๆœˆDๆ—ฅ', + 'LLL' => 'YYYYๅนดMๆœˆDๆ—ฅ HH:mm', + 'LLLL' => 'YYYYๅนดMๆœˆDๆ—ฅdddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ไปŠๅคฉ] LT', + 'nextDay' => '[ๆ˜Žๅคฉ] LT', + 'nextWeek' => '[ไธ‹]dddd LT', + 'lastDay' => '[ๆ˜จๅคฉ] LT', + 'lastWeek' => '[ไธŠ]dddd LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'ๆ—ฅ'; + case 'M': + return $number.'ๆœˆ'; + case 'w': + case 'W': + return $number.'ๅ‘จ'; + default: + return $number; + } + }, + 'meridiem' => function ($hour, $minute) { + $time = $hour * 100 + $minute; + if ($time < 600) { + return 'ๅ‡Œๆ™จ'; + } + if ($time < 900) { + return 'ๆ—ฉไธŠ'; + } + if ($time < 1130) { + return 'ไธŠๅˆ'; + } + if ($time < 1230) { + return 'ไธญๅˆ'; + } + if ($time < 1800) { + return 'ไธ‹ๅˆ'; + } + + return 'ๆ™šไธŠ'; + }, + 'months' => ['ไธ€ๆœˆ', 'ไบŒๆœˆ', 'ไธ‰ๆœˆ', 'ๅ››ๆœˆ', 'ไบ”ๆœˆ', 'ๅ…ญๆœˆ', 'ไธƒๆœˆ', 'ๅ…ซๆœˆ', 'ไนๆœˆ', 'ๅๆœˆ', 'ๅไธ€ๆœˆ', 'ๅไบŒๆœˆ'], + 'months_short' => ['1ๆœˆ', '2ๆœˆ', '3ๆœˆ', '4ๆœˆ', '5ๆœˆ', '6ๆœˆ', '7ๆœˆ', '8ๆœˆ', '9ๆœˆ', '10ๆœˆ', '11ๆœˆ', '12ๆœˆ'], + 'weekdays' => ['ๆ˜ŸๆœŸๆ—ฅ', 'ๆ˜ŸๆœŸไธ€', 'ๆ˜ŸๆœŸไบŒ', 'ๆ˜ŸๆœŸไธ‰', 'ๆ˜ŸๆœŸๅ››', 'ๆ˜ŸๆœŸไบ”', 'ๆ˜ŸๆœŸๅ…ญ'], + 'weekdays_short' => ['้€ฑๆ—ฅ', '้€ฑไธ€', '้€ฑไบŒ', '้€ฑไธ‰', '้€ฑๅ››', '้€ฑไบ”', '้€ฑๅ…ญ'], + 'weekdays_min' => ['ๆ—ฅ', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => '', +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_HK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_HK.php new file mode 100644 index 0000000000..e2526f1315 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_HK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_MO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_MO.php new file mode 100644 index 0000000000..e2526f1315 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_MO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_TW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_TW.php new file mode 100644 index 0000000000..e2526f1315 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_TW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_MO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_MO.php new file mode 100644 index 0000000000..1c86d477f6 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_MO.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - tarunvelli + * - Eddie + * - KID + * - shankesgk2 + */ +return array_replace_recursive(require __DIR__.'/zh_Hant.php', [ + 'after' => ':timeๅŽ', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_SG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_SG.php new file mode 100644 index 0000000000..c451a562c3 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_SG.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/zh.php', [ + 'formats' => [ + 'L' => 'YYYYๅนดMMๆœˆDDๆ—ฅ', + ], + 'months' => ['ไธ€ๆœˆ', 'ไบŒๆœˆ', 'ไธ‰ๆœˆ', 'ๅ››ๆœˆ', 'ไบ”ๆœˆ', 'ๅ…ญๆœˆ', 'ไธƒๆœˆ', 'ๅ…ซๆœˆ', 'ไนๆœˆ', 'ๅๆœˆ', 'ๅไธ€ๆœˆ', 'ๅไบŒๆœˆ'], + 'months_short' => ['ไธ€ๆœˆ', 'ไบŒๆœˆ', 'ไธ‰ๆœˆ', 'ๅ››ๆœˆ', 'ไบ”ๆœˆ', 'ๅ…ญๆœˆ', 'ไธƒๆœˆ', 'ๅ…ซๆœˆ', 'ไนๆœˆ', 'ๅๆœˆ', 'ๅไธ€ๆœˆ', 'ๅไบŒๆœˆ'], + 'weekdays' => ['ๆ˜ŸๆœŸๆ—ฅ', 'ๆ˜ŸๆœŸไธ€', 'ๆ˜ŸๆœŸไบŒ', 'ๆ˜ŸๆœŸไธ‰', 'ๆ˜ŸๆœŸๅ››', 'ๆ˜ŸๆœŸไบ”', 'ๆ˜ŸๆœŸๅ…ญ'], + 'weekdays_short' => ['ๆ—ฅ', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ'], + 'weekdays_min' => ['ๆ—ฅ', 'ไธ€', 'ไบŒ', 'ไธ‰', 'ๅ››', 'ไบ”', 'ๅ…ญ'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php new file mode 100644 index 0000000000..c6789ed2f5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant_TW.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_YUE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_YUE.php new file mode 100644 index 0000000000..b0d9ba8621 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_YUE.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/zh.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zu.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zu.php new file mode 100644 index 0000000000..9a6cce02c8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zu.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/zu_ZA.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zu_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zu_ZA.php new file mode 100644 index 0000000000..6bfb72f0af --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zu_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Januwari', 'Februwari', 'Mashi', 'Ephreli', 'Meyi', 'Juni', 'Julayi', 'Agasti', 'Septhemba', 'Okthoba', 'Novemba', 'Disemba'], + 'months_short' => ['Jan', 'Feb', 'Mas', 'Eph', 'Mey', 'Jun', 'Jul', 'Aga', 'Sep', 'Okt', 'Nov', 'Dis'], + 'weekdays' => ['iSonto', 'uMsombuluko', 'uLwesibili', 'uLwesithathu', 'uLwesine', 'uLwesihlanu', 'uMgqibelo'], + 'weekdays_short' => ['Son', 'Mso', 'Bil', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'weekdays_min' => ['Son', 'Mso', 'Bil', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'day_of_first_week_of_year' => 1, + + 'year' => 'kweminyaka engu-:count', + 'y' => 'kweminyaka engu-:count', + 'a_year' => 'kweminyaka engu-:count', + + 'month' => 'izinyanga ezingu-:count', + 'm' => 'izinyanga ezingu-:count', + 'a_month' => 'izinyanga ezingu-:count', + + 'week' => 'lwamasonto angu-:count', + 'w' => 'lwamasonto angu-:count', + 'a_week' => 'lwamasonto angu-:count', + + 'day' => 'ezingaba ngu-:count', + 'd' => 'ezingaba ngu-:count', + 'a_day' => 'ezingaba ngu-:count', + + 'hour' => 'amahora angu-:count', + 'h' => 'amahora angu-:count', + 'a_hour' => 'amahora angu-:count', + + 'minute' => 'ngemizuzu engu-:count', + 'min' => 'ngemizuzu engu-:count', + 'a_minute' => 'ngemizuzu engu-:count', + + 'second' => 'imizuzwana engu-:count', + 's' => 'imizuzwana engu-:count', + 'a_second' => 'imizuzwana engu-:count', +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Language.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Language.php new file mode 100644 index 0000000000..1fb5bafdc8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Language.php @@ -0,0 +1,342 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use JsonSerializable; +use ReturnTypeWillChange; + +class Language implements JsonSerializable +{ + /** + * @var array + */ + protected static $languagesNames; + + /** + * @var array + */ + protected static $regionsNames; + + /** + * @var string + */ + protected $id; + + /** + * @var string + */ + protected $code; + + /** + * @var string|null + */ + protected $variant; + + /** + * @var string|null + */ + protected $region; + + /** + * @var array + */ + protected $names; + + /** + * @var string + */ + protected $isoName; + + /** + * @var string + */ + protected $nativeName; + + public function __construct(string $id) + { + $this->id = str_replace('-', '_', $id); + $parts = explode('_', $this->id); + $this->code = $parts[0]; + + if (isset($parts[1])) { + if (!preg_match('/^[A-Z]+$/', $parts[1])) { + $this->variant = $parts[1]; + $parts[1] = $parts[2] ?? null; + } + if ($parts[1]) { + $this->region = $parts[1]; + } + } + } + + /** + * Get the list of the known languages. + * + * @return array + */ + public static function all() + { + if (!static::$languagesNames) { + static::$languagesNames = require __DIR__.'/List/languages.php'; + } + + return static::$languagesNames; + } + + /** + * Get the list of the known regions. + * + * @return array + */ + public static function regions() + { + if (!static::$regionsNames) { + static::$regionsNames = require __DIR__.'/List/regions.php'; + } + + return static::$regionsNames; + } + + /** + * Get both isoName and nativeName as an array. + * + * @return array + */ + public function getNames(): array + { + if (!$this->names) { + $this->names = static::all()[$this->code] ?? [ + 'isoName' => $this->code, + 'nativeName' => $this->code, + ]; + } + + return $this->names; + } + + /** + * Returns the original locale ID. + * + * @return string + */ + public function getId(): string + { + return $this->id; + } + + /** + * Returns the code of the locale "en"/"fr". + * + * @return string + */ + public function getCode(): string + { + return $this->code; + } + + /** + * Returns the variant code such as cyrl/latn. + * + * @return string|null + */ + public function getVariant(): ?string + { + return $this->variant; + } + + /** + * Returns the variant such as Cyrillic/Latin. + * + * @return string|null + */ + public function getVariantName(): ?string + { + if ($this->variant === 'Latn') { + return 'Latin'; + } + + if ($this->variant === 'Cyrl') { + return 'Cyrillic'; + } + + return $this->variant; + } + + /** + * Returns the region part of the locale. + * + * @return string|null + */ + public function getRegion(): ?string + { + return $this->region; + } + + /** + * Returns the region name for the current language. + * + * @return string|null + */ + public function getRegionName(): ?string + { + return $this->region ? (static::regions()[$this->region] ?? $this->region) : null; + } + + /** + * Returns the long ISO language name. + * + * @return string + */ + public function getFullIsoName(): string + { + if (!$this->isoName) { + $this->isoName = $this->getNames()['isoName']; + } + + return $this->isoName; + } + + /** + * Set the ISO language name. + * + * @param string $isoName + */ + public function setIsoName(string $isoName): self + { + $this->isoName = $isoName; + + return $this; + } + + /** + * Return the full name of the language in this language. + * + * @return string + */ + public function getFullNativeName(): string + { + if (!$this->nativeName) { + $this->nativeName = $this->getNames()['nativeName']; + } + + return $this->nativeName; + } + + /** + * Set the name of the language in this language. + * + * @param string $nativeName + */ + public function setNativeName(string $nativeName): self + { + $this->nativeName = $nativeName; + + return $this; + } + + /** + * Returns the short ISO language name. + * + * @return string + */ + public function getIsoName(): string + { + $name = $this->getFullIsoName(); + + return trim(strstr($name, ',', true) ?: $name); + } + + /** + * Get the short name of the language in this language. + * + * @return string + */ + public function getNativeName(): string + { + $name = $this->getFullNativeName(); + + return trim(strstr($name, ',', true) ?: $name); + } + + /** + * Get a string with short ISO name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getIsoDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getIsoName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Get a string with short native name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getNativeDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getNativeName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Get a string with long ISO name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getFullIsoDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getFullIsoName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Get a string with long native name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getFullNativeDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getFullNativeName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Returns the original locale ID. + * + * @return string + */ + public function __toString() + { + return $this->getId(); + } + + /** + * Get a string with short ISO name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + #[ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->getIsoDescription(); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php new file mode 100644 index 0000000000..84e241e3ef --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Laravel; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterval; +use Carbon\CarbonPeriod; +use Illuminate\Contracts\Events\Dispatcher as DispatcherContract; +use Illuminate\Events\Dispatcher; +use Illuminate\Events\EventDispatcher; +use Illuminate\Support\Carbon as IlluminateCarbon; +use Illuminate\Support\Facades\Date; +use Throwable; + +class ServiceProvider extends \Illuminate\Support\ServiceProvider +{ + /** @var callable|null */ + protected $appGetter = null; + + /** @var callable|null */ + protected $localeGetter = null; + + public function setAppGetter(?callable $appGetter): void + { + $this->appGetter = $appGetter; + } + + public function setLocaleGetter(?callable $localeGetter): void + { + $this->localeGetter = $localeGetter; + } + + public function boot() + { + $this->updateLocale(); + + if (!$this->app->bound('events')) { + return; + } + + $service = $this; + $events = $this->app['events']; + + if ($this->isEventDispatcher($events)) { + $events->listen(class_exists('Illuminate\Foundation\Events\LocaleUpdated') ? 'Illuminate\Foundation\Events\LocaleUpdated' : 'locale.changed', function () use ($service) { + $service->updateLocale(); + }); + } + } + + public function updateLocale() + { + $locale = $this->getLocale(); + + if ($locale === null) { + return; + } + + Carbon::setLocale($locale); + CarbonImmutable::setLocale($locale); + CarbonPeriod::setLocale($locale); + CarbonInterval::setLocale($locale); + + if (class_exists(IlluminateCarbon::class)) { + IlluminateCarbon::setLocale($locale); + } + + if (class_exists(Date::class)) { + try { + $root = Date::getFacadeRoot(); + $root->setLocale($locale); + } catch (Throwable $e) { + // Non Carbon class in use in Date facade + } + } + } + + public function register() + { + // Needed for Laravel < 5.3 compatibility + } + + protected function getLocale() + { + if ($this->localeGetter) { + return ($this->localeGetter)(); + } + + $app = $this->getApp(); + $app = $app && method_exists($app, 'getLocale') + ? $app + : $this->getGlobalApp('translator'); + + return $app ? $app->getLocale() : null; + } + + protected function getApp() + { + if ($this->appGetter) { + return ($this->appGetter)(); + } + + return $this->app ?? $this->getGlobalApp(); + } + + protected function getGlobalApp(...$args) + { + return \function_exists('app') ? \app(...$args) : null; + } + + protected function isEventDispatcher($instance) + { + return $instance instanceof EventDispatcher + || $instance instanceof Dispatcher + || $instance instanceof DispatcherContract; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/List/languages.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/List/languages.php new file mode 100644 index 0000000000..5b5d9a1e7b --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/List/languages.php @@ -0,0 +1,1239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + /* + * ISO 639-2 + */ + 'ab' => [ + 'isoName' => 'Abkhazian', + 'nativeName' => 'ะฐางััƒะฐ ะฑั‹ะทัˆำ™ะฐ, ะฐางััˆำ™ะฐ', + ], + 'aa' => [ + 'isoName' => 'Afar', + 'nativeName' => 'Afaraf', + ], + 'af' => [ + 'isoName' => 'Afrikaans', + 'nativeName' => 'Afrikaans', + ], + 'ak' => [ + 'isoName' => 'Akan', + 'nativeName' => 'Akan', + ], + 'sq' => [ + 'isoName' => 'Albanian', + 'nativeName' => 'Shqip', + ], + 'am' => [ + 'isoName' => 'Amharic', + 'nativeName' => 'แŠ แˆ›แˆญแŠ›', + ], + 'ar' => [ + 'isoName' => 'Arabic', + 'nativeName' => 'ุงู„ุนุฑุจูŠุฉ', + ], + 'an' => [ + 'isoName' => 'Aragonese', + 'nativeName' => 'aragonรฉs', + ], + 'hy' => [ + 'isoName' => 'Armenian', + 'nativeName' => 'ี€ีกีตีฅึ€ีฅีถ', + ], + 'as' => [ + 'isoName' => 'Assamese', + 'nativeName' => 'เฆ…เฆธเฆฎเง€เฆฏเฆผเฆพ', + ], + 'av' => [ + 'isoName' => 'Avaric', + 'nativeName' => 'ะฐะฒะฐั€ ะผะฐั†ำ€, ะผะฐะณำ€ะฐั€ัƒะป ะผะฐั†ำ€', + ], + 'ae' => [ + 'isoName' => 'Avestan', + 'nativeName' => 'avesta', + ], + 'ay' => [ + 'isoName' => 'Aymara', + 'nativeName' => 'aymar aru', + ], + 'az' => [ + 'isoName' => 'Azerbaijani', + 'nativeName' => 'azษ™rbaycan dili', + ], + 'bm' => [ + 'isoName' => 'Bambara', + 'nativeName' => 'bamanankan', + ], + 'ba' => [ + 'isoName' => 'Bashkir', + 'nativeName' => 'ะฑะฐัˆากะพั€ั‚ ั‚ะตะปะต', + ], + 'eu' => [ + 'isoName' => 'Basque', + 'nativeName' => 'euskara, euskera', + ], + 'be' => [ + 'isoName' => 'Belarusian', + 'nativeName' => 'ะฑะตะปะฐั€ัƒัะบะฐั ะผะพะฒะฐ', + ], + 'bn' => [ + 'isoName' => 'Bengali', + 'nativeName' => 'เฆฌเฆพเฆ‚เฆฒเฆพ', + ], + 'bh' => [ + 'isoName' => 'Bihari languages', + 'nativeName' => 'เคญเฅ‹เคœเคชเฅเคฐเฅ€', + ], + 'bi' => [ + 'isoName' => 'Bislama', + 'nativeName' => 'Bislama', + ], + 'bs' => [ + 'isoName' => 'Bosnian', + 'nativeName' => 'bosanski jezik', + ], + 'br' => [ + 'isoName' => 'Breton', + 'nativeName' => 'brezhoneg', + ], + 'bg' => [ + 'isoName' => 'Bulgarian', + 'nativeName' => 'ะฑัŠะปะณะฐั€ัะบะธ ะตะทะธะบ', + ], + 'my' => [ + 'isoName' => 'Burmese', + 'nativeName' => 'แ€—แ€™แ€ฌแ€…แ€ฌ', + ], + 'ca' => [ + 'isoName' => 'Catalan, Valencian', + 'nativeName' => 'catalร , valenciร ', + ], + 'ch' => [ + 'isoName' => 'Chamorro', + 'nativeName' => 'Chamoru', + ], + 'ce' => [ + 'isoName' => 'Chechen', + 'nativeName' => 'ะฝะพั…ั‡ะธะนะฝ ะผะพั‚ั‚', + ], + 'ny' => [ + 'isoName' => 'Chichewa, Chewa, Nyanja', + 'nativeName' => 'chiCheลตa, chinyanja', + ], + 'zh' => [ + 'isoName' => 'Chinese', + 'nativeName' => 'ไธญๆ–‡ย (Zhลngwรฉn),ย ๆฑ‰่ฏญ,ย ๆผข่ชž', + ], + 'cv' => [ + 'isoName' => 'Chuvash', + 'nativeName' => 'ั‡ำ‘ะฒะฐัˆ ั‡ำ—ะปั…ะธ', + ], + 'kw' => [ + 'isoName' => 'Cornish', + 'nativeName' => 'Kernewek', + ], + 'co' => [ + 'isoName' => 'Corsican', + 'nativeName' => 'corsu, lingua corsa', + ], + 'cr' => [ + 'isoName' => 'Cree', + 'nativeName' => 'แ“€แฆแƒแ”ญแแแฃ', + ], + 'hr' => [ + 'isoName' => 'Croatian', + 'nativeName' => 'hrvatski jezik', + ], + 'cs' => [ + 'isoName' => 'Czech', + 'nativeName' => 'ฤeลกtina, ฤeskรฝ jazyk', + ], + 'da' => [ + 'isoName' => 'Danish', + 'nativeName' => 'dansk', + ], + 'dv' => [ + 'isoName' => 'Divehi, Dhivehi, Maldivian', + 'nativeName' => '‹จˆฌ€จ', + ], + 'nl' => [ + 'isoName' => 'Dutch, Flemish', + 'nativeName' => 'Nederlands, Vlaams', + ], + 'dz' => [ + 'isoName' => 'Dzongkha', + 'nativeName' => 'เฝขเพซเฝผเฝ„เผ‹เฝ', + ], + 'en' => [ + 'isoName' => 'English', + 'nativeName' => 'English', + ], + 'eo' => [ + 'isoName' => 'Esperanto', + 'nativeName' => 'Esperanto', + ], + 'et' => [ + 'isoName' => 'Estonian', + 'nativeName' => 'eesti, eesti keel', + ], + 'ee' => [ + 'isoName' => 'Ewe', + 'nativeName' => 'Eส‹egbe', + ], + 'fo' => [ + 'isoName' => 'Faroese', + 'nativeName' => 'fรธroyskt', + ], + 'fj' => [ + 'isoName' => 'Fijian', + 'nativeName' => 'vosa Vakaviti', + ], + 'fi' => [ + 'isoName' => 'Finnish', + 'nativeName' => 'suomi, suomen kieli', + ], + 'fr' => [ + 'isoName' => 'French', + 'nativeName' => 'franรงais', + ], + 'ff' => [ + 'isoName' => 'Fulah', + 'nativeName' => 'Fulfulde, Pulaar, Pular', + ], + 'gl' => [ + 'isoName' => 'Galician', + 'nativeName' => 'Galego', + ], + 'ka' => [ + 'isoName' => 'Georgian', + 'nativeName' => 'แƒฅแƒแƒ แƒ—แƒฃแƒšแƒ˜', + ], + 'de' => [ + 'isoName' => 'German', + 'nativeName' => 'Deutsch', + ], + 'el' => [ + 'isoName' => 'Greekย (modern)', + 'nativeName' => 'ฮตฮปฮปฮทฮฝฮนฮบฮฌ', + ], + 'gn' => [ + 'isoName' => 'Guaranรญ', + 'nativeName' => 'Avaรฑe\'แบฝ', + ], + 'gu' => [ + 'isoName' => 'Gujarati', + 'nativeName' => 'เช—เซเชœเชฐเชพเชคเซ€', + ], + 'ht' => [ + 'isoName' => 'Haitian, Haitian Creole', + 'nativeName' => 'Kreyรฒl ayisyen', + ], + 'ha' => [ + 'isoName' => 'Hausa', + 'nativeName' => '(Hausa) ู‡ูŽูˆูุณูŽ', + ], + 'he' => [ + 'isoName' => 'Hebrewย (modern)', + 'nativeName' => 'ืขื‘ืจื™ืช', + ], + 'hz' => [ + 'isoName' => 'Herero', + 'nativeName' => 'Otjiherero', + ], + 'hi' => [ + 'isoName' => 'Hindi', + 'nativeName' => 'เคนเคฟเคจเฅเคฆเฅ€, เคนเคฟเค‚เคฆเฅ€', + ], + 'ho' => [ + 'isoName' => 'Hiri Motu', + 'nativeName' => 'Hiri Motu', + ], + 'hu' => [ + 'isoName' => 'Hungarian', + 'nativeName' => 'magyar', + ], + 'ia' => [ + 'isoName' => 'Interlingua', + 'nativeName' => 'Interlingua', + ], + 'id' => [ + 'isoName' => 'Indonesian', + 'nativeName' => 'Bahasa Indonesia', + ], + 'ie' => [ + 'isoName' => 'Interlingue', + 'nativeName' => 'Originally calledย Occidental; then Interlingue after WWII', + ], + 'ga' => [ + 'isoName' => 'Irish', + 'nativeName' => 'Gaeilge', + ], + 'ig' => [ + 'isoName' => 'Igbo', + 'nativeName' => 'Asแปฅsแปฅ Igbo', + ], + 'ik' => [ + 'isoName' => 'Inupiaq', + 'nativeName' => 'Iรฑupiaq, Iรฑupiatun', + ], + 'io' => [ + 'isoName' => 'Ido', + 'nativeName' => 'Ido', + ], + 'is' => [ + 'isoName' => 'Icelandic', + 'nativeName' => 'รslenska', + ], + 'it' => [ + 'isoName' => 'Italian', + 'nativeName' => 'Italiano', + ], + 'iu' => [ + 'isoName' => 'Inuktitut', + 'nativeName' => 'แƒแ“„แ’ƒแ‘Žแ‘แ‘ฆ', + ], + 'ja' => [ + 'isoName' => 'Japanese', + 'nativeName' => 'ๆ—ฅๆœฌ่ชžย (ใซใปใ‚“ใ”)', + ], + 'jv' => [ + 'isoName' => 'Javanese', + 'nativeName' => '๊ฆง๊ฆฑ๊ฆ—๊ฆฎ, Basa Jawa', + ], + 'kl' => [ + 'isoName' => 'Kalaallisut, Greenlandic', + 'nativeName' => 'kalaallisut, kalaallit oqaasii', + ], + 'kn' => [ + 'isoName' => 'Kannada', + 'nativeName' => 'เฒ•เฒจเณเฒจเฒก', + ], + 'kr' => [ + 'isoName' => 'Kanuri', + 'nativeName' => 'Kanuri', + ], + 'ks' => [ + 'isoName' => 'Kashmiri', + 'nativeName' => 'เค•เคถเฅเคฎเฅ€เคฐเฅ€,ย ูƒุดู…ูŠุฑูŠโ€Ž', + ], + 'kk' => [ + 'isoName' => 'Kazakh', + 'nativeName' => 'า›ะฐะทะฐา› ั‚ั–ะปั–', + ], + 'km' => [ + 'isoName' => 'Central Khmer', + 'nativeName' => 'แžแŸ’แž˜แŸ‚แžš, แžแŸแž˜แžšแž—แžถแžŸแžถ, แž—แžถแžŸแžถแžแŸ’แž˜แŸ‚แžš', + ], + 'ki' => [ + 'isoName' => 'Kikuyu, Gikuyu', + 'nativeName' => 'Gฤฉkลฉyลฉ', + ], + 'rw' => [ + 'isoName' => 'Kinyarwanda', + 'nativeName' => 'Ikinyarwanda', + ], + 'ky' => [ + 'isoName' => 'Kirghiz, Kyrgyz', + 'nativeName' => 'ะšั‹ั€ะณั‹ะทั‡ะฐ, ะšั‹ั€ะณั‹ะท ั‚ะธะปะธ', + ], + 'kv' => [ + 'isoName' => 'Komi', + 'nativeName' => 'ะบะพะผะธ ะบั‹ะฒ', + ], + 'kg' => [ + 'isoName' => 'Kongo', + 'nativeName' => 'Kikongo', + ], + 'ko' => [ + 'isoName' => 'Korean', + 'nativeName' => 'ํ•œ๊ตญ์–ด', + ], + 'ku' => [ + 'isoName' => 'Kurdish', + 'nativeName' => 'Kurdรฎ,ย ฺฉูˆุฑุฏŒโ€Ž', + ], + 'kj' => [ + 'isoName' => 'Kuanyama, Kwanyama', + 'nativeName' => 'Kuanyama', + ], + 'la' => [ + 'isoName' => 'Latin', + 'nativeName' => 'latine, lingua latina', + ], + 'lb' => [ + 'isoName' => 'Luxembourgish, Letzeburgesch', + 'nativeName' => 'Lรซtzebuergesch', + ], + 'lg' => [ + 'isoName' => 'Ganda', + 'nativeName' => 'Luganda', + ], + 'li' => [ + 'isoName' => 'Limburgan, Limburger, Limburgish', + 'nativeName' => 'Limburgs', + ], + 'ln' => [ + 'isoName' => 'Lingala', + 'nativeName' => 'Lingรกla', + ], + 'lo' => [ + 'isoName' => 'Lao', + 'nativeName' => 'เบžเบฒเบชเบฒเบฅเบฒเบง', + ], + 'lt' => [ + 'isoName' => 'Lithuanian', + 'nativeName' => 'lietuviลณ kalba', + ], + 'lu' => [ + 'isoName' => 'Luba-Katanga', + 'nativeName' => 'Kiluba', + ], + 'lv' => [ + 'isoName' => 'Latvian', + 'nativeName' => 'latvieลกu valoda', + ], + 'gv' => [ + 'isoName' => 'Manx', + 'nativeName' => 'Gaelg, Gailck', + ], + 'mk' => [ + 'isoName' => 'Macedonian', + 'nativeName' => 'ะผะฐะบะตะดะพะฝัะบะธ ั˜ะฐะทะธะบ', + ], + 'mg' => [ + 'isoName' => 'Malagasy', + 'nativeName' => 'fiteny malagasy', + ], + 'ms' => [ + 'isoName' => 'Malay', + 'nativeName' => 'Bahasa Melayu,ย ุจู‡ุงุณ ู…ู„ุงูŠูˆโ€Ž', + ], + 'ml' => [ + 'isoName' => 'Malayalam', + 'nativeName' => 'เดฎเดฒเดฏเดพเดณเด‚', + ], + 'mt' => [ + 'isoName' => 'Maltese', + 'nativeName' => 'Malti', + ], + 'mi' => [ + 'isoName' => 'Maori', + 'nativeName' => 'te reo Mฤori', + ], + 'mr' => [ + 'isoName' => 'Marathi', + 'nativeName' => 'เคฎเคฐเคพเค เฅ€', + ], + 'mh' => [ + 'isoName' => 'Marshallese', + 'nativeName' => 'Kajin Mฬงajeฤผ', + ], + 'mn' => [ + 'isoName' => 'Mongolian', + 'nativeName' => 'ะœะพะฝะณะพะป ั…ัะป', + ], + 'na' => [ + 'isoName' => 'Nauru', + 'nativeName' => 'Dorerin Naoero', + ], + 'nv' => [ + 'isoName' => 'Navajo, Navaho', + 'nativeName' => 'Dinรฉ bizaad', + ], + 'nd' => [ + 'isoName' => 'North Ndebele', + 'nativeName' => 'isiNdebele', + ], + 'ne' => [ + 'isoName' => 'Nepali', + 'nativeName' => 'เคจเฅ‡เคชเคพเคฒเฅ€', + ], + 'ng' => [ + 'isoName' => 'Ndonga', + 'nativeName' => 'Owambo', + ], + 'nb' => [ + 'isoName' => 'Norwegian Bokmรฅl', + 'nativeName' => 'Norsk Bokmรฅl', + ], + 'nn' => [ + 'isoName' => 'Norwegian Nynorsk', + 'nativeName' => 'Norsk Nynorsk', + ], + 'no' => [ + 'isoName' => 'Norwegian', + 'nativeName' => 'Norsk', + ], + 'ii' => [ + 'isoName' => 'Sichuan Yi, Nuosu', + 'nativeName' => '๊†ˆ๊Œ ๊’ฟ Nuosuhxop', + ], + 'nr' => [ + 'isoName' => 'South Ndebele', + 'nativeName' => 'isiNdebele', + ], + 'oc' => [ + 'isoName' => 'Occitan', + 'nativeName' => 'occitan, lenga d\'รฒc', + ], + 'oj' => [ + 'isoName' => 'Ojibwa', + 'nativeName' => 'แŠแ“‚แ”‘แ“ˆแฏแ’งแŽแ“', + ], + 'cu' => [ + 'isoName' => 'Churchย Slavic, Churchย Slavonic,ย Oldย Churchย Slavonic, Oldย Slavonic,ย Oldย Bulgarian', + 'nativeName' => 'ัฉะทั‹ะบัŠ ัะปะพะฒัฃะฝัŒัะบัŠ', + ], + 'om' => [ + 'isoName' => 'Oromo', + 'nativeName' => 'Afaan Oromoo', + ], + 'or' => [ + 'isoName' => 'Oriya', + 'nativeName' => 'เฌ“เฌกเฌผเฌฟเฌ†', + ], + 'os' => [ + 'isoName' => 'Ossetian, Ossetic', + 'nativeName' => 'ะธั€ะพะฝ รฆะฒะทะฐะณ', + ], + 'pa' => [ + 'isoName' => 'Panjabi, Punjabi', + 'nativeName' => 'เจชเฉฐเจœเจพเจฌเฉ€', + ], + 'pi' => [ + 'isoName' => 'Pali', + 'nativeName' => 'เคชเคพเคดเคฟ', + ], + 'fa' => [ + 'isoName' => 'Persian', + 'nativeName' => 'ูุงุฑุณŒ', + ], + 'pl' => [ + 'isoName' => 'Polish', + 'nativeName' => 'jฤ™zyk polski, polszczyzna', + ], + 'ps' => [ + 'isoName' => 'Pashto, Pushto', + 'nativeName' => 'ูพฺšุชูˆ', + ], + 'pt' => [ + 'isoName' => 'Portuguese', + 'nativeName' => 'Portuguรชs', + ], + 'qu' => [ + 'isoName' => 'Quechua', + 'nativeName' => 'Runa Simi, Kichwa', + ], + 'rm' => [ + 'isoName' => 'Romansh', + 'nativeName' => 'Rumantsch Grischun', + ], + 'rn' => [ + 'isoName' => 'Rundi', + 'nativeName' => 'Ikirundi', + ], + 'ro' => [ + 'isoName' => 'Romanian, Moldavian, Moldovan', + 'nativeName' => 'Romรขnฤƒ', + ], + 'ru' => [ + 'isoName' => 'Russian', + 'nativeName' => 'ั€ัƒััะบะธะน', + ], + 'sa' => [ + 'isoName' => 'Sanskrit', + 'nativeName' => 'เคธเค‚เคธเฅเค•เฅƒเคคเคฎเฅ', + ], + 'sc' => [ + 'isoName' => 'Sardinian', + 'nativeName' => 'sardu', + ], + 'sd' => [ + 'isoName' => 'Sindhi', + 'nativeName' => 'เคธเคฟเคจเฅเคงเฅ€,ย ุณู†ฺŒูŠุŒ ุณู†ุฏฺพŒโ€Ž', + ], + 'se' => [ + 'isoName' => 'Northern Sami', + 'nativeName' => 'Davvisรกmegiella', + ], + 'sm' => [ + 'isoName' => 'Samoan', + 'nativeName' => 'gagana fa\'a Samoa', + ], + 'sg' => [ + 'isoName' => 'Sango', + 'nativeName' => 'yรขngรข tรฎ sรคngรถ', + ], + 'sr' => [ + 'isoName' => 'Serbian', + 'nativeName' => 'ัั€ะฟัะบะธ ั˜ะตะทะธะบ', + ], + 'gd' => [ + 'isoName' => 'Gaelic, Scottish Gaelic', + 'nativeName' => 'Gร idhlig', + ], + 'sn' => [ + 'isoName' => 'Shona', + 'nativeName' => 'chiShona', + ], + 'si' => [ + 'isoName' => 'Sinhala, Sinhalese', + 'nativeName' => 'เทƒเท’เถ‚เท„เถฝ', + ], + 'sk' => [ + 'isoName' => 'Slovak', + 'nativeName' => 'Slovenฤina, Slovenskรฝ Jazyk', + ], + 'sl' => [ + 'isoName' => 'Slovene', + 'nativeName' => 'Slovenski Jezik, Slovenลกฤina', + ], + 'so' => [ + 'isoName' => 'Somali', + 'nativeName' => 'Soomaaliga, af Soomaali', + ], + 'st' => [ + 'isoName' => 'Southern Sotho', + 'nativeName' => 'Sesotho', + ], + 'es' => [ + 'isoName' => 'Spanish, Castilian', + 'nativeName' => 'Espaรฑol', + ], + 'su' => [ + 'isoName' => 'Sundanese', + 'nativeName' => 'Basa Sunda', + ], + 'sw' => [ + 'isoName' => 'Swahili', + 'nativeName' => 'Kiswahili', + ], + 'ss' => [ + 'isoName' => 'Swati', + 'nativeName' => 'SiSwati', + ], + 'sv' => [ + 'isoName' => 'Swedish', + 'nativeName' => 'Svenska', + ], + 'ta' => [ + 'isoName' => 'Tamil', + 'nativeName' => 'เฎคเฎฎเฎฟเฎดเฏ', + ], + 'te' => [ + 'isoName' => 'Telugu', + 'nativeName' => 'เฐคเฑ†เฐฒเฑเฐ—เฑ', + ], + 'tg' => [ + 'isoName' => 'Tajik', + 'nativeName' => 'ั‚ะพาทะธะบำฃ,ย toรงikฤซ,ย ุชุงุฌŒฺฉŒโ€Ž', + ], + 'th' => [ + 'isoName' => 'Thai', + 'nativeName' => 'เน„เธ—เธข', + ], + 'ti' => [ + 'isoName' => 'Tigrinya', + 'nativeName' => 'แ‰ตแŒแˆญแŠ›', + ], + 'bo' => [ + 'isoName' => 'Tibetan', + 'nativeName' => 'เฝ–เฝผเฝ‘เผ‹เฝกเฝฒเฝ‚', + ], + 'tk' => [ + 'isoName' => 'Turkmen', + 'nativeName' => 'Tรผrkmen, ะขาฏั€ะบะผะตะฝ', + ], + 'tl' => [ + 'isoName' => 'Tagalog', + 'nativeName' => 'Wikang Tagalog', + ], + 'tn' => [ + 'isoName' => 'Tswana', + 'nativeName' => 'Setswana', + ], + 'to' => [ + 'isoName' => 'Tonganย (Tonga Islands)', + 'nativeName' => 'Faka Tonga', + ], + 'tr' => [ + 'isoName' => 'Turkish', + 'nativeName' => 'Tรผrkรงe', + ], + 'ts' => [ + 'isoName' => 'Tsonga', + 'nativeName' => 'Xitsonga', + ], + 'tt' => [ + 'isoName' => 'Tatar', + 'nativeName' => 'ั‚ะฐั‚ะฐั€ ั‚ะตะปะต,ย tatar tele', + ], + 'tw' => [ + 'isoName' => 'Twi', + 'nativeName' => 'Twi', + ], + 'ty' => [ + 'isoName' => 'Tahitian', + 'nativeName' => 'Reo Tahiti', + ], + 'ug' => [ + 'isoName' => 'Uighur, Uyghur', + 'nativeName' => 'Uyฦฃurqษ™, โ€ซุฆ‡ูŠุบ‡ุฑฺ†', + ], + 'uk' => [ + 'isoName' => 'Ukrainian', + 'nativeName' => 'ะฃะบั€ะฐั—ะฝััŒะบะฐ', + ], + 'ur' => [ + 'isoName' => 'Urdu', + 'nativeName' => 'ุงุฑุฏูˆ', + ], + 'uz' => [ + 'isoName' => 'Uzbek', + 'nativeName' => 'Oสปzbek,ย ะŽะทะฑะตะบ,ย ุฃ‡ุฒุจูƒโ€Ž', + ], + 've' => [ + 'isoName' => 'Venda', + 'nativeName' => 'Tshivenแธ“a', + ], + 'vi' => [ + 'isoName' => 'Vietnamese', + 'nativeName' => 'Tiแบฟng Viแป‡t', + ], + 'vo' => [ + 'isoName' => 'Volapรผk', + 'nativeName' => 'Volapรผk', + ], + 'wa' => [ + 'isoName' => 'Walloon', + 'nativeName' => 'Walon', + ], + 'cy' => [ + 'isoName' => 'Welsh', + 'nativeName' => 'Cymraeg', + ], + 'wo' => [ + 'isoName' => 'Wolof', + 'nativeName' => 'Wollof', + ], + 'fy' => [ + 'isoName' => 'Western Frisian', + 'nativeName' => 'Frysk', + ], + 'xh' => [ + 'isoName' => 'Xhosa', + 'nativeName' => 'isiXhosa', + ], + 'yi' => [ + 'isoName' => 'Yiddish', + 'nativeName' => 'ื™ื™ึดื“ื™ืฉ', + ], + 'yo' => [ + 'isoName' => 'Yoruba', + 'nativeName' => 'Yorรนbรก', + ], + 'za' => [ + 'isoName' => 'Zhuang, Chuang', + 'nativeName' => 'Saษฏ cueล‹ฦ…, Saw cuengh', + ], + 'zu' => [ + 'isoName' => 'Zulu', + 'nativeName' => 'isiZulu', + ], + /* + * Add ISO 639-3 languages available in Carbon + */ + 'agq' => [ + 'isoName' => 'Aghem', + 'nativeName' => 'Aghem', + ], + 'agr' => [ + 'isoName' => 'Aguaruna', + 'nativeName' => 'Aguaruna', + ], + 'anp' => [ + 'isoName' => 'Angika', + 'nativeName' => 'Angika', + ], + 'asa' => [ + 'isoName' => 'Asu', + 'nativeName' => 'Asu', + ], + 'ast' => [ + 'isoName' => 'Asturian', + 'nativeName' => 'Asturian', + ], + 'ayc' => [ + 'isoName' => 'Southern Aymara', + 'nativeName' => 'Southern Aymara', + ], + 'bas' => [ + 'isoName' => 'Basaa', + 'nativeName' => 'Basaa', + ], + 'bem' => [ + 'isoName' => 'Bemba', + 'nativeName' => 'Bemba', + ], + 'bez' => [ + 'isoName' => 'Bena', + 'nativeName' => 'Bena', + ], + 'bhb' => [ + 'isoName' => 'Bhili', + 'nativeName' => 'Bhili', + ], + 'bho' => [ + 'isoName' => 'Bhojpuri', + 'nativeName' => 'Bhojpuri', + ], + 'brx' => [ + 'isoName' => 'Bodo', + 'nativeName' => 'Bodo', + ], + 'byn' => [ + 'isoName' => 'Bilin', + 'nativeName' => 'Bilin', + ], + 'ccp' => [ + 'isoName' => 'Chakma', + 'nativeName' => 'Chakma', + ], + 'cgg' => [ + 'isoName' => 'Chiga', + 'nativeName' => 'Chiga', + ], + 'chr' => [ + 'isoName' => 'Cherokee', + 'nativeName' => 'Cherokee', + ], + 'cmn' => [ + 'isoName' => 'Chinese', + 'nativeName' => 'Chinese', + ], + 'crh' => [ + 'isoName' => 'Crimean Turkish', + 'nativeName' => 'Crimean Turkish', + ], + 'csb' => [ + 'isoName' => 'Kashubian', + 'nativeName' => 'Kashubian', + ], + 'dav' => [ + 'isoName' => 'Taita', + 'nativeName' => 'Taita', + ], + 'dje' => [ + 'isoName' => 'Zarma', + 'nativeName' => 'Zarma', + ], + 'doi' => [ + 'isoName' => 'Dogri (macrolanguage)', + 'nativeName' => 'Dogri (macrolanguage)', + ], + 'dsb' => [ + 'isoName' => 'Lower Sorbian', + 'nativeName' => 'Lower Sorbian', + ], + 'dua' => [ + 'isoName' => 'Duala', + 'nativeName' => 'Duala', + ], + 'dyo' => [ + 'isoName' => 'Jola-Fonyi', + 'nativeName' => 'Jola-Fonyi', + ], + 'ebu' => [ + 'isoName' => 'Embu', + 'nativeName' => 'Embu', + ], + 'ewo' => [ + 'isoName' => 'Ewondo', + 'nativeName' => 'Ewondo', + ], + 'fil' => [ + 'isoName' => 'Filipino', + 'nativeName' => 'Filipino', + ], + 'fur' => [ + 'isoName' => 'Friulian', + 'nativeName' => 'Friulian', + ], + 'gez' => [ + 'isoName' => 'Geez', + 'nativeName' => 'Geez', + ], + 'gom' => [ + 'isoName' => 'Konkani, Goan', + 'nativeName' => 'เฒ•เณŠเฒ‚เฒ•เฒฃเฒฟ', + ], + 'gsw' => [ + 'isoName' => 'Swiss German', + 'nativeName' => 'Swiss German', + ], + 'guz' => [ + 'isoName' => 'Gusii', + 'nativeName' => 'Gusii', + ], + 'hak' => [ + 'isoName' => 'Hakka Chinese', + 'nativeName' => 'Hakka Chinese', + ], + 'haw' => [ + 'isoName' => 'Hawaiian', + 'nativeName' => 'Hawaiian', + ], + 'hif' => [ + 'isoName' => 'Fiji Hindi', + 'nativeName' => 'Fiji Hindi', + ], + 'hne' => [ + 'isoName' => 'Chhattisgarhi', + 'nativeName' => 'Chhattisgarhi', + ], + 'hsb' => [ + 'isoName' => 'Upper Sorbian', + 'nativeName' => 'Upper Sorbian', + ], + 'jgo' => [ + 'isoName' => 'Ngomba', + 'nativeName' => 'Ngomba', + ], + 'jmc' => [ + 'isoName' => 'Machame', + 'nativeName' => 'Machame', + ], + 'kab' => [ + 'isoName' => 'Kabyle', + 'nativeName' => 'Kabyle', + ], + 'kam' => [ + 'isoName' => 'Kamba', + 'nativeName' => 'Kamba', + ], + 'kde' => [ + 'isoName' => 'Makonde', + 'nativeName' => 'Makonde', + ], + 'kea' => [ + 'isoName' => 'Kabuverdianu', + 'nativeName' => 'Kabuverdianu', + ], + 'khq' => [ + 'isoName' => 'Koyra Chiini', + 'nativeName' => 'Koyra Chiini', + ], + 'kkj' => [ + 'isoName' => 'Kako', + 'nativeName' => 'Kako', + ], + 'kln' => [ + 'isoName' => 'Kalenjin', + 'nativeName' => 'Kalenjin', + ], + 'kok' => [ + 'isoName' => 'Konkani', + 'nativeName' => 'Konkani', + ], + 'ksb' => [ + 'isoName' => 'Shambala', + 'nativeName' => 'Shambala', + ], + 'ksf' => [ + 'isoName' => 'Bafia', + 'nativeName' => 'Bafia', + ], + 'ksh' => [ + 'isoName' => 'Colognian', + 'nativeName' => 'Colognian', + ], + 'lag' => [ + 'isoName' => 'Langi', + 'nativeName' => 'Langi', + ], + 'lij' => [ + 'isoName' => 'Ligurian', + 'nativeName' => 'Ligurian', + ], + 'lkt' => [ + 'isoName' => 'Lakota', + 'nativeName' => 'Lakota', + ], + 'lrc' => [ + 'isoName' => 'Northern Luri', + 'nativeName' => 'Northern Luri', + ], + 'luo' => [ + 'isoName' => 'Luo', + 'nativeName' => 'Luo', + ], + 'luy' => [ + 'isoName' => 'Luyia', + 'nativeName' => 'Luyia', + ], + 'lzh' => [ + 'isoName' => 'Literary Chinese', + 'nativeName' => 'Literary Chinese', + ], + 'mag' => [ + 'isoName' => 'Magahi', + 'nativeName' => 'Magahi', + ], + 'mai' => [ + 'isoName' => 'Maithili', + 'nativeName' => 'Maithili', + ], + 'mas' => [ + 'isoName' => 'Masai', + 'nativeName' => 'Masai', + ], + 'mer' => [ + 'isoName' => 'Meru', + 'nativeName' => 'Meru', + ], + 'mfe' => [ + 'isoName' => 'Morisyen', + 'nativeName' => 'Morisyen', + ], + 'mgh' => [ + 'isoName' => 'Makhuwa-Meetto', + 'nativeName' => 'Makhuwa-Meetto', + ], + 'mgo' => [ + 'isoName' => 'Metaสผ', + 'nativeName' => 'Metaสผ', + ], + 'mhr' => [ + 'isoName' => 'Eastern Mari', + 'nativeName' => 'Eastern Mari', + ], + 'miq' => [ + 'isoName' => 'Mรญskito', + 'nativeName' => 'Mรญskito', + ], + 'mjw' => [ + 'isoName' => 'Karbi', + 'nativeName' => 'Karbi', + ], + 'mni' => [ + 'isoName' => 'Manipuri', + 'nativeName' => 'Manipuri', + ], + 'mua' => [ + 'isoName' => 'Mundang', + 'nativeName' => 'Mundang', + ], + 'mzn' => [ + 'isoName' => 'Mazanderani', + 'nativeName' => 'Mazanderani', + ], + 'nan' => [ + 'isoName' => 'Min Nan Chinese', + 'nativeName' => 'Min Nan Chinese', + ], + 'naq' => [ + 'isoName' => 'Nama', + 'nativeName' => 'Nama', + ], + 'nds' => [ + 'isoName' => 'Low German', + 'nativeName' => 'Low German', + ], + 'nhn' => [ + 'isoName' => 'Central Nahuatl', + 'nativeName' => 'Central Nahuatl', + ], + 'niu' => [ + 'isoName' => 'Niuean', + 'nativeName' => 'Niuean', + ], + 'nmg' => [ + 'isoName' => 'Kwasio', + 'nativeName' => 'Kwasio', + ], + 'nnh' => [ + 'isoName' => 'Ngiemboon', + 'nativeName' => 'Ngiemboon', + ], + 'nso' => [ + 'isoName' => 'Northern Sotho', + 'nativeName' => 'Northern Sotho', + ], + 'nus' => [ + 'isoName' => 'Nuer', + 'nativeName' => 'Nuer', + ], + 'nyn' => [ + 'isoName' => 'Nyankole', + 'nativeName' => 'Nyankole', + ], + 'pap' => [ + 'isoName' => 'Papiamento', + 'nativeName' => 'Papiamento', + ], + 'prg' => [ + 'isoName' => 'Prussian', + 'nativeName' => 'Prussian', + ], + 'quz' => [ + 'isoName' => 'Cusco Quechua', + 'nativeName' => 'Cusco Quechua', + ], + 'raj' => [ + 'isoName' => 'Rajasthani', + 'nativeName' => 'Rajasthani', + ], + 'rof' => [ + 'isoName' => 'Rombo', + 'nativeName' => 'Rombo', + ], + 'rwk' => [ + 'isoName' => 'Rwa', + 'nativeName' => 'Rwa', + ], + 'sah' => [ + 'isoName' => 'Sakha', + 'nativeName' => 'Sakha', + ], + 'saq' => [ + 'isoName' => 'Samburu', + 'nativeName' => 'Samburu', + ], + 'sat' => [ + 'isoName' => 'Santali', + 'nativeName' => 'Santali', + ], + 'sbp' => [ + 'isoName' => 'Sangu', + 'nativeName' => 'Sangu', + ], + 'scr' => [ + 'isoName' => 'Serbo Croatian', + 'nativeName' => 'Serbo Croatian', + ], + 'seh' => [ + 'isoName' => 'Sena', + 'nativeName' => 'Sena', + ], + 'ses' => [ + 'isoName' => 'Koyraboro Senni', + 'nativeName' => 'Koyraboro Senni', + ], + 'sgs' => [ + 'isoName' => 'Samogitian', + 'nativeName' => 'Samogitian', + ], + 'shi' => [ + 'isoName' => 'Tachelhit', + 'nativeName' => 'Tachelhit', + ], + 'shn' => [ + 'isoName' => 'Shan', + 'nativeName' => 'Shan', + ], + 'shs' => [ + 'isoName' => 'Shuswap', + 'nativeName' => 'Shuswap', + ], + 'sid' => [ + 'isoName' => 'Sidamo', + 'nativeName' => 'Sidamo', + ], + 'smn' => [ + 'isoName' => 'Inari Sami', + 'nativeName' => 'Inari Sami', + ], + 'szl' => [ + 'isoName' => 'Silesian', + 'nativeName' => 'Silesian', + ], + 'tcy' => [ + 'isoName' => 'Tulu', + 'nativeName' => 'Tulu', + ], + 'teo' => [ + 'isoName' => 'Teso', + 'nativeName' => 'Teso', + ], + 'tet' => [ + 'isoName' => 'Tetum', + 'nativeName' => 'Tetum', + ], + 'the' => [ + 'isoName' => 'Chitwania Tharu', + 'nativeName' => 'Chitwania Tharu', + ], + 'tig' => [ + 'isoName' => 'Tigre', + 'nativeName' => 'Tigre', + ], + 'tlh' => [ + 'isoName' => 'Klingon', + 'nativeName' => 'tlhIngan Hol', + ], + 'tpi' => [ + 'isoName' => 'Tok Pisin', + 'nativeName' => 'Tok Pisin', + ], + 'twq' => [ + 'isoName' => 'Tasawaq', + 'nativeName' => 'Tasawaq', + ], + 'tzl' => [ + 'isoName' => 'Talossan', + 'nativeName' => 'Talossan', + ], + 'tzm' => [ + 'isoName' => 'Tamazight, Central Atlas', + 'nativeName' => 'โตœโตŽโดฐโตฃโต‰โต–โตœ', + ], + 'unm' => [ + 'isoName' => 'Unami', + 'nativeName' => 'Unami', + ], + 'vai' => [ + 'isoName' => 'Vai', + 'nativeName' => 'Vai', + ], + 'vun' => [ + 'isoName' => 'Vunjo', + 'nativeName' => 'Vunjo', + ], + 'wae' => [ + 'isoName' => 'Walser', + 'nativeName' => 'Walser', + ], + 'wal' => [ + 'isoName' => 'Wolaytta', + 'nativeName' => 'Wolaytta', + ], + 'xog' => [ + 'isoName' => 'Soga', + 'nativeName' => 'Soga', + ], + 'yav' => [ + 'isoName' => 'Yangben', + 'nativeName' => 'Yangben', + ], + 'yue' => [ + 'isoName' => 'Cantonese', + 'nativeName' => 'Cantonese', + ], + 'yuw' => [ + 'isoName' => 'Yau (Morobe Province)', + 'nativeName' => 'Yau (Morobe Province)', + ], + 'zgh' => [ + 'isoName' => 'Standard Moroccan Tamazight', + 'nativeName' => 'Standard Moroccan Tamazight', + ], +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/List/regions.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/List/regions.php new file mode 100644 index 0000000000..8ab8a9e3ae --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/List/regions.php @@ -0,0 +1,265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * ISO 3166-2 + */ +return [ + 'AD' => 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'ร…land Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthรฉlemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia (Plurinational State of)', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => 'Cรดte d\'Ivoire', + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cabo Verde', + 'CW' => 'Curaรงao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czechia', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia (Federated States of)', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom of Great Britain and Northern Ireland', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran (Islamic Republic of)', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => 'Korea (Democratic People\'s Republic of)', + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => 'Lao People\'s Democratic Republic', + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestine, State of', + 'PT' => 'Portugal', + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Rรฉunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten (Dutch part)', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Eswatini', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States of America', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela (Bolivarian Republic of)', + 'VG' => 'Virgin Islands (British)', + 'VI' => 'Virgin Islands (U.S.)', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', +]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/MessageFormatter/MessageFormatterMapper.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/MessageFormatter/MessageFormatterMapper.php new file mode 100644 index 0000000000..c054808764 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/MessageFormatter/MessageFormatterMapper.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\MessageFormatter; + +use ReflectionMethod; +use Symfony\Component\Translation\Formatter\MessageFormatter; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; + +// @codeCoverageIgnoreStart +$transMethod = new ReflectionMethod(MessageFormatterInterface::class, 'format'); + +require $transMethod->getParameters()[0]->hasType() + ? __DIR__.'/../../../lazy/Carbon/MessageFormatter/MessageFormatterMapperStrongType.php' + : __DIR__.'/../../../lazy/Carbon/MessageFormatter/MessageFormatterMapperWeakType.php'; +// @codeCoverageIgnoreEnd + +final class MessageFormatterMapper extends LazyMessageFormatter +{ + /** + * Wrapped formatter. + * + * @var MessageFormatterInterface + */ + protected $formatter; + + public function __construct(?MessageFormatterInterface $formatter = null) + { + $this->formatter = $formatter ?? new MessageFormatter(); + } + + protected function transformLocale(?string $locale): ?string + { + return $locale ? preg_replace('/[_@][A-Za-z][a-z]{2,}/', '', $locale) : $locale; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/AbstractMacro.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/AbstractMacro.php new file mode 100644 index 0000000000..fde67b36aa --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/AbstractMacro.php @@ -0,0 +1,286 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use Closure; +use InvalidArgumentException; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter as AdapterReflectionParameter; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionType as AdapterReflectionType; +use PHPStan\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass; +use PHPStan\BetterReflection\Reflection\ReflectionFunction as BetterReflectionFunction; +use PHPStan\BetterReflection\Reflection\ReflectionParameter as BetterReflectionParameter; +use PHPStan\Reflection\Php\BuiltinMethodReflection; +use PHPStan\TrinaryLogic; +use ReflectionClass; +use ReflectionFunction; +use ReflectionMethod; +use ReflectionParameter; +use ReflectionType; +use stdClass; +use Throwable; + +abstract class AbstractMacro implements BuiltinMethodReflection +{ + /** + * The reflection function/method. + * + * @var ReflectionFunction|ReflectionMethod + */ + protected $reflectionFunction; + + /** + * The class name. + * + * @var class-string + */ + private $className; + + /** + * The method name. + * + * @var string + */ + private $methodName; + + /** + * The parameters. + * + * @var ReflectionParameter[] + */ + private $parameters; + + /** + * The is static. + * + * @var bool + */ + private $static = false; + + /** + * Macro constructor. + * + * @param class-string $className + * @param string $methodName + * @param callable $macro + */ + public function __construct(string $className, string $methodName, $macro) + { + $this->className = $className; + $this->methodName = $methodName; + $rawReflectionFunction = \is_array($macro) + ? new ReflectionMethod($macro[0], $macro[1]) + : new ReflectionFunction($macro); + $this->reflectionFunction = self::hasModernParser() + ? $this->getReflectionFunction($macro) + : $rawReflectionFunction; // @codeCoverageIgnore + $this->parameters = array_map( + function ($parameter) { + if ($parameter instanceof BetterReflectionParameter) { + return new AdapterReflectionParameter($parameter); + } + + return $parameter; // @codeCoverageIgnore + }, + $this->reflectionFunction->getParameters() + ); + + if ($rawReflectionFunction->isClosure()) { + try { + $closure = $rawReflectionFunction->getClosure(); + $boundClosure = Closure::bind($closure, new stdClass()); + $this->static = (!$boundClosure || (new ReflectionFunction($boundClosure))->getClosureThis() === null); + } catch (Throwable $e) { + $this->static = true; + } + } + } + + private function getReflectionFunction($spec) + { + if (\is_array($spec) && \count($spec) === 2 && \is_string($spec[1])) { + \assert($spec[1] !== ''); + + if (\is_object($spec[0])) { + return BetterReflectionClass::createFromInstance($spec[0]) + ->getMethod($spec[1]); + } + + return BetterReflectionClass::createFromName($spec[0]) + ->getMethod($spec[1]); + } + + if (\is_string($spec)) { + return BetterReflectionFunction::createFromName($spec); + } + + if ($spec instanceof Closure) { + return BetterReflectionFunction::createFromClosure($spec); + } + + throw new InvalidArgumentException('Could not create reflection from the spec given'); // @codeCoverageIgnore + } + + /** + * {@inheritdoc} + */ + public function getDeclaringClass(): ReflectionClass + { + return new ReflectionClass($this->className); + } + + /** + * {@inheritdoc} + */ + public function isPrivate(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isPublic(): bool + { + return true; + } + + /** + * {@inheritdoc} + */ + public function isFinal(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isInternal(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isAbstract(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isStatic(): bool + { + return $this->static; + } + + /** + * {@inheritdoc} + */ + public function getDocComment(): ?string + { + return $this->reflectionFunction->getDocComment() ?: null; + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return $this->methodName; + } + + /** + * {@inheritdoc} + */ + public function getParameters(): array + { + return $this->parameters; + } + + /** + * {@inheritdoc} + */ + public function getReturnType(): ?ReflectionType + { + $type = $this->reflectionFunction->getReturnType(); + + if ($type instanceof ReflectionType) { + return $type; // @codeCoverageIgnore + } + + return self::adaptType($type); + } + + /** + * {@inheritdoc} + */ + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean( + $this->reflectionFunction->isDeprecated() || + preg_match('/@deprecated/i', $this->getDocComment() ?: '') + ); + } + + /** + * {@inheritdoc} + */ + public function isVariadic(): bool + { + return $this->reflectionFunction->isVariadic(); + } + + /** + * {@inheritdoc} + */ + public function getPrototype(): BuiltinMethodReflection + { + return $this; + } + + public function getTentativeReturnType(): ?ReflectionType + { + return null; + } + + public function returnsByReference(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + private static function adaptType($type) + { + $method = method_exists(AdapterReflectionType::class, 'fromTypeOrNull') + ? 'fromTypeOrNull' + : 'fromReturnTypeOrNull'; // @codeCoverageIgnore + + return AdapterReflectionType::$method($type); + } + + private static function hasModernParser(): bool + { + static $modernParser = null; + + if ($modernParser !== null) { + return $modernParser; + } + + $modernParser = method_exists(AdapterReflectionType::class, 'fromTypeOrNull'); + + return $modernParser; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/Macro.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/Macro.php new file mode 100644 index 0000000000..de3e51f6a0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/Macro.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use PHPStan\BetterReflection\Reflection\Adapter; +use PHPStan\Reflection\Php\BuiltinMethodReflection; +use ReflectionMethod; + +$method = new ReflectionMethod(BuiltinMethodReflection::class, 'getReflection'); + +require $method->hasReturnType() && $method->getReturnType()->getName() === Adapter\ReflectionMethod::class + ? __DIR__.'/../../../lazy/Carbon/PHPStan/AbstractMacroStatic.php' + : __DIR__.'/../../../lazy/Carbon/PHPStan/AbstractMacroBuiltin.php'; + +$method = new ReflectionMethod(BuiltinMethodReflection::class, 'getFileName'); + +require $method->hasReturnType() + ? __DIR__.'/../../../lazy/Carbon/PHPStan/MacroStrongType.php' + : __DIR__.'/../../../lazy/Carbon/PHPStan/MacroWeakType.php'; + +final class Macro extends LazyMacro +{ +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroExtension.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroExtension.php new file mode 100644 index 0000000000..2cd6fce541 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroExtension.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\MethodsClassReflectionExtension; +use PHPStan\Reflection\Php\PhpMethodReflectionFactory; +use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Type\TypehintHelper; + +/** + * Class MacroExtension. + * + * @codeCoverageIgnore Pure PHPStan wrapper. + */ +final class MacroExtension implements MethodsClassReflectionExtension +{ + /** + * @var PhpMethodReflectionFactory + */ + protected $methodReflectionFactory; + + /** + * @var MacroScanner + */ + protected $scanner; + + /** + * Extension constructor. + * + * @param PhpMethodReflectionFactory $methodReflectionFactory + * @param ReflectionProvider $reflectionProvider + */ + public function __construct( + PhpMethodReflectionFactory $methodReflectionFactory, + ReflectionProvider $reflectionProvider + ) { + $this->scanner = new MacroScanner($reflectionProvider); + $this->methodReflectionFactory = $methodReflectionFactory; + } + + /** + * {@inheritdoc} + */ + public function hasMethod(ClassReflection $classReflection, string $methodName): bool + { + return $this->scanner->hasMethod($classReflection->getName(), $methodName); + } + + /** + * {@inheritdoc} + */ + public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection + { + $builtinMacro = $this->scanner->getMethod($classReflection->getName(), $methodName); + $supportAssertions = class_exists(Assertions::class); + + return $this->methodReflectionFactory->create( + $classReflection, + null, + $builtinMacro, + $classReflection->getActiveTemplateTypeMap(), + [], + TypehintHelper::decideTypeFromReflection($builtinMacro->getReturnType()), + null, + null, + $builtinMacro->isDeprecated()->yes(), + $builtinMacro->isInternal(), + $builtinMacro->isFinal(), + $supportAssertions ? null : $builtinMacro->getDocComment(), + $supportAssertions ? Assertions::createEmpty() : null, + null, + $builtinMacro->getDocComment(), + [] + ); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroScanner.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroScanner.php new file mode 100644 index 0000000000..eb8957d4d0 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroScanner.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use Carbon\CarbonInterface; +use PHPStan\Reflection\ReflectionProvider; +use ReflectionClass; +use ReflectionException; + +final class MacroScanner +{ + /** + * @var \PHPStan\Reflection\ReflectionProvider + */ + private $reflectionProvider; + + /** + * MacroScanner constructor. + * + * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider + */ + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + /** + * Return true if the given pair class-method is a Carbon macro. + * + * @param class-string $className + * @param string $methodName + * + * @return bool + */ + public function hasMethod(string $className, string $methodName): bool + { + $classReflection = $this->reflectionProvider->getClass($className); + + if ( + $classReflection->getName() !== CarbonInterface::class && + !$classReflection->isSubclassOf(CarbonInterface::class) + ) { + return false; + } + + return \is_callable([$className, 'hasMacro']) && + $className::hasMacro($methodName); + } + + /** + * Return the Macro for a given pair class-method. + * + * @param class-string $className + * @param string $methodName + * + * @throws ReflectionException + * + * @return Macro + */ + public function getMethod(string $className, string $methodName): Macro + { + $reflectionClass = new ReflectionClass($className); + $property = $reflectionClass->getProperty('globalMacros'); + + $property->setAccessible(true); + $macro = $property->getValue()[$methodName]; + + return new Macro( + $className, + $methodName, + $macro + ); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Boundaries.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Boundaries.php new file mode 100644 index 0000000000..71bbb72301 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Boundaries.php @@ -0,0 +1,443 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Exceptions\UnknownUnitException; + +/** + * Trait Boundaries. + * + * startOf, endOf and derived method for each unit. + * + * Depends on the following properties: + * + * @property int $year + * @property int $month + * @property int $daysInMonth + * @property int $quarter + * + * Depends on the following methods: + * + * @method $this setTime(int $hour, int $minute, int $second = 0, int $microseconds = 0) + * @method $this setDate(int $year, int $month, int $day) + * @method $this addMonths(int $value = 1) + */ +trait Boundaries +{ + /** + * Resets the time to 00:00:00 start of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDay(); + * ``` + * + * @return static + */ + public function startOfDay() + { + return $this->setTime(0, 0, 0, 0); + } + + /** + * Resets the time to 23:59:59.999999 end of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDay(); + * ``` + * + * @return static + */ + public function endOfDay() + { + return $this->setTime(static::HOURS_PER_DAY - 1, static::MINUTES_PER_HOUR - 1, static::SECONDS_PER_MINUTE - 1, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Resets the date to the first day of the month and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMonth(); + * ``` + * + * @return static + */ + public function startOfMonth() + { + return $this->setDate($this->year, $this->month, 1)->startOfDay(); + } + + /** + * Resets the date to end of the month and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMonth(); + * ``` + * + * @return static + */ + public function endOfMonth() + { + return $this->setDate($this->year, $this->month, $this->daysInMonth)->endOfDay(); + } + + /** + * Resets the date to the first day of the quarter and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfQuarter(); + * ``` + * + * @return static + */ + public function startOfQuarter() + { + $month = ($this->quarter - 1) * static::MONTHS_PER_QUARTER + 1; + + return $this->setDate($this->year, $month, 1)->startOfDay(); + } + + /** + * Resets the date to end of the quarter and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfQuarter(); + * ``` + * + * @return static + */ + public function endOfQuarter() + { + return $this->startOfQuarter()->addMonths(static::MONTHS_PER_QUARTER - 1)->endOfMonth(); + } + + /** + * Resets the date to the first day of the year and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfYear(); + * ``` + * + * @return static + */ + public function startOfYear() + { + return $this->setDate($this->year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the year and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfYear(); + * ``` + * + * @return static + */ + public function endOfYear() + { + return $this->setDate($this->year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of the decade and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDecade(); + * ``` + * + * @return static + */ + public function startOfDecade() + { + $year = $this->year - $this->year % static::YEARS_PER_DECADE; + + return $this->setDate($year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the decade and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDecade(); + * ``` + * + * @return static + */ + public function endOfDecade() + { + $year = $this->year - $this->year % static::YEARS_PER_DECADE + static::YEARS_PER_DECADE - 1; + + return $this->setDate($year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of the century and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfCentury(); + * ``` + * + * @return static + */ + public function startOfCentury() + { + $year = $this->year - ($this->year - 1) % static::YEARS_PER_CENTURY; + + return $this->setDate($year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the century and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfCentury(); + * ``` + * + * @return static + */ + public function endOfCentury() + { + $year = $this->year - 1 - ($this->year - 1) % static::YEARS_PER_CENTURY + static::YEARS_PER_CENTURY; + + return $this->setDate($year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of the millennium and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMillennium(); + * ``` + * + * @return static + */ + public function startOfMillennium() + { + $year = $this->year - ($this->year - 1) % static::YEARS_PER_MILLENNIUM; + + return $this->setDate($year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the millennium and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMillennium(); + * ``` + * + * @return static + */ + public function endOfMillennium() + { + $year = $this->year - 1 - ($this->year - 1) % static::YEARS_PER_MILLENNIUM + static::YEARS_PER_MILLENNIUM; + + return $this->setDate($year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of week (defined in $weekStartsAt) and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek(Carbon::SUNDAY) . "\n"; + * ``` + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return static + */ + public function startOfWeek($weekStartsAt = null) + { + return $this->subDays((7 + $this->dayOfWeek - ($weekStartsAt ?? $this->firstWeekDay)) % 7)->startOfDay(); + } + + /** + * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek(Carbon::SATURDAY) . "\n"; + * ``` + * + * @param int $weekEndsAt optional start allow you to specify the day of week to use to end the week + * + * @return static + */ + public function endOfWeek($weekEndsAt = null) + { + return $this->addDays((7 - $this->dayOfWeek + ($weekEndsAt ?? $this->lastWeekDay)) % 7)->endOfDay(); + } + + /** + * Modify to start of current hour, minutes and seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfHour(); + * ``` + * + * @return static + */ + public function startOfHour() + { + return $this->setTime($this->hour, 0, 0, 0); + } + + /** + * Modify to end of current hour, minutes and seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfHour(); + * ``` + * + * @return static + */ + public function endOfHour() + { + return $this->setTime($this->hour, static::MINUTES_PER_HOUR - 1, static::SECONDS_PER_MINUTE - 1, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Modify to start of current minute, seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMinute(); + * ``` + * + * @return static + */ + public function startOfMinute() + { + return $this->setTime($this->hour, $this->minute, 0, 0); + } + + /** + * Modify to end of current minute, seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMinute(); + * ``` + * + * @return static + */ + public function endOfMinute() + { + return $this->setTime($this->hour, $this->minute, static::SECONDS_PER_MINUTE - 1, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Modify to start of current second, microseconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function startOfSecond() + { + return $this->setTime($this->hour, $this->minute, $this->second, 0); + } + + /** + * Modify to end of current second, microseconds become 999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->endOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function endOfSecond() + { + return $this->setTime($this->hour, $this->minute, $this->second, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Modify to start of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function startOf($unit, ...$params) + { + $ucfUnit = ucfirst(static::singularUnit($unit)); + $method = "startOf$ucfUnit"; + if (!method_exists($this, $method)) { + throw new UnknownUnitException($unit); + } + + return $this->$method(...$params); + } + + /** + * Modify to end of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function endOf($unit, ...$params) + { + $ucfUnit = ucfirst(static::singularUnit($unit)); + $method = "endOf$ucfUnit"; + if (!method_exists($this, $method)) { + throw new UnknownUnitException($unit); + } + + return $this->$method(...$params); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Cast.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Cast.php new file mode 100644 index 0000000000..5f7c7c011a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Cast.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Exceptions\InvalidCastException; +use DateTimeInterface; + +/** + * Trait Cast. + * + * Utils to cast into an other class. + */ +trait Cast +{ + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateTimeInterface + */ + public function cast(string $className) + { + if (!method_exists($className, 'instance')) { + if (is_a($className, DateTimeInterface::class, true)) { + return new $className($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone()); + } + + throw new InvalidCastException("$className has not the instance() method needed to cast the date."); + } + + return $className::instance($this); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Comparison.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Comparison.php new file mode 100644 index 0000000000..f6261d882d --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Comparison.php @@ -0,0 +1,1125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use BadMethodCallException; +use Carbon\CarbonInterface; +use Carbon\Exceptions\BadComparisonUnitException; +use InvalidArgumentException; + +/** + * Trait Comparison. + * + * Comparison utils and testers. All the following methods return booleans. + * nowWithSameTz + * + * Depends on the following methods: + * + * @method static resolveCarbon($date) + * @method static copy() + * @method static nowWithSameTz() + * @method static static yesterday($timezone = null) + * @method static static tomorrow($timezone = null) + */ +trait Comparison +{ + /** @var bool */ + protected $endOfTime = false; + + /** @var bool */ + protected $startOfTime = false; + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->eq(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see equalTo() + * + * @return bool + */ + public function eq($date): bool + { + return $this->equalTo($date); + } + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function equalTo($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this == $this->resolveCarbon($date); + } + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->ne(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($date): bool + { + return $this->notEqualTo($date); + } + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function notEqualTo($date): bool + { + return !$this->equalTo($date); + } + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function gt($date): bool + { + return $this->greaterThan($date); + } + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThan($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this > $this->resolveCarbon($date); + } + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function isAfter($date): bool + { + return $this->greaterThan($date); + } + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThanOrEqualTo() + * + * @return bool + */ + public function gte($date): bool + { + return $this->greaterThanOrEqualTo($date); + } + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThanOrEqualTo($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this >= $this->resolveCarbon($date); + } + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function lt($date): bool + { + return $this->lessThan($date); + } + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThan($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this < $this->resolveCarbon($date); + } + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function isBefore($date): bool + { + return $this->lessThan($date); + } + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThanOrEqualTo() + * + * @return bool + */ + public function lte($date): bool + { + return $this->lessThanOrEqualTo($date); + } + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThanOrEqualTo($date): bool + { + $this->discourageNull($date); + $this->discourageBoolean($date); + + return $this <= $this->resolveCarbon($date); + } + + /** + * Determines if the instance is between two others. + * + * The third argument allow you to specify if bounds are included or not (true by default) + * but for when you including/excluding bounds may produce different results in your application, + * we recommend to use the explicit methods ->betweenIncluded() or ->betweenExcluded() instead. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->between('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function between($date1, $date2, $equal = true): bool + { + $date1 = $this->resolveCarbon($date1); + $date2 = $this->resolveCarbon($date2); + + if ($date1->greaterThan($date2)) { + [$date1, $date2] = [$date2, $date1]; + } + + if ($equal) { + return $this >= $date1 && $this <= $date2; + } + + return $this > $date1 && $this < $date2; + } + + /** + * Determines if the instance is between two others, bounds included. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenIncluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-25', '2018-08-01'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenIncluded($date1, $date2): bool + { + return $this->between($date1, $date2, true); + } + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenExcluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-25', '2018-08-01'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenExcluded($date1, $date2): bool + { + return $this->between($date1, $date2, false); + } + + /** + * Determines if the instance is between two others + * + * @example + * ``` + * Carbon::parse('2018-07-25')->isBetween('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function isBetween($date1, $date2, $equal = true): bool + { + return $this->between($date1, $date2, $equal); + } + + /** + * Determines if the instance is a weekday. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekday(); // false + * Carbon::parse('2019-07-15')->isWeekday(); // true + * ``` + * + * @return bool + */ + public function isWeekday() + { + return !$this->isWeekend(); + } + + /** + * Determines if the instance is a weekend day. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekend(); // true + * Carbon::parse('2019-07-15')->isWeekend(); // false + * ``` + * + * @return bool + */ + public function isWeekend() + { + return \in_array($this->dayOfWeek, static::$weekendDays, true); + } + + /** + * Determines if the instance is yesterday. + * + * @example + * ``` + * Carbon::yesterday()->isYesterday(); // true + * Carbon::tomorrow()->isYesterday(); // false + * ``` + * + * @return bool + */ + public function isYesterday() + { + return $this->toDateString() === static::yesterday($this->getTimezone())->toDateString(); + } + + /** + * Determines if the instance is today. + * + * @example + * ``` + * Carbon::today()->isToday(); // true + * Carbon::tomorrow()->isToday(); // false + * ``` + * + * @return bool + */ + public function isToday() + { + return $this->toDateString() === $this->nowWithSameTz()->toDateString(); + } + + /** + * Determines if the instance is tomorrow. + * + * @example + * ``` + * Carbon::tomorrow()->isTomorrow(); // true + * Carbon::yesterday()->isTomorrow(); // false + * ``` + * + * @return bool + */ + public function isTomorrow() + { + return $this->toDateString() === static::tomorrow($this->getTimezone())->toDateString(); + } + + /** + * Determines if the instance is in the future, ie. greater (after) than now. + * + * @example + * ``` + * Carbon::now()->addHours(5)->isFuture(); // true + * Carbon::now()->subHours(5)->isFuture(); // false + * ``` + * + * @return bool + */ + public function isFuture() + { + return $this->greaterThan($this->nowWithSameTz()); + } + + /** + * Determines if the instance is in the past, ie. less (before) than now. + * + * @example + * ``` + * Carbon::now()->subHours(5)->isPast(); // true + * Carbon::now()->addHours(5)->isPast(); // false + * ``` + * + * @return bool + */ + public function isPast() + { + return $this->lessThan($this->nowWithSameTz()); + } + + /** + * Determines if the instance is a leap year. + * + * @example + * ``` + * Carbon::parse('2020-01-01')->isLeapYear(); // true + * Carbon::parse('2019-01-01')->isLeapYear(); // false + * ``` + * + * @return bool + */ + public function isLeapYear() + { + return $this->rawFormat('L') === '1'; + } + + /** + * Determines if the instance is a long year (using calendar year). + * + * โš ๏ธ This method completely ignores month and day to use the numeric year number, + * it's not correct if the exact date matters. For instance as `2019-12-30` is already + * in the first week of the 2020 year, if you want to know from this date if ISO week + * year 2020 is a long year, use `isLongIsoYear` instead. + * + * @example + * ``` + * Carbon::create(2015)->isLongYear(); // true + * Carbon::create(2016)->isLongYear(); // false + * ``` + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongYear() + { + return static::create($this->year, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === 53; + } + + /** + * Determines if the instance is a long year (using ISO 8601 year). + * + * @example + * ``` + * Carbon::parse('2015-01-01')->isLongIsoYear(); // true + * Carbon::parse('2016-01-01')->isLongIsoYear(); // true + * Carbon::parse('2016-01-03')->isLongIsoYear(); // false + * Carbon::parse('2019-12-29')->isLongIsoYear(); // false + * Carbon::parse('2019-12-30')->isLongIsoYear(); // true + * ``` + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongIsoYear() + { + return static::create($this->isoWeekYear, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === 53; + } + + /** + * Compares the formatted values of the two dates. + * + * @example + * ``` + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-12-13')); // true + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-06-14')); // false + * ``` + * + * @param string $format date formats to compare. + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date instance to compare with or null to use current day. + * + * @return bool + */ + public function isSameAs($format, $date = null) + { + return $this->rawFormat($format) === $this->resolveCarbon($date)->rawFormat($format); + } + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::parse('2019-01-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // true + * Carbon::parse('2018-12-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // false + * ``` + * + * @param string $unit singular unit string + * @param \Carbon\Carbon|\DateTimeInterface|null $date instance to compare with or null to use current day. + * + * @throws BadComparisonUnitException + * + * @return bool + */ + public function isSameUnit($unit, $date = null) + { + $units = [ + // @call isSameUnit + 'year' => 'Y', + // @call isSameUnit + 'week' => 'o-W', + // @call isSameUnit + 'day' => 'Y-m-d', + // @call isSameUnit + 'hour' => 'Y-m-d H', + // @call isSameUnit + 'minute' => 'Y-m-d H:i', + // @call isSameUnit + 'second' => 'Y-m-d H:i:s', + // @call isSameUnit + 'micro' => 'Y-m-d H:i:s.u', + // @call isSameUnit + 'microsecond' => 'Y-m-d H:i:s.u', + ]; + + if (isset($units[$unit])) { + return $this->isSameAs($units[$unit], $date); + } + + if (isset($this->$unit)) { + return $this->resolveCarbon($date)->$unit === $this->$unit; + } + + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new BadComparisonUnitException($unit); + } + + return false; + } + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::now()->isCurrentUnit('hour'); // true + * Carbon::now()->subHours(2)->isCurrentUnit('hour'); // false + * ``` + * + * @param string $unit The unit to test. + * + * @throws BadMethodCallException + * + * @return bool + */ + public function isCurrentUnit($unit) + { + return $this->{'isSame'.ucfirst($unit)}(); + } + + /** + * Checks if the passed in date is in the same quarter as the instance quarter (and year if needed). + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-03-01')); // true + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-04-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01'), false); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date The instance to compare with or null to use current day. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameQuarter($date = null, $ofSameYear = true) + { + $date = $this->resolveCarbon($date); + + return $this->quarter === $date->quarter && (!$ofSameYear || $this->isSameYear($date)); + } + + /** + * Checks if the passed in date is in the same month as the instanceยดs month. + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-01-01')); // true + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-02-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01'), false); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameMonth($date = null, $ofSameYear = true) + { + return $this->isSameAs($ofSameYear ? 'Y-m' : 'm', $date); + } + + /** + * Checks if this day is a specific day of the week. + * + * @example + * ``` + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::WEDNESDAY); // true + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::FRIDAY); // false + * Carbon::parse('2019-07-17')->isDayOfWeek('Wednesday'); // true + * Carbon::parse('2019-07-17')->isDayOfWeek('Friday'); // false + * ``` + * + * @param int $dayOfWeek + * + * @return bool + */ + public function isDayOfWeek($dayOfWeek) + { + if (\is_string($dayOfWeek) && \defined($constant = static::class.'::'.strtoupper($dayOfWeek))) { + $dayOfWeek = \constant($constant); + } + + return $this->dayOfWeek === $dayOfWeek; + } + + /** + * Check if its the birthday. Compares the date/month values of the two dates. + * + * @example + * ``` + * Carbon::now()->subYears(5)->isBirthday(); // true + * Carbon::now()->subYears(5)->subDay()->isBirthday(); // false + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-05')); // true + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-06')); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @return bool + */ + public function isBirthday($date = null) + { + return $this->isSameAs('md', $date); + } + + /** + * Check if today is the last day of the Month + * + * @example + * ``` + * Carbon::parse('2019-02-28')->isLastOfMonth(); // true + * Carbon::parse('2019-03-28')->isLastOfMonth(); // false + * Carbon::parse('2019-03-30')->isLastOfMonth(); // false + * Carbon::parse('2019-03-31')->isLastOfMonth(); // true + * Carbon::parse('2019-04-30')->isLastOfMonth(); // true + * ``` + * + * @return bool + */ + public function isLastOfMonth() + { + return $this->day === $this->daysInMonth; + } + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:01')->isStartOfDay(); // false + * Carbon::parse('2019-02-28 00:00:00.000000')->isStartOfDay(true); // true + * Carbon::parse('2019-02-28 00:00:00.000012')->isStartOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isStartOfDay($checkMicroseconds = false) + { + /* @var CarbonInterface $this */ + return $checkMicroseconds + ? $this->rawFormat('H:i:s.u') === '00:00:00.000000' + : $this->rawFormat('H:i:s') === '00:00:00'; + } + + /** + * Check if the instance is end of day. + * + * @example + * ``` + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:58.999999')->isEndOfDay(); // false + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(true); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(true); // false + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isEndOfDay($checkMicroseconds = false) + { + /* @var CarbonInterface $this */ + return $checkMicroseconds + ? $this->rawFormat('H:i:s.u') === '23:59:59.999999' + : $this->rawFormat('H:i:s') === '23:59:59'; + } + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:01')->isMidnight(); // false + * ``` + * + * @return bool + */ + public function isMidnight() + { + return $this->isStartOfDay(); + } + + /** + * Check if the instance is midday. + * + * @example + * ``` + * Carbon::parse('2019-02-28 11:59:59.999999')->isMidday(); // false + * Carbon::parse('2019-02-28 12:00:00')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:00.999999')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:01')->isMidday(); // false + * ``` + * + * @return bool + */ + public function isMidday() + { + /* @var CarbonInterface $this */ + return $this->rawFormat('G:i:s') === static::$midDayAt.':00:00'; + } + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormat('11:12:45', 'h:i:s'); // true + * Carbon::hasFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormat($date, $format) + { + // createFromFormat() is known to handle edge cases silently. + // E.g. "1975-5-1" (Y-n-j) will still be parsed correctly when "Y-m-d" is supplied as the format. + // To ensure we're really testing against our desired format, perform an additional regex validation. + + return self::matchFormatPattern((string) $date, preg_quote((string) $format, '/'), static::$regexFormats); + } + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormatWithModifiers('31/08/2015', 'd#m#Y'); // true + * Carbon::hasFormatWithModifiers('31/08/2015', 'm#d#Y'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormatWithModifiers($date, $format): bool + { + return self::matchFormatPattern((string) $date, (string) $format, array_merge(static::$regexFormats, static::$regexFormatModifiers)); + } + + /** + * Checks if the (date)time string is in a given format and valid to create a + * new instance. + * + * @example + * ``` + * Carbon::canBeCreatedFromFormat('11:12:45', 'h:i:s'); // true + * Carbon::canBeCreatedFromFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function canBeCreatedFromFormat($date, $format) + { + try { + // Try to create a DateTime object. Throws an InvalidArgumentException if the provided time string + // doesn't match the format in any way. + if (!static::rawCreateFromFormat($format, $date)) { + return false; + } + } catch (InvalidArgumentException $e) { + return false; + } + + return static::hasFormatWithModifiers($date, $format); + } + + /** + * Returns true if the current date matches the given string. + * + * @example + * ``` + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2018')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('Sunday')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('June')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:45')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:00')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12h')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3pm')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3am')); // false + * ``` + * + * @param string $tester day name, month name, hour, date, etc. as string + * + * @return bool + */ + public function is(string $tester) + { + $tester = trim($tester); + + if (preg_match('/^\d+$/', $tester)) { + return $this->year === (int) $tester; + } + + if (preg_match('/^\d{3,}-\d{1,2}$/', $tester)) { + return $this->isSameMonth(static::parse($tester)); + } + + if (preg_match('/^\d{1,2}-\d{1,2}$/', $tester)) { + return $this->isSameDay(static::parse($this->year.'-'.$tester)); + } + + $modifier = preg_replace('/(\d)h$/i', '$1:00', $tester); + + /* @var CarbonInterface $max */ + $median = static::parse('5555-06-15 12:30:30.555555')->modify($modifier); + $current = $this->avoidMutation(); + /* @var CarbonInterface $other */ + $other = $this->avoidMutation()->modify($modifier); + + if ($current->eq($other)) { + return true; + } + + if (preg_match('/\d:\d{1,2}:\d{1,2}$/', $tester)) { + return $current->startOfSecond()->eq($other); + } + + if (preg_match('/\d:\d{1,2}$/', $tester)) { + return $current->startOfMinute()->eq($other); + } + + if (preg_match('/\d(?:h|am|pm)$/', $tester)) { + return $current->startOfHour()->eq($other); + } + + if (preg_match( + '/^(?:january|february|march|april|may|june|july|august|september|october|november|december)(?:\s+\d+)?$/i', + $tester + )) { + return $current->startOfMonth()->eq($other->startOfMonth()); + } + + $units = [ + 'month' => [1, 'year'], + 'day' => [1, 'month'], + 'hour' => [0, 'day'], + 'minute' => [0, 'hour'], + 'second' => [0, 'minute'], + 'microsecond' => [0, 'second'], + ]; + + foreach ($units as $unit => [$minimum, $startUnit]) { + if ($minimum === $median->$unit) { + $current = $current->startOf($startUnit); + + break; + } + } + + return $current->eq($other); + } + + /** + * Checks if the (date)time string is in a given format with + * given list of pattern replacements. + * + * @example + * ``` + * Carbon::hasFormat('11:12:45', 'h:i:s'); // true + * Carbon::hasFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * @param array $replacements + * + * @return bool + */ + private static function matchFormatPattern(string $date, string $format, array $replacements): bool + { + // Preg quote, but remove escaped backslashes since we'll deal with escaped characters in the format string. + $regex = str_replace('\\\\', '\\', $format); + // Replace not-escaped letters + $regex = preg_replace_callback( + '/(?startOfTime ?? false; + } + + /** + * Returns true if the date was created using CarbonImmutable::endOfTime() + * + * @return bool + */ + public function isEndOfTime(): bool + { + return $this->endOfTime ?? false; + } + + private function discourageNull($value): void + { + if ($value === null) { + @trigger_error("Since 2.61.0, it's deprecated to compare a date to null, meaning of such comparison is ambiguous and will no longer be possible in 3.0.0, you should explicitly pass 'now' or make an other check to eliminate null values.", \E_USER_DEPRECATED); + } + } + + private function discourageBoolean($value): void + { + if (\is_bool($value)) { + @trigger_error("Since 2.61.0, it's deprecated to compare a date to true or false, meaning of such comparison is ambiguous and will no longer be possible in 3.0.0, you should explicitly pass 'now' or make an other check to eliminate boolean values.", \E_USER_DEPRECATED); + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Converter.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Converter.php new file mode 100644 index 0000000000..fff8a600a4 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Converter.php @@ -0,0 +1,639 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; +use Carbon\CarbonInterval; +use Carbon\CarbonPeriod; +use Carbon\CarbonPeriodImmutable; +use Carbon\Exceptions\UnitException; +use Closure; +use DateTime; +use DateTimeImmutable; +use ReturnTypeWillChange; + +/** + * Trait Converter. + * + * Change date into different string formats and types and + * handle the string cast. + * + * Depends on the following methods: + * + * @method static copy() + */ +trait Converter +{ + use ToStringFormat; + + /** + * Returns the formatted date string on success or FALSE on failure. + * + * @see https://php.net/manual/en/datetime.format.php + * + * @param string $format + * + * @return string + */ + #[ReturnTypeWillChange] + public function format($format) + { + $function = $this->localFormatFunction ?: static::$formatFunction; + + if (!$function) { + return $this->rawFormat($format); + } + + if (\is_string($function) && method_exists($this, $function)) { + $function = [$this, $function]; + } + + return $function(...\func_get_args()); + } + + /** + * @see https://php.net/manual/en/datetime.format.php + * + * @param string $format + * + * @return string + */ + public function rawFormat($format) + { + return parent::format($format); + } + + /** + * Format the instance as a string using the set format + * + * @example + * ``` + * echo Carbon::now(); // Carbon instances can be cast to string + * ``` + * + * @return string + */ + public function __toString() + { + $format = $this->localToStringFormat ?? static::$toStringFormat; + + return $format instanceof Closure + ? $format($this) + : $this->rawFormat($format ?: ( + \defined('static::DEFAULT_TO_STRING_FORMAT') + ? static::DEFAULT_TO_STRING_FORMAT + : CarbonInterface::DEFAULT_TO_STRING_FORMAT + )); + } + + /** + * Format the instance as date + * + * @example + * ``` + * echo Carbon::now()->toDateString(); + * ``` + * + * @return string + */ + public function toDateString() + { + return $this->rawFormat('Y-m-d'); + } + + /** + * Format the instance as a readable date + * + * @example + * ``` + * echo Carbon::now()->toFormattedDateString(); + * ``` + * + * @return string + */ + public function toFormattedDateString() + { + return $this->rawFormat('M j, Y'); + } + + /** + * Format the instance with the day, and a readable date + * + * @example + * ``` + * echo Carbon::now()->toFormattedDayDateString(); + * ``` + * + * @return string + */ + public function toFormattedDayDateString(): string + { + return $this->rawFormat('D, M j, Y'); + } + + /** + * Format the instance as time + * + * @example + * ``` + * echo Carbon::now()->toTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toTimeString($unitPrecision = 'second') + { + return $this->rawFormat(static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Format the instance as date and time + * + * @example + * ``` + * echo Carbon::now()->toDateTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeString($unitPrecision = 'second') + { + return $this->rawFormat('Y-m-d '.static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Return a format from H:i to H:i:s.u according to given unit precision. + * + * @param string $unitPrecision "minute", "second", "millisecond" or "microsecond" + * + * @return string + */ + public static function getTimeFormatByPrecision($unitPrecision) + { + switch (static::singularUnit($unitPrecision)) { + case 'minute': + return 'H:i'; + case 'second': + return 'H:i:s'; + case 'm': + case 'millisecond': + return 'H:i:s.v'; + case 'ยต': + case 'microsecond': + return 'H:i:s.u'; + } + + throw new UnitException('Precision unit expected among: minute, second, millisecond and microsecond.'); + } + + /** + * Format the instance as date and time T-separated with no timezone + * + * @example + * ``` + * echo Carbon::now()->toDateTimeLocalString(); + * echo "\n"; + * echo Carbon::now()->toDateTimeLocalString('minute'); // You can specify precision among: minute, second, millisecond and microsecond + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeLocalString($unitPrecision = 'second') + { + return $this->rawFormat('Y-m-d\T'.static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Format the instance with day, date and time + * + * @example + * ``` + * echo Carbon::now()->toDayDateTimeString(); + * ``` + * + * @return string + */ + public function toDayDateTimeString() + { + return $this->rawFormat('D, M j, Y g:i A'); + } + + /** + * Format the instance as ATOM + * + * @example + * ``` + * echo Carbon::now()->toAtomString(); + * ``` + * + * @return string + */ + public function toAtomString() + { + return $this->rawFormat(DateTime::ATOM); + } + + /** + * Format the instance as COOKIE + * + * @example + * ``` + * echo Carbon::now()->toCookieString(); + * ``` + * + * @return string + */ + public function toCookieString() + { + return $this->rawFormat(DateTime::COOKIE); + } + + /** + * Format the instance as ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601String(); + * ``` + * + * @return string + */ + public function toIso8601String() + { + return $this->toAtomString(); + } + + /** + * Format the instance as RFC822 + * + * @example + * ``` + * echo Carbon::now()->toRfc822String(); + * ``` + * + * @return string + */ + public function toRfc822String() + { + return $this->rawFormat(DateTime::RFC822); + } + + /** + * Convert the instance to UTC and return as Zulu ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601ZuluString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toIso8601ZuluString($unitPrecision = 'second') + { + return $this->avoidMutation() + ->utc() + ->rawFormat('Y-m-d\T'.static::getTimeFormatByPrecision($unitPrecision).'\Z'); + } + + /** + * Format the instance as RFC850 + * + * @example + * ``` + * echo Carbon::now()->toRfc850String(); + * ``` + * + * @return string + */ + public function toRfc850String() + { + return $this->rawFormat(DateTime::RFC850); + } + + /** + * Format the instance as RFC1036 + * + * @example + * ``` + * echo Carbon::now()->toRfc1036String(); + * ``` + * + * @return string + */ + public function toRfc1036String() + { + return $this->rawFormat(DateTime::RFC1036); + } + + /** + * Format the instance as RFC1123 + * + * @example + * ``` + * echo Carbon::now()->toRfc1123String(); + * ``` + * + * @return string + */ + public function toRfc1123String() + { + return $this->rawFormat(DateTime::RFC1123); + } + + /** + * Format the instance as RFC2822 + * + * @example + * ``` + * echo Carbon::now()->toRfc2822String(); + * ``` + * + * @return string + */ + public function toRfc2822String() + { + return $this->rawFormat(DateTime::RFC2822); + } + + /** + * Format the instance as RFC3339 + * + * @param bool $extended + * + * @example + * ``` + * echo Carbon::now()->toRfc3339String() . "\n"; + * echo Carbon::now()->toRfc3339String(true) . "\n"; + * ``` + * + * @return string + */ + public function toRfc3339String($extended = false) + { + $format = DateTime::RFC3339; + if ($extended) { + $format = DateTime::RFC3339_EXTENDED; + } + + return $this->rawFormat($format); + } + + /** + * Format the instance as RSS + * + * @example + * ``` + * echo Carbon::now()->toRssString(); + * ``` + * + * @return string + */ + public function toRssString() + { + return $this->rawFormat(DateTime::RSS); + } + + /** + * Format the instance as W3C + * + * @example + * ``` + * echo Carbon::now()->toW3cString(); + * ``` + * + * @return string + */ + public function toW3cString() + { + return $this->rawFormat(DateTime::W3C); + } + + /** + * Format the instance as RFC7231 + * + * @example + * ``` + * echo Carbon::now()->toRfc7231String(); + * ``` + * + * @return string + */ + public function toRfc7231String() + { + return $this->avoidMutation() + ->setTimezone('GMT') + ->rawFormat(\defined('static::RFC7231_FORMAT') ? static::RFC7231_FORMAT : CarbonInterface::RFC7231_FORMAT); + } + + /** + * Get default array representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toArray()); + * ``` + * + * @return array + */ + public function toArray() + { + return [ + 'year' => $this->year, + 'month' => $this->month, + 'day' => $this->day, + 'dayOfWeek' => $this->dayOfWeek, + 'dayOfYear' => $this->dayOfYear, + 'hour' => $this->hour, + 'minute' => $this->minute, + 'second' => $this->second, + 'micro' => $this->micro, + 'timestamp' => $this->timestamp, + 'formatted' => $this->rawFormat(\defined('static::DEFAULT_TO_STRING_FORMAT') ? static::DEFAULT_TO_STRING_FORMAT : CarbonInterface::DEFAULT_TO_STRING_FORMAT), + 'timezone' => $this->timezone, + ]; + } + + /** + * Get default object representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toObject()); + * ``` + * + * @return object + */ + public function toObject() + { + return (object) $this->toArray(); + } + + /** + * Returns english human readable complete date string. + * + * @example + * ``` + * echo Carbon::now()->toString(); + * ``` + * + * @return string + */ + public function toString() + { + return $this->avoidMutation()->locale('en')->isoFormat('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + } + + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z, if $keepOffset truthy, offset will be kept: + * 1977-04-22T01:00:00-05:00). + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toISOString() . "\n"; + * echo Carbon::now('America/Toronto')->toISOString(true) . "\n"; + * ``` + * + * @param bool $keepOffset Pass true to keep the date offset. Else forced to UTC. + * + * @return null|string + */ + public function toISOString($keepOffset = false) + { + if (!$this->isValid()) { + return null; + } + + $yearFormat = $this->year < 0 || $this->year > 9999 ? 'YYYYYY' : 'YYYY'; + $tzFormat = $keepOffset ? 'Z' : '[Z]'; + $date = $keepOffset ? $this : $this->avoidMutation()->utc(); + + return $date->isoFormat("$yearFormat-MM-DD[T]HH:mm:ss.SSSSSS$tzFormat"); + } + + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z) with UTC timezone. + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toJSON(); + * ``` + * + * @return null|string + */ + public function toJSON() + { + return $this->toISOString(); + } + + /** + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTime()); + * ``` + * + * @return DateTime + */ + public function toDateTime() + { + return new DateTime($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone()); + } + + /** + * Return native toDateTimeImmutable PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTimeImmutable()); + * ``` + * + * @return DateTimeImmutable + */ + public function toDateTimeImmutable() + { + return new DateTimeImmutable($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone()); + } + + /** + * @alias toDateTime + * + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDate()); + * ``` + * + * @return DateTime + */ + public function toDate() + { + return $this->toDateTime(); + } + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|Carbon|CarbonImmutable|int|null $end period end date or recurrences count if int + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function toPeriod($end = null, $interval = null, $unit = null) + { + if ($unit) { + $interval = CarbonInterval::make("$interval ".static::pluralUnit($unit)); + } + + $period = ($this->isMutable() ? new CarbonPeriod() : new CarbonPeriodImmutable()) + ->setDateClass(static::class) + ->setStartDate($this); + + if ($interval) { + $period = $period->setDateInterval($interval); + } + + if (\is_int($end) || (\is_string($end) && ctype_digit($end))) { + $period = $period->setRecurrences($end); + } elseif ($end) { + $period = $period->setEndDate($end); + } + + return $period; + } + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|Carbon|CarbonImmutable|null $end period end date + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function range($end = null, $interval = null, $unit = null) + { + return $this->toPeriod($end, $interval, $unit); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php new file mode 100644 index 0000000000..0d611ea22e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php @@ -0,0 +1,977 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; +use Carbon\Exceptions\InvalidDateException; +use Carbon\Exceptions\InvalidFormatException; +use Carbon\Exceptions\OutOfRangeException; +use Carbon\Translator; +use Closure; +use DateMalformedStringException; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use Exception; +use ReturnTypeWillChange; + +/** + * Trait Creator. + * + * Static creators. + * + * Depends on the following methods: + * + * @method static Carbon|CarbonImmutable getTestNow() + */ +trait Creator +{ + use ObjectInitialisation; + + /** + * The errors that can occur. + * + * @var array + */ + protected static $lastErrors; + + /** + * Create a new Carbon instance. + * + * Please see the testing aids section (specifically static::setTestNow()) + * for more on the possibility of this constructor returning a test instance. + * + * @param DateTimeInterface|string|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + */ + public function __construct($time = null, $tz = null) + { + if ($time instanceof DateTimeInterface) { + $time = $this->constructTimezoneFromDateTime($time, $tz)->format('Y-m-d H:i:s.u'); + } + + if (is_numeric($time) && (!\is_string($time) || !preg_match('/^\d{1,14}$/', $time))) { + $time = static::createFromTimestampUTC($time)->format('Y-m-d\TH:i:s.uP'); + } + + // If the class has a test now set and we are trying to create a now() + // instance then override as required + $isNow = empty($time) || $time === 'now'; + + if (method_exists(static::class, 'hasTestNow') && + method_exists(static::class, 'getTestNow') && + static::hasTestNow() && + ($isNow || static::hasRelativeKeywords($time)) + ) { + static::mockConstructorParameters($time, $tz); + } + + // Work-around for PHP bug https://bugs.php.net/bug.php?id=67127 + if (!str_contains((string) .1, '.')) { + $locale = setlocale(LC_NUMERIC, '0'); // @codeCoverageIgnore + setlocale(LC_NUMERIC, 'C'); // @codeCoverageIgnore + } + + try { + parent::__construct($time ?: 'now', static::safeCreateDateTimeZone($tz) ?: null); + } catch (Exception $exception) { + throw new InvalidFormatException($exception->getMessage(), 0, $exception); + } + + $this->constructedObjectId = spl_object_hash($this); + + if (isset($locale)) { + setlocale(LC_NUMERIC, $locale); // @codeCoverageIgnore + } + + self::setLastErrors(parent::getLastErrors()); + } + + /** + * Get timezone from a datetime instance. + * + * @param DateTimeInterface $date + * @param DateTimeZone|string|null $tz + * + * @return DateTimeInterface + */ + private function constructTimezoneFromDateTime(DateTimeInterface $date, &$tz) + { + if ($tz !== null) { + $safeTz = static::safeCreateDateTimeZone($tz); + + if ($safeTz) { + return ($date instanceof DateTimeImmutable ? $date : clone $date)->setTimezone($safeTz); + } + + return $date; + } + + $tz = $date->getTimezone(); + + return $date; + } + + /** + * Update constructedObjectId on cloned. + */ + public function __clone() + { + $this->constructedObjectId = spl_object_hash($this); + } + + /** + * Create a Carbon instance from a DateTime one. + * + * @param DateTimeInterface $date + * + * @return static + */ + public static function instance($date) + { + if ($date instanceof static) { + return clone $date; + } + + static::expectDateTime($date); + + $instance = new static($date->format('Y-m-d H:i:s.u'), $date->getTimezone()); + + if ($date instanceof CarbonInterface) { + $settings = $date->getSettings(); + + if (!$date->hasLocalTranslator()) { + unset($settings['locale']); + } + + $instance->settings($settings); + } + + return $instance; + } + + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function rawParse($time = null, $tz = null) + { + if ($time instanceof DateTimeInterface) { + return static::instance($time); + } + + try { + return new static($time, $tz); + } catch (Exception $exception) { + // @codeCoverageIgnoreStart + try { + $date = @static::now($tz)->change($time); + } catch (DateMalformedStringException $ignoredException) { + $date = null; + } + // @codeCoverageIgnoreEnd + + if (!$date) { + throw new InvalidFormatException("Could not parse '$time': ".$exception->getMessage(), 0, $exception); + } + + return $date; + } + } + + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parse($time = null, $tz = null) + { + $function = static::$parseFunction; + + if (!$function) { + return static::rawParse($time, $tz); + } + + if (\is_string($function) && method_exists(static::class, $function)) { + $function = [static::class, $function]; + } + + return $function(...\func_get_args()); + } + + /** + * Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * + * @param string $time date/time string in the given language (may also contain English). + * @param string|null $locale if locale is null or not specified, current global locale will be + * used instead. + * @param DateTimeZone|string|null $tz optional timezone for the new instance. + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parseFromLocale($time, $locale = null, $tz = null) + { + return static::rawParse(static::translateTimeString($time, $locale, 'en'), $tz); + } + + /** + * Get a Carbon instance for the current date and time. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function now($tz = null) + { + return new static(null, $tz); + } + + /** + * Create a Carbon instance for today. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function today($tz = null) + { + return static::rawParse('today', $tz); + } + + /** + * Create a Carbon instance for tomorrow. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function tomorrow($tz = null) + { + return static::rawParse('tomorrow', $tz); + } + + /** + * Create a Carbon instance for yesterday. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function yesterday($tz = null) + { + return static::rawParse('yesterday', $tz); + } + + /** + * Create a Carbon instance for the greatest supported date. + * + * @return static + */ + public static function maxValue() + { + if (self::$PHPIntSize === 4) { + // 32 bit + return static::createFromTimestamp(PHP_INT_MAX); // @codeCoverageIgnore + } + + // 64 bit + return static::create(9999, 12, 31, 23, 59, 59); + } + + /** + * Create a Carbon instance for the lowest supported date. + * + * @return static + */ + public static function minValue() + { + if (self::$PHPIntSize === 4) { + // 32 bit + return static::createFromTimestamp(~PHP_INT_MAX); // @codeCoverageIgnore + } + + // 64 bit + return static::create(1, 1, 1, 0, 0, 0); + } + + private static function assertBetween($unit, $value, $min, $max) + { + if (static::isStrictModeEnabled() && ($value < $min || $value > $max)) { + throw new OutOfRangeException($unit, $min, $max, $value); + } + } + + private static function createNowInstance($tz) + { + if (!static::hasTestNow()) { + return static::now($tz); + } + + $now = static::getTestNow(); + + if ($now instanceof Closure) { + return $now(static::now($tz)); + } + + return $now->avoidMutation()->tz($tz); + } + + /** + * Create a new Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * @param DateTimeInterface|int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) + { + if ((\is_string($year) && !is_numeric($year)) || $year instanceof DateTimeInterface) { + return static::parse($year, $tz ?: (\is_string($month) || $month instanceof DateTimeZone ? $month : null)); + } + + $defaults = null; + $getDefault = function ($unit) use ($tz, &$defaults) { + if ($defaults === null) { + $now = self::createNowInstance($tz); + + $defaults = array_combine([ + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + ], explode('-', $now->rawFormat('Y-n-j-G-i-s.u'))); + } + + return $defaults[$unit]; + }; + + $year = $year ?? $getDefault('year'); + $month = $month ?? $getDefault('month'); + $day = $day ?? $getDefault('day'); + $hour = $hour ?? $getDefault('hour'); + $minute = $minute ?? $getDefault('minute'); + $second = (float) ($second ?? $getDefault('second')); + + self::assertBetween('month', $month, 0, 99); + self::assertBetween('day', $day, 0, 99); + self::assertBetween('hour', $hour, 0, 99); + self::assertBetween('minute', $minute, 0, 99); + self::assertBetween('second', $second, 0, 99); + + $fixYear = null; + + if ($year < 0) { + $fixYear = $year; + $year = 0; + } elseif ($year > 9999) { + $fixYear = $year - 9999; + $year = 9999; + } + + $second = ($second < 10 ? '0' : '').number_format($second, 6); + $instance = static::rawCreateFromFormat('!Y-n-j G:i:s.u', sprintf('%s-%s-%s %s:%02s:%02s', $year, $month, $day, $hour, $minute, $second), $tz); + + if ($fixYear !== null) { + $instance = $instance->addYears($fixYear); + } + + return $instance; + } + + /** + * Create a new safe Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * If one of the set values is not valid, an InvalidDateException + * will be thrown. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidDateException + * + * @return static|false + */ + public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) + { + $fields = static::getRangesByUnit(); + + foreach ($fields as $field => $range) { + if ($$field !== null && (!\is_int($$field) || $$field < $range[0] || $$field > $range[1])) { + if (static::isStrictModeEnabled()) { + throw new InvalidDateException($field, $$field); + } + + return false; + } + } + + $instance = static::create($year, $month, $day, $hour, $minute, $second, $tz); + + foreach (array_reverse($fields) as $field => $range) { + if ($$field !== null && (!\is_int($$field) || $$field !== $instance->$field)) { + if (static::isStrictModeEnabled()) { + throw new InvalidDateException($field, $$field); + } + + return false; + } + } + + return $instance; + } + + /** + * Create a new Carbon instance from a specific date and time using strict validation. + * + * @see create() + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null): self + { + $initialStrictMode = static::isStrictModeEnabled(); + static::useStrictMode(true); + + try { + $date = static::create($year, $month, $day, $hour, $minute, $second, $tz); + } finally { + static::useStrictMode($initialStrictMode); + } + + return $date; + } + + /** + * Create a Carbon instance from just a date. The time portion is set to now. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromDate($year = null, $month = null, $day = null, $tz = null) + { + return static::create($year, $month, $day, null, null, null, $tz); + } + + /** + * Create a Carbon instance from just a date. The time portion is set to midnight. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createMidnightDate($year = null, $month = null, $day = null, $tz = null) + { + return static::create($year, $month, $day, 0, 0, 0, $tz); + } + + /** + * Create a Carbon instance from just a time. The date portion is set to today. + * + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) + { + return static::create(null, null, null, $hour, $minute, $second, $tz); + } + + /** + * Create a Carbon instance from a time string. The date portion is set to today. + * + * @param string $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromTimeString($time, $tz = null) + { + return static::today($tz)->setTimeFromTimeString($time); + } + + /** + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $originalTz + * + * @return DateTimeInterface|false + */ + private static function createFromFormatAndTimezone($format, $time, $originalTz) + { + // Work-around for https://bugs.php.net/bug.php?id=75577 + // @codeCoverageIgnoreStart + if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { + $format = str_replace('.v', '.u', $format); + } + // @codeCoverageIgnoreEnd + + if ($originalTz === null) { + return parent::createFromFormat($format, (string) $time); + } + + $tz = \is_int($originalTz) + ? @timezone_name_from_abbr('', (int) ($originalTz * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE), 1) + : $originalTz; + + $tz = static::safeCreateDateTimeZone($tz, $originalTz); + + if ($tz === false) { + return false; + } + + return parent::createFromFormat($format, (string) $time, $tz); + } + + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function rawCreateFromFormat($format, $time, $tz = null) + { + // Work-around for https://bugs.php.net/bug.php?id=80141 + $format = preg_replace('/(?getTimezone(); + } + + $mock = $mock->copy(); + + // Prepend mock datetime only if the format does not contain non escaped unix epoch reset flag. + if (!preg_match("/{$nonEscaped}[!|]/", $format)) { + if (preg_match('/[HhGgisvuB]/', $format)) { + $mock = $mock->setTime(0, 0); + } + + $format = static::MOCK_DATETIME_FORMAT.' '.$format; + $time = ($mock instanceof self ? $mock->rawFormat(static::MOCK_DATETIME_FORMAT) : $mock->format(static::MOCK_DATETIME_FORMAT)).' '.$time; + } + + // Regenerate date from the modified format to base result on the mocked instance instead of now. + $date = self::createFromFormatAndTimezone($format, $time, $tz); + } + + if ($date instanceof DateTimeInterface) { + $instance = static::instance($date); + $instance::setLastErrors($lastErrors); + + return $instance; + } + + if (static::isStrictModeEnabled()) { + throw new InvalidFormatException(implode(PHP_EOL, $lastErrors['errors'])); + } + + return false; + } + + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + #[ReturnTypeWillChange] + public static function createFromFormat($format, $time, $tz = null) + { + $function = static::$createFromFormatFunction; + + if (!$function) { + return static::rawCreateFromFormat($format, $time, $tz); + } + + if (\is_string($function) && method_exists(static::class, $function)) { + $function = [static::class, $function]; + } + + return $function(...\func_get_args()); + } + + /** + * Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz optional timezone + * @param string|null $locale locale to be used for LTS, LT, LL, LLL, etc. macro-formats (en by fault, unneeded if no such macro-format in use) + * @param \Symfony\Component\Translation\TranslatorInterface $translator optional custom translator to use for macro-formats + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) + { + $format = preg_replace_callback('/(? static::getTranslationMessageWith($translator, 'formats.LT', $locale, 'h:mm A'), + 'LTS' => static::getTranslationMessageWith($translator, 'formats.LTS', $locale, 'h:mm:ss A'), + 'L' => static::getTranslationMessageWith($translator, 'formats.L', $locale, 'MM/DD/YYYY'), + 'LL' => static::getTranslationMessageWith($translator, 'formats.LL', $locale, 'MMMM D, YYYY'), + 'LLL' => static::getTranslationMessageWith($translator, 'formats.LLL', $locale, 'MMMM D, YYYY h:mm A'), + 'LLLL' => static::getTranslationMessageWith($translator, 'formats.LLLL', $locale, 'dddd, MMMM D, YYYY h:mm A'), + ]; + } + + return $formats[$code] ?? preg_replace_callback( + '/MMMM|MM|DD|dddd/', + function ($code) { + return mb_substr($code[0], 1); + }, + $formats[strtoupper($code)] ?? '' + ); + }, $format); + + $format = preg_replace_callback('/(? 'd', + 'OM' => 'M', + 'OY' => 'Y', + 'OH' => 'G', + 'Oh' => 'g', + 'Om' => 'i', + 'Os' => 's', + 'D' => 'd', + 'DD' => 'd', + 'Do' => 'd', + 'd' => '!', + 'dd' => '!', + 'ddd' => 'D', + 'dddd' => 'D', + 'DDD' => 'z', + 'DDDD' => 'z', + 'DDDo' => 'z', + 'e' => '!', + 'E' => '!', + 'H' => 'G', + 'HH' => 'H', + 'h' => 'g', + 'hh' => 'h', + 'k' => 'G', + 'kk' => 'G', + 'hmm' => 'gi', + 'hmmss' => 'gis', + 'Hmm' => 'Gi', + 'Hmmss' => 'Gis', + 'm' => 'i', + 'mm' => 'i', + 'a' => 'a', + 'A' => 'a', + 's' => 's', + 'ss' => 's', + 'S' => '*', + 'SS' => '*', + 'SSS' => '*', + 'SSSS' => '*', + 'SSSSS' => '*', + 'SSSSSS' => 'u', + 'SSSSSSS' => 'u*', + 'SSSSSSSS' => 'u*', + 'SSSSSSSSS' => 'u*', + 'M' => 'm', + 'MM' => 'm', + 'MMM' => 'M', + 'MMMM' => 'M', + 'Mo' => 'm', + 'Q' => '!', + 'Qo' => '!', + 'G' => '!', + 'GG' => '!', + 'GGG' => '!', + 'GGGG' => '!', + 'GGGGG' => '!', + 'g' => '!', + 'gg' => '!', + 'ggg' => '!', + 'gggg' => '!', + 'ggggg' => '!', + 'W' => '!', + 'WW' => '!', + 'Wo' => '!', + 'w' => '!', + 'ww' => '!', + 'wo' => '!', + 'x' => 'U???', + 'X' => 'U', + 'Y' => 'Y', + 'YY' => 'y', + 'YYYY' => 'Y', + 'YYYYY' => 'Y', + 'YYYYYY' => 'Y', + 'z' => 'e', + 'zz' => 'e', + 'Z' => 'e', + 'ZZ' => 'e', + ]; + } + + $format = $replacements[$code] ?? '?'; + + if ($format === '!') { + throw new InvalidFormatException("Format $code not supported for creation."); + } + + return $format; + }, $format); + + return static::rawCreateFromFormat($format, $time, $tz); + } + + /** + * Create a Carbon instance from a specific format and a string in a given language. + * + * @param string $format Datetime format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleFormat($format, $locale, $time, $tz = null) + { + $format = preg_replace_callback( + '/(?:\\\\[a-zA-Z]|[bfkqCEJKQRV]){2,}/', + static function (array $match) use ($locale): string { + $word = str_replace('\\', '', $match[0]); + $translatedWord = static::translateTimeString($word, $locale, 'en'); + + return $word === $translatedWord + ? $match[0] + : preg_replace('/[a-zA-Z]/', '\\\\$0', $translatedWord); + }, + $format + ); + + return static::rawCreateFromFormat($format, static::translateTimeString($time, $locale, 'en'), $tz); + } + + /** + * Create a Carbon instance from a specific ISO format and a string in a given language. + * + * @param string $format Datetime ISO format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleIsoFormat($format, $locale, $time, $tz = null) + { + $time = static::translateTimeString($time, $locale, 'en', CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS | CarbonInterface::TRANSLATE_MERIDIEM); + + return static::createFromIsoFormat($format, $time, $tz, $locale); + } + + /** + * Make a Carbon instance from given variable if possible. + * + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * + * @param mixed $var + * + * @throws InvalidFormatException + * + * @return static|null + */ + public static function make($var) + { + if ($var instanceof DateTimeInterface) { + return static::instance($var); + } + + $date = null; + + if (\is_string($var)) { + $var = trim($var); + + if (!preg_match('/^P[\dT]/', $var) && + !preg_match('/^R\d/', $var) && + preg_match('/[a-z\d]/i', $var) + ) { + $date = static::parse($var); + } + } + + return $date; + } + + /** + * Set last errors. + * + * @param array|bool $lastErrors + * + * @return void + */ + private static function setLastErrors($lastErrors) + { + if (\is_array($lastErrors) || $lastErrors === false) { + static::$lastErrors = \is_array($lastErrors) ? $lastErrors : [ + 'warning_count' => 0, + 'warnings' => [], + 'error_count' => 0, + 'errors' => [], + ]; + } + } + + /** + * {@inheritdoc} + * + * @return array + */ + #[ReturnTypeWillChange] + public static function getLastErrors() + { + return static::$lastErrors; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Date.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Date.php new file mode 100644 index 0000000000..8ae5c17819 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Date.php @@ -0,0 +1,2747 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use BadMethodCallException; +use Carbon\Carbon; +use Carbon\CarbonInterface; +use Carbon\CarbonPeriod; +use Carbon\CarbonTimeZone; +use Carbon\Exceptions\BadComparisonUnitException; +use Carbon\Exceptions\ImmutableException; +use Carbon\Exceptions\InvalidTimeZoneException; +use Carbon\Exceptions\InvalidTypeException; +use Carbon\Exceptions\UnknownGetterException; +use Carbon\Exceptions\UnknownMethodException; +use Carbon\Exceptions\UnknownSetterException; +use Carbon\Exceptions\UnknownUnitException; +use Closure; +use DateInterval; +use DatePeriod; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use InvalidArgumentException; +use ReflectionException; +use ReturnTypeWillChange; +use Throwable; + +/** + * A simple API extension for DateTime. + * + * @mixin DeprecatedProperties + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method CarbonInterface years(int $value) Set current instance year to the given value. + * @method CarbonInterface year(int $value) Set current instance year to the given value. + * @method CarbonInterface setYears(int $value) Set current instance year to the given value. + * @method CarbonInterface setYear(int $value) Set current instance year to the given value. + * @method CarbonInterface months(int $value) Set current instance month to the given value. + * @method CarbonInterface month(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonths(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonth(int $value) Set current instance month to the given value. + * @method CarbonInterface days(int $value) Set current instance day to the given value. + * @method CarbonInterface day(int $value) Set current instance day to the given value. + * @method CarbonInterface setDays(int $value) Set current instance day to the given value. + * @method CarbonInterface setDay(int $value) Set current instance day to the given value. + * @method CarbonInterface hours(int $value) Set current instance hour to the given value. + * @method CarbonInterface hour(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHours(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHour(int $value) Set current instance hour to the given value. + * @method CarbonInterface minutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface minute(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinute(int $value) Set current instance minute to the given value. + * @method CarbonInterface seconds(int $value) Set current instance second to the given value. + * @method CarbonInterface second(int $value) Set current instance second to the given value. + * @method CarbonInterface setSeconds(int $value) Set current instance second to the given value. + * @method CarbonInterface setSecond(int $value) Set current instance second to the given value. + * @method CarbonInterface millis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface micros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface micro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addYear() Add one year to the instance (using date interval). + * @method CarbonInterface subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subYear() Sub one year to the instance (using date interval). + * @method CarbonInterface addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMonth() Add one month to the instance (using date interval). + * @method CarbonInterface subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMonth() Sub one month to the instance (using date interval). + * @method CarbonInterface addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDay() Add one day to the instance (using date interval). + * @method CarbonInterface subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDay() Sub one day to the instance (using date interval). + * @method CarbonInterface addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addHour() Add one hour to the instance (using date interval). + * @method CarbonInterface subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subHour() Sub one hour to the instance (using date interval). + * @method CarbonInterface addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMinute() Add one minute to the instance (using date interval). + * @method CarbonInterface subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMinute() Sub one minute to the instance (using date interval). + * @method CarbonInterface addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addSecond() Add one second to the instance (using date interval). + * @method CarbonInterface subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subSecond() Sub one second to the instance (using date interval). + * @method CarbonInterface addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonInterface subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonInterface addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addCentury() Add one century to the instance (using date interval). + * @method CarbonInterface subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subCentury() Sub one century to the instance (using date interval). + * @method CarbonInterface addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDecade() Add one decade to the instance (using date interval). + * @method CarbonInterface subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDecade() Sub one decade to the instance (using date interval). + * @method CarbonInterface addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonInterface subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonInterface addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeek() Add one week to the instance (using date interval). + * @method CarbonInterface subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeek() Sub one week to the instance (using date interval). + * @method CarbonInterface addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonInterface subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonInterface addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealSecond() Add one second to the instance (using timestamp). + * @method CarbonInterface subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method CarbonInterface addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMinute() Add one minute to the instance (using timestamp). + * @method CarbonInterface subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method CarbonInterface addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealHour() Add one hour to the instance (using timestamp). + * @method CarbonInterface subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method CarbonInterface addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDay() Add one day to the instance (using timestamp). + * @method CarbonInterface subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method CarbonInterface addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealWeek() Add one week to the instance (using timestamp). + * @method CarbonInterface subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method CarbonInterface addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMonth() Add one month to the instance (using timestamp). + * @method CarbonInterface subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method CarbonInterface addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonInterface subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method CarbonInterface addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealYear() Add one year to the instance (using timestamp). + * @method CarbonInterface subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method CarbonInterface addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDecade() Add one decade to the instance (using timestamp). + * @method CarbonInterface subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method CarbonInterface addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealCentury() Add one century to the instance (using timestamp). + * @method CarbonInterface subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method CarbonInterface addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonInterface subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method CarbonInterface roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonInterface ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * + * + */ +trait Date +{ + use Boundaries; + use Comparison; + use Converter; + use Creator; + use Difference; + use Macro; + use MagicParameter; + use Modifiers; + use Mutability; + use ObjectInitialisation; + use Options; + use Rounding; + use Serialization; + use Test; + use Timestamp; + use Units; + use Week; + + /** + * Names of days of the week. + * + * @var array + */ + protected static $days = [ + // @call isDayOfWeek + CarbonInterface::SUNDAY => 'Sunday', + // @call isDayOfWeek + CarbonInterface::MONDAY => 'Monday', + // @call isDayOfWeek + CarbonInterface::TUESDAY => 'Tuesday', + // @call isDayOfWeek + CarbonInterface::WEDNESDAY => 'Wednesday', + // @call isDayOfWeek + CarbonInterface::THURSDAY => 'Thursday', + // @call isDayOfWeek + CarbonInterface::FRIDAY => 'Friday', + // @call isDayOfWeek + CarbonInterface::SATURDAY => 'Saturday', + ]; + + /** + * Will UTF8 encoding be used to print localized date/time ? + * + * @var bool + */ + protected static $utf8 = false; + + /** + * List of unit and magic methods associated as doc-comments. + * + * @var array + */ + protected static $units = [ + // @call setUnit + // @call addUnit + 'year', + // @call setUnit + // @call addUnit + 'month', + // @call setUnit + // @call addUnit + 'day', + // @call setUnit + // @call addUnit + 'hour', + // @call setUnit + // @call addUnit + 'minute', + // @call setUnit + // @call addUnit + 'second', + // @call setUnit + // @call addUnit + 'milli', + // @call setUnit + // @call addUnit + 'millisecond', + // @call setUnit + // @call addUnit + 'micro', + // @call setUnit + // @call addUnit + 'microsecond', + ]; + + /** + * Creates a DateTimeZone from a string, DateTimeZone or integer offset. + * + * @param DateTimeZone|string|int|null $object original value to get CarbonTimeZone from it. + * @param DateTimeZone|string|int|null $objectDump dump of the object for error messages. + * + * @throws InvalidTimeZoneException + * + * @return CarbonTimeZone|false + */ + protected static function safeCreateDateTimeZone($object, $objectDump = null) + { + return CarbonTimeZone::instance($object, $objectDump); + } + + /** + * Get the TimeZone associated with the Carbon instance (as CarbonTimeZone). + * + * @return CarbonTimeZone + * + * @link https://php.net/manual/en/datetime.gettimezone.php + */ + #[ReturnTypeWillChange] + public function getTimezone() + { + return CarbonTimeZone::instance(parent::getTimezone()); + } + + /** + * List of minimum and maximums for each unit. + * + * @param int $daysInMonth + * + * @return array + */ + protected static function getRangesByUnit(int $daysInMonth = 31): array + { + return [ + // @call roundUnit + 'year' => [1, 9999], + // @call roundUnit + 'month' => [1, static::MONTHS_PER_YEAR], + // @call roundUnit + 'day' => [1, $daysInMonth], + // @call roundUnit + 'hour' => [0, static::HOURS_PER_DAY - 1], + // @call roundUnit + 'minute' => [0, static::MINUTES_PER_HOUR - 1], + // @call roundUnit + 'second' => [0, static::SECONDS_PER_MINUTE - 1], + ]; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + return clone $this; + } + + /** + * @alias copy + * + * Get a copy of the instance. + * + * @return static + */ + public function clone() + { + return clone $this; + } + + /** + * Clone the current instance if it's mutable. + * + * This method is convenient to ensure you don't mutate the initial object + * but avoid to make a useless copy of it if it's already immutable. + * + * @return static + */ + public function avoidMutation(): self + { + if ($this instanceof DateTimeImmutable) { + return $this; + } + + return clone $this; + } + + /** + * Returns a present instance in the same timezone. + * + * @return static + */ + public function nowWithSameTz() + { + return static::now($this->getTimezone()); + } + + /** + * Throws an exception if the given object is not a DateTime and does not implement DateTimeInterface. + * + * @param mixed $date + * @param string|array $other + * + * @throws InvalidTypeException + */ + protected static function expectDateTime($date, $other = []) + { + $message = 'Expected '; + foreach ((array) $other as $expect) { + $message .= "$expect, "; + } + + if (!$date instanceof DateTime && !$date instanceof DateTimeInterface) { + throw new InvalidTypeException( + $message.'DateTime or DateTimeInterface, '. + (\is_object($date) ? \get_class($date) : \gettype($date)).' given' + ); + } + } + + /** + * Return the Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param Carbon|DateTimeInterface|string|null $date + * + * @return static + */ + protected function resolveCarbon($date = null) + { + if (!$date) { + return $this->nowWithSameTz(); + } + + if (\is_string($date)) { + return static::parse($date, $this->getTimezone()); + } + + static::expectDateTime($date, ['null', 'string']); + + return $date instanceof self ? $date : static::instance($date); + } + + /** + * Return the Carbon instance passed through, a now instance in UTC + * if null given or parse the input if string given (using current timezone + * then switching to UTC). + * + * @param Carbon|DateTimeInterface|string|null $date + * + * @return static + */ + protected function resolveUTC($date = null): self + { + if (!$date) { + return static::now('UTC'); + } + + if (\is_string($date)) { + return static::parse($date, $this->getTimezone())->utc(); + } + + static::expectDateTime($date, ['null', 'string']); + + return $date instanceof self ? $date : static::instance($date)->utc(); + } + + /** + * Return the Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|DateTimeInterface|string|null $date + * + * @return static + */ + public function carbonize($date = null) + { + if ($date instanceof DateInterval) { + return $this->avoidMutation()->add($date); + } + + if ($date instanceof DatePeriod || $date instanceof CarbonPeriod) { + $date = $date->getStartDate(); + } + + return $this->resolveCarbon($date); + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// GETTERS AND SETTERS ///////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function get($name) + { + static $formats = [ + // @property int + 'year' => 'Y', + // @property int + 'yearIso' => 'o', + // @property int + // @call isSameUnit + 'month' => 'n', + // @property int + 'day' => 'j', + // @property int + 'hour' => 'G', + // @property int + 'minute' => 'i', + // @property int + 'second' => 's', + // @property int + 'micro' => 'u', + // @property int + 'microsecond' => 'u', + // @property-read int 0 (for Sunday) through 6 (for Saturday) + 'dayOfWeek' => 'w', + // @property-read int 1 (for Monday) through 7 (for Sunday) + 'dayOfWeekIso' => 'N', + // @property-read int ISO-8601 week number of year, weeks starting on Monday + 'weekOfYear' => 'W', + // @property-read int number of days in the given month + 'daysInMonth' => 't', + // @property int|float|string seconds since the Unix Epoch + 'timestamp' => 'U', + // @property-read string "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + 'latinMeridiem' => 'a', + // @property-read string "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + 'latinUpperMeridiem' => 'A', + // @property string the day of week in English + 'englishDayOfWeek' => 'l', + // @property string the abbreviated day of week in English + 'shortEnglishDayOfWeek' => 'D', + // @property string the month in English + 'englishMonth' => 'F', + // @property string the abbreviated month in English + 'shortEnglishMonth' => 'M', + // @property string the day of week in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('MMM') instead. + // since: 2.55.0 + 'localeDayOfWeek' => '%A', + // @property string the abbreviated day of week in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('dddd') instead. + // since: 2.55.0 + 'shortLocaleDayOfWeek' => '%a', + // @property string the month in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('ddd') instead. + // since: 2.55.0 + 'localeMonth' => '%B', + // @property string the abbreviated month in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('MMMM') instead. + // since: 2.55.0 + 'shortLocaleMonth' => '%b', + // @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + 'timezoneAbbreviatedName' => 'T', + // @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + 'tzAbbrName' => 'T', + ]; + + switch (true) { + case isset($formats[$name]): + $format = $formats[$name]; + $method = str_starts_with($format, '%') ? 'formatLocalized' : 'rawFormat'; + $value = $this->$method($format); + + return is_numeric($value) ? (int) $value : $value; + + // @property-read string long name of weekday translated according to Carbon locale, in english if no translation available for current language + case $name === 'dayName': + return $this->getTranslatedDayName(); + // @property-read string short name of weekday translated according to Carbon locale, in english if no translation available for current language + case $name === 'shortDayName': + return $this->getTranslatedShortDayName(); + // @property-read string very short name of weekday translated according to Carbon locale, in english if no translation available for current language + case $name === 'minDayName': + return $this->getTranslatedMinDayName(); + // @property-read string long name of month translated according to Carbon locale, in english if no translation available for current language + case $name === 'monthName': + return $this->getTranslatedMonthName(); + // @property-read string short name of month translated according to Carbon locale, in english if no translation available for current language + case $name === 'shortMonthName': + return $this->getTranslatedShortMonthName(); + // @property-read string lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + case $name === 'meridiem': + return $this->meridiem(true); + // @property-read string uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + case $name === 'upperMeridiem': + return $this->meridiem(); + // @property-read int current hour from 1 to 24 + case $name === 'noZeroHour': + return $this->hour ?: 24; + // @property int + case $name === 'milliseconds': + // @property int + case $name === 'millisecond': + // @property int + case $name === 'milli': + return (int) floor(((int) $this->rawFormat('u')) / 1000); + + // @property int 1 through 53 + case $name === 'week': + return (int) $this->week(); + + // @property int 1 through 53 + case $name === 'isoWeek': + return (int) $this->isoWeek(); + + // @property int year according to week format + case $name === 'weekYear': + return (int) $this->weekYear(); + + // @property int year according to ISO week format + case $name === 'isoWeekYear': + return (int) $this->isoWeekYear(); + + // @property-read int 51 through 53 + case $name === 'weeksInYear': + return $this->weeksInYear(); + + // @property-read int 51 through 53 + case $name === 'isoWeeksInYear': + return $this->isoWeeksInYear(); + + // @property-read int 1 through 5 + case $name === 'weekOfMonth': + return (int) ceil($this->day / static::DAYS_PER_WEEK); + + // @property-read int 1 through 5 + case $name === 'weekNumberInMonth': + return (int) ceil(($this->day + $this->avoidMutation()->startOfMonth()->dayOfWeekIso - 1) / static::DAYS_PER_WEEK); + + // @property-read int 0 through 6 + case $name === 'firstWeekDay': + return $this->localTranslator ? ($this->getTranslationMessage('first_day_of_week') ?? 0) : static::getWeekStartsAt(); + + // @property-read int 0 through 6 + case $name === 'lastWeekDay': + return $this->localTranslator ? (($this->getTranslationMessage('first_day_of_week') ?? 0) + static::DAYS_PER_WEEK - 1) % static::DAYS_PER_WEEK : static::getWeekEndsAt(); + + // @property int 1 through 366 + case $name === 'dayOfYear': + return 1 + (int) ($this->rawFormat('z')); + + // @property-read int 365 or 366 + case $name === 'daysInYear': + return $this->isLeapYear() ? 366 : 365; + + // @property int does a diffInYears() with default parameters + case $name === 'age': + return $this->diffInYears(); + + // @property-read int the quarter of this instance, 1 - 4 + // @call isSameUnit + case $name === 'quarter': + return (int) ceil($this->month / static::MONTHS_PER_QUARTER); + + // @property-read int the decade of this instance + // @call isSameUnit + case $name === 'decade': + return (int) ceil($this->year / static::YEARS_PER_DECADE); + + // @property-read int the century of this instance + // @call isSameUnit + case $name === 'century': + $factor = 1; + $year = $this->year; + if ($year < 0) { + $year = -$year; + $factor = -1; + } + + return (int) ($factor * ceil($year / static::YEARS_PER_CENTURY)); + + // @property-read int the millennium of this instance + // @call isSameUnit + case $name === 'millennium': + $factor = 1; + $year = $this->year; + if ($year < 0) { + $year = -$year; + $factor = -1; + } + + return (int) ($factor * ceil($year / static::YEARS_PER_MILLENNIUM)); + + // @property int the timezone offset in seconds from UTC + case $name === 'offset': + return $this->getOffset(); + + // @property int the timezone offset in minutes from UTC + case $name === 'offsetMinutes': + return $this->getOffset() / static::SECONDS_PER_MINUTE; + + // @property int the timezone offset in hours from UTC + case $name === 'offsetHours': + return $this->getOffset() / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR; + + // @property-read bool daylight savings time indicator, true if DST, false otherwise + case $name === 'dst': + return $this->rawFormat('I') === '1'; + + // @property-read bool checks if the timezone is local, true if local, false otherwise + case $name === 'local': + return $this->getOffset() === $this->avoidMutation()->setTimezone(date_default_timezone_get())->getOffset(); + + // @property-read bool checks if the timezone is UTC, true if UTC, false otherwise + case $name === 'utc': + return $this->getOffset() === 0; + + // @property CarbonTimeZone $timezone the current timezone + // @property CarbonTimeZone $tz alias of $timezone + case $name === 'timezone' || $name === 'tz': + return CarbonTimeZone::instance($this->getTimezone()); + + // @property-read string $timezoneName the current timezone name + // @property-read string $tzName alias of $timezoneName + case $name === 'timezoneName' || $name === 'tzName': + return $this->getTimezone()->getName(); + + // @property-read string locale of the current instance + case $name === 'locale': + return $this->getTranslatorLocale(); + + default: + $macro = $this->getLocalMacro('get'.ucfirst($name)); + + if ($macro) { + return $this->executeCallableWithContext($macro); + } + + throw new UnknownGetterException($name); + } + } + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset($name) + { + try { + $this->__get($name); + } catch (UnknownGetterException | ReflectionException $e) { + return false; + } + + return true; + } + + /** + * Set a part of the Carbon object + * + * @param string $name + * @param string|int|DateTimeZone $value + * + * @throws UnknownSetterException|ReflectionException + * + * @return void + */ + public function __set($name, $value) + { + if ($this->constructedObjectId === spl_object_hash($this)) { + $this->set($name, $value); + + return; + } + + $this->$name = $value; + } + + /** + * Set a part of the Carbon object + * + * @param string|array $name + * @param string|int|DateTimeZone $value + * + * @throws ImmutableException|UnknownSetterException + * + * @return $this + */ + public function set($name, $value = null) + { + if ($this->isImmutable()) { + throw new ImmutableException(sprintf('%s class', static::class)); + } + + if (\is_array($name)) { + foreach ($name as $key => $value) { + $this->set($key, $value); + } + + return $this; + } + + switch ($name) { + case 'milliseconds': + case 'millisecond': + case 'milli': + case 'microseconds': + case 'microsecond': + case 'micro': + if (str_starts_with($name, 'milli')) { + $value *= 1000; + } + + while ($value < 0) { + $this->subSecond(); + $value += static::MICROSECONDS_PER_SECOND; + } + + while ($value >= static::MICROSECONDS_PER_SECOND) { + $this->addSecond(); + $value -= static::MICROSECONDS_PER_SECOND; + } + + $this->modify($this->rawFormat('H:i:s.').str_pad((string) round($value), 6, '0', STR_PAD_LEFT)); + + break; + + case 'year': + case 'month': + case 'day': + case 'hour': + case 'minute': + case 'second': + [$year, $month, $day, $hour, $minute, $second] = array_map('intval', explode('-', $this->rawFormat('Y-n-j-G-i-s'))); + $$name = $value; + $this->setDateTime($year, $month, $day, $hour, $minute, $second); + + break; + + case 'week': + $this->week($value); + + break; + + case 'isoWeek': + $this->isoWeek($value); + + break; + + case 'weekYear': + $this->weekYear($value); + + break; + + case 'isoWeekYear': + $this->isoWeekYear($value); + + break; + + case 'dayOfYear': + $this->addDays($value - $this->dayOfYear); + + break; + + case 'timestamp': + $this->setTimestamp($value); + + break; + + case 'offset': + $this->setTimezone(static::safeCreateDateTimeZone($value / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR)); + + break; + + case 'offsetMinutes': + $this->setTimezone(static::safeCreateDateTimeZone($value / static::MINUTES_PER_HOUR)); + + break; + + case 'offsetHours': + $this->setTimezone(static::safeCreateDateTimeZone($value)); + + break; + + case 'timezone': + case 'tz': + $this->setTimezone($value); + + break; + + default: + $macro = $this->getLocalMacro('set'.ucfirst($name)); + + if ($macro) { + $this->executeCallableWithContext($macro, $value); + + break; + } + + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new UnknownSetterException($name); + } + + $this->$name = $value; + } + + return $this; + } + + protected function getTranslatedFormByRegExp($baseKey, $keySuffix, $context, $subKey, $defaultValue) + { + $key = $baseKey.$keySuffix; + $standaloneKey = "{$key}_standalone"; + $baseTranslation = $this->getTranslationMessage($key); + + if ($baseTranslation instanceof Closure) { + return $baseTranslation($this, $context, $subKey) ?: $defaultValue; + } + + if ( + $this->getTranslationMessage("$standaloneKey.$subKey") && + (!$context || (($regExp = $this->getTranslationMessage("{$baseKey}_regexp")) && !preg_match($regExp, $context))) + ) { + $key = $standaloneKey; + } + + return $this->getTranslationMessage("$key.$subKey", null, $defaultValue); + } + + /** + * Get the translation of the current week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "", "_short" or "_min" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedDayName($context = null, $keySuffix = '', $defaultValue = null) + { + return $this->getTranslatedFormByRegExp('weekdays', $keySuffix, $context, $this->dayOfWeek, $defaultValue ?: $this->englishDayOfWeek); + } + + /** + * Get the translation of the current short week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortDayName($context = null) + { + return $this->getTranslatedDayName($context, '_short', $this->shortEnglishDayOfWeek); + } + + /** + * Get the translation of the current abbreviated week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedMinDayName($context = null) + { + return $this->getTranslatedDayName($context, '_min', $this->shortEnglishDayOfWeek); + } + + /** + * Get the translation of the current month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "" or "_short" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedMonthName($context = null, $keySuffix = '', $defaultValue = null) + { + return $this->getTranslatedFormByRegExp('months', $keySuffix, $context, $this->month - 1, $defaultValue ?: $this->englishMonth); + } + + /** + * Get the translation of the current short month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortMonthName($context = null) + { + return $this->getTranslatedMonthName($context, '_short', $this->shortEnglishMonth); + } + + /** + * Get/set the day of year. + * + * @param int|null $value new value for day of year if using as setter. + * + * @return static|int + */ + public function dayOfYear($value = null) + { + $dayOfYear = $this->dayOfYear; + + return $value === null ? $dayOfYear : $this->addDays($value - $dayOfYear); + } + + /** + * Get/set the weekday from 0 (Sunday) to 6 (Saturday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function weekday($value = null) + { + if ($value === null) { + return $this->dayOfWeek; + } + + $firstDay = (int) ($this->getTranslationMessage('first_day_of_week') ?? 0); + $dayOfWeek = ($this->dayOfWeek + 7 - $firstDay) % 7; + + return $this->addDays((($value + 7 - $firstDay) % 7) - $dayOfWeek); + } + + /** + * Get/set the ISO weekday from 1 (Monday) to 7 (Sunday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function isoWeekday($value = null) + { + $dayOfWeekIso = $this->dayOfWeekIso; + + return $value === null ? $dayOfWeekIso : $this->addDays($value - $dayOfWeekIso); + } + + /** + * Return the number of days since the start of the week (using the current locale or the first parameter + * if explicitly given). + * + * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) + * + * @return int + */ + public function getDaysFromStartOfWeek(int $weekStartsAt = null): int + { + $firstDay = (int) ($weekStartsAt ?? $this->getTranslationMessage('first_day_of_week') ?? 0); + + return ($this->dayOfWeek + 7 - $firstDay) % 7; + } + + /** + * Set the day (keeping the current time) to the start of the week + the number of days passed as the first + * parameter. First day of week is driven by the locale unless explicitly set with the second parameter. + * + * @param int $numberOfDays number of days to add after the start of the current week + * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) + * + * @return static + */ + public function setDaysFromStartOfWeek(int $numberOfDays, int $weekStartsAt = null) + { + return $this->addDays($numberOfDays - $this->getDaysFromStartOfWeek($weekStartsAt)); + } + + /** + * Set any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value new value for the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function setUnitNoOverflow($valueUnit, $value, $overflowUnit) + { + try { + $original = $this->avoidMutation(); + /** @var static $date */ + $date = $this->$valueUnit($value); + $end = $original->avoidMutation()->endOf($overflowUnit); + $start = $original->avoidMutation()->startOf($overflowUnit); + if ($date < $start) { + $date = $date->setDateTimeFrom($start); + } elseif ($date > $end) { + $date = $date->setDateTimeFrom($end); + } + + return $date; + } catch (BadMethodCallException | ReflectionException $exception) { + throw new UnknownUnitException($valueUnit, 0, $exception); + } + } + + /** + * Add any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to add to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function addUnitNoOverflow($valueUnit, $value, $overflowUnit) + { + return $this->setUnitNoOverflow($valueUnit, $this->$valueUnit + $value, $overflowUnit); + } + + /** + * Subtract any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to subtract to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function subUnitNoOverflow($valueUnit, $value, $overflowUnit) + { + return $this->setUnitNoOverflow($valueUnit, $this->$valueUnit - $value, $overflowUnit); + } + + /** + * Returns the minutes offset to UTC if no arguments passed, else set the timezone with given minutes shift passed. + * + * @param int|null $minuteOffset + * + * @return int|static + */ + public function utcOffset(int $minuteOffset = null) + { + if (\func_num_args() < 1) { + return $this->offsetMinutes; + } + + return $this->setTimezone(CarbonTimeZone::createFromMinuteOffset($minuteOffset)); + } + + /** + * Set the date with gregorian year, month and day numbers. + * + * @see https://php.net/manual/en/datetime.setdate.php + * + * @param int $year + * @param int $month + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setDate($year, $month, $day) + { + return parent::setDate((int) $year, (int) $month, (int) $day); + } + + /** + * Set a date according to the ISO 8601 standard - using weeks and day offsets rather than specific dates. + * + * @see https://php.net/manual/en/datetime.setisodate.php + * + * @param int $year + * @param int $week + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setISODate($year, $week, $day = 1) + { + return parent::setISODate((int) $year, (int) $week, (int) $day); + } + + /** + * Set the date and time all together. + * + * @param int $year + * @param int $month + * @param int $day + * @param int $hour + * @param int $minute + * @param int $second + * @param int $microseconds + * + * @return static + */ + public function setDateTime($year, $month, $day, $hour, $minute, $second = 0, $microseconds = 0) + { + return $this->setDate($year, $month, $day)->setTime((int) $hour, (int) $minute, (int) $second, (int) $microseconds); + } + + /** + * Resets the current time of the DateTime object to a different time. + * + * @see https://php.net/manual/en/datetime.settime.php + * + * @param int $hour + * @param int $minute + * @param int $second + * @param int $microseconds + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTime($hour, $minute, $second = 0, $microseconds = 0) + { + return parent::setTime((int) $hour, (int) $minute, (int) $second, (int) $microseconds); + } + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimestamp($unixTimestamp) + { + [$timestamp, $microseconds] = self::getIntegerAndDecimalParts($unixTimestamp); + + return parent::setTimestamp((int) $timestamp)->setMicroseconds((int) $microseconds); + } + + /** + * Set the time by time string. + * + * @param string $time + * + * @return static + */ + public function setTimeFromTimeString($time) + { + if (!str_contains($time, ':')) { + $time .= ':0'; + } + + return $this->modify($time); + } + + /** + * @alias setTimezone + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function timezone($value) + { + return $this->setTimezone($value); + } + + /** + * Set the timezone or returns the timezone name if no arguments passed. + * + * @param DateTimeZone|string $value + * + * @return static|string + */ + public function tz($value = null) + { + if (\func_num_args() < 1) { + return $this->tzName; + } + + return $this->setTimezone($value); + } + + /** + * Set the instance's timezone from a string or object. + * + * @param DateTimeZone|string $value + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimezone($value) + { + $tz = static::safeCreateDateTimeZone($value); + + if ($tz === false && !self::isStrictModeEnabled()) { + $tz = new CarbonTimeZone(); + } + + return parent::setTimezone($tz); + } + + /** + * Set the instance's timezone from a string or object and add/subtract the offset difference. + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function shiftTimezone($value) + { + $dateTimeString = $this->format('Y-m-d H:i:s.u'); + + return $this + ->setTimezone($value) + ->modify($dateTimeString); + } + + /** + * Set the instance's timezone to UTC. + * + * @return static + */ + public function utc() + { + return $this->setTimezone('UTC'); + } + + /** + * Set the year, month, and date for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setDateFrom($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->setDate($date->year, $date->month, $date->day); + } + + /** + * Set the hour, minute, second and microseconds for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setTimeFrom($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->setTime($date->hour, $date->minute, $date->second, $date->microsecond); + } + + /** + * Set the date and time for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date + * + * @return static + */ + public function setDateTimeFrom($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->modify($date->rawFormat('Y-m-d H:i:s.u')); + } + + /** + * Get the days of the week + * + * @return array + */ + public static function getDays() + { + return static::$days; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// WEEK SPECIAL DAYS ///////////////////////// + /////////////////////////////////////////////////////////////////// + + private static function getFirstDayOfWeek(): int + { + return (int) static::getTranslationMessageWith( + static::getTranslator(), + 'first_day_of_week' + ); + } + + /** + * Get the first day of week + * + * @return int + */ + public static function getWeekStartsAt() + { + if (static::$weekStartsAt === static::WEEK_DAY_AUTO) { + return self::getFirstDayOfWeek(); + } + + return static::$weekStartsAt; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * + * Set the first day of week + * + * @param int|string $day week start day (or 'auto' to get the first day of week from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekStartsAt($day) + { + static::$weekStartsAt = $day === static::WEEK_DAY_AUTO ? $day : max(0, (7 + $day) % 7); + } + + /** + * Get the last day of week + * + * @return int + */ + public static function getWeekEndsAt() + { + if (static::$weekStartsAt === static::WEEK_DAY_AUTO) { + return (int) (static::DAYS_PER_WEEK - 1 + self::getFirstDayOfWeek()) % static::DAYS_PER_WEEK; + } + + return static::$weekEndsAt; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * + * Set the last day of week + * + * @param int|string $day week end day (or 'auto' to get the day before the first day of week + * from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekEndsAt($day) + { + static::$weekEndsAt = $day === static::WEEK_DAY_AUTO ? $day : max(0, (7 + $day) % 7); + } + + /** + * Get weekend days + * + * @return array + */ + public static function getWeekendDays() + { + return static::$weekendDays; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * + * Set weekend days + * + * @param array $days + * + * @return void + */ + public static function setWeekendDays($days) + { + static::$weekendDays = $days; + } + + /** + * Determine if a time string will produce a relative date. + * + * @param string $time + * + * @return bool true if time match a relative date, false if absolute or invalid time string + */ + public static function hasRelativeKeywords($time) + { + if (!$time || strtotime($time) === false) { + return false; + } + + $date1 = new DateTime('2000-01-01T00:00:00Z'); + $date1->modify($time); + $date2 = new DateTime('2001-12-25T00:00:00Z'); + $date2->modify($time); + + return $date1 != $date2; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// STRING FORMATTING ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * + * Set if UTF8 will be used for localized date/time. + * + * @param bool $utf8 + */ + public static function setUtf8($utf8) + { + static::$utf8 = $utf8; + } + + /** + * Format the instance with the current locale. You can set the current + * locale using setlocale() https://php.net/setlocale. + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat() instead. + * Deprecated since 2.55.0 + * + * @param string $format + * + * @return string + */ + public function formatLocalized($format) + { + // Check for Windows to find and replace the %e modifier correctly. + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $format = preg_replace('#(?toDateTimeString()); + $formatted = ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) + ? strftime($format, $time) + : @strftime($format, $time); + + return static::$utf8 + ? ( + \function_exists('mb_convert_encoding') + ? mb_convert_encoding($formatted, 'UTF-8', mb_list_encodings()) + : utf8_encode($formatted) + ) + : $formatted; + } + + /** + * Returns list of locale formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getIsoFormats($locale = null) + { + return [ + 'LT' => $this->getTranslationMessage('formats.LT', $locale, 'h:mm A'), + 'LTS' => $this->getTranslationMessage('formats.LTS', $locale, 'h:mm:ss A'), + 'L' => $this->getTranslationMessage('formats.L', $locale, 'MM/DD/YYYY'), + 'LL' => $this->getTranslationMessage('formats.LL', $locale, 'MMMM D, YYYY'), + 'LLL' => $this->getTranslationMessage('formats.LLL', $locale, 'MMMM D, YYYY h:mm A'), + 'LLLL' => $this->getTranslationMessage('formats.LLLL', $locale, 'dddd, MMMM D, YYYY h:mm A'), + 'l' => $this->getTranslationMessage('formats.l', $locale), + 'll' => $this->getTranslationMessage('formats.ll', $locale), + 'lll' => $this->getTranslationMessage('formats.lll', $locale), + 'llll' => $this->getTranslationMessage('formats.llll', $locale), + ]; + } + + /** + * Returns list of calendar formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getCalendarFormats($locale = null) + { + return [ + 'sameDay' => $this->getTranslationMessage('calendar.sameDay', $locale, '[Today at] LT'), + 'nextDay' => $this->getTranslationMessage('calendar.nextDay', $locale, '[Tomorrow at] LT'), + 'nextWeek' => $this->getTranslationMessage('calendar.nextWeek', $locale, 'dddd [at] LT'), + 'lastDay' => $this->getTranslationMessage('calendar.lastDay', $locale, '[Yesterday at] LT'), + 'lastWeek' => $this->getTranslationMessage('calendar.lastWeek', $locale, '[Last] dddd [at] LT'), + 'sameElse' => $this->getTranslationMessage('calendar.sameElse', $locale, 'L'), + ]; + } + + /** + * Returns list of locale units for ISO formatting. + * + * @return array + */ + public static function getIsoUnits() + { + static $units = null; + + if ($units === null) { + $units = [ + 'OD' => ['getAltNumber', ['day']], + 'OM' => ['getAltNumber', ['month']], + 'OY' => ['getAltNumber', ['year']], + 'OH' => ['getAltNumber', ['hour']], + 'Oh' => ['getAltNumber', ['h']], + 'Om' => ['getAltNumber', ['minute']], + 'Os' => ['getAltNumber', ['second']], + 'D' => 'day', + 'DD' => ['rawFormat', ['d']], + 'Do' => ['ordinal', ['day', 'D']], + 'd' => 'dayOfWeek', + 'dd' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedMinDayName($originalFormat); + }, + 'ddd' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedShortDayName($originalFormat); + }, + 'dddd' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedDayName($originalFormat); + }, + 'DDD' => 'dayOfYear', + 'DDDD' => ['getPaddedUnit', ['dayOfYear', 3]], + 'DDDo' => ['ordinal', ['dayOfYear', 'DDD']], + 'e' => ['weekday', []], + 'E' => 'dayOfWeekIso', + 'H' => ['rawFormat', ['G']], + 'HH' => ['rawFormat', ['H']], + 'h' => ['rawFormat', ['g']], + 'hh' => ['rawFormat', ['h']], + 'k' => 'noZeroHour', + 'kk' => ['getPaddedUnit', ['noZeroHour']], + 'hmm' => ['rawFormat', ['gi']], + 'hmmss' => ['rawFormat', ['gis']], + 'Hmm' => ['rawFormat', ['Gi']], + 'Hmmss' => ['rawFormat', ['Gis']], + 'm' => 'minute', + 'mm' => ['rawFormat', ['i']], + 'a' => 'meridiem', + 'A' => 'upperMeridiem', + 's' => 'second', + 'ss' => ['getPaddedUnit', ['second']], + 'S' => function (CarbonInterface $date) { + return (string) floor($date->micro / 100000); + }, + 'SS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 10000), 2, '0', STR_PAD_LEFT); + }, + 'SSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 1000), 3, '0', STR_PAD_LEFT); + }, + 'SSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 100), 4, '0', STR_PAD_LEFT); + }, + 'SSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 10), 5, '0', STR_PAD_LEFT); + }, + 'SSSSSS' => ['getPaddedUnit', ['micro', 6]], + 'SSSSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro * 10), 7, '0', STR_PAD_LEFT); + }, + 'SSSSSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro * 100), 8, '0', STR_PAD_LEFT); + }, + 'SSSSSSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro * 1000), 9, '0', STR_PAD_LEFT); + }, + 'M' => 'month', + 'MM' => ['rawFormat', ['m']], + 'MMM' => function (CarbonInterface $date, $originalFormat = null) { + $month = $date->getTranslatedShortMonthName($originalFormat); + $suffix = $date->getTranslationMessage('mmm_suffix'); + if ($suffix && $month !== $date->monthName) { + $month .= $suffix; + } + + return $month; + }, + 'MMMM' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedMonthName($originalFormat); + }, + 'Mo' => ['ordinal', ['month', 'M']], + 'Q' => 'quarter', + 'Qo' => ['ordinal', ['quarter', 'M']], + 'G' => 'isoWeekYear', + 'GG' => ['getPaddedUnit', ['isoWeekYear']], + 'GGG' => ['getPaddedUnit', ['isoWeekYear', 3]], + 'GGGG' => ['getPaddedUnit', ['isoWeekYear', 4]], + 'GGGGG' => ['getPaddedUnit', ['isoWeekYear', 5]], + 'g' => 'weekYear', + 'gg' => ['getPaddedUnit', ['weekYear']], + 'ggg' => ['getPaddedUnit', ['weekYear', 3]], + 'gggg' => ['getPaddedUnit', ['weekYear', 4]], + 'ggggg' => ['getPaddedUnit', ['weekYear', 5]], + 'W' => 'isoWeek', + 'WW' => ['getPaddedUnit', ['isoWeek']], + 'Wo' => ['ordinal', ['isoWeek', 'W']], + 'w' => 'week', + 'ww' => ['getPaddedUnit', ['week']], + 'wo' => ['ordinal', ['week', 'w']], + 'x' => ['valueOf', []], + 'X' => 'timestamp', + 'Y' => 'year', + 'YY' => ['rawFormat', ['y']], + 'YYYY' => ['getPaddedUnit', ['year', 4]], + 'YYYYY' => ['getPaddedUnit', ['year', 5]], + 'YYYYYY' => function (CarbonInterface $date) { + return ($date->year < 0 ? '' : '+').$date->getPaddedUnit('year', 6); + }, + 'z' => ['rawFormat', ['T']], + 'zz' => 'tzName', + 'Z' => ['getOffsetString', []], + 'ZZ' => ['getOffsetString', ['']], + ]; + } + + return $units; + } + + /** + * Returns a unit of the instance padded with 0 by default or any other string if specified. + * + * @param string $unit Carbon unit name + * @param int $length Length of the output (2 by default) + * @param string $padString String to use for padding ("0" by default) + * @param int $padType Side(s) to pad (STR_PAD_LEFT by default) + * + * @return string + */ + public function getPaddedUnit($unit, $length = 2, $padString = '0', $padType = STR_PAD_LEFT) + { + return ($this->$unit < 0 ? '-' : '').str_pad((string) abs($this->$unit), $length, $padString, $padType); + } + + /** + * Return a property with its ordinal. + * + * @param string $key + * @param string|null $period + * + * @return string + */ + public function ordinal(string $key, ?string $period = null): string + { + $number = $this->$key; + $result = $this->translate('ordinal', [ + ':number' => $number, + ':period' => (string) $period, + ]); + + return (string) ($result === 'ordinal' ? $number : $result); + } + + /** + * Return the meridiem of the current time in the current locale. + * + * @param bool $isLower if true, returns lowercase variant if available in the current locale. + * + * @return string + */ + public function meridiem(bool $isLower = false): string + { + $hour = $this->hour; + $index = $hour < 12 ? 0 : 1; + + if ($isLower) { + $key = 'meridiem.'.($index + 2); + $result = $this->translate($key); + + if ($result !== $key) { + return $result; + } + } + + $key = "meridiem.$index"; + $result = $this->translate($key); + if ($result === $key) { + $result = $this->translate('meridiem', [ + ':hour' => $this->hour, + ':minute' => $this->minute, + ':isLower' => $isLower, + ]); + + if ($result === 'meridiem') { + return $isLower ? $this->latinMeridiem : $this->latinUpperMeridiem; + } + } elseif ($isLower) { + $result = mb_strtolower($result); + } + + return $result; + } + + /** + * Returns the alternative number for a given date property if available in the current locale. + * + * @param string $key date property + * + * @return string + */ + public function getAltNumber(string $key): string + { + return $this->translateNumber(\strlen($key) > 1 ? $this->$key : $this->rawFormat('h')); + } + + /** + * Format in the current language using ISO replacement patterns. + * + * @param string $format + * @param string|null $originalFormat provide context if a chunk has been passed alone + * + * @return string + */ + public function isoFormat(string $format, ?string $originalFormat = null): string + { + $result = ''; + $length = mb_strlen($format); + $originalFormat = $originalFormat ?: $format; + $inEscaped = false; + $formats = null; + $units = null; + + for ($i = 0; $i < $length; $i++) { + $char = mb_substr($format, $i, 1); + + if ($char === '\\') { + $result .= mb_substr($format, ++$i, 1); + + continue; + } + + if ($char === '[' && !$inEscaped) { + $inEscaped = true; + + continue; + } + + if ($char === ']' && $inEscaped) { + $inEscaped = false; + + continue; + } + + if ($inEscaped) { + $result .= $char; + + continue; + } + + $input = mb_substr($format, $i); + + if (preg_match('/^(LTS|LT|l{1,4}|L{1,4})/', $input, $match)) { + if ($formats === null) { + $formats = $this->getIsoFormats(); + } + + $code = $match[0]; + $sequence = $formats[$code] ?? preg_replace_callback( + '/MMMM|MM|DD|dddd/', + function ($code) { + return mb_substr($code[0], 1); + }, + $formats[strtoupper($code)] ?? '' + ); + $rest = mb_substr($format, $i + mb_strlen($code)); + $format = mb_substr($format, 0, $i).$sequence.$rest; + $length = mb_strlen($format); + $input = $sequence.$rest; + } + + if (preg_match('/^'.CarbonInterface::ISO_FORMAT_REGEXP.'/', $input, $match)) { + $code = $match[0]; + + if ($units === null) { + $units = static::getIsoUnits(); + } + + $sequence = $units[$code] ?? ''; + + if ($sequence instanceof Closure) { + $sequence = $sequence($this, $originalFormat); + } elseif (\is_array($sequence)) { + try { + $sequence = $this->{$sequence[0]}(...$sequence[1]); + } catch (ReflectionException | InvalidArgumentException | BadMethodCallException $e) { + $sequence = ''; + } + } elseif (\is_string($sequence)) { + $sequence = $this->$sequence ?? $code; + } + + $format = mb_substr($format, 0, $i).$sequence.mb_substr($format, $i + mb_strlen($code)); + $i += mb_strlen((string) $sequence) - 1; + $length = mb_strlen($format); + $char = $sequence; + } + + $result .= $char; + } + + return $result; + } + + /** + * List of replacements from date() format to isoFormat(). + * + * @return array + */ + public static function getFormatsToIsoReplacements() + { + static $replacements = null; + + if ($replacements === null) { + $replacements = [ + 'd' => true, + 'D' => 'ddd', + 'j' => true, + 'l' => 'dddd', + 'N' => true, + 'S' => function ($date) { + $day = $date->rawFormat('j'); + + return str_replace((string) $day, '', $date->isoFormat('Do')); + }, + 'w' => true, + 'z' => true, + 'W' => true, + 'F' => 'MMMM', + 'm' => true, + 'M' => 'MMM', + 'n' => true, + 't' => true, + 'L' => true, + 'o' => true, + 'Y' => true, + 'y' => true, + 'a' => 'a', + 'A' => 'A', + 'B' => true, + 'g' => true, + 'G' => true, + 'h' => true, + 'H' => true, + 'i' => true, + 's' => true, + 'u' => true, + 'v' => true, + 'E' => true, + 'I' => true, + 'O' => true, + 'P' => true, + 'Z' => true, + 'c' => true, + 'r' => true, + 'U' => true, + 'T' => true, + ]; + } + + return $replacements; + } + + /** + * Format as ->format() do (using date replacements patterns from https://php.net/manual/en/function.date.php) + * but translate words whenever possible (months, day names, etc.) using the current locale. + * + * @param string $format + * + * @return string + */ + public function translatedFormat(string $format): string + { + $replacements = static::getFormatsToIsoReplacements(); + $context = ''; + $isoFormat = ''; + $length = mb_strlen($format); + + for ($i = 0; $i < $length; $i++) { + $char = mb_substr($format, $i, 1); + + if ($char === '\\') { + $replacement = mb_substr($format, $i, 2); + $isoFormat .= $replacement; + $i++; + + continue; + } + + if (!isset($replacements[$char])) { + $replacement = preg_match('/^[A-Za-z]$/', $char) ? "\\$char" : $char; + $isoFormat .= $replacement; + $context .= $replacement; + + continue; + } + + $replacement = $replacements[$char]; + + if ($replacement === true) { + static $contextReplacements = null; + + if ($contextReplacements === null) { + $contextReplacements = [ + 'm' => 'MM', + 'd' => 'DD', + 't' => 'D', + 'j' => 'D', + 'N' => 'e', + 'w' => 'e', + 'n' => 'M', + 'o' => 'YYYY', + 'Y' => 'YYYY', + 'y' => 'YY', + 'g' => 'h', + 'G' => 'H', + 'h' => 'hh', + 'H' => 'HH', + 'i' => 'mm', + 's' => 'ss', + ]; + } + + $isoFormat .= '['.$this->rawFormat($char).']'; + $context .= $contextReplacements[$char] ?? ' '; + + continue; + } + + if ($replacement instanceof Closure) { + $replacement = '['.$replacement($this).']'; + $isoFormat .= $replacement; + $context .= $replacement; + + continue; + } + + $isoFormat .= $replacement; + $context .= $replacement; + } + + return $this->isoFormat($isoFormat, $context); + } + + /** + * Returns the offset hour and minute formatted with +/- and a given separator (":" by default). + * For example, if the time zone is 9 hours 30 minutes, you'll get "+09:30", with "@@" as first + * argument, "+09@@30", with "" as first argument, "+0930". Negative offset will return something + * like "-12:00". + * + * @param string $separator string to place between hours and minutes (":" by default) + * + * @return string + */ + public function getOffsetString($separator = ':') + { + $second = $this->getOffset(); + $symbol = $second < 0 ? '-' : '+'; + $minute = abs($second) / static::SECONDS_PER_MINUTE; + $hour = str_pad((string) floor($minute / static::MINUTES_PER_HOUR), 2, '0', STR_PAD_LEFT); + $minute = str_pad((string) (((int) $minute) % static::MINUTES_PER_HOUR), 2, '0', STR_PAD_LEFT); + + return "$symbol$hour$separator$minute"; + } + + protected static function executeStaticCallable($macro, ...$parameters) + { + return static::bindMacroContext(null, function () use (&$macro, &$parameters) { + if ($macro instanceof Closure) { + $boundMacro = @Closure::bind($macro, null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + }); + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws BadMethodCallException + * + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + if (!static::hasMacro($method)) { + foreach (static::getGenericMacros() as $callback) { + try { + return static::executeStaticCallable($callback, $method, ...$parameters); + } catch (BadMethodCallException $exception) { + continue; + } + } + if (static::isStrictModeEnabled()) { + throw new UnknownMethodException(sprintf('%s::%s', static::class, $method)); + } + + return null; + } + + return static::executeStaticCallable(static::$globalMacros[$method], ...$parameters); + } + + /** + * Set specified unit to new given value. + * + * @param string $unit year, month, day, hour, minute, second or microsecond + * @param int $value new value for given unit + * + * @return static + */ + public function setUnit($unit, $value = null) + { + $unit = static::singularUnit($unit); + $dateUnits = ['year', 'month', 'day']; + if (\in_array($unit, $dateUnits)) { + return $this->setDate(...array_map(function ($name) use ($unit, $value) { + return (int) ($name === $unit ? $value : $this->$name); + }, $dateUnits)); + } + + $units = ['hour', 'minute', 'second', 'micro']; + if ($unit === 'millisecond' || $unit === 'milli') { + $value *= 1000; + $unit = 'micro'; + } elseif ($unit === 'microsecond') { + $unit = 'micro'; + } + + return $this->setTime(...array_map(function ($name) use ($unit, $value) { + return (int) ($name === $unit ? $value : $this->$name); + }, $units)); + } + + /** + * Returns standardized singular of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function singularUnit(string $unit): string + { + $unit = rtrim(mb_strtolower($unit), 's'); + + if ($unit === 'centurie') { + return 'century'; + } + + if ($unit === 'millennia') { + return 'millennium'; + } + + return $unit; + } + + /** + * Returns standardized plural of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function pluralUnit(string $unit): string + { + $unit = rtrim(strtolower($unit), 's'); + + if ($unit === 'century') { + return 'centuries'; + } + + if ($unit === 'millennium' || $unit === 'millennia') { + return 'millennia'; + } + + return "{$unit}s"; + } + + protected function executeCallable($macro, ...$parameters) + { + if ($macro instanceof Closure) { + $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + } + + protected function executeCallableWithContext($macro, ...$parameters) + { + return static::bindMacroContext($this, function () use (&$macro, &$parameters) { + return $this->executeCallable($macro, ...$parameters); + }); + } + + protected static function getGenericMacros() + { + foreach (static::$globalGenericMacros as $list) { + foreach ($list as $macro) { + yield $macro; + } + } + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws UnknownMethodException|BadMethodCallException|ReflectionException|Throwable + * + * @return mixed + */ + public function __call($method, $parameters) + { + $diffSizes = [ + // @mode diffForHumans + 'short' => true, + // @mode diffForHumans + 'long' => false, + ]; + $diffSyntaxModes = [ + // @call diffForHumans + 'Absolute' => CarbonInterface::DIFF_ABSOLUTE, + // @call diffForHumans + 'Relative' => CarbonInterface::DIFF_RELATIVE_AUTO, + // @call diffForHumans + 'RelativeToNow' => CarbonInterface::DIFF_RELATIVE_TO_NOW, + // @call diffForHumans + 'RelativeToOther' => CarbonInterface::DIFF_RELATIVE_TO_OTHER, + ]; + $sizePattern = implode('|', array_keys($diffSizes)); + $syntaxPattern = implode('|', array_keys($diffSyntaxModes)); + + if (preg_match("/^(?$sizePattern)(?$syntaxPattern)DiffForHumans$/", $method, $match)) { + $dates = array_filter($parameters, function ($parameter) { + return $parameter instanceof DateTimeInterface; + }); + $other = null; + + if (\count($dates)) { + $key = key($dates); + $other = current($dates); + array_splice($parameters, $key, 1); + } + + return $this->diffForHumans($other, $diffSyntaxModes[$match['syntax']], $diffSizes[$match['size']], ...$parameters); + } + + $roundedValue = $this->callRoundMethod($method, $parameters); + + if ($roundedValue !== null) { + return $roundedValue; + } + + $unit = rtrim($method, 's'); + + if (str_starts_with($unit, 'is')) { + $word = substr($unit, 2); + + if (\in_array($word, static::$days, true)) { + return $this->isDayOfWeek($word); + } + + switch ($word) { + // @call is Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + case 'Utc': + case 'UTC': + return $this->utc; + // @call is Check if the current instance has non-UTC timezone. + case 'Local': + return $this->local; + // @call is Check if the current instance is a valid date. + case 'Valid': + return $this->year !== 0; + // @call is Check if the current instance is in a daylight saving time. + case 'DST': + return $this->dst; + } + } + + $action = substr($unit, 0, 3); + $overflow = null; + + if ($action === 'set') { + $unit = strtolower(substr($unit, 3)); + } + + if (\in_array($unit, static::$units, true)) { + return $this->setUnit($unit, ...$parameters); + } + + if ($action === 'add' || $action === 'sub') { + $unit = substr($unit, 3); + + if (str_starts_with($unit, 'Real')) { + $unit = static::singularUnit(substr($unit, 4)); + + return $this->{"{$action}RealUnit"}($unit, ...$parameters); + } + + if (preg_match('/^(Month|Quarter|Year|Decade|Century|Centurie|Millennium|Millennia)s?(No|With|Without|WithNo)Overflow$/', $unit, $match)) { + $unit = $match[1]; + $overflow = $match[2] === 'With'; + } + + $unit = static::singularUnit($unit); + } + + if (static::isModifiableUnit($unit)) { + return $this->{"{$action}Unit"}($unit, $this->getMagicParameter($parameters, 0, 'value', 1), $overflow); + } + + $sixFirstLetters = substr($unit, 0, 6); + $factor = -1; + + if ($sixFirstLetters === 'isLast') { + $sixFirstLetters = 'isNext'; + $factor = 1; + } + + if ($sixFirstLetters === 'isNext') { + $lowerUnit = strtolower(substr($unit, 6)); + + if (static::isModifiableUnit($lowerUnit)) { + return $this->copy()->addUnit($lowerUnit, $factor, false)->isSameUnit($lowerUnit, ...$parameters); + } + } + + if ($sixFirstLetters === 'isSame') { + try { + return $this->isSameUnit(strtolower(substr($unit, 6)), ...$parameters); + } catch (BadComparisonUnitException $exception) { + // Try next + } + } + + if (str_starts_with($unit, 'isCurrent')) { + try { + return $this->isCurrentUnit(strtolower(substr($unit, 9))); + } catch (BadComparisonUnitException | BadMethodCallException $exception) { + // Try next + } + } + + if (str_ends_with($method, 'Until')) { + try { + $unit = static::singularUnit(substr($method, 0, -5)); + + return $this->range( + $this->getMagicParameter($parameters, 0, 'endDate', $this), + $this->getMagicParameter($parameters, 1, 'factor', 1), + $unit + ); + } catch (InvalidArgumentException $exception) { + // Try macros + } + } + + return static::bindMacroContext($this, function () use (&$method, &$parameters) { + $macro = $this->getLocalMacro($method); + + if (!$macro) { + foreach ([$this->localGenericMacros ?: [], static::getGenericMacros()] as $list) { + foreach ($list as $callback) { + try { + return $this->executeCallable($callback, $method, ...$parameters); + } catch (BadMethodCallException $exception) { + continue; + } + } + } + + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new UnknownMethodException($method); + } + + return null; + } + + return $this->executeCallable($macro, ...$parameters); + }); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedProperties.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedProperties.php new file mode 100644 index 0000000000..5acc6f5c7e --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedProperties.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +trait DeprecatedProperties +{ + /** + * the day of week in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('MMM') instead. + * Deprecated since 2.55.0 + */ + public $localeDayOfWeek; + + /** + * the abbreviated day of week in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('dddd') instead. + * Deprecated since 2.55.0 + */ + public $shortLocaleDayOfWeek; + + /** + * the month in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('ddd') instead. + * Deprecated since 2.55.0 + */ + public $localeMonth; + + /** + * the abbreviated month in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('MMMM') instead. + * Deprecated since 2.55.0 + */ + public $shortLocaleMonth; +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Difference.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Difference.php new file mode 100644 index 0000000000..ab5b65d234 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Difference.php @@ -0,0 +1,1182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; +use Carbon\CarbonInterval; +use Carbon\CarbonPeriod; +use Carbon\Translator; +use Closure; +use DateInterval; +use DateTimeInterface; +use ReturnTypeWillChange; + +/** + * Trait Difference. + * + * Depends on the following methods: + * + * @method bool lessThan($date) + * @method static copy() + * @method static resolveCarbon($date = null) + * @method static Translator translator() + */ +trait Difference +{ + /** + * @codeCoverageIgnore + * + * @param CarbonInterval $diff + */ + protected static function fixNegativeMicroseconds(CarbonInterval $diff) + { + if ($diff->s !== 0 || $diff->i !== 0 || $diff->h !== 0 || $diff->d !== 0 || $diff->m !== 0 || $diff->y !== 0) { + $diff->f = (round($diff->f * 1000000) + 1000000) / 1000000; + $diff->s--; + + if ($diff->s < 0) { + $diff->s += 60; + $diff->i--; + + if ($diff->i < 0) { + $diff->i += 60; + $diff->h--; + + if ($diff->h < 0) { + $diff->h += 24; + $diff->d--; + + if ($diff->d < 0) { + $diff->d += 30; + $diff->m--; + + if ($diff->m < 0) { + $diff->m += 12; + $diff->y--; + } + } + } + } + } + + return; + } + + $diff->f *= -1; + $diff->invert(); + } + + /** + * @param DateInterval $diff + * @param bool $absolute + * + * @return CarbonInterval + */ + protected static function fixDiffInterval(DateInterval $diff, $absolute, array $skip = []) + { + $diff = CarbonInterval::instance($diff, $skip); + + // Work-around for https://bugs.php.net/bug.php?id=77145 + // @codeCoverageIgnoreStart + if ($diff->f > 0 && $diff->y === -1 && $diff->m === 11 && $diff->d >= 27 && $diff->h === 23 && $diff->i === 59 && $diff->s === 59) { + $diff->y = 0; + $diff->m = 0; + $diff->d = 0; + $diff->h = 0; + $diff->i = 0; + $diff->s = 0; + $diff->f = (1000000 - round($diff->f * 1000000)) / 1000000; + $diff->invert(); + } elseif ($diff->f < 0) { + static::fixNegativeMicroseconds($diff); + } + // @codeCoverageIgnoreEnd + + if ($absolute && $diff->invert) { + $diff->invert(); + } + + return $diff; + } + + /** + * Get the difference as a DateInterval instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return DateInterval + */ + #[ReturnTypeWillChange] + public function diff($date = null, $absolute = false) + { + $other = $this->resolveCarbon($date); + + // Work-around for https://bugs.php.net/bug.php?id=81458 + // It was initially introduced for https://bugs.php.net/bug.php?id=80998 + // The very specific case of 80998 was fixed in PHP 8.1beta3, but it introduced 81458 + // So we still need to keep this for now + // @codeCoverageIgnoreStart + if (version_compare(PHP_VERSION, '8.1.0-dev', '>=') && $other->tz !== $this->tz) { + $other = $other->avoidMutation()->tz($this->tz); + } + // @codeCoverageIgnoreEnd + + return parent::diff($other, (bool) $absolute); + } + + /** + * Get the difference as a CarbonInterval instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return CarbonInterval + */ + public function diffAsCarbonInterval($date = null, $absolute = true, array $skip = []) + { + return static::fixDiffInterval($this->diff($this->resolveCarbon($date), $absolute), $absolute, $skip); + } + + /** + * Get the difference in years + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInYears($date = null, $absolute = true) + { + return (int) $this->diff($this->resolveCarbon($date), $absolute)->format('%r%y'); + } + + /** + * Get the difference in quarters rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInQuarters($date = null, $absolute = true) + { + return (int) ($this->diffInMonths($date, $absolute) / static::MONTHS_PER_QUARTER); + } + + /** + * Get the difference in months rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMonths($date = null, $absolute = true) + { + $date = $this->resolveCarbon($date)->avoidMutation()->tz($this->tz); + + [$yearStart, $monthStart, $dayStart] = explode('-', $this->format('Y-m-dHisu')); + [$yearEnd, $monthEnd, $dayEnd] = explode('-', $date->format('Y-m-dHisu')); + + $diff = (((int) $yearEnd) - ((int) $yearStart)) * static::MONTHS_PER_YEAR + + ((int) $monthEnd) - ((int) $monthStart); + + if ($diff > 0) { + $diff -= ($dayStart > $dayEnd ? 1 : 0); + } elseif ($diff < 0) { + $diff += ($dayStart < $dayEnd ? 1 : 0); + } + + return $absolute ? abs($diff) : $diff; + } + + /** + * Get the difference in weeks rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeeks($date = null, $absolute = true) + { + return (int) ($this->diffInDays($date, $absolute) / static::DAYS_PER_WEEK); + } + + /** + * Get the difference in days rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDays($date = null, $absolute = true) + { + return $this->getIntervalDayDiff($this->diff($this->resolveCarbon($date), $absolute)); + } + + /** + * Get the difference in days using a filter closure rounded down. + * + * @param Closure $callback + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDaysFiltered(Closure $callback, $date = null, $absolute = true) + { + return $this->diffFiltered(CarbonInterval::day(), $callback, $date, $absolute); + } + + /** + * Get the difference in hours using a filter closure rounded down. + * + * @param Closure $callback + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHoursFiltered(Closure $callback, $date = null, $absolute = true) + { + return $this->diffFiltered(CarbonInterval::hour(), $callback, $date, $absolute); + } + + /** + * Get the difference by the given interval using a filter closure. + * + * @param CarbonInterval $ci An interval to traverse by + * @param Closure $callback + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffFiltered(CarbonInterval $ci, Closure $callback, $date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $inverse = false; + + if ($end < $start) { + $start = $end; + $end = $this; + $inverse = true; + } + + $options = CarbonPeriod::EXCLUDE_END_DATE | ($this->isMutable() ? 0 : CarbonPeriod::IMMUTABLE); + $diff = $ci->toPeriod($start, $end, $options)->filter($callback)->count(); + + return $inverse && !$absolute ? -$diff : $diff; + } + + /** + * Get the difference in weekdays rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekdays($date = null, $absolute = true) + { + return $this->diffInDaysFiltered(static function (CarbonInterface $date) { + return $date->isWeekday(); + }, $this->resolveCarbon($date)->avoidMutation()->modify($this->format('H:i:s.u')), $absolute); + } + + /** + * Get the difference in weekend days using a filter rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekendDays($date = null, $absolute = true) + { + return $this->diffInDaysFiltered(static function (CarbonInterface $date) { + return $date->isWeekend(); + }, $this->resolveCarbon($date)->avoidMutation()->modify($this->format('H:i:s.u')), $absolute); + } + + /** + * Get the difference in hours rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHours($date = null, $absolute = true) + { + return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR); + } + + /** + * Get the difference in hours rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealHours($date = null, $absolute = true) + { + return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR); + } + + /** + * Get the difference in minutes rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMinutes($date = null, $absolute = true) + { + return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE); + } + + /** + * Get the difference in minutes rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMinutes($date = null, $absolute = true) + { + return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE); + } + + /** + * Get the difference in seconds rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInSeconds($date = null, $absolute = true) + { + $diff = $this->diff($date); + + if ($diff->days === 0) { + $diff = static::fixDiffInterval($diff, $absolute); + } + + $value = (((($diff->m || $diff->y ? $diff->days : $diff->d) * static::HOURS_PER_DAY) + + $diff->h) * static::MINUTES_PER_HOUR + + $diff->i) * static::SECONDS_PER_MINUTE + + $diff->s; + + return $absolute || !$diff->invert ? $value : -$value; + } + + /** + * Get the difference in microseconds. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMicroseconds($date = null, $absolute = true) + { + $diff = $this->diff($date); + $value = (int) round(((((($diff->m || $diff->y ? $diff->days : $diff->d) * static::HOURS_PER_DAY) + + $diff->h) * static::MINUTES_PER_HOUR + + $diff->i) * static::SECONDS_PER_MINUTE + + ($diff->f + $diff->s)) * static::MICROSECONDS_PER_SECOND); + + return $absolute || !$diff->invert ? $value : -$value; + } + + /** + * Get the difference in milliseconds rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMilliseconds($date = null, $absolute = true) + { + return (int) ($this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND); + } + + /** + * Get the difference in seconds using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealSeconds($date = null, $absolute = true) + { + /** @var CarbonInterface $date */ + $date = $this->resolveCarbon($date); + $value = $date->getTimestamp() - $this->getTimestamp(); + + return $absolute ? abs($value) : $value; + } + + /** + * Get the difference in microseconds using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMicroseconds($date = null, $absolute = true) + { + /** @var CarbonInterface $date */ + $date = $this->resolveCarbon($date); + $value = ($date->timestamp - $this->timestamp) * static::MICROSECONDS_PER_SECOND + + $date->micro - $this->micro; + + return $absolute ? abs($value) : $value; + } + + /** + * Get the difference in milliseconds rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMilliseconds($date = null, $absolute = true) + { + return (int) ($this->diffInRealMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND); + } + + /** + * Get the difference in seconds as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInSeconds($date = null, $absolute = true) + { + return (float) ($this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_SECOND); + } + + /** + * Get the difference in minutes as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMinutes($date = null, $absolute = true) + { + return $this->floatDiffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE; + } + + /** + * Get the difference in hours as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInHours($date = null, $absolute = true) + { + return $this->floatDiffInMinutes($date, $absolute) / static::MINUTES_PER_HOUR; + } + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInDays($date = null, $absolute = true) + { + $hoursDiff = $this->floatDiffInHours($date, $absolute); + $interval = $this->diff($date, $absolute); + + if ($interval->y === 0 && $interval->m === 0 && $interval->d === 0) { + return $hoursDiff / static::HOURS_PER_DAY; + } + + $daysDiff = $this->getIntervalDayDiff($interval); + + return $daysDiff + fmod($hoursDiff, static::HOURS_PER_DAY) / static::HOURS_PER_DAY; + } + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInWeeks($date = null, $absolute = true) + { + return $this->floatDiffInDays($date, $absolute) / static::DAYS_PER_WEEK; + } + + /** + * Get the difference in months as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMonths($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $monthsDiff = $start->diffInMonths($end); + /** @var Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addMonths($monthsDiff); + + if ($floorEnd >= $end) { + return $sign * $monthsDiff; + } + + /** @var Carbon|CarbonImmutable $startOfMonthAfterFloorEnd */ + $startOfMonthAfterFloorEnd = $floorEnd->avoidMutation()->addMonth()->startOfMonth(); + + if ($startOfMonthAfterFloorEnd > $end) { + return $sign * ($monthsDiff + $floorEnd->floatDiffInDays($end) / $floorEnd->daysInMonth); + } + + return $sign * ($monthsDiff + $floorEnd->floatDiffInDays($startOfMonthAfterFloorEnd) / $floorEnd->daysInMonth + $startOfMonthAfterFloorEnd->floatDiffInDays($end) / $end->daysInMonth); + } + + /** + * Get the difference in year as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInYears($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $yearsDiff = $start->diffInYears($end); + /** @var Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addYears($yearsDiff); + + if ($floorEnd >= $end) { + return $sign * $yearsDiff; + } + + /** @var Carbon|CarbonImmutable $startOfYearAfterFloorEnd */ + $startOfYearAfterFloorEnd = $floorEnd->avoidMutation()->addYear()->startOfYear(); + + if ($startOfYearAfterFloorEnd > $end) { + return $sign * ($yearsDiff + $floorEnd->floatDiffInDays($end) / $floorEnd->daysInYear); + } + + return $sign * ($yearsDiff + $floorEnd->floatDiffInDays($startOfYearAfterFloorEnd) / $floorEnd->daysInYear + $startOfYearAfterFloorEnd->floatDiffInDays($end) / $end->daysInYear); + } + + /** + * Get the difference in seconds as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealSeconds($date = null, $absolute = true) + { + return $this->diffInRealMicroseconds($date, $absolute) / static::MICROSECONDS_PER_SECOND; + } + + /** + * Get the difference in minutes as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMinutes($date = null, $absolute = true) + { + return $this->floatDiffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE; + } + + /** + * Get the difference in hours as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealHours($date = null, $absolute = true) + { + return $this->floatDiffInRealMinutes($date, $absolute) / static::MINUTES_PER_HOUR; + } + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealDays($date = null, $absolute = true) + { + $date = $this->resolveUTC($date); + $utc = $this->avoidMutation()->utc(); + $hoursDiff = $utc->floatDiffInRealHours($date, $absolute); + + return ($hoursDiff < 0 ? -1 : 1) * $utc->diffInDays($date) + fmod($hoursDiff, static::HOURS_PER_DAY) / static::HOURS_PER_DAY; + } + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealWeeks($date = null, $absolute = true) + { + return $this->floatDiffInRealDays($date, $absolute) / static::DAYS_PER_WEEK; + } + + /** + * Get the difference in months as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMonths($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $monthsDiff = $start->diffInMonths($end); + /** @var Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addMonths($monthsDiff); + + if ($floorEnd >= $end) { + return $sign * $monthsDiff; + } + + /** @var Carbon|CarbonImmutable $startOfMonthAfterFloorEnd */ + $startOfMonthAfterFloorEnd = $floorEnd->avoidMutation()->addMonth()->startOfMonth(); + + if ($startOfMonthAfterFloorEnd > $end) { + return $sign * ($monthsDiff + $floorEnd->floatDiffInRealDays($end) / $floorEnd->daysInMonth); + } + + return $sign * ($monthsDiff + $floorEnd->floatDiffInRealDays($startOfMonthAfterFloorEnd) / $floorEnd->daysInMonth + $startOfMonthAfterFloorEnd->floatDiffInRealDays($end) / $end->daysInMonth); + } + + /** + * Get the difference in year as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealYears($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $yearsDiff = $start->diffInYears($end); + /** @var Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addYears($yearsDiff); + + if ($floorEnd >= $end) { + return $sign * $yearsDiff; + } + + /** @var Carbon|CarbonImmutable $startOfYearAfterFloorEnd */ + $startOfYearAfterFloorEnd = $floorEnd->avoidMutation()->addYear()->startOfYear(); + + if ($startOfYearAfterFloorEnd > $end) { + return $sign * ($yearsDiff + $floorEnd->floatDiffInRealDays($end) / $floorEnd->daysInYear); + } + + return $sign * ($yearsDiff + $floorEnd->floatDiffInRealDays($startOfYearAfterFloorEnd) / $floorEnd->daysInYear + $startOfYearAfterFloorEnd->floatDiffInRealDays($end) / $end->daysInYear); + } + + /** + * The number of seconds since midnight. + * + * @return int + */ + public function secondsSinceMidnight() + { + return $this->diffInSeconds($this->avoidMutation()->startOfDay()); + } + + /** + * The number of seconds until 23:59:59. + * + * @return int + */ + public function secondsUntilEndOfDay() + { + return $this->diffInSeconds($this->avoidMutation()->endOfDay()); + } + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @example + * ``` + * echo Carbon::tomorrow()->diffForHumans() . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 2]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 3, 'join' => true]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday()) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday(), ['short' => true]) . "\n"; + * ``` + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'skip' entry, list of units to skip (array of strings or a single string, + * ` it can be the unit name (singular or plural) or its shortcut + * ` (y, m, w, d, h, min, s, ms, ยตs). + * - 'aUnit' entry, prefer "an hour" over "1 hour" if true + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * - 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ` short form of the units, e.g. 'hour' or 'h' (default value: s) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function diffForHumans($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + /* @var CarbonInterface $this */ + if (\is_array($other)) { + $other['syntax'] = \array_key_exists('syntax', $other) ? $other['syntax'] : $syntax; + $syntax = $other; + $other = $syntax['other'] ?? null; + } + + $intSyntax = &$syntax; + if (\is_array($syntax)) { + $syntax['syntax'] = $syntax['syntax'] ?? null; + $intSyntax = &$syntax['syntax']; + } + $intSyntax = (int) ($intSyntax ?? static::DIFF_RELATIVE_AUTO); + $intSyntax = $intSyntax === static::DIFF_RELATIVE_AUTO && $other === null ? static::DIFF_RELATIVE_TO_NOW : $intSyntax; + + $parts = min(7, max(1, (int) $parts)); + $skip = \is_array($syntax) ? ($syntax['skip'] ?? []) : []; + + return $this->diffAsCarbonInterval($other, false, (array) $skip) + ->setLocalTranslator($this->getLocalTranslator()) + ->forHumans($syntax, (bool) $short, $parts, $options ?? $this->localHumanDiffOptions ?? static::getHumanDiffOptions()); + } + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function from($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->diffForHumans($other, $syntax, $short, $parts, $options); + } + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + */ + public function since($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->diffForHumans($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * When comparing a value in the past to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the future to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the past to another value: + * 1 hour after + * 5 months after + * + * When comparing a value in the future to another value: + * 1 hour before + * 5 months before + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function to($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + if (!$syntax && !$other) { + $syntax = CarbonInterface::DIFF_RELATIVE_TO_NOW; + } + + return $this->resolveCarbon($other)->diffForHumans($this, $syntax, $short, $parts, $options); + } + + /** + * @alias to + * + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function until($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->to($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from current + * instance to now. + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function fromNow($syntax = null, $short = false, $parts = 1, $options = null) + { + $other = null; + + if ($syntax instanceof DateTimeInterface) { + [$other, $syntax, $short, $parts, $options] = array_pad(\func_get_args(), 5, null); + } + + return $this->from($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function toNow($syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->to(null, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function ago($syntax = null, $short = false, $parts = 1, $options = null) + { + $other = null; + + if ($syntax instanceof DateTimeInterface) { + [$other, $syntax, $short, $parts, $options] = array_pad(\func_get_args(), 5, null); + } + + return $this->from($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @return string + */ + public function timespan($other = null, $timezone = null) + { + if (!$other instanceof DateTimeInterface) { + $other = static::parse($other, $timezone); + } + + return $this->diffForHumans($other, [ + 'join' => ', ', + 'syntax' => CarbonInterface::DIFF_ABSOLUTE, + 'options' => CarbonInterface::NO_ZERO_DIFF, + 'parts' => -1, + ]); + } + + /** + * Returns either day of week + time (e.g. "Last Friday at 3:30 PM") if reference time is within 7 days, + * or a calendar date (e.g. "10/29/2017") otherwise. + * + * Language, date and time formats will change according to the current locale. + * + * @param Carbon|\DateTimeInterface|string|null $referenceTime + * @param array $formats + * + * @return string + */ + public function calendar($referenceTime = null, array $formats = []) + { + /** @var CarbonInterface $current */ + $current = $this->avoidMutation()->startOfDay(); + /** @var CarbonInterface $other */ + $other = $this->resolveCarbon($referenceTime)->avoidMutation()->setTimezone($this->getTimezone())->startOfDay(); + $diff = $other->diffInDays($current, false); + $format = $diff < -6 ? 'sameElse' : ( + $diff < -1 ? 'lastWeek' : ( + $diff < 0 ? 'lastDay' : ( + $diff < 1 ? 'sameDay' : ( + $diff < 2 ? 'nextDay' : ( + $diff < 7 ? 'nextWeek' : 'sameElse' + ) + ) + ) + ) + ); + $format = array_merge($this->getCalendarFormats(), $formats)[$format]; + if ($format instanceof Closure) { + $format = $format($current, $other) ?? ''; + } + + return $this->isoFormat((string) $format); + } + + private function getIntervalDayDiff(DateInterval $interval): int + { + $daysDiff = (int) $interval->format('%a'); + $sign = $interval->format('%r') === '-' ? -1 : 1; + + if (\is_int($interval->days) && + $interval->y === 0 && + $interval->m === 0 && + version_compare(PHP_VERSION, '8.1.0-dev', '<') && + abs($interval->d - $daysDiff) === 1 + ) { + $daysDiff = abs($interval->d); // @codeCoverageIgnore + } + + return $daysDiff * $sign; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/IntervalRounding.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/IntervalRounding.php new file mode 100644 index 0000000000..f069c280d7 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/IntervalRounding.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterval; +use Carbon\Exceptions\InvalidIntervalException; +use DateInterval; + +/** + * Trait to call rounding methods to interval or the interval of a period. + */ +trait IntervalRounding +{ + protected function callRoundMethod(string $method, array $parameters) + { + $action = substr($method, 0, 4); + + if ($action !== 'ceil') { + $action = substr($method, 0, 5); + } + + if (\in_array($action, ['round', 'floor', 'ceil'])) { + return $this->{$action.'Unit'}(substr($method, \strlen($action)), ...$parameters); + } + + return null; + } + + protected function roundWith($precision, $function) + { + $unit = 'second'; + + if ($precision instanceof DateInterval) { + $precision = (string) CarbonInterval::instance($precision, [], true); + } + + if (\is_string($precision) && preg_match('/^\s*(?\d+)?\s*(?\w+)(?\W.*)?$/', $precision, $match)) { + if (trim($match['other'] ?? '') !== '') { + throw new InvalidIntervalException('Rounding is only possible with single unit intervals.'); + } + + $precision = (int) ($match['precision'] ?: 1); + $unit = $match['unit']; + } + + return $this->roundUnit($unit, $precision, $function); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/IntervalStep.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/IntervalStep.php new file mode 100644 index 0000000000..82d7c32649 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/IntervalStep.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; +use Closure; +use DateTimeImmutable; +use DateTimeInterface; + +trait IntervalStep +{ + /** + * Step to apply instead of a fixed interval to get the new date. + * + * @var Closure|null + */ + protected $step; + + /** + * Get the dynamic step in use. + * + * @return Closure + */ + public function getStep(): ?Closure + { + return $this->step; + } + + /** + * Set a step to apply instead of a fixed interval to get the new date. + * + * Or pass null to switch to fixed interval. + * + * @param Closure|null $step + */ + public function setStep(?Closure $step): void + { + $this->step = $step; + } + + /** + * Take a date and apply either the step if set, or the current interval else. + * + * The interval/step is applied negatively (typically subtraction instead of addition) if $negated is true. + * + * @param DateTimeInterface $dateTime + * @param bool $negated + * + * @return CarbonInterface + */ + public function convertDate(DateTimeInterface $dateTime, bool $negated = false): CarbonInterface + { + /** @var CarbonInterface $carbonDate */ + $carbonDate = $dateTime instanceof CarbonInterface ? $dateTime : $this->resolveCarbon($dateTime); + + if ($this->step) { + return $carbonDate->setDateTimeFrom(($this->step)($carbonDate->avoidMutation(), $negated)); + } + + if ($negated) { + return $carbonDate->rawSub($this); + } + + return $carbonDate->rawAdd($this); + } + + /** + * Convert DateTimeImmutable instance to CarbonImmutable instance and DateTime instance to Carbon instance. + * + * @param DateTimeInterface $dateTime + * + * @return Carbon|CarbonImmutable + */ + private function resolveCarbon(DateTimeInterface $dateTime) + { + if ($dateTime instanceof DateTimeImmutable) { + return CarbonImmutable::instance($dateTime); + } + + return Carbon::instance($dateTime); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Localization.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Localization.php new file mode 100644 index 0000000000..46aff113e5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Localization.php @@ -0,0 +1,840 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use Carbon\Exceptions\InvalidTypeException; +use Carbon\Exceptions\NotLocaleAwareException; +use Carbon\Language; +use Carbon\Translator; +use Carbon\TranslatorStrongTypeInterface; +use Closure; +use Symfony\Component\Translation\TranslatorBagInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface as ContractsTranslatorInterface; + +// @codeCoverageIgnoreStart +if (interface_exists('Symfony\\Contracts\\Translation\\TranslatorInterface') && + !interface_exists('Symfony\\Component\\Translation\\TranslatorInterface') +) { + class_alias( + 'Symfony\\Contracts\\Translation\\TranslatorInterface', + 'Symfony\\Component\\Translation\\TranslatorInterface' + ); +} +// @codeCoverageIgnoreEnd + +/** + * Trait Localization. + * + * Embed default and locale translators and translation base methods. + */ +trait Localization +{ + /** + * Default translator. + * + * @var \Symfony\Component\Translation\TranslatorInterface + */ + protected static $translator; + + /** + * Specific translator of the current instance. + * + * @var \Symfony\Component\Translation\TranslatorInterface + */ + protected $localTranslator; + + /** + * Options for diffForHumans(). + * + * @var int + */ + protected static $humanDiffOptions = CarbonInterface::NO_ZERO_DIFF; + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOptions + */ + public static function setHumanDiffOptions($humanDiffOptions) + { + static::$humanDiffOptions = $humanDiffOptions; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function enableHumanDiffOption($humanDiffOption) + { + static::$humanDiffOptions = static::getHumanDiffOptions() | $humanDiffOption; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function disableHumanDiffOption($humanDiffOption) + { + static::$humanDiffOptions = static::getHumanDiffOptions() & ~$humanDiffOption; + } + + /** + * Return default humanDiff() options (merged flags as integer). + * + * @return int + */ + public static function getHumanDiffOptions() + { + return static::$humanDiffOptions; + } + + /** + * Get the default translator instance in use. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public static function getTranslator() + { + return static::translator(); + } + + /** + * Set the default translator instance to use. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return void + */ + public static function setTranslator(TranslatorInterface $translator) + { + static::$translator = $translator; + } + + /** + * Return true if the current instance has its own translator. + * + * @return bool + */ + public function hasLocalTranslator() + { + return isset($this->localTranslator); + } + + /** + * Get the translator of the current instance or the default if none set. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public function getLocalTranslator() + { + return $this->localTranslator ?: static::translator(); + } + + /** + * Set the translator for the current instance. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return $this + */ + public function setLocalTranslator(TranslatorInterface $translator) + { + $this->localTranslator = $translator; + + return $this; + } + + /** + * Returns raw translation message for a given key. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator the translator to use + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * + * @return string + */ + public static function getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) + { + if (!($translator instanceof TranslatorBagInterface && $translator instanceof TranslatorInterface)) { + throw new InvalidTypeException( + 'Translator does not implement '.TranslatorInterface::class.' and '.TranslatorBagInterface::class.'. '. + (\is_object($translator) ? \get_class($translator) : \gettype($translator)).' has been given.' + ); + } + + if (!$locale && $translator instanceof LocaleAwareInterface) { + $locale = $translator->getLocale(); + } + + $result = self::getFromCatalogue($translator, $translator->getCatalogue($locale), $key); + + return $result === $key ? $default : $result; + } + + /** + * Returns raw translation message for a given key. + * + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * @param \Symfony\Component\Translation\TranslatorInterface $translator an optional translator to use + * + * @return string + */ + public function getTranslationMessage(string $key, ?string $locale = null, ?string $default = null, $translator = null) + { + return static::getTranslationMessageWith($translator ?: $this->getLocalTranslator(), $key, $locale, $default); + } + + /** + * Translate using translation string or callback available. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * @param string $key + * @param array $parameters + * @param null $number + * + * @return string + */ + public static function translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null): string + { + $message = static::getTranslationMessageWith($translator, $key, null, $key); + if ($message instanceof Closure) { + return (string) $message(...array_values($parameters)); + } + + if ($number !== null) { + $parameters['%count%'] = $number; + } + if (isset($parameters['%count%'])) { + $parameters[':count'] = $parameters['%count%']; + } + + // @codeCoverageIgnoreStart + $choice = $translator instanceof ContractsTranslatorInterface + ? $translator->trans($key, $parameters) + : $translator->transChoice($key, $number, $parameters); + // @codeCoverageIgnoreEnd + + return (string) $choice; + } + + /** + * Translate using translation string or callback available. + * + * @param string $key + * @param array $parameters + * @param string|int|float|null $number + * @param \Symfony\Component\Translation\TranslatorInterface|null $translator + * @param bool $altNumbers + * + * @return string + */ + public function translate(string $key, array $parameters = [], $number = null, ?TranslatorInterface $translator = null, bool $altNumbers = false): string + { + $translation = static::translateWith($translator ?: $this->getLocalTranslator(), $key, $parameters, $number); + + if ($number !== null && $altNumbers) { + return str_replace($number, $this->translateNumber($number), $translation); + } + + return $translation; + } + + /** + * Returns the alternative number for a given integer if available in the current locale. + * + * @param int $number + * + * @return string + */ + public function translateNumber(int $number): string + { + $translateKey = "alt_numbers.$number"; + $symbol = $this->translate($translateKey); + + if ($symbol !== $translateKey) { + return $symbol; + } + + if ($number > 99 && $this->translate('alt_numbers.99') !== 'alt_numbers.99') { + $start = ''; + foreach ([10000, 1000, 100] as $exp) { + $key = "alt_numbers_pow.$exp"; + if ($number >= $exp && $number < $exp * 10 && ($pow = $this->translate($key)) !== $key) { + $unit = floor($number / $exp); + $number -= $unit * $exp; + $start .= ($unit > 1 ? $this->translate("alt_numbers.$unit") : '').$pow; + } + } + $result = ''; + while ($number) { + $chunk = $number % 100; + $result = $this->translate("alt_numbers.$chunk").$result; + $number = floor($number / 100); + } + + return "$start$result"; + } + + if ($number > 9 && $this->translate('alt_numbers.9') !== 'alt_numbers.9') { + $result = ''; + while ($number) { + $chunk = $number % 10; + $result = $this->translate("alt_numbers.$chunk").$result; + $number = floor($number / 10); + } + + return $result; + } + + return (string) $number; + } + + /** + * Translate a time string from a locale to an other. + * + * @param string $timeString date/time/duration string to translate (may also contain English) + * @param string|null $from input locale of the $timeString parameter (`Carbon::getLocale()` by default) + * @param string|null $to output locale of the result returned (`"en"` by default) + * @param int $mode specify what to translate with options: + * - CarbonInterface::TRANSLATE_ALL (default) + * - CarbonInterface::TRANSLATE_MONTHS + * - CarbonInterface::TRANSLATE_DAYS + * - CarbonInterface::TRANSLATE_UNITS + * - CarbonInterface::TRANSLATE_MERIDIEM + * You can use pipe to group: CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS + * + * @return string + */ + public static function translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) + { + // Fallback source and destination locales + $from = $from ?: static::getLocale(); + $to = $to ?: 'en'; + + if ($from === $to) { + return $timeString; + } + + // Standardize apostrophe + $timeString = strtr($timeString, ['โ€™' => "'"]); + + $fromTranslations = []; + $toTranslations = []; + + foreach (['from', 'to'] as $key) { + $language = $$key; + $translator = Translator::get($language); + $translations = $translator->getMessages(); + + if (!isset($translations[$language])) { + return $timeString; + } + + $translationKey = $key.'Translations'; + $messages = $translations[$language]; + $months = $messages['months'] ?? []; + $weekdays = $messages['weekdays'] ?? []; + $meridiem = $messages['meridiem'] ?? ['AM', 'PM']; + + if (isset($messages['ordinal_words'])) { + $timeString = self::replaceOrdinalWords( + $timeString, + $key === 'from' ? array_flip($messages['ordinal_words']) : $messages['ordinal_words'] + ); + } + + if ($key === 'from') { + foreach (['months', 'weekdays'] as $variable) { + $list = $messages[$variable.'_standalone'] ?? null; + + if ($list) { + foreach ($$variable as $index => &$name) { + $name .= '|'.$messages[$variable.'_standalone'][$index]; + } + } + } + } + + $$translationKey = array_merge( + $mode & CarbonInterface::TRANSLATE_MONTHS ? static::getTranslationArray($months, 12, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_MONTHS ? static::getTranslationArray($messages['months_short'] ?? [], 12, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_DAYS ? static::getTranslationArray($weekdays, 7, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_DAYS ? static::getTranslationArray($messages['weekdays_short'] ?? [], 7, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_DIFF ? static::translateWordsByKeys([ + 'diff_now', + 'diff_today', + 'diff_yesterday', + 'diff_tomorrow', + 'diff_before_yesterday', + 'diff_after_tomorrow', + ], $messages, $key) : [], + $mode & CarbonInterface::TRANSLATE_UNITS ? static::translateWordsByKeys([ + 'year', + 'month', + 'week', + 'day', + 'hour', + 'minute', + 'second', + ], $messages, $key) : [], + $mode & CarbonInterface::TRANSLATE_MERIDIEM ? array_map(function ($hour) use ($meridiem) { + if (\is_array($meridiem)) { + return $meridiem[$hour < 12 ? 0 : 1]; + } + + return $meridiem($hour, 0, false); + }, range(0, 23)) : [] + ); + } + + return substr(preg_replace_callback('/(?<=[\d\s+.\/,_-])('.implode('|', $fromTranslations).')(?=[\d\s+.\/,_-])/iu', function ($match) use ($fromTranslations, $toTranslations) { + [$chunk] = $match; + + foreach ($fromTranslations as $index => $word) { + if (preg_match("/^$word\$/iu", $chunk)) { + return $toTranslations[$index] ?? ''; + } + } + + return $chunk; // @codeCoverageIgnore + }, " $timeString "), 1, -1); + } + + /** + * Translate a time string from the current locale (`$date->locale()`) to an other. + * + * @param string $timeString time string to translate + * @param string|null $to output locale of the result returned ("en" by default) + * + * @return string + */ + public function translateTimeStringTo($timeString, $to = null) + { + return static::translateTimeString($timeString, $this->getTranslatorLocale(), $to); + } + + /** + * Get/set the locale for the current instance. + * + * @param string|null $locale + * @param string ...$fallbackLocales + * + * @return $this|string + */ + public function locale(string $locale = null, ...$fallbackLocales) + { + if ($locale === null) { + return $this->getTranslatorLocale(); + } + + if (!$this->localTranslator || $this->getTranslatorLocale($this->localTranslator) !== $locale) { + $translator = Translator::get($locale); + + if (!empty($fallbackLocales)) { + $translator->setFallbackLocales($fallbackLocales); + + foreach ($fallbackLocales as $fallbackLocale) { + $messages = Translator::get($fallbackLocale)->getMessages(); + + if (isset($messages[$fallbackLocale])) { + $translator->setMessages($fallbackLocale, $messages[$fallbackLocale]); + } + } + } + + $this->localTranslator = $translator; + } + + return $this; + } + + /** + * Get the current translator locale. + * + * @return string + */ + public static function getLocale() + { + return static::getLocaleAwareTranslator()->getLocale(); + } + + /** + * Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function setLocale($locale) + { + return static::getLocaleAwareTranslator()->setLocale($locale) !== false; + } + + /** + * Set the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @param string $locale + */ + public static function setFallbackLocale($locale) + { + $translator = static::getTranslator(); + + if (method_exists($translator, 'setFallbackLocales')) { + $translator->setFallbackLocales([$locale]); + + if ($translator instanceof Translator) { + $preferredLocale = $translator->getLocale(); + $translator->setMessages($preferredLocale, array_replace_recursive( + $translator->getMessages()[$locale] ?? [], + Translator::get($locale)->getMessages()[$locale] ?? [], + $translator->getMessages($preferredLocale) + )); + } + } + } + + /** + * Get the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @return string|null + */ + public static function getFallbackLocale() + { + $translator = static::getTranslator(); + + if (method_exists($translator, 'getFallbackLocales')) { + return $translator->getFallbackLocales()[0] ?? null; + } + + return null; + } + + /** + * Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * + * @param string $locale locale ex. en + * @param callable $func + * + * @return mixed + */ + public static function executeWithLocale($locale, $func) + { + $currentLocale = static::getLocale(); + $result = $func(static::setLocale($locale) ? static::getLocale() : false, static::translator()); + static::setLocale($currentLocale); + + return $result; + } + + /** + * Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasShortUnits($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return ($newLocale && (($y = static::translateWith($translator, 'y')) !== 'y' && $y !== static::translateWith($translator, 'year'))) || ( + ($y = static::translateWith($translator, 'd')) !== 'd' && + $y !== static::translateWith($translator, 'day') + ) || ( + ($y = static::translateWith($translator, 'h')) !== 'h' && + $y !== static::translateWith($translator, 'hour') + ); + }); + } + + /** + * Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffSyntax($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + if (!$newLocale) { + return false; + } + + foreach (['ago', 'from_now', 'before', 'after'] as $key) { + if ($translator instanceof TranslatorBagInterface && + self::getFromCatalogue($translator, $translator->getCatalogue($newLocale), $key) instanceof Closure + ) { + continue; + } + + if ($translator->trans($key) === $key) { + return false; + } + } + + return true; + }); + } + + /** + * Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffOneDayWords($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('diff_now') !== 'diff_now' && + $translator->trans('diff_yesterday') !== 'diff_yesterday' && + $translator->trans('diff_tomorrow') !== 'diff_tomorrow'; + }); + } + + /** + * Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffTwoDayWords($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('diff_before_yesterday') !== 'diff_before_yesterday' && + $translator->trans('diff_after_tomorrow') !== 'diff_after_tomorrow'; + }); + } + + /** + * Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasPeriodSyntax($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('period_recurrences') !== 'period_recurrences' && + $translator->trans('period_interval') !== 'period_interval' && + $translator->trans('period_start_date') !== 'period_start_date' && + $translator->trans('period_end_date') !== 'period_end_date'; + }); + } + + /** + * Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * + * @return array + */ + public static function getAvailableLocales() + { + $translator = static::getLocaleAwareTranslator(); + + return $translator instanceof Translator + ? $translator->getAvailableLocales() + : [$translator->getLocale()]; + } + + /** + * Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * + * @return Language[] + */ + public static function getAvailableLocalesInfo() + { + $languages = []; + foreach (static::getAvailableLocales() as $id) { + $languages[$id] = new Language($id); + } + + return $languages; + } + + /** + * Initialize the default translator instance if necessary. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + protected static function translator() + { + if (static::$translator === null) { + static::$translator = Translator::get(); + } + + return static::$translator; + } + + /** + * Get the locale of a given translator. + * + * If null or omitted, current local translator is used. + * If no local translator is in use, current global translator is used. + * + * @param null $translator + * + * @return string|null + */ + protected function getTranslatorLocale($translator = null): ?string + { + if (\func_num_args() === 0) { + $translator = $this->getLocalTranslator(); + } + + $translator = static::getLocaleAwareTranslator($translator); + + return $translator ? $translator->getLocale() : null; + } + + /** + * Throw an error if passed object is not LocaleAwareInterface. + * + * @param LocaleAwareInterface|null $translator + * + * @return LocaleAwareInterface|null + */ + protected static function getLocaleAwareTranslator($translator = null) + { + if (\func_num_args() === 0) { + $translator = static::translator(); + } + + if ($translator && !($translator instanceof LocaleAwareInterface || method_exists($translator, 'getLocale'))) { + throw new NotLocaleAwareException($translator); // @codeCoverageIgnore + } + + return $translator; + } + + /** + * @param mixed $translator + * @param \Symfony\Component\Translation\MessageCatalogueInterface $catalogue + * + * @return mixed + */ + private static function getFromCatalogue($translator, $catalogue, string $id, string $domain = 'messages') + { + return $translator instanceof TranslatorStrongTypeInterface + ? $translator->getFromCatalogue($catalogue, $id, $domain) // @codeCoverageIgnore + : $catalogue->get($id, $domain); + } + + /** + * Return the word cleaned from its translation codes. + * + * @param string $word + * + * @return string + */ + private static function cleanWordFromTranslationString($word) + { + $word = str_replace([':count', '%count', ':time'], '', $word); + $word = strtr($word, ['โ€™' => "'"]); + $word = preg_replace('/({\d+(,(\d+|Inf))?}|[\[\]]\d+(,(\d+|Inf))?[\[\]])/', '', $word); + + return trim($word); + } + + /** + * Translate a list of words. + * + * @param string[] $keys keys to translate. + * @param string[] $messages messages bag handling translations. + * @param string $key 'to' (to get the translation) or 'from' (to get the detection RegExp pattern). + * + * @return string[] + */ + private static function translateWordsByKeys($keys, $messages, $key): array + { + return array_map(function ($wordKey) use ($messages, $key) { + $message = $key === 'from' && isset($messages[$wordKey.'_regexp']) + ? $messages[$wordKey.'_regexp'] + : ($messages[$wordKey] ?? null); + + if (!$message) { + return '>>DO NOT REPLACE<<'; + } + + $parts = explode('|', $message); + + return $key === 'to' + ? self::cleanWordFromTranslationString(end($parts)) + : '(?:'.implode('|', array_map([static::class, 'cleanWordFromTranslationString'], $parts)).')'; + }, $keys); + } + + /** + * Get an array of translations based on the current date. + * + * @param callable $translation + * @param int $length + * @param string $timeString + * + * @return string[] + */ + private static function getTranslationArray($translation, $length, $timeString): array + { + $filler = '>>DO NOT REPLACE<<'; + + if (\is_array($translation)) { + return array_pad($translation, $length, $filler); + } + + $list = []; + $date = static::now(); + + for ($i = 0; $i < $length; $i++) { + $list[] = $translation($date, $timeString, $i) ?? $filler; + } + + return $list; + } + + private static function replaceOrdinalWords(string $timeString, array $ordinalWords): string + { + return preg_replace_callback('/(? + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +/** + * Trait Macros. + * + * Allows users to register macros within the Carbon class. + */ +trait Macro +{ + use Mixin; + + /** + * The registered macros. + * + * @var array + */ + protected static $globalMacros = []; + + /** + * The registered generic macros. + * + * @var array + */ + protected static $globalGenericMacros = []; + + /** + * Register a custom macro. + * + * @example + * ``` + * $userSettings = [ + * 'locale' => 'pt', + * 'timezone' => 'America/Sao_Paulo', + * ]; + * Carbon::macro('userFormat', function () use ($userSettings) { + * return $this->copy()->locale($userSettings['locale'])->tz($userSettings['timezone'])->calendar(); + * }); + * echo Carbon::yesterday()->hours(11)->userFormat(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$globalMacros[$name] = $macro; + } + + /** + * Remove all macros and generic macros. + */ + public static function resetMacros() + { + static::$globalMacros = []; + static::$globalGenericMacros = []; + } + + /** + * Register a custom macro. + * + * @param object|callable $macro + * @param int $priority marco with higher priority is tried first + * + * @return void + */ + public static function genericMacro($macro, $priority = 0) + { + if (!isset(static::$globalGenericMacros[$priority])) { + static::$globalGenericMacros[$priority] = []; + krsort(static::$globalGenericMacros, SORT_NUMERIC); + } + + static::$globalGenericMacros[$priority][] = $macro; + } + + /** + * Checks if macro is registered globally. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$globalMacros[$name]); + } + + /** + * Get the raw callable macro registered globally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public static function getMacro($name) + { + return static::$globalMacros[$name] ?? null; + } + + /** + * Checks if macro is registered globally or locally. + * + * @param string $name + * + * @return bool + */ + public function hasLocalMacro($name) + { + return ($this->localMacros && isset($this->localMacros[$name])) || static::hasMacro($name); + } + + /** + * Get the raw callable macro registered globally or locally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public function getLocalMacro($name) + { + return ($this->localMacros ?? [])[$name] ?? static::getMacro($name); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/MagicParameter.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/MagicParameter.php new file mode 100644 index 0000000000..310a44d0ca --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/MagicParameter.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +/** + * Trait MagicParameter. + * + * Allows to retrieve parameter in magic calls by index or name. + */ +trait MagicParameter +{ + private function getMagicParameter(array $parameters, int $index, string $key, $default) + { + if (\array_key_exists($index, $parameters)) { + return $parameters[$index]; + } + + if (\array_key_exists($key, $parameters)) { + return $parameters[$key]; + } + + return $default; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Mixin.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Mixin.php new file mode 100644 index 0000000000..5822454564 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Mixin.php @@ -0,0 +1,226 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use Carbon\CarbonInterval; +use Carbon\CarbonPeriod; +use Closure; +use Generator; +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; +use Throwable; + +/** + * Trait Mixin. + * + * Allows mixing in entire classes with multiple macros. + */ +trait Mixin +{ + /** + * Stack of macro instance contexts. + * + * @var array + */ + protected static $macroContextStack = []; + + /** + * Mix another object into the class. + * + * @example + * ``` + * Carbon::mixin(new class { + * public function addMoon() { + * return function () { + * return $this->addDays(30); + * }; + * } + * public function subMoon() { + * return function () { + * return $this->subDays(30); + * }; + * } + * }); + * $fullMoon = Carbon::create('2018-12-22'); + * $nextFullMoon = $fullMoon->addMoon(); + * $blackMoon = Carbon::create('2019-01-06'); + * $previousBlackMoon = $blackMoon->subMoon(); + * echo "$nextFullMoon\n"; + * echo "$previousBlackMoon\n"; + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + \is_string($mixin) && trait_exists($mixin) + ? self::loadMixinTrait($mixin) + : self::loadMixinClass($mixin); + } + + /** + * @param object|string $mixin + * + * @throws ReflectionException + */ + private static function loadMixinClass($mixin) + { + $methods = (new ReflectionClass($mixin))->getMethods( + ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED + ); + + foreach ($methods as $method) { + if ($method->isConstructor() || $method->isDestructor()) { + continue; + } + + $method->setAccessible(true); + + static::macro($method->name, $method->invoke($mixin)); + } + } + + /** + * @param string $trait + */ + private static function loadMixinTrait($trait) + { + $context = eval(self::getAnonymousClassCodeForTrait($trait)); + $className = \get_class($context); + $baseClass = static::class; + + foreach (self::getMixableMethods($context) as $name) { + $closureBase = Closure::fromCallable([$context, $name]); + + static::macro($name, function (...$parameters) use ($closureBase, $className, $baseClass) { + $downContext = isset($this) ? ($this) : new $baseClass(); + $context = isset($this) ? $this->cast($className) : new $className(); + + try { + // @ is required to handle error if not converted into exceptions + $closure = @$closureBase->bindTo($context); + } catch (Throwable $throwable) { // @codeCoverageIgnore + $closure = $closureBase; // @codeCoverageIgnore + } + + // in case of errors not converted into exceptions + $closure = $closure ?: $closureBase; + + $result = $closure(...$parameters); + + if (!($result instanceof $className)) { + return $result; + } + + if ($downContext instanceof CarbonInterface && $result instanceof CarbonInterface) { + if ($context !== $result) { + $downContext = $downContext->copy(); + } + + return $downContext + ->setTimezone($result->getTimezone()) + ->modify($result->format('Y-m-d H:i:s.u')) + ->settings($result->getSettings()); + } + + if ($downContext instanceof CarbonInterval && $result instanceof CarbonInterval) { + if ($context !== $result) { + $downContext = $downContext->copy(); + } + + $downContext->copyProperties($result); + self::copyStep($downContext, $result); + self::copyNegativeUnits($downContext, $result); + + return $downContext->settings($result->getSettings()); + } + + if ($downContext instanceof CarbonPeriod && $result instanceof CarbonPeriod) { + if ($context !== $result) { + $downContext = $downContext->copy(); + } + + return $downContext + ->setDates($result->getStartDate(), $result->getEndDate()) + ->setRecurrences($result->getRecurrences()) + ->setOptions($result->getOptions()) + ->settings($result->getSettings()); + } + + return $result; + }); + } + } + + private static function getAnonymousClassCodeForTrait(string $trait) + { + return 'return new class() extends '.static::class.' {use '.$trait.';};'; + } + + private static function getMixableMethods(self $context): Generator + { + foreach (get_class_methods($context) as $name) { + if (method_exists(static::class, $name)) { + continue; + } + + yield $name; + } + } + + /** + * Stack a Carbon context from inside calls of self::this() and execute a given action. + * + * @param static|null $context + * @param callable $callable + * + * @throws Throwable + * + * @return mixed + */ + protected static function bindMacroContext($context, callable $callable) + { + static::$macroContextStack[] = $context; + + try { + return $callable(); + } finally { + array_pop(static::$macroContextStack); + } + } + + /** + * Return the current context from inside a macro callee or a null if static. + * + * @return static|null + */ + protected static function context() + { + return end(static::$macroContextStack) ?: null; + } + + /** + * Return the current context from inside a macro callee or a new one if static. + * + * @return static + */ + protected static function this() + { + return end(static::$macroContextStack) ?: new static(); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Modifiers.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Modifiers.php new file mode 100644 index 0000000000..39343d8fa8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Modifiers.php @@ -0,0 +1,472 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use ReturnTypeWillChange; + +/** + * Trait Modifiers. + * + * Returns dates relative to current date using modifier short-hand. + */ +trait Modifiers +{ + /** + * Midday/noon hour. + * + * @var int + */ + protected static $midDayAt = 12; + + /** + * get midday/noon hour + * + * @return int + */ + public static function getMidDayAt() + { + return static::$midDayAt; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * + * Set midday/noon hour + * + * @param int $hour midday hour + * + * @return void + */ + public static function setMidDayAt($hour) + { + static::$midDayAt = $hour; + } + + /** + * Modify to midday, default to self::$midDayAt + * + * @return static + */ + public function midDay() + { + return $this->setTime(static::$midDayAt, 0, 0, 0); + } + + /** + * Modify to the next occurrence of a given modifier such as a day of + * the week. If no modifier is provided, modify to the next occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param string|int|null $modifier + * + * @return static|false + */ + public function next($modifier = null) + { + if ($modifier === null) { + $modifier = $this->dayOfWeek; + } + + return $this->change( + 'next '.(\is_string($modifier) ? $modifier : static::$days[$modifier]) + ); + } + + /** + * Go forward or backward to the next week- or weekend-day. + * + * @param bool $weekday + * @param bool $forward + * + * @return static + */ + private function nextOrPreviousDay($weekday = true, $forward = true) + { + /** @var CarbonInterface $date */ + $date = $this; + $step = $forward ? 1 : -1; + + do { + $date = $date->addDays($step); + } while ($weekday ? $date->isWeekend() : $date->isWeekday()); + + return $date; + } + + /** + * Go forward to the next weekday. + * + * @return static + */ + public function nextWeekday() + { + return $this->nextOrPreviousDay(); + } + + /** + * Go backward to the previous weekday. + * + * @return static + */ + public function previousWeekday() + { + return $this->nextOrPreviousDay(true, false); + } + + /** + * Go forward to the next weekend day. + * + * @return static + */ + public function nextWeekendDay() + { + return $this->nextOrPreviousDay(false); + } + + /** + * Go backward to the previous weekend day. + * + * @return static + */ + public function previousWeekendDay() + { + return $this->nextOrPreviousDay(false, false); + } + + /** + * Modify to the previous occurrence of a given modifier such as a day of + * the week. If no dayOfWeek is provided, modify to the previous occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param string|int|null $modifier + * + * @return static|false + */ + public function previous($modifier = null) + { + if ($modifier === null) { + $modifier = $this->dayOfWeek; + } + + return $this->change( + 'last '.(\is_string($modifier) ? $modifier : static::$days[$modifier]) + ); + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * first day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function firstOfMonth($dayOfWeek = null) + { + $date = $this->startOfDay(); + + if ($dayOfWeek === null) { + return $date->day(1); + } + + return $date->modify('first '.static::$days[$dayOfWeek].' of '.$date->rawFormat('F').' '.$date->year); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * last day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function lastOfMonth($dayOfWeek = null) + { + $date = $this->startOfDay(); + + if ($dayOfWeek === null) { + return $date->day($date->daysInMonth); + } + + return $date->modify('last '.static::$days[$dayOfWeek].' of '.$date->rawFormat('F').' '.$date->year); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current month. If the calculated occurrence is outside the scope + * of the current month, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfMonth($nth, $dayOfWeek) + { + $date = $this->avoidMutation()->firstOfMonth(); + $check = $date->rawFormat('Y-m'); + $date = $date->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return $date->rawFormat('Y-m') === $check ? $this->modify((string) $date) : false; + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * first day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfQuarter($dayOfWeek = null) + { + return $this->setDate($this->year, $this->quarter * static::MONTHS_PER_QUARTER - 2, 1)->firstOfMonth($dayOfWeek); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * last day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfQuarter($dayOfWeek = null) + { + return $this->setDate($this->year, $this->quarter * static::MONTHS_PER_QUARTER, 1)->lastOfMonth($dayOfWeek); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current quarter. If the calculated occurrence is outside the scope + * of the current quarter, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfQuarter($nth, $dayOfWeek) + { + $date = $this->avoidMutation()->day(1)->month($this->quarter * static::MONTHS_PER_QUARTER); + $lastMonth = $date->month; + $year = $date->year; + $date = $date->firstOfQuarter()->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return ($lastMonth < $date->month || $year !== $date->year) ? false : $this->modify((string) $date); + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * first day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfYear($dayOfWeek = null) + { + return $this->month(1)->firstOfMonth($dayOfWeek); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * last day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfYear($dayOfWeek = null) + { + return $this->month(static::MONTHS_PER_YEAR)->lastOfMonth($dayOfWeek); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current year. If the calculated occurrence is outside the scope + * of the current year, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfYear($nth, $dayOfWeek) + { + $date = $this->avoidMutation()->firstOfYear()->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return $this->year === $date->year ? $this->modify((string) $date) : false; + } + + /** + * Modify the current instance to the average of a given instance (default now) and the current instance + * (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date + * + * @return static + */ + public function average($date = null) + { + return $this->addRealMicroseconds((int) ($this->diffInRealMicroseconds($this->resolveCarbon($date), false) / 2)); + } + + /** + * Get the closest date from the instance (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function closest($date1, $date2) + { + return $this->diffInRealMicroseconds($date1) < $this->diffInRealMicroseconds($date2) ? $date1 : $date2; + } + + /** + * Get the farthest date from the instance (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function farthest($date1, $date2) + { + return $this->diffInRealMicroseconds($date1) > $this->diffInRealMicroseconds($date2) ? $date1 : $date2; + } + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return static + */ + public function min($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->lt($date) ? $this : $date; + } + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see min() + * + * @return static + */ + public function minimum($date = null) + { + return $this->min($date); + } + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return static + */ + public function max($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->gt($date) ? $this : $date; + } + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see max() + * + * @return static + */ + public function maximum($date = null) + { + return $this->max($date); + } + + /** + * Calls \DateTime::modify if mutable or \DateTimeImmutable::modify else. + * + * @see https://php.net/manual/en/datetime.modify.php + * + * @return static|false + */ + #[ReturnTypeWillChange] + public function modify($modify) + { + return parent::modify((string) $modify); + } + + /** + * Similar to native modify() method of DateTime but can handle more grammars. + * + * @example + * ``` + * echo Carbon::now()->change('next 2pm'); + * ``` + * + * @link https://php.net/manual/en/datetime.modify.php + * + * @param string $modifier + * + * @return static|false + */ + public function change($modifier) + { + return $this->modify(preg_replace_callback('/^(next|previous|last)\s+(\d{1,2}(h|am|pm|:\d{1,2}(:\d{1,2})?))$/i', function ($match) { + $match[2] = str_replace('h', ':00', $match[2]); + $test = $this->avoidMutation()->modify($match[2]); + $method = $match[1] === 'next' ? 'lt' : 'gt'; + $match[1] = $test->$method($this) ? $match[1].' day' : 'today'; + + return $match[1].' '.$match[2]; + }, strtr(trim($modifier), [ + ' at ' => ' ', + 'just now' => 'now', + 'after tomorrow' => 'tomorrow +1 day', + 'before yesterday' => 'yesterday -1 day', + ]))); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Mutability.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Mutability.php new file mode 100644 index 0000000000..561c867d0f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Mutability.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; + +/** + * Trait Mutability. + * + * Utils to know if the current object is mutable or immutable and convert it. + */ +trait Mutability +{ + use Cast; + + /** + * Returns true if the current class/instance is mutable. + * + * @return bool + */ + public static function isMutable() + { + return false; + } + + /** + * Returns true if the current class/instance is immutable. + * + * @return bool + */ + public static function isImmutable() + { + return !static::isMutable(); + } + + /** + * Return a mutable copy of the instance. + * + * @return Carbon + */ + public function toMutable() + { + /** @var Carbon $date */ + $date = $this->cast(Carbon::class); + + return $date; + } + + /** + * Return a immutable copy of the instance. + * + * @return CarbonImmutable + */ + public function toImmutable() + { + /** @var CarbonImmutable $date */ + $date = $this->cast(CarbonImmutable::class); + + return $date; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/ObjectInitialisation.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/ObjectInitialisation.php new file mode 100644 index 0000000000..c77a102444 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/ObjectInitialisation.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +trait ObjectInitialisation +{ + /** + * True when parent::__construct has been called. + * + * @var string + */ + protected $constructedObjectId; +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Options.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Options.php new file mode 100644 index 0000000000..48f9739773 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Options.php @@ -0,0 +1,471 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use DateTimeInterface; +use Throwable; + +/** + * Trait Options. + * + * Embed base methods to change settings of Carbon classes. + * + * Depends on the following methods: + * + * @method static shiftTimezone($timezone) Set the timezone + */ +trait Options +{ + use Localization; + + /** + * Customizable PHP_INT_SIZE override. + * + * @var int + */ + public static $PHPIntSize = PHP_INT_SIZE; + + /** + * First day of week. + * + * @var int|string + */ + protected static $weekStartsAt = CarbonInterface::MONDAY; + + /** + * Last day of week. + * + * @var int|string + */ + protected static $weekEndsAt = CarbonInterface::SUNDAY; + + /** + * Days of weekend. + * + * @var array + */ + protected static $weekendDays = [ + CarbonInterface::SATURDAY, + CarbonInterface::SUNDAY, + ]; + + /** + * Format regex patterns. + * + * @var array + */ + protected static $regexFormats = [ + 'd' => '(3[01]|[12][0-9]|0[1-9])', + 'D' => '(Sun|Mon|Tue|Wed|Thu|Fri|Sat)', + 'j' => '([123][0-9]|[1-9])', + 'l' => '([a-zA-Z]{2,})', + 'N' => '([1-7])', + 'S' => '(st|nd|rd|th)', + 'w' => '([0-6])', + 'z' => '(36[0-5]|3[0-5][0-9]|[12][0-9]{2}|[1-9]?[0-9])', + 'W' => '(5[012]|[1-4][0-9]|0?[1-9])', + 'F' => '([a-zA-Z]{2,})', + 'm' => '(1[012]|0[1-9])', + 'M' => '([a-zA-Z]{3})', + 'n' => '(1[012]|[1-9])', + 't' => '(2[89]|3[01])', + 'L' => '(0|1)', + 'o' => '([1-9][0-9]{0,4})', + 'Y' => '([1-9]?[0-9]{4})', + 'y' => '([0-9]{2})', + 'a' => '(am|pm)', + 'A' => '(AM|PM)', + 'B' => '([0-9]{3})', + 'g' => '(1[012]|[1-9])', + 'G' => '(2[0-3]|1?[0-9])', + 'h' => '(1[012]|0[1-9])', + 'H' => '(2[0-3]|[01][0-9])', + 'i' => '([0-5][0-9])', + 's' => '([0-5][0-9])', + 'u' => '([0-9]{1,6})', + 'v' => '([0-9]{1,3})', + 'e' => '([a-zA-Z]{1,5})|([a-zA-Z]*\\/[a-zA-Z]*)', + 'I' => '(0|1)', + 'O' => '([+-](1[012]|0[0-9])[0134][05])', + 'P' => '([+-](1[012]|0[0-9]):[0134][05])', + 'p' => '(Z|[+-](1[012]|0[0-9]):[0134][05])', + 'T' => '([a-zA-Z]{1,5})', + 'Z' => '(-?[1-5]?[0-9]{1,4})', + 'U' => '([0-9]*)', + + // The formats below are combinations of the above formats. + 'c' => '(([1-9]?[0-9]{4})-(1[012]|0[1-9])-(3[01]|[12][0-9]|0[1-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])[+-](1[012]|0[0-9]):([0134][05]))', // Y-m-dTH:i:sP + 'r' => '(([a-zA-Z]{3}), ([123][0-9]|0[1-9]) ([a-zA-Z]{3}) ([1-9]?[0-9]{4}) (2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]) [+-](1[012]|0[0-9])([0134][05]))', // D, d M Y H:i:s O + ]; + + /** + * Format modifiers (such as available in createFromFormat) regex patterns. + * + * @var array + */ + protected static $regexFormatModifiers = [ + '*' => '.+', + ' ' => '[ ]', + '#' => '[;:\\/.,()-]', + '?' => '([^a]|[a])', + '!' => '', + '|' => '', + '+' => '', + ]; + + /** + * Indicates if months should be calculated with overflow. + * Global setting. + * + * @var bool + */ + protected static $monthsOverflow = true; + + /** + * Indicates if years should be calculated with overflow. + * Global setting. + * + * @var bool + */ + protected static $yearsOverflow = true; + + /** + * Indicates if the strict mode is in use. + * Global setting. + * + * @var bool + */ + protected static $strictModeEnabled = true; + + /** + * Function to call instead of format. + * + * @var string|callable|null + */ + protected static $formatFunction; + + /** + * Function to call instead of createFromFormat. + * + * @var string|callable|null + */ + protected static $createFromFormatFunction; + + /** + * Function to call instead of parse. + * + * @var string|callable|null + */ + protected static $parseFunction; + + /** + * Indicates if months should be calculated with overflow. + * Specific setting. + * + * @var bool|null + */ + protected $localMonthsOverflow; + + /** + * Indicates if years should be calculated with overflow. + * Specific setting. + * + * @var bool|null + */ + protected $localYearsOverflow; + + /** + * Indicates if the strict mode is in use. + * Specific setting. + * + * @var bool|null + */ + protected $localStrictModeEnabled; + + /** + * Options for diffForHumans and forHumans methods. + * + * @var bool|null + */ + protected $localHumanDiffOptions; + + /** + * Format to use on string cast. + * + * @var string|null + */ + protected $localToStringFormat; + + /** + * Format to use on JSON serialization. + * + * @var string|null + */ + protected $localSerializer; + + /** + * Instance-specific macros. + * + * @var array|null + */ + protected $localMacros; + + /** + * Instance-specific generic macros. + * + * @var array|null + */ + protected $localGenericMacros; + + /** + * Function to call instead of format. + * + * @var string|callable|null + */ + protected $localFormatFunction; + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * Enable the strict mode (or disable with passing false). + * + * @param bool $strictModeEnabled + */ + public static function useStrictMode($strictModeEnabled = true) + { + static::$strictModeEnabled = $strictModeEnabled; + } + + /** + * Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * + * @return bool + */ + public static function isStrictModeEnabled() + { + return static::$strictModeEnabled; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if months should be calculated with overflow. + * + * @param bool $monthsOverflow + * + * @return void + */ + public static function useMonthsOverflow($monthsOverflow = true) + { + static::$monthsOverflow = $monthsOverflow; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetMonthsOverflow() + { + static::$monthsOverflow = true; + } + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowMonths() + { + return static::$monthsOverflow; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if years should be calculated with overflow. + * + * @param bool $yearsOverflow + * + * @return void + */ + public static function useYearsOverflow($yearsOverflow = true) + { + static::$yearsOverflow = $yearsOverflow; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetYearsOverflow() + { + static::$yearsOverflow = true; + } + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowYears() + { + return static::$yearsOverflow; + } + + /** + * Set specific options. + * - strictMode: true|false|null + * - monthOverflow: true|false|null + * - yearOverflow: true|false|null + * - humanDiffOptions: int|null + * - toStringFormat: string|Closure|null + * - toJsonFormat: string|Closure|null + * - locale: string|null + * - timezone: \DateTimeZone|string|int|null + * - macros: array|null + * - genericMacros: array|null + * + * @param array $settings + * + * @return $this|static + */ + public function settings(array $settings) + { + $this->localStrictModeEnabled = $settings['strictMode'] ?? null; + $this->localMonthsOverflow = $settings['monthOverflow'] ?? null; + $this->localYearsOverflow = $settings['yearOverflow'] ?? null; + $this->localHumanDiffOptions = $settings['humanDiffOptions'] ?? null; + $this->localToStringFormat = $settings['toStringFormat'] ?? null; + $this->localSerializer = $settings['toJsonFormat'] ?? null; + $this->localMacros = $settings['macros'] ?? null; + $this->localGenericMacros = $settings['genericMacros'] ?? null; + $this->localFormatFunction = $settings['formatFunction'] ?? null; + + if (isset($settings['locale'])) { + $locales = $settings['locale']; + + if (!\is_array($locales)) { + $locales = [$locales]; + } + + $this->locale(...$locales); + } + + if (isset($settings['innerTimezone'])) { + return $this->setTimezone($settings['innerTimezone']); + } + + if (isset($settings['timezone'])) { + return $this->shiftTimezone($settings['timezone']); + } + + return $this; + } + + /** + * Returns current local settings. + * + * @return array + */ + public function getSettings() + { + $settings = []; + $map = [ + 'localStrictModeEnabled' => 'strictMode', + 'localMonthsOverflow' => 'monthOverflow', + 'localYearsOverflow' => 'yearOverflow', + 'localHumanDiffOptions' => 'humanDiffOptions', + 'localToStringFormat' => 'toStringFormat', + 'localSerializer' => 'toJsonFormat', + 'localMacros' => 'macros', + 'localGenericMacros' => 'genericMacros', + 'locale' => 'locale', + 'tzName' => 'timezone', + 'localFormatFunction' => 'formatFunction', + ]; + + foreach ($map as $property => $key) { + $value = $this->$property ?? null; + + if ($value !== null && ($key !== 'locale' || $value !== 'en' || $this->localTranslator)) { + $settings[$key] = $value; + } + } + + return $settings; + } + + /** + * Show truthy properties on var_dump(). + * + * @return array + */ + public function __debugInfo() + { + $infos = array_filter(get_object_vars($this), static function ($var) { + return $var; + }); + + foreach (['dumpProperties', 'constructedObjectId', 'constructed'] as $property) { + if (isset($infos[$property])) { + unset($infos[$property]); + } + } + + $this->addExtraDebugInfos($infos); + + return $infos; + } + + protected function addExtraDebugInfos(&$infos): void + { + if ($this instanceof DateTimeInterface) { + try { + if (!isset($infos['date'])) { + $infos['date'] = $this->format(CarbonInterface::MOCK_DATETIME_FORMAT); + } + + if (!isset($infos['timezone'])) { + $infos['timezone'] = $this->tzName; + } + } catch (Throwable $exception) { + // noop + } + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Rounding.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Rounding.php new file mode 100644 index 0000000000..85ff5a711f --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Rounding.php @@ -0,0 +1,254 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use Carbon\Exceptions\UnknownUnitException; + +/** + * Trait Rounding. + * + * Round, ceil, floor units. + * + * Depends on the following methods: + * + * @method static copy() + * @method static startOfWeek(int $weekStartsAt = null) + */ +trait Rounding +{ + use IntervalRounding; + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int $precision + * @param string $function + * + * @return CarbonInterface + */ + public function roundUnit($unit, $precision = 1, $function = 'round') + { + $metaUnits = [ + // @call roundUnit + 'millennium' => [static::YEARS_PER_MILLENNIUM, 'year'], + // @call roundUnit + 'century' => [static::YEARS_PER_CENTURY, 'year'], + // @call roundUnit + 'decade' => [static::YEARS_PER_DECADE, 'year'], + // @call roundUnit + 'quarter' => [static::MONTHS_PER_QUARTER, 'month'], + // @call roundUnit + 'millisecond' => [1000, 'microsecond'], + ]; + $normalizedUnit = static::singularUnit($unit); + $ranges = array_merge(static::getRangesByUnit($this->daysInMonth), [ + // @call roundUnit + 'microsecond' => [0, 999999], + ]); + $factor = 1; + + if ($normalizedUnit === 'week') { + $normalizedUnit = 'day'; + $precision *= static::DAYS_PER_WEEK; + } + + if (isset($metaUnits[$normalizedUnit])) { + [$factor, $normalizedUnit] = $metaUnits[$normalizedUnit]; + } + + $precision *= $factor; + + if (!isset($ranges[$normalizedUnit])) { + throw new UnknownUnitException($unit); + } + + $found = false; + $fraction = 0; + $arguments = null; + $initialValue = null; + $factor = $this->year < 0 ? -1 : 1; + $changes = []; + $minimumInc = null; + + foreach ($ranges as $unit => [$minimum, $maximum]) { + if ($normalizedUnit === $unit) { + $arguments = [$this->$unit, $minimum]; + $initialValue = $this->$unit; + $fraction = $precision - floor($precision); + $found = true; + + continue; + } + + if ($found) { + $delta = $maximum + 1 - $minimum; + $factor /= $delta; + $fraction *= $delta; + $inc = ($this->$unit - $minimum) * $factor; + + if ($inc !== 0.0) { + $minimumInc = $minimumInc ?? ($arguments[0] / pow(2, 52)); + + // If value is still the same when adding a non-zero increment/decrement, + // it means precision got lost in the addition + if (abs($inc) < $minimumInc) { + $inc = $minimumInc * ($inc < 0 ? -1 : 1); + } + + // If greater than $precision, assume precision loss caused an overflow + if ($function !== 'floor' || abs($arguments[0] + $inc - $initialValue) >= $precision) { + $arguments[0] += $inc; + } + } + + $changes[$unit] = round( + $minimum + ($fraction ? $fraction * $function(($this->$unit - $minimum) / $fraction) : 0) + ); + + // Cannot use modulo as it lose double precision + while ($changes[$unit] >= $delta) { + $changes[$unit] -= $delta; + } + + $fraction -= floor($fraction); + } + } + + [$value, $minimum] = $arguments; + $normalizedValue = floor($function(($value - $minimum) / $precision) * $precision + $minimum); + + /** @var CarbonInterface $result */ + $result = $this; + + foreach ($changes as $unit => $value) { + $result = $result->$unit($value); + } + + return $result->$normalizedUnit($normalizedValue); + } + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function floorUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'floor'); + } + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function ceilUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'ceil'); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return CarbonInterface + */ + public function round($precision = 1, $function = 'round') + { + return $this->roundWith($precision, $function); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function floor($precision = 1) + { + return $this->round($precision, 'floor'); + } + + /** + * Ceil the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function ceil($precision = 1) + { + return $this->round($precision, 'ceil'); + } + + /** + * Round the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function roundWeek($weekStartsAt = null) + { + return $this->closest( + $this->avoidMutation()->floorWeek($weekStartsAt), + $this->avoidMutation()->ceilWeek($weekStartsAt) + ); + } + + /** + * Truncate the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function floorWeek($weekStartsAt = null) + { + return $this->startOfWeek($weekStartsAt); + } + + /** + * Ceil the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function ceilWeek($weekStartsAt = null) + { + if ($this->isMutable()) { + $startOfWeek = $this->avoidMutation()->startOfWeek($weekStartsAt); + + return $startOfWeek != $this ? + $this->startOfWeek($weekStartsAt)->addWeek() : + $this; + } + + $startOfWeek = $this->startOfWeek($weekStartsAt); + + return $startOfWeek != $this ? + $startOfWeek->addWeek() : + $this->avoidMutation(); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Serialization.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Serialization.php new file mode 100644 index 0000000000..c1d5c5e139 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Serialization.php @@ -0,0 +1,326 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Exceptions\InvalidFormatException; +use ReturnTypeWillChange; +use Throwable; + +/** + * Trait Serialization. + * + * Serialization and JSON stuff. + * + * Depends on the following properties: + * + * @property int $year + * @property int $month + * @property int $daysInMonth + * @property int $quarter + * + * Depends on the following methods: + * + * @method string|static locale(string $locale = null, string ...$fallbackLocales) + * @method string toJSON() + */ +trait Serialization +{ + use ObjectInitialisation; + + /** + * The custom Carbon JSON serializer. + * + * @var callable|null + */ + protected static $serializer; + + /** + * List of key to use for dump/serialization. + * + * @var string[] + */ + protected $dumpProperties = ['date', 'timezone_type', 'timezone']; + + /** + * Locale to dump comes here before serialization. + * + * @var string|null + */ + protected $dumpLocale; + + /** + * Embed date properties to dump in a dedicated variables so it won't overlap native + * DateTime ones. + * + * @var array|null + */ + protected $dumpDateProperties; + + /** + * Return a serialized string of the instance. + * + * @return string + */ + public function serialize() + { + return serialize($this); + } + + /** + * Create an instance from a serialized string. + * + * @param string $value + * + * @throws InvalidFormatException + * + * @return static + */ + public static function fromSerialized($value) + { + $instance = @unserialize((string) $value); + + if (!$instance instanceof static) { + throw new InvalidFormatException("Invalid serialized value: $value"); + } + + return $instance; + } + + /** + * The __set_state handler. + * + * @param string|array $dump + * + * @return static + */ + #[ReturnTypeWillChange] + public static function __set_state($dump) + { + if (\is_string($dump)) { + return static::parse($dump); + } + + /** @var \DateTimeInterface $date */ + $date = get_parent_class(static::class) && method_exists(parent::class, '__set_state') + ? parent::__set_state((array) $dump) + : (object) $dump; + + return static::instance($date); + } + + /** + * Returns the list of properties to dump on serialize() called on. + * + * Only used by PHP < 7.4. + * + * @return array + */ + public function __sleep() + { + $properties = $this->getSleepProperties(); + + if ($this->localTranslator ?? null) { + $properties[] = 'dumpLocale'; + $this->dumpLocale = $this->locale ?? null; + } + + return $properties; + } + + /** + * Returns the values to dump on serialize() called on. + * + * Only used by PHP >= 7.4. + * + * @return array + */ + public function __serialize(): array + { + // @codeCoverageIgnoreStart + if (isset($this->timezone_type, $this->timezone, $this->date)) { + return [ + 'date' => $this->date ?? null, + 'timezone_type' => $this->timezone_type, + 'timezone' => $this->timezone ?? null, + ]; + } + // @codeCoverageIgnoreEnd + + $timezone = $this->getTimezone(); + $export = [ + 'date' => $this->format('Y-m-d H:i:s.u'), + 'timezone_type' => $timezone->getType(), + 'timezone' => $timezone->getName(), + ]; + + // @codeCoverageIgnoreStart + if (\extension_loaded('msgpack') && isset($this->constructedObjectId)) { + $export['dumpDateProperties'] = [ + 'date' => $this->format('Y-m-d H:i:s.u'), + 'timezone' => serialize($this->timezone ?? null), + ]; + } + // @codeCoverageIgnoreEnd + + if ($this->localTranslator ?? null) { + $export['dumpLocale'] = $this->locale ?? null; + } + + return $export; + } + + /** + * Set locale if specified on unserialize() called. + * + * Only used by PHP < 7.4. + * + * @return void + */ + #[ReturnTypeWillChange] + public function __wakeup() + { + if (parent::class && method_exists(parent::class, '__wakeup')) { + // @codeCoverageIgnoreStart + try { + parent::__wakeup(); + } catch (Throwable $exception) { + try { + // FatalError occurs when calling msgpack_unpack() in PHP 7.4 or later. + ['date' => $date, 'timezone' => $timezone] = $this->dumpDateProperties; + parent::__construct($date, unserialize($timezone)); + } catch (Throwable $ignoredException) { + throw $exception; + } + } + // @codeCoverageIgnoreEnd + } + + $this->constructedObjectId = spl_object_hash($this); + + if (isset($this->dumpLocale)) { + $this->locale($this->dumpLocale); + $this->dumpLocale = null; + } + + $this->cleanupDumpProperties(); + } + + /** + * Set locale if specified on unserialize() called. + * + * Only used by PHP >= 7.4. + * + * @return void + */ + public function __unserialize(array $data): void + { + // @codeCoverageIgnoreStart + try { + $this->__construct($data['date'] ?? null, $data['timezone'] ?? null); + } catch (Throwable $exception) { + if (!isset($data['dumpDateProperties']['date'], $data['dumpDateProperties']['timezone'])) { + throw $exception; + } + + try { + // FatalError occurs when calling msgpack_unpack() in PHP 7.4 or later. + ['date' => $date, 'timezone' => $timezone] = $data['dumpDateProperties']; + $this->__construct($date, unserialize($timezone)); + } catch (Throwable $ignoredException) { + throw $exception; + } + } + // @codeCoverageIgnoreEnd + + if (isset($data['dumpLocale'])) { + $this->locale($data['dumpLocale']); + } + } + + /** + * Prepare the object for JSON serialization. + * + * @return array|string + */ + #[ReturnTypeWillChange] + public function jsonSerialize() + { + $serializer = $this->localSerializer ?? static::$serializer; + + if ($serializer) { + return \is_string($serializer) + ? $this->rawFormat($serializer) + : $serializer($this); + } + + return $this->toJSON(); + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform Carbon object before the serialization. + * + * JSON serialize all Carbon instances using the given callback. + * + * @param callable $callback + * + * @return void + */ + public static function serializeUsing($callback) + { + static::$serializer = $callback; + } + + /** + * Cleanup properties attached to the public scope of DateTime when a dump of the date is requested. + * foreach ($date as $_) {} + * serializer($date) + * var_export($date) + * get_object_vars($date) + */ + public function cleanupDumpProperties() + { + // @codeCoverageIgnoreStart + if (PHP_VERSION < 8.2) { + foreach ($this->dumpProperties as $property) { + if (isset($this->$property)) { + unset($this->$property); + } + } + } + // @codeCoverageIgnoreEnd + + return $this; + } + + private function getSleepProperties(): array + { + $properties = $this->dumpProperties; + + // @codeCoverageIgnoreStart + if (!\extension_loaded('msgpack')) { + return $properties; + } + + if (isset($this->constructedObjectId)) { + $this->dumpDateProperties = [ + 'date' => $this->format('Y-m-d H:i:s.u'), + 'timezone' => serialize($this->timezone ?? null), + ]; + + $properties[] = 'dumpDateProperties'; + } + + return $properties; + // @codeCoverageIgnoreEnd + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Test.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Test.php new file mode 100644 index 0000000000..f23c72e8fd --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Test.php @@ -0,0 +1,228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use Carbon\CarbonTimeZone; +use Closure; +use DateTimeImmutable; +use DateTimeInterface; +use InvalidArgumentException; +use Throwable; + +trait Test +{ + /////////////////////////////////////////////////////////////////// + ///////////////////////// TESTING AIDS //////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * A test Carbon instance to be returned when now instances are created. + * + * @var Closure|static|null + */ + protected static $testNow; + + /** + * The timezone to resto to when clearing the time mock. + * + * @var string|null + */ + protected static $testDefaultTimezone; + + /** + * Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + */ + public static function setTestNow($testNow = null) + { + static::$testNow = $testNow instanceof self || $testNow instanceof Closure + ? $testNow + : static::make($testNow); + } + + /** + * Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + */ + public static function setTestNowAndTimezone($testNow = null, $tz = null) + { + if ($testNow) { + self::$testDefaultTimezone = self::$testDefaultTimezone ?? date_default_timezone_get(); + } + + $useDateInstanceTimezone = $testNow instanceof DateTimeInterface; + + if ($useDateInstanceTimezone) { + self::setDefaultTimezone($testNow->getTimezone()->getName(), $testNow); + } + + static::setTestNow($testNow); + + if (!$useDateInstanceTimezone) { + $now = static::getMockedTestNow(\func_num_args() === 1 ? null : $tz); + $tzName = $now ? $now->tzName : null; + self::setDefaultTimezone($tzName ?? self::$testDefaultTimezone ?? 'UTC', $now); + } + + if (!$testNow) { + self::$testDefaultTimezone = null; + } + } + + /** + * Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * + * /!\ Use this method for unit tests only. + * + * @template T + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + * @param Closure(): T $callback + * + * @return T + */ + public static function withTestNow($testNow, $callback) + { + static::setTestNow($testNow); + + try { + $result = $callback(); + } finally { + static::setTestNow(); + } + + return $result; + } + + /** + * Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * + * @return Closure|static the current instance used for testing + */ + public static function getTestNow() + { + return static::$testNow; + } + + /** + * Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * + * @return bool true if there is a test instance, otherwise false + */ + public static function hasTestNow() + { + return static::getTestNow() !== null; + } + + /** + * Get the mocked date passed in setTestNow() and if it's a Closure, execute it. + * + * @param string|\DateTimeZone $tz + * + * @return \Carbon\CarbonImmutable|\Carbon\Carbon|null + */ + protected static function getMockedTestNow($tz) + { + $testNow = static::getTestNow(); + + if ($testNow instanceof Closure) { + $realNow = new DateTimeImmutable('now'); + $testNow = $testNow(static::parse( + $realNow->format('Y-m-d H:i:s.u'), + $tz ?: $realNow->getTimezone() + )); + } + /* @var \Carbon\CarbonImmutable|\Carbon\Carbon|null $testNow */ + + return $testNow instanceof CarbonInterface + ? $testNow->avoidMutation()->tz($tz) + : $testNow; + } + + protected static function mockConstructorParameters(&$time, $tz) + { + /** @var \Carbon\CarbonImmutable|\Carbon\Carbon $testInstance */ + $testInstance = clone static::getMockedTestNow($tz); + + if (static::hasRelativeKeywords($time)) { + $testInstance = $testInstance->modify($time); + } + + $time = $testInstance instanceof self + ? $testInstance->rawFormat(static::MOCK_DATETIME_FORMAT) + : $testInstance->format(static::MOCK_DATETIME_FORMAT); + } + + private static function setDefaultTimezone($timezone, DateTimeInterface $date = null) + { + $previous = null; + $success = false; + + try { + $success = date_default_timezone_set($timezone); + } catch (Throwable $exception) { + $previous = $exception; + } + + if (!$success) { + $suggestion = @CarbonTimeZone::create($timezone)->toRegionName($date); + + throw new InvalidArgumentException( + "Timezone ID '$timezone' is invalid". + ($suggestion && $suggestion !== $timezone ? ", did you mean '$suggestion'?" : '.')."\n". + "It must be one of the IDs from DateTimeZone::listIdentifiers(),\n". + 'For the record, hours/minutes offset are relevant only for a particular moment, '. + 'but not as a default timezone.', + 0, + $previous + ); + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Timestamp.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Timestamp.php new file mode 100644 index 0000000000..88a465c938 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Timestamp.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +/** + * Trait Timestamp. + */ +trait Timestamp +{ + /** + * Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestamp($timestamp, $tz = null) + { + return static::createFromTimestampUTC($timestamp)->setTimezone($tz); + } + + /** + * Create a Carbon instance from an timestamp keeping the timezone to UTC. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampUTC($timestamp) + { + [$integer, $decimal] = self::getIntegerAndDecimalParts($timestamp); + $delta = floor($decimal / static::MICROSECONDS_PER_SECOND); + $integer += $delta; + $decimal -= $delta * static::MICROSECONDS_PER_SECOND; + $decimal = str_pad((string) $decimal, 6, '0', STR_PAD_LEFT); + + return static::rawCreateFromFormat('U u', "$integer $decimal"); + } + + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampMsUTC($timestamp) + { + [$milliseconds, $microseconds] = self::getIntegerAndDecimalParts($timestamp, 3); + $sign = $milliseconds < 0 || ($milliseconds === 0.0 && $microseconds < 0) ? -1 : 1; + $milliseconds = abs($milliseconds); + $microseconds = $sign * abs($microseconds) + static::MICROSECONDS_PER_MILLISECOND * ($milliseconds % static::MILLISECONDS_PER_SECOND); + $seconds = $sign * floor($milliseconds / static::MILLISECONDS_PER_SECOND); + $delta = floor($microseconds / static::MICROSECONDS_PER_SECOND); + $seconds += $delta; + $microseconds -= $delta * static::MICROSECONDS_PER_SECOND; + $microseconds = str_pad($microseconds, 6, '0', STR_PAD_LEFT); + + return static::rawCreateFromFormat('U u', "$seconds $microseconds"); + } + + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestampMs($timestamp, $tz = null) + { + return static::createFromTimestampMsUTC($timestamp) + ->setTimezone($tz); + } + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + public function timestamp($unixTimestamp) + { + return $this->setTimestamp($unixTimestamp); + } + + /** + * Returns a timestamp rounded with the given precision (6 by default). + * + * @example getPreciseTimestamp() 1532087464437474 (microsecond maximum precision) + * @example getPreciseTimestamp(6) 1532087464437474 + * @example getPreciseTimestamp(5) 153208746443747 (1/100000 second precision) + * @example getPreciseTimestamp(4) 15320874644375 (1/10000 second precision) + * @example getPreciseTimestamp(3) 1532087464437 (millisecond precision) + * @example getPreciseTimestamp(2) 153208746444 (1/100 second precision) + * @example getPreciseTimestamp(1) 15320874644 (1/10 second precision) + * @example getPreciseTimestamp(0) 1532087464 (second precision) + * @example getPreciseTimestamp(-1) 153208746 (10 second precision) + * @example getPreciseTimestamp(-2) 15320875 (100 second precision) + * + * @param int $precision + * + * @return float + */ + public function getPreciseTimestamp($precision = 6) + { + return round(((float) $this->rawFormat('Uu')) / pow(10, 6 - $precision)); + } + + /** + * Returns the milliseconds timestamps used amongst other by Date javascript objects. + * + * @return float + */ + public function valueOf() + { + return $this->getPreciseTimestamp(3); + } + + /** + * Returns the timestamp with millisecond precision. + * + * @return int + */ + public function getTimestampMs() + { + return (int) $this->getPreciseTimestamp(3); + } + + /** + * @alias getTimestamp + * + * Returns the UNIX timestamp for the current date. + * + * @return int + */ + public function unix() + { + return $this->getTimestamp(); + } + + /** + * Return an array with integer part digits and decimals digits split from one or more positive numbers + * (such as timestamps) as string with the given number of decimals (6 by default). + * + * By splitting integer and decimal, this method obtain a better precision than + * number_format when the input is a string. + * + * @param float|int|string $numbers one or more numbers + * @param int $decimals number of decimals precision (6 by default) + * + * @return array 0-index is integer part, 1-index is decimal part digits + */ + private static function getIntegerAndDecimalParts($numbers, $decimals = 6) + { + if (\is_int($numbers) || \is_float($numbers)) { + $numbers = number_format($numbers, $decimals, '.', ''); + } + + $sign = str_starts_with($numbers, '-') ? -1 : 1; + $integer = 0; + $decimal = 0; + + foreach (preg_split('`[^\d.]+`', $numbers) as $chunk) { + [$integerPart, $decimalPart] = explode('.', "$chunk."); + + $integer += (int) $integerPart; + $decimal += (float) ("0.$decimalPart"); + } + + $overflow = floor($decimal); + $integer += $overflow; + $decimal -= $overflow; + + return [$sign * $integer, $decimal === 0.0 ? 0.0 : $sign * round($decimal * pow(10, $decimals))]; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/ToStringFormat.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/ToStringFormat.php new file mode 100644 index 0000000000..a81164f995 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/ToStringFormat.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Closure; + +/** + * Trait ToStringFormat. + * + * Handle global format customization for string cast of the object. + */ +trait ToStringFormat +{ + /** + * Format to use for __toString method when type juggling occurs. + * + * @var string|Closure|null + */ + protected static $toStringFormat; + + /** + * Reset the format used to the default when type juggling a Carbon instance to a string + * + * @return void + */ + public static function resetToStringFormat() + { + static::setToStringFormat(null); + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump another string + * format. + * + * Set the default format used when type juggling a Carbon instance to a string. + * + * @param string|Closure|null $format + * + * @return void + */ + public static function setToStringFormat($format) + { + static::$toStringFormat = $format; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Units.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Units.php new file mode 100644 index 0000000000..5be14ec7ef --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Units.php @@ -0,0 +1,412 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonConverterInterface; +use Carbon\CarbonInterface; +use Carbon\CarbonInterval; +use Carbon\Exceptions\UnitException; +use Closure; +use DateInterval; +use DateMalformedStringException; +use ReturnTypeWillChange; + +/** + * Trait Units. + * + * Add, subtract and set units. + */ +trait Units +{ + /** + * Add seconds to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param string $unit + * @param int $value + * + * @return static + */ + public function addRealUnit($unit, $value = 1) + { + switch ($unit) { + // @call addRealUnit + case 'micro': + + // @call addRealUnit + case 'microsecond': + /* @var CarbonInterface $this */ + $diff = $this->microsecond + $value; + $time = $this->getTimestamp(); + $seconds = (int) floor($diff / static::MICROSECONDS_PER_SECOND); + $time += $seconds; + $diff -= $seconds * static::MICROSECONDS_PER_SECOND; + $microtime = str_pad((string) $diff, 6, '0', STR_PAD_LEFT); + $tz = $this->tz; + + return $this->tz('UTC')->modify("@$time.$microtime")->tz($tz); + + // @call addRealUnit + case 'milli': + // @call addRealUnit + case 'millisecond': + return $this->addRealUnit('microsecond', $value * static::MICROSECONDS_PER_MILLISECOND); + + // @call addRealUnit + case 'second': + break; + + // @call addRealUnit + case 'minute': + $value *= static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'hour': + $value *= static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'day': + $value *= static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'week': + $value *= static::DAYS_PER_WEEK * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'month': + $value *= 30 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'quarter': + $value *= static::MONTHS_PER_QUARTER * 30 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'year': + $value *= 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'decade': + $value *= static::YEARS_PER_DECADE * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'century': + $value *= static::YEARS_PER_CENTURY * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'millennium': + $value *= static::YEARS_PER_MILLENNIUM * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + default: + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new UnitException("Invalid unit for real timestamp add/sub: '$unit'"); + } + + return $this; + } + + /* @var CarbonInterface $this */ + return $this->setTimestamp((int) ($this->getTimestamp() + $value)); + } + + public function subRealUnit($unit, $value = 1) + { + return $this->addRealUnit($unit, -$value); + } + + /** + * Returns true if a property can be changed via setter. + * + * @param string $unit + * + * @return bool + */ + public static function isModifiableUnit($unit) + { + static $modifiableUnits = [ + // @call addUnit + 'millennium', + // @call addUnit + 'century', + // @call addUnit + 'decade', + // @call addUnit + 'quarter', + // @call addUnit + 'week', + // @call addUnit + 'weekday', + ]; + + return \in_array($unit, $modifiableUnits, true) || \in_array($unit, static::$units, true); + } + + /** + * Call native PHP DateTime/DateTimeImmutable add() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawAdd(DateInterval $interval) + { + return parent::add($interval); + } + + /** + * Add given units or interval to the current instance. + * + * @example $date->add('hour', 3) + * @example $date->add(15, 'days') + * @example $date->add(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function add($unit, $value = 1, $overflow = null) + { + if (\is_string($unit) && \func_num_args() === 1) { + $unit = CarbonInterval::make($unit, [], true); + } + + if ($unit instanceof CarbonConverterInterface) { + return $this->resolveCarbon($unit->convertDate($this, false)); + } + + if ($unit instanceof Closure) { + return $this->resolveCarbon($unit($this, false)); + } + + if ($unit instanceof DateInterval) { + return parent::add($unit); + } + + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + return $this->addUnit($unit, $value, $overflow); + } + + /** + * Add given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function addUnit($unit, $value = 1, $overflow = null) + { + $originalArgs = \func_get_args(); + + $date = $this; + + if (!is_numeric($value) || !(float) $value) { + return $date->isMutable() ? $date : $date->avoidMutation(); + } + + $unit = self::singularUnit($unit); + $metaUnits = [ + 'millennium' => [static::YEARS_PER_MILLENNIUM, 'year'], + 'century' => [static::YEARS_PER_CENTURY, 'year'], + 'decade' => [static::YEARS_PER_DECADE, 'year'], + 'quarter' => [static::MONTHS_PER_QUARTER, 'month'], + ]; + + if (isset($metaUnits[$unit])) { + [$factor, $unit] = $metaUnits[$unit]; + $value *= $factor; + } + + if ($unit === 'weekday') { + $weekendDays = static::getWeekendDays(); + + if ($weekendDays !== [static::SATURDAY, static::SUNDAY]) { + $absoluteValue = abs($value); + $sign = $value / max(1, $absoluteValue); + $weekDaysCount = 7 - min(6, \count(array_unique($weekendDays))); + $weeks = floor($absoluteValue / $weekDaysCount); + + for ($diff = $absoluteValue % $weekDaysCount; $diff; $diff--) { + /** @var static $date */ + $date = $date->addDays($sign); + + while (\in_array($date->dayOfWeek, $weekendDays, true)) { + $date = $date->addDays($sign); + } + } + + $value = $weeks * $sign; + $unit = 'week'; + } + + $timeString = $date->toTimeString(); + } elseif ($canOverflow = (\in_array($unit, [ + 'month', + 'year', + ]) && ($overflow === false || ( + $overflow === null && + ($ucUnit = ucfirst($unit).'s') && + !($this->{'local'.$ucUnit.'Overflow'} ?? static::{'shouldOverflow'.$ucUnit}()) + )))) { + $day = $date->day; + } + + $value = (int) $value; + + if ($unit === 'milli' || $unit === 'millisecond') { + $unit = 'microsecond'; + $value *= static::MICROSECONDS_PER_MILLISECOND; + } + + // Work-around for bug https://bugs.php.net/bug.php?id=75642 + if ($unit === 'micro' || $unit === 'microsecond') { + $microseconds = $this->micro + $value; + $second = (int) floor($microseconds / static::MICROSECONDS_PER_SECOND); + $microseconds %= static::MICROSECONDS_PER_SECOND; + if ($microseconds < 0) { + $microseconds += static::MICROSECONDS_PER_SECOND; + } + $date = $date->microseconds($microseconds); + $unit = 'second'; + $value = $second; + } + + try { + $date = $date->modify("$value $unit"); + + if (isset($timeString)) { + $date = $date->setTimeFromTimeString($timeString); + } elseif (isset($canOverflow, $day) && $canOverflow && $day !== $date->day) { + $date = $date->modify('last day of previous month'); + } + } catch (DateMalformedStringException $ignoredException) { // @codeCoverageIgnore + $date = null; // @codeCoverageIgnore + } + + if (!$date) { + throw new UnitException('Unable to add unit '.var_export($originalArgs, true)); + } + + return $date; + } + + /** + * Subtract given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subUnit($unit, $value = 1, $overflow = null) + { + return $this->addUnit($unit, -$value, $overflow); + } + + /** + * Call native PHP DateTime/DateTimeImmutable sub() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawSub(DateInterval $interval) + { + return parent::sub($interval); + } + + /** + * Subtract given units or interval to the current instance. + * + * @example $date->sub('hour', 3) + * @example $date->sub(15, 'days') + * @example $date->sub(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function sub($unit, $value = 1, $overflow = null) + { + if (\is_string($unit) && \func_num_args() === 1) { + $unit = CarbonInterval::make($unit, [], true); + } + + if ($unit instanceof CarbonConverterInterface) { + return $this->resolveCarbon($unit->convertDate($this, true)); + } + + if ($unit instanceof Closure) { + return $this->resolveCarbon($unit($this, true)); + } + + if ($unit instanceof DateInterval) { + return parent::sub($unit); + } + + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + return $this->addUnit($unit, -(float) $value, $overflow); + } + + /** + * Subtract given units or interval to the current instance. + * + * @see sub() + * + * @param string|DateInterval $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subtract($unit, $value = 1, $overflow = null) + { + if (\is_string($unit) && \func_num_args() === 1) { + $unit = CarbonInterval::make($unit, [], true); + } + + return $this->sub($unit, $value, $overflow); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Week.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Week.php new file mode 100644 index 0000000000..6f1481456a --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Week.php @@ -0,0 +1,219 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +/** + * Trait Week. + * + * week and ISO week number, year and count in year. + * + * Depends on the following properties: + * + * @property int $daysInYear + * @property int $dayOfWeek + * @property int $dayOfYear + * @property int $year + * + * Depends on the following methods: + * + * @method static addWeeks(int $weeks = 1) + * @method static copy() + * @method static dayOfYear(int $dayOfYear) + * @method string getTranslationMessage(string $key, ?string $locale = null, ?string $default = null, $translator = null) + * @method static next(int|string $day = null) + * @method static startOfWeek(int $day = 1) + * @method static subWeeks(int $weeks = 1) + * @method static year(int $year = null) + */ +trait Week +{ + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function isoWeekYear($year = null, $dayOfWeek = null, $dayOfYear = null) + { + return $this->weekYear( + $year, + $dayOfWeek ?? 1, + $dayOfYear ?? 4 + ); + } + + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function weekYear($year = null, $dayOfWeek = null, $dayOfYear = null) + { + $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? 0; + $dayOfYear = $dayOfYear ?? $this->getTranslationMessage('day_of_first_week_of_year') ?? 1; + + if ($year !== null) { + $year = (int) round($year); + + if ($this->weekYear(null, $dayOfWeek, $dayOfYear) === $year) { + return $this->avoidMutation(); + } + + $week = $this->week(null, $dayOfWeek, $dayOfYear); + $day = $this->dayOfWeek; + $date = $this->year($year); + switch ($date->weekYear(null, $dayOfWeek, $dayOfYear) - $year) { + case 1: + $date = $date->subWeeks(26); + + break; + case -1: + $date = $date->addWeeks(26); + + break; + } + + $date = $date->addWeeks($week - $date->week(null, $dayOfWeek, $dayOfYear))->startOfWeek($dayOfWeek); + + if ($date->dayOfWeek === $day) { + return $date; + } + + return $date->next($day); + } + + $year = $this->year; + $day = $this->dayOfYear; + $date = $this->avoidMutation()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + + if ($date->year === $year && $day < $date->dayOfYear) { + return $year - 1; + } + + $date = $this->avoidMutation()->addYear()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + + if ($date->year === $year && $day >= $date->dayOfYear) { + return $year + 1; + } + + return $year; + } + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function isoWeeksInYear($dayOfWeek = null, $dayOfYear = null) + { + return $this->weeksInYear( + $dayOfWeek ?? 1, + $dayOfYear ?? 4 + ); + } + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function weeksInYear($dayOfWeek = null, $dayOfYear = null) + { + $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? 0; + $dayOfYear = $dayOfYear ?? $this->getTranslationMessage('day_of_first_week_of_year') ?? 1; + $year = $this->year; + $start = $this->avoidMutation()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + $startDay = $start->dayOfYear; + if ($start->year !== $year) { + $startDay -= $start->daysInYear; + } + $end = $this->avoidMutation()->addYear()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + $endDay = $end->dayOfYear; + if ($end->year !== $year) { + $endDay += $this->daysInYear; + } + + return (int) round(($endDay - $startDay) / 7); + } + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function week($week = null, $dayOfWeek = null, $dayOfYear = null) + { + $date = $this; + $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? 0; + $dayOfYear = $dayOfYear ?? $this->getTranslationMessage('day_of_first_week_of_year') ?? 1; + + if ($week !== null) { + return $date->addWeeks(round($week) - $this->week(null, $dayOfWeek, $dayOfYear)); + } + + $start = $date->avoidMutation()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + $end = $date->avoidMutation()->startOfWeek($dayOfWeek); + if ($start > $end) { + $start = $start->subWeeks(26)->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + } + $week = (int) ($start->diffInDays($end) / 7 + 1); + + return $week > $end->weeksInYear($dayOfWeek, $dayOfYear) ? 1 : $week; + } + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function isoWeek($week = null, $dayOfWeek = null, $dayOfYear = null) + { + return $this->week( + $week, + $dayOfWeek ?? 1, + $dayOfYear ?? 4 + ); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Translator.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Translator.php new file mode 100644 index 0000000000..491c9e7204 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Translator.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use ReflectionMethod; +use Symfony\Component\Translation; +use Symfony\Contracts\Translation\TranslatorInterface; + +$transMethod = new ReflectionMethod( + class_exists(TranslatorInterface::class) + ? TranslatorInterface::class + : Translation\Translator::class, + 'trans' +); + +require $transMethod->hasReturnType() + ? __DIR__.'/../../lazy/Carbon/TranslatorStrongType.php' + : __DIR__.'/../../lazy/Carbon/TranslatorWeakType.php'; + +class Translator extends LazyTranslator +{ + // Proxy dynamically loaded LazyTranslator in a static way +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/TranslatorImmutable.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/TranslatorImmutable.php new file mode 100644 index 0000000000..ce6b2f90ac --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/TranslatorImmutable.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\ImmutableException; +use Symfony\Component\Config\ConfigCacheFactoryInterface; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; + +class TranslatorImmutable extends Translator +{ + /** @var bool */ + private $constructed = false; + + public function __construct($locale, MessageFormatterInterface $formatter = null, $cacheDir = null, $debug = false) + { + parent::__construct($locale, $formatter, $cacheDir, $debug); + $this->constructed = true; + } + + /** + * @codeCoverageIgnore + */ + public function setDirectories(array $directories) + { + $this->disallowMutation(__METHOD__); + + return parent::setDirectories($directories); + } + + public function setLocale($locale) + { + $this->disallowMutation(__METHOD__); + + return parent::setLocale($locale); + } + + /** + * @codeCoverageIgnore + */ + public function setMessages($locale, $messages) + { + $this->disallowMutation(__METHOD__); + + return parent::setMessages($locale, $messages); + } + + /** + * @codeCoverageIgnore + */ + public function setTranslations($messages) + { + $this->disallowMutation(__METHOD__); + + return parent::setTranslations($messages); + } + + /** + * @codeCoverageIgnore + */ + public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory): void + { + $this->disallowMutation(__METHOD__); + + parent::setConfigCacheFactory($configCacheFactory); + } + + public function resetMessages($locale = null) + { + $this->disallowMutation(__METHOD__); + + return parent::resetMessages($locale); + } + + /** + * @codeCoverageIgnore + */ + public function setFallbackLocales(array $locales) + { + $this->disallowMutation(__METHOD__); + + parent::setFallbackLocales($locales); + } + + private function disallowMutation($method) + { + if ($this->constructed) { + throw new ImmutableException($method.' not allowed on '.static::class); + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/TranslatorStrongTypeInterface.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/TranslatorStrongTypeInterface.php new file mode 100644 index 0000000000..ef4dee8e28 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/TranslatorStrongTypeInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Mark translator using strong type from symfony/translation >= 6. + */ +interface TranslatorStrongTypeInterface +{ + public function getFromCatalogue(MessageCatalogueInterface $catalogue, string $id, string $domain = 'messages'); +} diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/LICENSE b/data/web/inc/lib/vendor/paragonie/random_compat/LICENSE new file mode 100644 index 0000000000..45c7017dfb --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Paragon Initiative Enterprises + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/build-phar.sh b/data/web/inc/lib/vendor/paragonie/random_compat/build-phar.sh new file mode 100755 index 0000000000..b4a5ba31cc --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/build-phar.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) ) + +php -dphar.readonly=0 "$basedir/other/build_phar.php" $* \ No newline at end of file diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/composer.json b/data/web/inc/lib/vendor/paragonie/random_compat/composer.json new file mode 100644 index 0000000000..f2b9c4e510 --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/composer.json @@ -0,0 +1,34 @@ +{ + "name": "paragonie/random_compat", + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "random", + "polyfill", + "pseudorandom" + ], + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "support": { + "issues": "https://github.com/paragonie/random_compat/issues", + "email": "info@paragonie.com", + "source": "https://github.com/paragonie/random_compat" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "vimeo/psalm": "^1", + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + } +} diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey new file mode 100644 index 0000000000..eb50ebfcd6 --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm +pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p ++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc +-----END PUBLIC KEY----- diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc new file mode 100644 index 0000000000..6a1d7f3006 --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v2.0.22 (MingW32) + +iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip +QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg +1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW +NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA +NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV +JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74= +=B6+8 +-----END PGP SIGNATURE----- diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/lib/random.php b/data/web/inc/lib/vendor/paragonie/random_compat/lib/random.php new file mode 100644 index 0000000000..c7731a56ff --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/lib/random.php @@ -0,0 +1,32 @@ +buildFromDirectory(dirname(__DIR__).'/lib'); +rename( + dirname(__DIR__).'/lib/index.php', + dirname(__DIR__).'/lib/random.php' +); + +/** + * If we pass an (optional) path to a private key as a second argument, we will + * sign the Phar with OpenSSL. + * + * If you leave this out, it will produce an unsigned .phar! + */ +if ($argc > 1) { + if (!@is_readable($argv[1])) { + echo 'Could not read the private key file:', $argv[1], "\n"; + exit(255); + } + $pkeyFile = file_get_contents($argv[1]); + + $private = openssl_get_privatekey($pkeyFile); + if ($private !== false) { + $pkey = ''; + openssl_pkey_export($private, $pkey); + $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey); + + /** + * Save the corresponding public key to the file + */ + if (!@is_readable($dist.'/random_compat.phar.pubkey')) { + $details = openssl_pkey_get_details($private); + file_put_contents( + $dist.'/random_compat.phar.pubkey', + $details['key'] + ); + } + } else { + echo 'An error occurred reading the private key from OpenSSL.', "\n"; + exit(255); + } +} diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/psalm-autoload.php b/data/web/inc/lib/vendor/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 0000000000..d71d1b818c --- /dev/null +++ b/data/web/inc/lib/vendor/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ + + + + + + + + + + + + + + + diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/.github/workflows/main.yml b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/.github/workflows/main.yml new file mode 100644 index 0000000000..7931d00bce --- /dev/null +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/.github/workflows/main.yml @@ -0,0 +1,60 @@ +name: CI + +on: [push] + +jobs: + + tests: + runs-on: ubuntu-latest + strategy: + matrix: + php: [7.2, 7.3, 7.4, 8.0] + dependency-version: [prefer-lowest, prefer-stable] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: mailparse + coverage: none + + - name: Install dependencies + run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest --no-progress + + - name: Execute tests + run: vendor/bin/phpunit + + php-cs: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: composer update --no-progress --ignore-platform-reqs + + - name: Execute phpcs + run: vendor/bin/phpcs src tests --standard=psr2 + + coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: mailparse + + - name: Install dependencies + run: composer update --prefer-dist --no-interaction --no-suggest --no-progress + + - name: Execute code coverage + run: | + vendor/bin/phpunit --coverage-clover=coverage.xml --whitelist src + bash <(curl -s https://codecov.io/bash) -t ${{ secrets.COVERALLS_REPO_TOKEN }} diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/README.md b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/README.md index 0b0502fc41..24a6e0ebd7 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/README.md +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/README.md @@ -1,6 +1,9 @@ # php-mime-mail-parser -A fully tested mailparse extension wrapper for PHP 5.4+ +A fully tested email parser for PHP 7.2+ (mailparse extension wrapper). + +It's the most effective php email parser around in terms of performance, foreign character encoding, attachment handling, and ease of use. +Internet Message Format RFC [822](https://tools.ietf.org/html/rfc822), [2822](https://tools.ietf.org/html/rfc2822), [5322](https://tools.ietf.org/html/rfc5322). [![Latest Version](https://img.shields.io/packagist/v/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://github.com/php-mime-mail-parser/php-mime-mail-parser/releases) [![Total Downloads](https://img.shields.io/packagist/dt/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://packagist.org/packages/php-mime-mail-parser/php-mime-mail-parser) @@ -10,6 +13,7 @@ A fully tested mailparse extension wrapper for PHP 5.4+ This extension can be used to... * Parse and read email from Postfix + * For reading messages (Filename extension: eml) * Create webmail * Store email information such a subject, HTML body, attachments, and etc. into a database @@ -17,11 +21,12 @@ This extension can be used to... Yes. All known issues have been reproduced, fixed and tested. -We use Travis CI to help ensure code quality. You can see real-time statistics below: +We use GitHub Actions, Codecov, Codacy to help ensure code quality. You can see real-time statistics below: + +[![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fphp-mime-mail-parser%2Fphp-mime-mail-parser%2Fbadge%3Fref%3Dmaster&style=flat-square)](https://actions-badge.atrox.dev/php-mime-mail-parser/php-mime-mail-parser/goto?ref=master) +[![Coverage](https://img.shields.io/codecov/c/gh/php-mime-mail-parser/php-mime-mail-parser?style=flat-square)](https://codecov.io/gh/php-mime-mail-parser/php-mime-mail-parser) +[![Code Quality](https://img.shields.io/codacy/grade/4e0e44fee21147ddbdd18ff976251875?style=flat-square)](https://app.codacy.com/app/php-mime-mail-parser/php-mime-mail-parser) -[![Build Status](https://img.shields.io/travis/php-mime-mail-parser/php-mime-mail-parser/master.svg?style=flat-square)](https://travis-ci.org/php-mime-mail-parser/php-mime-mail-parser) -[![Coverage](https://img.shields.io/coveralls/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://coveralls.io/r/php-mime-mail-parser/php-mime-mail-parser) -[![Quality Score](https://img.shields.io/scrutinizer/g/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-mime-mail-parser/php-mime-mail-parser) ## How do I install it? @@ -35,105 +40,186 @@ To install the latest version of PHP MIME Mail Parser, run the command below: The following versions of PHP are supported: -* PHP 5.4 -* PHP 5.5 -* PHP 5.6 -* PHP 7 -* HHVM +* PHP 7.2 +* PHP 7.3 +* PHP 7.4 + +Previous Versions: + +| PHP Compatibility | Version | +| ------------- | ------------- | +| HHVM | php-mime-mail-parser 2.11.1 | +| PHP 5.4 | php-mime-mail-parser 2.11.1 | +| PHP 5.5 | php-mime-mail-parser 2.11.1 | +| PHP 5.6 | php-mime-mail-parser 3.0.4 | +| PHP 7.0 | php-mime-mail-parser 3.0.4 | +| PHP 7.1 | php-mime-mail-parser 5.0.5 | + +Make sure you have the mailparse extension (http://php.net/manual/en/book.mailparse.php) properly installed. The command line `php -m | grep mailparse` need to return "mailparse". + + +### Install mailparse extension +#### Ubuntu, Debian & derivatives ``` -sudo apt install php-cli php-pear php-dev php-mbstring +sudo apt install php-cli php-mailparse ``` -Make sure you have the mailparse extension (http://php.net/manual/en/book.mailparse.php) properly installed. The command line `php -m | grep mailparse` need to return "mailparse" else install it: -* PHP version > 7.0: mailparse -* PHP version < 7.0: mailparse 2.1.6 +#### Others platforms +``` +sudo apt install php-cli php-pear php-dev php-mbstring +pecl install mailparse +``` -Follow this steps to install mailparse: +#### From source -* Compile in the temp folder the extension mailparse or mailparse-2.1.6 (workaround because pecl install doesn't work yet) +AAAAMMDD should be `php-config --extension-dir` ``` -cd -pecl download mailparse -tar -xvf mailparse-3.0.2.tgz -cd mailparse-3.0.2/ +git clone https://github.com/php/pecl-mail-mailparse.git +cd pecl-mail-mailparse phpize ./configure sed -i 's/#if\s!HAVE_MBSTRING/#ifndef MBFL_MBFILTER_H/' ./mailparse.c make -sudo mv modules/mailparse.so /usr/lib/php/20160303/ -``` -* Add the extension mailparse and activate it -``` +sudo mv modules/mailparse.so /usr/lib/php/AAAAMMDD/ echo "extension=mailparse.so" | sudo tee /etc/php/7.1/mods-available/mailparse.ini sudo phpenmod mailparse ``` -On Windows, you need to download mailparse DLL from http://pecl.php.net/package/mailparse and add the line "extension=php_mailparse.dll" to php.ini accordingly. +#### Windows +You need to download mailparse DLL from http://pecl.php.net/package/mailparse and add the line "extension=php_mailparse.dll" to php.ini accordingly. ## How do I use it? +### Loading an email + +You can load an email with 4 differents ways. You only need to use one of the following four. + ```php -setPath($path); -// 1. Specify a file path to the mime mail. -$Parser->setPath($path); +// 2. Specify the raw mime mail text (string) +$parser->setText(file_get_contents($path)); -// 2. Specify a php file resource (stream) to the mime mail. -$Parser->setStream(fopen($path, "r")); +// 3. Specify a php file resource (stream) +$parser->setStream(fopen($path, "r")); -// 3. Specify the raw mime mail text. -$Parser->setText(file_get_contents($path)); +// 4. Specify a stream to work with mail server (stream) +$parser->setStream(fopen("php://stdin", "r")); +``` -// 4. Specify a stream to work with mail server -$Parser->setStream(fopen("php://stdin", "r")); +### Get the metadata of the message -// Once we've indicated where to find the mail, we can parse out the data -$to = $Parser->getHeader('to'); // "test" , "test2" -$addressesTo = $Parser->getAddresses('to'); //Return an array : [["display"=>"test", "address"=>"test@example.com", false],["display"=>"test2", "address"=>"test2@example.com", false]] +Get the sender and the receiver: -$from = $Parser->getHeader('from'); // "test" -$addressesFrom = $Parser->getAddresses('from'); //Return an array : [["display"=>"test", "address"=>"test@example.com", "is_group"=>false]] +```php +$rawHeaderTo = $parser->getHeader('to'); +// return "test" , "test2" -$subject = $Parser->getHeader('subject'); +$arrayHeaderTo = $parser->getAddresses('to'); +// return [["display"=>"test", "address"=>"test@example.com", false]] -$text = $Parser->getMessageBody('text'); +$rawHeaderFrom = $parser->getHeader('from'); +// return "test" -$html = $Parser->getMessageBody('html'); -$htmlEmbedded = $Parser->getMessageBody('htmlEmbedded'); //HTML Body included data +$arrayHeaderFrom = $parser->getAddresses('from'); +// return [["display"=>"test", "address"=>"test@example.com", "is_group"=>false]] +``` -$stringHeaders = $Parser->getHeadersRaw(); // Get all headers as a string, no charset conversion -$arrayHeaders = $Parser->getHeaders(); // Get all headers as an array, with charset conversion +Get the subject: -// Pass in a writeable path to save attachments -$attach_dir = '/path/to/save/attachments/'; // Be sure to include the trailing slash -$include_inline = true; // Optional argument to include inline attachments (default: true) -$Parser->saveAttachments($attach_dir [,$include_inline]); +```php +$subject = $parser->getHeader('subject'); +``` -// Get an array of Attachment items from $Parser -$attachments = $Parser->getAttachments([$include_inline]); +Get other headers: -// Loop through all the Attachments -if (count($attachments) > 0) { - foreach ($attachments as $attachment) { - echo 'Filename : '.$attachment->getFilename().'
    '; // logo.jpg - echo 'Filesize : '.filesize($attach_dir.$attachment->getFilename()).'
    '; // 1000 - echo 'Filetype : '.$attachment->getContentType().'
    '; // image/jpeg - echo 'MIME part string : '.$attachment->getMimePartStr().'
    '; // (the whole MIME part of the attachment) - } -} +```php +$stringHeaders = $parser->getHeadersRaw(); +// return all headers as a string, no charset conversion -?> +$arrayHeaders = $parser->getHeaders(); +// return all headers as an array, with charset conversion ``` +### Get the body of the message + +```php +$text = $parser->getMessageBody('text'); +// return the text version + +$html = $parser->getMessageBody('html'); +// return the html version + +$htmlEmbedded = $parser->getMessageBody('htmlEmbedded'); +// return the html version with the embedded contents like images + +``` + +### Get attachments + +Save all attachments in a directory + +```php +$parser->saveAttachments('/path/to/save/attachments/'); +// return all attachments saved in the directory (include inline attachments) + +$parser->saveAttachments('/path/to/save/attachments/', false); +// return all attachments saved in the directory (exclude inline attachments) + +// Save all attachments with the strategy ATTACHMENT_DUPLICATE_SUFFIX (default) +$parser->saveAttachments('/path/to/save/attachments/', false, Parser::ATTACHMENT_DUPLICATE_SUFFIX); +// return all attachments saved in the directory: logo.jpg, logo_1.jpg, ..., logo_100.jpg, YY34UFHBJ.jpg + +// Save all attachments with the strategy ATTACHMENT_RANDOM_FILENAME +$parser->saveAttachments('/path/to/save/attachments/', false, Parser::ATTACHMENT_RANDOM_FILENAME); +// return all attachments saved in the directory: YY34UFHBJ.jpg and F98DBZ9FZF.jpg + +// Save all attachments with the strategy ATTACHMENT_DUPLICATE_THROW +$parser->saveAttachments('/path/to/save/attachments/', false, Parser::ATTACHMENT_DUPLICATE_THROW); +// return an exception when there is attachments duplicate. + +``` + +Get all attachments + +```php +$attachments = $parser->getAttachments(); +// return an array of all attachments (include inline attachments) + +$attachments = $parser->getAttachments(false); +// return an array of all attachments (exclude inline attachments) +``` + + +Loop through all the Attachments +```php +foreach ($attachments as $attachment) { + echo 'Filename : '.$attachment->getFilename().'
    '; + // return logo.jpg + + echo 'Filesize : '.filesize($attach_dir.$attachment->getFilename()).'
    '; + // return 1000 + + echo 'Filetype : '.$attachment->getContentType().'
    '; + // return image/jpeg + + echo 'MIME part string : '.$attachment->getMimePartStr().'
    '; + // return the whole MIME part of the attachment + + $attachment->save('/path/to/save/myattachment/', Parser::ATTACHMENT_DUPLICATE_SUFFIX); + // return the path and the filename saved (same strategy available than saveAttachments) +} +``` + +## Postfix configuration to manage email from a mail server + Next you need to forward emails to this script above. For that I'm using [Postfix](http://www.postfix.org/) like a mail server, you need to configure /etc/postfix/master.cf Add this line at the end of the file (specify myhook to send all emails to the script test.php) @@ -150,6 +236,8 @@ smtp inet n - - - - smtpd The php script must use the fourth method to work with this configuration. +And finally the easiest way is to use my SaaS https://mailcare.io + ## Can I contribute? @@ -162,6 +250,6 @@ Feel free to contribute! If you report an issue, please provide the raw email that triggered it. This helps us reproduce the issue and fix it more quickly. -### License +## License The php-mime-mail-parser/php-mime-mail-parser is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/compile_mailparse.sh b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/compile_mailparse.sh new file mode 100755 index 0000000000..8505077f3e --- /dev/null +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/compile_mailparse.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +git clone https://github.com/php/pecl-mail-mailparse.git +cd pecl-mail-mailparse +phpize +./configure +sed -i 's/#if\s!HAVE_MBSTRING/#ifndef MBFL_MBFILTER_H/' ./mailparse.c +make +sudo mv modules/mailparse.so /home/travis/.phpenv/versions/7.3.2/lib/php/extensions/no-debug-zts-20180731/ +echo 'extension=mailparse.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini \ No newline at end of file diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/composer.json b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/composer.json index 320d08d24c..e9861a2416 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/composer.json +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/composer.json @@ -1,8 +1,8 @@ { "name": "php-mime-mail-parser/php-mime-mail-parser", "type": "library", - "description": "Fully Tested Mailparse Extension Wrapper for PHP 5.4+", - "keywords": ["mime", "mail", "mailparse", "MimeMailParser"], + "description": "A fully tested email parser for PHP 7.2+ (mailparse extension wrapper).", + "keywords": ["mime", "mail", "mailparse", "MimeMailParser", "parser", "php"], "homepage": "https://github.com/php-mime-mail-parser/php-mime-mail-parser", "license": "MIT", "authors": [ @@ -42,14 +42,13 @@ "url":"https://github.com/php-mime-mail-parser/php-mime-mail-parser.git" }, "require": { - "php": "^5.4.0 || ^7.0", + "php": "^7.2|^8.0", "ext-mailparse": "*" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0", - "phpunit/php-token-stream": "^1.3.0", - "satooshi/php-coveralls": "0.*", - "squizlabs/PHP_CodeSniffer": "2.*" + "phpunit/phpunit": "^8.0", + "php-coveralls/php-coveralls": "^2.2", + "squizlabs/php_codesniffer": "^3.5" }, "replace": { "exorus/php-mime-mail-parser": "*", diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/mailparse-stubs.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/mailparse-stubs.php index 743033efe3..e2b5e0e218 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/mailparse-stubs.php +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/mailparse-stubs.php @@ -135,7 +135,7 @@ function mailparse_msg_create() * @param resource $mimemail A valid MIME resource allocated by * mailparse_msg_create or mailparse_msg_parse_file * - * @return bool + * @return boolean|null */ function mailparse_msg_free($mimemail) { @@ -149,7 +149,7 @@ function mailparse_msg_free($mimemail) * @param resource $mimemail A valid MIME resource * @param string $data * - * @return bool + * @return boolean|null */ function mailparse_msg_parse($mimemail, $data) { @@ -199,7 +199,7 @@ function mailparse_determine_best_xfer_encoding($fp) * @param string $encoding One of the character encodings supported by the mbstring * module * - * @return bool + * @return boolean|null */ function mailparse_stream_encode($sourcefp, $destfp, $encoding) { diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Attachment.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Attachment.php index 520126229b..1a731635f8 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Attachment.php +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Attachment.php @@ -2,6 +2,8 @@ namespace PhpMimeMailParser; +use function var_dump; + /** * Attachment of php-mime-mail-parser * @@ -50,6 +52,11 @@ class Attachment */ protected $mimePartStr; + /** + * @var integer $maxDuplicateNumber + */ + public $maxDuplicateNumber = 100; + /** * Attachment constructor. * @@ -133,13 +140,44 @@ public function getHeaders() /** * Get a handle to the stream * - * @return stream + * @return resource */ public function getStream() { return $this->stream; } + /** + * Rename a file if it already exists at its destination. + * Renaming is done by adding a duplicate number to the file name. E.g. existingFileName_1.ext. + * After a max duplicate number, renaming the file will switch over to generating a random suffix. + * + * @param string $fileName Complete path to the file. + * @return string The suffixed file name. + */ + protected function suffixFileName(string $fileName): string + { + $pathInfo = pathinfo($fileName); + $dirname = $pathInfo['dirname'].DIRECTORY_SEPARATOR; + $filename = $pathInfo['filename']; + $extension = empty($pathInfo['extension']) ? '' : '.'.$pathInfo['extension']; + + $i = 0; + do { + $i++; + + if ($i > $this->maxDuplicateNumber) { + $duplicateExtension = uniqid(); + } else { + $duplicateExtension = $i; + } + + $resultName = $dirname.$filename."_$duplicateExtension".$extension; + } while (file_exists($resultName)); + + return $resultName; + } + /** * Read the contents a few bytes at a time until completed * Once read to completion, it always returns false @@ -180,4 +218,59 @@ public function getMimePartStr() { return $this->mimePartStr; } + + /** + * Save the attachment individually + * + * @param string $attach_dir + * @param string $filenameStrategy + * + * @return string + */ + public function save( + $attach_dir, + $filenameStrategy = Parser::ATTACHMENT_DUPLICATE_SUFFIX + ) { + $attach_dir = rtrim($attach_dir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; + if (!is_dir($attach_dir)) { + mkdir($attach_dir); + } + + // Determine filename + switch ($filenameStrategy) { + case Parser::ATTACHMENT_RANDOM_FILENAME: + $fileInfo = pathinfo($this->getFilename()); + $extension = empty($fileInfo['extension']) ? '' : '.'.$fileInfo['extension']; + $attachment_path = $attach_dir.uniqid().$extension; + break; + case Parser::ATTACHMENT_DUPLICATE_THROW: + case Parser::ATTACHMENT_DUPLICATE_SUFFIX: + $attachment_path = $attach_dir.$this->getFilename(); + break; + default: + throw new Exception('Invalid filename strategy argument provided.'); + } + + // Handle duplicate filename + if (file_exists($attachment_path)) { + switch ($filenameStrategy) { + case Parser::ATTACHMENT_DUPLICATE_THROW: + throw new Exception('Could not create file for attachment: duplicate filename.'); + case Parser::ATTACHMENT_DUPLICATE_SUFFIX: + $attachment_path = $this->suffixFileName($attachment_path); + break; + } + } + + /** @var resource $fp */ + if ($fp = fopen($attachment_path, 'w')) { + while ($bytes = $this->read()) { + fwrite($fp, $bytes); + } + fclose($fp); + return realpath($attachment_path); + } else { + throw new Exception('Could not write attachments. Your directory may be unwritable by PHP.'); + } + } } diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Charset.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Charset.php index 04315c1e7c..cd219f2268 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Charset.php +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Charset.php @@ -12,33 +12,33 @@ class Charset implements CharsetManager 'us-ascii' => 'us-ascii', 'ansi_x3.4-1968' => 'us-ascii', '646' => 'us-ascii', - 'iso-8859-1' => 'ISO-8859-1', - 'iso-8859-2' => 'ISO-8859-2', - 'iso-8859-3' => 'ISO-8859-3', - 'iso-8859-4' => 'ISO-8859-4', - 'iso-8859-5' => 'ISO-8859-5', - 'iso-8859-6' => 'ISO-8859-6', - 'iso-8859-6-i' => 'ISO-8859-6-I', - 'iso-8859-6-e' => 'ISO-8859-6-E', - 'iso-8859-7' => 'ISO-8859-7', - 'iso-8859-8' => 'ISO-8859-8', - 'iso-8859-8-i' => 'ISO-8859-8', - 'iso-8859-8-e' => 'ISO-8859-8-E', - 'iso-8859-9' => 'ISO-8859-9', - 'iso-8859-10' => 'ISO-8859-10', - 'iso-8859-11' => 'ISO-8859-11', - 'iso-8859-13' => 'ISO-8859-13', - 'iso-8859-14' => 'ISO-8859-14', - 'iso-8859-15' => 'ISO-8859-15', - 'iso-8859-16' => 'ISO-8859-16', - 'iso-ir-111' => 'ISO-IR-111', - 'iso-2022-cn' => 'ISO-2022-CN', - 'iso-2022-cn-ext' => 'ISO-2022-CN', - 'iso-2022-kr' => 'ISO-2022-KR', - 'iso-2022-jp' => 'ISO-2022-JP', - 'utf-16be' => 'UTF-16BE', - 'utf-16le' => 'UTF-16LE', - 'utf-16' => 'UTF-16', + 'iso-8859-1' => 'iso-8859-1', + 'iso-8859-2' => 'iso-8859-2', + 'iso-8859-3' => 'iso-8859-3', + 'iso-8859-4' => 'iso-8859-4', + 'iso-8859-5' => 'iso-8859-5', + 'iso-8859-6' => 'iso-8859-6', + 'iso-8859-6-i' => 'iso-8859-6-i', + 'iso-8859-6-e' => 'iso-8859-6-e', + 'iso-8859-7' => 'iso-8859-7', + 'iso-8859-8' => 'iso-8859-8', + 'iso-8859-8-i' => 'iso-8859-8', + 'iso-8859-8-e' => 'iso-8859-8-e', + 'iso-8859-9' => 'iso-8859-9', + 'iso-8859-10' => 'iso-8859-10', + 'iso-8859-11' => 'iso-8859-11', + 'iso-8859-13' => 'iso-8859-13', + 'iso-8859-14' => 'iso-8859-14', + 'iso-8859-15' => 'iso-8859-15', + 'iso-8859-16' => 'iso-8859-16', + 'iso-ir-111' => 'iso-ir-111', + 'iso-2022-cn' => 'iso-2022-cn', + 'iso-2022-cn-ext' => 'iso-2022-cn', + 'iso-2022-kr' => 'iso-2022-kr', + 'iso-2022-jp' => 'iso-2022-jp', + 'utf-16be' => 'utf-16be', + 'utf-16le' => 'utf-16le', + 'utf-16' => 'utf-16', 'windows-1250' => 'windows-1250', 'windows-1251' => 'windows-1251', 'windows-1252' => 'windows-1252', @@ -48,172 +48,172 @@ class Charset implements CharsetManager 'windows-1256' => 'windows-1256', 'windows-1257' => 'windows-1257', 'windows-1258' => 'windows-1258', - 'ibm866' => 'IBM866', - 'ibm850' => 'IBM850', - 'ibm852' => 'IBM852', - 'ibm855' => 'IBM855', - 'ibm857' => 'IBM857', - 'ibm862' => 'IBM862', - 'ibm864' => 'IBM864', - 'utf-8' => 'UTF-8', - 'utf-7' => 'UTF-7', - 'shift_jis' => 'Shift_JIS', - 'big5' => 'Big5', - 'euc-jp' => 'EUC-JP', - 'euc-kr' => 'EUC-KR', - 'gb2312' => 'GB2312', + 'ibm866' => 'ibm866', + 'ibm850' => 'ibm850', + 'ibm852' => 'ibm852', + 'ibm855' => 'ibm855', + 'ibm857' => 'ibm857', + 'ibm862' => 'ibm862', + 'ibm864' => 'ibm864', + 'utf-8' => 'utf-8', + 'utf-7' => 'utf-7', + 'shift_jis' => 'shift_jis', + 'big5' => 'big5', + 'euc-jp' => 'euc-jp', + 'euc-kr' => 'euc-kr', + 'gb2312' => 'gb2312', 'gb18030' => 'gb18030', - 'viscii' => 'VISCII', - 'koi8-r' => 'KOI8-R', - 'koi8_r' => 'KOI8-R', - 'cskoi8r' => 'KOI8-R', - 'koi' => 'KOI8-R', - 'koi8' => 'KOI8-R', - 'koi8-u' => 'KOI8-U', - 'tis-620' => 'TIS-620', - 't.61-8bit' => 'T.61-8bit', - 'hz-gb-2312' => 'HZ-GB-2312', - 'big5-hkscs' => 'Big5-HKSCS', + 'viscii' => 'viscii', + 'koi8-r' => 'koi8-r', + 'koi8_r' => 'koi8-r', + 'cskoi8r' => 'koi8-r', + 'koi' => 'koi8-r', + 'koi8' => 'koi8-r', + 'koi8-u' => 'koi8-u', + 'tis-620' => 'tis-620', + 't.61-8bit' => 't.61-8bit', + 'hz-gb-2312' => 'hz-gb-2312', + 'big5-hkscs' => 'big5-hkscs', 'gbk' => 'gbk', 'cns11643' => 'x-euc-tw', 'x-imap4-modified-utf7' => 'x-imap4-modified-utf7', 'x-euc-tw' => 'x-euc-tw', - 'x-mac-ce' => 'x-mac-ce', - 'x-mac-turkish' => 'x-mac-turkish', - 'x-mac-greek' => 'x-mac-greek', - 'x-mac-icelandic' => 'x-mac-icelandic', - 'x-mac-croatian' => 'x-mac-croatian', - 'x-mac-romanian' => 'x-mac-romanian', - 'x-mac-cyrillic' => 'x-mac-cyrillic', - 'x-mac-ukrainian' => 'x-mac-cyrillic', - 'x-mac-hebrew' => 'x-mac-hebrew', - 'x-mac-arabic' => 'x-mac-arabic', - 'x-mac-farsi' => 'x-mac-farsi', - 'x-mac-devanagari' => 'x-mac-devanagari', - 'x-mac-gujarati' => 'x-mac-gujarati', - 'x-mac-gurmukhi' => 'x-mac-gurmukhi', + 'x-mac-ce' => 'macce', + 'x-mac-turkish' => 'macturkish', + 'x-mac-greek' => 'macgreek', + 'x-mac-icelandic' => 'macicelandic', + 'x-mac-croatian' => 'maccroatian', + 'x-mac-romanian' => 'macromanian', + 'x-mac-cyrillic' => 'maccyrillic', + 'x-mac-ukrainian' => 'macukrainian', + 'x-mac-hebrew' => 'machebrew', + 'x-mac-arabic' => 'macarabic', + 'x-mac-farsi' => 'macfarsi', + 'x-mac-devanagari' => 'macdevanagari', + 'x-mac-gujarati' => 'macgujarati', + 'x-mac-gurmukhi' => 'macgurmukhi', 'armscii-8' => 'armscii-8', 'x-viet-tcvn5712' => 'x-viet-tcvn5712', 'x-viet-vps' => 'x-viet-vps', - 'iso-10646-ucs-2' => 'UTF-16BE', - 'x-iso-10646-ucs-2-be' => 'UTF-16BE', - 'x-iso-10646-ucs-2-le' => 'UTF-16LE', + 'iso-10646-ucs-2' => 'utf-16be', + 'x-iso-10646-ucs-2-be' => 'utf-16be', + 'x-iso-10646-ucs-2-le' => 'utf-16le', 'x-user-defined' => 'x-user-defined', 'x-johab' => 'x-johab', - 'latin1' => 'ISO-8859-1', - 'iso_8859-1' => 'ISO-8859-1', - 'iso8859-1' => 'ISO-8859-1', - 'iso8859-2' => 'ISO-8859-2', - 'iso8859-3' => 'ISO-8859-3', - 'iso8859-4' => 'ISO-8859-4', - 'iso8859-5' => 'ISO-8859-5', - 'iso8859-6' => 'ISO-8859-6', - 'iso8859-7' => 'ISO-8859-7', - 'iso8859-8' => 'ISO-8859-8', - 'iso8859-9' => 'ISO-8859-9', - 'iso8859-10' => 'ISO-8859-10', - 'iso8859-11' => 'ISO-8859-11', - 'iso8859-13' => 'ISO-8859-13', - 'iso8859-14' => 'ISO-8859-14', - 'iso8859-15' => 'ISO-8859-15', - 'iso_8859-1:1987' => 'ISO-8859-1', - 'iso-ir-100' => 'ISO-8859-1', - 'l1' => 'ISO-8859-1', - 'ibm819' => 'ISO-8859-1', - 'cp819' => 'ISO-8859-1', - 'csisolatin1' => 'ISO-8859-1', - 'latin2' => 'ISO-8859-2', - 'iso_8859-2' => 'ISO-8859-2', - 'iso_8859-2:1987' => 'ISO-8859-2', - 'iso-ir-101' => 'ISO-8859-2', - 'l2' => 'ISO-8859-2', - 'csisolatin2' => 'ISO-8859-2', - 'latin3' => 'ISO-8859-3', - 'iso_8859-3' => 'ISO-8859-3', - 'iso_8859-3:1988' => 'ISO-8859-3', - 'iso-ir-109' => 'ISO-8859-3', - 'l3' => 'ISO-8859-3', - 'csisolatin3' => 'ISO-8859-3', - 'latin4' => 'ISO-8859-4', - 'iso_8859-4' => 'ISO-8859-4', - 'iso_8859-4:1988' => 'ISO-8859-4', - 'iso-ir-110' => 'ISO-8859-4', - 'l4' => 'ISO-8859-4', - 'csisolatin4' => 'ISO-8859-4', - 'cyrillic' => 'ISO-8859-5', - 'iso_8859-5' => 'ISO-8859-5', - 'iso_8859-5:1988' => 'ISO-8859-5', - 'iso-ir-144' => 'ISO-8859-5', - 'csisolatincyrillic' => 'ISO-8859-5', - 'arabic' => 'ISO-8859-6', - 'iso_8859-6' => 'ISO-8859-6', - 'iso_8859-6:1987' => 'ISO-8859-6', - 'iso-ir-127' => 'ISO-8859-6', - 'ecma-114' => 'ISO-8859-6', - 'asmo-708' => 'ISO-8859-6', - 'csisolatinarabic' => 'ISO-8859-6', - 'csiso88596i' => 'ISO-8859-6-I', - 'csiso88596e' => 'ISO-8859-6-E', - 'greek' => 'ISO-8859-7', - 'greek8' => 'ISO-8859-7', - 'sun_eu_greek' => 'ISO-8859-7', - 'iso_8859-7' => 'ISO-8859-7', - 'iso_8859-7:1987' => 'ISO-8859-7', - 'iso-ir-126' => 'ISO-8859-7', - 'elot_928' => 'ISO-8859-7', - 'ecma-118' => 'ISO-8859-7', - 'csisolatingreek' => 'ISO-8859-7', - 'hebrew' => 'ISO-8859-8', - 'iso_8859-8' => 'ISO-8859-8', - 'visual' => 'ISO-8859-8', - 'iso_8859-8:1988' => 'ISO-8859-8', - 'iso-ir-138' => 'ISO-8859-8', - 'csisolatinhebrew' => 'ISO-8859-8', - 'csiso88598i' => 'ISO-8859-8', - 'iso-8859-8i' => 'ISO-8859-8', - 'logical' => 'ISO-8859-8', - 'csiso88598e' => 'ISO-8859-8-E', - 'latin5' => 'ISO-8859-9', - 'iso_8859-9' => 'ISO-8859-9', - 'iso_8859-9:1989' => 'ISO-8859-9', - 'iso-ir-148' => 'ISO-8859-9', - 'l5' => 'ISO-8859-9', - 'csisolatin5' => 'ISO-8859-9', - 'unicode-1-1-utf-8' => 'UTF-8', - 'utf8' => 'UTF-8', - 'x-sjis' => 'Shift_JIS', - 'shift-jis' => 'Shift_JIS', - 'ms_kanji' => 'Shift_JIS', - 'csshiftjis' => 'Shift_JIS', - 'windows-31j' => 'Shift_JIS', - 'cp932' => 'Shift_JIS', - 'sjis' => 'Shift_JIS', - 'cseucpkdfmtjapanese' => 'EUC-JP', - 'x-euc-jp' => 'EUC-JP', - 'csiso2022jp' => 'ISO-2022-JP', - 'iso-2022-jp-2' => 'ISO-2022-JP', - 'csiso2022jp2' => 'ISO-2022-JP', - 'csbig5' => 'Big5', - 'cn-big5' => 'Big5', - 'x-x-big5' => 'Big5', - 'zh_tw-big5' => 'Big5', - 'cseuckr' => 'EUC-KR', - 'ks_c_5601-1987' => 'EUC-KR', - 'iso-ir-149' => 'EUC-KR', - 'ks_c_5601-1989' => 'EUC-KR', - 'ksc_5601' => 'EUC-KR', - 'ksc5601' => 'EUC-KR', - 'korean' => 'EUC-KR', - 'csksc56011987' => 'EUC-KR', - '5601' => 'EUC-KR', - 'windows-949' => 'EUC-KR', - 'gb_2312-80' => 'GB2312', - 'iso-ir-58' => 'GB2312', - 'chinese' => 'GB2312', - 'csiso58gb231280' => 'GB2312', - 'csgb2312' => 'GB2312', - 'zh_cn.euc' => 'GB2312', - 'gb_2312' => 'GB2312', + 'latin1' => 'iso-8859-1', + 'iso_8859-1' => 'iso-8859-1', + 'iso8859-1' => 'iso-8859-1', + 'iso8859-2' => 'iso-8859-2', + 'iso8859-3' => 'iso-8859-3', + 'iso8859-4' => 'iso-8859-4', + 'iso8859-5' => 'iso-8859-5', + 'iso8859-6' => 'iso-8859-6', + 'iso8859-7' => 'iso-8859-7', + 'iso8859-8' => 'iso-8859-8', + 'iso8859-9' => 'iso-8859-9', + 'iso8859-10' => 'iso-8859-10', + 'iso8859-11' => 'iso-8859-11', + 'iso8859-13' => 'iso-8859-13', + 'iso8859-14' => 'iso-8859-14', + 'iso8859-15' => 'iso-8859-15', + 'iso_8859-1:1987' => 'iso-8859-1', + 'iso-ir-100' => 'iso-8859-1', + 'l1' => 'iso-8859-1', + 'ibm819' => 'iso-8859-1', + 'cp819' => 'iso-8859-1', + 'csisolatin1' => 'iso-8859-1', + 'latin2' => 'iso-8859-2', + 'iso_8859-2' => 'iso-8859-2', + 'iso_8859-2:1987' => 'iso-8859-2', + 'iso-ir-101' => 'iso-8859-2', + 'l2' => 'iso-8859-2', + 'csisolatin2' => 'iso-8859-2', + 'latin3' => 'iso-8859-3', + 'iso_8859-3' => 'iso-8859-3', + 'iso_8859-3:1988' => 'iso-8859-3', + 'iso-ir-109' => 'iso-8859-3', + 'l3' => 'iso-8859-3', + 'csisolatin3' => 'iso-8859-3', + 'latin4' => 'iso-8859-4', + 'iso_8859-4' => 'iso-8859-4', + 'iso_8859-4:1988' => 'iso-8859-4', + 'iso-ir-110' => 'iso-8859-4', + 'l4' => 'iso-8859-4', + 'csisolatin4' => 'iso-8859-4', + 'cyrillic' => 'iso-8859-5', + 'iso_8859-5' => 'iso-8859-5', + 'iso_8859-5:1988' => 'iso-8859-5', + 'iso-ir-144' => 'iso-8859-5', + 'csisolatincyrillic' => 'iso-8859-5', + 'arabic' => 'iso-8859-6', + 'iso_8859-6' => 'iso-8859-6', + 'iso_8859-6:1987' => 'iso-8859-6', + 'iso-ir-127' => 'iso-8859-6', + 'ecma-114' => 'iso-8859-6', + 'asmo-708' => 'iso-8859-6', + 'csisolatinarabic' => 'iso-8859-6', + 'csiso88596i' => 'iso-8859-6-i', + 'csiso88596e' => 'iso-8859-6-e', + 'greek' => 'iso-8859-7', + 'greek8' => 'iso-8859-7', + 'sun_eu_greek' => 'iso-8859-7', + 'iso_8859-7' => 'iso-8859-7', + 'iso_8859-7:1987' => 'iso-8859-7', + 'iso-ir-126' => 'iso-8859-7', + 'elot_928' => 'iso-8859-7', + 'ecma-118' => 'iso-8859-7', + 'csisolatingreek' => 'iso-8859-7', + 'hebrew' => 'iso-8859-8', + 'iso_8859-8' => 'iso-8859-8', + 'visual' => 'iso-8859-8', + 'iso_8859-8:1988' => 'iso-8859-8', + 'iso-ir-138' => 'iso-8859-8', + 'csisolatinhebrew' => 'iso-8859-8', + 'csiso88598i' => 'iso-8859-8', + 'iso-8859-8i' => 'iso-8859-8', + 'logical' => 'iso-8859-8', + 'csiso88598e' => 'iso-8859-8-e', + 'latin5' => 'iso-8859-9', + 'iso_8859-9' => 'iso-8859-9', + 'iso_8859-9:1989' => 'iso-8859-9', + 'iso-ir-148' => 'iso-8859-9', + 'l5' => 'iso-8859-9', + 'csisolatin5' => 'iso-8859-9', + 'unicode-1-1-utf-8' => 'utf-8', + 'utf8' => 'utf-8', + 'x-sjis' => 'shift_jis', + 'shift-jis' => 'shift_jis', + 'ms_kanji' => 'shift_jis', + 'csshiftjis' => 'shift_jis', + 'windows-31j' => 'shift_jis', + 'cp932' => 'shift_jis', + 'sjis' => 'shift_jis', + 'cseucpkdfmtjapanese' => 'euc-jp', + 'x-euc-jp' => 'euc-jp', + 'csiso2022jp' => 'iso-2022-jp', + 'iso-2022-jp-2' => 'iso-2022-jp', + 'csiso2022jp2' => 'iso-2022-jp', + 'csbig5' => 'big5', + 'cn-big5' => 'big5', + 'x-x-big5' => 'big5', + 'zh_tw-big5' => 'big5', + 'cseuckr' => 'euc-kr', + 'ks_c_5601-1987' => 'euc-kr', + 'iso-ir-149' => 'euc-kr', + 'ks_c_5601-1989' => 'euc-kr', + 'ksc_5601' => 'euc-kr', + 'ksc5601' => 'euc-kr', + 'korean' => 'euc-kr', + 'csksc56011987' => 'euc-kr', + '5601' => 'euc-kr', + 'windows-949' => 'euc-kr', + 'gb_2312-80' => 'gb2312', + 'iso-ir-58' => 'gb2312', + 'chinese' => 'gb2312', + 'csiso58gb231280' => 'gb2312', + 'csgb2312' => 'gb2312', + 'zh_cn.euc' => 'gb2312', + 'gb_2312' => 'gb2312', 'x-cp1250' => 'windows-1250', 'x-cp1251' => 'windows-1251', 'x-cp1252' => 'windows-1252', @@ -230,72 +230,72 @@ class Charset implements CharsetManager 'x-mac-roman' => 'macintosh', 'mac' => 'macintosh', 'csmacintosh' => 'macintosh', - 'cp866' => 'IBM866', - 'cp-866' => 'IBM866', - '866' => 'IBM866', - 'csibm866' => 'IBM866', - 'cp850' => 'IBM850', - '850' => 'IBM850', - 'csibm850' => 'IBM850', - 'cp852' => 'IBM852', - '852' => 'IBM852', - 'csibm852' => 'IBM852', - 'cp855' => 'IBM855', - '855' => 'IBM855', - 'csibm855' => 'IBM855', - 'cp857' => 'IBM857', - '857' => 'IBM857', - 'csibm857' => 'IBM857', - 'cp862' => 'IBM862', - '862' => 'IBM862', - 'csibm862' => 'IBM862', - 'cp864' => 'IBM864', - '864' => 'IBM864', - 'csibm864' => 'IBM864', - 'ibm-864' => 'IBM864', - 't.61' => 'T.61-8bit', - 'iso-ir-103' => 'T.61-8bit', - 'csiso103t618bit' => 'T.61-8bit', - 'x-unicode-2-0-utf-7' => 'UTF-7', - 'unicode-2-0-utf-7' => 'UTF-7', - 'unicode-1-1-utf-7' => 'UTF-7', - 'csunicode11utf7' => 'UTF-7', - 'csunicode' => 'UTF-16BE', - 'csunicode11' => 'UTF-16BE', - 'iso-10646-ucs-basic' => 'UTF-16BE', - 'csunicodeascii' => 'UTF-16BE', - 'iso-10646-unicode-latin1' => 'UTF-16BE', - 'csunicodelatin1' => 'UTF-16BE', - 'iso-10646' => 'UTF-16BE', - 'iso-10646-j-1' => 'UTF-16BE', - 'latin6' => 'ISO-8859-10', - 'iso-ir-157' => 'ISO-8859-10', - 'l6' => 'ISO-8859-10', - 'csisolatin6' => 'ISO-8859-10', - 'iso_8859-15' => 'ISO-8859-15', - 'csisolatin9' => 'ISO-8859-15', - 'l9' => 'ISO-8859-15', - 'ecma-cyrillic' => 'ISO-IR-111', - 'csiso111ecmacyrillic' => 'ISO-IR-111', - 'csiso2022kr' => 'ISO-2022-KR', - 'csviscii' => 'VISCII', + 'cp866' => 'ibm866', + 'cp-866' => 'ibm866', + '866' => 'ibm866', + 'csibm866' => 'ibm866', + 'cp850' => 'ibm850', + '850' => 'ibm850', + 'csibm850' => 'ibm850', + 'cp852' => 'ibm852', + '852' => 'ibm852', + 'csibm852' => 'ibm852', + 'cp855' => 'ibm855', + '855' => 'ibm855', + 'csibm855' => 'ibm855', + 'cp857' => 'ibm857', + '857' => 'ibm857', + 'csibm857' => 'ibm857', + 'cp862' => 'ibm862', + '862' => 'ibm862', + 'csibm862' => 'ibm862', + 'cp864' => 'ibm864', + '864' => 'ibm864', + 'csibm864' => 'ibm864', + 'ibm-864' => 'ibm864', + 't.61' => 't.61-8bit', + 'iso-ir-103' => 't.61-8bit', + 'csiso103t618bit' => 't.61-8bit', + 'x-unicode-2-0-utf-7' => 'utf-7', + 'unicode-2-0-utf-7' => 'utf-7', + 'unicode-1-1-utf-7' => 'utf-7', + 'csunicode11utf7' => 'utf-7', + 'csunicode' => 'utf-16be', + 'csunicode11' => 'utf-16be', + 'iso-10646-ucs-basic' => 'utf-16be', + 'csunicodeascii' => 'utf-16be', + 'iso-10646-unicode-latin1' => 'utf-16be', + 'csunicodelatin1' => 'utf-16be', + 'iso-10646' => 'utf-16be', + 'iso-10646-j-1' => 'utf-16be', + 'latin6' => 'iso-8859-10', + 'iso-ir-157' => 'iso-8859-10', + 'l6' => 'iso-8859-10', + 'csisolatin6' => 'iso-8859-10', + 'iso_8859-15' => 'iso-8859-15', + 'csisolatin9' => 'iso-8859-15', + 'l9' => 'iso-8859-15', + 'ecma-cyrillic' => 'iso-ir-111', + 'csiso111ecmacyrillic' => 'iso-ir-111', + 'csiso2022kr' => 'iso-2022-kr', + 'csviscii' => 'viscii', 'zh_tw-euc' => 'x-euc-tw', - 'iso88591' => 'ISO-8859-1', - 'iso88592' => 'ISO-8859-2', - 'iso88593' => 'ISO-8859-3', - 'iso88594' => 'ISO-8859-4', - 'iso88595' => 'ISO-8859-5', - 'iso88596' => 'ISO-8859-6', - 'iso88597' => 'ISO-8859-7', - 'iso88598' => 'ISO-8859-8', - 'iso88599' => 'ISO-8859-9', - 'iso885910' => 'ISO-8859-10', - 'iso885911' => 'ISO-8859-11', - 'iso885912' => 'ISO-8859-12', - 'iso885913' => 'ISO-8859-13', - 'iso885914' => 'ISO-8859-14', - 'iso885915' => 'ISO-8859-15', - 'tis620' => 'TIS-620', + 'iso88591' => 'iso-8859-1', + 'iso88592' => 'iso-8859-2', + 'iso88593' => 'iso-8859-3', + 'iso88594' => 'iso-8859-4', + 'iso88595' => 'iso-8859-5', + 'iso88596' => 'iso-8859-6', + 'iso88597' => 'iso-8859-7', + 'iso88598' => 'iso-8859-8', + 'iso88599' => 'iso-8859-9', + 'iso885910' => 'iso-8859-10', + 'iso885911' => 'iso-8859-11', + 'iso885912' => 'iso-8859-12', + 'iso885913' => 'iso-8859-13', + 'iso885914' => 'iso-8859-14', + 'iso885915' => 'iso-8859-15', + 'tis620' => 'tis-620', 'cp1250' => 'windows-1250', 'cp1251' => 'windows-1251', 'cp1252' => 'windows-1252', @@ -315,11 +315,23 @@ class Charset implements CharsetManager */ public function decodeCharset($encodedString, $charset) { - if (strtolower($charset) == 'utf-8' || strtolower($charset) == 'us-ascii') { + $charset = $this->getCharsetAlias($charset); + + if ($charset == 'utf-8' || $charset == 'us-ascii') { return $encodedString; - } else { - return iconv($this->getCharsetAlias($charset), 'UTF-8//TRANSLIT//IGNORE', $encodedString); } + + if (function_exists('mb_convert_encoding')) { + if ($charset == 'iso-2022-jp') { + return mb_convert_encoding($encodedString, 'utf-8', 'iso-2022-jp-ms'); + } + + if (array_search($charset, $this->getSupportedEncodings())) { + return mb_convert_encoding($encodedString, 'utf-8', $charset); + } + } + + return iconv($charset, 'utf-8//translit//ignore', $encodedString); } /** @@ -331,8 +343,28 @@ public function getCharsetAlias($charset) if (array_key_exists($charset, $this->charsetAlias)) { return $this->charsetAlias[$charset]; - } else { - return null; } + + return 'us-ascii'; + } + + private function getSupportedEncodings() + { + return + array_map( + 'strtolower', + array_unique( + array_merge( + $enc = mb_list_encodings(), + call_user_func_array( + 'array_merge', + array_map( + "mb_encoding_aliases", + $enc + ) + ) + ) + ) + ); } } diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Middleware.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Middleware.php index c32fc0653f..19eb90a333 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Middleware.php +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Middleware.php @@ -7,6 +7,8 @@ */ class Middleware implements Contracts\Middleware { + protected $parser; + /** * Create a middleware using a callable $fn * diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MiddlewareStack.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MiddlewareStack.php index 967783a487..3ef6da9349 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MiddlewareStack.php +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MiddlewareStack.php @@ -2,7 +2,7 @@ namespace PhpMimeMailParser; -use PhpMimeMailParser\Contracts\MiddleWare; +use PhpMimeMailParser\Contracts\MiddleWare as MiddleWareContracts; /** * A stack of middleware chained together by (MiddlewareStack $next) @@ -29,7 +29,7 @@ class MiddlewareStack * * @param Middleware $middleware */ - public function __construct(Middleware $middleware = null) + public function __construct(MiddleWareContracts $middleware = null) { $this->middleware = $middleware; } @@ -40,7 +40,7 @@ public function __construct(Middleware $middleware = null) * @param Middleware $middleware * @return MiddlewareStack Immutable MiddlewareStack */ - public function add(Middleware $middleware) + public function add(MiddleWareContracts $middleware) { $stack = new static($middleware); $stack->next = $this; diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MimePart.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MimePart.php index a42728fbe6..a7b3cb0d37 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MimePart.php +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MimePart.php @@ -84,9 +84,9 @@ public function offsetSet($offset, $value) { if (is_null($offset)) { $this->part[] = $value; - } else { - $this->part[$offset] = $value; + return; } + $this->part[$offset] = $value; } /** diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Parser.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Parser.php index 3a641d4854..6502b57ec8 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Parser.php +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Parser.php @@ -110,6 +110,15 @@ public function __destruct() */ public function setPath($path) { + if (is_writable($path)) { + $file = fopen($path, 'a+'); + fseek($file, -1, SEEK_END); + if (fread($file, 1) != "\n") { + fwrite($file, PHP_EOL); + } + fclose($file); + } + // should parse message incrementally from file $this->resource = mailparse_msg_parse_file($path); $this->stream = fopen($path, 'r'); @@ -142,6 +151,11 @@ public function setStream($stream) while (!feof($stream)) { fwrite($tmp_fp, fread($stream, 2028)); } + + if (fread($tmp_fp, 1) != "\n") { + fwrite($tmp_fp, PHP_EOL); + } + fseek($tmp_fp, 0); $this->stream = &$tmp_fp; } else { @@ -170,9 +184,14 @@ public function setStream($stream) */ public function setText($data) { - if (!$data) { + if (empty($data)) { throw new Exception('You must not call MimeMailParser::setText with an empty string parameter'); } + + if (substr($data, -1) != "\n") { + $data = $data.PHP_EOL; + } + $this->resource = mailparse_msg_create(); // does not parse incrementally, fast memory hog might explode mailparse_msg_parse($this->resource, $data); @@ -205,7 +224,7 @@ protected function parse() * * @param string $name Header name (case-insensitive) * - * @return string + * @return string|bool * @throws Exception */ public function getRawHeader($name) @@ -214,7 +233,7 @@ public function getRawHeader($name) if (isset($this->parts[1])) { $headers = $this->getPart('headers', $this->parts[1]); - return (isset($headers[$name])) ? $headers[$name] : false; + return isset($headers[$name]) ? $headers[$name] : false; } else { throw new Exception( 'setPath() or setText() or setStream() must be called before retrieving email headers.' @@ -227,7 +246,7 @@ public function getRawHeader($name) * * @param string $name Header name (case-insensitive) * - * @return string + * @return string|bool */ public function getHeader($name) { @@ -249,7 +268,7 @@ public function getHeaders() { if (isset($this->parts[1])) { $headers = $this->getPart('headers', $this->parts[1]); - foreach ($headers as $name => &$value) { + foreach ($headers as &$value) { if (is_array($value)) { foreach ($value as &$v) { $v = $this->decodeSingleHeader($v); @@ -495,20 +514,17 @@ public function getAttachments($include_inline = true) if (isset($part['disposition-filename'])) { $filename = $this->decodeHeader($part['disposition-filename']); - // Escape all potentially unsafe characters from the filename - $filename = preg_replace('((^\.)|\/|(\.$))', '_', $filename); } elseif (isset($part['content-name'])) { // if we have no disposition but we have a content-name, it's a valid attachment. // we simulate the presence of an attachment disposition with a disposition filename $filename = $this->decodeHeader($part['content-name']); - // Escape all potentially unsafe characters from the filename - $filename = preg_replace('((^\.)|\/|(\.$))', '_', $filename); $disposition = 'attachment'; } elseif (in_array($part['content-type'], $non_attachment_types, true) && $disposition !== 'attachment') { // it is a message body, no attachment continue; - } elseif (substr($part['content-type'], 0, 10) !== 'multipart/') { + } elseif (substr($part['content-type'], 0, 10) !== 'multipart/' + && $part['content-type'] !== 'text/plain; (error)') { // if we cannot get it by getMessageBody(), we assume it is an attachment $disposition = 'attachment'; } @@ -520,6 +536,9 @@ public function getAttachments($include_inline = true) if ($filename == 'noname') { $nonameIter++; $filename = 'noname'.$nonameIter; + } else { + // Escape all potentially unsafe characters from the filename + $filename = preg_replace('((^\.)|\/|[\n|\r|\n\r]|(\.$))', '_', $filename); } $headersAttachments = $this->getPart('headers', $part); @@ -559,50 +578,10 @@ public function saveAttachments( $filenameStrategy = self::ATTACHMENT_DUPLICATE_SUFFIX ) { $attachments = $this->getAttachments($include_inline); - if (empty($attachments)) { - return false; - } - - if (!is_dir($attach_dir)) { - mkdir($attach_dir); - } $attachments_paths = []; foreach ($attachments as $attachment) { - // Determine filename - switch ($filenameStrategy) { - case self::ATTACHMENT_RANDOM_FILENAME: - $attachment_path = tempnam($attach_dir, ''); - break; - case self::ATTACHMENT_DUPLICATE_THROW: - case self::ATTACHMENT_DUPLICATE_SUFFIX: - $attachment_path = $attach_dir.$attachment->getFilename(); - break; - default: - throw new Exception('Invalid filename strategy argument provided.'); - } - - // Handle duplicate filename - if (file_exists($attachment_path)) { - switch ($filenameStrategy) { - case self::ATTACHMENT_DUPLICATE_THROW: - throw new Exception('Could not create file for attachment: duplicate filename.'); - case self::ATTACHMENT_DUPLICATE_SUFFIX: - $attachment_path = tempnam($attach_dir, $attachment->getFilename()); - break; - } - } - - /** @var resource $fp */ - if ($fp = fopen($attachment_path, 'w')) { - while ($bytes = $attachment->read()) { - fwrite($fp, $bytes); - } - fclose($fp); - $attachments_paths[] = realpath($attachment_path); - } else { - throw new Exception('Could not write attachments. Your directory may be unwritable by PHP.'); - } + $attachments_paths[] = $attachment->save($attach_dir, $filenameStrategy); } return $attachments_paths; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/.github/ISSUE_TEMPLATE.md b/data/web/inc/lib/vendor/phpmailer/phpmailer/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 97776f1cd9..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1 +0,0 @@ -Non-security issues and pull requests are no longer being accepted for the legacy PHPMailer 5.2 branch. Migrate to PHPMailer 6.0 (or later) and report your issue there. \ No newline at end of file diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/.github/PULL_REQUEST_TEMPLATE.md b/data/web/inc/lib/vendor/phpmailer/phpmailer/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 97776f1cd9..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1 +0,0 @@ -Non-security issues and pull requests are no longer being accepted for the legacy PHPMailer 5.2 branch. Migrate to PHPMailer 6.0 (or later) and report your issue there. \ No newline at end of file diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/COMMITMENT b/data/web/inc/lib/vendor/phpmailer/phpmailer/COMMITMENT new file mode 100644 index 0000000000..a687e0ddb6 --- /dev/null +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/COMMITMENT @@ -0,0 +1,46 @@ +GPL Cooperation Commitment +Version 1.0 + +Before filing or continuing to prosecute any legal proceeding or claim +(other than a Defensive Action) arising from termination of a Covered +License, we commit to extend to the person or entity ('you') accused +of violating the Covered License the following provisions regarding +cure and reinstatement, taken from GPL version 3. As used here, the +term 'this License' refers to the specific Covered License being +enforced. + + 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. + +We intend this Commitment to be irrevocable, and binding and +enforceable against us and assignees of or successors to our +copyrights. + +Definitions + +'Covered License' means the GNU General Public License, version 2 +(GPLv2), the GNU Lesser General Public License, version 2.1 +(LGPLv2.1), or the GNU Library General Public License, version 2 +(LGPLv2), all as published by the Free Software Foundation. + +'Defensive Action' means a legal proceeding or claim that We bring +against you in response to a prior proceeding or claim initiated by +you or your affiliate. + +'We' means each contributor to this repository as of the date of +inclusion of this file, including subsidiaries of a corporate +contributor. + +This work is available under a Creative Commons Attribution-ShareAlike +4.0 International license (https://creativecommons.org/licenses/by-sa/4.0/). diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/LICENSE b/data/web/inc/lib/vendor/phpmailer/phpmailer/LICENSE index 8e0763d1c2..f166cc57b2 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/LICENSE +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/LICENSE @@ -1,8 +1,8 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -10,7 +10,7 @@ as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] - Preamble + Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public @@ -55,7 +55,7 @@ modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. - + Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a @@ -111,8 +111,8 @@ modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE + + GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other @@ -146,7 +146,7 @@ such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. - + 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an @@ -158,7 +158,7 @@ Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. - + 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 @@ -216,7 +216,7 @@ instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. - + Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. @@ -267,7 +267,7 @@ Library will still fall under Section 6.) distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. - + 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work @@ -312,7 +312,7 @@ of these things: from a designated place, offer equivalent access to copy the above specified materials from the same place. - e) verify that the user has already received a copy of these + e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the @@ -329,7 +329,7 @@ restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. - + 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined @@ -370,7 +370,7 @@ subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. - + 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or @@ -422,7 +422,7 @@ conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. - + 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is @@ -432,7 +432,7 @@ decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - NO WARRANTY + NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. @@ -455,8 +455,8 @@ FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - END OF TERMS AND CONDITIONS - + END OF TERMS AND CONDITIONS + How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest @@ -485,7 +485,7 @@ convey the exclusion of warranty; and each file should have at least the You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. @@ -499,6 +499,4 @@ necessary. Here is a sample; alter the names: , 1 April 1990 Ty Coon, President of Vice -That's all there is to it! - - +That's all there is to it! \ No newline at end of file diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/PHPMailerAutoload.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/PHPMailerAutoload.php deleted file mode 100644 index eaa2e30349..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/PHPMailerAutoload.php +++ /dev/null @@ -1,49 +0,0 @@ - - * @author Jim Jagielski (jimjag) - * @author Andy Prevost (codeworxtech) - * @author Brent R. Matzelle (original founder) - * @copyright 2012 - 2014 Marcus Bointon - * @copyright 2010 - 2012 Jim Jagielski - * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License - * @note This program is distributed in the hope that it will be useful - WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. - */ - -/** - * PHPMailer SPL autoloader. - * @param string $classname The name of the class to load - */ -function PHPMailerAutoload($classname) -{ - //Can't use __DIR__ as it's only in PHP 5.3+ - $filename = dirname(__FILE__).DIRECTORY_SEPARATOR.'class.'.strtolower($classname).'.php'; - if (is_readable($filename)) { - require $filename; - } -} - -if (version_compare(PHP_VERSION, '5.1.2', '>=')) { - //SPL autoloading was introduced in PHP 5.1.2 - if (version_compare(PHP_VERSION, '5.3.0', '>=')) { - spl_autoload_register('PHPMailerAutoload', true, true); - } else { - spl_autoload_register('PHPMailerAutoload'); - } -} else { - /** - * Fall back to traditional autoload for old PHP versions - * @param string $classname The name of the class to load - */ - function __autoload($classname) - { - PHPMailerAutoload($classname); - } -} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/README.md b/data/web/inc/lib/vendor/phpmailer/phpmailer/README.md new file mode 100644 index 0000000000..81b0897637 --- /dev/null +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/README.md @@ -0,0 +1,227 @@ +![PHPMailer](https://raw.github.com/PHPMailer/PHPMailer/master/examples/images/phpmailer.png) + +# PHPMailer โ€“ A full-featured email creation and transfer class for PHP + +[![Test status](https://github.com/PHPMailer/PHPMailer/workflows/Tests/badge.svg)](https://github.com/PHPMailer/PHPMailer/actions) +[![codecov.io](https://codecov.io/gh/PHPMailer/PHPMailer/branch/master/graph/badge.svg?token=iORZpwmYmM)](https://codecov.io/gh/PHPMailer/PHPMailer) +[![Latest Stable Version](https://poser.pugx.org/phpmailer/phpmailer/v/stable.svg)](https://packagist.org/packages/phpmailer/phpmailer) +[![Total Downloads](https://poser.pugx.org/phpmailer/phpmailer/downloads)](https://packagist.org/packages/phpmailer/phpmailer) +[![License](https://poser.pugx.org/phpmailer/phpmailer/license.svg)](https://packagist.org/packages/phpmailer/phpmailer) +[![API Docs](https://github.com/phpmailer/phpmailer/workflows/Docs/badge.svg)](https://phpmailer.github.io/PHPMailer/) + +## Features +- Probably the world's most popular code for sending email from PHP! +- Used by many open-source projects: WordPress, Drupal, 1CRM, SugarCRM, Yii, Joomla! and many more +- Integrated SMTP support โ€“ send without a local mail server +- Send emails with multiple To, CC, BCC and Reply-to addresses +- Multipart/alternative emails for mail clients that do not read HTML email +- Add attachments, including inline +- Support for UTF-8 content and 8bit, base64, binary, and quoted-printable encodings +- SMTP authentication with LOGIN, PLAIN, CRAM-MD5, and XOAUTH2 mechanisms over SMTPS and SMTP+STARTTLS transports +- Validates email addresses automatically +- Protects against header injection attacks +- Error messages in over 50 languages! +- DKIM and S/MIME signing support +- Compatible with PHP 5.5 and later, including PHP 8.1 +- Namespaced to prevent name clashes +- Much more! + +## Why you might need it +Many PHP developers need to send email from their code. The only PHP function that supports this directly is [`mail()`](https://www.php.net/manual/en/function.mail.php). However, it does not provide any assistance for making use of popular features such as encryption, authentication, HTML messages, and attachments. + +Formatting email correctly is surprisingly difficult. There are myriad overlapping (and conflicting) standards, requiring tight adherence to horribly complicated formatting and encoding rules โ€“ the vast majority of code that you'll find online that uses the `mail()` function directly is just plain wrong, if not unsafe! + +The PHP `mail()` function usually sends via a local mail server, typically fronted by a `sendmail` binary on Linux, BSD, and macOS platforms, however, Windows usually doesn't include a local mail server; PHPMailer's integrated SMTP client allows email sending on all platforms without needing a local mail server. Be aware though, that the `mail()` function should be avoided when possible; it's both faster and [safer](https://exploitbox.io/paper/Pwning-PHP-Mail-Function-For-Fun-And-RCE.html) to use SMTP to localhost. + +*Please* don't be tempted to do it yourself โ€“ if you don't use PHPMailer, there are many other excellent libraries that +you should look at before rolling your own. Try [SwiftMailer](https://swiftmailer.symfony.com/) +, [Laminas/Mail](https://docs.laminas.dev/laminas-mail/), [ZetaComponents](https://github.com/zetacomponents/Mail) etc. + +## License +This software is distributed under the [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) license, along with the [GPL Cooperation Commitment](https://gplcc.github.io/gplcc/). Please read [LICENSE](https://github.com/PHPMailer/PHPMailer/blob/master/LICENSE) for information on the software availability and distribution. + +## Installation & loading +PHPMailer is available on [Packagist](https://packagist.org/packages/phpmailer/phpmailer) (using semantic versioning), and installation via [Composer](https://getcomposer.org) is the recommended way to install PHPMailer. Just add this line to your `composer.json` file: + +```json +"phpmailer/phpmailer": "^6.5" +``` + +or run + +```sh +composer require phpmailer/phpmailer +``` + +Note that the `vendor` folder and the `vendor/autoload.php` script are generated by Composer; they are not part of PHPMailer. + +If you want to use the Gmail XOAUTH2 authentication class, you will also need to add a dependency on the `league/oauth2-client` package in your `composer.json`. + +Alternatively, if you're not using Composer, you +can [download PHPMailer as a zip file](https://github.com/PHPMailer/PHPMailer/archive/master.zip), (note that docs and examples are not included in the zip file), then copy the contents of the PHPMailer folder into one of the `include_path` directories specified in your PHP configuration and load each class file manually: + +```php +SMTPDebug = SMTP::DEBUG_SERVER; //Enable verbose debug output + $mail->isSMTP(); //Send using SMTP + $mail->Host = 'smtp.example.com'; //Set the SMTP server to send through + $mail->SMTPAuth = true; //Enable SMTP authentication + $mail->Username = 'user@example.com'; //SMTP username + $mail->Password = 'secret'; //SMTP password + $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; //Enable implicit TLS encryption + $mail->Port = 465; //TCP port to connect to; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS` + + //Recipients + $mail->setFrom('from@example.com', 'Mailer'); + $mail->addAddress('joe@example.net', 'Joe User'); //Add a recipient + $mail->addAddress('ellen@example.com'); //Name is optional + $mail->addReplyTo('info@example.com', 'Information'); + $mail->addCC('cc@example.com'); + $mail->addBCC('bcc@example.com'); + + //Attachments + $mail->addAttachment('/var/tmp/file.tar.gz'); //Add attachments + $mail->addAttachment('/tmp/image.jpg', 'new.jpg'); //Optional name + + //Content + $mail->isHTML(true); //Set email format to HTML + $mail->Subject = 'Here is the subject'; + $mail->Body = 'This is the HTML message body in bold!'; + $mail->AltBody = 'This is the body in plain text for non-HTML mail clients'; + + $mail->send(); + echo 'Message has been sent'; +} catch (Exception $e) { + echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}"; +} +``` + +You'll find plenty to play with in the [examples](https://github.com/PHPMailer/PHPMailer/tree/master/examples) folder, which covers many common scenarios including sending through gmail, building contact forms, sending to mailing lists, and more. + +If you are re-using the instance (e.g. when sending to a mailing list), you may need to clear the recipient list to avoid sending duplicate messages. See [the mailing list example](https://github.com/PHPMailer/PHPMailer/blob/master/examples/mailing_list.phps) for further guidance. + +That's it. You should now be ready to use PHPMailer! + +## Localization +PHPMailer defaults to English, but in the [language](https://github.com/PHPMailer/PHPMailer/tree/master/language/) folder you'll find many translations for PHPMailer error messages that you may encounter. Their filenames contain [ISO 639-1](http://en.wikipedia.org/wiki/ISO_639-1) language code for the translations, for example `fr` for French. To specify a language, you need to tell PHPMailer which one to use, like this: + +```php +//To load the French version +$mail->setLanguage('fr', '/optional/path/to/language/directory/'); +``` + +We welcome corrections and new languages โ€“ if you're looking for corrections, run the [PHPMailerLangTest.php](https://github.com/PHPMailer/PHPMailer/tree/master/test/PHPMailerLangTest.php) script in the tests folder and it will show any missing translations. + +## Documentation +Start reading at the [GitHub wiki](https://github.com/PHPMailer/PHPMailer/wiki). If you're having trouble, head for [the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting) as it's frequently updated. + +Examples of how to use PHPMailer for common scenarios can be found in the [examples](https://github.com/PHPMailer/PHPMailer/tree/master/examples) folder. If you're looking for a good starting point, we recommend you start with [the Gmail example](https://github.com/PHPMailer/PHPMailer/tree/master/examples/gmail.phps). + +To reduce PHPMailer's deployed code footprint, examples are not included if you load PHPMailer via Composer or via [GitHub's zip file download](https://github.com/PHPMailer/PHPMailer/archive/master.zip), so you'll need to either clone the git repository or use the above links to get to the examples directly. + +Complete generated API documentation is [available online](https://phpmailer.github.io/PHPMailer/). + +You can generate complete API-level documentation by running `phpdoc` in the top-level folder, and documentation will appear in the `docs` folder, though you'll need to have [PHPDocumentor](http://www.phpdoc.org) installed. You may find [the unit tests](https://github.com/PHPMailer/PHPMailer/blob/master/test/PHPMailerTest.php) a good reference for how to do various operations such as encryption. + +If the documentation doesn't cover what you need, search the [many questions on Stack Overflow](http://stackoverflow.com/questions/tagged/phpmailer), and before you ask a question about "SMTP Error: Could not connect to SMTP host.", [read the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting). + +## Tests +[PHPMailer tests](https://github.com/PHPMailer/PHPMailer/tree/master/test/) use PHPUnit 9, with [a polyfill](https://github.com/Yoast/PHPUnit-Polyfills) to let 9-style tests run on older PHPUnit and PHP versions. + +[![Test status](https://github.com/PHPMailer/PHPMailer/workflows/Tests/badge.svg)](https://github.com/PHPMailer/PHPMailer/actions) + +If this isn't passing, is there something you can do to help? + +## Security +Please disclose any vulnerabilities found responsibly โ€“ report security issues to the maintainers privately. + +See [SECURITY](https://github.com/PHPMailer/PHPMailer/tree/master/SECURITY.md) and [PHPMailer's security advisories on GitHub](https://github.com/PHPMailer/PHPMailer/security). + +## Contributing +Please submit bug reports, suggestions and pull requests to the [GitHub issue tracker](https://github.com/PHPMailer/PHPMailer/issues). + +We're particularly interested in fixing edge-cases, expanding test coverage and updating translations. + +If you found a mistake in the docs, or want to add something, go ahead and amend the wiki โ€“ anyone can edit it. + +If you have git clones from prior to the move to the PHPMailer GitHub organisation, you'll need to update any remote URLs referencing the old GitHub location with a command like this from within your clone: + +```sh +git remote set-url upstream https://github.com/PHPMailer/PHPMailer.git +``` + +Please *don't* use the SourceForge or Google Code projects any more; they are obsolete and no longer maintained. + +## Sponsorship +Development time and resources for PHPMailer are provided by [Smartmessages.net](https://info.smartmessages.net/), the world's only privacy-first email marketing system. + +Smartmessages.net privacy-first email marketing logo + +Donations are very welcome, whether in beer ๐Ÿบ, T-shirts ๐Ÿ‘•, or cold, hard cash ๐Ÿ’ฐ. Sponsorship through GitHub is a simple and convenient way to say "thank you" to PHPMailer's maintainers and contributors โ€“ just click the "Sponsor" button [on the project page](https://github.com/PHPMailer/PHPMailer). If your company uses PHPMailer, consider taking part in Tidelift's enterprise support programme. + +## PHPMailer For Enterprise + +Available as part of the Tidelift Subscription. + +The maintainers of PHPMailer and thousands of other packages are working with Tidelift to deliver commercial +support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and +improve code health, while paying the maintainers of the exact packages you +use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-phpmailer-phpmailer?utm_source=packagist-phpmailer-phpmailer&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) + +## Changelog +See [changelog](changelog.md). + +## History +- PHPMailer was originally written in 2001 by Brent R. Matzelle as a [SourceForge project](http://sourceforge.net/projects/phpmailer/). +- [Marcus Bointon](https://github.com/Synchro) (`coolbru` on SF) and Andy Prevost (`codeworxtech`) took over the project in 2004. +- Became an Apache incubator project on Google Code in 2010, managed by Jim Jagielski. +- Marcus created [his fork on GitHub](https://github.com/Synchro/PHPMailer) in 2008. +- Jim and Marcus decide to join forces and use GitHub as the canonical and official repo for PHPMailer in 2013. +- PHPMailer moves to [the PHPMailer organisation](https://github.com/PHPMailer) on GitHub in 2013. + +### What's changed since moving from SourceForge? +- Official successor to the SourceForge and Google Code projects. +- Test suite. +- Continuous integration with Github Actions. +- Composer support. +- Public development. +- Additional languages and language strings. +- CRAM-MD5 authentication support. +- Preserves full repo history of authors, commits and branches from the original SourceForge project. diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/SECURITY.md b/data/web/inc/lib/vendor/phpmailer/phpmailer/SECURITY.md new file mode 100644 index 0000000000..035a87f7b0 --- /dev/null +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/SECURITY.md @@ -0,0 +1,37 @@ +# Security notices relating to PHPMailer + +Please disclose any security issues or vulnerabilities found through [Tidelift's coordinated disclosure system](https://tidelift.com/security) or to the maintainers privately. + +PHPMailer 6.4.1 and earlier contain a vulnerability that can result in untrusted code being called (if such code is injected into the host project's scope by other means). If the `$patternselect` parameter to `validateAddress()` is set to `'php'` (the default, defined by `PHPMailer::$validator`), and the global namespace contains a function called `php`, it will be called in preference to the built-in validator of the same name. Mitigated in PHPMailer 6.5.0 by denying the use of simple strings as validator function names. Recorded as [CVE-2021-3603](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2021-3603). Reported by [Vikrant Singh Chauhan](mailto:vi@hackberry.xyz) via [huntr.dev](https://www.huntr.dev/). + +PHPMailer versions 6.4.1 and earlier contain a possible remote code execution vulnerability through the `$lang_path` parameter of the `setLanguage()` method. If the `$lang_path` parameter is passed unfiltered from user input, it can be set to [a UNC path](https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats#unc-paths), and if an attacker is also able to persuade the server to load a file from that UNC path, a script file under their control may be executed. This vulnerability only applies to systems that resolve UNC paths, typically only Microsoft Windows. +PHPMailer 6.5.0 mitigates this by no longer treating translation files as PHP code, but by parsing their text content directly. This approach avoids the possibility of executing unknown code while retaining backward compatibility. This isn't ideal, so the current translation format is deprecated and will be replaced in the next major release. Recorded as [CVE-2021-34551](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2021-34551). Reported by [Jilin Diting Information Technology Co., Ltd](https://listensec.com) via Tidelift. + +PHPMailer versions between 6.1.8 and 6.4.0 contain a regression of the earlier CVE-2018-19296 object injection vulnerability as a result of [a fix for Windows UNC paths in 6.1.8](https://github.com/PHPMailer/PHPMailer/commit/e2e07a355ee8ff36aba21d0242c5950c56e4c6f9). Recorded as [CVE-2020-36326](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-36326). Reported by Fariskhi Vidyan via Tidelift. 6.4.1 fixes this issue, and also enforces stricter checks for URL schemes in local path contexts. + +PHPMailer versions 6.1.5 and earlier contain an output escaping bug that occurs in `Content-Type` and `Content-Disposition` when filenames passed into `addAttachment` and other methods that accept attachment names contain double quote characters, in contravention of RFC822 3.4.1. No specific vulnerability has been found relating to this, but it could allow file attachments to bypass attachment filters that are based on matching filename extensions. Recorded as [CVE-2020-13625](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-13625). Reported by Elar Lang of Clarified Security. + +PHPMailer versions prior to 6.0.6 and 5.2.27 are vulnerable to an object injection attack by passing `phar://` paths into `addAttachment()` and other functions that may receive unfiltered local paths, possibly leading to RCE. Recorded as [CVE-2018-19296](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2018-19296). See [this article](https://knasmueller.net/5-answers-about-php-phar-exploitation) for more info on this type of vulnerability. Mitigated by blocking the use of paths containing URL-protocol style prefixes such as `phar://`. Reported by Sehun Oh of cyberone.kr. + +PHPMailer versions prior to 5.2.24 (released July 26th 2017) have an XSS vulnerability in one of the code examples, [CVE-2017-11503](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-11503). The `code_generator.phps` example did not filter user input prior to output. This file is distributed with a `.phps` extension, so it it not normally executable unless it is explicitly renamed, and the file is not included when PHPMailer is loaded through composer, so it is safe by default. There was also an undisclosed potential XSS vulnerability in the default exception handler (unused by default). Patches for both issues kindly provided by Patrick Monnerat of the Fedora Project. + +PHPMailer versions prior to 5.2.22 (released January 9th 2017) have a local file disclosure vulnerability, [CVE-2017-5223](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-5223). If content passed into `msgHTML()` is sourced from unfiltered user input, relative paths can map to absolute local file paths and added as attachments. Also note that `addAttachment` (just like `file_get_contents`, `passthru`, `unlink`, etc) should not be passed user-sourced params either! Reported by Yongxiang Li of Asiasecurity. + +PHPMailer versions prior to 5.2.20 (released December 28th 2016) are vulnerable to [CVE-2016-10045](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10045) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10045-Vuln-Patch-Bypass.html), and patched by Paul Buonopane (@Zenexer). + +PHPMailer versions prior to 5.2.18 (released December 2016) are vulnerable to [CVE-2016-10033](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10033) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](http://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10033-Vuln.html). + +PHPMailer versions prior to 5.2.14 (released November 2015) are vulnerable to [CVE-2015-8476](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-8476) an SMTP CRLF injection bug permitting arbitrary message sending. + +PHPMailer versions prior to 5.2.10 (released May 2015) are vulnerable to [CVE-2008-5619](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2008-5619), a remote code execution vulnerability in the bundled html2text library. This file was removed in 5.2.10, so if you are using a version prior to that and make use of the html2text function, it's vitally important that you upgrade and remove this file. + +PHPMailer versions prior to 2.0.7 and 2.2.1 are vulnerable to [CVE-2012-0796](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2012-0796), an email header injection attack. + +Joomla 1.6.0 uses PHPMailer in an unsafe way, allowing it to reveal local file paths, reported in [CVE-2011-3747](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-3747). + +PHPMailer didn't sanitise the `$lang_path` parameter in `SetLanguage`. This wasn't a problem in itself, but some apps (PHPClassifieds, ATutor) also failed to sanitise user-provided parameters passed to it, permitting semi-arbitrary local file inclusion, reported in [CVE-2010-4914](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2010-4914), [CVE-2007-2021](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2007-2021) and [CVE-2006-5734](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2006-5734). + +PHPMailer 1.7.2 and earlier contained a possible DDoS vulnerability reported in [CVE-2005-1807](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2005-1807). + +PHPMailer 1.7 and earlier (June 2003) have a possible vulnerability in the `SendmailSend` method where shell commands may not be sanitised. Reported in [CVE-2007-3215](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2007-3215). + diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION b/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION index f0fb1a22ad..cd802a1ec4 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION @@ -1 +1 @@ -5.2.26 +6.6.0 \ No newline at end of file diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php deleted file mode 100644 index 99f9092c08..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php +++ /dev/null @@ -1,4044 +0,0 @@ - - * @author Jim Jagielski (jimjag) - * @author Andy Prevost (codeworxtech) - * @author Brent R. Matzelle (original founder) - * @copyright 2012 - 2014 Marcus Bointon - * @copyright 2010 - 2012 Jim Jagielski - * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License - * @note This program is distributed in the hope that it will be useful - WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. - */ - -/** - * PHPMailer - PHP email creation and transport class. - * @package PHPMailer - * @author Marcus Bointon (Synchro/coolbru) - * @author Jim Jagielski (jimjag) - * @author Andy Prevost (codeworxtech) - * @author Brent R. Matzelle (original founder) - */ -class PHPMailer -{ - /** - * The PHPMailer Version number. - * @var string - */ - public $Version = '5.2.26'; - - /** - * Email priority. - * Options: null (default), 1 = High, 3 = Normal, 5 = low. - * When null, the header is not set at all. - * @var integer - */ - public $Priority = null; - - /** - * The character set of the message. - * @var string - */ - public $CharSet = 'iso-8859-1'; - - /** - * The MIME Content-type of the message. - * @var string - */ - public $ContentType = 'text/plain'; - - /** - * The message encoding. - * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". - * @var string - */ - public $Encoding = '8bit'; - - /** - * Holds the most recent mailer error message. - * @var string - */ - public $ErrorInfo = ''; - - /** - * The From email address for the message. - * @var string - */ - public $From = 'root@localhost'; - - /** - * The From name of the message. - * @var string - */ - public $FromName = 'Root User'; - - /** - * The Sender email (Return-Path) of the message. - * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode. - * @var string - */ - public $Sender = ''; - - /** - * The Return-Path of the message. - * If empty, it will be set to either From or Sender. - * @var string - * @deprecated Email senders should never set a return-path header; - * it's the receiver's job (RFC5321 section 4.4), so this no longer does anything. - * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference - */ - public $ReturnPath = ''; - - /** - * The Subject of the message. - * @var string - */ - public $Subject = ''; - - /** - * An HTML or plain text message body. - * If HTML then call isHTML(true). - * @var string - */ - public $Body = ''; - - /** - * The plain-text message body. - * This body can be read by mail clients that do not have HTML email - * capability such as mutt & Eudora. - * Clients that can read HTML will view the normal Body. - * @var string - */ - public $AltBody = ''; - - /** - * An iCal message part body. - * Only supported in simple alt or alt_inline message types - * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator - * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ - * @link http://kigkonsult.se/iCalcreator/ - * @var string - */ - public $Ical = ''; - - /** - * The complete compiled MIME message body. - * @access protected - * @var string - */ - protected $MIMEBody = ''; - - /** - * The complete compiled MIME message headers. - * @var string - * @access protected - */ - protected $MIMEHeader = ''; - - /** - * Extra headers that createHeader() doesn't fold in. - * @var string - * @access protected - */ - protected $mailHeader = ''; - - /** - * Word-wrap the message body to this number of chars. - * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. - * @var integer - */ - public $WordWrap = 0; - - /** - * Which method to use to send mail. - * Options: "mail", "sendmail", or "smtp". - * @var string - */ - public $Mailer = 'mail'; - - /** - * The path to the sendmail program. - * @var string - */ - public $Sendmail = '/usr/sbin/sendmail'; - - /** - * Whether mail() uses a fully sendmail-compatible MTA. - * One which supports sendmail's "-oi -f" options. - * @var boolean - */ - public $UseSendmailOptions = true; - - /** - * Path to PHPMailer plugins. - * Useful if the SMTP class is not in the PHP include path. - * @var string - * @deprecated Should not be needed now there is an autoloader. - */ - public $PluginDir = ''; - - /** - * The email address that a reading confirmation should be sent to, also known as read receipt. - * @var string - */ - public $ConfirmReadingTo = ''; - - /** - * The hostname to use in the Message-ID header and as default HELO string. - * If empty, PHPMailer attempts to find one with, in order, - * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value - * 'localhost.localdomain'. - * @var string - */ - public $Hostname = ''; - - /** - * An ID to be used in the Message-ID header. - * If empty, a unique id will be generated. - * You can set your own, but it must be in the format "", - * as defined in RFC5322 section 3.6.4 or it will be ignored. - * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 - * @var string - */ - public $MessageID = ''; - - /** - * The message Date to be used in the Date header. - * If empty, the current date will be added. - * @var string - */ - public $MessageDate = ''; - - /** - * SMTP hosts. - * Either a single hostname or multiple semicolon-delimited hostnames. - * You can also specify a different port - * for each host by using this format: [hostname:port] - * (e.g. "smtp1.example.com:25;smtp2.example.com"). - * You can also specify encryption type, for example: - * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). - * Hosts will be tried in order. - * @var string - */ - public $Host = 'localhost'; - - /** - * The default SMTP server port. - * @var integer - * @TODO Why is this needed when the SMTP class takes care of it? - */ - public $Port = 25; - - /** - * The SMTP HELO of the message. - * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find - * one with the same method described above for $Hostname. - * @var string - * @see PHPMailer::$Hostname - */ - public $Helo = ''; - - /** - * What kind of encryption to use on the SMTP connection. - * Options: '', 'ssl' or 'tls' - * @var string - */ - public $SMTPSecure = ''; - - /** - * Whether to enable TLS encryption automatically if a server supports it, - * even if `SMTPSecure` is not set to 'tls'. - * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. - * @var boolean - */ - public $SMTPAutoTLS = true; - - /** - * Whether to use SMTP authentication. - * Uses the Username and Password properties. - * @var boolean - * @see PHPMailer::$Username - * @see PHPMailer::$Password - */ - public $SMTPAuth = false; - - /** - * Options array passed to stream_context_create when connecting via SMTP. - * @var array - */ - public $SMTPOptions = array(); - - /** - * SMTP username. - * @var string - */ - public $Username = ''; - - /** - * SMTP password. - * @var string - */ - public $Password = ''; - - /** - * SMTP auth type. - * Options are CRAM-MD5, LOGIN, PLAIN, NTLM, XOAUTH2, attempted in that order if not specified - * @var string - */ - public $AuthType = ''; - - /** - * SMTP realm. - * Used for NTLM auth - * @var string - */ - public $Realm = ''; - - /** - * SMTP workstation. - * Used for NTLM auth - * @var string - */ - public $Workstation = ''; - - /** - * The SMTP server timeout in seconds. - * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 - * @var integer - */ - public $Timeout = 300; - - /** - * SMTP class debug output mode. - * Debug output level. - * Options: - * * `0` No output - * * `1` Commands - * * `2` Data and commands - * * `3` As 2 plus connection status - * * `4` Low-level data output - * @var integer - * @see SMTP::$do_debug - */ - public $SMTPDebug = 0; - - /** - * How to handle debug output. - * Options: - * * `echo` Output plain-text as-is, appropriate for CLI - * * `html` Output escaped, line breaks converted to `
    `, appropriate for browser output - * * `error_log` Output to error log as configured in php.ini - * - * Alternatively, you can provide a callable expecting two params: a message string and the debug level: - * - * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; - * - * @var string|callable - * @see SMTP::$Debugoutput - */ - public $Debugoutput = 'echo'; - - /** - * Whether to keep SMTP connection open after each message. - * If this is set to true then to close the connection - * requires an explicit call to smtpClose(). - * @var boolean - */ - public $SMTPKeepAlive = false; - - /** - * Whether to split multiple to addresses into multiple messages - * or send them all in one message. - * Only supported in `mail` and `sendmail` transports, not in SMTP. - * @var boolean - */ - public $SingleTo = false; - - /** - * Storage for addresses when SingleTo is enabled. - * @var array - * @TODO This should really not be public - */ - public $SingleToArray = array(); - - /** - * Whether to generate VERP addresses on send. - * Only applicable when sending via SMTP. - * @link https://en.wikipedia.org/wiki/Variable_envelope_return_path - * @link http://www.postfix.org/VERP_README.html Postfix VERP info - * @var boolean - */ - public $do_verp = false; - - /** - * Whether to allow sending messages with an empty body. - * @var boolean - */ - public $AllowEmpty = false; - - /** - * The default line ending. - * @note The default remains "\n". We force CRLF where we know - * it must be used via self::CRLF. - * @var string - */ - public $LE = "\n"; - - /** - * DKIM selector. - * @var string - */ - public $DKIM_selector = ''; - - /** - * DKIM Identity. - * Usually the email address used as the source of the email. - * @var string - */ - public $DKIM_identity = ''; - - /** - * DKIM passphrase. - * Used if your key is encrypted. - * @var string - */ - public $DKIM_passphrase = ''; - - /** - * DKIM signing domain name. - * @example 'example.com' - * @var string - */ - public $DKIM_domain = ''; - - /** - * DKIM private key file path. - * @var string - */ - public $DKIM_private = ''; - - /** - * DKIM private key string. - * If set, takes precedence over `$DKIM_private`. - * @var string - */ - public $DKIM_private_string = ''; - - /** - * Callback Action function name. - * - * The function that handles the result of the send email action. - * It is called out by send() for each email sent. - * - * Value can be any php callable: http://www.php.net/is_callable - * - * Parameters: - * boolean $result result of the send action - * array $to email addresses of the recipients - * array $cc cc email addresses - * array $bcc bcc email addresses - * string $subject the subject - * string $body the email body - * string $from email address of sender - * @var string - */ - public $action_function = ''; - - /** - * What to put in the X-Mailer header. - * Options: An empty string for PHPMailer default, whitespace for none, or a string to use - * @var string - */ - public $XMailer = ''; - - /** - * Which validator to use by default when validating email addresses. - * May be a callable to inject your own validator, but there are several built-in validators. - * @see PHPMailer::validateAddress() - * @var string|callable - * @static - */ - public static $validator = 'auto'; - - /** - * An instance of the SMTP sender class. - * @var SMTP - * @access protected - */ - protected $smtp = null; - - /** - * The array of 'to' names and addresses. - * @var array - * @access protected - */ - protected $to = array(); - - /** - * The array of 'cc' names and addresses. - * @var array - * @access protected - */ - protected $cc = array(); - - /** - * The array of 'bcc' names and addresses. - * @var array - * @access protected - */ - protected $bcc = array(); - - /** - * The array of reply-to names and addresses. - * @var array - * @access protected - */ - protected $ReplyTo = array(); - - /** - * An array of all kinds of addresses. - * Includes all of $to, $cc, $bcc - * @var array - * @access protected - * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc - */ - protected $all_recipients = array(); - - /** - * An array of names and addresses queued for validation. - * In send(), valid and non duplicate entries are moved to $all_recipients - * and one of $to, $cc, or $bcc. - * This array is used only for addresses with IDN. - * @var array - * @access protected - * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc - * @see PHPMailer::$all_recipients - */ - protected $RecipientsQueue = array(); - - /** - * An array of reply-to names and addresses queued for validation. - * In send(), valid and non duplicate entries are moved to $ReplyTo. - * This array is used only for addresses with IDN. - * @var array - * @access protected - * @see PHPMailer::$ReplyTo - */ - protected $ReplyToQueue = array(); - - /** - * The array of attachments. - * @var array - * @access protected - */ - protected $attachment = array(); - - /** - * The array of custom headers. - * @var array - * @access protected - */ - protected $CustomHeader = array(); - - /** - * The most recent Message-ID (including angular brackets). - * @var string - * @access protected - */ - protected $lastMessageID = ''; - - /** - * The message's MIME type. - * @var string - * @access protected - */ - protected $message_type = ''; - - /** - * The array of MIME boundary strings. - * @var array - * @access protected - */ - protected $boundary = array(); - - /** - * The array of available languages. - * @var array - * @access protected - */ - protected $language = array(); - - /** - * The number of errors encountered. - * @var integer - * @access protected - */ - protected $error_count = 0; - - /** - * The S/MIME certificate file path. - * @var string - * @access protected - */ - protected $sign_cert_file = ''; - - /** - * The S/MIME key file path. - * @var string - * @access protected - */ - protected $sign_key_file = ''; - - /** - * The optional S/MIME extra certificates ("CA Chain") file path. - * @var string - * @access protected - */ - protected $sign_extracerts_file = ''; - - /** - * The S/MIME password for the key. - * Used only if the key is encrypted. - * @var string - * @access protected - */ - protected $sign_key_pass = ''; - - /** - * Whether to throw exceptions for errors. - * @var boolean - * @access protected - */ - protected $exceptions = false; - - /** - * Unique ID used for message ID and boundaries. - * @var string - * @access protected - */ - protected $uniqueid = ''; - - /** - * Error severity: message only, continue processing. - */ - const STOP_MESSAGE = 0; - - /** - * Error severity: message, likely ok to continue processing. - */ - const STOP_CONTINUE = 1; - - /** - * Error severity: message, plus full stop, critical error reached. - */ - const STOP_CRITICAL = 2; - - /** - * SMTP RFC standard line ending. - */ - const CRLF = "\r\n"; - - /** - * The maximum line length allowed by RFC 2822 section 2.1.1 - * @var integer - */ - const MAX_LINE_LENGTH = 998; - - /** - * Constructor. - * @param boolean $exceptions Should we throw external exceptions? - */ - public function __construct($exceptions = null) - { - if ($exceptions !== null) { - $this->exceptions = (boolean)$exceptions; - } - //Pick an appropriate debug output format automatically - $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html'); - } - - /** - * Destructor. - */ - public function __destruct() - { - //Close any open SMTP connection nicely - $this->smtpClose(); - } - - /** - * Call mail() in a safe_mode-aware fashion. - * Also, unless sendmail_path points to sendmail (or something that - * claims to be sendmail), don't pass params (not a perfect fix, - * but it will do) - * @param string $to To - * @param string $subject Subject - * @param string $body Message Body - * @param string $header Additional Header(s) - * @param string $params Params - * @access private - * @return boolean - */ - private function mailPassthru($to, $subject, $body, $header, $params) - { - //Check overloading of mail function to avoid double-encoding - if (ini_get('mbstring.func_overload') & 1) { - $subject = $this->secureHeader($subject); - } else { - $subject = $this->encodeHeader($this->secureHeader($subject)); - } - - //Can't use additional_parameters in safe_mode, calling mail() with null params breaks - //@link http://php.net/manual/en/function.mail.php - if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) { - $result = @mail($to, $subject, $body, $header); - } else { - $result = @mail($to, $subject, $body, $header, $params); - } - return $result; - } - /** - * Output debugging info via user-defined method. - * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug). - * @see PHPMailer::$Debugoutput - * @see PHPMailer::$SMTPDebug - * @param string $str - */ - protected function edebug($str) - { - if ($this->SMTPDebug <= 0) { - return; - } - //Avoid clash with built-in function names - if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { - call_user_func($this->Debugoutput, $str, $this->SMTPDebug); - return; - } - switch ($this->Debugoutput) { - case 'error_log': - //Don't output, just log - error_log($str); - break; - case 'html': - //Cleans up output a bit for a better looking, HTML-safe output - echo htmlentities( - preg_replace('/[\r\n]+/', '', $str), - ENT_QUOTES, - 'UTF-8' - ) - . "
    \n"; - break; - case 'echo': - default: - //Normalize line breaks - $str = preg_replace('/\r\n?/ms', "\n", $str); - echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( - "\n", - "\n \t ", - trim($str) - ) . "\n"; - } - } - - /** - * Sets message type to HTML or plain. - * @param boolean $isHtml True for HTML mode. - * @return void - */ - public function isHTML($isHtml = true) - { - if ($isHtml) { - $this->ContentType = 'text/html'; - } else { - $this->ContentType = 'text/plain'; - } - } - - /** - * Send messages using SMTP. - * @return void - */ - public function isSMTP() - { - $this->Mailer = 'smtp'; - } - - /** - * Send messages using PHP's mail() function. - * @return void - */ - public function isMail() - { - $this->Mailer = 'mail'; - } - - /** - * Send messages using $Sendmail. - * @return void - */ - public function isSendmail() - { - $ini_sendmail_path = ini_get('sendmail_path'); - - if (!stristr($ini_sendmail_path, 'sendmail')) { - $this->Sendmail = '/usr/sbin/sendmail'; - } else { - $this->Sendmail = $ini_sendmail_path; - } - $this->Mailer = 'sendmail'; - } - - /** - * Send messages using qmail. - * @return void - */ - public function isQmail() - { - $ini_sendmail_path = ini_get('sendmail_path'); - - if (!stristr($ini_sendmail_path, 'qmail')) { - $this->Sendmail = '/var/qmail/bin/qmail-inject'; - } else { - $this->Sendmail = $ini_sendmail_path; - } - $this->Mailer = 'qmail'; - } - - /** - * Add a "To" address. - * @param string $address The email address to send to - * @param string $name - * @return boolean true on success, false if address already used or invalid in some way - */ - public function addAddress($address, $name = '') - { - return $this->addOrEnqueueAnAddress('to', $address, $name); - } - - /** - * Add a "CC" address. - * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. - * @param string $address The email address to send to - * @param string $name - * @return boolean true on success, false if address already used or invalid in some way - */ - public function addCC($address, $name = '') - { - return $this->addOrEnqueueAnAddress('cc', $address, $name); - } - - /** - * Add a "BCC" address. - * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. - * @param string $address The email address to send to - * @param string $name - * @return boolean true on success, false if address already used or invalid in some way - */ - public function addBCC($address, $name = '') - { - return $this->addOrEnqueueAnAddress('bcc', $address, $name); - } - - /** - * Add a "Reply-To" address. - * @param string $address The email address to reply to - * @param string $name - * @return boolean true on success, false if address already used or invalid in some way - */ - public function addReplyTo($address, $name = '') - { - return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); - } - - /** - * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer - * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still - * be modified after calling this function), addition of such addresses is delayed until send(). - * Addresses that have been added already return false, but do not throw exceptions. - * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' - * @param string $address The email address to send, resp. to reply to - * @param string $name - * @throws phpmailerException - * @return boolean true on success, false if address already used or invalid in some way - * @access protected - */ - protected function addOrEnqueueAnAddress($kind, $address, $name) - { - $address = trim($address); - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim - if (($pos = strrpos($address, '@')) === false) { - // At-sign is misssing. - $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address"; - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new phpmailerException($error_message); - } - return false; - } - $params = array($kind, $address, $name); - // Enqueue addresses with IDN until we know the PHPMailer::$CharSet. - if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) { - if ($kind != 'Reply-To') { - if (!array_key_exists($address, $this->RecipientsQueue)) { - $this->RecipientsQueue[$address] = $params; - return true; - } - } else { - if (!array_key_exists($address, $this->ReplyToQueue)) { - $this->ReplyToQueue[$address] = $params; - return true; - } - } - return false; - } - // Immediately add standard addresses without IDN. - return call_user_func_array(array($this, 'addAnAddress'), $params); - } - - /** - * Add an address to one of the recipient arrays or to the ReplyTo array. - * Addresses that have been added already return false, but do not throw exceptions. - * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' - * @param string $address The email address to send, resp. to reply to - * @param string $name - * @throws phpmailerException - * @return boolean true on success, false if address already used or invalid in some way - * @access protected - */ - protected function addAnAddress($kind, $address, $name = '') - { - if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) { - $error_message = $this->lang('Invalid recipient kind: ') . $kind; - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new phpmailerException($error_message); - } - return false; - } - if (!$this->validateAddress($address)) { - $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address"; - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new phpmailerException($error_message); - } - return false; - } - if ($kind != 'Reply-To') { - if (!array_key_exists(strtolower($address), $this->all_recipients)) { - array_push($this->$kind, array($address, $name)); - $this->all_recipients[strtolower($address)] = true; - return true; - } - } else { - if (!array_key_exists(strtolower($address), $this->ReplyTo)) { - $this->ReplyTo[strtolower($address)] = array($address, $name); - return true; - } - } - return false; - } - - /** - * Parse and validate a string containing one or more RFC822-style comma-separated email addresses - * of the form "display name
    " into an array of name/address pairs. - * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. - * Note that quotes in the name part are removed. - * @param string $addrstr The address list string - * @param bool $useimap Whether to use the IMAP extension to parse the list - * @return array - * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation - */ - public function parseAddresses($addrstr, $useimap = true) - { - $addresses = array(); - if ($useimap and function_exists('imap_rfc822_parse_adrlist')) { - //Use this built-in parser if it's available - $list = imap_rfc822_parse_adrlist($addrstr, ''); - foreach ($list as $address) { - if ($address->host != '.SYNTAX-ERROR.') { - if ($this->validateAddress($address->mailbox . '@' . $address->host)) { - $addresses[] = array( - 'name' => (property_exists($address, 'personal') ? $address->personal : ''), - 'address' => $address->mailbox . '@' . $address->host - ); - } - } - } - } else { - //Use this simpler parser - $list = explode(',', $addrstr); - foreach ($list as $address) { - $address = trim($address); - //Is there a separate name part? - if (strpos($address, '<') === false) { - //No separate name, just use the whole thing - if ($this->validateAddress($address)) { - $addresses[] = array( - 'name' => '', - 'address' => $address - ); - } - } else { - list($name, $email) = explode('<', $address); - $email = trim(str_replace('>', '', $email)); - if ($this->validateAddress($email)) { - $addresses[] = array( - 'name' => trim(str_replace(array('"', "'"), '', $name)), - 'address' => $email - ); - } - } - } - } - return $addresses; - } - - /** - * Set the From and FromName properties. - * @param string $address - * @param string $name - * @param boolean $auto Whether to also set the Sender address, defaults to true - * @throws phpmailerException - * @return boolean - */ - public function setFrom($address, $name = '', $auto = true) - { - $address = trim($address); - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim - // Don't validate now addresses with IDN. Will be done in send(). - if (($pos = strrpos($address, '@')) === false or - (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and - !$this->validateAddress($address)) { - $error_message = $this->lang('invalid_address') . " (setFrom) $address"; - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new phpmailerException($error_message); - } - return false; - } - $this->From = $address; - $this->FromName = $name; - if ($auto) { - if (empty($this->Sender)) { - $this->Sender = $address; - } - } - return true; - } - - /** - * Return the Message-ID header of the last email. - * Technically this is the value from the last time the headers were created, - * but it's also the message ID of the last sent message except in - * pathological cases. - * @return string - */ - public function getLastMessageID() - { - return $this->lastMessageID; - } - - /** - * Check that a string looks like an email address. - * @param string $address The email address to check - * @param string|callable $patternselect A selector for the validation pattern to use : - * * `auto` Pick best pattern automatically; - * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14; - * * `pcre` Use old PCRE implementation; - * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; - * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. - * * `noregex` Don't use a regex: super fast, really dumb. - * Alternatively you may pass in a callable to inject your own validator, for example: - * PHPMailer::validateAddress('user@example.com', function($address) { - * return (strpos($address, '@') !== false); - * }); - * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. - * @return boolean - * @static - * @access public - */ - public static function validateAddress($address, $patternselect = null) - { - if (is_null($patternselect)) { - $patternselect = self::$validator; - } - if (is_callable($patternselect)) { - return call_user_func($patternselect, $address); - } - //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 - if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) { - return false; - } - if (!$patternselect or $patternselect == 'auto') { - //Check this constant first so it works when extension_loaded() is disabled by safe mode - //Constant was added in PHP 5.2.4 - if (defined('PCRE_VERSION')) { - //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2 - if (version_compare(PCRE_VERSION, '8.0.3') >= 0) { - $patternselect = 'pcre8'; - } else { - $patternselect = 'pcre'; - } - } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) { - //Fall back to older PCRE - $patternselect = 'pcre'; - } else { - //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension - if (version_compare(PHP_VERSION, '5.2.0') >= 0) { - $patternselect = 'php'; - } else { - $patternselect = 'noregex'; - } - } - } - switch ($patternselect) { - case 'pcre8': - /** - * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains. - * @link http://squiloople.com/2009/12/20/email-address-validation/ - * @copyright 2009-2010 Michael Rushton - * Feel free to use and redistribute this code. But please keep this copyright notice. - */ - return (boolean)preg_match( - '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . - '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . - '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . - '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . - '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . - '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . - '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . - '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . - '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', - $address - ); - case 'pcre': - //An older regex that doesn't need a recent PCRE - return (boolean)preg_match( - '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' . - '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' . - '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' . - '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' . - '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' . - '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' . - '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' . - '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' . - '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' . - '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD', - $address - ); - case 'html5': - /** - * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. - * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email) - */ - return (boolean)preg_match( - '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . - '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', - $address - ); - case 'noregex': - //No PCRE! Do something _very_ approximate! - //Check the address is 3 chars or longer and contains an @ that's not the first or last char - return (strlen($address) >= 3 - and strpos($address, '@') >= 1 - and strpos($address, '@') != strlen($address) - 1); - case 'php': - default: - return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL); - } - } - - /** - * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the - * "intl" and "mbstring" PHP extensions. - * @return bool "true" if required functions for IDN support are present - */ - public function idnSupported() - { - // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2. - return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding'); - } - - /** - * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. - * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. - * This function silently returns unmodified address if: - * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) - * - Conversion to punycode is impossible (e.g. required PHP functions are not available) - * or fails for any reason (e.g. domain has characters not allowed in an IDN) - * @see PHPMailer::$CharSet - * @param string $address The email address to convert - * @return string The encoded address in ASCII form - */ - public function punyencodeAddress($address) - { - // Verify we have required functions, CharSet, and at-sign. - if ($this->idnSupported() and - !empty($this->CharSet) and - ($pos = strrpos($address, '@')) !== false) { - $domain = substr($address, ++$pos); - // Verify CharSet string is a valid one, and domain properly encoded in this CharSet. - if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) { - $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet); - if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ? - idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) : - idn_to_ascii($domain)) !== false) { - return substr($address, 0, $pos) . $punycode; - } - } - } - return $address; - } - - /** - * Create a message and send it. - * Uses the sending method specified by $Mailer. - * @throws phpmailerException - * @return boolean false on error - See the ErrorInfo property for details of the error. - */ - public function send() - { - try { - if (!$this->preSend()) { - return false; - } - return $this->postSend(); - } catch (phpmailerException $exc) { - $this->mailHeader = ''; - $this->setError($exc->getMessage()); - if ($this->exceptions) { - throw $exc; - } - return false; - } - } - - /** - * Prepare a message for sending. - * @throws phpmailerException - * @return boolean - */ - public function preSend() - { - try { - $this->error_count = 0; // Reset errors - $this->mailHeader = ''; - - // Dequeue recipient and Reply-To addresses with IDN - foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { - $params[1] = $this->punyencodeAddress($params[1]); - call_user_func_array(array($this, 'addAnAddress'), $params); - } - if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { - throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL); - } - - // Validate From, Sender, and ConfirmReadingTo addresses - foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) { - $this->$address_kind = trim($this->$address_kind); - if (empty($this->$address_kind)) { - continue; - } - $this->$address_kind = $this->punyencodeAddress($this->$address_kind); - if (!$this->validateAddress($this->$address_kind)) { - $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind; - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new phpmailerException($error_message); - } - return false; - } - } - - // Set whether the message is multipart/alternative - if ($this->alternativeExists()) { - $this->ContentType = 'multipart/alternative'; - } - - $this->setMessageType(); - // Refuse to send an empty message unless we are specifically allowing it - if (!$this->AllowEmpty and empty($this->Body)) { - throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL); - } - - // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) - $this->MIMEHeader = ''; - $this->MIMEBody = $this->createBody(); - // createBody may have added some headers, so retain them - $tempheaders = $this->MIMEHeader; - $this->MIMEHeader = $this->createHeader(); - $this->MIMEHeader .= $tempheaders; - - // To capture the complete message when using mail(), create - // an extra header list which createHeader() doesn't fold in - if ($this->Mailer == 'mail') { - if (count($this->to) > 0) { - $this->mailHeader .= $this->addrAppend('To', $this->to); - } else { - $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); - } - $this->mailHeader .= $this->headerLine( - 'Subject', - $this->encodeHeader($this->secureHeader(trim($this->Subject))) - ); - } - - // Sign with DKIM if enabled - if (!empty($this->DKIM_domain) - && !empty($this->DKIM_selector) - && (!empty($this->DKIM_private_string) - || (!empty($this->DKIM_private) && file_exists($this->DKIM_private)) - ) - ) { - $header_dkim = $this->DKIM_Add( - $this->MIMEHeader . $this->mailHeader, - $this->encodeHeader($this->secureHeader($this->Subject)), - $this->MIMEBody - ); - $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF . - str_replace("\r\n", "\n", $header_dkim) . self::CRLF; - } - return true; - } catch (phpmailerException $exc) { - $this->setError($exc->getMessage()); - if ($this->exceptions) { - throw $exc; - } - return false; - } - } - - /** - * Actually send a message. - * Send the email via the selected mechanism - * @throws phpmailerException - * @return boolean - */ - public function postSend() - { - try { - // Choose the mailer and send through it - switch ($this->Mailer) { - case 'sendmail': - case 'qmail': - return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); - case 'smtp': - return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); - case 'mail': - return $this->mailSend($this->MIMEHeader, $this->MIMEBody); - default: - $sendMethod = $this->Mailer.'Send'; - if (method_exists($this, $sendMethod)) { - return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); - } - - return $this->mailSend($this->MIMEHeader, $this->MIMEBody); - } - } catch (phpmailerException $exc) { - $this->setError($exc->getMessage()); - $this->edebug($exc->getMessage()); - if ($this->exceptions) { - throw $exc; - } - } - return false; - } - - /** - * Send mail using the $Sendmail program. - * @param string $header The message headers - * @param string $body The message body - * @see PHPMailer::$Sendmail - * @throws phpmailerException - * @access protected - * @return boolean - */ - protected function sendmailSend($header, $body) - { - // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. - if (!empty($this->Sender) and self::isShellSafe($this->Sender)) { - if ($this->Mailer == 'qmail') { - $sendmailFmt = '%s -f%s'; - } else { - $sendmailFmt = '%s -oi -f%s -t'; - } - } else { - if ($this->Mailer == 'qmail') { - $sendmailFmt = '%s'; - } else { - $sendmailFmt = '%s -oi -t'; - } - } - - // TODO: If possible, this should be changed to escapeshellarg. Needs thorough testing. - $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); - - if ($this->SingleTo) { - foreach ($this->SingleToArray as $toAddr) { - if (!@$mail = popen($sendmail, 'w')) { - throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - fputs($mail, 'To: ' . $toAddr . "\n"); - fputs($mail, $header); - fputs($mail, $body); - $result = pclose($mail); - $this->doCallback( - ($result == 0), - array($toAddr), - $this->cc, - $this->bcc, - $this->Subject, - $body, - $this->From - ); - if ($result != 0) { - throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - } - } else { - if (!@$mail = popen($sendmail, 'w')) { - throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - fputs($mail, $header); - fputs($mail, $body); - $result = pclose($mail); - $this->doCallback( - ($result == 0), - $this->to, - $this->cc, - $this->bcc, - $this->Subject, - $body, - $this->From - ); - if ($result != 0) { - throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - } - return true; - } - - /** - * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. - * - * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. - * @param string $string The string to be validated - * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report - * @access protected - * @return boolean - */ - protected static function isShellSafe($string) - { - // Future-proof - if (escapeshellcmd($string) !== $string - or !in_array(escapeshellarg($string), array("'$string'", "\"$string\"")) - ) { - return false; - } - - $length = strlen($string); - - for ($i = 0; $i < $length; $i++) { - $c = $string[$i]; - - // All other characters have a special meaning in at least one common shell, including = and +. - // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. - // Note that this does permit non-Latin alphanumeric characters based on the current locale. - if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { - return false; - } - } - - return true; - } - - /** - * Send mail using the PHP mail() function. - * @param string $header The message headers - * @param string $body The message body - * @link http://www.php.net/manual/en/book.mail.php - * @throws phpmailerException - * @access protected - * @return boolean - */ - protected function mailSend($header, $body) - { - $toArr = array(); - foreach ($this->to as $toaddr) { - $toArr[] = $this->addrFormat($toaddr); - } - $to = implode(', ', $toArr); - - $params = null; - //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver - if (!empty($this->Sender) and $this->validateAddress($this->Sender)) { - // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. - if (self::isShellSafe($this->Sender)) { - $params = sprintf('-f%s', $this->Sender); - } - } - if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) { - $old_from = ini_get('sendmail_from'); - ini_set('sendmail_from', $this->Sender); - } - $result = false; - if ($this->SingleTo and count($toArr) > 1) { - foreach ($toArr as $toAddr) { - $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); - $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From); - } - } else { - $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); - $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); - } - if (isset($old_from)) { - ini_set('sendmail_from', $old_from); - } - if (!$result) { - throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL); - } - return true; - } - - /** - * Get an instance to use for SMTP operations. - * Override this function to load your own SMTP implementation - * @return SMTP - */ - public function getSMTPInstance() - { - if (!is_object($this->smtp)) { - $this->smtp = new SMTP; - } - return $this->smtp; - } - - /** - * Send mail via SMTP. - * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. - * Uses the PHPMailerSMTP class by default. - * @see PHPMailer::getSMTPInstance() to use a different class. - * @param string $header The message headers - * @param string $body The message body - * @throws phpmailerException - * @uses SMTP - * @access protected - * @return boolean - */ - protected function smtpSend($header, $body) - { - $bad_rcpt = array(); - if (!$this->smtpConnect($this->SMTPOptions)) { - throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); - } - if (!empty($this->Sender) and $this->validateAddress($this->Sender)) { - $smtp_from = $this->Sender; - } else { - $smtp_from = $this->From; - } - if (!$this->smtp->mail($smtp_from)) { - $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); - throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL); - } - - // Attempt to send to all recipients - foreach (array($this->to, $this->cc, $this->bcc) as $togroup) { - foreach ($togroup as $to) { - if (!$this->smtp->recipient($to[0])) { - $error = $this->smtp->getError(); - $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']); - $isSent = false; - } else { - $isSent = true; - } - $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From); - } - } - - // Only send the DATA command if we have viable recipients - if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) { - throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL); - } - if ($this->SMTPKeepAlive) { - $this->smtp->reset(); - } else { - $this->smtp->quit(); - $this->smtp->close(); - } - //Create error message for any bad addresses - if (count($bad_rcpt) > 0) { - $errstr = ''; - foreach ($bad_rcpt as $bad) { - $errstr .= $bad['to'] . ': ' . $bad['error']; - } - throw new phpmailerException( - $this->lang('recipients_failed') . $errstr, - self::STOP_CONTINUE - ); - } - return true; - } - - /** - * Initiate a connection to an SMTP server. - * Returns false if the operation failed. - * @param array $options An array of options compatible with stream_context_create() - * @uses SMTP - * @access public - * @throws phpmailerException - * @return boolean - */ - public function smtpConnect($options = null) - { - if (is_null($this->smtp)) { - $this->smtp = $this->getSMTPInstance(); - } - - //If no options are provided, use whatever is set in the instance - if (is_null($options)) { - $options = $this->SMTPOptions; - } - - // Already connected? - if ($this->smtp->connected()) { - return true; - } - - $this->smtp->setTimeout($this->Timeout); - $this->smtp->setDebugLevel($this->SMTPDebug); - $this->smtp->setDebugOutput($this->Debugoutput); - $this->smtp->setVerp($this->do_verp); - $hosts = explode(';', $this->Host); - $lastexception = null; - - foreach ($hosts as $hostentry) { - $hostinfo = array(); - if (!preg_match( - '/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*|\[[a-fA-F0-9:]+\]):?([0-9]*)$/', - trim($hostentry), - $hostinfo - )) { - // Not a valid host entry - $this->edebug('Ignoring invalid host: ' . $hostentry); - continue; - } - // $hostinfo[2]: optional ssl or tls prefix - // $hostinfo[3]: the hostname - // $hostinfo[4]: optional port number - // The host string prefix can temporarily override the current setting for SMTPSecure - // If it's not specified, the default value is used - $prefix = ''; - $secure = $this->SMTPSecure; - $tls = ($this->SMTPSecure == 'tls'); - if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) { - $prefix = 'ssl://'; - $tls = false; // Can't have SSL and TLS at the same time - $secure = 'ssl'; - } elseif ($hostinfo[2] == 'tls') { - $tls = true; - // tls doesn't use a prefix - $secure = 'tls'; - } - //Do we need the OpenSSL extension? - $sslext = defined('OPENSSL_ALGO_SHA1'); - if ('tls' === $secure or 'ssl' === $secure) { - //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled - if (!$sslext) { - throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL); - } - } - $host = $hostinfo[3]; - $port = $this->Port; - $tport = (integer)$hostinfo[4]; - if ($tport > 0 and $tport < 65536) { - $port = $tport; - } - if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { - try { - if ($this->Helo) { - $hello = $this->Helo; - } else { - $hello = $this->serverHostname(); - } - $this->smtp->hello($hello); - //Automatically enable TLS encryption if: - // * it's not disabled - // * we have openssl extension - // * we are not already using SSL - // * the server offers STARTTLS - if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) { - $tls = true; - } - if ($tls) { - if (!$this->smtp->startTLS()) { - throw new phpmailerException($this->lang('connect_host')); - } - // We must resend EHLO after TLS negotiation - $this->smtp->hello($hello); - } - if ($this->SMTPAuth) { - if (!$this->smtp->authenticate( - $this->Username, - $this->Password, - $this->AuthType, - $this->Realm, - $this->Workstation - ) - ) { - throw new phpmailerException($this->lang('authenticate')); - } - } - return true; - } catch (phpmailerException $exc) { - $lastexception = $exc; - $this->edebug($exc->getMessage()); - // We must have connected, but then failed TLS or Auth, so close connection nicely - $this->smtp->quit(); - } - } - } - // If we get here, all connection attempts have failed, so close connection hard - $this->smtp->close(); - // As we've caught all exceptions, just report whatever the last one was - if ($this->exceptions and !is_null($lastexception)) { - throw $lastexception; - } - return false; - } - - /** - * Close the active SMTP session if one exists. - * @return void - */ - public function smtpClose() - { - if (is_a($this->smtp, 'SMTP')) { - if ($this->smtp->connected()) { - $this->smtp->quit(); - $this->smtp->close(); - } - } - } - - /** - * Set the language for error messages. - * Returns false if it cannot load the language file. - * The default language is English. - * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") - * @param string $lang_path Path to the language file directory, with trailing separator (slash) - * @return boolean - * @access public - */ - public function setLanguage($langcode = 'en', $lang_path = '') - { - // Backwards compatibility for renamed language codes - $renamed_langcodes = array( - 'br' => 'pt_br', - 'cz' => 'cs', - 'dk' => 'da', - 'no' => 'nb', - 'se' => 'sv', - 'sr' => 'rs' - ); - - if (isset($renamed_langcodes[$langcode])) { - $langcode = $renamed_langcodes[$langcode]; - } - - // Define full set of translatable strings in English - $PHPMAILER_LANG = array( - 'authenticate' => 'SMTP Error: Could not authenticate.', - 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', - 'data_not_accepted' => 'SMTP Error: data not accepted.', - 'empty_message' => 'Message body empty', - 'encoding' => 'Unknown encoding: ', - 'execute' => 'Could not execute: ', - 'file_access' => 'Could not access file: ', - 'file_open' => 'File Error: Could not open file: ', - 'from_failed' => 'The following From address failed: ', - 'instantiate' => 'Could not instantiate mail function.', - 'invalid_address' => 'Invalid address: ', - 'mailer_not_supported' => ' mailer is not supported.', - 'provide_address' => 'You must provide at least one recipient email address.', - 'recipients_failed' => 'SMTP Error: The following recipients failed: ', - 'signing' => 'Signing Error: ', - 'smtp_connect_failed' => 'SMTP connect() failed.', - 'smtp_error' => 'SMTP server error: ', - 'variable_set' => 'Cannot set or reset variable: ', - 'extension_missing' => 'Extension missing: ' - ); - if (empty($lang_path)) { - // Calculate an absolute path so it can work if CWD is not here - $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR; - } - //Validate $langcode - if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) { - $langcode = 'en'; - } - $foundlang = true; - $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; - // There is no English translation file - if ($langcode != 'en') { - // Make sure language file path is readable - if (!is_readable($lang_file)) { - $foundlang = false; - } else { - // Overwrite language-specific strings. - // This way we'll never have missing translation keys. - $foundlang = include $lang_file; - } - } - $this->language = $PHPMAILER_LANG; - return (boolean)$foundlang; // Returns false if language not found - } - - /** - * Get the array of strings for the current language. - * @return array - */ - public function getTranslations() - { - return $this->language; - } - - /** - * Create recipient headers. - * @access public - * @param string $type - * @param array $addr An array of recipient, - * where each recipient is a 2-element indexed array with element 0 containing an address - * and element 1 containing a name, like: - * array(array('joe@example.com', 'Joe User'), array('zoe@example.com', 'Zoe User')) - * @return string - */ - public function addrAppend($type, $addr) - { - $addresses = array(); - foreach ($addr as $address) { - $addresses[] = $this->addrFormat($address); - } - return $type . ': ' . implode(', ', $addresses) . $this->LE; - } - - /** - * Format an address for use in a message header. - * @access public - * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name - * like array('joe@example.com', 'Joe User') - * @return string - */ - public function addrFormat($addr) - { - if (empty($addr[1])) { // No name provided - return $this->secureHeader($addr[0]); - } else { - return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader( - $addr[0] - ) . '>'; - } - } - - /** - * Word-wrap message. - * For use with mailers that do not automatically perform wrapping - * and for quoted-printable encoded messages. - * Original written by philippe. - * @param string $message The message to wrap - * @param integer $length The line length to wrap to - * @param boolean $qp_mode Whether to run in Quoted-Printable mode - * @access public - * @return string - */ - public function wrapText($message, $length, $qp_mode = false) - { - if ($qp_mode) { - $soft_break = sprintf(' =%s', $this->LE); - } else { - $soft_break = $this->LE; - } - // If utf-8 encoding is used, we will need to make sure we don't - // split multibyte characters when we wrap - $is_utf8 = (strtolower($this->CharSet) == 'utf-8'); - $lelen = strlen($this->LE); - $crlflen = strlen(self::CRLF); - - $message = $this->fixEOL($message); - //Remove a trailing line break - if (substr($message, -$lelen) == $this->LE) { - $message = substr($message, 0, -$lelen); - } - - //Split message into lines - $lines = explode($this->LE, $message); - //Message will be rebuilt in here - $message = ''; - foreach ($lines as $line) { - $words = explode(' ', $line); - $buf = ''; - $firstword = true; - foreach ($words as $word) { - if ($qp_mode and (strlen($word) > $length)) { - $space_left = $length - strlen($buf) - $crlflen; - if (!$firstword) { - if ($space_left > 20) { - $len = $space_left; - if ($is_utf8) { - $len = $this->utf8CharBoundary($word, $len); - } elseif (substr($word, $len - 1, 1) == '=') { - $len--; - } elseif (substr($word, $len - 2, 1) == '=') { - $len -= 2; - } - $part = substr($word, 0, $len); - $word = substr($word, $len); - $buf .= ' ' . $part; - $message .= $buf . sprintf('=%s', self::CRLF); - } else { - $message .= $buf . $soft_break; - } - $buf = ''; - } - while (strlen($word) > 0) { - if ($length <= 0) { - break; - } - $len = $length; - if ($is_utf8) { - $len = $this->utf8CharBoundary($word, $len); - } elseif (substr($word, $len - 1, 1) == '=') { - $len--; - } elseif (substr($word, $len - 2, 1) == '=') { - $len -= 2; - } - $part = substr($word, 0, $len); - $word = substr($word, $len); - - if (strlen($word) > 0) { - $message .= $part . sprintf('=%s', self::CRLF); - } else { - $buf = $part; - } - } - } else { - $buf_o = $buf; - if (!$firstword) { - $buf .= ' '; - } - $buf .= $word; - - if (strlen($buf) > $length and $buf_o != '') { - $message .= $buf_o . $soft_break; - $buf = $word; - } - } - $firstword = false; - } - $message .= $buf . self::CRLF; - } - - return $message; - } - - /** - * Find the last character boundary prior to $maxLength in a utf-8 - * quoted-printable encoded string. - * Original written by Colin Brown. - * @access public - * @param string $encodedText utf-8 QP text - * @param integer $maxLength Find the last character boundary prior to this length - * @return integer - */ - public function utf8CharBoundary($encodedText, $maxLength) - { - $foundSplitPos = false; - $lookBack = 3; - while (!$foundSplitPos) { - $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); - $encodedCharPos = strpos($lastChunk, '='); - if (false !== $encodedCharPos) { - // Found start of encoded character byte within $lookBack block. - // Check the encoded byte value (the 2 chars after the '=') - $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); - $dec = hexdec($hex); - if ($dec < 128) { - // Single byte character. - // If the encoded char was found at pos 0, it will fit - // otherwise reduce maxLength to start of the encoded char - if ($encodedCharPos > 0) { - $maxLength = $maxLength - ($lookBack - $encodedCharPos); - } - $foundSplitPos = true; - } elseif ($dec >= 192) { - // First byte of a multi byte character - // Reduce maxLength to split at start of character - $maxLength = $maxLength - ($lookBack - $encodedCharPos); - $foundSplitPos = true; - } elseif ($dec < 192) { - // Middle byte of a multi byte character, look further back - $lookBack += 3; - } - } else { - // No encoded character found - $foundSplitPos = true; - } - } - return $maxLength; - } - - /** - * Apply word wrapping to the message body. - * Wraps the message body to the number of chars set in the WordWrap property. - * You should only do this to plain-text bodies as wrapping HTML tags may break them. - * This is called automatically by createBody(), so you don't need to call it yourself. - * @access public - * @return void - */ - public function setWordWrap() - { - if ($this->WordWrap < 1) { - return; - } - - switch ($this->message_type) { - case 'alt': - case 'alt_inline': - case 'alt_attach': - case 'alt_inline_attach': - $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap); - break; - default: - $this->Body = $this->wrapText($this->Body, $this->WordWrap); - break; - } - } - - /** - * Assemble message headers. - * @access public - * @return string The assembled headers - */ - public function createHeader() - { - $result = ''; - - $result .= $this->headerLine('Date', $this->MessageDate == '' ? self::rfcDate() : $this->MessageDate); - - // To be created automatically by mail() - if ($this->SingleTo) { - if ($this->Mailer != 'mail') { - foreach ($this->to as $toaddr) { - $this->SingleToArray[] = $this->addrFormat($toaddr); - } - } - } else { - if (count($this->to) > 0) { - if ($this->Mailer != 'mail') { - $result .= $this->addrAppend('To', $this->to); - } - } elseif (count($this->cc) == 0) { - $result .= $this->headerLine('To', 'undisclosed-recipients:;'); - } - } - - $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName))); - - // sendmail and mail() extract Cc from the header before sending - if (count($this->cc) > 0) { - $result .= $this->addrAppend('Cc', $this->cc); - } - - // sendmail and mail() extract Bcc from the header before sending - if (( - $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail' - ) - and count($this->bcc) > 0 - ) { - $result .= $this->addrAppend('Bcc', $this->bcc); - } - - if (count($this->ReplyTo) > 0) { - $result .= $this->addrAppend('Reply-To', $this->ReplyTo); - } - - // mail() sets the subject itself - if ($this->Mailer != 'mail') { - $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject))); - } - - // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4 - // https://tools.ietf.org/html/rfc5322#section-3.6.4 - if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) { - $this->lastMessageID = $this->MessageID; - } else { - $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname()); - } - $result .= $this->headerLine('Message-ID', $this->lastMessageID); - if (!is_null($this->Priority)) { - $result .= $this->headerLine('X-Priority', $this->Priority); - } - if ($this->XMailer == '') { - $result .= $this->headerLine( - 'X-Mailer', - 'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)' - ); - } else { - $myXmailer = trim($this->XMailer); - if ($myXmailer) { - $result .= $this->headerLine('X-Mailer', $myXmailer); - } - } - - if ($this->ConfirmReadingTo != '') { - $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>'); - } - - // Add custom headers - foreach ($this->CustomHeader as $header) { - $result .= $this->headerLine( - trim($header[0]), - $this->encodeHeader(trim($header[1])) - ); - } - if (!$this->sign_key_file) { - $result .= $this->headerLine('MIME-Version', '1.0'); - $result .= $this->getMailMIME(); - } - - return $result; - } - - /** - * Get the message MIME type headers. - * @access public - * @return string - */ - public function getMailMIME() - { - $result = ''; - $ismultipart = true; - switch ($this->message_type) { - case 'inline': - $result .= $this->headerLine('Content-Type', 'multipart/related;'); - $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); - break; - case 'attach': - case 'inline_attach': - case 'alt_attach': - case 'alt_inline_attach': - $result .= $this->headerLine('Content-Type', 'multipart/mixed;'); - $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); - break; - case 'alt': - case 'alt_inline': - $result .= $this->headerLine('Content-Type', 'multipart/alternative;'); - $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); - break; - default: - // Catches case 'plain': and case '': - $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet); - $ismultipart = false; - break; - } - // RFC1341 part 5 says 7bit is assumed if not specified - if ($this->Encoding != '7bit') { - // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE - if ($ismultipart) { - if ($this->Encoding == '8bit') { - $result .= $this->headerLine('Content-Transfer-Encoding', '8bit'); - } - // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible - } else { - $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); - } - } - - if ($this->Mailer != 'mail') { - $result .= $this->LE; - } - - return $result; - } - - /** - * Returns the whole MIME message. - * Includes complete headers and body. - * Only valid post preSend(). - * @see PHPMailer::preSend() - * @access public - * @return string - */ - public function getSentMIMEMessage() - { - return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody; - } - - /** - * Create unique ID - * @return string - */ - protected function generateId() { - return md5(uniqid(time())); - } - - /** - * Assemble the message body. - * Returns an empty string on failure. - * @access public - * @throws phpmailerException - * @return string The assembled message body - */ - public function createBody() - { - $body = ''; - //Create unique IDs and preset boundaries - $this->uniqueid = $this->generateId(); - $this->boundary[1] = 'b1_' . $this->uniqueid; - $this->boundary[2] = 'b2_' . $this->uniqueid; - $this->boundary[3] = 'b3_' . $this->uniqueid; - - if ($this->sign_key_file) { - $body .= $this->getMailMIME() . $this->LE; - } - - $this->setWordWrap(); - - $bodyEncoding = $this->Encoding; - $bodyCharSet = $this->CharSet; - //Can we do a 7-bit downgrade? - if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) { - $bodyEncoding = '7bit'; - //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit - $bodyCharSet = 'us-ascii'; - } - //If lines are too long, and we're not already using an encoding that will shorten them, - //change to quoted-printable transfer encoding for the body part only - if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) { - $bodyEncoding = 'quoted-printable'; - } - - $altBodyEncoding = $this->Encoding; - $altBodyCharSet = $this->CharSet; - //Can we do a 7-bit downgrade? - if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) { - $altBodyEncoding = '7bit'; - //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit - $altBodyCharSet = 'us-ascii'; - } - //If lines are too long, and we're not already using an encoding that will shorten them, - //change to quoted-printable transfer encoding for the alt body part only - if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) { - $altBodyEncoding = 'quoted-printable'; - } - //Use this as a preamble in all multipart message types - $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE; - switch ($this->message_type) { - case 'inline': - $body .= $mimepre; - $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('inline', $this->boundary[1]); - break; - case 'attach': - $body .= $mimepre; - $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('attachment', $this->boundary[1]); - break; - case 'inline_attach': - $body .= $mimepre; - $body .= $this->textLine('--' . $this->boundary[1]); - $body .= $this->headerLine('Content-Type', 'multipart/related;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('inline', $this->boundary[2]); - $body .= $this->LE; - $body .= $this->attachAll('attachment', $this->boundary[1]); - break; - case 'alt': - $body .= $mimepre; - $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding); - $body .= $this->encodeString($this->AltBody, $altBodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - if (!empty($this->Ical)) { - $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', ''); - $body .= $this->encodeString($this->Ical, $this->Encoding); - $body .= $this->LE . $this->LE; - } - $body .= $this->endBoundary($this->boundary[1]); - break; - case 'alt_inline': - $body .= $mimepre; - $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding); - $body .= $this->encodeString($this->AltBody, $altBodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->textLine('--' . $this->boundary[1]); - $body .= $this->headerLine('Content-Type', 'multipart/related;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('inline', $this->boundary[2]); - $body .= $this->LE; - $body .= $this->endBoundary($this->boundary[1]); - break; - case 'alt_attach': - $body .= $mimepre; - $body .= $this->textLine('--' . $this->boundary[1]); - $body .= $this->headerLine('Content-Type', 'multipart/alternative;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding); - $body .= $this->encodeString($this->AltBody, $altBodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->endBoundary($this->boundary[2]); - $body .= $this->LE; - $body .= $this->attachAll('attachment', $this->boundary[1]); - break; - case 'alt_inline_attach': - $body .= $mimepre; - $body .= $this->textLine('--' . $this->boundary[1]); - $body .= $this->headerLine('Content-Type', 'multipart/alternative;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding); - $body .= $this->encodeString($this->AltBody, $altBodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->textLine('--' . $this->boundary[2]); - $body .= $this->headerLine('Content-Type', 'multipart/related;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('inline', $this->boundary[3]); - $body .= $this->LE; - $body .= $this->endBoundary($this->boundary[2]); - $body .= $this->LE; - $body .= $this->attachAll('attachment', $this->boundary[1]); - break; - default: - // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types - //Reset the `Encoding` property in case we changed it for line length reasons - $this->Encoding = $bodyEncoding; - $body .= $this->encodeString($this->Body, $this->Encoding); - break; - } - - if ($this->isError()) { - $body = ''; - } elseif ($this->sign_key_file) { - try { - if (!defined('PKCS7_TEXT')) { - throw new phpmailerException($this->lang('extension_missing') . 'openssl'); - } - // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1 - $file = tempnam(sys_get_temp_dir(), 'mail'); - if (false === file_put_contents($file, $body)) { - throw new phpmailerException($this->lang('signing') . ' Could not write temp file'); - } - $signed = tempnam(sys_get_temp_dir(), 'signed'); - //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197 - if (empty($this->sign_extracerts_file)) { - $sign = @openssl_pkcs7_sign( - $file, - $signed, - 'file://' . realpath($this->sign_cert_file), - array('file://' . realpath($this->sign_key_file), $this->sign_key_pass), - null - ); - } else { - $sign = @openssl_pkcs7_sign( - $file, - $signed, - 'file://' . realpath($this->sign_cert_file), - array('file://' . realpath($this->sign_key_file), $this->sign_key_pass), - null, - PKCS7_DETACHED, - $this->sign_extracerts_file - ); - } - if ($sign) { - @unlink($file); - $body = file_get_contents($signed); - @unlink($signed); - //The message returned by openssl contains both headers and body, so need to split them up - $parts = explode("\n\n", $body, 2); - $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE; - $body = $parts[1]; - } else { - @unlink($file); - @unlink($signed); - throw new phpmailerException($this->lang('signing') . openssl_error_string()); - } - } catch (phpmailerException $exc) { - $body = ''; - if ($this->exceptions) { - throw $exc; - } - } - } - return $body; - } - - /** - * Return the start of a message boundary. - * @access protected - * @param string $boundary - * @param string $charSet - * @param string $contentType - * @param string $encoding - * @return string - */ - protected function getBoundary($boundary, $charSet, $contentType, $encoding) - { - $result = ''; - if ($charSet == '') { - $charSet = $this->CharSet; - } - if ($contentType == '') { - $contentType = $this->ContentType; - } - if ($encoding == '') { - $encoding = $this->Encoding; - } - $result .= $this->textLine('--' . $boundary); - $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet); - $result .= $this->LE; - // RFC1341 part 5 says 7bit is assumed if not specified - if ($encoding != '7bit') { - $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); - } - $result .= $this->LE; - - return $result; - } - - /** - * Return the end of a message boundary. - * @access protected - * @param string $boundary - * @return string - */ - protected function endBoundary($boundary) - { - return $this->LE . '--' . $boundary . '--' . $this->LE; - } - - /** - * Set the message type. - * PHPMailer only supports some preset message types, not arbitrary MIME structures. - * @access protected - * @return void - */ - protected function setMessageType() - { - $type = array(); - if ($this->alternativeExists()) { - $type[] = 'alt'; - } - if ($this->inlineImageExists()) { - $type[] = 'inline'; - } - if ($this->attachmentExists()) { - $type[] = 'attach'; - } - $this->message_type = implode('_', $type); - if ($this->message_type == '') { - //The 'plain' message_type refers to the message having a single body element, not that it is plain-text - $this->message_type = 'plain'; - } - } - - /** - * Format a header line. - * @access public - * @param string $name - * @param string $value - * @return string - */ - public function headerLine($name, $value) - { - return $name . ': ' . $value . $this->LE; - } - - /** - * Return a formatted mail line. - * @access public - * @param string $value - * @return string - */ - public function textLine($value) - { - return $value . $this->LE; - } - - /** - * Add an attachment from a path on the filesystem. - * Never use a user-supplied path to a file! - * Returns false if the file could not be found or read. - * @param string $path Path to the attachment. - * @param string $name Overrides the attachment name. - * @param string $encoding File encoding (see $Encoding). - * @param string $type File extension (MIME) type. - * @param string $disposition Disposition to use - * @throws phpmailerException - * @return boolean - */ - public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment') - { - try { - if (!@is_file($path)) { - throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE); - } - - // If a MIME type is not specified, try to work it out from the file name - if ($type == '') { - $type = self::filenameToType($path); - } - - $filename = basename($path); - if ($name == '') { - $name = $filename; - } - - $this->attachment[] = array( - 0 => $path, - 1 => $filename, - 2 => $name, - 3 => $encoding, - 4 => $type, - 5 => false, // isStringAttachment - 6 => $disposition, - 7 => 0 - ); - - } catch (phpmailerException $exc) { - $this->setError($exc->getMessage()); - $this->edebug($exc->getMessage()); - if ($this->exceptions) { - throw $exc; - } - return false; - } - return true; - } - - /** - * Return the array of attachments. - * @return array - */ - public function getAttachments() - { - return $this->attachment; - } - - /** - * Attach all file, string, and binary attachments to the message. - * Returns an empty string on failure. - * @access protected - * @param string $disposition_type - * @param string $boundary - * @return string - */ - protected function attachAll($disposition_type, $boundary) - { - // Return text of body - $mime = array(); - $cidUniq = array(); - $incl = array(); - - // Add all attachments - foreach ($this->attachment as $attachment) { - // Check if it is a valid disposition_filter - if ($attachment[6] == $disposition_type) { - // Check for string attachment - $string = ''; - $path = ''; - $bString = $attachment[5]; - if ($bString) { - $string = $attachment[0]; - } else { - $path = $attachment[0]; - } - - $inclhash = md5(serialize($attachment)); - if (in_array($inclhash, $incl)) { - continue; - } - $incl[] = $inclhash; - $name = $attachment[2]; - $encoding = $attachment[3]; - $type = $attachment[4]; - $disposition = $attachment[6]; - $cid = $attachment[7]; - if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) { - continue; - } - $cidUniq[$cid] = true; - - $mime[] = sprintf('--%s%s', $boundary, $this->LE); - //Only include a filename property if we have one - if (!empty($name)) { - $mime[] = sprintf( - 'Content-Type: %s; name="%s"%s', - $type, - $this->encodeHeader($this->secureHeader($name)), - $this->LE - ); - } else { - $mime[] = sprintf( - 'Content-Type: %s%s', - $type, - $this->LE - ); - } - // RFC1341 part 5 says 7bit is assumed if not specified - if ($encoding != '7bit') { - $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE); - } - - if ($disposition == 'inline') { - $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE); - } - - // If a filename contains any of these chars, it should be quoted, - // but not otherwise: RFC2183 & RFC2045 5.1 - // Fixes a warning in IETF's msglint MIME checker - // Allow for bypassing the Content-Disposition header totally - if (!(empty($disposition))) { - $encoded_name = $this->encodeHeader($this->secureHeader($name)); - if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) { - $mime[] = sprintf( - 'Content-Disposition: %s; filename="%s"%s', - $disposition, - $encoded_name, - $this->LE . $this->LE - ); - } else { - if (!empty($encoded_name)) { - $mime[] = sprintf( - 'Content-Disposition: %s; filename=%s%s', - $disposition, - $encoded_name, - $this->LE . $this->LE - ); - } else { - $mime[] = sprintf( - 'Content-Disposition: %s%s', - $disposition, - $this->LE . $this->LE - ); - } - } - } else { - $mime[] = $this->LE; - } - - // Encode as string attachment - if ($bString) { - $mime[] = $this->encodeString($string, $encoding); - if ($this->isError()) { - return ''; - } - $mime[] = $this->LE . $this->LE; - } else { - $mime[] = $this->encodeFile($path, $encoding); - if ($this->isError()) { - return ''; - } - $mime[] = $this->LE . $this->LE; - } - } - } - - $mime[] = sprintf('--%s--%s', $boundary, $this->LE); - - return implode('', $mime); - } - - /** - * Encode a file attachment in requested format. - * Returns an empty string on failure. - * @param string $path The full path to the file - * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' - * @throws phpmailerException - * @access protected - * @return string - */ - protected function encodeFile($path, $encoding = 'base64') - { - try { - if (!is_readable($path)) { - throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE); - } - $magic_quotes = get_magic_quotes_runtime(); - if ($magic_quotes) { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - set_magic_quotes_runtime(false); - } else { - //Doesn't exist in PHP 5.4, but we don't need to check because - //get_magic_quotes_runtime always returns false in 5.4+ - //so it will never get here - ini_set('magic_quotes_runtime', false); - } - } - $file_buffer = file_get_contents($path); - $file_buffer = $this->encodeString($file_buffer, $encoding); - if ($magic_quotes) { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - set_magic_quotes_runtime($magic_quotes); - } else { - ini_set('magic_quotes_runtime', $magic_quotes); - } - } - return $file_buffer; - } catch (Exception $exc) { - $this->setError($exc->getMessage()); - return ''; - } - } - - /** - * Encode a string in requested format. - * Returns an empty string on failure. - * @param string $str The text to encode - * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' - * @access public - * @return string - */ - public function encodeString($str, $encoding = 'base64') - { - $encoded = ''; - switch (strtolower($encoding)) { - case 'base64': - $encoded = chunk_split(base64_encode($str), 76, $this->LE); - break; - case '7bit': - case '8bit': - $encoded = $this->fixEOL($str); - // Make sure it ends with a line break - if (substr($encoded, -(strlen($this->LE))) != $this->LE) { - $encoded .= $this->LE; - } - break; - case 'binary': - $encoded = $str; - break; - case 'quoted-printable': - $encoded = $this->encodeQP($str); - break; - default: - $this->setError($this->lang('encoding') . $encoding); - break; - } - return $encoded; - } - - /** - * Encode a header string optimally. - * Picks shortest of Q, B, quoted-printable or none. - * @access public - * @param string $str - * @param string $position - * @return string - */ - public function encodeHeader($str, $position = 'text') - { - $matchcount = 0; - switch (strtolower($position)) { - case 'phrase': - if (!preg_match('/[\200-\377]/', $str)) { - // Can't use addslashes as we don't know the value of magic_quotes_sybase - $encoded = addcslashes($str, "\0..\37\177\\\""); - if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { - return ($encoded); - } else { - return ("\"$encoded\""); - } - } - $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); - break; - /** @noinspection PhpMissingBreakStatementInspection */ - case 'comment': - $matchcount = preg_match_all('/[()"]/', $str, $matches); - // Intentional fall-through - case 'text': - default: - $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); - break; - } - - //There are no chars that need encoding - if ($matchcount == 0) { - return ($str); - } - - $maxlen = 75 - 7 - strlen($this->CharSet); - // Try to select the encoding which should produce the shortest output - if ($matchcount > strlen($str) / 3) { - // More than a third of the content will need encoding, so B encoding will be most efficient - $encoding = 'B'; - if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) { - // Use a custom function which correctly encodes and wraps long - // multibyte strings without breaking lines within a character - $encoded = $this->base64EncodeWrapMB($str, "\n"); - } else { - $encoded = base64_encode($str); - $maxlen -= $maxlen % 4; - $encoded = trim(chunk_split($encoded, $maxlen, "\n")); - } - } else { - $encoding = 'Q'; - $encoded = $this->encodeQ($str, $position); - $encoded = $this->wrapText($encoded, $maxlen, true); - $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded)); - } - - $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); - $encoded = trim(str_replace("\n", $this->LE, $encoded)); - - return $encoded; - } - - /** - * Check if a string contains multi-byte characters. - * @access public - * @param string $str multi-byte text to wrap encode - * @return boolean - */ - public function hasMultiBytes($str) - { - if (function_exists('mb_strlen')) { - return (strlen($str) > mb_strlen($str, $this->CharSet)); - } else { // Assume no multibytes (we can't handle without mbstring functions anyway) - return false; - } - } - - /** - * Does a string contain any 8-bit chars (in any charset)? - * @param string $text - * @return boolean - */ - public function has8bitChars($text) - { - return (boolean)preg_match('/[\x80-\xFF]/', $text); - } - - /** - * Encode and wrap long multibyte strings for mail headers - * without breaking lines within a character. - * Adapted from a function by paravoid - * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 - * @access public - * @param string $str multi-byte text to wrap encode - * @param string $linebreak string to use as linefeed/end-of-line - * @return string - */ - public function base64EncodeWrapMB($str, $linebreak = null) - { - $start = '=?' . $this->CharSet . '?B?'; - $end = '?='; - $encoded = ''; - if ($linebreak === null) { - $linebreak = $this->LE; - } - - $mb_length = mb_strlen($str, $this->CharSet); - // Each line must have length <= 75, including $start and $end - $length = 75 - strlen($start) - strlen($end); - // Average multi-byte ratio - $ratio = $mb_length / strlen($str); - // Base64 has a 4:3 ratio - $avgLength = floor($length * $ratio * .75); - - for ($i = 0; $i < $mb_length; $i += $offset) { - $lookBack = 0; - do { - $offset = $avgLength - $lookBack; - $chunk = mb_substr($str, $i, $offset, $this->CharSet); - $chunk = base64_encode($chunk); - $lookBack++; - } while (strlen($chunk) > $length); - $encoded .= $chunk . $linebreak; - } - - // Chomp the last linefeed - $encoded = substr($encoded, 0, -strlen($linebreak)); - return $encoded; - } - - /** - * Encode a string in quoted-printable format. - * According to RFC2045 section 6.7. - * @access public - * @param string $string The text to encode - * @param integer $line_max Number of chars allowed on a line before wrapping - * @return string - * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment - */ - public function encodeQP($string, $line_max = 76) - { - // Use native function if it's available (>= PHP5.3) - if (function_exists('quoted_printable_encode')) { - return quoted_printable_encode($string); - } - // Fall back to a pure PHP implementation - $string = str_replace( - array('%20', '%0D%0A.', '%0D%0A', '%'), - array(' ', "\r\n=2E", "\r\n", '='), - rawurlencode($string) - ); - return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string); - } - - /** - * Backward compatibility wrapper for an old QP encoding function that was removed. - * @see PHPMailer::encodeQP() - * @access public - * @param string $string - * @param integer $line_max - * @param boolean $space_conv - * @return string - * @deprecated Use encodeQP instead. - */ - public function encodeQPphp( - $string, - $line_max = 76, - /** @noinspection PhpUnusedParameterInspection */ $space_conv = false - ) { - return $this->encodeQP($string, $line_max); - } - - /** - * Encode a string using Q encoding. - * @link http://tools.ietf.org/html/rfc2047 - * @param string $str the text to encode - * @param string $position Where the text is going to be used, see the RFC for what that means - * @access public - * @return string - */ - public function encodeQ($str, $position = 'text') - { - // There should not be any EOL in the string - $pattern = ''; - $encoded = str_replace(array("\r", "\n"), '', $str); - switch (strtolower($position)) { - case 'phrase': - // RFC 2047 section 5.3 - $pattern = '^A-Za-z0-9!*+\/ -'; - break; - /** @noinspection PhpMissingBreakStatementInspection */ - case 'comment': - // RFC 2047 section 5.2 - $pattern = '\(\)"'; - // intentional fall-through - // for this reason we build the $pattern without including delimiters and [] - case 'text': - default: - // RFC 2047 section 5.1 - // Replace every high ascii, control, =, ? and _ characters - $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern; - break; - } - $matches = array(); - if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { - // If the string contains an '=', make sure it's the first thing we replace - // so as to avoid double-encoding - $eqkey = array_search('=', $matches[0]); - if (false !== $eqkey) { - unset($matches[0][$eqkey]); - array_unshift($matches[0], '='); - } - foreach (array_unique($matches[0]) as $char) { - $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); - } - } - // Replace every spaces to _ (more readable than =20) - return str_replace(' ', '_', $encoded); - } - - /** - * Add a string or binary attachment (non-filesystem). - * This method can be used to attach ascii or binary data, - * such as a BLOB record from a database. - * @param string $string String attachment data. - * @param string $filename Name of the attachment. - * @param string $encoding File encoding (see $Encoding). - * @param string $type File extension (MIME) type. - * @param string $disposition Disposition to use - * @return void - */ - public function addStringAttachment( - $string, - $filename, - $encoding = 'base64', - $type = '', - $disposition = 'attachment' - ) { - // If a MIME type is not specified, try to work it out from the file name - if ($type == '') { - $type = self::filenameToType($filename); - } - // Append to $attachment array - $this->attachment[] = array( - 0 => $string, - 1 => $filename, - 2 => basename($filename), - 3 => $encoding, - 4 => $type, - 5 => true, // isStringAttachment - 6 => $disposition, - 7 => 0 - ); - } - - /** - * Add an embedded (inline) attachment from a file. - * This can include images, sounds, and just about any other document type. - * These differ from 'regular' attachments in that they are intended to be - * displayed inline with the message, not just attached for download. - * This is used in HTML messages that embed the images - * the HTML refers to using the $cid value. - * Never use a user-supplied path to a file! - * @param string $path Path to the attachment. - * @param string $cid Content ID of the attachment; Use this to reference - * the content when using an embedded image in HTML. - * @param string $name Overrides the attachment name. - * @param string $encoding File encoding (see $Encoding). - * @param string $type File MIME type. - * @param string $disposition Disposition to use - * @return boolean True on successfully adding an attachment - */ - public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline') - { - if (!@is_file($path)) { - $this->setError($this->lang('file_access') . $path); - return false; - } - - // If a MIME type is not specified, try to work it out from the file name - if ($type == '') { - $type = self::filenameToType($path); - } - - $filename = basename($path); - if ($name == '') { - $name = $filename; - } - - // Append to $attachment array - $this->attachment[] = array( - 0 => $path, - 1 => $filename, - 2 => $name, - 3 => $encoding, - 4 => $type, - 5 => false, // isStringAttachment - 6 => $disposition, - 7 => $cid - ); - return true; - } - - /** - * Add an embedded stringified attachment. - * This can include images, sounds, and just about any other document type. - * Be sure to set the $type to an image type for images: - * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'. - * @param string $string The attachment binary data. - * @param string $cid Content ID of the attachment; Use this to reference - * the content when using an embedded image in HTML. - * @param string $name - * @param string $encoding File encoding (see $Encoding). - * @param string $type MIME type. - * @param string $disposition Disposition to use - * @return boolean True on successfully adding an attachment - */ - public function addStringEmbeddedImage( - $string, - $cid, - $name = '', - $encoding = 'base64', - $type = '', - $disposition = 'inline' - ) { - // If a MIME type is not specified, try to work it out from the name - if ($type == '' and !empty($name)) { - $type = self::filenameToType($name); - } - - // Append to $attachment array - $this->attachment[] = array( - 0 => $string, - 1 => $name, - 2 => $name, - 3 => $encoding, - 4 => $type, - 5 => true, // isStringAttachment - 6 => $disposition, - 7 => $cid - ); - return true; - } - - /** - * Check if an inline attachment is present. - * @access public - * @return boolean - */ - public function inlineImageExists() - { - foreach ($this->attachment as $attachment) { - if ($attachment[6] == 'inline') { - return true; - } - } - return false; - } - - /** - * Check if an attachment (non-inline) is present. - * @return boolean - */ - public function attachmentExists() - { - foreach ($this->attachment as $attachment) { - if ($attachment[6] == 'attachment') { - return true; - } - } - return false; - } - - /** - * Check if this message has an alternative body set. - * @return boolean - */ - public function alternativeExists() - { - return !empty($this->AltBody); - } - - /** - * Clear queued addresses of given kind. - * @access protected - * @param string $kind 'to', 'cc', or 'bcc' - * @return void - */ - public function clearQueuedAddresses($kind) - { - $RecipientsQueue = $this->RecipientsQueue; - foreach ($RecipientsQueue as $address => $params) { - if ($params[0] == $kind) { - unset($this->RecipientsQueue[$address]); - } - } - } - - /** - * Clear all To recipients. - * @return void - */ - public function clearAddresses() - { - foreach ($this->to as $to) { - unset($this->all_recipients[strtolower($to[0])]); - } - $this->to = array(); - $this->clearQueuedAddresses('to'); - } - - /** - * Clear all CC recipients. - * @return void - */ - public function clearCCs() - { - foreach ($this->cc as $cc) { - unset($this->all_recipients[strtolower($cc[0])]); - } - $this->cc = array(); - $this->clearQueuedAddresses('cc'); - } - - /** - * Clear all BCC recipients. - * @return void - */ - public function clearBCCs() - { - foreach ($this->bcc as $bcc) { - unset($this->all_recipients[strtolower($bcc[0])]); - } - $this->bcc = array(); - $this->clearQueuedAddresses('bcc'); - } - - /** - * Clear all ReplyTo recipients. - * @return void - */ - public function clearReplyTos() - { - $this->ReplyTo = array(); - $this->ReplyToQueue = array(); - } - - /** - * Clear all recipient types. - * @return void - */ - public function clearAllRecipients() - { - $this->to = array(); - $this->cc = array(); - $this->bcc = array(); - $this->all_recipients = array(); - $this->RecipientsQueue = array(); - } - - /** - * Clear all filesystem, string, and binary attachments. - * @return void - */ - public function clearAttachments() - { - $this->attachment = array(); - } - - /** - * Clear all custom headers. - * @return void - */ - public function clearCustomHeaders() - { - $this->CustomHeader = array(); - } - - /** - * Add an error message to the error container. - * @access protected - * @param string $msg - * @return void - */ - protected function setError($msg) - { - $this->error_count++; - if ($this->Mailer == 'smtp' and !is_null($this->smtp)) { - $lasterror = $this->smtp->getError(); - if (!empty($lasterror['error'])) { - $msg .= $this->lang('smtp_error') . $lasterror['error']; - if (!empty($lasterror['detail'])) { - $msg .= ' Detail: '. $lasterror['detail']; - } - if (!empty($lasterror['smtp_code'])) { - $msg .= ' SMTP code: ' . $lasterror['smtp_code']; - } - if (!empty($lasterror['smtp_code_ex'])) { - $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex']; - } - } - } - $this->ErrorInfo = $msg; - } - - /** - * Return an RFC 822 formatted date. - * @access public - * @return string - * @static - */ - public static function rfcDate() - { - // Set the time zone to whatever the default is to avoid 500 errors - // Will default to UTC if it's not set properly in php.ini - date_default_timezone_set(@date_default_timezone_get()); - return date('D, j M Y H:i:s O'); - } - - /** - * Get the server hostname. - * Returns 'localhost.localdomain' if unknown. - * @access protected - * @return string - */ - protected function serverHostname() - { - $result = 'localhost.localdomain'; - if (!empty($this->Hostname)) { - $result = $this->Hostname; - } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) { - $result = $_SERVER['SERVER_NAME']; - } elseif (function_exists('gethostname') && gethostname() !== false) { - $result = gethostname(); - } elseif (php_uname('n') !== false) { - $result = php_uname('n'); - } - return $result; - } - - /** - * Get an error message in the current language. - * @access protected - * @param string $key - * @return string - */ - protected function lang($key) - { - if (count($this->language) < 1) { - $this->setLanguage('en'); // set the default language - } - - if (array_key_exists($key, $this->language)) { - if ($key == 'smtp_connect_failed') { - //Include a link to troubleshooting docs on SMTP connection failure - //this is by far the biggest cause of support questions - //but it's usually not PHPMailer's fault. - return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting'; - } - return $this->language[$key]; - } else { - //Return the key as a fallback - return $key; - } - } - - /** - * Check if an error occurred. - * @access public - * @return boolean True if an error did occur. - */ - public function isError() - { - return ($this->error_count > 0); - } - - /** - * Ensure consistent line endings in a string. - * Changes every end of line from CRLF, CR or LF to $this->LE. - * @access public - * @param string $str String to fixEOL - * @return string - */ - public function fixEOL($str) - { - // Normalise to \n - $nstr = str_replace(array("\r\n", "\r"), "\n", $str); - // Now convert LE as needed - if ($this->LE !== "\n") { - $nstr = str_replace("\n", $this->LE, $nstr); - } - return $nstr; - } - - /** - * Add a custom header. - * $name value can be overloaded to contain - * both header name and value (name:value) - * @access public - * @param string $name Custom header name - * @param string $value Header value - * @return void - */ - public function addCustomHeader($name, $value = null) - { - if ($value === null) { - // Value passed in as name:value - $this->CustomHeader[] = explode(':', $name, 2); - } else { - $this->CustomHeader[] = array($name, $value); - } - } - - /** - * Returns all custom headers. - * @return array - */ - public function getCustomHeaders() - { - return $this->CustomHeader; - } - - /** - * Create a message body from an HTML string. - * Automatically inlines images and creates a plain-text version by converting the HTML, - * overwriting any existing values in Body and AltBody. - * Do not source $message content from user input! - * $basedir is prepended when handling relative URLs, e.g. and must not be empty - * will look for an image file in $basedir/images/a.png and convert it to inline. - * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email) - * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly. - * @access public - * @param string $message HTML message string - * @param string $basedir Absolute path to a base directory to prepend to relative paths to images - * @param boolean|callable $advanced Whether to use the internal HTML to text converter - * or your own custom converter @see PHPMailer::html2text() - * @return string $message The transformed message Body - */ - public function msgHTML($message, $basedir = '', $advanced = false) - { - preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images); - if (array_key_exists(2, $images)) { - if (strlen($basedir) > 1 && substr($basedir, -1) != '/') { - // Ensure $basedir has a trailing / - $basedir .= '/'; - } - foreach ($images[2] as $imgindex => $url) { - // Convert data URIs into embedded images - if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) { - $data = substr($url, strpos($url, ',')); - if ($match[2]) { - $data = base64_decode($data); - } else { - $data = rawurldecode($data); - } - $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 - if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) { - $message = str_replace( - $images[0][$imgindex], - $images[1][$imgindex] . '="cid:' . $cid . '"', - $message - ); - } - continue; - } - if ( - // Only process relative URLs if a basedir is provided (i.e. no absolute local paths) - !empty($basedir) - // Ignore URLs containing parent dir traversal (..) - && (strpos($url, '..') === false) - // Do not change urls that are already inline images - && substr($url, 0, 4) !== 'cid:' - // Do not change absolute URLs, including anonymous protocol - && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url) - ) { - $filename = basename($url); - $directory = dirname($url); - if ($directory == '.') { - $directory = ''; - } - $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 - if (strlen($directory) > 1 && substr($directory, -1) != '/') { - $directory .= '/'; - } - if ($this->addEmbeddedImage( - $basedir . $directory . $filename, - $cid, - $filename, - 'base64', - self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION)) - ) - ) { - $message = preg_replace( - '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', - $images[1][$imgindex] . '="cid:' . $cid . '"', - $message - ); - } - } - } - } - $this->isHTML(true); - // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better - $this->Body = $this->normalizeBreaks($message); - $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced)); - if (!$this->alternativeExists()) { - $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . - self::CRLF . self::CRLF; - } - return $this->Body; - } - - /** - * Convert an HTML string into plain text. - * This is used by msgHTML(). - * Note - older versions of this function used a bundled advanced converter - * which was been removed for license reasons in #232. - * Example usage: - * - * // Use default conversion - * $plain = $mail->html2text($html); - * // Use your own custom converter - * $plain = $mail->html2text($html, function($html) { - * $converter = new MyHtml2text($html); - * return $converter->get_text(); - * }); - * - * @param string $html The HTML text to convert - * @param boolean|callable $advanced Any boolean value to use the internal converter, - * or provide your own callable for custom conversion. - * @return string - */ - public function html2text($html, $advanced = false) - { - if (is_callable($advanced)) { - return call_user_func($advanced, $html); - } - return html_entity_decode( - trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))), - ENT_QUOTES, - $this->CharSet - ); - } - - /** - * Get the MIME type for a file extension. - * @param string $ext File extension - * @access public - * @return string MIME type of file. - * @static - */ - public static function _mime_types($ext = '') - { - $mimes = array( - 'xl' => 'application/excel', - 'js' => 'application/javascript', - 'hqx' => 'application/mac-binhex40', - 'cpt' => 'application/mac-compactpro', - 'bin' => 'application/macbinary', - 'doc' => 'application/msword', - 'word' => 'application/msword', - 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', - 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', - 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', - 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', - 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', - 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', - 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', - 'class' => 'application/octet-stream', - 'dll' => 'application/octet-stream', - 'dms' => 'application/octet-stream', - 'exe' => 'application/octet-stream', - 'lha' => 'application/octet-stream', - 'lzh' => 'application/octet-stream', - 'psd' => 'application/octet-stream', - 'sea' => 'application/octet-stream', - 'so' => 'application/octet-stream', - 'oda' => 'application/oda', - 'pdf' => 'application/pdf', - 'ai' => 'application/postscript', - 'eps' => 'application/postscript', - 'ps' => 'application/postscript', - 'smi' => 'application/smil', - 'smil' => 'application/smil', - 'mif' => 'application/vnd.mif', - 'xls' => 'application/vnd.ms-excel', - 'ppt' => 'application/vnd.ms-powerpoint', - 'wbxml' => 'application/vnd.wap.wbxml', - 'wmlc' => 'application/vnd.wap.wmlc', - 'dcr' => 'application/x-director', - 'dir' => 'application/x-director', - 'dxr' => 'application/x-director', - 'dvi' => 'application/x-dvi', - 'gtar' => 'application/x-gtar', - 'php3' => 'application/x-httpd-php', - 'php4' => 'application/x-httpd-php', - 'php' => 'application/x-httpd-php', - 'phtml' => 'application/x-httpd-php', - 'phps' => 'application/x-httpd-php-source', - 'swf' => 'application/x-shockwave-flash', - 'sit' => 'application/x-stuffit', - 'tar' => 'application/x-tar', - 'tgz' => 'application/x-tar', - 'xht' => 'application/xhtml+xml', - 'xhtml' => 'application/xhtml+xml', - 'zip' => 'application/zip', - 'mid' => 'audio/midi', - 'midi' => 'audio/midi', - 'mp2' => 'audio/mpeg', - 'mp3' => 'audio/mpeg', - 'mpga' => 'audio/mpeg', - 'aif' => 'audio/x-aiff', - 'aifc' => 'audio/x-aiff', - 'aiff' => 'audio/x-aiff', - 'ram' => 'audio/x-pn-realaudio', - 'rm' => 'audio/x-pn-realaudio', - 'rpm' => 'audio/x-pn-realaudio-plugin', - 'ra' => 'audio/x-realaudio', - 'wav' => 'audio/x-wav', - 'bmp' => 'image/bmp', - 'gif' => 'image/gif', - 'jpeg' => 'image/jpeg', - 'jpe' => 'image/jpeg', - 'jpg' => 'image/jpeg', - 'png' => 'image/png', - 'tiff' => 'image/tiff', - 'tif' => 'image/tiff', - 'eml' => 'message/rfc822', - 'css' => 'text/css', - 'html' => 'text/html', - 'htm' => 'text/html', - 'shtml' => 'text/html', - 'log' => 'text/plain', - 'text' => 'text/plain', - 'txt' => 'text/plain', - 'rtx' => 'text/richtext', - 'rtf' => 'text/rtf', - 'vcf' => 'text/vcard', - 'vcard' => 'text/vcard', - 'xml' => 'text/xml', - 'xsl' => 'text/xml', - 'mpeg' => 'video/mpeg', - 'mpe' => 'video/mpeg', - 'mpg' => 'video/mpeg', - 'mov' => 'video/quicktime', - 'qt' => 'video/quicktime', - 'rv' => 'video/vnd.rn-realvideo', - 'avi' => 'video/x-msvideo', - 'movie' => 'video/x-sgi-movie' - ); - if (array_key_exists(strtolower($ext), $mimes)) { - return $mimes[strtolower($ext)]; - } - return 'application/octet-stream'; - } - - /** - * Map a file name to a MIME type. - * Defaults to 'application/octet-stream', i.e.. arbitrary binary data. - * @param string $filename A file name or full path, does not need to exist as a file - * @return string - * @static - */ - public static function filenameToType($filename) - { - // In case the path is a URL, strip any query string before getting extension - $qpos = strpos($filename, '?'); - if (false !== $qpos) { - $filename = substr($filename, 0, $qpos); - } - $pathinfo = self::mb_pathinfo($filename); - return self::_mime_types($pathinfo['extension']); - } - - /** - * Multi-byte-safe pathinfo replacement. - * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe. - * Works similarly to the one in PHP >= 5.2.0 - * @link http://www.php.net/manual/en/function.pathinfo.php#107461 - * @param string $path A filename or path, does not need to exist as a file - * @param integer|string $options Either a PATHINFO_* constant, - * or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2 - * @return string|array - * @static - */ - public static function mb_pathinfo($path, $options = null) - { - $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''); - $pathinfo = array(); - if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) { - if (array_key_exists(1, $pathinfo)) { - $ret['dirname'] = $pathinfo[1]; - } - if (array_key_exists(2, $pathinfo)) { - $ret['basename'] = $pathinfo[2]; - } - if (array_key_exists(5, $pathinfo)) { - $ret['extension'] = $pathinfo[5]; - } - if (array_key_exists(3, $pathinfo)) { - $ret['filename'] = $pathinfo[3]; - } - } - switch ($options) { - case PATHINFO_DIRNAME: - case 'dirname': - return $ret['dirname']; - case PATHINFO_BASENAME: - case 'basename': - return $ret['basename']; - case PATHINFO_EXTENSION: - case 'extension': - return $ret['extension']; - case PATHINFO_FILENAME: - case 'filename': - return $ret['filename']; - default: - return $ret; - } - } - - /** - * Set or reset instance properties. - * You should avoid this function - it's more verbose, less efficient, more error-prone and - * harder to debug than setting properties directly. - * Usage Example: - * `$mail->set('SMTPSecure', 'tls');` - * is the same as: - * `$mail->SMTPSecure = 'tls';` - * @access public - * @param string $name The property name to set - * @param mixed $value The value to set the property to - * @return boolean - * @TODO Should this not be using the __set() magic function? - */ - public function set($name, $value = '') - { - if (property_exists($this, $name)) { - $this->$name = $value; - return true; - } else { - $this->setError($this->lang('variable_set') . $name); - return false; - } - } - - /** - * Strip newlines to prevent header injection. - * @access public - * @param string $str - * @return string - */ - public function secureHeader($str) - { - return trim(str_replace(array("\r", "\n"), '', $str)); - } - - /** - * Normalize line breaks in a string. - * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format. - * Defaults to CRLF (for message bodies) and preserves consecutive breaks. - * @param string $text - * @param string $breaktype What kind of line break to use, defaults to CRLF - * @return string - * @access public - * @static - */ - public static function normalizeBreaks($text, $breaktype = "\r\n") - { - return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text); - } - - /** - * Set the public and private key files and password for S/MIME signing. - * @access public - * @param string $cert_filename - * @param string $key_filename - * @param string $key_pass Password for private key - * @param string $extracerts_filename Optional path to chain certificate - */ - public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '') - { - $this->sign_cert_file = $cert_filename; - $this->sign_key_file = $key_filename; - $this->sign_key_pass = $key_pass; - $this->sign_extracerts_file = $extracerts_filename; - } - - /** - * Quoted-Printable-encode a DKIM header. - * @access public - * @param string $txt - * @return string - */ - public function DKIM_QP($txt) - { - $line = ''; - for ($i = 0; $i < strlen($txt); $i++) { - $ord = ord($txt[$i]); - if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) { - $line .= $txt[$i]; - } else { - $line .= '=' . sprintf('%02X', $ord); - } - } - return $line; - } - - /** - * Generate a DKIM signature. - * @access public - * @param string $signHeader - * @throws phpmailerException - * @return string The DKIM signature value - */ - public function DKIM_Sign($signHeader) - { - if (!defined('PKCS7_TEXT')) { - if ($this->exceptions) { - throw new phpmailerException($this->lang('extension_missing') . 'openssl'); - } - return ''; - } - $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private); - if ('' != $this->DKIM_passphrase) { - $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); - } else { - $privKey = openssl_pkey_get_private($privKeyStr); - } - //Workaround for missing digest algorithms in old PHP & OpenSSL versions - //@link http://stackoverflow.com/a/11117338/333340 - if (version_compare(PHP_VERSION, '5.3.0') >= 0 and - in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) { - if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) { - openssl_pkey_free($privKey); - return base64_encode($signature); - } - } else { - $pinfo = openssl_pkey_get_details($privKey); - $hash = hash('sha256', $signHeader); - //'Magic' constant for SHA256 from RFC3447 - //@link https://tools.ietf.org/html/rfc3447#page-43 - $t = '3031300d060960864801650304020105000420' . $hash; - $pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3); - $eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t); - - if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) { - openssl_pkey_free($privKey); - return base64_encode($signature); - } - } - openssl_pkey_free($privKey); - return ''; - } - - /** - * Generate a DKIM canonicalization header. - * @access public - * @param string $signHeader Header - * @return string - */ - public function DKIM_HeaderC($signHeader) - { - $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader); - $lines = explode("\r\n", $signHeader); - foreach ($lines as $key => $line) { - list($heading, $value) = explode(':', $line, 2); - $heading = strtolower($heading); - $value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces - $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value - } - $signHeader = implode("\r\n", $lines); - return $signHeader; - } - - /** - * Generate a DKIM canonicalization body. - * @access public - * @param string $body Message Body - * @return string - */ - public function DKIM_BodyC($body) - { - if ($body == '') { - return "\r\n"; - } - // stabilize line endings - $body = str_replace("\r\n", "\n", $body); - $body = str_replace("\n", "\r\n", $body); - // END stabilize line endings - while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") { - $body = substr($body, 0, strlen($body) - 2); - } - return $body; - } - - /** - * Create the DKIM header and body in a new message header. - * @access public - * @param string $headers_line Header lines - * @param string $subject Subject - * @param string $body Body - * @return string - */ - public function DKIM_Add($headers_line, $subject, $body) - { - $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms - $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body - $DKIMquery = 'dns/txt'; // Query method - $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) - $subject_header = "Subject: $subject"; - $headers = explode($this->LE, $headers_line); - $from_header = ''; - $to_header = ''; - $date_header = ''; - $current = ''; - foreach ($headers as $header) { - if (strpos($header, 'From:') === 0) { - $from_header = $header; - $current = 'from_header'; - } elseif (strpos($header, 'To:') === 0) { - $to_header = $header; - $current = 'to_header'; - } elseif (strpos($header, 'Date:') === 0) { - $date_header = $header; - $current = 'date_header'; - } else { - if (!empty($$current) && strpos($header, ' =?') === 0) { - $$current .= $header; - } else { - $current = ''; - } - } - } - $from = str_replace('|', '=7C', $this->DKIM_QP($from_header)); - $to = str_replace('|', '=7C', $this->DKIM_QP($to_header)); - $date = str_replace('|', '=7C', $this->DKIM_QP($date_header)); - $subject = str_replace( - '|', - '=7C', - $this->DKIM_QP($subject_header) - ); // Copied header fields (dkim-quoted-printable) - $body = $this->DKIM_BodyC($body); - $DKIMlen = strlen($body); // Length of body - $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body - if ('' == $this->DKIM_identity) { - $ident = ''; - } else { - $ident = ' i=' . $this->DKIM_identity . ';'; - } - $dkimhdrs = 'DKIM-Signature: v=1; a=' . - $DKIMsignatureType . '; q=' . - $DKIMquery . '; l=' . - $DKIMlen . '; s=' . - $this->DKIM_selector . - ";\r\n" . - "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" . - "\th=From:To:Date:Subject;\r\n" . - "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" . - "\tz=$from\r\n" . - "\t|$to\r\n" . - "\t|$date\r\n" . - "\t|$subject;\r\n" . - "\tbh=" . $DKIMb64 . ";\r\n" . - "\tb="; - $toSign = $this->DKIM_HeaderC( - $from_header . "\r\n" . - $to_header . "\r\n" . - $date_header . "\r\n" . - $subject_header . "\r\n" . - $dkimhdrs - ); - $signed = $this->DKIM_Sign($toSign); - return $dkimhdrs . $signed . "\r\n"; - } - - /** - * Detect if a string contains a line longer than the maximum line length allowed. - * @param string $str - * @return boolean - * @static - */ - public static function hasLineLongerThanMax($str) - { - //+2 to include CRLF line break for a 1000 total - return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str); - } - - /** - * Allows for public read access to 'to' property. - * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. - * @access public - * @return array - */ - public function getToAddresses() - { - return $this->to; - } - - /** - * Allows for public read access to 'cc' property. - * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. - * @access public - * @return array - */ - public function getCcAddresses() - { - return $this->cc; - } - - /** - * Allows for public read access to 'bcc' property. - * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. - * @access public - * @return array - */ - public function getBccAddresses() - { - return $this->bcc; - } - - /** - * Allows for public read access to 'ReplyTo' property. - * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. - * @access public - * @return array - */ - public function getReplyToAddresses() - { - return $this->ReplyTo; - } - - /** - * Allows for public read access to 'all_recipients' property. - * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. - * @access public - * @return array - */ - public function getAllRecipientAddresses() - { - return $this->all_recipients; - } - - /** - * Perform a callback. - * @param boolean $isSent - * @param array $to - * @param array $cc - * @param array $bcc - * @param string $subject - * @param string $body - * @param string $from - */ - protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from) - { - if (!empty($this->action_function) && is_callable($this->action_function)) { - $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from); - call_user_func_array($this->action_function, $params); - } - } -} - -/** - * PHPMailer exception handler - * @package PHPMailer - */ -class phpmailerException extends Exception -{ - /** - * Prettify error message output - * @return string - */ - public function errorMessage() - { - $errorMsg = '' . htmlspecialchars($this->getMessage()) . "
    \n"; - return $errorMsg; - } -} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmaileroauth.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmaileroauth.php deleted file mode 100644 index b1bb09f0a3..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmaileroauth.php +++ /dev/null @@ -1,197 +0,0 @@ - - * @author Jim Jagielski (jimjag) - * @author Andy Prevost (codeworxtech) - * @author Brent R. Matzelle (original founder) - * @copyright 2012 - 2014 Marcus Bointon - * @copyright 2010 - 2012 Jim Jagielski - * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License - * @note This program is distributed in the hope that it will be useful - WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. - */ - -/** - * PHPMailerOAuth - PHPMailer subclass adding OAuth support. - * @package PHPMailer - * @author @sherryl4george - * @author Marcus Bointon (@Synchro) - */ -class PHPMailerOAuth extends PHPMailer -{ - /** - * The OAuth user's email address - * @var string - */ - public $oauthUserEmail = ''; - - /** - * The OAuth refresh token - * @var string - */ - public $oauthRefreshToken = ''; - - /** - * The OAuth client ID - * @var string - */ - public $oauthClientId = ''; - - /** - * The OAuth client secret - * @var string - */ - public $oauthClientSecret = ''; - - /** - * An instance of the PHPMailerOAuthGoogle class. - * @var PHPMailerOAuthGoogle - * @access protected - */ - protected $oauth = null; - - /** - * Get a PHPMailerOAuthGoogle instance to use. - * @return PHPMailerOAuthGoogle - */ - public function getOAUTHInstance() - { - if (!is_object($this->oauth)) { - $this->oauth = new PHPMailerOAuthGoogle( - $this->oauthUserEmail, - $this->oauthClientSecret, - $this->oauthClientId, - $this->oauthRefreshToken - ); - } - return $this->oauth; - } - - /** - * Initiate a connection to an SMTP server. - * Overrides the original smtpConnect method to add support for OAuth. - * @param array $options An array of options compatible with stream_context_create() - * @uses SMTP - * @access public - * @return bool - * @throws phpmailerException - */ - public function smtpConnect($options = array()) - { - if (is_null($this->smtp)) { - $this->smtp = $this->getSMTPInstance(); - } - - if (is_null($this->oauth)) { - $this->oauth = $this->getOAUTHInstance(); - } - - // Already connected? - if ($this->smtp->connected()) { - return true; - } - - $this->smtp->setTimeout($this->Timeout); - $this->smtp->setDebugLevel($this->SMTPDebug); - $this->smtp->setDebugOutput($this->Debugoutput); - $this->smtp->setVerp($this->do_verp); - $hosts = explode(';', $this->Host); - $lastexception = null; - - foreach ($hosts as $hostentry) { - $hostinfo = array(); - if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) { - // Not a valid host entry - continue; - } - // $hostinfo[2]: optional ssl or tls prefix - // $hostinfo[3]: the hostname - // $hostinfo[4]: optional port number - // The host string prefix can temporarily override the current setting for SMTPSecure - // If it's not specified, the default value is used - $prefix = ''; - $secure = $this->SMTPSecure; - $tls = ($this->SMTPSecure == 'tls'); - if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) { - $prefix = 'ssl://'; - $tls = false; // Can't have SSL and TLS at the same time - $secure = 'ssl'; - } elseif ($hostinfo[2] == 'tls') { - $tls = true; - // tls doesn't use a prefix - $secure = 'tls'; - } - //Do we need the OpenSSL extension? - $sslext = defined('OPENSSL_ALGO_SHA1'); - if ('tls' === $secure or 'ssl' === $secure) { - //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled - if (!$sslext) { - throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL); - } - } - $host = $hostinfo[3]; - $port = $this->Port; - $tport = (integer)$hostinfo[4]; - if ($tport > 0 and $tport < 65536) { - $port = $tport; - } - if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { - try { - if ($this->Helo) { - $hello = $this->Helo; - } else { - $hello = $this->serverHostname(); - } - $this->smtp->hello($hello); - //Automatically enable TLS encryption if: - // * it's not disabled - // * we have openssl extension - // * we are not already using SSL - // * the server offers STARTTLS - if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) { - $tls = true; - } - if ($tls) { - if (!$this->smtp->startTLS()) { - throw new phpmailerException($this->lang('connect_host')); - } - // We must resend HELO after tls negotiation - $this->smtp->hello($hello); - } - if ($this->SMTPAuth) { - if (!$this->smtp->authenticate( - $this->Username, - $this->Password, - $this->AuthType, - $this->Realm, - $this->Workstation, - $this->oauth - ) - ) { - throw new phpmailerException($this->lang('authenticate')); - } - } - return true; - } catch (phpmailerException $exc) { - $lastexception = $exc; - $this->edebug($exc->getMessage()); - // We must have connected, but then failed TLS or Auth, so close connection nicely - $this->smtp->quit(); - } - } - } - // If we get here, all connection attempts have failed, so close connection hard - $this->smtp->close(); - // As we've caught all exceptions, just report whatever the last one was - if ($this->exceptions and !is_null($lastexception)) { - throw $lastexception; - } - return false; - } -} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmaileroauthgoogle.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmaileroauthgoogle.php deleted file mode 100644 index 71c9bd32ff..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmaileroauthgoogle.php +++ /dev/null @@ -1,77 +0,0 @@ - - * @author Jim Jagielski (jimjag) - * @author Andy Prevost (codeworxtech) - * @author Brent R. Matzelle (original founder) - * @copyright 2012 - 2014 Marcus Bointon - * @copyright 2010 - 2012 Jim Jagielski - * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License - * @note This program is distributed in the hope that it will be useful - WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. - */ - -/** - * PHPMailerOAuthGoogle - Wrapper for League OAuth2 Google provider. - * @package PHPMailer - * @author @sherryl4george - * @author Marcus Bointon (@Synchro) - * @link https://github.com/thephpleague/oauth2-client - */ -class PHPMailerOAuthGoogle -{ - private $oauthUserEmail = ''; - private $oauthRefreshToken = ''; - private $oauthClientId = ''; - private $oauthClientSecret = ''; - - /** - * @param string $UserEmail - * @param string $ClientSecret - * @param string $ClientId - * @param string $RefreshToken - */ - public function __construct( - $UserEmail, - $ClientSecret, - $ClientId, - $RefreshToken - ) { - $this->oauthClientId = $ClientId; - $this->oauthClientSecret = $ClientSecret; - $this->oauthRefreshToken = $RefreshToken; - $this->oauthUserEmail = $UserEmail; - } - - private function getProvider() - { - return new League\OAuth2\Client\Provider\Google([ - 'clientId' => $this->oauthClientId, - 'clientSecret' => $this->oauthClientSecret - ]); - } - - private function getGrant() - { - return new \League\OAuth2\Client\Grant\RefreshToken(); - } - - private function getToken() - { - $provider = $this->getProvider(); - $grant = $this->getGrant(); - return $provider->getAccessToken($grant, ['refresh_token' => $this->oauthRefreshToken]); - } - - public function getOauth64() - { - $token = $this->getToken(); - return base64_encode("user=" . $this->oauthUserEmail . "\001auth=Bearer " . $token . "\001\001"); - } -} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php deleted file mode 100644 index f833ac615d..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php +++ /dev/null @@ -1,407 +0,0 @@ - - * @author Jim Jagielski (jimjag) - * @author Andy Prevost (codeworxtech) - * @author Brent R. Matzelle (original founder) - * @copyright 2012 - 2014 Marcus Bointon - * @copyright 2010 - 2012 Jim Jagielski - * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License - * @note This program is distributed in the hope that it will be useful - WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. - */ - -/** - * PHPMailer POP-Before-SMTP Authentication Class. - * Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication. - * Does not support APOP. - * @package PHPMailer - * @author Richard Davey (original author) - * @author Marcus Bointon (Synchro/coolbru) - * @author Jim Jagielski (jimjag) - * @author Andy Prevost (codeworxtech) - */ -class POP3 -{ - /** - * The POP3 PHPMailer Version number. - * @var string - * @access public - */ - public $Version = '5.2.26'; - - /** - * Default POP3 port number. - * @var integer - * @access public - */ - public $POP3_PORT = 110; - - /** - * Default timeout in seconds. - * @var integer - * @access public - */ - public $POP3_TIMEOUT = 30; - - /** - * POP3 Carriage Return + Line Feed. - * @var string - * @access public - * @deprecated Use the constant instead - */ - public $CRLF = "\r\n"; - - /** - * Debug display level. - * Options: 0 = no, 1+ = yes - * @var integer - * @access public - */ - public $do_debug = 0; - - /** - * POP3 mail server hostname. - * @var string - * @access public - */ - public $host; - - /** - * POP3 port number. - * @var integer - * @access public - */ - public $port; - - /** - * POP3 Timeout Value in seconds. - * @var integer - * @access public - */ - public $tval; - - /** - * POP3 username - * @var string - * @access public - */ - public $username; - - /** - * POP3 password. - * @var string - * @access public - */ - public $password; - - /** - * Resource handle for the POP3 connection socket. - * @var resource - * @access protected - */ - protected $pop_conn; - - /** - * Are we connected? - * @var boolean - * @access protected - */ - protected $connected = false; - - /** - * Error container. - * @var array - * @access protected - */ - protected $errors = array(); - - /** - * Line break constant - */ - const CRLF = "\r\n"; - - /** - * Simple static wrapper for all-in-one POP before SMTP - * @param $host - * @param integer|boolean $port The port number to connect to - * @param integer|boolean $timeout The timeout value - * @param string $username - * @param string $password - * @param integer $debug_level - * @return boolean - */ - public static function popBeforeSmtp( - $host, - $port = false, - $timeout = false, - $username = '', - $password = '', - $debug_level = 0 - ) { - $pop = new POP3; - return $pop->authorise($host, $port, $timeout, $username, $password, $debug_level); - } - - /** - * Authenticate with a POP3 server. - * A connect, login, disconnect sequence - * appropriate for POP-before SMTP authorisation. - * @access public - * @param string $host The hostname to connect to - * @param integer|boolean $port The port number to connect to - * @param integer|boolean $timeout The timeout value - * @param string $username - * @param string $password - * @param integer $debug_level - * @return boolean - */ - public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0) - { - $this->host = $host; - // If no port value provided, use default - if (false === $port) { - $this->port = $this->POP3_PORT; - } else { - $this->port = (integer)$port; - } - // If no timeout value provided, use default - if (false === $timeout) { - $this->tval = $this->POP3_TIMEOUT; - } else { - $this->tval = (integer)$timeout; - } - $this->do_debug = $debug_level; - $this->username = $username; - $this->password = $password; - // Reset the error log - $this->errors = array(); - // connect - $result = $this->connect($this->host, $this->port, $this->tval); - if ($result) { - $login_result = $this->login($this->username, $this->password); - if ($login_result) { - $this->disconnect(); - return true; - } - } - // We need to disconnect regardless of whether the login succeeded - $this->disconnect(); - return false; - } - - /** - * Connect to a POP3 server. - * @access public - * @param string $host - * @param integer|boolean $port - * @param integer $tval - * @return boolean - */ - public function connect($host, $port = false, $tval = 30) - { - // Are we already connected? - if ($this->connected) { - return true; - } - - //On Windows this will raise a PHP Warning error if the hostname doesn't exist. - //Rather than suppress it with @fsockopen, capture it cleanly instead - set_error_handler(array($this, 'catchWarning')); - - if (false === $port) { - $port = $this->POP3_PORT; - } - - // connect to the POP3 server - $this->pop_conn = fsockopen( - $host, // POP3 Host - $port, // Port # - $errno, // Error Number - $errstr, // Error Message - $tval - ); // Timeout (seconds) - // Restore the error handler - restore_error_handler(); - - // Did we connect? - if (false === $this->pop_conn) { - // It would appear not... - $this->setError(array( - 'error' => "Failed to connect to server $host on port $port", - 'errno' => $errno, - 'errstr' => $errstr - )); - return false; - } - - // Increase the stream time-out - stream_set_timeout($this->pop_conn, $tval, 0); - - // Get the POP3 server response - $pop3_response = $this->getResponse(); - // Check for the +OK - if ($this->checkResponse($pop3_response)) { - // The connection is established and the POP3 server is talking - $this->connected = true; - return true; - } - return false; - } - - /** - * Log in to the POP3 server. - * Does not support APOP (RFC 2828, 4949). - * @access public - * @param string $username - * @param string $password - * @return boolean - */ - public function login($username = '', $password = '') - { - if (!$this->connected) { - $this->setError('Not connected to POP3 server'); - } - if (empty($username)) { - $username = $this->username; - } - if (empty($password)) { - $password = $this->password; - } - - // Send the Username - $this->sendString("USER $username" . self::CRLF); - $pop3_response = $this->getResponse(); - if ($this->checkResponse($pop3_response)) { - // Send the Password - $this->sendString("PASS $password" . self::CRLF); - $pop3_response = $this->getResponse(); - if ($this->checkResponse($pop3_response)) { - return true; - } - } - return false; - } - - /** - * Disconnect from the POP3 server. - * @access public - */ - public function disconnect() - { - $this->sendString('QUIT'); - //The QUIT command may cause the daemon to exit, which will kill our connection - //So ignore errors here - try { - @fclose($this->pop_conn); - } catch (Exception $e) { - //Do nothing - }; - } - - /** - * Get a response from the POP3 server. - * $size is the maximum number of bytes to retrieve - * @param integer $size - * @return string - * @access protected - */ - protected function getResponse($size = 128) - { - $response = fgets($this->pop_conn, $size); - if ($this->do_debug >= 1) { - echo "Server -> Client: $response"; - } - return $response; - } - - /** - * Send raw data to the POP3 server. - * @param string $string - * @return integer - * @access protected - */ - protected function sendString($string) - { - if ($this->pop_conn) { - if ($this->do_debug >= 2) { //Show client messages when debug >= 2 - echo "Client -> Server: $string"; - } - return fwrite($this->pop_conn, $string, strlen($string)); - } - return 0; - } - - /** - * Checks the POP3 server response. - * Looks for for +OK or -ERR. - * @param string $string - * @return boolean - * @access protected - */ - protected function checkResponse($string) - { - if (substr($string, 0, 3) !== '+OK') { - $this->setError(array( - 'error' => "Server reported an error: $string", - 'errno' => 0, - 'errstr' => '' - )); - return false; - } else { - return true; - } - } - - /** - * Add an error to the internal error store. - * Also display debug output if it's enabled. - * @param $error - * @access protected - */ - protected function setError($error) - { - $this->errors[] = $error; - if ($this->do_debug >= 1) { - echo '
    ';
    -            foreach ($this->errors as $error) {
    -                print_r($error);
    -            }
    -            echo '
    '; - } - } - - /** - * Get an array of error messages, if any. - * @return array - */ - public function getErrors() - { - return $this->errors; - } - - /** - * POP3 connection error handler. - * @param integer $errno - * @param string $errstr - * @param string $errfile - * @param integer $errline - * @access protected - */ - protected function catchWarning($errno, $errstr, $errfile, $errline) - { - $this->setError(array( - 'error' => "Connecting to the POP3 server raised a PHP warning: ", - 'errno' => $errno, - 'errstr' => $errstr, - 'errfile' => $errfile, - 'errline' => $errline - )); - } -} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php deleted file mode 100644 index be6ddce4bd..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php +++ /dev/null @@ -1,1276 +0,0 @@ - - * @author Jim Jagielski (jimjag) - * @author Andy Prevost (codeworxtech) - * @author Brent R. Matzelle (original founder) - * @copyright 2014 Marcus Bointon - * @copyright 2010 - 2012 Jim Jagielski - * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License - * @note This program is distributed in the hope that it will be useful - WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. - */ - -/** - * PHPMailer RFC821 SMTP email transport class. - * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server. - * @package PHPMailer - * @author Chris Ryan - * @author Marcus Bointon - */ -class SMTP -{ - /** - * The PHPMailer SMTP version number. - * @var string - */ - const VERSION = '5.2.26'; - - /** - * SMTP line break constant. - * @var string - */ - const CRLF = "\r\n"; - - /** - * The SMTP port to use if one is not specified. - * @var integer - */ - const DEFAULT_SMTP_PORT = 25; - - /** - * The maximum line length allowed by RFC 2822 section 2.1.1 - * @var integer - */ - const MAX_LINE_LENGTH = 998; - - /** - * Debug level for no output - */ - const DEBUG_OFF = 0; - - /** - * Debug level to show client -> server messages - */ - const DEBUG_CLIENT = 1; - - /** - * Debug level to show client -> server and server -> client messages - */ - const DEBUG_SERVER = 2; - - /** - * Debug level to show connection status, client -> server and server -> client messages - */ - const DEBUG_CONNECTION = 3; - - /** - * Debug level to show all messages - */ - const DEBUG_LOWLEVEL = 4; - - /** - * The PHPMailer SMTP Version number. - * @var string - * @deprecated Use the `VERSION` constant instead - * @see SMTP::VERSION - */ - public $Version = '5.2.26'; - - /** - * SMTP server port number. - * @var integer - * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead - * @see SMTP::DEFAULT_SMTP_PORT - */ - public $SMTP_PORT = 25; - - /** - * SMTP reply line ending. - * @var string - * @deprecated Use the `CRLF` constant instead - * @see SMTP::CRLF - */ - public $CRLF = "\r\n"; - - /** - * Debug output level. - * Options: - * * self::DEBUG_OFF (`0`) No debug output, default - * * self::DEBUG_CLIENT (`1`) Client commands - * * self::DEBUG_SERVER (`2`) Client commands and server responses - * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status - * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages - * @var integer - */ - public $do_debug = self::DEBUG_OFF; - - /** - * How to handle debug output. - * Options: - * * `echo` Output plain-text as-is, appropriate for CLI - * * `html` Output escaped, line breaks converted to `
    `, appropriate for browser output - * * `error_log` Output to error log as configured in php.ini - * - * Alternatively, you can provide a callable expecting two params: a message string and the debug level: - * - * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; - * - * @var string|callable - */ - public $Debugoutput = 'echo'; - - /** - * Whether to use VERP. - * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path - * @link http://www.postfix.org/VERP_README.html Info on VERP - * @var boolean - */ - public $do_verp = false; - - /** - * The timeout value for connection, in seconds. - * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 - * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. - * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2 - * @var integer - */ - public $Timeout = 300; - - /** - * How long to wait for commands to complete, in seconds. - * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 - * @var integer - */ - public $Timelimit = 300; - - /** - * @var array Patterns to extract an SMTP transaction id from reply to a DATA command. - * The first capture group in each regex will be used as the ID. - */ - protected $smtp_transaction_id_patterns = array( - 'exim' => '/[0-9]{3} OK id=(.*)/', - 'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/', - 'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/' - ); - - /** - * @var string The last transaction ID issued in response to a DATA command, - * if one was detected - */ - protected $last_smtp_transaction_id; - - /** - * The socket for the server connection. - * @var resource - */ - protected $smtp_conn; - - /** - * Error information, if any, for the last SMTP command. - * @var array - */ - protected $error = array( - 'error' => '', - 'detail' => '', - 'smtp_code' => '', - 'smtp_code_ex' => '' - ); - - /** - * The reply the server sent to us for HELO. - * If null, no HELO string has yet been received. - * @var string|null - */ - protected $helo_rply = null; - - /** - * The set of SMTP extensions sent in reply to EHLO command. - * Indexes of the array are extension names. - * Value at index 'HELO' or 'EHLO' (according to command that was sent) - * represents the server name. In case of HELO it is the only element of the array. - * Other values can be boolean TRUE or an array containing extension options. - * If null, no HELO/EHLO string has yet been received. - * @var array|null - */ - protected $server_caps = null; - - /** - * The most recent reply received from the server. - * @var string - */ - protected $last_reply = ''; - - /** - * Output debugging info via a user-selected method. - * @see SMTP::$Debugoutput - * @see SMTP::$do_debug - * @param string $str Debug string to output - * @param integer $level The debug level of this message; see DEBUG_* constants - * @return void - */ - protected function edebug($str, $level = 0) - { - if ($level > $this->do_debug) { - return; - } - //Avoid clash with built-in function names - if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { - call_user_func($this->Debugoutput, $str, $level); - return; - } - switch ($this->Debugoutput) { - case 'error_log': - //Don't output, just log - error_log($str); - break; - case 'html': - //Cleans up output a bit for a better looking, HTML-safe output - echo gmdate('Y-m-d H:i:s') . ' ' . htmlentities( - preg_replace('/[\r\n]+/', '', $str), - ENT_QUOTES, - 'UTF-8' - ) . "
    \n"; - break; - case 'echo': - default: - //Normalize line breaks - $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str); - echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( - "\n", - "\n \t ", - trim($str) - ) . "\n"; - } - } - - /** - * Connect to an SMTP server. - * @param string $host SMTP server IP or host name - * @param integer $port The port number to connect to - * @param integer $timeout How long to wait for the connection to open - * @param array $options An array of options for stream_context_create() - * @access public - * @return boolean - */ - public function connect($host, $port = null, $timeout = 30, $options = array()) - { - static $streamok; - //This is enabled by default since 5.0.0 but some providers disable it - //Check this once and cache the result - if (is_null($streamok)) { - $streamok = function_exists('stream_socket_client'); - } - // Clear errors to avoid confusion - $this->setError(''); - // Make sure we are __not__ connected - if ($this->connected()) { - // Already connected, generate error - $this->setError('Already connected to a server'); - return false; - } - if (empty($port)) { - $port = self::DEFAULT_SMTP_PORT; - } - // Connect to the SMTP server - $this->edebug( - "Connection: opening to $host:$port, timeout=$timeout, options=" . - var_export($options, true), - self::DEBUG_CONNECTION - ); - $errno = 0; - $errstr = ''; - if ($streamok) { - $socket_context = stream_context_create($options); - set_error_handler(array($this, 'errorHandler')); - $this->smtp_conn = stream_socket_client( - $host . ":" . $port, - $errno, - $errstr, - $timeout, - STREAM_CLIENT_CONNECT, - $socket_context - ); - restore_error_handler(); - } else { - //Fall back to fsockopen which should work in more places, but is missing some features - $this->edebug( - "Connection: stream_socket_client not available, falling back to fsockopen", - self::DEBUG_CONNECTION - ); - set_error_handler(array($this, 'errorHandler')); - $this->smtp_conn = fsockopen( - $host, - $port, - $errno, - $errstr, - $timeout - ); - restore_error_handler(); - } - // Verify we connected properly - if (!is_resource($this->smtp_conn)) { - $this->setError( - 'Failed to connect to server', - $errno, - $errstr - ); - $this->edebug( - 'SMTP ERROR: ' . $this->error['error'] - . ": $errstr ($errno)", - self::DEBUG_CLIENT - ); - return false; - } - $this->edebug('Connection: opened', self::DEBUG_CONNECTION); - // SMTP server can take longer to respond, give longer timeout for first read - // Windows does not have support for this timeout function - if (substr(PHP_OS, 0, 3) != 'WIN') { - $max = ini_get('max_execution_time'); - // Don't bother if unlimited - if ($max != 0 && $timeout > $max) { - @set_time_limit($timeout); - } - stream_set_timeout($this->smtp_conn, $timeout, 0); - } - // Get any announcement - $announce = $this->get_lines(); - $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); - return true; - } - - /** - * Initiate a TLS (encrypted) session. - * @access public - * @return boolean - */ - public function startTLS() - { - if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { - return false; - } - - //Allow the best TLS version(s) we can - $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT; - - //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT - //so add them back in manually if we can - if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { - $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; - $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; - } - - // Begin encrypted connection - set_error_handler(array($this, 'errorHandler')); - $crypto_ok = stream_socket_enable_crypto( - $this->smtp_conn, - true, - $crypto_method - ); - restore_error_handler(); - return $crypto_ok; - } - - /** - * Perform SMTP authentication. - * Must be run after hello(). - * @see hello() - * @param string $username The user name - * @param string $password The password - * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5, XOAUTH2) - * @param string $realm The auth realm for NTLM - * @param string $workstation The auth workstation for NTLM - * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth) - * @return bool True if successfully authenticated.* @access public - */ - public function authenticate( - $username, - $password, - $authtype = null, - $realm = '', - $workstation = '', - $OAuth = null - ) { - if (!$this->server_caps) { - $this->setError('Authentication is not allowed before HELO/EHLO'); - return false; - } - - if (array_key_exists('EHLO', $this->server_caps)) { - // SMTP extensions are available; try to find a proper authentication method - if (!array_key_exists('AUTH', $this->server_caps)) { - $this->setError('Authentication is not allowed at this stage'); - // 'at this stage' means that auth may be allowed after the stage changes - // e.g. after STARTTLS - return false; - } - - self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL); - self::edebug( - 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), - self::DEBUG_LOWLEVEL - ); - - if (empty($authtype)) { - foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN', 'NTLM', 'XOAUTH2') as $method) { - if (in_array($method, $this->server_caps['AUTH'])) { - $authtype = $method; - break; - } - } - if (empty($authtype)) { - $this->setError('No supported authentication methods found'); - return false; - } - self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL); - } - - if (!in_array($authtype, $this->server_caps['AUTH'])) { - $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); - return false; - } - } elseif (empty($authtype)) { - $authtype = 'LOGIN'; - } - switch ($authtype) { - case 'PLAIN': - // Start authentication - if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { - return false; - } - // Send encoded username and password - if (!$this->sendCommand( - 'User & Password', - base64_encode("\0" . $username . "\0" . $password), - 235 - ) - ) { - return false; - } - break; - case 'LOGIN': - // Start authentication - if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { - return false; - } - if (!$this->sendCommand("Username", base64_encode($username), 334)) { - return false; - } - if (!$this->sendCommand("Password", base64_encode($password), 235)) { - return false; - } - break; - case 'XOAUTH2': - //If the OAuth Instance is not set. Can be a case when PHPMailer is used - //instead of PHPMailerOAuth - if (is_null($OAuth)) { - return false; - } - $oauth = $OAuth->getOauth64(); - - // Start authentication - if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) { - return false; - } - break; - case 'NTLM': - /* - * ntlm_sasl_client.php - * Bundled with Permission - * - * How to telnet in windows: - * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx - * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication - */ - require_once 'extras/ntlm_sasl_client.php'; - $temp = new stdClass; - $ntlm_client = new ntlm_sasl_client_class; - //Check that functions are available - if (!$ntlm_client->initialize($temp)) { - $this->setError($temp->error); - $this->edebug( - 'You need to enable some modules in your php.ini file: ' - . $this->error['error'], - self::DEBUG_CLIENT - ); - return false; - } - //msg1 - $msg1 = $ntlm_client->typeMsg1($realm, $workstation); //msg1 - - if (!$this->sendCommand( - 'AUTH NTLM', - 'AUTH NTLM ' . base64_encode($msg1), - 334 - ) - ) { - return false; - } - //Though 0 based, there is a white space after the 3 digit number - //msg2 - $challenge = substr($this->last_reply, 3); - $challenge = base64_decode($challenge); - $ntlm_res = $ntlm_client->NTLMResponse( - substr($challenge, 24, 8), - $password - ); - //msg3 - $msg3 = $ntlm_client->typeMsg3( - $ntlm_res, - $username, - $realm, - $workstation - ); - // send encoded username - return $this->sendCommand('Username', base64_encode($msg3), 235); - case 'CRAM-MD5': - // Start authentication - if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { - return false; - } - // Get the challenge - $challenge = base64_decode(substr($this->last_reply, 4)); - - // Build the response - $response = $username . ' ' . $this->hmac($challenge, $password); - - // send encoded credentials - return $this->sendCommand('Username', base64_encode($response), 235); - default: - $this->setError("Authentication method \"$authtype\" is not supported"); - return false; - } - return true; - } - - /** - * Calculate an MD5 HMAC hash. - * Works like hash_hmac('md5', $data, $key) - * in case that function is not available - * @param string $data The data to hash - * @param string $key The key to hash with - * @access protected - * @return string - */ - protected function hmac($data, $key) - { - if (function_exists('hash_hmac')) { - return hash_hmac('md5', $data, $key); - } - - // The following borrowed from - // http://php.net/manual/en/function.mhash.php#27225 - - // RFC 2104 HMAC implementation for php. - // Creates an md5 HMAC. - // Eliminates the need to install mhash to compute a HMAC - // by Lance Rushing - - $bytelen = 64; // byte length for md5 - if (strlen($key) > $bytelen) { - $key = pack('H*', md5($key)); - } - $key = str_pad($key, $bytelen, chr(0x00)); - $ipad = str_pad('', $bytelen, chr(0x36)); - $opad = str_pad('', $bytelen, chr(0x5c)); - $k_ipad = $key ^ $ipad; - $k_opad = $key ^ $opad; - - return md5($k_opad . pack('H*', md5($k_ipad . $data))); - } - - /** - * Check connection state. - * @access public - * @return boolean True if connected. - */ - public function connected() - { - if (is_resource($this->smtp_conn)) { - $sock_status = stream_get_meta_data($this->smtp_conn); - if ($sock_status['eof']) { - // The socket is valid but we are not connected - $this->edebug( - 'SMTP NOTICE: EOF caught while checking if connected', - self::DEBUG_CLIENT - ); - $this->close(); - return false; - } - return true; // everything looks good - } - return false; - } - - /** - * Close the socket and clean up the state of the class. - * Don't use this function without first trying to use QUIT. - * @see quit() - * @access public - * @return void - */ - public function close() - { - $this->setError(''); - $this->server_caps = null; - $this->helo_rply = null; - if (is_resource($this->smtp_conn)) { - // close the connection and cleanup - fclose($this->smtp_conn); - $this->smtp_conn = null; //Makes for cleaner serialization - $this->edebug('Connection: closed', self::DEBUG_CONNECTION); - } - } - - /** - * Send an SMTP DATA command. - * Issues a data command and sends the msg_data to the server, - * finializing the mail transaction. $msg_data is the message - * that is to be send with the headers. Each header needs to be - * on a single line followed by a with the message headers - * and the message body being separated by and additional . - * Implements rfc 821: DATA - * @param string $msg_data Message data to send - * @access public - * @return boolean - */ - public function data($msg_data) - { - //This will use the standard timelimit - if (!$this->sendCommand('DATA', 'DATA', 354)) { - return false; - } - - /* The server is ready to accept data! - * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF) - * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into - * smaller lines to fit within the limit. - * We will also look for lines that start with a '.' and prepend an additional '.'. - * NOTE: this does not count towards line-length limit. - */ - - // Normalize line breaks before exploding - $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data)); - - /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field - * of the first line (':' separated) does not contain a space then it _should_ be a header and we will - * process all lines before a blank line as headers. - */ - - $field = substr($lines[0], 0, strpos($lines[0], ':')); - $in_headers = false; - if (!empty($field) && strpos($field, ' ') === false) { - $in_headers = true; - } - - foreach ($lines as $line) { - $lines_out = array(); - if ($in_headers and $line == '') { - $in_headers = false; - } - //Break this line up into several smaller lines if it's too long - //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), - while (isset($line[self::MAX_LINE_LENGTH])) { - //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on - //so as to avoid breaking in the middle of a word - $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); - //Deliberately matches both false and 0 - if (!$pos) { - //No nice break found, add a hard break - $pos = self::MAX_LINE_LENGTH - 1; - $lines_out[] = substr($line, 0, $pos); - $line = substr($line, $pos); - } else { - //Break at the found point - $lines_out[] = substr($line, 0, $pos); - //Move along by the amount we dealt with - $line = substr($line, $pos + 1); - } - //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 - if ($in_headers) { - $line = "\t" . $line; - } - } - $lines_out[] = $line; - - //Send the lines to the server - foreach ($lines_out as $line_out) { - //RFC2821 section 4.5.2 - if (!empty($line_out) and $line_out[0] == '.') { - $line_out = '.' . $line_out; - } - $this->client_send($line_out . self::CRLF); - } - } - - //Message data has been sent, complete the command - //Increase timelimit for end of DATA command - $savetimelimit = $this->Timelimit; - $this->Timelimit = $this->Timelimit * 2; - $result = $this->sendCommand('DATA END', '.', 250); - $this->recordLastTransactionID(); - //Restore timelimit - $this->Timelimit = $savetimelimit; - return $result; - } - - /** - * Send an SMTP HELO or EHLO command. - * Used to identify the sending server to the receiving server. - * This makes sure that client and server are in a known state. - * Implements RFC 821: HELO - * and RFC 2821 EHLO. - * @param string $host The host name or IP to connect to - * @access public - * @return boolean - */ - public function hello($host = '') - { - //Try extended hello first (RFC 2821) - return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); - } - - /** - * Send an SMTP HELO or EHLO command. - * Low-level implementation used by hello() - * @see hello() - * @param string $hello The HELO string - * @param string $host The hostname to say we are - * @access protected - * @return boolean - */ - protected function sendHello($hello, $host) - { - $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); - $this->helo_rply = $this->last_reply; - if ($noerror) { - $this->parseHelloFields($hello); - } else { - $this->server_caps = null; - } - return $noerror; - } - - /** - * Parse a reply to HELO/EHLO command to discover server extensions. - * In case of HELO, the only parameter that can be discovered is a server name. - * @access protected - * @param string $type - 'HELO' or 'EHLO' - */ - protected function parseHelloFields($type) - { - $this->server_caps = array(); - $lines = explode("\n", $this->helo_rply); - - foreach ($lines as $n => $s) { - //First 4 chars contain response code followed by - or space - $s = trim(substr($s, 4)); - if (empty($s)) { - continue; - } - $fields = explode(' ', $s); - if (!empty($fields)) { - if (!$n) { - $name = $type; - $fields = $fields[0]; - } else { - $name = array_shift($fields); - switch ($name) { - case 'SIZE': - $fields = ($fields ? $fields[0] : 0); - break; - case 'AUTH': - if (!is_array($fields)) { - $fields = array(); - } - break; - default: - $fields = true; - } - } - $this->server_caps[$name] = $fields; - } - } - } - - /** - * Send an SMTP MAIL command. - * Starts a mail transaction from the email address specified in - * $from. Returns true if successful or false otherwise. If True - * the mail transaction is started and then one or more recipient - * commands may be called followed by a data command. - * Implements rfc 821: MAIL FROM: - * @param string $from Source address of this message - * @access public - * @return boolean - */ - public function mail($from) - { - $useVerp = ($this->do_verp ? ' XVERP' : ''); - return $this->sendCommand( - 'MAIL FROM', - 'MAIL FROM:<' . $from . '>' . $useVerp, - 250 - ); - } - - /** - * Send an SMTP QUIT command. - * Closes the socket if there is no error or the $close_on_error argument is true. - * Implements from rfc 821: QUIT - * @param boolean $close_on_error Should the connection close if an error occurs? - * @access public - * @return boolean - */ - public function quit($close_on_error = true) - { - $noerror = $this->sendCommand('QUIT', 'QUIT', 221); - $err = $this->error; //Save any error - if ($noerror or $close_on_error) { - $this->close(); - $this->error = $err; //Restore any error from the quit command - } - return $noerror; - } - - /** - * Send an SMTP RCPT command. - * Sets the TO argument to $toaddr. - * Returns true if the recipient was accepted false if it was rejected. - * Implements from rfc 821: RCPT TO: - * @param string $address The address the message is being sent to - * @access public - * @return boolean - */ - public function recipient($address) - { - return $this->sendCommand( - 'RCPT TO', - 'RCPT TO:<' . $address . '>', - array(250, 251) - ); - } - - /** - * Send an SMTP RSET command. - * Abort any transaction that is currently in progress. - * Implements rfc 821: RSET - * @access public - * @return boolean True on success. - */ - public function reset() - { - return $this->sendCommand('RSET', 'RSET', 250); - } - - /** - * Send a command to an SMTP server and check its return code. - * @param string $command The command name - not sent to the server - * @param string $commandstring The actual command to send - * @param integer|array $expect One or more expected integer success codes - * @access protected - * @return boolean True on success. - */ - protected function sendCommand($command, $commandstring, $expect) - { - if (!$this->connected()) { - $this->setError("Called $command without being connected"); - return false; - } - //Reject line breaks in all commands - if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) { - $this->setError("Command '$command' contained line breaks"); - return false; - } - $this->client_send($commandstring . self::CRLF); - - $this->last_reply = $this->get_lines(); - // Fetch SMTP code and possible error code explanation - $matches = array(); - if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) { - $code = $matches[1]; - $code_ex = (count($matches) > 2 ? $matches[2] : null); - // Cut off error code from each response line - $detail = preg_replace( - "/{$code}[ -]" . - ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . "/m", - '', - $this->last_reply - ); - } else { - // Fall back to simple parsing if regex fails - $code = substr($this->last_reply, 0, 3); - $code_ex = null; - $detail = substr($this->last_reply, 4); - } - - $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); - - if (!in_array($code, (array)$expect)) { - $this->setError( - "$command command failed", - $detail, - $code, - $code_ex - ); - $this->edebug( - 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, - self::DEBUG_CLIENT - ); - return false; - } - - $this->setError(''); - return true; - } - - /** - * Send an SMTP SAML command. - * Starts a mail transaction from the email address specified in $from. - * Returns true if successful or false otherwise. If True - * the mail transaction is started and then one or more recipient - * commands may be called followed by a data command. This command - * will send the message to the users terminal if they are logged - * in and send them an email. - * Implements rfc 821: SAML FROM: - * @param string $from The address the message is from - * @access public - * @return boolean - */ - public function sendAndMail($from) - { - return $this->sendCommand('SAML', "SAML FROM:$from", 250); - } - - /** - * Send an SMTP VRFY command. - * @param string $name The name to verify - * @access public - * @return boolean - */ - public function verify($name) - { - return $this->sendCommand('VRFY', "VRFY $name", array(250, 251)); - } - - /** - * Send an SMTP NOOP command. - * Used to keep keep-alives alive, doesn't actually do anything - * @access public - * @return boolean - */ - public function noop() - { - return $this->sendCommand('NOOP', 'NOOP', 250); - } - - /** - * Send an SMTP TURN command. - * This is an optional command for SMTP that this class does not support. - * This method is here to make the RFC821 Definition complete for this class - * and _may_ be implemented in future - * Implements from rfc 821: TURN - * @access public - * @return boolean - */ - public function turn() - { - $this->setError('The SMTP TURN command is not implemented'); - $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); - return false; - } - - /** - * Send raw data to the server. - * @param string $data The data to send - * @access public - * @return integer|boolean The number of bytes sent to the server or false on error - */ - public function client_send($data) - { - $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); - set_error_handler(array($this, 'errorHandler')); - $result = fwrite($this->smtp_conn, $data); - restore_error_handler(); - return $result; - } - - /** - * Get the latest error. - * @access public - * @return array - */ - public function getError() - { - return $this->error; - } - - /** - * Get SMTP extensions available on the server - * @access public - * @return array|null - */ - public function getServerExtList() - { - return $this->server_caps; - } - - /** - * A multipurpose method - * The method works in three ways, dependent on argument value and current state - * 1. HELO/EHLO was not sent - returns null and set up $this->error - * 2. HELO was sent - * $name = 'HELO': returns server name - * $name = 'EHLO': returns boolean false - * $name = any string: returns null and set up $this->error - * 3. EHLO was sent - * $name = 'HELO'|'EHLO': returns server name - * $name = any string: if extension $name exists, returns boolean True - * or its options. Otherwise returns boolean False - * In other words, one can use this method to detect 3 conditions: - * - null returned: handshake was not or we don't know about ext (refer to $this->error) - * - false returned: the requested feature exactly not exists - * - positive value returned: the requested feature exists - * @param string $name Name of SMTP extension or 'HELO'|'EHLO' - * @return mixed - */ - public function getServerExt($name) - { - if (!$this->server_caps) { - $this->setError('No HELO/EHLO was sent'); - return null; - } - - // the tight logic knot ;) - if (!array_key_exists($name, $this->server_caps)) { - if ($name == 'HELO') { - return $this->server_caps['EHLO']; - } - if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) { - return false; - } - $this->setError('HELO handshake was used. Client knows nothing about server extensions'); - return null; - } - - return $this->server_caps[$name]; - } - - /** - * Get the last reply from the server. - * @access public - * @return string - */ - public function getLastReply() - { - return $this->last_reply; - } - - /** - * Read the SMTP server's response. - * Either before eof or socket timeout occurs on the operation. - * With SMTP we can tell if we have more lines to read if the - * 4th character is '-' symbol. If it is a space then we don't - * need to read anything else. - * @access protected - * @return string - */ - protected function get_lines() - { - // If the connection is bad, give up straight away - if (!is_resource($this->smtp_conn)) { - return ''; - } - $data = ''; - $endtime = 0; - stream_set_timeout($this->smtp_conn, $this->Timeout); - if ($this->Timelimit > 0) { - $endtime = time() + $this->Timelimit; - } - while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { - $str = @fgets($this->smtp_conn, 515); - $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); - $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); - $data .= $str; - // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled), - // or 4th character is a space, we are done reading, break the loop, - // string array access is a micro-optimisation over strlen - if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) { - break; - } - // Timed-out? Log and break - $info = stream_get_meta_data($this->smtp_conn); - if ($info['timed_out']) { - $this->edebug( - 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', - self::DEBUG_LOWLEVEL - ); - break; - } - // Now check if reads took too long - if ($endtime and time() > $endtime) { - $this->edebug( - 'SMTP -> get_lines(): timelimit reached (' . - $this->Timelimit . ' sec)', - self::DEBUG_LOWLEVEL - ); - break; - } - } - return $data; - } - - /** - * Enable or disable VERP address generation. - * @param boolean $enabled - */ - public function setVerp($enabled = false) - { - $this->do_verp = $enabled; - } - - /** - * Get VERP address generation mode. - * @return boolean - */ - public function getVerp() - { - return $this->do_verp; - } - - /** - * Set error messages and codes. - * @param string $message The error message - * @param string $detail Further detail on the error - * @param string $smtp_code An associated SMTP error code - * @param string $smtp_code_ex Extended SMTP code - */ - protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '') - { - $this->error = array( - 'error' => $message, - 'detail' => $detail, - 'smtp_code' => $smtp_code, - 'smtp_code_ex' => $smtp_code_ex - ); - } - - /** - * Set debug output method. - * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it. - */ - public function setDebugOutput($method = 'echo') - { - $this->Debugoutput = $method; - } - - /** - * Get debug output method. - * @return string - */ - public function getDebugOutput() - { - return $this->Debugoutput; - } - - /** - * Set debug output level. - * @param integer $level - */ - public function setDebugLevel($level = 0) - { - $this->do_debug = $level; - } - - /** - * Get debug output level. - * @return integer - */ - public function getDebugLevel() - { - return $this->do_debug; - } - - /** - * Set SMTP timeout. - * @param integer $timeout - */ - public function setTimeout($timeout = 0) - { - $this->Timeout = $timeout; - } - - /** - * Get SMTP timeout. - * @return integer - */ - public function getTimeout() - { - return $this->Timeout; - } - - /** - * Reports an error number and string. - * @param integer $errno The error number returned by PHP. - * @param string $errmsg The error message returned by PHP. - * @param string $errfile The file the error occurred in - * @param integer $errline The line number the error occurred on - */ - protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0) - { - $notice = 'Connection failed.'; - $this->setError( - $notice, - $errno, - $errmsg - ); - $this->edebug( - $notice . ' Error #' . $errno . ': ' . $errmsg . " [$errfile line $errline]", - self::DEBUG_CONNECTION - ); - } - - /** - * Extract and return the ID of the last SMTP transaction based on - * a list of patterns provided in SMTP::$smtp_transaction_id_patterns. - * Relies on the host providing the ID in response to a DATA command. - * If no reply has been received yet, it will return null. - * If no pattern was matched, it will return false. - * @return bool|null|string - */ - protected function recordLastTransactionID() - { - $reply = $this->getLastReply(); - - if (empty($reply)) { - $this->last_smtp_transaction_id = null; - } else { - $this->last_smtp_transaction_id = false; - foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) { - if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) { - $this->last_smtp_transaction_id = $matches[1]; - } - } - } - - return $this->last_smtp_transaction_id; - } - - /** - * Get the queue/transaction ID of the last SMTP transaction - * If no reply has been received yet, it will return null. - * If no pattern was matched, it will return false. - * @return bool|null|string - * @see recordLastTransactionID() - */ - public function getLastTransactionID() - { - return $this->last_smtp_transaction_id; - } -} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/composer.json b/data/web/inc/lib/vendor/phpmailer/phpmailer/composer.json index a0ac296494..b13732bed8 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/composer.json +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/composer.json @@ -19,43 +19,58 @@ "name": "Brent R. Matzelle" } ], + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, "require": { + "php": ">=5.5.0", "ext-ctype": "*", - "php": ">=5.0.0" + "ext-filter": "*", + "ext-hash": "*" }, "require-dev": { - "doctrine/annotations": "1.2.*", - "jms/serializer": "0.16.*", - "phpdocumentor/phpdocumentor": "2.*", - "phpunit/phpunit": "4.8.*", - "symfony/debug": "2.8.*", - "symfony/filesystem": "2.8.*", - "symfony/translation": "2.8.*", - "symfony/yaml": "2.8.*", - "zendframework/zend-cache": "2.5.1", - "zendframework/zend-config": "2.5.1", - "zendframework/zend-eventmanager": "2.5.1", - "zendframework/zend-filter": "2.5.1", - "zendframework/zend-i18n": "2.5.1", - "zendframework/zend-json": "2.5.1", - "zendframework/zend-math": "2.5.1", - "zendframework/zend-serializer": "2.5.*", - "zendframework/zend-servicemanager": "2.5.*", - "zendframework/zend-stdlib": "2.5.1" + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.2", + "php-parallel-lint/php-console-highlighter": "^0.5.0", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.6.2", + "yoast/phpunit-polyfills": "^1.0.0" }, "suggest": { - "league/oauth2-google": "Needed for Google XOAUTH2 authentication" + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" }, "autoload": { - "classmap": [ - "class.phpmailer.php", - "class.phpmaileroauth.php", - "class.phpmaileroauthgoogle.php", - "class.smtp.php", - "class.pop3.php", - "extras/EasyPeasyICS.php", - "extras/ntlm_sasl_client.php" - ] + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } }, - "license": "LGPL-2.1" + "autoload-dev": { + "psr-4": { + "PHPMailer\\Test\\": "test/" + } + }, + "license": "LGPL-2.1-only", + "scripts": { + "check": "./vendor/bin/phpcs", + "test": "./vendor/bin/phpunit --no-coverage", + "coverage": "./vendor/bin/phpunit", + "lint": [ + "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php,phps --exclude vendor --exclude .git --exclude build" + ] + } } diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/composer.lock b/data/web/inc/lib/vendor/phpmailer/phpmailer/composer.lock deleted file mode 100644 index 9808f28328..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/composer.lock +++ /dev/null @@ -1,3593 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", - "This file is @generated automatically" - ], - "content-hash": "7e4b1bef833056eed0df39fad5399d7a", - "packages": [], - "packages-dev": [ - { - "name": "cilex/cilex", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/Cilex/Cilex.git", - "reference": "7acd965a609a56d0345e8b6071c261fbdb926cb5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Cilex/Cilex/zipball/7acd965a609a56d0345e8b6071c261fbdb926cb5", - "reference": "7acd965a609a56d0345e8b6071c261fbdb926cb5", - "shasum": "" - }, - "require": { - "cilex/console-service-provider": "1.*", - "php": ">=5.3.3", - "pimple/pimple": "~1.0", - "symfony/finder": "~2.1", - "symfony/process": "~2.1" - }, - "require-dev": { - "phpunit/phpunit": "3.7.*", - "symfony/validator": "~2.1" - }, - "suggest": { - "monolog/monolog": ">=1.0.0", - "symfony/validator": ">=1.0.0", - "symfony/yaml": ">=1.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "Cilex": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" - } - ], - "description": "The PHP micro-framework for Command line tools based on the Symfony2 Components", - "homepage": "http://cilex.github.com", - "keywords": [ - "cli", - "microframework" - ], - "time": "2014-03-29T14:03:13+00:00" - }, - { - "name": "cilex/console-service-provider", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/Cilex/console-service-provider.git", - "reference": "25ee3d1875243d38e1a3448ff94bdf944f70d24e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Cilex/console-service-provider/zipball/25ee3d1875243d38e1a3448ff94bdf944f70d24e", - "reference": "25ee3d1875243d38e1a3448ff94bdf944f70d24e", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "pimple/pimple": "1.*@dev", - "symfony/console": "~2.1" - }, - "require-dev": { - "cilex/cilex": "1.*@dev", - "silex/silex": "1.*@dev" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "Cilex\\Provider\\Console": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Beau Simensen", - "email": "beau@dflydev.com", - "homepage": "http://beausimensen.com" - }, - { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" - } - ], - "description": "Console Service Provider", - "keywords": [ - "cilex", - "console", - "pimple", - "service-provider", - "silex" - ], - "time": "2012-12-19T10:50:58+00:00" - }, - { - "name": "doctrine/annotations", - "version": "v1.2.7", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/f25c8aab83e0c3e976fd7d19875f198ccf2f7535", - "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535", - "shasum": "" - }, - "require": { - "doctrine/lexer": "1.*", - "php": ">=5.3.2" - }, - "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "4.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-0": { - "Doctrine\\Common\\Annotations\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "time": "2015-08-31T12:32:49+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", - "shasum": "" - }, - "require": { - "php": ">=5.3,<8.0-DEV" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", - "keywords": [ - "constructor", - "instantiate" - ], - "time": "2015-06-14T21:17:01+00:00" - }, - { - "name": "doctrine/lexer", - "version": "v1.0.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "lexer", - "parser" - ], - "time": "2014-09-09T13:34:57+00:00" - }, - { - "name": "erusev/parsedown", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/20ff8bbb57205368b4b42d094642a3e52dac85fb", - "reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", - "keywords": [ - "markdown", - "parser" - ], - "time": "2016-11-02T15:56:58+00:00" - }, - { - "name": "herrera-io/json", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/kherge-php/json.git", - "reference": "60c696c9370a1e5136816ca557c17f82a6fa83f1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/kherge-php/json/zipball/60c696c9370a1e5136816ca557c17f82a6fa83f1", - "reference": "60c696c9370a1e5136816ca557c17f82a6fa83f1", - "shasum": "" - }, - "require": { - "ext-json": "*", - "justinrainbow/json-schema": ">=1.0,<2.0-dev", - "php": ">=5.3.3", - "seld/jsonlint": ">=1.0,<2.0-dev" - }, - "require-dev": { - "herrera-io/phpunit-test-case": "1.*", - "mikey179/vfsstream": "1.1.0", - "phpunit/phpunit": "3.7.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "files": [ - "src/lib/json_version.php" - ], - "psr-0": { - "Herrera\\Json": "src/lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kevin Herrera", - "email": "kevin@herrera.io", - "homepage": "http://kevin.herrera.io/", - "role": "Developer" - } - ], - "description": "A library for simplifying JSON linting and validation.", - "homepage": "http://herrera-io.github.com/php-json", - "keywords": [ - "json", - "lint", - "schema", - "validate" - ], - "abandoned": "kherge/json", - "time": "2013-10-30T16:51:34+00:00" - }, - { - "name": "herrera-io/phar-update", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/kherge-abandoned/php-phar-update.git", - "reference": "00a79e1d5b8cf3c080a2e3becf1ddf7a7fea025b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/kherge-abandoned/php-phar-update/zipball/00a79e1d5b8cf3c080a2e3becf1ddf7a7fea025b", - "reference": "00a79e1d5b8cf3c080a2e3becf1ddf7a7fea025b", - "shasum": "" - }, - "require": { - "herrera-io/json": "1.*", - "kherge/version": "1.*", - "php": ">=5.3.3" - }, - "require-dev": { - "herrera-io/phpunit-test-case": "1.*", - "mikey179/vfsstream": "1.1.0", - "phpunit/phpunit": "3.7.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "files": [ - "src/lib/constants.php" - ], - "psr-0": { - "Herrera\\Phar\\Update": "src/lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kevin Herrera", - "email": "kevin@herrera.io", - "homepage": "http://kevin.herrera.io/", - "role": "Developer" - } - ], - "description": "A library for self-updating Phars.", - "homepage": "http://herrera-io.github.com/php-phar-update", - "keywords": [ - "phar", - "update" - ], - "abandoned": true, - "time": "2013-10-30T17:23:01+00:00" - }, - { - "name": "jms/metadata", - "version": "1.6.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/metadata.git", - "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/6a06970a10e0a532fb52d3959547123b84a3b3ab", - "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "doctrine/cache": "~1.0", - "symfony/cache": "~3.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5.x-dev" - } - }, - "autoload": { - "psr-0": { - "Metadata\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Class/method/property metadata management in PHP", - "keywords": [ - "annotations", - "metadata", - "xml", - "yaml" - ], - "time": "2016-12-05T10:18:33+00:00" - }, - { - "name": "jms/parser-lib", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/parser-lib.git", - "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/parser-lib/zipball/c509473bc1b4866415627af0e1c6cc8ac97fa51d", - "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d", - "shasum": "" - }, - "require": { - "phpoption/phpoption": ">=0.9,<2.0-dev" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "JMS\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "description": "A library for easily creating recursive-descent parsers.", - "time": "2012-11-18T18:08:43+00:00" - }, - { - "name": "jms/serializer", - "version": "0.16.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/serializer.git", - "reference": "c8a171357ca92b6706e395c757f334902d430ea9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/c8a171357ca92b6706e395c757f334902d430ea9", - "reference": "c8a171357ca92b6706e395c757f334902d430ea9", - "shasum": "" - }, - "require": { - "doctrine/annotations": "1.*", - "jms/metadata": "~1.1", - "jms/parser-lib": "1.*", - "php": ">=5.3.2", - "phpcollection/phpcollection": "~0.1" - }, - "require-dev": { - "doctrine/orm": "~2.1", - "doctrine/phpcr-odm": "~1.0.1", - "jackalope/jackalope-doctrine-dbal": "1.0.*", - "propel/propel1": "~1.7", - "symfony/filesystem": "2.*", - "symfony/form": "~2.1", - "symfony/translation": "~2.0", - "symfony/validator": "~2.0", - "symfony/yaml": "2.*", - "twig/twig": ">=1.8,<2.0-dev" - }, - "suggest": { - "symfony/yaml": "Required if you'd like to serialize data to YAML format." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.15-dev" - } - }, - "autoload": { - "psr-0": { - "JMS\\Serializer": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "authors": [ - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh", - "role": "Developer of wrapped JMSSerializerBundle" - } - ], - "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", - "homepage": "http://jmsyst.com/libs/serializer", - "keywords": [ - "deserialization", - "jaxb", - "json", - "serialization", - "xml" - ], - "time": "2014-03-18T08:39:00+00:00" - }, - { - "name": "justinrainbow/json-schema", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "cc84765fb7317f6b07bd8ac78364747f95b86341" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/cc84765fb7317f6b07bd8ac78364747f95b86341", - "reference": "cc84765fb7317f6b07bd8ac78364747f95b86341", - "shasum": "" - }, - "require": { - "php": ">=5.3.29" - }, - "require-dev": { - "json-schema/json-schema-test-suite": "1.1.0", - "phpdocumentor/phpdocumentor": "~2", - "phpunit/phpunit": "~3.7" - }, - "bin": [ - "bin/validate-json" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, - "autoload": { - "psr-4": { - "JsonSchema\\": "src/JsonSchema/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" - }, - { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Robert Schรถnthal", - "email": "seroscho@googlemail.com" - } - ], - "description": "A library to validate a json schema.", - "homepage": "https://github.com/justinrainbow/json-schema", - "keywords": [ - "json", - "schema" - ], - "time": "2016-01-25T15:43:01+00:00" - }, - { - "name": "kherge/version", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/kherge-abandoned/Version.git", - "reference": "f07cf83f8ce533be8f93d2893d96d674bbeb7e30" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/kherge-abandoned/Version/zipball/f07cf83f8ce533be8f93d2893d96d674bbeb7e30", - "reference": "f07cf83f8ce533be8f93d2893d96d674bbeb7e30", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "KevinGH\\Version": "src/lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kevin Herrera", - "email": "me@kevingh.com", - "homepage": "http://www.kevingh.com/" - } - ], - "description": "A parsing and comparison library for semantic versioning.", - "homepage": "http://github.com/kherge/Version", - "abandoned": true, - "time": "2012-08-16T17:13:03+00:00" - }, - { - "name": "monolog/monolog", - "version": "1.22.1", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1e044bc4b34e91743943479f1be7a1d5eb93add0", - "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" - }, - "provide": { - "psr/log-implementation": "1.0.0" - }, - "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", - "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "jakub-onderka/php-parallel-lint": "0.9", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpunit/phpunit": "~4.5", - "phpunit/phpunit-mock-objects": "2.3.0", - "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "~5.3" - }, - "suggest": { - "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", - "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", - "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", - "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", - "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Monolog\\": "src/Monolog" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", - "keywords": [ - "log", - "logging", - "psr-3" - ], - "time": "2017-03-13T07:08:03+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v1.4.1", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51", - "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "files": [ - "lib/bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "time": "2015-09-19T14:15:08+00:00" - }, - { - "name": "phpcollection/phpcollection", - "version": "0.5.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/php-collection.git", - "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-collection/zipball/f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", - "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", - "shasum": "" - }, - "require": { - "phpoption/phpoption": "1.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-0": { - "PhpCollection": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "General-Purpose Collection Library for PHP", - "keywords": [ - "collection", - "list", - "map", - "sequence", - "set" - ], - "time": "2015-05-17T12:39:23+00:00" - }, - { - "name": "phpdocumentor/fileset", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/Fileset.git", - "reference": "bfa78d8fa9763dfce6d0e5d3730c1d8ab25d34b0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/Fileset/zipball/bfa78d8fa9763dfce6d0e5d3730c1d8ab25d34b0", - "reference": "bfa78d8fa9763dfce6d0e5d3730c1d8ab25d34b0", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "symfony/finder": "~2.1" - }, - "require-dev": { - "phpunit/phpunit": "~3.7" - }, - "type": "library", - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/", - "tests/unit/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Fileset component for collecting a set of files given directories and file paths", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "files", - "fileset", - "phpdoc" - ], - "time": "2013-08-06T21:07:42+00:00" - }, - { - "name": "phpdocumentor/graphviz", - "version": "1.0.4", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/GraphViz.git", - "reference": "a906a90a9f230535f25ea31caf81b2323956283f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/GraphViz/zipball/a906a90a9f230535f25ea31caf81b2323956283f", - "reference": "a906a90a9f230535f25ea31caf81b2323956283f", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/", - "tests/unit" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" - } - ], - "time": "2016-02-02T13:00:08+00:00" - }, - { - "name": "phpdocumentor/phpdocumentor", - "version": "v2.9.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/phpDocumentor2.git", - "reference": "be607da0eef9b9249c43c5b4820d25d631c73667" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/phpDocumentor2/zipball/be607da0eef9b9249c43c5b4820d25d631c73667", - "reference": "be607da0eef9b9249c43c5b4820d25d631c73667", - "shasum": "" - }, - "require": { - "cilex/cilex": "~1.0", - "erusev/parsedown": "~1.0", - "herrera-io/phar-update": "1.0.3", - "jms/serializer": ">=0.12", - "monolog/monolog": "~1.6", - "php": ">=5.3.3", - "phpdocumentor/fileset": "~1.0", - "phpdocumentor/graphviz": "~1.0", - "phpdocumentor/reflection": "^3.0", - "phpdocumentor/reflection-docblock": "~2.0", - "symfony/config": "~2.3", - "symfony/console": "~2.3", - "symfony/event-dispatcher": "~2.1", - "symfony/process": "~2.0", - "symfony/stopwatch": "~2.3", - "symfony/validator": "~2.2", - "twig/twig": "~1.3", - "zendframework/zend-cache": "~2.1", - "zendframework/zend-config": "~2.1", - "zendframework/zend-filter": "~2.1", - "zendframework/zend-i18n": "~2.1", - "zendframework/zend-serializer": "~2.1", - "zendframework/zend-servicemanager": "~2.1", - "zendframework/zend-stdlib": "~2.1", - "zetacomponents/document": ">=1.3.1" - }, - "require-dev": { - "behat/behat": "~3.0", - "mikey179/vfsstream": "~1.2", - "mockery/mockery": "~0.9@dev", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.4", - "symfony/expression-language": "~2.4" - }, - "suggest": { - "ext-twig": "Enabling the twig extension improves the generation of twig based templates.", - "ext-xslcache": "Enabling the XSLCache extension improves the generation of xml based templates." - }, - "bin": [ - "bin/phpdoc.php", - "bin/phpdoc" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-develop": "2.9-dev" - } - }, - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/", - "tests/unit/" - ], - "Cilex\\Provider": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Documentation Generator for PHP", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "api", - "application", - "dga", - "documentation", - "phpdoc" - ], - "time": "2016-05-22T09:50:56+00:00" - }, - { - "name": "phpdocumentor/reflection", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/Reflection.git", - "reference": "793bfd92d9a0fc96ae9608fb3e947c3f59fb3a0d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/793bfd92d9a0fc96ae9608fb3e947c3f59fb3a0d", - "reference": "793bfd92d9a0fc96ae9608fb3e947c3f59fb3a0d", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^1.0", - "php": ">=5.3.3", - "phpdocumentor/reflection-docblock": "~2.0", - "psr/log": "~1.0" - }, - "require-dev": { - "behat/behat": "~2.4", - "mockery/mockery": "~0.8", - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/", - "tests/unit/", - "tests/mocks/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Reflection library to do Static Analysis for PHP Projects", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "time": "2016-05-21T08:42:32+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "dflydev/markdown": "~1.0", - "erusev/parsedown": "~1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" - } - ], - "time": "2015-02-03T12:10:50+00:00" - }, - { - "name": "phpoption/phpoption", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/php-option.git", - "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/94e644f7d2051a5f0fcf77d81605f152eecff0ed", - "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "4.7.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "autoload": { - "psr-0": { - "PhpOption\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Option Type for PHP", - "keywords": [ - "language", - "option", - "php", - "type" - ], - "time": "2015-07-25T16:39:46+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "v1.7.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", - "sebastian/comparator": "^1.1|^2.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" - }, - "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8 || ^5.6.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, - "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2017-03-02T20:05:34+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "2.2.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", - "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "~1.3", - "sebastian/environment": "^1.3.2", - "sebastian/version": "~1.0" - }, - "require-dev": { - "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "~4" - }, - "suggest": { - "ext-dom": "*", - "ext-xdebug": ">=2.2.1", - "ext-xmlwriter": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "time": "2015-10-06T15:47:00+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "1.4.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", - "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "time": "2016-10-03T07:40:28+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2015-06-21T13:50:34+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "1.0.9", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "time": "2017-02-26T11:10:40+00:00" - }, - { - "name": "phpunit/php-token-stream", - "version": "1.4.11", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "time": "2017-02-27T10:12:30+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "4.8.35", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/791b1a67c25af50e230f841ee7a9c6eba507dc87", - "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=5.3.3", - "phpspec/prophecy": "^1.3.1", - "phpunit/php-code-coverage": "~2.1", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "~2.3", - "sebastian/comparator": "~1.2.2", - "sebastian/diff": "~1.2", - "sebastian/environment": "~1.3", - "sebastian/exporter": "~1.2", - "sebastian/global-state": "~1.0", - "sebastian/version": "~1.0", - "symfony/yaml": "~2.1|~3.0" - }, - "suggest": { - "phpunit/php-invoker": "~1.1" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.8.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2017-02-06T05:18:07+00:00" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "2.3.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", - "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": ">=5.3.3", - "phpunit/php-text-template": "~1.2", - "sebastian/exporter": "~1.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2015-10-02T06:51:40+00:00" - }, - { - "name": "pimple/pimple", - "version": "v1.1.1", - "source": { - "type": "git", - "url": "https://github.com/silexphp/Pimple.git", - "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silexphp/Pimple/zipball/2019c145fe393923f3441b23f29bbdfaa5c58c4d", - "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-0": { - "Pimple": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - } - ], - "description": "Pimple is a simple Dependency Injection Container for PHP 5.3", - "homepage": "http://pimple.sensiolabs.org", - "keywords": [ - "container", - "dependency injection" - ], - "time": "2013-11-22T08:30:29+00:00" - }, - { - "name": "psr/log", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "time": "2016-10-10T12:19:37+00:00" - }, - { - "name": "sebastian/comparator", - "version": "1.2.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2 || ~2.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "time": "2017-01-29T09:50:25+00:00" - }, - { - "name": "sebastian/diff", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff" - ], - "time": "2015-12-08T07:14:41+00:00" - }, - { - "name": "sebastian/environment", - "version": "1.3.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", - "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8 || ^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "time": "2016-08-18T05:49:44+00:00" - }, - { - "name": "sebastian/exporter", - "version": "1.2.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", - "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~1.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "time": "2016-06-17T09:04:28+00:00" - }, - { - "name": "sebastian/global-state", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "time": "2015-10-12T03:26:01+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", - "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2016-10-03T07:41:43+00:00" - }, - { - "name": "sebastian/version", - "version": "1.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", - "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", - "shasum": "" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2015-06-21T13:59:46+00:00" - }, - { - "name": "seld/jsonlint", - "version": "1.6.0", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "791f8c594f300d246cdf01c6b3e1e19611e301d8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/791f8c594f300d246cdf01c6b3e1e19611e301d8", - "reference": "791f8c594f300d246cdf01c6b3e1e19611e301d8", - "shasum": "" - }, - "require": { - "php": "^5.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.5" - }, - "bin": [ - "bin/jsonlint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Seld\\JsonLint\\": "src/Seld/JsonLint/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "JSON Linter", - "keywords": [ - "json", - "linter", - "parser", - "validator" - ], - "time": "2017-03-06T16:42:24+00:00" - }, - { - "name": "symfony/config", - "version": "v2.8.18", - "source": { - "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "06ce6bb46c24963ec09323da45d0f4f85d3cecd2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/06ce6bb46c24963ec09323da45d0f4f85d3cecd2", - "reference": "06ce6bb46c24963ec09323da45d0f4f85d3cecd2", - "shasum": "" - }, - "require": { - "php": ">=5.3.9", - "symfony/filesystem": "~2.3|~3.0.0" - }, - "require-dev": { - "symfony/yaml": "~2.7|~3.0.0" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Config\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Config Component", - "homepage": "https://symfony.com", - "time": "2017-03-01T18:13:50+00:00" - }, - { - "name": "symfony/console", - "version": "v2.8.18", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "81508e6fac4476771275a3f4f53c3fee9b956bfa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/81508e6fac4476771275a3f4f53c3fee9b956bfa", - "reference": "81508e6fac4476771275a3f4f53c3fee9b956bfa", - "shasum": "" - }, - "require": { - "php": ">=5.3.9", - "symfony/debug": "^2.7.2|~3.0.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/event-dispatcher": "~2.1|~3.0.0", - "symfony/process": "~2.1|~3.0.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/process": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Console Component", - "homepage": "https://symfony.com", - "time": "2017-03-04T11:00:12+00:00" - }, - { - "name": "symfony/debug", - "version": "v2.8.18", - "source": { - "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "e90099a2958d4833a02d05b504cc06e1c234abcc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/e90099a2958d4833a02d05b504cc06e1c234abcc", - "reference": "e90099a2958d4833a02d05b504cc06e1c234abcc", - "shasum": "" - }, - "require": { - "php": ">=5.3.9", - "psr/log": "~1.0" - }, - "conflict": { - "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" - }, - "require-dev": { - "symfony/class-loader": "~2.2|~3.0.0", - "symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2|~3.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Debug Component", - "homepage": "https://symfony.com", - "time": "2017-02-18T19:13:35+00:00" - }, - { - "name": "symfony/event-dispatcher", - "version": "v2.8.18", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "bb4ec47e8e109c1c1172145732d0aa468d967cd0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/bb4ec47e8e109c1c1172145732d0aa468d967cd0", - "reference": "bb4ec47e8e109c1c1172145732d0aa468d967cd0", - "shasum": "" - }, - "require": { - "php": ">=5.3.9" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^2.0.5|~3.0.0", - "symfony/dependency-injection": "~2.6|~3.0.0", - "symfony/expression-language": "~2.6|~3.0.0", - "symfony/stopwatch": "~2.3|~3.0.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony EventDispatcher Component", - "homepage": "https://symfony.com", - "time": "2017-02-21T08:33:48+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v2.8.18", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "e542d4765092d22552b1bf01ddccfb01d98ee325" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e542d4765092d22552b1bf01ddccfb01d98ee325", - "reference": "e542d4765092d22552b1bf01ddccfb01d98ee325", - "shasum": "" - }, - "require": { - "php": ">=5.3.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Filesystem Component", - "homepage": "https://symfony.com", - "time": "2017-02-18T17:06:33+00:00" - }, - { - "name": "symfony/finder", - "version": "v2.8.18", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "5fc4b5cab38b9d28be318fcffd8066988e7d9451" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/5fc4b5cab38b9d28be318fcffd8066988e7d9451", - "reference": "5fc4b5cab38b9d28be318fcffd8066988e7d9451", - "shasum": "" - }, - "require": { - "php": ">=5.3.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Finder Component", - "homepage": "https://symfony.com", - "time": "2017-02-21T08:33:48+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "time": "2016-11-14T01:06:16+00:00" - }, - { - "name": "symfony/process", - "version": "v2.8.18", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "41336b20b52f5fd5b42a227e394e673c8071118f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/41336b20b52f5fd5b42a227e394e673c8071118f", - "reference": "41336b20b52f5fd5b42a227e394e673c8071118f", - "shasum": "" - }, - "require": { - "php": ">=5.3.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Process Component", - "homepage": "https://symfony.com", - "time": "2017-03-04T12:20:59+00:00" - }, - { - "name": "symfony/stopwatch", - "version": "v2.8.18", - "source": { - "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "9e4369666d02ee9b8830da878b7f6a769eb96f4b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/9e4369666d02ee9b8830da878b7f6a769eb96f4b", - "reference": "9e4369666d02ee9b8830da878b7f6a769eb96f4b", - "shasum": "" - }, - "require": { - "php": ">=5.3.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Stopwatch Component", - "homepage": "https://symfony.com", - "time": "2017-02-18T17:06:33+00:00" - }, - { - "name": "symfony/translation", - "version": "v2.8.18", - "source": { - "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "b538355bc99db2ec7cc35284ec76d92ae7d1d256" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/b538355bc99db2ec7cc35284ec76d92ae7d1d256", - "reference": "b538355bc99db2ec7cc35284ec76d92ae7d1d256", - "shasum": "" - }, - "require": { - "php": ">=5.3.9", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/config": "<2.7" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.8", - "symfony/intl": "~2.7.25|^2.8.18|~3.2.5", - "symfony/yaml": "~2.2|~3.0.0" - }, - "suggest": { - "psr/log": "To use logging capability in translator", - "symfony/config": "", - "symfony/yaml": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Translation\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Translation Component", - "homepage": "https://symfony.com", - "time": "2017-03-04T12:20:59+00:00" - }, - { - "name": "symfony/validator", - "version": "v2.8.18", - "source": { - "type": "git", - "url": "https://github.com/symfony/validator.git", - "reference": "8d4bfa7ec24e70ebc28d0cea5f2702d3f1257a63" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/8d4bfa7ec24e70ebc28d0cea5f2702d3f1257a63", - "reference": "8d4bfa7ec24e70ebc28d0cea5f2702d3f1257a63", - "shasum": "" - }, - "require": { - "php": ">=5.3.9", - "symfony/polyfill-mbstring": "~1.0", - "symfony/translation": "~2.4|~3.0.0" - }, - "require-dev": { - "doctrine/annotations": "~1.0", - "doctrine/cache": "~1.0", - "egulias/email-validator": "^1.2.1", - "symfony/config": "~2.2|~3.0.0", - "symfony/expression-language": "~2.4|~3.0.0", - "symfony/http-foundation": "~2.3|~3.0.0", - "symfony/intl": "~2.7.25|^2.8.18|~3.2.5", - "symfony/property-access": "~2.3|~3.0.0", - "symfony/yaml": "^2.0.5|~3.0.0" - }, - "suggest": { - "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", - "doctrine/cache": "For using the default cached annotation reader and metadata cache.", - "egulias/email-validator": "Strict (RFC compliant) email validation", - "symfony/config": "", - "symfony/expression-language": "For using the 2.4 Expression validator", - "symfony/http-foundation": "", - "symfony/intl": "", - "symfony/property-access": "For using the 2.4 Validator API", - "symfony/yaml": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Validator\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Validator Component", - "homepage": "https://symfony.com", - "time": "2017-02-28T02:24:56+00:00" - }, - { - "name": "symfony/yaml", - "version": "v2.8.18", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "2a7bab3c16f6f452c47818fdd08f3b1e49ffcf7d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/2a7bab3c16f6f452c47818fdd08f3b1e49ffcf7d", - "reference": "2a7bab3c16f6f452c47818fdd08f3b1e49ffcf7d", - "shasum": "" - }, - "require": { - "php": ">=5.3.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2017-03-01T18:13:50+00:00" - }, - { - "name": "twig/twig", - "version": "v1.32.0", - "source": { - "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "9935b662e24d6e634da88901ab534cc12e8c728f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/9935b662e24d6e634da88901ab534cc12e8c728f", - "reference": "9935b662e24d6e634da88901ab534cc12e8c728f", - "shasum": "" - }, - "require": { - "php": ">=5.2.7" - }, - "require-dev": { - "psr/container": "^1.0", - "symfony/debug": "~2.7", - "symfony/phpunit-bridge": "~3.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.32-dev" - } - }, - "autoload": { - "psr-0": { - "Twig_": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - }, - { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" - }, - { - "name": "Twig Team", - "homepage": "http://twig.sensiolabs.org/contributors", - "role": "Contributors" - } - ], - "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "http://twig.sensiolabs.org", - "keywords": [ - "templating" - ], - "time": "2017-02-27T00:07:03+00:00" - }, - { - "name": "zendframework/zend-cache", - "version": "2.5.1", - "source": { - "type": "git", - "url": "https://github.com/zendframework/zend-cache.git", - "reference": "5999e5a03f7dcf82abbbe67eea74da641f959684" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-cache/zipball/5999e5a03f7dcf82abbbe67eea74da641f959684", - "reference": "5999e5a03f7dcf82abbbe67eea74da641f959684", - "shasum": "" - }, - "require": { - "php": ">=5.3.23", - "zendframework/zend-eventmanager": "~2.5", - "zendframework/zend-serializer": "~2.5", - "zendframework/zend-servicemanager": "~2.5", - "zendframework/zend-stdlib": "~2.5" - }, - "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "zendframework/zend-session": "~2.5" - }, - "suggest": { - "ext-apc": "APC >= 3.1.6 to use the APC storage adapter", - "ext-dba": "DBA, to use the DBA storage adapter", - "ext-memcached": "Memcached >= 1.0.0 to use the Memcached storage adapter", - "ext-mongo": "Mongo, to use MongoDb storage adapter", - "ext-wincache": "WinCache, to use the WinCache storage adapter", - "mongofill/mongofill": "Alternative to ext-mongo - a pure PHP implementation designed as a drop in replacement", - "zendframework/zend-serializer": "Zend\\Serializer component", - "zendframework/zend-session": "Zend\\Session component" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev", - "dev-develop": "2.6-dev" - } - }, - "autoload": { - "psr-4": { - "Zend\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "provides a generic way to cache any data", - "homepage": "https://github.com/zendframework/zend-cache", - "keywords": [ - "cache", - "zf2" - ], - "time": "2015-06-03T15:31:59+00:00" - }, - { - "name": "zendframework/zend-config", - "version": "2.5.1", - "source": { - "type": "git", - "url": "https://github.com/zendframework/zend-config.git", - "reference": "ec49b1df1bdd9772df09dc2f612fbfc279bf4c27" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-config/zipball/ec49b1df1bdd9772df09dc2f612fbfc279bf4c27", - "reference": "ec49b1df1bdd9772df09dc2f612fbfc279bf4c27", - "shasum": "" - }, - "require": { - "php": ">=5.3.23", - "zendframework/zend-stdlib": "~2.5" - }, - "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "zendframework/zend-filter": "~2.5", - "zendframework/zend-i18n": "~2.5", - "zendframework/zend-json": "~2.5", - "zendframework/zend-mvc": "~2.5", - "zendframework/zend-servicemanager": "~2.5" - }, - "suggest": { - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-json": "Zend\\Json to use the Json reader or writer classes", - "zendframework/zend-servicemanager": "Zend\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev", - "dev-develop": "2.6-dev" - } - }, - "autoload": { - "psr-4": { - "Zend\\Config\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "provides a nested object property based user interface for accessing this configuration data within application code", - "homepage": "https://github.com/zendframework/zend-config", - "keywords": [ - "config", - "zf2" - ], - "time": "2015-06-03T15:32:00+00:00" - }, - { - "name": "zendframework/zend-eventmanager", - "version": "2.5.1", - "source": { - "type": "git", - "url": "https://github.com/zendframework/zend-eventmanager.git", - "reference": "d94a16039144936f107f906896349900fd634443" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/d94a16039144936f107f906896349900fd634443", - "reference": "d94a16039144936f107f906896349900fd634443", - "shasum": "" - }, - "require": { - "php": ">=5.3.23", - "zendframework/zend-stdlib": "~2.5" - }, - "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev", - "dev-develop": "2.6-dev" - } - }, - "autoload": { - "psr-4": { - "Zend\\EventManager\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "homepage": "https://github.com/zendframework/zend-eventmanager", - "keywords": [ - "eventmanager", - "zf2" - ], - "time": "2015-06-03T15:32:01+00:00" - }, - { - "name": "zendframework/zend-filter", - "version": "2.5.1", - "source": { - "type": "git", - "url": "https://github.com/zendframework/zend-filter.git", - "reference": "93e6990a198e6cdd811064083acac4693f4b29ae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/93e6990a198e6cdd811064083acac4693f4b29ae", - "reference": "93e6990a198e6cdd811064083acac4693f4b29ae", - "shasum": "" - }, - "require": { - "php": ">=5.3.23", - "zendframework/zend-stdlib": "~2.5" - }, - "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "zendframework/zend-config": "~2.5", - "zendframework/zend-crypt": "~2.5", - "zendframework/zend-i18n": "~2.5", - "zendframework/zend-loader": "~2.5", - "zendframework/zend-servicemanager": "~2.5", - "zendframework/zend-uri": "~2.5" - }, - "suggest": { - "zendframework/zend-crypt": "Zend\\Crypt component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-uri": "Zend\\Uri component for UriNormalize filter" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev", - "dev-develop": "2.6-dev" - } - }, - "autoload": { - "psr-4": { - "Zend\\Filter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "provides a set of commonly needed data filters", - "homepage": "https://github.com/zendframework/zend-filter", - "keywords": [ - "filter", - "zf2" - ], - "time": "2015-06-03T15:32:01+00:00" - }, - { - "name": "zendframework/zend-i18n", - "version": "2.5.1", - "source": { - "type": "git", - "url": "https://github.com/zendframework/zend-i18n.git", - "reference": "509271eb7947e4aabebfc376104179cffea42696" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/509271eb7947e4aabebfc376104179cffea42696", - "reference": "509271eb7947e4aabebfc376104179cffea42696", - "shasum": "" - }, - "require": { - "php": ">=5.3.23", - "zendframework/zend-stdlib": "~2.5" - }, - "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "zendframework/zend-cache": "~2.5", - "zendframework/zend-config": "~2.5", - "zendframework/zend-eventmanager": "~2.5", - "zendframework/zend-filter": "~2.5", - "zendframework/zend-servicemanager": "~2.5", - "zendframework/zend-validator": "~2.5", - "zendframework/zend-view": "~2.5" - }, - "suggest": { - "ext-intl": "Required for most features of Zend\\I18n; included in default builds of PHP", - "zendframework/zend-cache": "Zend\\Cache component", - "zendframework/zend-config": "Zend\\Config component", - "zendframework/zend-eventmanager": "You should install this package to use the events in the translator", - "zendframework/zend-filter": "You should install this package to use the provided filters", - "zendframework/zend-resources": "Translation resources", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-validator": "You should install this package to use the provided validators", - "zendframework/zend-view": "You should install this package to use the provided view helpers" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev", - "dev-develop": "2.6-dev" - } - }, - "autoload": { - "psr-4": { - "Zend\\I18n\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "homepage": "https://github.com/zendframework/zend-i18n", - "keywords": [ - "i18n", - "zf2" - ], - "time": "2015-06-03T15:32:01+00:00" - }, - { - "name": "zendframework/zend-json", - "version": "2.5.1", - "source": { - "type": "git", - "url": "https://github.com/zendframework/zend-json.git", - "reference": "c74eaf17d2dd37dc1e964be8dfde05706a821ebc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-json/zipball/c74eaf17d2dd37dc1e964be8dfde05706a821ebc", - "reference": "c74eaf17d2dd37dc1e964be8dfde05706a821ebc", - "shasum": "" - }, - "require": { - "php": ">=5.3.23", - "zendframework/zend-stdlib": "~2.5" - }, - "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "zendframework/zend-http": "~2.5", - "zendframework/zend-server": "~2.5", - "zendframework/zendxml": "~1.0" - }, - "suggest": { - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-server": "Zend\\Server component", - "zendframework/zendxml": "To support Zend\\Json\\Json::fromXml() usage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev", - "dev-develop": "2.6-dev" - } - }, - "autoload": { - "psr-4": { - "Zend\\Json\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP", - "homepage": "https://github.com/zendframework/zend-json", - "keywords": [ - "json", - "zf2" - ], - "time": "2015-06-03T15:32:01+00:00" - }, - { - "name": "zendframework/zend-math", - "version": "2.5.1", - "source": { - "type": "git", - "url": "https://github.com/zendframework/zend-math.git", - "reference": "9f02a1ac4d3374d3332c80f9215deec9c71558fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-math/zipball/9f02a1ac4d3374d3332c80f9215deec9c71558fc", - "reference": "9f02a1ac4d3374d3332c80f9215deec9c71558fc", - "shasum": "" - }, - "require": { - "php": ">=5.3.23" - }, - "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "ircmaxell/random-lib": "~1.1", - "phpunit/phpunit": "~4.0", - "zendframework/zend-servicemanager": "~2.5" - }, - "suggest": { - "ext-bcmath": "If using the bcmath functionality", - "ext-gmp": "If using the gmp functionality", - "ircmaxell/random-lib": "Fallback random byte generator for Zend\\Math\\Rand if OpenSSL/Mcrypt extensions are unavailable", - "zendframework/zend-servicemanager": ">= current version, if using the BigInteger::factory functionality" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev", - "dev-develop": "2.6-dev" - } - }, - "autoload": { - "psr-4": { - "Zend\\Math\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "homepage": "https://github.com/zendframework/zend-math", - "keywords": [ - "math", - "zf2" - ], - "time": "2015-06-03T15:32:02+00:00" - }, - { - "name": "zendframework/zend-serializer", - "version": "2.5.1", - "source": { - "type": "git", - "url": "https://github.com/zendframework/zend-serializer.git", - "reference": "b7208eb17dc4a4fb3a660b85e6c4af035eeed40c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-serializer/zipball/b7208eb17dc4a4fb3a660b85e6c4af035eeed40c", - "reference": "b7208eb17dc4a4fb3a660b85e6c4af035eeed40c", - "shasum": "" - }, - "require": { - "php": ">=5.3.23", - "zendframework/zend-json": "~2.5", - "zendframework/zend-math": "~2.5", - "zendframework/zend-stdlib": "~2.5" - }, - "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "zendframework/zend-servicemanager": "~2.5" - }, - "suggest": { - "zendframework/zend-servicemanager": "To support plugin manager support" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev", - "dev-develop": "2.6-dev" - } - }, - "autoload": { - "psr-4": { - "Zend\\Serializer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "provides an adapter based interface to simply generate storable representation of PHP types by different facilities, and recover", - "homepage": "https://github.com/zendframework/zend-serializer", - "keywords": [ - "serializer", - "zf2" - ], - "time": "2015-06-03T15:32:02+00:00" - }, - { - "name": "zendframework/zend-servicemanager", - "version": "2.5.1", - "source": { - "type": "git", - "url": "https://github.com/zendframework/zend-servicemanager.git", - "reference": "3b22c403e351d92526c642cba0bd810bc22e1c56" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/3b22c403e351d92526c642cba0bd810bc22e1c56", - "reference": "3b22c403e351d92526c642cba0bd810bc22e1c56", - "shasum": "" - }, - "require": { - "php": ">=5.3.23" - }, - "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "zendframework/zend-di": "~2.5", - "zendframework/zend-mvc": "~2.5" - }, - "suggest": { - "ocramius/proxy-manager": "ProxyManager 0.5.* to handle lazy initialization of services", - "zendframework/zend-di": "Zend\\Di component" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev", - "dev-develop": "2.6-dev" - } - }, - "autoload": { - "psr-4": { - "Zend\\ServiceManager\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "homepage": "https://github.com/zendframework/zend-servicemanager", - "keywords": [ - "servicemanager", - "zf2" - ], - "time": "2015-06-03T15:32:02+00:00" - }, - { - "name": "zendframework/zend-stdlib", - "version": "2.5.1", - "source": { - "type": "git", - "url": "https://github.com/zendframework/zend-stdlib.git", - "reference": "cc8e90a60dd5d44b9730b77d07b97550091da1ae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/cc8e90a60dd5d44b9730b77d07b97550091da1ae", - "reference": "cc8e90a60dd5d44b9730b77d07b97550091da1ae", - "shasum": "" - }, - "require": { - "php": ">=5.3.23" - }, - "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "zendframework/zend-config": "~2.5", - "zendframework/zend-eventmanager": "~2.5", - "zendframework/zend-filter": "~2.5", - "zendframework/zend-inputfilter": "~2.5", - "zendframework/zend-serializer": "~2.5", - "zendframework/zend-servicemanager": "~2.5" - }, - "suggest": { - "zendframework/zend-eventmanager": "To support aggregate hydrator usage", - "zendframework/zend-filter": "To support naming strategy hydrator usage", - "zendframework/zend-serializer": "Zend\\Serializer component", - "zendframework/zend-servicemanager": "To support hydrator plugin manager usage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev", - "dev-develop": "2.6-dev" - } - }, - "autoload": { - "psr-4": { - "Zend\\Stdlib\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "homepage": "https://github.com/zendframework/zend-stdlib", - "keywords": [ - "stdlib", - "zf2" - ], - "time": "2015-06-03T15:32:03+00:00" - }, - { - "name": "zetacomponents/base", - "version": "1.9", - "source": { - "type": "git", - "url": "https://github.com/zetacomponents/Base.git", - "reference": "f20df24e8de3e48b6b69b2503f917e457281e687" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zetacomponents/Base/zipball/f20df24e8de3e48b6b69b2503f917e457281e687", - "reference": "f20df24e8de3e48b6b69b2503f917e457281e687", - "shasum": "" - }, - "require-dev": { - "zetacomponents/unit-test": "*" - }, - "type": "library", - "autoload": { - "classmap": [ - "src" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Sergey Alexeev" - }, - { - "name": "Sebastian Bergmann" - }, - { - "name": "Jan Borsodi" - }, - { - "name": "Raymond Bosman" - }, - { - "name": "Frederik Holljen" - }, - { - "name": "Kore Nordmann" - }, - { - "name": "Derick Rethans" - }, - { - "name": "Vadym Savchuk" - }, - { - "name": "Tobias Schlitt" - }, - { - "name": "Alexandru Stanoi" - } - ], - "description": "The Base package provides the basic infrastructure that all packages rely on. Therefore every component relies on this package.", - "homepage": "https://github.com/zetacomponents", - "time": "2014-09-19T03:28:34+00:00" - }, - { - "name": "zetacomponents/document", - "version": "1.3.1", - "source": { - "type": "git", - "url": "https://github.com/zetacomponents/Document.git", - "reference": "688abfde573cf3fe0730f82538fbd7aa9fc95bc8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zetacomponents/Document/zipball/688abfde573cf3fe0730f82538fbd7aa9fc95bc8", - "reference": "688abfde573cf3fe0730f82538fbd7aa9fc95bc8", - "shasum": "" - }, - "require": { - "zetacomponents/base": "*" - }, - "require-dev": { - "zetacomponents/unit-test": "dev-master" - }, - "type": "library", - "autoload": { - "classmap": [ - "src" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Sebastian Bergmann" - }, - { - "name": "Kore Nordmann" - }, - { - "name": "Derick Rethans" - }, - { - "name": "Tobias Schlitt" - }, - { - "name": "Alexandru Stanoi" - } - ], - "description": "The Document components provides a general conversion framework for different semantic document markup languages like XHTML, Docbook, RST and similar.", - "homepage": "https://github.com/zetacomponents", - "time": "2013-12-19T11:40:00+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=5.0.0" - }, - "platform-dev": [] -} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/DKIM.phps b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/DKIM.phps deleted file mode 100644 index e3d2bae02a..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/DKIM.phps +++ /dev/null @@ -1,38 +0,0 @@ -setFrom('from@example.com', 'First Last'); -//Set an alternative reply-to address -$mail->addReplyTo('replyto@example.com', 'First Last'); -//Set who the message is to be sent to -$mail->addAddress('whoto@example.com', 'John Doe'); -//Set the subject line -$mail->Subject = 'PHPMailer DKIM test'; -//This should be the same as the domain of your From address -$mail->DKIM_domain = 'example.com'; -//Path to your private key file -$mail->DKIM_private = 'dkim_private.pem'; -//Set this to your own selector -$mail->DKIM_selector = 'phpmailer'; -//If your private key has a passphrase, set it here -$mail->DKIM_passphrase = ''; -//The identity you're signing as - usually your From address -$mail->DKIM_identity = $mail->From; - -//send the message, check for errors -if (!$mail->send()) { - echo "Mailer Error: " . $mail->ErrorInfo; -} else { - echo "Message sent!"; -} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/code_generator.phps b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/code_generator.phps deleted file mode 100644 index 2182663d6b..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/code_generator.phps +++ /dev/null @@ -1,604 +0,0 @@ -CharSet = 'utf-8'; -ini_set('default_charset', 'UTF-8'); -$mail->Debugoutput = $CFG['smtp_debugoutput']; -$example_code .= "\n\n\$mail = new PHPMailer(true);"; -$example_code .= "\n\$mail->CharSet = 'utf-8';"; -$example_code .= "\nini_set('default_charset', 'UTF-8');"; - -class phpmailerAppException extends phpmailerException -{ -} - -$example_code .= "\n\nclass phpmailerAppException extends phpmailerException {}"; -$example_code .= "\n\ntry {"; - -// Convert a string to its JavaScript representation. -function JSString($s) { - static $from = array("\\", "/", "\n", "\t", "\r", "\b", "\f", '"'); - static $to = array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\\"'); - return is_null($s)? 'null': '"' . str_replace($from, $to, "$s") . '"'; -} - -try { - if (isset($_POST["submit"]) && $_POST['submit'] == "Submit") { - $to = $to_email; - if (!PHPMailer::validateAddress($to)) { - throw new phpmailerAppException("Email address " . $to . " is invalid -- aborting!"); - } - - $example_code .= "\n\$to = '" . addslashes($to_email) . "';"; - $example_code .= "\nif(!PHPMailer::validateAddress(\$to)) {"; - $example_code .= "\n throw new phpmailerAppException(\"Email address \" . " . - "\$to . \" is invalid -- aborting!\");"; - $example_code .= "\n}"; - - switch ($test_type) { - case 'smtp': - $mail->isSMTP(); // telling the class to use SMTP - $mail->SMTPDebug = (integer)$smtp_debug; - $mail->Host = $smtp_server; // SMTP server - $mail->Port = (integer)$smtp_port; // set the SMTP port - if ($smtp_secure) { - $mail->SMTPSecure = strtolower($smtp_secure); - } - $mail->SMTPAuth = array_key_exists('smtp_authenticate', $_POST); // enable SMTP authentication? - if (array_key_exists('smtp_authenticate', $_POST)) { - $mail->Username = $authenticate_username; // SMTP account username - $mail->Password = $authenticate_password; // SMTP account password - } - - $example_code .= "\n\$mail->isSMTP();"; - $example_code .= "\n\$mail->SMTPDebug = " . (integer) $smtp_debug . ";"; - $example_code .= "\n\$mail->Host = \"" . addslashes($smtp_server) . "\";"; - $example_code .= "\n\$mail->Port = \"" . addslashes($smtp_port) . "\";"; - $example_code .= "\n\$mail->SMTPSecure = \"" . addslashes(strtolower($smtp_secure)) . "\";"; - $example_code .= "\n\$mail->SMTPAuth = " . (array_key_exists( - 'smtp_authenticate', - $_POST - ) ? 'true' : 'false') . ";"; - if (array_key_exists('smtp_authenticate', $_POST)) { - $example_code .= "\n\$mail->Username = \"" . addslashes($authenticate_username) . "\";"; - $example_code .= "\n\$mail->Password = \"" . addslashes($authenticate_password) . "\";"; - } - break; - case 'mail': - $mail->isMail(); // telling the class to use PHP's mail() - $example_code .= "\n\$mail->isMail();"; - break; - case 'sendmail': - $mail->isSendmail(); // telling the class to use Sendmail - $example_code .= "\n\$mail->isSendmail();"; - break; - case 'qmail': - $mail->isQmail(); // telling the class to use Qmail - $example_code .= "\n\$mail->isQmail();"; - break; - default: - throw new phpmailerAppException('Invalid test_type provided'); - } - - try { - if ($_POST['From_Name'] != '') { - $mail->addReplyTo($from_email, $from_name); - $mail->setFrom($from_email, $from_name); - - $example_code .= "\n\$mail->addReplyTo(\"" . - addslashes($from_email) . "\", \"" . addslashes($from_name) . "\");"; - $example_code .= "\n\$mail->setFrom(\"" . - addslashes($from_email) . "\", \"" . addslashes($from_name) . "\");"; - } else { - $mail->addReplyTo($from_email); - $mail->setFrom($from_email, $from_email); - - $example_code .= "\n\$mail->addReplyTo(\"" . addslashes($from_email) . "\");"; - $example_code .= "\n\$mail->setFrom(\"" . - addslashes($from_email) . "\", \"" . addslashes($from_email) . "\");"; - } - - if ($_POST['To_Name'] != '') { - $mail->addAddress($to, $to_name); - $example_code .= "\n\$mail->addAddress(\"$to\", \"" . addslashes($to_name) . "\");"; - } else { - $mail->addAddress($to); - $example_code .= "\n\$mail->addAddress(\"$to\");"; - } - - if ($_POST['bcc_Email'] != '') { - $indiBCC = explode(" ", $bcc_email); - foreach ($indiBCC as $key => $value) { - $mail->addBCC($value); - $example_code .= "\n\$mail->addBCC(\"" . addslashes($value) . "\");"; - } - } - - if ($_POST['cc_Email'] != '') { - $indiCC = explode(" ", $cc_Email); - foreach ($indiCC as $key => $value) { - $mail->addCC($value); - $example_code .= "\n\$mail->addCC(\"" . addslashes($value) . "\");"; - } - } - } catch (phpmailerException $e) { //Catch all kinds of bad addressing - throw new phpmailerAppException($e->getMessage()); - } - $mail->Subject = $subject . ' (PHPMailer test using ' . strtoupper($test_type) . ')'; - $example_code .= "\n\$mail->Subject = \"" . addslashes($subject) . - ' (PHPMailer test using ' . addslashes(strtoupper($test_type)) . ')";'; - - if ($_POST['Message'] == '') { - $body = file_get_contents('contents.html'); - } else { - $body = $message; - } - - $example_code .= "\n\$body = <<<'EOT'\n$body\nEOT;"; - - $mail->WordWrap = 78; // set word wrap to the RFC2822 limit - $mail->msgHTML($body, dirname(__FILE__), true); //Create message bodies and embed images - - $example_code .= "\n\$mail->WordWrap = 78;"; - $example_code .= "\n\$mail->msgHTML(\$body, dirname(__FILE__), true); //Create message bodies and embed images"; - - $mail->addAttachment('images/phpmailer_mini.png', 'phpmailer_mini.png'); // optional name - $mail->addAttachment('images/phpmailer.png', 'phpmailer.png'); // optional name - $example_code .= "\n\$mail->addAttachment('images/phpmailer_mini.png'," . - "'phpmailer_mini.png'); // optional name"; - $example_code .= "\n\$mail->addAttachment('images/phpmailer.png', 'phpmailer.png'); // optional name"; - - $example_code .= "\n\ntry {"; - $example_code .= "\n \$mail->send();"; - $example_code .= "\n \$results_messages[] = \"Message has been sent using " . - addslashes(strtoupper($test_type)) . "\";"; - $example_code .= "\n}"; - $example_code .= "\ncatch (phpmailerException \$e) {"; - $example_code .= "\n throw new phpmailerAppException('Unable to send to: ' . \$to. ': '.\$e->getMessage());"; - $example_code .= "\n}"; - - try { - $mail->send(); - $results_messages[] = "Message has been sent using " . strtoupper($test_type); - } catch (phpmailerException $e) { - throw new phpmailerAppException("Unable to send to: " . $to . ': ' . $e->getMessage()); - } - } -} catch (phpmailerAppException $e) { - $results_messages[] = $e->errorMessage(); -} -$example_code .= "\n}"; -$example_code .= "\ncatch (phpmailerAppException \$e) {"; -$example_code .= "\n \$results_messages[] = \$e->errorMessage();"; -$example_code .= "\n}"; -$example_code .= "\n\nif (count(\$results_messages) > 0) {"; -$example_code .= "\n echo \"

    Run results

    \\n\";"; -$example_code .= "\n echo \"
      \\n\";"; -$example_code .= "\nforeach (\$results_messages as \$result) {"; -$example_code .= "\n echo \"
    • \$result
    • \\n\";"; -$example_code .= "\n}"; -$example_code .= "\necho \"
    \\n\";"; -$example_code .= "\n}"; -?> - - - - PHPMailer Test Page - - - - - - - - -"; - echo exit("ERROR: Wrong PHP version. Must be PHP 5 or above."); -} - -if (count($results_messages) > 0) { - echo '

    Run results

    '; - echo '
      '; - foreach ($results_messages as $result) { - echo "
    • $result
    • "; - } - echo '
    '; -} - -if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") { - echo "
    \n"; - echo "
    Script:\n"; - echo "
    \n";
    -    echo htmlentities($example_code);
    -    echo "\n
    \n"; - echo "\n
    \n"; -} -?> -
    -
    -
    -
    - Mail Details - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -
    - - - -
    - - - -
    - - - -
    - - - -
    - - - -
    - - - -
    - - - -
    -
    Test will include two attachments.
    -
    -
    -
    -
    - Mail Test Specs - - - - - -
    Test Type -
    - - - required> -
    -
    - - - required> -
    -
    - - - required> -
    -
    - - - required> -
    -
    -
    "> - SMTP Specific Options: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    - -
    - - value="true"> -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    -
    - - diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/contactform.phps b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/contactform.phps deleted file mode 100644 index d85e204561..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/contactform.phps +++ /dev/null @@ -1,71 +0,0 @@ -isSMTP(); - $mail->Host = 'localhost'; - $mail->Port = 25; - - //Use a fixed address in your own domain as the from address - //**DO NOT** use the submitter's address here as it will be forgery - //and will cause your messages to fail SPF checks - $mail->setFrom('from@example.com', 'First Last'); - //Send the message to yourself, or whoever should receive contact for submissions - $mail->addAddress('whoto@example.com', 'John Doe'); - //Put the submitter's address in a reply-to header - //This will fail if the address provided is invalid, - //in which case we should ignore the whole request - if ($mail->addReplyTo($_POST['email'], $_POST['name'])) { - $mail->Subject = 'PHPMailer contact form'; - //Keep it simple - don't use HTML - $mail->isHTML(false); - //Build a simple message body - $mail->Body = <<send()) { - //The reason for failing to send will be in $mail->ErrorInfo - //but you shouldn't display errors to users - process the error, log it on your server. - $msg = 'Sorry, something went wrong. Please try again later.'; - } else { - $msg = 'Message sent! Thanks for contacting us.'; - } - } else { - $msg = 'Invalid email address, message ignored.'; - } -} -?> - - - - - Contact form - - -

    Contact us

    -$msg"; -} ?> -
    -
    -
    -
    - -
    - - diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/contents.html b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/contents.html deleted file mode 100644 index dc3fc6676c..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/contents.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - PHPMailer Test - - -
    -

    This is a test of PHPMailer.

    -
    - PHPMailer rocks -
    -

    This example uses HTML.

    -

    ISO-8859-1 text: ้่๎็ล๑ๆ฿

    -
    - - diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/contentsutf8.html b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/contentsutf8.html deleted file mode 100644 index 035d10c8db..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/contentsutf8.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - PHPMailer Test - - -
    -

    This is a test of PHPMailer.

    -
    - PHPMailer rocks -
    -

    This example uses HTML.

    -

    Chinese text: ้ƒตไปถๅ…งๅฎน็‚บ็ฉบ

    -

    Russian text: ะŸัƒัั‚ะพะต ั‚ะตะปะพ ัะพะพะฑั‰ะตะฝะธั

    -

    Armenian text: ี€ีกีฒีธึ€ีคีกีฃึ€ีธึ‚ีฉีตีธึ‚ีถีจ ีคีกีฟีกึ€ีฏ ีง

    -

    Czech text: Prรกzdnรฉ tฤ›lo zprรกvy

    -

    Emoji: ๐Ÿ˜‚ ๐Ÿฆ„ ๐Ÿ’ฅ ๐Ÿ“ค ๐Ÿ“ง

    -
    - - diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/exceptions.phps b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/exceptions.phps deleted file mode 100644 index 0e941e7336..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/exceptions.phps +++ /dev/null @@ -1,35 +0,0 @@ -setFrom('from@example.com', 'First Last'); - //Set an alternative reply-to address - $mail->addReplyTo('replyto@example.com', 'First Last'); - //Set who the message is to be sent to - $mail->addAddress('whoto@example.com', 'John Doe'); - //Set the subject line - $mail->Subject = 'PHPMailer Exceptions test'; - //Read an HTML message body from an external file, convert referenced images to embedded, - //and convert the HTML into a basic plain-text alternative body - $mail->msgHTML(file_get_contents('contents.html'), dirname(__FILE__)); - //Replace the plain text body with one created manually - $mail->AltBody = 'This is a plain-text message body'; - //Attach an image file - $mail->addAttachment('images/phpmailer_mini.png'); - //send the message - //Note that we don't need check the response from this because it will throw an exception if it has trouble - $mail->send(); - echo "Message sent!"; -} catch (phpmailerException $e) { - echo $e->errorMessage(); //Pretty error messages from PHPMailer -} catch (Exception $e) { - echo $e->getMessage(); //Boring error messages from anything else! -} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/gmail.phps b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/gmail.phps deleted file mode 100644 index 121ca70a27..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/gmail.phps +++ /dev/null @@ -1,99 +0,0 @@ -isSMTP(); - -//Enable SMTP debugging -// 0 = off (for production use) -// 1 = client messages -// 2 = client and server messages -$mail->SMTPDebug = 2; - -//Ask for HTML-friendly debug output -$mail->Debugoutput = 'html'; - -//Set the hostname of the mail server -$mail->Host = 'smtp.gmail.com'; -// use -// $mail->Host = gethostbyname('smtp.gmail.com'); -// if your network does not support SMTP over IPv6 - -//Set the SMTP port number - 587 for authenticated TLS, a.k.a. RFC4409 SMTP submission -$mail->Port = 587; - -//Set the encryption system to use - ssl (deprecated) or tls -$mail->SMTPSecure = 'tls'; - -//Whether to use SMTP authentication -$mail->SMTPAuth = true; - -//Username to use for SMTP authentication - use full email address for gmail -$mail->Username = "username@gmail.com"; - -//Password to use for SMTP authentication -$mail->Password = "yourpassword"; - -//Set who the message is to be sent from -$mail->setFrom('from@example.com', 'First Last'); - -//Set an alternative reply-to address -$mail->addReplyTo('replyto@example.com', 'First Last'); - -//Set who the message is to be sent to -$mail->addAddress('whoto@example.com', 'John Doe'); - -//Set the subject line -$mail->Subject = 'PHPMailer GMail SMTP test'; - -//Read an HTML message body from an external file, convert referenced images to embedded, -//convert HTML into a basic plain-text alternative body -$mail->msgHTML(file_get_contents('contents.html'), dirname(__FILE__)); - -//Replace the plain text body with one created manually -$mail->AltBody = 'This is a plain-text message body'; - -//Attach an image file -$mail->addAttachment('images/phpmailer_mini.png'); - -//send the message, check for errors -if (!$mail->send()) { - echo "Mailer Error: " . $mail->ErrorInfo; -} else { - echo "Message sent!"; - //Section 2: IMAP - //Uncomment these to save your message in the 'Sent Mail' folder. - #if (save_mail($mail)) { - # echo "Message saved!"; - #} -} - -//Section 2: IMAP -//IMAP commands requires the PHP IMAP Extension, found at: https://php.net/manual/en/imap.setup.php -//Function to call which uses the PHP imap_*() functions to save messages: https://php.net/manual/en/book.imap.php -//You can use imap_getmailboxes($imapStream, '/imap/ssl') to get a list of available folders or labels, this can -//be useful if you are trying to get this working on a non-Gmail IMAP server. -function save_mail($mail) { - //You can change 'Sent Mail' to any other folder or tag - $path = "{imap.gmail.com:993/imap/ssl}[Gmail]/Sent Mail"; - - //Tell your server to open an IMAP connection using the same username and password as you used for SMTP - $imapStream = imap_open($path, $mail->Username, $mail->Password); - - $result = imap_append($imapStream, $path, $mail->getSentMIMEMessage()); - imap_close($imapStream); - - return $result; -} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/gmail_xoauth.phps b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/gmail_xoauth.phps deleted file mode 100644 index 2aec1814d8..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/gmail_xoauth.phps +++ /dev/null @@ -1,85 +0,0 @@ -isSMTP(); - -//Enable SMTP debugging -// 0 = off (for production use) -// 1 = client messages -// 2 = client and server messages -$mail->SMTPDebug = 0; - -//Ask for HTML-friendly debug output -$mail->Debugoutput = 'html'; - -//Set the hostname of the mail server -$mail->Host = 'smtp.gmail.com'; - -//Set the SMTP port number - 587 for authenticated TLS, a.k.a. RFC4409 SMTP submission -$mail->Port = 587; - -//Set the encryption system to use - ssl (deprecated) or tls -$mail->SMTPSecure = 'tls'; - -//Whether to use SMTP authentication -$mail->SMTPAuth = true; - -//Set AuthType -$mail->AuthType = 'XOAUTH2'; - -//User Email to use for SMTP authentication - user who gave consent to our app -$mail->oauthUserEmail = "from@gmail.com"; - -//Obtained From Google Developer Console -$mail->oauthClientId = "RANDOMCHARS-----duv1n2.apps.googleusercontent.com"; - -//Obtained From Google Developer Console -$mail->oauthClientSecret = "RANDOMCHARS-----lGyjPcRtvP"; - -//Obtained By running get_oauth_token.php after setting up APP in Google Developer Console. -//Set Redirect URI in Developer Console as [https/http]:////get_oauth_token.php -// eg: http://localhost/phpmail/get_oauth_token.php -$mail->oauthRefreshToken = "RANDOMCHARS-----DWxgOvPT003r-yFUV49TQYag7_Aod7y0"; - -//Set who the message is to be sent from -//For gmail, this generally needs to be the same as the user you logged in as -$mail->setFrom('from@example.com', 'First Last'); - -//Set who the message is to be sent to -$mail->addAddress('whoto@example.com', 'John Doe'); - -//Set the subject line -$mail->Subject = 'PHPMailer GMail SMTP test'; - -//Read an HTML message body from an external file, convert referenced images to embedded, -//convert HTML into a basic plain-text alternative body -$mail->msgHTML(file_get_contents('contents.html'), dirname(__FILE__)); - -//Replace the plain text body with one created manually -$mail->AltBody = 'This is a plain-text message body'; - -//Attach an image file -$mail->addAttachment('images/phpmailer_mini.png'); - -//send the message, check for errors -if (!$mail->send()) { - echo "Mailer Error: " . $mail->ErrorInfo; -} else { - echo "Message sent!"; -} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/images/phpmailer.png b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/images/phpmailer.png deleted file mode 100644 index 9bdd83c8de..0000000000 Binary files a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/images/phpmailer.png and /dev/null differ diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/images/phpmailer_mini.png b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/images/phpmailer_mini.png deleted file mode 100644 index e6915f4317..0000000000 Binary files a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/images/phpmailer_mini.png and /dev/null differ diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/index.html b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/index.html deleted file mode 100644 index bbb830d19b..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/index.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - PHPMailer Examples - - -

    PHPMailer code examplesPHPMailer logo

    -

    This folder contains a collection of examples of using PHPMailer.

    -

    About testing email sending

    -

    When working on email sending code you'll find yourself worrying about what might happen if all these test emails got sent to your mailing list. The solution is to use a fake mail server, one that acts just like the real thing, but just doesn't actually send anything out. Some offer web interfaces, feedback, logging, the ability to return specific error codes, all things that are useful for testing error handling, authentication etc. Here's a selection of mail testing tools you might like to try:

    -
      -
    • FakeSMTP, a Java desktop app with the ability to show an SMTP log and save messages to a folder.
    • -
    • FakeEmail, a Python-based fake mail server with a web interface.
    • -
    • smtp-sink, part of the Postfix mail server, so you probably already have this installed. This is used in the Travis-CI configuration to run PHPMailer's unit tests.
    • -
    • smtp4dev, a dummy SMTP server for Windows.
    • -
    • fakesendmail.sh, part of PHPMailer's test setup, this is a shell script that emulates sendmail for testing 'mail' or 'sendmail' methods in PHPMailer.
    • -
    • msglint, not a mail server, the IETF's MIME structure analyser checks the formatting of your messages.
    • -
    -
    -

    Security note

    -

    Before running these examples you'll need to rename them with '.php' extensions. They are supplied as '.phps' files which will usually be displayed with syntax highlighting by PHP instead of running them. This prevents potential security issues with running potential spam-gateway code if you happen to deploy these code examples on a live site - please don't do that! Similarly, don't leave your passwords in these files as they will be visible to the world!

    -
    -

    code_generator.phps

    -

    This script is a simple code generator - fill in the form and hit submit, and it will use when you entered to email you a message, and will also generate PHP code using your settings that you can copy and paste to use in your own apps. If you need to get going quickly, this is probably the best place to start.

    -

    mail.phps

    -

    This script is a basic example which creates an email message from an external HTML file, creates a plain text body, sets various addresses, adds an attachment and sends the message. It uses PHP's built-in mail() function which is the simplest to use, but relies on the presence of a local mail server, something which is not usually available on Windows. If you find yourself in that situation, either install a local mail server, or use a remote one and send using SMTP instead.

    -

    exceptions.phps

    -

    The same as the mail example, but shows how to use PHPMailer's optional exceptions for error handling.

    -

    smtp.phps

    -

    A simple example sending using SMTP with authentication.

    -

    smtp_no_auth.phps

    -

    A simple example sending using SMTP without authentication.

    -

    sendmail.phps

    -

    A simple example using sendmail. Sendmail is a program (usually found on Linux/BSD, OS X and other UNIX-alikes) that can be used to submit messages to a local mail server without a lengthy SMTP conversation. It's probably the fastest sending mechanism, but lacks some error reporting features. There are sendmail emulators for most popular mail servers including postfix, qmail, exim etc.

    -

    gmail.phps

    -

    Submitting email via Google's Gmail service is a popular use of PHPMailer. It's much the same as normal SMTP sending, just with some specific settings, namely using TLS encryption, authentication is enabled, and it connects to the SMTP submission port 587 on the smtp.gmail.com host. This example does all that.

    -

    pop_before_smtp.phps

    -

    Before effective SMTP authentication mechanisms were available, it was common for ISPs to use POP-before-SMTP authentication. As it implies, you authenticate using the POP3 protocol (an older protocol now mostly replaced by the far superior IMAP), and then the SMTP server will allow send access from your IP address for a short while, usually 5-15 minutes. PHPMailer includes a POP3 protocol client, so it can carry out this sequence - it's just like a normal SMTP conversation (without authentication), but connects via POP first.

    -

    mailing_list.phps

    -

    This is a somewhat naรฏve example of sending similar emails to a list of different addresses. It sets up a PHPMailer instance using SMTP, then connects to a MySQL database to retrieve a list of recipients. The code loops over this list, sending email to each person using their info and marks them as sent in the database. It makes use of SMTP keepalive which saves reconnecting and re-authenticating between each message.

    -
    -

    smtp_check.phps

    -

    This is an example showing how to use the SMTP class by itself (without PHPMailer) to check an SMTP connection.

    -
    -

    Most of these examples use the 'example.com' domain. This domain is reserved by IANA for illustrative purposes, as documented in RFC 2606. Don't use made-up domains like 'mydomain.com' or 'somedomain.com' in examples as someone, somewhere, probably owns them!

    - - diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/mail.phps b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/mail.phps deleted file mode 100644 index 8e129f47dd..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/mail.phps +++ /dev/null @@ -1,31 +0,0 @@ -setFrom('from@example.com', 'First Last'); -//Set an alternative reply-to address -$mail->addReplyTo('replyto@example.com', 'First Last'); -//Set who the message is to be sent to -$mail->addAddress('whoto@example.com', 'John Doe'); -//Set the subject line -$mail->Subject = 'PHPMailer mail() test'; -//Read an HTML message body from an external file, convert referenced images to embedded, -//convert HTML into a basic plain-text alternative body -$mail->msgHTML(file_get_contents('contents.html'), dirname(__FILE__)); -//Replace the plain text body with one created manually -$mail->AltBody = 'This is a plain-text message body'; -//Attach an image file -$mail->addAttachment('images/phpmailer_mini.png'); - -//send the message, check for errors -if (!$mail->send()) { - echo "Mailer Error: " . $mail->ErrorInfo; -} else { - echo "Message sent!"; -} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/mailing_list.phps b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/mailing_list.phps deleted file mode 100644 index 8644bb5968..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/mailing_list.phps +++ /dev/null @@ -1,59 +0,0 @@ -isSMTP(); -$mail->Host = 'smtp.example.com'; -$mail->SMTPAuth = true; -$mail->SMTPKeepAlive = true; // SMTP connection will not close after each email sent, reduces SMTP overhead -$mail->Port = 25; -$mail->Username = 'yourname@example.com'; -$mail->Password = 'yourpassword'; -$mail->setFrom('list@example.com', 'List manager'); -$mail->addReplyTo('list@example.com', 'List manager'); - -$mail->Subject = "PHPMailer Simple database mailing list test"; - -//Same body for all messages, so set this before the sending loop -//If you generate a different body for each recipient (e.g. you're using a templating system), -//set it inside the loop -$mail->msgHTML($body); -//msgHTML also sets AltBody, but if you want a custom one, set it afterwards -$mail->AltBody = 'To view the message, please use an HTML compatible email viewer!'; - -//Connect to the database and select the recipients from your mailing list that have not yet been sent to -//You'll need to alter this to match your database -$mysql = mysqli_connect('localhost', 'username', 'password'); -mysqli_select_db($mysql, 'mydb'); -$result = mysqli_query($mysql, 'SELECT full_name, email, photo FROM mailinglist WHERE sent = false'); - -foreach ($result as $row) { //This iterator syntax only works in PHP 5.4+ - $mail->addAddress($row['email'], $row['full_name']); - if (!empty($row['photo'])) { - $mail->addStringAttachment($row['photo'], 'YourPhoto.jpg'); //Assumes the image data is stored in the DB - } - - if (!$mail->send()) { - echo "Mailer Error (" . str_replace("@", "@", $row["email"]) . ') ' . $mail->ErrorInfo . '
    '; - break; //Abandon sending - } else { - echo "Message sent to :" . $row['full_name'] . ' (' . str_replace("@", "@", $row['email']) . ')
    '; - //Mark it as sent in the DB - mysqli_query( - $mysql, - "UPDATE mailinglist SET sent = true WHERE email = '" . - mysqli_real_escape_string($mysql, $row['email']) . "'" - ); - } - // Clear all addresses and attachments for next loop - $mail->clearAddresses(); - $mail->clearAttachments(); -} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/pop_before_smtp.phps b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/pop_before_smtp.phps deleted file mode 100644 index 164dfe8dd8..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/pop_before_smtp.phps +++ /dev/null @@ -1,54 +0,0 @@ -isSMTP(); - //Enable SMTP debugging - // 0 = off (for production use) - // 1 = client messages - // 2 = client and server messages - $mail->SMTPDebug = 2; - //Ask for HTML-friendly debug output - $mail->Debugoutput = 'html'; - //Set the hostname of the mail server - $mail->Host = "mail.example.com"; - //Set the SMTP port number - likely to be 25, 465 or 587 - $mail->Port = 25; - //Whether to use SMTP authentication - $mail->SMTPAuth = false; - //Set who the message is to be sent from - $mail->setFrom('from@example.com', 'First Last'); - //Set an alternative reply-to address - $mail->addReplyTo('replyto@example.com', 'First Last'); - //Set who the message is to be sent to - $mail->addAddress('whoto@example.com', 'John Doe'); - //Set the subject line - $mail->Subject = 'PHPMailer POP-before-SMTP test'; - //Read an HTML message body from an external file, convert referenced images to embedded, - //and convert the HTML into a basic plain-text alternative body - $mail->msgHTML(file_get_contents('contents.html'), dirname(__FILE__)); - //Replace the plain text body with one created manually - $mail->AltBody = 'This is a plain-text message body'; - //Attach an image file - $mail->addAttachment('images/phpmailer_mini.png'); - //send the message - //Note that we don't need check the response from this because it will throw an exception if it has trouble - $mail->send(); - echo "Message sent!"; -} catch (phpmailerException $e) { - echo $e->errorMessage(); //Pretty error messages from PHPMailer -} catch (Exception $e) { - echo $e->getMessage(); //Boring error messages from anything else! -} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/scripts/XRegExp.js b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/scripts/XRegExp.js deleted file mode 100644 index feb66798a8..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/scripts/XRegExp.js +++ /dev/null @@ -1,664 +0,0 @@ -// XRegExp 1.5.1 -// (c) 2007-2012 Steven Levithan -// MIT License -// -// Provides an augmented, extensible, cross-browser implementation of regular expressions, -// including support for additional syntax, flags, and methods - -var XRegExp; - -if (XRegExp) { - // Avoid running twice, since that would break references to native globals - throw Error("can't load XRegExp twice in the same frame"); -} - -// Run within an anonymous function to protect variables and avoid new globals -(function (undefined) { - - //--------------------------------- - // Constructor - //--------------------------------- - - // Accepts a pattern and flags; returns a new, extended `RegExp` object. Differs from a native - // regular expression in that additional syntax and flags are supported and cross-browser - // syntax inconsistencies are ameliorated. `XRegExp(/regex/)` clones an existing regex and - // converts to type XRegExp - XRegExp = function (pattern, flags) { - var output = [], - currScope = XRegExp.OUTSIDE_CLASS, - pos = 0, - context, tokenResult, match, chr, regex; - - if (XRegExp.isRegExp(pattern)) { - if (flags !== undefined) - throw TypeError("can't supply flags when constructing one RegExp from another"); - return clone(pattern); - } - // Tokens become part of the regex construction process, so protect against infinite - // recursion when an XRegExp is constructed within a token handler or trigger - if (isInsideConstructor) - throw Error("can't call the XRegExp constructor within token definition functions"); - - flags = flags || ""; - context = { // `this` object for custom tokens - hasNamedCapture: false, - captureNames: [], - hasFlag: function (flag) {return flags.indexOf(flag) > -1;}, - setFlag: function (flag) {flags += flag;} - }; - - while (pos < pattern.length) { - // Check for custom tokens at the current position - tokenResult = runTokens(pattern, pos, currScope, context); - - if (tokenResult) { - output.push(tokenResult.output); - pos += (tokenResult.match[0].length || 1); - } else { - // Check for native multicharacter metasequences (excluding character classes) at - // the current position - if (match = nativ.exec.call(nativeTokens[currScope], pattern.slice(pos))) { - output.push(match[0]); - pos += match[0].length; - } else { - chr = pattern.charAt(pos); - if (chr === "[") - currScope = XRegExp.INSIDE_CLASS; - else if (chr === "]") - currScope = XRegExp.OUTSIDE_CLASS; - // Advance position one character - output.push(chr); - pos++; - } - } - } - - regex = RegExp(output.join(""), nativ.replace.call(flags, flagClip, "")); - regex._xregexp = { - source: pattern, - captureNames: context.hasNamedCapture ? context.captureNames : null - }; - return regex; - }; - - - //--------------------------------- - // Public properties - //--------------------------------- - - XRegExp.version = "1.5.1"; - - // Token scope bitflags - XRegExp.INSIDE_CLASS = 1; - XRegExp.OUTSIDE_CLASS = 2; - - - //--------------------------------- - // Private variables - //--------------------------------- - - var replacementToken = /\$(?:(\d\d?|[$&`'])|{([$\w]+)})/g, - flagClip = /[^gimy]+|([\s\S])(?=[\s\S]*\1)/g, // Nonnative and duplicate flags - quantifier = /^(?:[?*+]|{\d+(?:,\d*)?})\??/, - isInsideConstructor = false, - tokens = [], - // Copy native globals for reference ("native" is an ES3 reserved keyword) - nativ = { - exec: RegExp.prototype.exec, - test: RegExp.prototype.test, - match: String.prototype.match, - replace: String.prototype.replace, - split: String.prototype.split - }, - compliantExecNpcg = nativ.exec.call(/()??/, "")[1] === undefined, // check `exec` handling of nonparticipating capturing groups - compliantLastIndexIncrement = function () { - var x = /^/g; - nativ.test.call(x, ""); - return !x.lastIndex; - }(), - hasNativeY = RegExp.prototype.sticky !== undefined, - nativeTokens = {}; - - // `nativeTokens` match native multicharacter metasequences only (including deprecated octals, - // excluding character classes) - nativeTokens[XRegExp.INSIDE_CLASS] = /^(?:\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S]))/; - nativeTokens[XRegExp.OUTSIDE_CLASS] = /^(?:\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9]\d*|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S])|\(\?[:=!]|[?*+]\?|{\d+(?:,\d*)?}\??)/; - - - //--------------------------------- - // Public methods - //--------------------------------- - - // Lets you extend or change XRegExp syntax and create custom flags. This is used internally by - // the XRegExp library and can be used to create XRegExp plugins. This function is intended for - // users with advanced knowledge of JavaScript's regular expression syntax and behavior. It can - // be disabled by `XRegExp.freezeTokens` - XRegExp.addToken = function (regex, handler, scope, trigger) { - tokens.push({ - pattern: clone(regex, "g" + (hasNativeY ? "y" : "")), - handler: handler, - scope: scope || XRegExp.OUTSIDE_CLASS, - trigger: trigger || null - }); - }; - - // Accepts a pattern and flags; returns an extended `RegExp` object. If the pattern and flag - // combination has previously been cached, the cached copy is returned; otherwise the newly - // created regex is cached - XRegExp.cache = function (pattern, flags) { - var key = pattern + "/" + (flags || ""); - return XRegExp.cache[key] || (XRegExp.cache[key] = XRegExp(pattern, flags)); - }; - - // Accepts a `RegExp` instance; returns a copy with the `/g` flag set. The copy has a fresh - // `lastIndex` (set to zero). If you want to copy a regex without forcing the `global` - // property, use `XRegExp(regex)`. Do not use `RegExp(regex)` because it will not preserve - // special properties required for named capture - XRegExp.copyAsGlobal = function (regex) { - return clone(regex, "g"); - }; - - // Accepts a string; returns the string with regex metacharacters escaped. The returned string - // can safely be used at any point within a regex to match the provided literal string. Escaped - // characters are [ ] { } ( ) * + ? - . , \ ^ $ | # and whitespace - XRegExp.escape = function (str) { - return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); - }; - - // Accepts a string to search, regex to search with, position to start the search within the - // string (default: 0), and an optional Boolean indicating whether matches must start at-or- - // after the position or at the specified position only. This function ignores the `lastIndex` - // of the provided regex in its own handling, but updates the property for compatibility - XRegExp.execAt = function (str, regex, pos, anchored) { - var r2 = clone(regex, "g" + ((anchored && hasNativeY) ? "y" : "")), - match; - r2.lastIndex = pos = pos || 0; - match = r2.exec(str); // Run the altered `exec` (required for `lastIndex` fix, etc.) - if (anchored && match && match.index !== pos) - match = null; - if (regex.global) - regex.lastIndex = match ? r2.lastIndex : 0; - return match; - }; - - // Breaks the unrestorable link to XRegExp's private list of tokens, thereby preventing - // syntax and flag changes. Should be run after XRegExp and any plugins are loaded - XRegExp.freezeTokens = function () { - XRegExp.addToken = function () { - throw Error("can't run addToken after freezeTokens"); - }; - }; - - // Accepts any value; returns a Boolean indicating whether the argument is a `RegExp` object. - // Note that this is also `true` for regex literals and regexes created by the `XRegExp` - // constructor. This works correctly for variables created in another frame, when `instanceof` - // and `constructor` checks would fail to work as intended - XRegExp.isRegExp = function (o) { - return Object.prototype.toString.call(o) === "[object RegExp]"; - }; - - // Executes `callback` once per match within `str`. Provides a simpler and cleaner way to - // iterate over regex matches compared to the traditional approaches of subverting - // `String.prototype.replace` or repeatedly calling `exec` within a `while` loop - XRegExp.iterate = function (str, regex, callback, context) { - var r2 = clone(regex, "g"), - i = -1, match; - while (match = r2.exec(str)) { // Run the altered `exec` (required for `lastIndex` fix, etc.) - if (regex.global) - regex.lastIndex = r2.lastIndex; // Doing this to follow expectations if `lastIndex` is checked within `callback` - callback.call(context, match, ++i, str, regex); - if (r2.lastIndex === match.index) - r2.lastIndex++; - } - if (regex.global) - regex.lastIndex = 0; - }; - - // Accepts a string and an array of regexes; returns the result of using each successive regex - // to search within the matches of the previous regex. The array of regexes can also contain - // objects with `regex` and `backref` properties, in which case the named or numbered back- - // references specified are passed forward to the next regex or returned. E.g.: - // var xregexpImgFileNames = XRegExp.matchChain(html, [ - // {regex: /]+)>/i, backref: 1}, // tag attributes - // {regex: XRegExp('(?ix) \\s src=" (? [^"]+ )'), backref: "src"}, // src attribute values - // {regex: XRegExp("^http://xregexp\\.com(/[^#?]+)", "i"), backref: 1}, // xregexp.com paths - // /[^\/]+$/ // filenames (strip directory paths) - // ]); - XRegExp.matchChain = function (str, chain) { - return function recurseChain (values, level) { - var item = chain[level].regex ? chain[level] : {regex: chain[level]}, - regex = clone(item.regex, "g"), - matches = [], i; - for (i = 0; i < values.length; i++) { - XRegExp.iterate(values[i], regex, function (match) { - matches.push(item.backref ? (match[item.backref] || "") : match[0]); - }); - } - return ((level === chain.length - 1) || !matches.length) ? - matches : recurseChain(matches, level + 1); - }([str], 0); - }; - - - //--------------------------------- - // New RegExp prototype methods - //--------------------------------- - - // Accepts a context object and arguments array; returns the result of calling `exec` with the - // first value in the arguments array. the context is ignored but is accepted for congruity - // with `Function.prototype.apply` - RegExp.prototype.apply = function (context, args) { - return this.exec(args[0]); - }; - - // Accepts a context object and string; returns the result of calling `exec` with the provided - // string. the context is ignored but is accepted for congruity with `Function.prototype.call` - RegExp.prototype.call = function (context, str) { - return this.exec(str); - }; - - - //--------------------------------- - // Overridden native methods - //--------------------------------- - - // Adds named capture support (with backreferences returned as `result.name`), and fixes two - // cross-browser issues per ES3: - // - Captured values for nonparticipating capturing groups should be returned as `undefined`, - // rather than the empty string. - // - `lastIndex` should not be incremented after zero-length matches. - RegExp.prototype.exec = function (str) { - var match, name, r2, origLastIndex; - if (!this.global) - origLastIndex = this.lastIndex; - match = nativ.exec.apply(this, arguments); - if (match) { - // Fix browsers whose `exec` methods don't consistently return `undefined` for - // nonparticipating capturing groups - if (!compliantExecNpcg && match.length > 1 && indexOf(match, "") > -1) { - r2 = RegExp(this.source, nativ.replace.call(getNativeFlags(this), "g", "")); - // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed - // matching due to characters outside the match - nativ.replace.call((str + "").slice(match.index), r2, function () { - for (var i = 1; i < arguments.length - 2; i++) { - if (arguments[i] === undefined) - match[i] = undefined; - } - }); - } - // Attach named capture properties - if (this._xregexp && this._xregexp.captureNames) { - for (var i = 1; i < match.length; i++) { - name = this._xregexp.captureNames[i - 1]; - if (name) - match[name] = match[i]; - } - } - // Fix browsers that increment `lastIndex` after zero-length matches - if (!compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index)) - this.lastIndex--; - } - if (!this.global) - this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows) - return match; - }; - - // Fix browser bugs in native method - RegExp.prototype.test = function (str) { - // Use the native `exec` to skip some processing overhead, even though the altered - // `exec` would take care of the `lastIndex` fixes - var match, origLastIndex; - if (!this.global) - origLastIndex = this.lastIndex; - match = nativ.exec.call(this, str); - // Fix browsers that increment `lastIndex` after zero-length matches - if (match && !compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index)) - this.lastIndex--; - if (!this.global) - this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows) - return !!match; - }; - - // Adds named capture support and fixes browser bugs in native method - String.prototype.match = function (regex) { - if (!XRegExp.isRegExp(regex)) - regex = RegExp(regex); // Native `RegExp` - if (regex.global) { - var result = nativ.match.apply(this, arguments); - regex.lastIndex = 0; // Fix IE bug - return result; - } - return regex.exec(this); // Run the altered `exec` - }; - - // Adds support for `${n}` tokens for named and numbered backreferences in replacement text, - // and provides named backreferences to replacement functions as `arguments[0].name`. Also - // fixes cross-browser differences in replacement text syntax when performing a replacement - // using a nonregex search value, and the value of replacement regexes' `lastIndex` property - // during replacement iterations. Note that this doesn't support SpiderMonkey's proprietary - // third (`flags`) parameter - String.prototype.replace = function (search, replacement) { - var isRegex = XRegExp.isRegExp(search), - captureNames, result, str, origLastIndex; - - // There are too many combinations of search/replacement types/values and browser bugs that - // preclude passing to native `replace`, so don't try - //if (...) - // return nativ.replace.apply(this, arguments); - - if (isRegex) { - if (search._xregexp) - captureNames = search._xregexp.captureNames; // Array or `null` - if (!search.global) - origLastIndex = search.lastIndex; - } else { - search = search + ""; // Type conversion - } - - if (Object.prototype.toString.call(replacement) === "[object Function]") { - result = nativ.replace.call(this + "", search, function () { - if (captureNames) { - // Change the `arguments[0]` string primitive to a String object which can store properties - arguments[0] = new String(arguments[0]); - // Store named backreferences on `arguments[0]` - for (var i = 0; i < captureNames.length; i++) { - if (captureNames[i]) - arguments[0][captureNames[i]] = arguments[i + 1]; - } - } - // Update `lastIndex` before calling `replacement` (fix browsers) - if (isRegex && search.global) - search.lastIndex = arguments[arguments.length - 2] + arguments[0].length; - return replacement.apply(null, arguments); - }); - } else { - str = this + ""; // Type conversion, so `args[args.length - 1]` will be a string (given nonstring `this`) - result = nativ.replace.call(str, search, function () { - var args = arguments; // Keep this function's `arguments` available through closure - return nativ.replace.call(replacement + "", replacementToken, function ($0, $1, $2) { - // Numbered backreference (without delimiters) or special variable - if ($1) { - switch ($1) { - case "$": return "$"; - case "&": return args[0]; - case "`": return args[args.length - 1].slice(0, args[args.length - 2]); - case "'": return args[args.length - 1].slice(args[args.length - 2] + args[0].length); - // Numbered backreference - default: - // What does "$10" mean? - // - Backreference 10, if 10 or more capturing groups exist - // - Backreference 1 followed by "0", if 1-9 capturing groups exist - // - Otherwise, it's the string "$10" - // Also note: - // - Backreferences cannot be more than two digits (enforced by `replacementToken`) - // - "$01" is equivalent to "$1" if a capturing group exists, otherwise it's the string "$01" - // - There is no "$0" token ("$&" is the entire match) - var literalNumbers = ""; - $1 = +$1; // Type conversion; drop leading zero - if (!$1) // `$1` was "0" or "00" - return $0; - while ($1 > args.length - 3) { - literalNumbers = String.prototype.slice.call($1, -1) + literalNumbers; - $1 = Math.floor($1 / 10); // Drop the last digit - } - return ($1 ? args[$1] || "" : "$") + literalNumbers; - } - // Named backreference or delimited numbered backreference - } else { - // What does "${n}" mean? - // - Backreference to numbered capture n. Two differences from "$n": - // - n can be more than two digits - // - Backreference 0 is allowed, and is the entire match - // - Backreference to named capture n, if it exists and is not a number overridden by numbered capture - // - Otherwise, it's the string "${n}" - var n = +$2; // Type conversion; drop leading zeros - if (n <= args.length - 3) - return args[n]; - n = captureNames ? indexOf(captureNames, $2) : -1; - return n > -1 ? args[n + 1] : $0; - } - }); - }); - } - - if (isRegex) { - if (search.global) - search.lastIndex = 0; // Fix IE, Safari bug (last tested IE 9.0.5, Safari 5.1.2 on Windows) - else - search.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows) - } - - return result; - }; - - // A consistent cross-browser, ES3 compliant `split` - String.prototype.split = function (s /* separator */, limit) { - // If separator `s` is not a regex, use the native `split` - if (!XRegExp.isRegExp(s)) - return nativ.split.apply(this, arguments); - - var str = this + "", // Type conversion - output = [], - lastLastIndex = 0, - match, lastLength; - - // Behavior for `limit`: if it's... - // - `undefined`: No limit - // - `NaN` or zero: Return an empty array - // - A positive number: Use `Math.floor(limit)` - // - A negative number: No limit - // - Other: Type-convert, then use the above rules - if (limit === undefined || +limit < 0) { - limit = Infinity; - } else { - limit = Math.floor(+limit); - if (!limit) - return []; - } - - // This is required if not `s.global`, and it avoids needing to set `s.lastIndex` to zero - // and restore it to its original value when we're done using the regex - s = XRegExp.copyAsGlobal(s); - - while (match = s.exec(str)) { // Run the altered `exec` (required for `lastIndex` fix, etc.) - if (s.lastIndex > lastLastIndex) { - output.push(str.slice(lastLastIndex, match.index)); - - if (match.length > 1 && match.index < str.length) - Array.prototype.push.apply(output, match.slice(1)); - - lastLength = match[0].length; - lastLastIndex = s.lastIndex; - - if (output.length >= limit) - break; - } - - if (s.lastIndex === match.index) - s.lastIndex++; - } - - if (lastLastIndex === str.length) { - if (!nativ.test.call(s, "") || lastLength) - output.push(""); - } else { - output.push(str.slice(lastLastIndex)); - } - - return output.length > limit ? output.slice(0, limit) : output; - }; - - - //--------------------------------- - // Private helper functions - //--------------------------------- - - // Supporting function for `XRegExp`, `XRegExp.copyAsGlobal`, etc. Returns a copy of a `RegExp` - // instance with a fresh `lastIndex` (set to zero), preserving properties required for named - // capture. Also allows adding new flags in the process of copying the regex - function clone (regex, additionalFlags) { - if (!XRegExp.isRegExp(regex)) - throw TypeError("type RegExp expected"); - var x = regex._xregexp; - regex = XRegExp(regex.source, getNativeFlags(regex) + (additionalFlags || "")); - if (x) { - regex._xregexp = { - source: x.source, - captureNames: x.captureNames ? x.captureNames.slice(0) : null - }; - } - return regex; - } - - function getNativeFlags (regex) { - return (regex.global ? "g" : "") + - (regex.ignoreCase ? "i" : "") + - (regex.multiline ? "m" : "") + - (regex.extended ? "x" : "") + // Proposed for ES4; included in AS3 - (regex.sticky ? "y" : ""); - } - - function runTokens (pattern, index, scope, context) { - var i = tokens.length, - result, match, t; - // Protect against constructing XRegExps within token handler and trigger functions - isInsideConstructor = true; - // Must reset `isInsideConstructor`, even if a `trigger` or `handler` throws - try { - while (i--) { // Run in reverse order - t = tokens[i]; - if ((scope & t.scope) && (!t.trigger || t.trigger.call(context))) { - t.pattern.lastIndex = index; - match = t.pattern.exec(pattern); // Running the altered `exec` here allows use of named backreferences, etc. - if (match && match.index === index) { - result = { - output: t.handler.call(context, match, scope), - match: match - }; - break; - } - } - } - } catch (err) { - throw err; - } finally { - isInsideConstructor = false; - } - return result; - } - - function indexOf (array, item, from) { - if (Array.prototype.indexOf) // Use the native array method if available - return array.indexOf(item, from); - for (var i = from || 0; i < array.length; i++) { - if (array[i] === item) - return i; - } - return -1; - } - - - //--------------------------------- - // Built-in tokens - //--------------------------------- - - // Augment XRegExp's regular expression syntax and flags. Note that when adding tokens, the - // third (`scope`) argument defaults to `XRegExp.OUTSIDE_CLASS` - - // Comment pattern: (?# ) - XRegExp.addToken( - /\(\?#[^)]*\)/, - function (match) { - // Keep tokens separated unless the following token is a quantifier - return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)"; - } - ); - - // Capturing group (match the opening parenthesis only). - // Required for support of named capturing groups - XRegExp.addToken( - /\((?!\?)/, - function () { - this.captureNames.push(null); - return "("; - } - ); - - // Named capturing group (match the opening delimiter only): (? - XRegExp.addToken( - /\(\?<([$\w]+)>/, - function (match) { - this.captureNames.push(match[1]); - this.hasNamedCapture = true; - return "("; - } - ); - - // Named backreference: \k - XRegExp.addToken( - /\\k<([\w$]+)>/, - function (match) { - var index = indexOf(this.captureNames, match[1]); - // Keep backreferences separate from subsequent literal numbers. Preserve back- - // references to named groups that are undefined at this point as literal strings - return index > -1 ? - "\\" + (index + 1) + (isNaN(match.input.charAt(match.index + match[0].length)) ? "" : "(?:)") : - match[0]; - } - ); - - // Empty character class: [] or [^] - XRegExp.addToken( - /\[\^?]/, - function (match) { - // For cross-browser compatibility with ES3, convert [] to \b\B and [^] to [\s\S]. - // (?!) should work like \b\B, but is unreliable in Firefox - return match[0] === "[]" ? "\\b\\B" : "[\\s\\S]"; - } - ); - - // Mode modifier at the start of the pattern only, with any combination of flags imsx: (?imsx) - // Does not support x(?i), (?-i), (?i-m), (?i: ), (?i)(?m), etc. - XRegExp.addToken( - /^\(\?([imsx]+)\)/, - function (match) { - this.setFlag(match[1]); - return ""; - } - ); - - // Whitespace and comments, in free-spacing (aka extended) mode only - XRegExp.addToken( - /(?:\s+|#.*)+/, - function (match) { - // Keep tokens separated unless the following token is a quantifier - return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)"; - }, - XRegExp.OUTSIDE_CLASS, - function () {return this.hasFlag("x");} - ); - - // Dot, in dotall (aka singleline) mode only - XRegExp.addToken( - /\./, - function () {return "[\\s\\S]";}, - XRegExp.OUTSIDE_CLASS, - function () {return this.hasFlag("s");} - ); - - - //--------------------------------- - // Backward compatibility - //--------------------------------- - - // Uncomment the following block for compatibility with XRegExp 1.0-1.2: - /* - XRegExp.matchWithinChain = XRegExp.matchChain; - RegExp.prototype.addFlags = function (s) {return clone(this, s);}; - RegExp.prototype.execAll = function (s) {var r = []; XRegExp.iterate(s, this, function (m) {r.push(m);}); return r;}; - RegExp.prototype.forEachExec = function (s, f, c) {return XRegExp.iterate(s, this, f, c);}; - RegExp.prototype.validate = function (s) {var r = RegExp("^(?:" + this.source + ")$(?!\\s)", getNativeFlags(this)); if (this.global) this.lastIndex = 0; return s.search(r) === 0;}; - */ - -})(); - diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/scripts/shAutoloader.js b/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/scripts/shAutoloader.js deleted file mode 100644 index 9f5942ee27..0000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/examples/scripts/shAutoloader.js +++ /dev/null @@ -1,122 +0,0 @@ -(function() { - -var sh = SyntaxHighlighter; - -/** - * Provides functionality to dynamically load only the brushes that a needed to render the current page. - * - * There are two syntaxes that autoload understands. For example: - * - * SyntaxHighlighter.autoloader( - * [ 'applescript', 'Scripts/shBrushAppleScript.js' ], - * [ 'actionscript3', 'as3', 'Scripts/shBrushAS3.js' ] - * ); - * - * or a more easily comprehendable one: - * - * SyntaxHighlighter.autoloader( - * 'applescript Scripts/shBrushAppleScript.js', - * 'actionscript3 as3 Scripts/shBrushAS3.js' - * ); - */ -sh.autoloader = function() -{ - var list = arguments, - elements = sh.findElements(), - brushes = {}, - scripts = {}, - all = SyntaxHighlighter.all, - allCalled = false, - allParams = null, - i - ; - - SyntaxHighlighter.all = function(params) - { - allParams = params; - allCalled = true; - }; - - function addBrush(aliases, url) - { - for (var i = 0; i < aliases.length; i++) - brushes[aliases[i]] = url; - }; - - function getAliases(item) - { - return item.pop - ? item - : item.split(/\s+/) - ; - } - - // create table of aliases and script urls - for (i = 0; i < list.length; i++) - { - var aliases = getAliases(list[i]), - url = aliases.pop() - ; - - addBrush(aliases, url); - } - - // dynamically add - - - - - -
    - - - - - - -
    - -

    - 0 Authenticators currently registered. -

    - - - - diff --git a/data/web/inc/lib/vendor/yubico/u2flib-server/examples/pdo/index.phps b/data/web/inc/lib/vendor/yubico/u2flib-server/examples/pdo/index.phps deleted file mode 100644 index c04d63e247..0000000000 --- a/data/web/inc/lib/vendor/yubico/u2flib-server/examples/pdo/index.phps +++ /dev/null @@ -1,204 +0,0 @@ -setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); -$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); - -$pdo->exec("create table if not exists users (id integer primary key, name varchar(255))"); -$pdo->exec("create table if not exists registrations (id integer primary key, user_id integer, keyHandle varchar(255), publicKey varchar(255), certificate text, counter integer)"); - -$scheme = isset($_SERVER['HTTPS']) ? "https://" : "http://"; -$u2f = new u2flib_server\U2F($scheme . $_SERVER['HTTP_HOST']); - -session_start(); - -function createAndGetUser($name) { - global $pdo; - $sel = $pdo->prepare("select * from users where name = ?"); - $sel->execute(array($name)); - $user = $sel->fetch(); - if(!$user) { - $ins = $pdo->prepare("insert into users (name) values(?)"); - $ins->execute(array($name)); - $sel->execute(array($name)); - $user = $sel->fetch(); - } - return $user; -} - -function getRegs($user_id) { - global $pdo; - $sel = $pdo->prepare("select * from registrations where user_id = ?"); - $sel->execute(array($user_id)); - return $sel->fetchAll(); -} - -function addReg($user_id, $reg) { - global $pdo; - $ins = $pdo->prepare("insert into registrations (user_id, keyHandle, publicKey, certificate, counter) values (?, ?, ?, ?, ?)"); - $ins->execute(array($user_id, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter)); -} - -function updateReg($reg) { - global $pdo; - $upd = $pdo->prepare("update registrations set counter = ? where id = ?"); - $upd->execute(array($reg->counter, $reg->id)); -} - -?> - - - - PHP U2F example - - - - - - - -
    - username:
    - register:
    - authenticate:
    - - - -
    - - - diff --git a/data/web/inc/lib/vendor/yubico/u2flib-server/phpunit.xml b/data/web/inc/lib/vendor/yubico/u2flib-server/phpunit.xml deleted file mode 100644 index fa6f08e836..0000000000 --- a/data/web/inc/lib/vendor/yubico/u2flib-server/phpunit.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - tests - - - - - diff --git a/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php b/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php deleted file mode 100644 index a11c78fbac..0000000000 --- a/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php +++ /dev/null @@ -1,507 +0,0 @@ -appId = $appId; - $this->attestDir = $attestDir; - } - - /** - * Called to get a registration request to send to a user. - * Returns an array of one registration request and a array of sign requests. - * - * @param array $registrations List of current registrations for this - * user, to prevent the user from registering the same authenticator several - * times. - * @return array An array of two elements, the first containing a - * RegisterRequest the second being an array of SignRequest - * @throws Error - */ - public function getRegisterData(array $registrations = array()) - { - $challenge = $this->createChallenge(); - $request = new RegisterRequest($challenge, $this->appId); - $signs = $this->getAuthenticateData($registrations); - return array($request, $signs); - } - - /** - * Called to verify and unpack a registration message. - * - * @param RegisterRequest $request this is a reply to - * @param object $response response from a user - * @param bool $includeCert set to true if the attestation certificate should be - * included in the returned Registration object - * @return Registration - * @throws Error - */ - public function doRegister($request, $response, $includeCert = true) - { - if( !is_object( $request ) ) { - throw new \InvalidArgumentException('$request of doRegister() method only accepts object.'); - } - - if( !is_object( $response ) ) { - throw new \InvalidArgumentException('$response of doRegister() method only accepts object.'); - } - - if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) { - throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING ); - } - - if( !is_bool( $includeCert ) ) { - throw new \InvalidArgumentException('$include_cert of doRegister() method only accepts boolean.'); - } - - $rawReg = $this->base64u_decode($response->registrationData); - $regData = array_values(unpack('C*', $rawReg)); - $clientData = $this->base64u_decode($response->clientData); - $cli = json_decode($clientData); - - if($cli->challenge !== $request->challenge) { - throw new Error('Registration challenge does not match', ERR_UNMATCHED_CHALLENGE ); - } - - $registration = new Registration(); - $offs = 1; - $pubKey = substr($rawReg, $offs, PUBKEY_LEN); - $offs += PUBKEY_LEN; - // decode the pubKey to make sure it's good - $tmpKey = $this->pubkey_to_pem($pubKey); - if($tmpKey === null) { - throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE ); - } - $registration->publicKey = base64_encode($pubKey); - $khLen = $regData[$offs++]; - $kh = substr($rawReg, $offs, $khLen); - $offs += $khLen; - $registration->keyHandle = $this->base64u_encode($kh); - - // length of certificate is stored in byte 3 and 4 (excluding the first 4 bytes) - $certLen = 4; - $certLen += ($regData[$offs + 2] << 8); - $certLen += $regData[$offs + 3]; - - $rawCert = $this->fixSignatureUnusedBits(substr($rawReg, $offs, $certLen)); - $offs += $certLen; - $pemCert = "-----BEGIN CERTIFICATE-----\r\n"; - $pemCert .= chunk_split(base64_encode($rawCert), 64); - $pemCert .= "-----END CERTIFICATE-----"; - if($includeCert) { - $registration->certificate = base64_encode($rawCert); - } - if($this->attestDir) { - if(openssl_x509_checkpurpose($pemCert, -1, $this->get_certs()) !== true) { - throw new Error('Attestation certificate can not be validated', ERR_ATTESTATION_VERIFICATION ); - } - } - - if(!openssl_pkey_get_public($pemCert)) { - throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE ); - } - $signature = substr($rawReg, $offs); - - $dataToVerify = chr(0); - $dataToVerify .= hash('sha256', $request->appId, true); - $dataToVerify .= hash('sha256', $clientData, true); - $dataToVerify .= $kh; - $dataToVerify .= $pubKey; - - if(openssl_verify($dataToVerify, $signature, $pemCert, 'sha256') === 1) { - return $registration; - } else { - throw new Error('Attestation signature does not match', ERR_ATTESTATION_SIGNATURE ); - } - } - - /** - * Called to get an authentication request. - * - * @param array $registrations An array of the registrations to create authentication requests for. - * @return array An array of SignRequest - * @throws Error - */ - public function getAuthenticateData(array $registrations) - { - $sigs = array(); - $challenge = $this->createChallenge(); - foreach ($registrations as $reg) { - if( !is_object( $reg ) ) { - throw new \InvalidArgumentException('$registrations of getAuthenticateData() method only accepts array of object.'); - } - - $sig = new SignRequest(); - $sig->appId = $this->appId; - $sig->keyHandle = $reg->keyHandle; - $sig->challenge = $challenge; - $sigs[] = $sig; - } - return $sigs; - } - - /** - * Called to verify an authentication response - * - * @param array $requests An array of outstanding authentication requests - * @param array $registrations An array of current registrations - * @param object $response A response from the authenticator - * @return Registration - * @throws Error - * - * The Registration object returned on success contains an updated counter - * that should be saved for future authentications. - * If the Error returned is ERR_COUNTER_TOO_LOW this is an indication of - * token cloning or similar and appropriate action should be taken. - */ - public function doAuthenticate(array $requests, array $registrations, $response) - { - if( !is_object( $response ) ) { - throw new \InvalidArgumentException('$response of doAuthenticate() method only accepts object.'); - } - - if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) { - throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING ); - } - - /** @var object|null $req */ - $req = null; - - /** @var object|null $reg */ - $reg = null; - - $clientData = $this->base64u_decode($response->clientData); - $decodedClient = json_decode($clientData); - foreach ($requests as $req) { - if( !is_object( $req ) ) { - throw new \InvalidArgumentException('$requests of doAuthenticate() method only accepts array of object.'); - } - - if($req->keyHandle === $response->keyHandle && $req->challenge === $decodedClient->challenge) { - break; - } - - $req = null; - } - if($req === null) { - throw new Error('No matching request found', ERR_NO_MATCHING_REQUEST ); - } - foreach ($registrations as $reg) { - if( !is_object( $reg ) ) { - throw new \InvalidArgumentException('$registrations of doAuthenticate() method only accepts array of object.'); - } - - if($reg->keyHandle === $response->keyHandle) { - break; - } - $reg = null; - } - if($reg === null) { - throw new Error('No matching registration found', ERR_NO_MATCHING_REGISTRATION ); - } - $pemKey = $this->pubkey_to_pem($this->base64u_decode($reg->publicKey)); - if($pemKey === null) { - throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE ); - } - - $signData = $this->base64u_decode($response->signatureData); - $dataToVerify = hash('sha256', $req->appId, true); - $dataToVerify .= substr($signData, 0, 5); - $dataToVerify .= hash('sha256', $clientData, true); - $signature = substr($signData, 5); - - if(openssl_verify($dataToVerify, $signature, $pemKey, 'sha256') === 1) { - $ctr = unpack("Nctr", substr($signData, 1, 4)); - $counter = $ctr['ctr']; - /* TODO: wrap-around should be handled somehow.. */ - if($counter > $reg->counter) { - $reg->counter = $counter; - return $reg; - } else { - throw new Error('Counter too low.', ERR_COUNTER_TOO_LOW ); - } - } else { - throw new Error('Authentication failed', ERR_AUTHENTICATION_FAILURE ); - } - } - - /** - * @return array - */ - private function get_certs() - { - $files = array(); - $dir = $this->attestDir; - if($dir && $handle = opendir($dir)) { - while(false !== ($entry = readdir($handle))) { - if(is_file("$dir/$entry")) { - $files[] = "$dir/$entry"; - } - } - closedir($handle); - } - return $files; - } - - /** - * @param string $data - * @return string - */ - private function base64u_encode($data) - { - return trim(strtr(base64_encode($data), '+/', '-_'), '='); - } - - /** - * @param string $data - * @return string - */ - private function base64u_decode($data) - { - return base64_decode(strtr($data, '-_', '+/')); - } - - /** - * @param string $key - * @return null|string - */ - private function pubkey_to_pem($key) - { - if(strlen($key) !== PUBKEY_LEN || $key[0] !== "\x04") { - return null; - } - - /* - * Convert the public key to binary DER format first - * Using the ECC SubjectPublicKeyInfo OIDs from RFC 5480 - * - * SEQUENCE(2 elem) 30 59 - * SEQUENCE(2 elem) 30 13 - * OID1.2.840.10045.2.1 (id-ecPublicKey) 06 07 2a 86 48 ce 3d 02 01 - * OID1.2.840.10045.3.1.7 (secp256r1) 06 08 2a 86 48 ce 3d 03 01 07 - * BIT STRING(520 bit) 03 42 ..key.. - */ - $der = "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01"; - $der .= "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42"; - $der .= "\0".$key; - - $pem = "-----BEGIN PUBLIC KEY-----\r\n"; - $pem .= chunk_split(base64_encode($der), 64); - $pem .= "-----END PUBLIC KEY-----"; - - return $pem; - } - - /** - * @return string - * @throws Error - */ - private function createChallenge() - { - $challenge = openssl_random_pseudo_bytes(32, $crypto_strong ); - if( $crypto_strong !== true ) { - throw new Error('Unable to obtain a good source of randomness', ERR_BAD_RANDOM); - } - - $challenge = $this->base64u_encode( $challenge ); - - return $challenge; - } - - /** - * Fixes a certificate where the signature contains unused bits. - * - * @param string $cert - * @return mixed - */ - private function fixSignatureUnusedBits($cert) - { - if(in_array(hash('sha256', $cert), $this->FIXCERTS)) { - $cert[strlen($cert) - 257] = "\0"; - } - return $cert; - } -} - -/** - * Class for building a registration request - * - * @package u2flib_server - */ -class RegisterRequest -{ - /** Protocol version */ - public $version = U2F_VERSION; - - /** Registration challenge */ - public $challenge; - - /** Application id */ - public $appId; - - /** - * @param string $challenge - * @param string $appId - * @internal - */ - public function __construct($challenge, $appId) - { - $this->challenge = $challenge; - $this->appId = $appId; - } -} - -/** - * Class for building up an authentication request - * - * @package u2flib_server - */ -class SignRequest -{ - /** Protocol version */ - public $version = U2F_VERSION; - - /** Authentication challenge */ - public $challenge; - - /** Key handle of a registered authenticator */ - public $keyHandle; - - /** Application id */ - public $appId; -} - -/** - * Class returned for successful registrations - * - * @package u2flib_server - */ -class Registration -{ - /** The key handle of the registered authenticator */ - public $keyHandle; - - /** The public key of the registered authenticator */ - public $publicKey; - - /** The attestation certificate of the registered authenticator */ - public $certificate; - - /** The counter associated with this registration */ - public $counter = -1; -} - -/** - * Error class, returned on errors - * - * @package u2flib_server - */ -class Error extends \Exception -{ - /** - * Override constructor and make message and code mandatory - * @param string $message - * @param int $code - * @param \Exception|null $previous - */ - public function __construct($message, $code, \Exception $previous = null) { - parent::__construct($message, $code, $previous); - } -} diff --git a/data/web/inc/lib/vendor/yubico/u2flib-server/tests/certs/yubico-u2f-ca-1.pem b/data/web/inc/lib/vendor/yubico/u2flib-server/tests/certs/yubico-u2f-ca-1.pem deleted file mode 100644 index 15a1dc287b..0000000000 --- a/data/web/inc/lib/vendor/yubico/u2flib-server/tests/certs/yubico-u2f-ca-1.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ -dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw -MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290 -IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk -5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep -8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw -nebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT -9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw -LvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ -hjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN -BgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4 -MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt -hX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k -LVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U -sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc -U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw== ------END CERTIFICATE----- diff --git a/data/web/inc/lib/vendor/yubico/u2flib-server/tests/u2flib_test.php b/data/web/inc/lib/vendor/yubico/u2flib-server/tests/u2flib_test.php deleted file mode 100644 index 56af62bf66..0000000000 --- a/data/web/inc/lib/vendor/yubico/u2flib-server/tests/u2flib_test.php +++ /dev/null @@ -1,296 +0,0 @@ -u2f = new u2flib_server\U2F("http://demo.example.com"); - } - - public function testGetRegisterData() { - list($reg, $signData) = $this->u2f->getRegisterData(); - $this->assertJsonStringEqualsJsonString(json_encode(array()), json_encode($signData)); - $this->assertEquals('U2F_V2', $reg->version); - $this->assertObjectHasAttribute('challenge', $reg); - $this->assertEquals('http://demo.example.com', $reg->appId); - } - - public function testDoRegister() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9", "errorCode": 0 }'); - $reg = $this->u2f->doRegister($req, $resp); - $this->assertEquals('CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w', $reg->keyHandle); - $this->assertEquals('BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y/yaFORPUe3c=', $reg->publicKey); - $this->assertEquals('MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp/VRZHOwd2NZNzpnB9ePNKvUaWCGK/gN+cynnYFdwJ75iSgMVYb/RnFcdPwnsBzBU68hbhTnu/FvJxWo7rZJ2q7qXpA10eLVXJr4/4oSXEk9I/0IIHqOP98Ck/fAoI5gYI7ygndyqoPJ/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh/h7oKEKamCWk19dJp5jHQmumkHlvQhH/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg/0J+xOb4zl6a1z65nae4OTj7628/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg==', $reg->certificate); - $this->assertLessThan(0, $reg->counter); - } - - public function testDoRegisterNoCert() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $reg = $this->u2f->doRegister($req, $resp, false); - $this->assertEquals('CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w', $reg->keyHandle); - $this->assertEquals('BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y/yaFORPUe3c=', $reg->publicKey); - $this->assertEquals('', $reg->certificate); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_ATTESTATION_VERIFICATION - */ - public function testDoRegisterAttestFail() { - $this->u2f = new u2flib_server\U2F("http://demo.example.com", __DIR__ . "/../tests/certs"); - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $this->u2f->doRegister($req, $resp, true); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_ATTESTATION_SIGNATURE - */ - public function testDoRegisterFail2() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8NwW=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $this->u2f->doRegister($req, $resp, false); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_UNMATCHED_CHALLENGE - */ - public function testDoRegisterFail() { - $req = json_decode('{"version":"U2F_V2","challenge":"YKA0X075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $this->u2f->doRegister($req, $resp, false); - } - - public function testDoRegisterAttest() { - $this->u2f = new u2flib_server\U2F("http://demo.example.com", __DIR__ . "/../tests/certs"); - $req = json_decode('{"version":"U2F_V2","challenge":"5CBRhGBb2CXSum71GNREBGft7yz9g1jZO7JTkHGFsVY","appId":"http:\/\/demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQRX1gfcG-ofTlk9rjB9spsIMrmT9ba0DLto5fzk8FDB05ModNU2sWAqoQRemYiUrILQdbNGpN_aHA0_oq8kcd_XQCK-Ut0PWaOtz43t0aAV04U788e-dvpeqLtHxtINjgmutKM8_GJQ7F-3W0dogUjSANuRYRdkkSEHPcVdLSkpyfowggIbMIIBBaADAgECAgRAxBIlMAsGCSqGSIb3DQEBCzAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowKjEoMCYGA1UEAwwfWXViaWNvIFUyRiBFRSBTZXJpYWwgMTA4NjU5MTUyNTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABK2iSVV7KGNEdPE-oHGvobNnHVw6ZZ6vB3jNIYB1C4t32OucHzMweHqM5CAMSMDHtfp1vuJYaiQSk7jb6M48WtejEjAQMA4GCisGAQQBgsQKAQEEADALBgkqhkiG9w0BAQsDggEBAVg0BoEHEEp4LJLYPYFACRGS8WZiXkCA8crYLgGnzvfKXwPwyKJlUzYxxv5xoRrl5zjkIUXhZ4mnHZVsnj9EY_VGDuRRzKX7YtxTZpFZn7ej3abjLhckTkkQ_AhUkmP7VuK2AWLgYsS8ejGUqughBsKvh_84uxTAEr5BS-OGg2yi7UIjd8W0nOCc6EN8d_8wCiPOjt2Y_-TKpLLTXKszk4UnWNzRdxBThmBBprJBZbF1VyVRvJm5yRLBpth3G8KMvrt4Nu3Ecoj_Q154IJpWe1Dp1upDFLOG9nWCRQk25Y264k9BDISfqs-wHvUjIo2iDnKl5UVoauTWaT7M6KuEwl4wRAIgYUVjS_yTwJAtF35glSbf9Et-5tJzlHOeAqmbACd6pwsCIE0MkTR5XNQoO4XqDaUZCXmadWu8yU1gfE7AJI9JUUcc", "clientData": "eyAiY2hhbGxlbmdlIjogIjVDQlJoR0JiMkNYU3VtNzFHTlJFQkdmdDd5ejlnMWpaTzdKVGtIR0ZzVlkiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $reg = $this->u2f->doRegister($req, $resp, true); - $this->assertEquals('Ir5S3Q9Zo63Pje3RoBXThTvzx752-l6ou0fG0g2OCa60ozz8YlDsX7dbR2iBSNIA25FhF2SRIQc9xV0tKSnJ-g', $reg->keyHandle); - $this->assertEquals('BFfWB9wb6h9OWT2uMH2ymwgyuZP1trQMu2jl/OTwUMHTkyh01TaxYCqhBF6ZiJSsgtB1s0ak39ocDT+iryRx39c=', $reg->publicKey); - $this->assertEquals('MIICGzCCAQWgAwIBAgIEQMQSJTALBgkqhkiG9w0BAQswLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMCoxKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDEwODY1OTE1MjUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAStoklVeyhjRHTxPqBxr6GzZx1cOmWerwd4zSGAdQuLd9jrnB8zMHh6jOQgDEjAx7X6db7iWGokEpO42+jOPFrXoxIwEDAOBgorBgEEAYLECgEBBAAwCwYJKoZIhvcNAQELA4IBAQBYNAaBBxBKeCyS2D2BQAkRkvFmYl5AgPHK2C4Bp873yl8D8MiiZVM2Mcb+caEa5ec45CFF4WeJpx2VbJ4/RGP1Rg7kUcyl+2LcU2aRWZ+3o92m4y4XJE5JEPwIVJJj+1bitgFi4GLEvHoxlKroIQbCr4f/OLsUwBK+QUvjhoNsou1CI3fFtJzgnOhDfHf/MAojzo7dmP/kyqSy01yrM5OFJ1jc0XcQU4ZgQaayQWWxdVclUbyZuckSwabYdxvCjL67eDbtxHKI/0NeeCCaVntQ6dbqQxSzhvZ1gkUJNuWNuuJPQQyEn6rPsB71IyKNog5ypeVFaGrk1mk+zOirhMJe', $reg->certificate); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_PUBKEY_DECODE - */ - public function testDoRegisterBadKeyInCert() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABdsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $this->u2f->doRegister($req, $resp); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_PUBKEY_DECODE - */ - public function testDoRegisterBadKey() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQMtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $this->u2f->doRegister($req, $resp); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage $request of doRegister() method only accepts object. - */ - public function testDoRegisterInvalidRequest() { - $req = 'request'; - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $this->u2f->doRegister($req, $resp); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage $response of doRegister() method only accepts object. - */ - public function testDoRegisterInvalidResponse() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = 'response'; - $this->u2f->doRegister($req, $resp); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_BAD_UA_RETURNING - */ - public function testDoRegisterUAError() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{"errorCode": "4"}'); - $this->u2f->doRegister($req, $resp); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage $include_cert of doRegister() method only accepts boolean. - */ - public function testDoRegisterInvalidInclude_cert() { - $req = json_decode('{"version":"U2F_V2","challenge":"yKA0x075tjJ-GE7fKTfnzTOSaNUOWQxRd9TWz5aFOg8","appId":"http://demo.example.com"}'); - $resp = json_decode('{ "registrationData": "BQQtEmhWVgvbh-8GpjsHbj_d5FB9iNoRL8mNEq34-ANufKWUpVdIj6BSB_m3eMoZ3GqnaDy3RA5eWP8mhTkT1Ht3QAk1GsmaPIQgXgvrBkCQoQtMFvmwYPfW5jpRgoMPFxquHS7MTt8lofZkWAK2caHD-YQQdaRBgd22yWIjPuWnHOcwggLiMIHLAgEBMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBDQTAeFw0xNDA1MTUxMjU4NTRaFw0xNDA2MTQxMjU4NTRaMB0xGzAZBgNVBAMTEll1YmljbyBVMkYgVGVzdCBFRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNsK2_Uhx1zOY9ym4eglBg2U5idUGU-dJK8mGr6tmUQflaNxkQo6IOc-kV4T6L44BXrVeqN-dpCPr-KKlLYw650wDQYJKoZIhvcNAQELBQADggIBAJVAa1Bhfa2Eo7TriA_jMA8togoA2SUE7nL6Z99YUQ8LRwKcPkEpSpOsKYWJLaR6gTIoV3EB76hCiBaWN5HV3-CPyTyNsM2JcILsedPGeHMpMuWrbL1Wn9VFkc7B3Y1k3OmcH1480q9RpYIYr-A35zKedgV3AnvmJKAxVhv9GcVx0_CewHMFTryFuFOe78W8nFajutknarupekDXR4tVcmvj_ihJcST0j_Qggeo4_3wKT98CgjmBgjvKCd3Kqg8n9aSDVWyaOZsVOhZj3Fv5rFu895--D4qiPDETozJIyliH-HugoQpqYJaTX10mnmMdCa6aQeW9CEf-5QmbIP0S4uZAf7pKYTNmDQ5z27DVopqaFw00MIVqQkae_zSPX4dsNeeoTTXrwUGqitLaGap5ol81LKD9JdP3nSUYLfq0vLsHNDyNgb306TfbOenRRVsgQS8tJyLcknSKktWD_Qn7E5vjOXprXPrmdp7g5OPvrbz9QkWa1JTRfo2n2AXV02LPFc-UfR9bWCBEIJBxvmbpmqt0MnBTHWnth2b0CU_KJTDCY3kAPLGbOT8A4KiI73pRW-e9SWTaQXskw3Ei_dHRILM_l9OXsqoYHJ4Dd3tbfvmjoNYggSw4j50l3unI9d1qR5xlBFpW5sLr8gKX4bnY4SR2nyNiOQNLyPc0B0nW502aMEUCIQDTGOX-i_QrffJDY8XvKbPwMuBVrOSO-ayvTnWs_WSuDQIgZ7fMAvD_Ezyy5jg6fQeuOkoJi8V2naCtzV-HTly8Nww=", "clientData": "eyAiY2hhbGxlbmdlIjogInlLQTB4MDc1dGpKLUdFN2ZLVGZuelRPU2FOVU9XUXhSZDlUV3o1YUZPZzgiLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50IiB9" }'); - $this->u2f->doRegister($req, $resp, 'bar'); - } - - public function testGetAuthenticateData() { - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg=="}')); - $data = $this->u2f->getAuthenticateData($regs); - $inst = $data[0]; - $this->assertEquals("U2F_V2", $inst->version); - $this->assertObjectHasAttribute("challenge", $inst); - $this->assertEquals('CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w', $inst->keyHandle); - $this->assertEquals('http://demo.example.com', $inst->appId); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage $registrations of getAuthenticateData() method only accepts array of object. - */ - public function testGetAuthenticateDataInvalidRegistrations2() { - $regs = array('YubiKey NEO', 'YubiKey Standard'); - $data = $this->u2f->getAuthenticateData($regs); - } - - public function testDoAuthenticate() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg==", "counter":3}')); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w", "errorCode": 0 }'); - $data = $this->u2f->doAuthenticate($reqs, $regs, $resp); - $this->assertEquals(4, $data->counter); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_COUNTER_TOO_LOW - */ - public function testDoAuthenticateCtrFail() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg==", "counter":5}')); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_AUTHENTICATION_FAILURE - */ - public function testDoAuthenticateFail() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg=="}')); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAnG==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_NO_MATCHING_REQUEST - */ - public function testDoAuthenticateWrongReq() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"cTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg=="}')); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_NO_MATCHING_REGISTRATION - */ - public function testDoAuthenticateWrongReg() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"cTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg=="}')); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_PUBKEY_DECODE - */ - public function testDoAuthenticateBadKey() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"bC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg==", "counter":3}')); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage $requests of doAuthenticate() method only accepts array of object. - */ - public function testDoAuthenticateInvalidRequests2() { - $reqs = array('YubiKey NEO', 'YubiKey Standard'); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg==", "counter":3}')); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage $registrations of doAuthenticate() method only accepts array of object. - */ - public function testDoAuthenticateInvalidRegistrations2() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array('YubiKey NEO', 'YubiKey Standard'); - $resp = json_decode('{ "signatureData": "AQAAAAQwRQIhAI6FSrMD3KUUtkpiP0jpIEakql-HNhwWFngyw553pS1CAiAKLjACPOhxzZXuZsVO8im-HStEcYGC50PKhsGp_SUAng==", "clientData": "eyAiY2hhbGxlbmdlIjogImZFbmM5b1Y3OUVhQmdLNUJvTkVSVTVnUEtNMlhHWVdyejRmVWpnYzBRN2ciLCAib3JpZ2luIjogImh0dHA6XC9cL2RlbW8uZXhhbXBsZS5jb20iLCAidHlwIjogIm5hdmlnYXRvci5pZC5nZXRBc3NlcnRpb24iIH0=", "keyHandle": "CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w" }'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage $response of doAuthenticate() method only accepts object. - */ - public function testDoAuthenticateInvalidResponse() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg==", "counter":3}')); - $resp = 'Response'; - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } - - /** - * @expectedException u2flib_server\Error - * @expectedExceptionCode u2flib_server\ERR_BAD_UA_RETURNING - */ - public function testDoAuthenticateUAError() { - $reqs = array(json_decode('{"version":"U2F_V2","challenge":"fEnc9oV79EaBgK5BoNERU5gPKM2XGYWrz4fUjgc0Q7g","keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","appId":"http://demo.example.com"}')); - $regs = array(json_decode('{"keyHandle":"CTUayZo8hCBeC-sGQJChC0wW-bBg99bmOlGCgw8XGq4dLsxO3yWh9mRYArZxocP5hBB1pEGB3bbJYiM-5acc5w","publicKey":"BC0SaFZWC9uH7wamOwduP93kUH2I2hEvyY0Srfj4A258pZSlV0iPoFIH+bd4yhncaqdoPLdEDl5Y\/yaFORPUe3c=","certificate":"MIIC4jCBywIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgQ0EwHhcNMTQwNTE1MTI1ODU0WhcNMTQwNjE0MTI1ODU0WjAdMRswGQYDVQQDExJZdWJpY28gVTJGIFRlc3QgRUUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATbCtv1IcdczmPcpuHoJQYNlOYnVBlPnSSvJhq+rZlEH5WjcZEKOiDnPpFeE+i+OAV61XqjfnaQj6\/iipS2MOudMA0GCSqGSIb3DQEBCwUAA4ICAQCVQGtQYX2thKO064gP4zAPLaIKANklBO5y+mffWFEPC0cCnD5BKUqTrCmFiS2keoEyKFdxAe+oQogWljeR1d\/gj8k8jbDNiXCC7HnTxnhzKTLlq2y9Vp\/VRZHOwd2NZNzpnB9ePNKvUaWCGK\/gN+cynnYFdwJ75iSgMVYb\/RnFcdPwnsBzBU68hbhTnu\/FvJxWo7rZJ2q7qXpA10eLVXJr4\/4oSXEk9I\/0IIHqOP98Ck\/fAoI5gYI7ygndyqoPJ\/Wkg1VsmjmbFToWY9xb+axbvPefvg+KojwxE6MySMpYh\/h7oKEKamCWk19dJp5jHQmumkHlvQhH\/uUJmyD9EuLmQH+6SmEzZg0Oc9uw1aKamhcNNDCFakJGnv80j1+HbDXnqE0168FBqorS2hmqeaJfNSyg\/SXT950lGC36tLy7BzQ8jYG99Ok32znp0UVbIEEvLSci3JJ0ipLVg\/0J+xOb4zl6a1z65nae4OTj7628\/UJFmtSU0X6Np9gF1dNizxXPlH0fW1ggRCCQcb5m6ZqrdDJwUx1p7Ydm9AlPyiUwwmN5ADyxmzk\/AOCoiO96UVvnvUlk2kF7JMNxIv3R0SCzP5fTl7KqGByeA3d7W375o6DWIIEsOI+dJd7pyPXdakecZQRaVubC6\/ICl+G52OEkdp8jYjkDS8j3NAdJ1udNmg==", "counter":3}')); - $resp = json_decode('{"errorCode": "5"}'); - $this->u2f->doAuthenticate($reqs, $regs, $resp); - } -} - -?> diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index 67f72b78c5..5e57a4d417 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -1,9 +1,29 @@ addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates'); // Redis $redis = new Redis(); -$redis->connect('redis-mailcow', 6379); +try { + if (!empty(getenv('REDIS_SLAVEOF_IP'))) { + $redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT')); + } + else { + $redis->connect('redis-mailcow', 6379); + } + $redis->auth(getenv("REDISPASS")); +} +catch (Exception $e) { +// Stop when redis is not available +http_response_code(500); +?> +
    Connection to Redis failed.

    The following error was reported:
    getMessage();?>
    +getOffset() / 60; -$sgn = ($mins < 0 ? -1 : 1); -$mins = abs($mins); -$hrs = floor($mins / 60); -$mins -= $hrs * 60; -$offset = sprintf('%+d:%02d', $hrs*$sgn, $mins); +// $now = new DateTime(); +// $mins = $now->getOffset() / 60; +// $sgn = ($mins < 0 ? -1 : 1); +// $mins = abs($mins); +// $hrs = floor($mins / 60); +// $mins -= $hrs * 60; +// $offset = sprintf('%+d:%02d', $hrs*$sgn, $mins); $dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; $opt = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, - PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;", + //PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;", ]; try { $pdo = new PDO($dsn, $database_user, $database_pass, $opt); } catch (PDOException $e) { // Stop when SQL connection fails +http_response_code(500); ?>
    Connection to database failed.

    The following error was reported:
    getMessage();?>
    Connection to dockerapi container failed.

    The following error was reported:
    -
    config['user_table'] = 'mailbox'; + } + public function checkUserCredentials($username, $password) { + if (check_login($username, $password, array("role" => "user", "service" => "NONE")) == 'user') { + return true; + } + return false; + } + public function getUserDetails($username) { + return $this->getUser($username); + } +} +$oauth2_scope_storage = new OAuth2\Storage\Memory(array('default_scope' => 'profile', 'supported_scopes' => array('profile'))); +$oauth2_storage = new mailcowPdo(array('dsn' => $dsn, 'username' => $database_user, 'password' => $database_pass)); +$oauth2_server = new OAuth2\Server($oauth2_storage, array( + 'refresh_token_lifetime' => $REFRESH_TOKEN_LIFETIME, + 'access_lifetime' => $ACCESS_TOKEN_LIFETIME, +)); +$oauth2_server->setScopeUtil(new OAuth2\Scope($oauth2_scope_storage)); +$oauth2_server->addGrantType(new OAuth2\GrantType\AuthorizationCode($oauth2_storage)); +$oauth2_server->addGrantType(new OAuth2\GrantType\UserCredentials($oauth2_storage)); +$oauth2_server->addGrantType(new OAuth2\GrantType\RefreshToken($oauth2_storage, array( + 'always_issue_new_refresh_token' => true +))); + +function exception_handler($e) { if ($e instanceof PDOException) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -74,48 +156,98 @@ function pdo_exception_handler($e) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__), - 'msg' => array('mysql_error', 'unknown error') + 'msg' => 'An unknown error occured: ' . print_r($e, true) ); return false; } } -set_exception_handler('pdo_exception_handler'); +if(!$DEV_MODE) { + set_exception_handler('exception_handler'); +} // TODO: Move function -function get_remote_ip($anonymize = null) { - global $ANONYMIZE_IPS; - if ($anonymize === null) { - $anonymize = $ANONYMIZE_IPS; - } - elseif ($anonymize !== true && $anonymize !== false) { - $anonymize = true; - } +function get_remote_ip() { $remote = $_SERVER['REMOTE_ADDR']; if (filter_var($remote, FILTER_VALIDATE_IP) === false) { return '0.0.0.0'; } - if ($anonymize) { - if (strlen(inet_pton($remote)) == 4) { - return inet_ntop(inet_pton($remote) & inet_pton("255.255.255.0")); - } - elseif (strlen(inet_pton($remote)) == 16) { - return inet_ntop(inet_pton($remote) & inet_pton('ffff:ffff:ffff:ffff:0000:0000:0000:0000')); - } - } - else { - return $remote; - } + return $remote; } +// Load core functions first +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php'; +// Init Identity Provider +$iam_provider = identity_provider('init'); +$iam_settings = identity_provider('get'); + +// IMAP lib +// use Ddeboer\Imap\Server; +// $imap_server = new Server('dovecot', 143, '/imap/tls/novalidate-cert'); + // Set language if (!isset($_SESSION['mailcow_locale']) && !isset($_COOKIE['mailcow_locale'])) { if ($DETECT_LANGUAGE && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - $header_lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); - foreach ($AVAILABLE_LANGUAGES as $available_lang) { - if ($header_lang == $available_lang) { - $_SESSION['mailcow_locale'] = strtolower(trim($header_lang)); + // regex inspired from @GabrielAnderson on http://stackoverflow.com/questions/6038236/http-accept-language + preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})*)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse); + + $langs = $lang_parse[1]; + $ranks = $lang_parse[4]; + + // (create an associative array 'language' => 'preference') + $lang2pref = array(); + for ($i=0; $i $lang2pref[$b]) + return -1; + elseif ($lang2pref[$a] < $lang2pref[$b]) + return 1; + elseif (strlen($a) > strlen($b)) + return -1; + elseif (strlen($a) < strlen($b)) + return 1; + else + return 0; + }; + + // sort the languages by prefered language and by the most specific region + uksort($lang2pref, $cmpLangs); + + // generate language array without the region part + $AVAILABLE_BASE_LANGUAGES=array(); + foreach ($AVAILABLE_LANGUAGES as $code => $lang) { + $base_code = substr($code, 0, 2); + if (!array_key_exists($base_code, $AVAILABLE_BASE_LANGUAGES)) { + $AVAILABLE_BASE_LANGUAGES[$base_code] = $code; + } + } + + // Find a perfect match or partial match + // Match en-gb or en + foreach ($lang2pref as $lang => $q) { + if (array_key_exists($lang, $AVAILABLE_LANGUAGES)) { + $_SESSION['mailcow_locale'] = $lang; + break; + } elseif (array_key_exists($lang, $AVAILABLE_BASE_LANGUAGES)) { + $_SESSION['mailcow_locale'] = $AVAILABLE_BASE_LANGUAGES[$lang]; + break; + } + } + + // Try suggest match + // e.g. suggest en-gb when only en-us is provided + if (!isset($_SESSION['mailcow_locale'])) { + foreach ($lang2pref as $lang => $q) { + if (array_key_exists(substr($lang, 0, 2), $AVAILABLE_BASE_LANGUAGES)) { + $_SESSION['mailcow_locale'] = $AVAILABLE_BASE_LANGUAGES[substr($lang, 0, 2)]; + break; + } } } } @@ -124,37 +256,81 @@ function get_remote_ip($anonymize = null) { } } if (isset($_COOKIE['mailcow_locale'])) { - $_SESSION['mailcow_locale'] = $_COOKIE['mailcow_locale']; + (preg_match('/^[a-z]{2}$/', $_COOKIE['mailcow_locale'])) ? $_SESSION['mailcow_locale'] = $_COOKIE['mailcow_locale'] : setcookie("mailcow_locale", "", time() - 300); } -if (isset($_GET['lang']) && in_array($_GET['lang'], $AVAILABLE_LANGUAGES)) { +if (isset($_GET['lang']) && array_key_exists($_GET['lang'], $AVAILABLE_LANGUAGES)) { $_SESSION['mailcow_locale'] = $_GET['lang']; setcookie("mailcow_locale", $_GET['lang'], time()+30758400); // one year } -require_once $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en.php'; -include $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.php'; -require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php'; +/* + * load language + */ +$lang = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en-gb.json'), true); + +$langFile = $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.json'; +if(file_exists($langFile)) { + $lang = array_merge_real($lang, json_decode(file_get_contents($langFile), true)); +} + + require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.acl.inc.php'; -require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php'; -require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.customize.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.address_rewriting.inc.php'; -require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.domain_admin.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.admin.inc.php'; -require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.quarantine.inc.php'; -require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.app_passwd.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.customize.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.dkim.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.docker.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.domain_admin.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fwdhost.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailq.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.oauth2.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.presets.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.pushover.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.quarantine.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.quota_notification.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php'; -require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.relayhost.inc.php'; -require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.rsettings.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.rspamd.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.tls_policy_maps.inc.php'; -require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php'; -require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.docker.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.transports.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/init_db.inc.php'; -require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.global.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/twig.inc.php'; init_db_schema(); if (isset($_SESSION['mailcow_cc_role'])) { + // if ($_SESSION['mailcow_cc_role'] == 'user') { + // list($master_user, $master_passwd) = explode(':', file_get_contents('/etc/sogo/sieve.creds')); + // $imap_connection = $imap_server->authenticate($_SESSION['mailcow_cc_username'] . '*' . trim($master_user), trim($master_passwd)); + // $master_user = $master_passwd = null; + // } acl('to_session'); } + +// init frontend +// Minify JS +use MatthiasMullie\Minify; +$js_minifier = new JSminifierExtended(); +$js_dir = array_diff(scandir('/web/js/build'), array('..', '.')); +// Minify CSS +$css_minifier = new CSSminifierExtended(); +$css_dir = array_diff(scandir('/web/css/build'), array('..', '.')); +// get customized ui data $UI_TEXTS = customize('get', 'ui_texts'); + +// minify bootstrap theme +if (file_exists('/web/css/themes/'.$UI_THEME.'-bootstrap.css')) + $css_minifier->add('/web/css/themes/'.$UI_THEME.'-bootstrap.css'); +else + $css_minifier->add('/web/css/themes/lumen-bootstrap.css'); +// minify css build files +foreach ($css_dir as $css_file) { + $css_minifier->add('/web/css/build/' . $css_file); +} +// minify js build files +foreach ($js_dir as $js_file) { + $js_minifier->add('/web/js/build/' . $js_file); +} diff --git a/data/web/inc/presets/rspamd/preset_1.yml b/data/web/inc/presets/rspamd/preset_1.yml new file mode 100644 index 0000000000..41833cf0fa --- /dev/null +++ b/data/web/inc/presets/rspamd/preset_1.yml @@ -0,0 +1,7 @@ +headline: lang.rsettings_preset_1 +content: | + priority = 10; + authenticated = yes; + apply "default" { + symbols_enabled = ["BCC", "DKIM_SIGNED", "RATELIMITED", "RATELIMIT_UPDATE", "RATELIMIT_CHECK", "DYN_RL_CHECK", "HISTORY_SAVE", "MILTER_HEADERS", "ARC_SIGNED"]; + } diff --git a/data/web/inc/presets/rspamd/preset_3.yml b/data/web/inc/presets/rspamd/preset_3.yml new file mode 100644 index 0000000000..37a81d0192 --- /dev/null +++ b/data/web/inc/presets/rspamd/preset_3.yml @@ -0,0 +1,13 @@ +headline: lang.rsettings_preset_3 +content: | + priority = 10; + rcpt = "internal-only@domain.tld"; + # Multiple allowed hosts + # from = "/^((?!(.*@allowed-domain\.tld|.*@other-allowed-domain\.tld)).)*$/"; + from = "/^((?!.*@domain\.tld).)*$/"; + apply "default" { + MAILCOW_BLACK = 9999.0; + } + symbols [ + "MAILCOW_BLACK" + ] \ No newline at end of file diff --git a/data/web/inc/presets/rspamd/preset_4.yml b/data/web/inc/presets/rspamd/preset_4.yml new file mode 100644 index 0000000000..c63071b9da --- /dev/null +++ b/data/web/inc/presets/rspamd/preset_4.yml @@ -0,0 +1,11 @@ +headline: lang.rsettings_preset_4 +content: | + priority = 10; + rcpt = "/.*@develcow.de$/"; + from = "/.*/"; + apply "default" { + MAILCOW_WHITE = -9999.0; + } + symbols [ + "MAILCOW_WHITE" + ] \ No newline at end of file diff --git a/data/web/inc/presets/sieve/sieve_1.yml b/data/web/inc/presets/sieve/sieve_1.yml new file mode 100644 index 0000000000..45e0eb46ca --- /dev/null +++ b/data/web/inc/presets/sieve/sieve_1.yml @@ -0,0 +1,10 @@ +headline: lang.sieve_preset_1 +content: | + require ["reject","body","regex"]; + if anyof (body :raw :regex ["filename=\".*\\.(doc|exe|moo)\""]) { + reject text: + doc, exe and moo are dangerous file extensions. + Why would you do that? I am a sad cow. + . + ; + } \ No newline at end of file diff --git a/data/web/inc/presets/sieve/sieve_2.yml b/data/web/inc/presets/sieve/sieve_2.yml new file mode 100644 index 0000000000..1dafec19ec --- /dev/null +++ b/data/web/inc/presets/sieve/sieve_2.yml @@ -0,0 +1,7 @@ +headline: lang.sieve_preset_2 +content: | + require ["envelope", "imap4flags"]; + if envelope "from" "mark@me-read.com" + { + setflag "\\seen"; + } diff --git a/data/web/inc/presets/sieve/sieve_3.yml b/data/web/inc/presets/sieve/sieve_3.yml new file mode 100644 index 0000000000..92f0e109bb --- /dev/null +++ b/data/web/inc/presets/sieve/sieve_3.yml @@ -0,0 +1,3 @@ +headline: lang.sieve_preset_3 +content: | + discard; stop; \ No newline at end of file diff --git a/data/web/inc/presets/sieve/sieve_4.yml b/data/web/inc/presets/sieve/sieve_4.yml new file mode 100644 index 0000000000..38c7b5f6cc --- /dev/null +++ b/data/web/inc/presets/sieve/sieve_4.yml @@ -0,0 +1,4 @@ +headline: lang.sieve_preset_4 +content: | + require "fileinto"; + fileinto "INBOX"; stop; \ No newline at end of file diff --git a/data/web/inc/presets/sieve/sieve_5.yml b/data/web/inc/presets/sieve/sieve_5.yml new file mode 100644 index 0000000000..adef757258 --- /dev/null +++ b/data/web/inc/presets/sieve/sieve_5.yml @@ -0,0 +1,16 @@ +headline: lang.sieve_preset_5 +content: | + require "vacation-seconds"; + if true + { + # Only send once a day: + # vacation :seconds 86400 text: + # Only send once every 300 seconds: + # vacation :seconds 300 text: + # Else: + vacation text: + I am on holidays, yay! + Mail me later, alligator. + . + ; + } \ No newline at end of file diff --git a/data/web/inc/presets/sieve/sieve_6.yml b/data/web/inc/presets/sieve/sieve_6.yml new file mode 100644 index 0000000000..38eaa7c9b9 --- /dev/null +++ b/data/web/inc/presets/sieve/sieve_6.yml @@ -0,0 +1,10 @@ +headline: lang.sieve_preset_6 +content: | + require ["reject"]; + # Rejects are sent from postmaster@mailcow_hostname, make sure DMARC/SPF are fine + # You should prefer rejects via mailcow blacklists + reject text: + I am not taking mail from you, and I don't + want your birdseed, either! + . + ; \ No newline at end of file diff --git a/data/web/inc/presets/sieve/sieve_7.yml b/data/web/inc/presets/sieve/sieve_7.yml new file mode 100644 index 0000000000..6f405e96a9 --- /dev/null +++ b/data/web/inc/presets/sieve/sieve_7.yml @@ -0,0 +1,7 @@ +headline: lang.sieve_preset_7 +content: | + redirect "other-address@domain.tld"; + # Either keep after redirecting: + # keep; + # Or redirect and drop: + discard; stop; \ No newline at end of file diff --git a/data/web/inc/presets/sieve/sieve_8.yml b/data/web/inc/presets/sieve/sieve_8.yml new file mode 100644 index 0000000000..bd44a73685 --- /dev/null +++ b/data/web/inc/presets/sieve/sieve_8.yml @@ -0,0 +1,18 @@ +headline: lang.sieve_preset_8 +content: | + require "fileinto"; + require "mailbox"; + require "variables"; + require "subaddress"; + require "envelope"; + require "duplicate"; + require "imap4flags"; + if header :matches "To" "*mail@domain.tld*" { + redirect "anothermail@anotherdomain.tld"; + setflag "\\seen"; /* Mark mail as read */ + fileInto "INBOX/SubFolder"; /* Move mail on subfolder after */ + } else { + # The rest goes into INBOX + # default is "implicit keep", we do it explicitly here + keep; + } \ No newline at end of file diff --git a/data/web/inc/sessions.inc.php b/data/web/inc/sessions.inc.php index ac77e9ae5a..ac308b687f 100644 --- a/data/web/inc/sessions.inc.php +++ b/data/web/inc/sessions.inc.php @@ -1,97 +1,176 @@ -prepare("SELECT `allow_from` FROM `api` WHERE `api_key` = :api_key AND `active` = '1';"); - $stmt->execute(array( - ':api_key' => preg_replace('/[^a-zA-Z0-9-]/', '', $_SERVER['HTTP_X_API_KEY']) - )); - $api_return = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($api_return['username'])) { - $remote = get_remote_ip(false); - $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $api_return['allow_from'])); - if (in_array($remote, $allow_from)) { - $_SESSION['mailcow_cc_username'] = 'API'; - $_SESSION['mailcow_cc_role'] = 'admin'; - $_SESSION['mailcow_cc_api'] = true; - } - } -} -// Update session cookie -// setcookie(session_name() ,session_id(), time() + $SESSION_LIFETIME); - -// Check session -function session_check() { - if ($_SESSION['mailcow_cc_api'] === true) { - return true; - } - if (!isset($_SESSION['SESS_REMOTE_UA']) || ($_SESSION['SESS_REMOTE_UA'] != $_SERVER['HTTP_USER_AGENT'])) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'msg' => 'session_ua' - ); - return false; - } - if (!empty($_POST)) { - if ($_SESSION['CSRF']['TOKEN'] != $_POST['csrf_token']) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'msg' => 'session_token' - ); - return false; - } - unset($_POST['csrf_token']); - $_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32)); - $_SESSION['CSRF']['TIME'] = time(); - } - return true; -} - -if (isset($_SESSION['mailcow_cc_role']) && session_check() === false) { - $_POST = array(); - $_FILES = array(); -} - -// Handle logouts -if (isset($_POST["logout"])) { - if (isset($_SESSION["dual-login"])) { - $_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"]; - $_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"]; - unset($_SESSION["dual-login"]); - header("Location: /mailbox"); - exit(); - } - else { - session_regenerate_id(true); - session_unset(); - session_destroy(); - session_write_close(); - header("Location: /"); - } -} + $SESSION_LIFETIME)) { + session_unset(); + session_destroy(); +} +$_SESSION['LAST_ACTIVITY'] = time(); + +// API +if (!empty($_SERVER['HTTP_X_API_KEY'])) { + $stmt = $pdo->prepare("SELECT * FROM `api` WHERE `api_key` = :api_key AND `active` = '1';"); + $stmt->execute(array( + ':api_key' => preg_replace('/[^a-zA-Z0-9-]/', '', $_SERVER['HTTP_X_API_KEY']) + )); + $api_return = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($api_return['api_key'])) { + $skip_ip_check = ($api_return['skip_ip_check'] == 1); + $remote = get_remote_ip(false); + $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $api_return['allow_from'])); + if ($skip_ip_check === true || ip_acl($remote, $allow_from)) { + $_SESSION['mailcow_cc_username'] = 'API'; + $_SESSION['mailcow_cc_role'] = 'admin'; + $_SESSION['mailcow_cc_api'] = true; + if ($api_return['access'] == 'rw') { + $_SESSION['mailcow_cc_api_access'] = 'rw'; + } + else { + $_SESSION['mailcow_cc_api_access'] = 'ro'; + } + } + else { + $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']); + error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); + http_response_code(401); + echo json_encode(array( + 'type' => 'error', + 'msg' => 'api access denied for ip ' . $_SERVER['REMOTE_ADDR'] + )); + unset($_POST); + exit(); + } + } + else { + $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']); + error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); + http_response_code(401); + echo json_encode(array( + 'type' => 'error', + 'msg' => 'authentication failed' + )); + unset($_POST); + exit(); + } +} + +// Handle logouts +if (isset($_POST["logout"])) { + if (isset($_SESSION["dual-login"])) { + $_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"]; + $_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"]; + unset($_SESSION['sogo-sso-user-allowed']); + unset($_SESSION['sogo-sso-pass']); + unset($_SESSION["dual-login"]); + if ($_SESSION["mailcow_cc_role"] == "admin"){ + header("Location: /admin/mailbox"); + } elseif ($_SESSION["mailcow_cc_role"] == "domainadmin") { + header("Location: /domainadmin/mailbox"); + } else { + header("Location: /"); + } + exit(); + } + else { + $role = $_SESSION["mailcow_cc_role"]; + session_regenerate_id(true); + session_unset(); + session_destroy(); + session_write_close(); + if ($role == "admin") { + header("Location: /admin"); + } + elseif ($role == "domainadmin") { + header("Location: /domainadmin"); + } + else { + header("Location: /"); + } + } +} + +// Check session +function session_check() { + if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) { + return true; + } + if (!isset($_SESSION['SESS_REMOTE_UA']) || ($_SESSION['SESS_REMOTE_UA'] != $_SERVER['HTTP_USER_AGENT'])) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'msg' => 'session_ua' + ); + return false; + } + // Check if this is a POST request (form-encoded or JSON) + $is_post_request = !empty($_POST) || ( + isset($_SERVER['CONTENT_TYPE']) && + strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false + ); + + if ($is_post_request) { + // Skip CSRF check for DataTables server-side processing endpoints + // These are read-only operations (equivalent to GET) authenticated by session + $is_search_endpoint = ( + isset($_GET['query']) && + preg_match('#^search/(domain|mailbox)$#', $_GET['query']) + ); + + if (!$is_search_endpoint && !empty($_POST)) { + if ($_SESSION['CSRF']['TOKEN'] != $_POST['csrf_token']) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'msg' => 'session_token' + ); + return false; + } + unset($_POST['csrf_token']); + $_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32)); + $_SESSION['CSRF']['TIME'] = time(); + } + } + return true; +} + +if (isset($_SESSION['mailcow_cc_role']) && session_check() === false) { + $_POST = array(); + $_FILES = array(); +} diff --git a/data/web/inc/spf.inc.php b/data/web/inc/spf.inc.php index 18dd289347..55e164b43b 100644 --- a/data/web/inc/spf.inc.php +++ b/data/web/inc/spf.inc.php @@ -1,11 +1,9 @@ \ No newline at end of file +?> diff --git a/data/web/inc/triggers.admin.inc.php b/data/web/inc/triggers.admin.inc.php new file mode 100644 index 0000000000..92043190ed --- /dev/null +++ b/data/web/inc/triggers.admin.inc.php @@ -0,0 +1,125 @@ + "verify", + "token" => $_POST["token"], + "user" => "admin" + )); + if (is_array($res) && $res['role'] == "admin" && !empty($res['username'])){ + $_SESSION["mailcow_cc_username"] = $res['username']; + $_SESSION["mailcow_cc_role"] = $res['role']; + $_SESSION["fido2_cid"] = $res['cid']; + } + exit; +} + +if (isset($_GET["cancel_tfa_login"])) { + unset($_SESSION['pending_pw_reset_token']); + unset($_SESSION['pending_pw_new_password']); + unset($_SESSION['pending_mailcow_cc_username']); + unset($_SESSION['pending_mailcow_cc_role']); + unset($_SESSION['pending_tfa_methods']); + + header("Location: /admin"); +} + +if (isset($_GET["cancel_tfa_setup"])) { + session_regenerate_id(true); + session_unset(); + session_destroy(); + session_write_close(); + header("Location: /admin"); + exit(); +} + +if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { + $login_user = strtolower(trim($_POST["login_user"])); + $as = check_login($login_user, $_POST["pass_user"], array("role" => "admin", "service" => "MAILCOWUI")); + + if ($as == "admin") { + session_regenerate_id(true); + $_SESSION['mailcow_cc_username'] = $login_user; + $_SESSION['mailcow_cc_role'] = "admin"; + // If pending actions exist, redirect to /admin to show modal + if (!empty($_SESSION['pending_tfa_setup']) || !empty($_SESSION['pending_pw_update'])) { + header("Location: /admin"); + die(); + } + header("Location: /admin/dashboard"); + die(); + } + elseif ($as != "pending") { + unset($_SESSION['pending_mailcow_cc_username']); + unset($_SESSION['pending_mailcow_cc_role']); + unset($_SESSION['pending_tfa_methods']); + unset($_SESSION['mailcow_cc_username']); + unset($_SESSION['mailcow_cc_role']); + } +} + +if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin" && !isset($_SESSION['mailcow_cc_api'])) { + // TODO: Move file upload to API? + if (isset($_POST["submit_main_logo"])) { + if ($_FILES['main_logo']['error'] == 0) { + customize('add', 'main_logo', $_FILES); + } + if ($_FILES['main_logo_dark']['error'] == 0) { + customize('add', 'main_logo_dark', $_FILES); + } + } + if (isset($_POST["reset_main_logo"])) { + customize('delete', 'main_logo'); + customize('delete', 'main_logo_dark'); + } + // Some actions will not be available via API + if (isset($_POST["license_validate_now"])) { + license('verify'); + } + if (isset($_POST["admin_api"])) { + if (isset($_POST["admin_api"]["ro"])) { + admin_api('ro', 'edit', $_POST); + } + elseif (isset($_POST["admin_api"]["rw"])) { + admin_api('rw', 'edit', $_POST); + } + } + if (isset($_POST["admin_api_regen_key"])) { + if (isset($_POST["admin_api_regen_key"]["ro"])) { + admin_api('ro', 'regen_key', $_POST); + } + elseif (isset($_POST["admin_api_regen_key"]["rw"])) { + admin_api('rw', 'regen_key', $_POST); + } + } + if (isset($_POST["rspamd_ui"])) { + rspamd_ui('edit', $_POST); + } + if (isset($_POST["mass_send"])) { + sys_mail($_POST); + } +} +?> diff --git a/data/web/inc/triggers.domainadmin.inc.php b/data/web/inc/triggers.domainadmin.inc.php new file mode 100644 index 0000000000..2eee8b993b --- /dev/null +++ b/data/web/inc/triggers.domainadmin.inc.php @@ -0,0 +1,94 @@ + "verify", + "token" => $_POST["token"], + "user" => "domainadmin" + )); + if (is_array($res) && $res['role'] == "domainadmin" && !empty($res['username'])){ + $_SESSION["mailcow_cc_username"] = $res['username']; + $_SESSION["mailcow_cc_role"] = $res['role']; + $_SESSION["fido2_cid"] = $res['cid']; + } + exit; +} + +if (isset($_GET["cancel_tfa_login"])) { + unset($_SESSION['pending_pw_reset_token']); + unset($_SESSION['pending_pw_new_password']); + unset($_SESSION['pending_mailcow_cc_username']); + unset($_SESSION['pending_mailcow_cc_role']); + unset($_SESSION['pending_tfa_methods']); + + header("Location: /domainadmin"); +} + +if (isset($_GET["cancel_tfa_setup"])) { + session_regenerate_id(true); + session_unset(); + session_destroy(); + session_write_close(); + header("Location: /domainadmin"); + exit(); +} + +if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { + $login_user = strtolower(trim($_POST["login_user"])); + $as = check_login($login_user, $_POST["pass_user"], array("role" => "domain_admin", "service" => "MAILCOWUI")); + + if ($as == "domainadmin") { + session_regenerate_id(true); + $_SESSION['mailcow_cc_username'] = $login_user; + $_SESSION['mailcow_cc_role'] = "domainadmin"; + // If pending actions exist, redirect to /domainadmin to show modal + if (!empty($_SESSION['pending_tfa_setup']) || !empty($_SESSION['pending_pw_update'])) { + header("Location: /domainadmin"); + die(); + } + header("Location: /domainadmin/mailbox"); + die(); + } + elseif ($as != "pending") { + unset($_SESSION['pending_mailcow_cc_username']); + unset($_SESSION['pending_mailcow_cc_role']); + unset($_SESSION['pending_tfa_methods']); + unset($_SESSION['mailcow_cc_username']); + unset($_SESSION['mailcow_cc_role']); + } +} +?> diff --git a/data/web/inc/triggers.global.inc.php b/data/web/inc/triggers.global.inc.php new file mode 100644 index 0000000000..1f8f92004c --- /dev/null +++ b/data/web/inc/triggers.global.inc.php @@ -0,0 +1,67 @@ + "unset_fido2_key", "post_data" => $_POST)); + } +} +?> diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php deleted file mode 100644 index d9d8735134..0000000000 --- a/data/web/inc/triggers.inc.php +++ /dev/null @@ -1,100 +0,0 @@ - diff --git a/data/web/inc/triggers.user.inc.php b/data/web/inc/triggers.user.inc.php new file mode 100644 index 0000000000..5dcde0b189 --- /dev/null +++ b/data/web/inc/triggers.user.inc.php @@ -0,0 +1,183 @@ + $_POST['new_password'], + 'new_password2' => $_POST['new_password2'], + 'token' => $_POST['token'], + 'username' => $username, + 'check_tfa' => True + )); + + if ($reset_result){ + header("Location: /"); + exit; + } +} +if (isset($_POST["verify_tfa_login"])) { + if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) { + if ($_SESSION['pending_mailcow_cc_role'] == "user") { + if (isset($_SESSION['pending_pw_reset_token']) && isset($_SESSION['pending_pw_new_password'])) { + reset_password("reset", array( + 'new_password' => $_SESSION['pending_pw_new_password'], + 'new_password2' => $_SESSION['pending_pw_new_password'], + 'token' => $_SESSION['pending_pw_reset_token'], + 'username' => $_SESSION['pending_mailcow_cc_username'] + )); + unset($_SESSION['pending_pw_reset_token']); + unset($_SESSION['pending_pw_new_password']); + unset($_SESSION['pending_mailcow_cc_username']); + unset($_SESSION['pending_tfa_methods']); + + header("Location: /"); + die(); + } else { + set_user_loggedin_session($_SESSION['pending_mailcow_cc_username']); + + if (isset($_SESSION['oauth2_request'])) { + $oauth2_request = $_SESSION['oauth2_request']; + unset($_SESSION['oauth2_request']); + header('Location: ' . $oauth2_request); + die(); + } + + $user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']); + $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false; + // If pending actions exist, redirect to / to show modal + if (!empty($_SESSION['pending_tfa_setup']) || !empty($_SESSION['pending_pw_update'])) { + header("Location: /"); + die(); + } + if (intval($user_details['attributes']['sogo_access']) == 1 && + intval($user_details['attributes']['force_pw_update']) != 1 && + getenv('SKIP_SOGO') != "y" && + !$is_dual) { + header("Location: /SOGo/so/"); + die(); + } else { + header("Location: /user"); + die(); + } + } + } + } + + unset($_SESSION['pending_mailcow_cc_username']); + unset($_SESSION['pending_mailcow_cc_role']); + unset($_SESSION['pending_tfa_methods']); +} +if (isset($_POST["verify_fido2_login"])) { + $res = fido2(array( + "action" => "verify", + "token" => $_POST["token"], + "user" => "user" + )); + if (is_array($res) && $res['role'] == "user" && !empty($res['username'])){ + set_user_loggedin_session($res['username']); + $_SESSION["fido2_cid"] = $res['cid']; + } + exit; +} + +if (isset($_GET["cancel_tfa_login"])) { + unset($_SESSION['pending_pw_reset_token']); + unset($_SESSION['pending_pw_new_password']); + unset($_SESSION['pending_mailcow_cc_username']); + unset($_SESSION['pending_mailcow_cc_role']); + unset($_SESSION['pending_tfa_methods']); + + header("Location: /"); +} + +if (isset($_GET["cancel_tfa_setup"])) { + session_regenerate_id(true); + session_unset(); + session_destroy(); + session_write_close(); + header("Location: /"); + exit(); +} + +if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { + $login_user = strtolower(trim($_POST["login_user"])); + $as = check_login($login_user, $_POST["pass_user"], array("role" => "user", "service" => "MAILCOWUI")); + + if ($as == "user") { + set_user_loggedin_session($login_user); + $http_parameters = explode('&', $_SESSION['index_query_string']); + unset($_SESSION['index_query_string']); + if (in_array('mobileconfig', $http_parameters)) { + if (in_array('only_email', $http_parameters)) { + header("Location: /mobileconfig.php?only_email"); + die(); + } + header("Location: /mobileconfig.php"); + die(); + } + if (isset($_SESSION['oauth2_request'])) { + $oauth2_request = $_SESSION['oauth2_request']; + unset($_SESSION['oauth2_request']); + header('Location: ' . $oauth2_request); + die(); + } + + $user_details = mailbox("get", "mailbox_details", $login_user); + $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false; + // If pending actions exist, redirect to / to show modal + if (!empty($_SESSION['pending_tfa_setup']) || !empty($_SESSION['pending_pw_update'])) { + header("Location: /"); + die(); + } + if (intval($user_details['attributes']['sogo_access']) == 1 && + intval($user_details['attributes']['force_pw_update']) != 1 && + getenv('SKIP_SOGO') != "y" && + !$is_dual) { + header("Location: /SOGo/so/"); + die(); + } else { + header("Location: /user"); + die(); + } + } + elseif ($as != "pending") { + unset($_SESSION['pending_mailcow_cc_username']); + unset($_SESSION['pending_mailcow_cc_role']); + unset($_SESSION['pending_tfa_methods']); + unset($_SESSION['mailcow_cc_username']); + unset($_SESSION['mailcow_cc_role']); + } +} +?> diff --git a/data/web/inc/twig.inc.php b/data/web/inc/twig.inc.php new file mode 100644 index 0000000000..c4d0669d9e --- /dev/null +++ b/data/web/inc/twig.inc.php @@ -0,0 +1,29 @@ + $DEV_MODE, + 'cache' => $_SERVER['DOCUMENT_ROOT'].'/templates/cache', +]); + +// functions +$twig->addFunction(new TwigFunction('query_string', function (array $params = []) { + $allowed = ['lang', 'mobileconfig']; + $filtered = array_intersect_key($_GET, array_flip($allowed)); + return http_build_query(array_merge($filtered, $params)); +})); + +$twig->addFunction(new TwigFunction('is_uri', function (string $uri, string $where = null) { + if (is_null($where)) $where = $_SERVER['REQUEST_URI']; + return preg_match('/'.$uri.'/i', $where); +})); + +// filters +$twig->addFilter(new TwigFilter('rot13', 'str_rot13')); +$twig->addFilter(new TwigFilter('base64_encode', 'base64_encode')); +$twig->addFilter(new TwigFilter('formatBytes', 'formatBytes')); \ No newline at end of file diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index 9cb4c0dad9..cec402a968 100644 --- a/data/web/inc/vars.inc.php +++ b/data/web/inc/vars.inc.php @@ -17,6 +17,7 @@ // Other variables $mailcow_hostname = getenv('MAILCOW_HOSTNAME'); +$default_pass_scheme = getenv('MAILCOW_PASS_SCHEME'); // Autodiscover settings // === @@ -27,36 +28,40 @@ } else { $https_port = substr($_SERVER['HTTP_HOST'], $https_port+1); } + // Alternatively select port here => //$https_port = 1234; // Other settings => $autodiscover_config = array( + 'displayName' => 'A mailcow mail server', + 'displayShortName' => 'mail server', // General autodiscover service type: "activesync" or "imap" // emClient uses autodiscover, but does not support ActiveSync. mailcow excludes emClient from ActiveSync. + // With SOGo disabled, the type will always fallback to imap. CalDAV and CardDAV will be excluded, too. 'autodiscoverType' => 'activesync', // If autodiscoverType => activesync, also use ActiveSync (EAS) for Outlook desktop clients (>= Outlook 2013 on Windows) // Outlook for Mac does not support ActiveSync - 'useEASforOutlook' => 'yes', + 'useEASforOutlook' => 'no', // Please don't use STARTTLS-enabled service ports in the "port" variable. // The autodiscover service will always point to SMTPS and IMAPS (TLS-wrapped services). // The autoconfig service will additionally announce the STARTTLS-enabled ports, specified in the "tlsport" variable. 'imap' => array( 'server' => $mailcow_hostname, - 'port' => array_pop(explode(':', getenv('IMAPS_PORT'))), - 'tlsport' => array_pop(explode(':', getenv('IMAP_PORT'))), + 'port' => (int)filter_var(substr(getenv('IMAPS_PORT'), strrpos(getenv('IMAPS_PORT'), ':')), FILTER_SANITIZE_NUMBER_INT), + 'tlsport' => (int)filter_var(substr(getenv('IMAP_PORT'), strrpos(getenv('IMAP_PORT'), ':')), FILTER_SANITIZE_NUMBER_INT) ), 'pop3' => array( 'server' => $mailcow_hostname, - 'port' => array_pop(explode(':', getenv('POPS_PORT'))), - 'tlsport' => array_pop(explode(':', getenv('POP_PORT'))), + 'port' => (int)filter_var(substr(getenv('POPS_PORT'), strrpos(getenv('POPS_PORT'), ':')), FILTER_SANITIZE_NUMBER_INT), + 'tlsport' => (int)filter_var(substr(getenv('POP_PORT'), strrpos(getenv('POP_PORT'), ':')), FILTER_SANITIZE_NUMBER_INT) ), 'smtp' => array( 'server' => $mailcow_hostname, - 'port' => array_pop(explode(':', getenv('SMTPS_PORT'))), - 'tlsport' => array_pop(explode(':', getenv('SUBMISSION_PORT'))), + 'port' => (int)filter_var(substr(getenv('SMTPS_PORT'), strrpos(getenv('SMTPS_PORT'), ':')), FILTER_SANITIZE_NUMBER_INT), + 'tlsport' => (int)filter_var(substr(getenv('SUBMISSION_PORT'), strrpos(getenv('SUBMISSION_PORT'), ':')), FILTER_SANITIZE_NUMBER_INT) ), 'activesync' => array( - 'url' => 'https://'.$mailcow_hostname.($https_port == 443 ? '' : ':'.$https_port).'/Microsoft-Server-ActiveSync', + 'url' => 'https://' . $mailcow_hostname . ($https_port == 443 ? '' : ':' . $https_port) . '/Microsoft-Server-ActiveSync', ), 'caldav' => array( 'server' => $mailcow_hostname, @@ -67,32 +72,56 @@ 'port' => $https_port, ), ); -unset($https_port); // If false, we will use DEFAULT_LANG // Uses HTTP_ACCEPT_LANGUAGE header $DETECT_LANGUAGE = true; -// Change default language, "de", "en", "es", "nl", "pt", "ru" -$DEFAULT_LANG = 'en'; +// Change default language +$DEFAULT_LANG = 'en-gb'; // Available languages -$AVAILABLE_LANGUAGES = array('de', 'en', 'es', 'fr', 'lv', 'nl', 'pl', 'pt', 'ru', 'it', 'ca'); - -// Change theme (default: lumen) -// Needs to be one of those: cerulean, cosmo, cyborg, darkly, flatly, journal, lumen, paper, readable, sandstone, -// simplex, slate, spacelab, superhero, united, yeti -// See https://bootswatch.com/ -// WARNING: Only lumen is loaded locally. Enabling any other theme, will download external sources. -$DEFAULT_THEME = 'lumen'; - -// Password complexity as regular expression -// Min. 6 characters -//$PASSWD_REGEP = '.{6,}'; -// Min. 6 characters, which must include at least one uppercase letter, one lowercase letter and one number -// $PASSWD_REGEP = '^(?=.*[A-Z])(?=.*[0-9])(?=.*[a-z]).{6,}$'; -// Min. 6 characters, which must include at least one letter and one number -$PASSWD_REGEP = '^(?=.*[0-9])(?=.*[A-Za-z]).{6,}$'; +// https://www.iso.org/obp/ui/#search +// https://en.wikipedia.org/wiki/IETF_language_tag +$AVAILABLE_LANGUAGES = array( + // 'ca-es' => 'Catalร  (Catalan)', + 'bg-bg' => 'ะ‘ัŠะปะณะฐั€ัะบะธ (Bulgarian)', + 'cs-cz' => 'ฤŒeลกtina (Czech)', + 'da-dk' => 'Dansk (Danish)', + 'de-de' => 'Deutsch (German)', + 'en-gb' => 'English', + 'es-es' => 'Espaรฑol (Spanish)', + 'fi-fi' => 'Suomi (Finish)', + 'fr-fr' => 'Franรงais (French)', + 'gr-gr' => 'ฮ•ฮปฮปฮทฮฝฮนฮบฮฌ (Greek)', + 'hu-hu' => 'Magyar (Hungarian)', + 'it-it' => 'Italiano (Italian)', + 'ja-jp' => 'ๆ—ฅๆœฌ่ชž (Japanese)', + 'ko-kr' => 'ํ•œ๊ตญ์–ด (Korean)', + 'lv-lv' => 'latvieลกu (Latvian)', + 'lt-lt' => 'Lietuviลณ (Lithuanian)', + 'nb-no' => 'Norsk (Norwegian)', + 'nl-nl' => 'Nederlands (Dutch)', + 'pl-pl' => 'Jฤ™zyk Polski (Polish)', + 'pt-br' => 'Portuguรชs brasileiro (Brazilian Portuguese)', + 'pt-pt' => 'Portuguรชs (Portuguese)', + 'ro-ro' => 'Romรขnฤƒ (Romanian)', + 'ru-ru' => 'Pัƒััะบะธะน (Russian)', + 'si-si' => 'Slovenลกฤina (Slovenian)', + 'sk-sk' => 'Slovenฤina (Slovak)', + 'sv-se' => 'Svenska (Swedish)', + 'tr-tr' => 'Tรผrkรงe (Turkish)', + 'uk-ua' => 'ะฃะบั€ะฐั—ะฝััŒะบะฐ (Ukrainian)', + 'uz-uz' => 'Oสปzbekcha (Uzbek)', + 'vi-vn' => 'Tiแบฟng Viแป‡t (Vietnamese)', + 'zh-cn' => '็ฎ€ไฝ“ไธญๆ–‡ (Simplified Chinese)', + 'zh-tw' => '็น้ซ”ไธญๆ–‡ (Traditional Chinese)', +); + +// default theme is lumen +// additional themes can be found here: https://bootswatch.com/ +// copy them to data/web/css/themes/{THEME-NAME}-bootstrap.css +$UI_THEME = "lumen"; // Show DKIM private keys - false by default $SHOW_DKIM_PRIV_KEYS = false; @@ -100,34 +129,61 @@ // mailcow Apps - buttons on login screen $MAILCOW_APPS = array( array( - 'name' => 'SOGo', - 'link' => '/SOGo/', + 'name' => 'Webmail', + 'link' => '/SOGo/so', + 'user_link' => '/SOGo/so', + 'hide' => true ) ); +// Logo max file size in bytes +$LOGO_LIMITS['max_size'] = 15 * 1024 * 1024; // 15MB + +// Logo max width in pixels +$LOGO_LIMITS['max_width'] = 1920; + +// Logo max height in pixels +$LOGO_LIMITS['max_height'] = 1920; + // Rows until pagination begins -$PAGINATION_SIZE = 20; +$PAGINATION_SIZE = 25; // Default number of rows/lines to display (log table) -$LOG_LINES = 100; +$LOG_LINES = 1000; // Rows until pagination begins (log table) -$LOG_PAGINATION_SIZE = 30; +$LOG_PAGINATION_SIZE = 50; // Session lifetime in seconds -$SESSION_LIFETIME = 3600; +$SESSION_LIFETIME = 10800; + +// Session SameSite Policy +// Use "None", "Lax" or "Strict" +$SESSION_SAMESITE_POLICY = "Lax"; + +// Name of the session cookie +$SESSION_NAME = "MCSESSID"; // Label for OTP devices $OTP_LABEL = "mailcow UI"; -// Default "to" address in relay test tool -$RELAY_TO = "null@hosted.mailcow.de"; - // How long to wait (in s) for cURL Docker requests $DOCKER_TIMEOUT = 60; -// Anonymize IPs logged via UI -$ANONYMIZE_IPS = true; +// Split DKIM key notation (bind format) +$SPLIT_DKIM_255 = false; + +// OAuth2 settings +$REFRESH_TOKEN_LIFETIME = 2678400; +$ACCESS_TOKEN_LIFETIME = 86400; +// Logout from mailcow after first OAuth2 session profile request +$OAUTH2_FORGET_SESSION_AFTER_LOGIN = false; + +// Set a limit for mailbox and domain tagging +$TAGGING_LIMIT = 25; + +// MAILBOX_DEFAULT_ATTRIBUTES define default attributes for new mailboxes +// These settings will not change existing mailboxes // Force incoming TLS for new mailboxes by default $MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in'] = false; @@ -137,3 +193,219 @@ // Force password change on next login (only allows login to mailcow UI) $MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'] = false; + +// Force 2FA enrollment at next login +$MAILBOX_DEFAULT_ATTRIBUTES['force_tfa'] = false; + +// Enable SOGo access - Users will be redirected to SOGo after login (set to false to disable redirect by default) +$MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'] = true; + +// How to handle tagged emails +// none - No special handling +// subfolder - Create subfolder under INBOX (e.g. "INBOX/Facebook") +// subject - Add tag to subject (e.g. "[Facebook] Subject") +$MAILBOX_DEFAULT_ATTRIBUTES['tagged_mail_handler'] = "none"; + +// Send notification when quarantine is not empty (never, hourly, daily, weekly) +$MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification'] = 'hourly'; + +// Mailbox has IMAP access by default +$MAILBOX_DEFAULT_ATTRIBUTES['imap_access'] = true; + +// Mailbox has POP3 access by default +$MAILBOX_DEFAULT_ATTRIBUTES['pop3_access'] = true; + +// Mailbox has SMTP access by default +$MAILBOX_DEFAULT_ATTRIBUTES['smtp_access'] = true; + +// Mailbox has sieve access by default +$MAILBOX_DEFAULT_ATTRIBUTES['sieve_access'] = true; + +// Mailbox has ActiveSync/EAS access by default +$MAILBOX_DEFAULT_ATTRIBUTES['eas_access'] = true; + +// Mailbox has CalDAV/CardDAV (DAV) access by default +$MAILBOX_DEFAULT_ATTRIBUTES['dav_access'] = true; + +// Mailbox receives notifications about... +// "add_header" - mail that was put into the Junk folder +// "reject" - mail that was rejected +// "all" - mail that was rejected and put into the Junk folder +$MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category'] = 'reject'; + +// Default mailbox format, should not be changed unless you know exactly, what you do, keep the trailing ":" +// Check dovecot.conf for further changes (e.g. shared namespace) +$MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format'] = 'maildir:'; + +// Show last IMAP and POP3 logins +$SHOW_LAST_LOGIN = true; + +// Maximum number of password reset tokens that can be generated at once per user +$PW_RESET_TOKEN_LIMIT = 3; + +// Maximum time in minutes a password reset token is valid +$PW_RESET_TOKEN_LIFETIME = 15; + +// UV flag handling in FIDO2/WebAuthn - defaults to false to allow iOS logins +// true = required +// false = preferred +// string 'required' 'preferred' 'discouraged' +$WEBAUTHN_UV_FLAG_REGISTER = false; +$WEBAUTHN_UV_FLAG_LOGIN = false; +$WEBAUTHN_USER_PRESENT_FLAG = true; + +$FIDO2_UV_FLAG_REGISTER = 'preferred'; +$FIDO2_UV_FLAG_LOGIN = 'preferred'; // iOS ignores the key via NFC if required - known issue +$FIDO2_USER_PRESENT_FLAG = true; + +$FIDO2_FORMATS = array('apple', 'android-key', 'android-safetynet', 'fido-u2f', 'none', 'packed', 'tpm'); + + +// Set visible Rspamd maps in mailcow UI, do not change unless you know what you are doing +$RSPAMD_MAPS = array( + 'regex' => array( + 'Header-From: Denylist' => 'global_mime_from_blacklist.map', + 'Header-From: Allowlist' => 'global_mime_from_whitelist.map', + 'Envelope Sender Denylist' => 'global_smtp_from_blacklist.map', + 'Envelope Sender Allowlist' => 'global_smtp_from_whitelist.map', + 'Recipient Denylist' => 'global_rcpt_blacklist.map', + 'Recipient Allowlist' => 'global_rcpt_whitelist.map', + 'Fishy TLDS (only fired in combination with bad words)' => 'fishy_tlds.map', + 'Bad Words (only fired in combination with fishy TLDs)' => 'bad_words.map', + 'Bad Words DE (only fired in combination with fishy TLDs)' => 'bad_words_de.map', + 'Bad Languages' => 'bad_languages.map', + 'Bulk Mail Headers' => 'bulk_header.map', + 'Bad (Junk) Mail Headers' => 'bad_header.map', + 'Monitoring Hosts' => 'monitoring_nolog.map' + ) +); + + +$IMAPSYNC_OPTIONS = array( + 'whitelist' => array( + 'abort', + 'authmd51', + 'authmd52', + 'authmech1', + 'authmech2', + 'authuser1', + 'authuser2', + 'debug', + 'debugcontent', + 'debugcrossduplicates', + 'debugflags', + 'debugfolders', + 'debugimap', + 'debugimap1', + 'debugimap2', + 'debugmemory', + 'debugssl', + 'delete1emptyfolders', + 'delete2folders', + 'disarmreadreceipts', + 'domain1', + 'domain2', + 'domino1', + 'domino2', + 'dry', + 'errorsmax', + 'exchange1', + 'exchange2', + 'exitwhenover', + 'expunge1', + 'f1f2', + 'filterbuggyflags', + 'folder', + 'folderfirst', + 'folderlast', + 'folderrec', + 'gmail1', + 'gmail2', + 'idatefromheader', + 'include', + 'inet4', + 'inet6', + 'justconnect', + 'justfolders', + 'justfoldersizes', + 'justlogin', + 'keepalive1', + 'keepalive2', + 'log', + 'logdir', + 'logfile', + 'maxbytesafter', + 'maxlinelength', + 'maxmessagespersecond', + 'maxsize', + 'maxsleep', + 'minage', + 'minsize', + 'noabletosearch', + 'noabletosearch1', + 'noabletosearch2', + 'noexpunge1', + 'noexpunge2', + 'nofoldersizesatend', + 'noid', + 'nolog', + 'nomixfolders', + 'noresyncflags', + 'nossl1', + 'nossl2', + 'nosyncacls', + 'notls1', + 'notls2', + 'nouidexpunge2', + 'nousecache', + 'oauthaccesstoken1', + 'oauthaccesstoken2', + 'oauthdirect1', + 'oauthdirect2', + 'office1', + 'office2', + 'pidfile', + 'pidfilelocking', + 'prefix1', + 'prefix2', + 'proxyauth1', + 'proxyauth2', + 'resyncflags', + 'resynclabels', + 'search', + 'search1', + 'search2', + 'sep1', + 'sep2', + 'showpasswords', + 'skipemptyfolders', + 'ssl2', + 'sslargs1', + 'sslargs2', + 'subfolder1', + 'subscribe', + 'subscribed', + 'syncacls', + 'syncduplicates', + 'syncinternaldates', + 'synclabels', + 'tests', + 'testslive', + 'testslive6', + 'tls2', + 'truncmess', + 'usecache', + 'useheader', + 'useuid' + ), + 'blacklist' => array( + 'skipmess', + 'delete2foldersonly', + 'delete2foldersbutnot', + 'regexflag', + 'regexmess', + 'pipemess', + 'regextrans2', + 'maxlinelengthcmd' + ) +); diff --git a/data/web/index.php b/data/web/index.php index fcb35de94f..b037c14f12 100644 --- a/data/web/index.php +++ b/data/web/index.php @@ -1,113 +1,63 @@ -
    -
    -
    -
    -
    -
    - - -
    -
    - -
    -
    - -
    -
    -
    - -
    -
    - -
    -
    -
    - -
    - - -
    -
    -
    - -

    - - - -   - $val): - ?> -   - -
    -
    -
    -
    -
    -
    - -
    -
    -
    - -

    - -

    -

    -

    -

    - -
    -
    -
    -
    -
    -
    - - @$_SESSION['oauth2_request'], + 'is_mobileconfig' => str_contains($_SESSION['index_query_string'], 'mobileconfig'), + 'login_delay' => @$_SESSION['ldelay'], + 'has_iam_sso' => $has_iam_sso, + 'custom_login' => $custom_login, +]; + +$js_minifier->add('/web/js/site/index.js'); require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; diff --git a/data/web/js/add.js b/data/web/js/add.js deleted file mode 100644 index cbc1d3f12c..0000000000 --- a/data/web/js/add.js +++ /dev/null @@ -1,23 +0,0 @@ -$(document).ready(function() { - // Auto-fill domain quota when adding new domain - auto_fill_quota = function(domain) { - $.get("/api/v1/get/domain/" + domain, function(data){ - var result = $.parseJSON(JSON.stringify(data)); - max_new_mailbox_quota = ( result.max_new_mailbox_quota / 1048576); - if (max_new_mailbox_quota != '0') { - $("#quotaBadge").html('max. ' + max_new_mailbox_quota + ' MiB'); - $('#addInputQuota').attr({"disabled": false, "value": "", "type": "number", "max": max_new_mailbox_quota}); - $('#addInputQuota').val(max_new_mailbox_quota); - } - else { - $("#quotaBadge").html('max. ' + max_new_mailbox_quota + ' MiB'); - $('#addInputQuota').attr({"disabled": true, "value": "", "type": "text", "value": "n/a"}); - $('#addInputQuota').val(max_new_mailbox_quota); - } - }); - } - $('#addSelectDomain').on('change', function() { - auto_fill_quota($('#addSelectDomain').val()); - }); - auto_fill_quota($('#addSelectDomain').val()); -}); diff --git a/data/web/js/admin.js b/data/web/js/admin.js deleted file mode 100644 index d5ea7f67f3..0000000000 --- a/data/web/js/admin.js +++ /dev/null @@ -1,280 +0,0 @@ -// Base64 functions -var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}}; -jQuery(function($){ - // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery - var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; - function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} - function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e Test' + - ' ' + lang.edit + '' + - ' ' + lang.remove + '' + - ''; - item.chkbox = ''; - }); - } else if (table == 'forwardinghoststable') { - $.each(data, function (i, item) { - item.action = '
    ' + - ' ' + lang.remove + '' + - '
    '; - if (item.keep_spam == "yes") { - item.keep_spam = lang.no; - } - else { - item.keep_spam = lang.yes; - } - item.chkbox = ''; - }); - } else if (table == 'domainadminstable') { - $.each(data, function (i, item) { - item.selected_domains = escapeHtml(item.selected_domains); - item.selected_domains = item.selected_domains.toString().replace(/,/g, "
    "); - item.chkbox = ''; - item.action = ''; - }); - } else if (table == 'adminstable') { - $.each(data, function (i, item) { - if (admin_username == item.username) { - item.usr = 'โ†’ ' + item.username; - } else { - item.usr = item.username; - } - item.chkbox = ''; - item.action = ''; - }); - } - return data - }; - // Initial table drawings - draw_domain_admins(); - draw_admins(); - draw_fwd_hosts(); - draw_relayhosts(); - // Relayhost - $('#testRelayhostModal').on('show.bs.modal', function (e) { - $('#test_relayhost_result').text("-"); - button = $(e.relatedTarget) - if (button != null) { - $('#relayhost_id').val(button.data('relayhost-id')); - } - }) - $('#test_relayhost').on('click', function (e) { - e.preventDefault(); - prev = $('#test_relayhost').text(); - $(this).prop("disabled",true); - $(this).html(' '); - $.ajax({ - type: 'GET', - url: 'inc/ajax/relay_check.php', - dataType: 'text', - data: $('#test_relayhost_form').serialize(), - complete: function (data) { - $('#test_relayhost_result').html(data.responseText); - $('#test_relayhost').prop("disabled",false); - $('#test_relayhost').text(prev); - } - }); - }) - // DKIM private key modal - $('#showDKIMprivKey').on('show.bs.modal', function (e) { - $('#priv_key_pre').text("-"); - p_related = $(e.relatedTarget) - if (p_related != null) { - var decoded_key = Base64.decode((p_related.data('priv-key'))); - $('#priv_key_pre').text(decoded_key); - } - }) - // App links - function add_table_row(table_id) { - var row = $(''); - cols = ''; - cols += ''; - cols += 'Remove row'; - row.append(cols); - table_id.append(row); - } - $('#app_link_table').on('click', 'tr a', function (e) { - e.preventDefault(); - $(this).parents('tr').remove(); - }); - $('#add_app_link_row').click(function() { - add_table_row($('#app_link_table')); - }); -}); -$(window).load(function(){ - initial_width = $("#sidebar-admin").width(); - $("#scrollbox").css("width", initial_width); - if (sessionStorage.scrollTop > 70) { - $('#scrollbox').addClass('scrollboxFixed'); - } - $(window).bind('scroll', function() { - if ($(window).scrollTop() > 70) { - $('#scrollbox').addClass('scrollboxFixed'); - } else { - $('#scrollbox').removeClass('scrollboxFixed'); - } - }); -}); -function resizeScrollbox() { - on_resize_width = $("#sidebar-admin").width(); - $("#scrollbox").removeAttr("style"); - $("#scrollbox").css("width", on_resize_width); -} -$(window).on('resize', resizeScrollbox); -$('a[data-toggle="tab"]').on('shown.bs.tab', resizeScrollbox); diff --git a/data/web/js/api.js b/data/web/js/api.js deleted file mode 100644 index 1a5f4b06bd..0000000000 --- a/data/web/js/api.js +++ /dev/null @@ -1,317 +0,0 @@ -$(document).ready(function() { - function is_active(elem) { - if ($(elem).data('submitted') == '1') { - return true; - } else { - var parent_btn_grp = $(elem).parentsUntil(".btn-group").parent(); - if (parent_btn_grp.hasClass('btn-group')) { - parent_btn_grp.replaceWith(''+this.options.header+"":"",f=this.options.liveSearch?'':"",g=this.multiple&&this.options.actionsBox?'
    ":"",h=this.multiple&&this.options.doneButton?'
    ":"",j='
    ";return a(j)},createView:function(){var a=this.createDropdown(),b=this.createLi();return a.find("ul")[0].innerHTML=b,a},reloadLi:function(){var a=this.createLi();this.$menuInner[0].innerHTML=a},createLi:function(){var c=this,d=[],e=0,f=document.createElement("option"),g=-1,h=function(a,b,c,d){return""+a+""},j=function(d,e,f,g){return''+d+''};if(this.options.title&&!this.multiple&&(g--,!this.$element.find(".bs-title-option").length)){var k=this.$element[0];f.className="bs-title-option",f.innerHTML=this.options.title,f.value="",k.insertBefore(f,k.firstChild);var l=a(k.options[k.selectedIndex]);void 0===l.attr("selected")&&void 0===this.$element.data("selected")&&(f.selected=!0)}return this.$element.find("option").each(function(b){var f=a(this);if(g++,!f.hasClass("bs-title-option")){var k=this.className||"",l=this.style.cssText,m=f.data("content")?f.data("content"):f.html(),n=f.data("tokens")?f.data("tokens"):null,o="undefined"!=typeof f.data("subtext")?''+f.data("subtext")+"":"",p="undefined"!=typeof f.data("icon")?' ':"",q=f.parent(),r="OPTGROUP"===q[0].tagName,s=r&&q[0].disabled,t=this.disabled||s;if(""!==p&&t&&(p=""+p+""),c.options.hideDisabled&&(t&&!r||s))return void g--;if(f.data("content")||(m=p+''+m+o+""),r&&f.data("divider")!==!0){if(c.options.hideDisabled&&t){if(void 0===q.data("allOptionsDisabled")){var u=q.children();q.data("allOptionsDisabled",u.filter(":disabled").length===u.length)}if(q.data("allOptionsDisabled"))return void g--}var v=" "+q[0].className||"";if(0===f.index()){e+=1;var w=q[0].label,x="undefined"!=typeof q.data("subtext")?''+q.data("subtext")+"":"",y=q.data("icon")?' ':"";w=y+''+i(w)+x+"",0!==b&&d.length>0&&(g++,d.push(h("",null,"divider",e+"div"))),g++,d.push(h(w,null,"dropdown-header"+v,e))}if(c.options.hideDisabled&&t)return void g--;d.push(h(j(m,"opt "+k+v,l,n),b,"",e))}else if(f.data("divider")===!0)d.push(h("",b,"divider"));else if(f.data("hidden")===!0)d.push(h(j(m,k,l,n),b,"hidden is-hidden"));else{var z=this.previousElementSibling&&"OPTGROUP"===this.previousElementSibling.tagName;if(!z&&c.options.hideDisabled)for(var A=a(this).prevAll(),B=0;B ':"";return b=d.options.showSubtext&&c.data("subtext")&&!d.multiple?' '+c.data("subtext")+"":"","undefined"!=typeof c.attr("title")?c.attr("title"):c.data("content")&&d.options.showContent?c.data("content").toString():e+c.html()+b}}).toArray(),f=this.multiple?e.join(this.options.multipleSeparator):e[0];if(this.multiple&&this.options.selectedTextFormat.indexOf("count")>-1){var g=this.options.selectedTextFormat.split(">");if(g.length>1&&e.length>g[1]||1==g.length&&e.length>=2){c=this.options.hideDisabled?", [disabled]":"";var h=this.$element.find("option").not('[data-divider="true"], [data-hidden="true"]'+c).length,i="function"==typeof this.options.countSelectedText?this.options.countSelectedText(e.length,h):this.options.countSelectedText;f=i.replace("{0}",e.length.toString()).replace("{1}",h.toString())}}void 0==this.options.title&&(this.options.title=this.$element.attr("title")),"static"==this.options.selectedTextFormat&&(f=this.options.title),f||(f="undefined"!=typeof this.options.title?this.options.title:this.options.noneSelectedText),this.$button.attr("title",j(a.trim(f.replace(/<[^>]*>?/g,"")))),this.$button.children(".filter-option").html(f),this.$element.trigger("rendered.bs.select")},setStyle:function(a,b){this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,""));var c=a?a:this.options.style;"add"==b?this.$button.addClass(c):"remove"==b?this.$button.removeClass(c):(this.$button.removeClass(this.options.style),this.$button.addClass(c))},liHeight:function(b){if(b||this.options.size!==!1&&!this.sizeInfo){var c=document.createElement("div"),d=document.createElement("div"),e=document.createElement("ul"),f=document.createElement("li"),g=document.createElement("li"),h=document.createElement("a"),i=document.createElement("span"),j=this.options.header&&this.$menu.find(".popover-title").length>0?this.$menu.find(".popover-title")[0].cloneNode(!0):null,k=this.options.liveSearch?document.createElement("div"):null,l=this.options.actionsBox&&this.multiple&&this.$menu.find(".bs-actionsbox").length>0?this.$menu.find(".bs-actionsbox")[0].cloneNode(!0):null,m=this.options.doneButton&&this.multiple&&this.$menu.find(".bs-donebutton").length>0?this.$menu.find(".bs-donebutton")[0].cloneNode(!0):null;if(i.className="text",c.className=this.$menu[0].parentNode.className+" open",d.className="dropdown-menu open",e.className="dropdown-menu inner",f.className="divider",i.appendChild(document.createTextNode("Inner text")),h.appendChild(i),g.appendChild(h),e.appendChild(g),e.appendChild(f),j&&d.appendChild(j),k){var n=document.createElement("input");k.className="bs-searchbox",n.className="form-control",k.appendChild(n),d.appendChild(k)}l&&d.appendChild(l),d.appendChild(e),m&&d.appendChild(m),c.appendChild(d),document.body.appendChild(c);var o=h.offsetHeight,p=j?j.offsetHeight:0,q=k?k.offsetHeight:0,r=l?l.offsetHeight:0,s=m?m.offsetHeight:0,t=a(f).outerHeight(!0),u="function"==typeof getComputedStyle&&getComputedStyle(d),v=u?null:a(d),w={vert:parseInt(u?u.paddingTop:v.css("paddingTop"))+parseInt(u?u.paddingBottom:v.css("paddingBottom"))+parseInt(u?u.borderTopWidth:v.css("borderTopWidth"))+parseInt(u?u.borderBottomWidth:v.css("borderBottomWidth")),horiz:parseInt(u?u.paddingLeft:v.css("paddingLeft"))+parseInt(u?u.paddingRight:v.css("paddingRight"))+parseInt(u?u.borderLeftWidth:v.css("borderLeftWidth"))+parseInt(u?u.borderRightWidth:v.css("borderRightWidth"))},x={vert:w.vert+parseInt(u?u.marginTop:v.css("marginTop"))+parseInt(u?u.marginBottom:v.css("marginBottom"))+2,horiz:w.horiz+parseInt(u?u.marginLeft:v.css("marginLeft"))+parseInt(u?u.marginRight:v.css("marginRight"))+2};document.body.removeChild(c),this.sizeInfo={liHeight:o,headerHeight:p,searchHeight:q,actionsHeight:r,doneButtonHeight:s,dividerHeight:t,menuPadding:w,menuExtras:x}}},setSize:function(){if(this.findLis(),this.liHeight(),this.options.header&&this.$menu.css("padding-top",0),this.options.size!==!1){var b,c,d,e,f,g,h,i,j=this,k=this.$menu,l=this.$menuInner,m=a(window),n=this.$newElement[0].offsetHeight,o=this.$newElement[0].offsetWidth,p=this.sizeInfo.liHeight,q=this.sizeInfo.headerHeight,r=this.sizeInfo.searchHeight,s=this.sizeInfo.actionsHeight,t=this.sizeInfo.doneButtonHeight,u=this.sizeInfo.dividerHeight,v=this.sizeInfo.menuPadding,w=this.sizeInfo.menuExtras,x=this.options.hideDisabled?".disabled":"",y=function(){var b,c=j.$newElement.offset(),d=a(j.options.container);j.options.container&&!d.is("body")?(b=d.offset(),b.top+=parseInt(d.css("borderTopWidth")),b.left+=parseInt(d.css("borderLeftWidth"))):b={top:0,left:0};var e=j.options.windowPadding;f=c.top-b.top-m.scrollTop(),g=m.height()-f-n-b.top-e[2],h=c.left-b.left-m.scrollLeft(),i=m.width()-h-o-b.left-e[1],f-=e[0],h-=e[3]};if(y(),"auto"===this.options.size){var z=function(){var m,n=function(b,c){return function(d){return c?d.classList?d.classList.contains(b):a(d).hasClass(b):!(d.classList?d.classList.contains(b):a(d).hasClass(b))}},u=j.$menuInner[0].getElementsByTagName("li"),x=Array.prototype.filter?Array.prototype.filter.call(u,n("hidden",!1)):j.$lis.not(".hidden"),z=Array.prototype.filter?Array.prototype.filter.call(x,n("dropdown-header",!0)):x.filter(".dropdown-header");y(),b=g-w.vert,c=i-w.horiz,j.options.container?(k.data("height")||k.data("height",k.height()),d=k.data("height"),k.data("width")||k.data("width",k.width()),e=k.data("width")):(d=k.height(),e=k.width()),j.options.dropupAuto&&j.$newElement.toggleClass("dropup",f>g&&b-w.verti&&c-w.horiz3?3*p+w.vert-2:0,k.css({"max-height":b+"px",overflow:"hidden","min-height":m+q+r+s+t+"px"}),l.css({"max-height":b-q-r-s-t-v.vert+"px","overflow-y":"auto","min-height":Math.max(m-v.vert,0)+"px"})};z(),this.$searchbox.off("input.getSize propertychange.getSize").on("input.getSize propertychange.getSize",z),m.off("resize.getSize scroll.getSize").on("resize.getSize scroll.getSize",z)}else if(this.options.size&&"auto"!=this.options.size&&this.$lis.not(x).length>this.options.size){var A=this.$lis.not(".divider").not(x).children().slice(0,this.options.size).last().parent().index(),B=this.$lis.slice(0,A+1).filter(".divider").length;b=p*this.options.size+B*u+v.vert,j.options.container?(k.data("height")||k.data("height",k.height()),d=k.data("height")):d=k.height(),j.options.dropupAuto&&this.$newElement.toggleClass("dropup",f>g&&b-w.vert');var b,c,d,e=this,f=a(this.options.container),g=function(a){e.$bsContainer.addClass(a.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass("dropup",a.hasClass("dropup")),b=a.offset(),f.is("body")?c={top:0,left:0}:(c=f.offset(),c.top+=parseInt(f.css("borderTopWidth"))-f.scrollTop(),c.left+=parseInt(f.css("borderLeftWidth"))-f.scrollLeft()),d=a.hasClass("dropup")?0:a[0].offsetHeight,e.$bsContainer.css({top:b.top-c.top+d,left:b.left-c.left,width:a[0].offsetWidth})};this.$button.on("click",function(){var b=a(this);e.isDisabled()||(g(e.$newElement),e.$bsContainer.appendTo(e.options.container).toggleClass("open",!b.hasClass("open")).append(e.$menu))}),a(window).on("resize scroll",function(){g(e.$newElement)}),this.$element.on("hide.bs.select",function(){e.$menu.data("height",e.$menu.height()),e.$bsContainer.detach()})},setSelected:function(a,b,c){c||(this.togglePlaceholder(),c=this.findLis().eq(this.liObj[a])),c.toggleClass("selected",b).find("a").attr("aria-selected",b)},setDisabled:function(a,b,c){c||(c=this.findLis().eq(this.liObj[a])),b?c.addClass("disabled").children("a").attr("href","#").attr("tabindex",-1).attr("aria-disabled",!0):c.removeClass("disabled").children("a").removeAttr("href").attr("tabindex",0).attr("aria-disabled",!1)},isDisabled:function(){return this.$element[0].disabled},checkDisabled:function(){var a=this;this.isDisabled()?(this.$newElement.addClass("disabled"),this.$button.addClass("disabled").attr("tabindex",-1).attr("aria-disabled",!0)):(this.$button.hasClass("disabled")&&(this.$newElement.removeClass("disabled"),this.$button.removeClass("disabled").attr("aria-disabled",!1)),this.$button.attr("tabindex")!=-1||this.$element.data("tabindex")||this.$button.removeAttr("tabindex")),this.$button.click(function(){return!a.isDisabled()})},togglePlaceholder:function(){var a=this.$element.val();this.$button.toggleClass("bs-placeholder",null===a||""===a||a.constructor===Array&&0===a.length)},tabIndex:function(){this.$element.data("tabindex")!==this.$element.attr("tabindex")&&this.$element.attr("tabindex")!==-98&&"-98"!==this.$element.attr("tabindex")&&(this.$element.data("tabindex",this.$element.attr("tabindex")),this.$button.attr("tabindex",this.$element.data("tabindex"))),this.$element.attr("tabindex",-98)},clickListener:function(){var b=this,c=a(document);c.data("spaceSelect",!1),this.$button.on("keyup",function(a){/(32)/.test(a.keyCode.toString(10))&&c.data("spaceSelect")&&(a.preventDefault(),c.data("spaceSelect",!1))}),this.$button.on("click",function(){b.setSize()}),this.$element.on("shown.bs.select",function(){if(b.options.liveSearch||b.multiple){if(!b.multiple){var a=b.liObj[b.$element[0].selectedIndex];if("number"!=typeof a||b.options.size===!1)return;var c=b.$lis.eq(a)[0].offsetTop-b.$menuInner[0].offsetTop;c=c-b.$menuInner[0].offsetHeight/2+b.sizeInfo.liHeight/2,b.$menuInner[0].scrollTop=c}}else b.$menuInner.find(".selected a").focus()}),this.$menuInner.on("click","li a",function(c){var d=a(this),f=d.parent().data("originalIndex"),g=b.$element.val(),h=b.$element.prop("selectedIndex"),i=!0;if(b.multiple&&1!==b.options.maxOptions&&c.stopPropagation(),c.preventDefault(),!b.isDisabled()&&!d.parent().hasClass("disabled")){var j=b.$element.find("option"),k=j.eq(f),l=k.prop("selected"),m=k.parent("optgroup"),n=b.options.maxOptions,o=m.data("maxOptions")||!1;if(b.multiple){if(k.prop("selected",!l),b.setSelected(f,!l),d.blur(),n!==!1||o!==!1){var p=n');t[2]&&(u=u.replace("{var}",t[2][n>1?0:1]),v=v.replace("{var}",t[2][o>1?0:1])),k.prop("selected",!1),b.$menu.append(w),n&&p&&(w.append(a("
    "+u+"
    ")),i=!1,b.$element.trigger("maxReached.bs.select")),o&&q&&(w.append(a("
    "+v+"
    ")),i=!1,b.$element.trigger("maxReachedGrp.bs.select")),setTimeout(function(){b.setSelected(f,!1)},10),w.delay(750).fadeOut(300,function(){a(this).remove()})}}}else j.prop("selected",!1),k.prop("selected",!0),b.$menuInner.find(".selected").removeClass("selected").find("a").attr("aria-selected",!1),b.setSelected(f,!0);!b.multiple||b.multiple&&1===b.options.maxOptions?b.$button.focus():b.options.liveSearch&&b.$searchbox.focus(),i&&(g!=b.$element.val()&&b.multiple||h!=b.$element.prop("selectedIndex")&&!b.multiple)&&(e=[f,k.prop("selected"),l],b.$element.triggerNative("change"))}}),this.$menu.on("click","li.disabled a, .popover-title, .popover-title :not(.close)",function(c){c.currentTarget==this&&(c.preventDefault(),c.stopPropagation(),b.options.liveSearch&&!a(c.target).hasClass("close")?b.$searchbox.focus():b.$button.focus())}),this.$menuInner.on("click",".divider, .dropdown-header",function(a){a.preventDefault(),a.stopPropagation(),b.options.liveSearch?b.$searchbox.focus():b.$button.focus()}),this.$menu.on("click",".popover-title .close",function(){b.$button.click()}),this.$searchbox.on("click",function(a){a.stopPropagation()}),this.$menu.on("click",".actions-btn",function(c){b.options.liveSearch?b.$searchbox.focus():b.$button.focus(),c.preventDefault(),c.stopPropagation(),a(this).hasClass("bs-select-all")?b.selectAll():b.deselectAll()}),this.$element.change(function(){b.render(!1),b.$element.trigger("changed.bs.select",e),e=null})},liveSearchListener:function(){var c=this,d=a('
  • ');this.$button.on("click.dropdown.data-api",function(){c.$menuInner.find(".active").removeClass("active"),c.$searchbox.val()&&(c.$searchbox.val(""),c.$lis.not(".is-hidden").removeClass("hidden"),d.parent().length&&d.remove()),c.multiple||c.$menuInner.find(".selected").addClass("active"),setTimeout(function(){c.$searchbox.focus()},10)}),this.$searchbox.on("click.dropdown.data-api focus.dropdown.data-api touchend.dropdown.data-api",function(a){a.stopPropagation()}),this.$searchbox.on("input propertychange",function(){if(c.$lis.not(".is-hidden").removeClass("hidden"),c.$lis.filter(".active").removeClass("active"),d.remove(),c.$searchbox.val()){var e,f=c.$lis.not(".is-hidden, .divider, .dropdown-header");if(e=c.options.liveSearchNormalize?f.find("a").not(":a"+c._searchStyle()+'("'+b(c.$searchbox.val())+'")'):f.find("a").not(":"+c._searchStyle()+'("'+c.$searchbox.val()+'")'),e.length===f.length)d.html(c.options.noneResultsText.replace("{0}",'"'+i(c.$searchbox.val())+'"')),c.$menuInner.append(d),c.$lis.addClass("hidden");else{e.parent().addClass("hidden");var g,h=c.$lis.not(".hidden");h.each(function(b){var c=a(this);c.hasClass("divider")?void 0===g?c.addClass("hidden"):(g&&g.addClass("hidden"),g=c):c.hasClass("dropdown-header")&&h.eq(b+1).data("optgroup")!==c.data("optgroup")?c.addClass("hidden"):g=null}),g&&g.addClass("hidden"),f.not(".hidden").first().addClass("active")}}})},_searchStyle:function(){var a={begins:"ibegins",startsWith:"ibegins"};return a[this.options.liveSearchStyle]||"icontains"},val:function(a){return"undefined"!=typeof a?(this.$element.val(a),this.render(),this.$element):this.$element.val()},changeAll:function(b){if(this.multiple){"undefined"==typeof b&&(b=!0),this.findLis();var c=this.$element.find("option"),d=this.$lis.not(".divider, .dropdown-header, .disabled, .hidden"),e=d.length,f=[];if(b){if(d.filter(".selected").length===d.length)return}else if(0===d.filter(".selected").length)return;d.toggleClass("selected",b);for(var g=0;g=48&&c.keyCode<=57||c.keyCode>=96&&c.keyCode<=105||c.keyCode>=65&&c.keyCode<=90))return o.options.container?o.$button.trigger("click"):(o.setSize(),o.$menu.parent().addClass("open"),l=!0),void o.$searchbox.focus();if(o.options.liveSearch&&(/(^9$|27)/.test(c.keyCode.toString(10))&&l&&(c.preventDefault(),c.stopPropagation(),o.$menuInner.click(),o.$button.focus()),d=a('[role="listbox"] li'+p,n),m.val()||/(38|40)/.test(c.keyCode.toString(10))||0===d.filter(".active").length&&(d=o.$menuInner.find("li"),d=o.options.liveSearchNormalize?d.filter(":a"+o._searchStyle()+"("+b(q[c.keyCode])+")"):d.filter(":"+o._searchStyle()+"("+q[c.keyCode]+")"))),d.length){if(/(38|40)/.test(c.keyCode.toString(10)))e=d.index(d.find("a").filter(":focus").parent()),g=d.filter(p).first().index(),h=d.filter(p).last().index(),f=d.eq(e).nextAll(p).eq(0).index(),i=d.eq(e).prevAll(p).eq(0).index(),j=d.eq(f).prevAll(p).eq(0).index(),o.options.liveSearch&&(d.each(function(b){a(this).hasClass("disabled")||a(this).data("index",b)}),e=d.index(d.filter(".active")),g=d.first().data("index"),h=d.last().data("index"),f=d.eq(e).nextAll().eq(0).data("index"),i=d.eq(e).prevAll().eq(0).data("index"),j=d.eq(f).prevAll().eq(0).data("index")),k=m.data("prevIndex"),38==c.keyCode?(o.options.liveSearch&&e--,e!=j&&e>i&&(e=i),eh&&(e=h),e==k&&(e=g)),m.data("prevIndex",e),o.options.liveSearch?(c.preventDefault(),m.hasClass("dropdown-toggle")||(d.removeClass("active").eq(e).addClass("active").children("a").focus(),m.focus())):d.eq(e).children("a").focus();else if(!m.is("input")){var r,s,t=[];d.each(function(){a(this).hasClass("disabled")||a.trim(a(this).children("a").text().toLowerCase()).substring(0,1)==q[c.keyCode]&&t.push(a(this).index())}),r=a(document).data("keycount"),r++,a(document).data("keycount",r),s=a.trim(a(":focus").text().toLowerCase()).substring(0,1),s!=q[c.keyCode]?(r=1,a(document).data("keycount",r)):r>=t.length&&(a(document).data("keycount",0),r>t.length&&(r=1)),d.eq(t[r-1]).children("a").focus()}if((/(13|32)/.test(c.keyCode.toString(10))||/(^9$)/.test(c.keyCode.toString(10))&&o.options.selectOnTab)&&l){if(/(32)/.test(c.keyCode.toString(10))||c.preventDefault(),o.options.liveSearch)/(32)/.test(c.keyCode.toString(10))||(o.$menuInner.find(".active a").click(), -m.focus());else{var u=a(":focus");u.click(),u.focus(),c.preventDefault(),a(document).data("spaceSelect",!0)}a(document).data("keycount",0)}(/(^9$|27)/.test(c.keyCode.toString(10))&&l&&(o.multiple||o.options.liveSearch)||/(27)/.test(c.keyCode.toString(10))&&!l)&&(o.$menu.parent().removeClass("open"),o.options.container&&o.$newElement.removeClass("open"),o.$button.focus())}},mobile:function(){this.$element.addClass("mobile-device")},refresh:function(){this.$lis=null,this.liObj={},this.reloadLi(),this.render(),this.checkDisabled(),this.liHeight(!0),this.setStyle(),this.setWidth(),this.$lis&&this.$searchbox.trigger("propertychange"),this.$element.trigger("refreshed.bs.select")},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),this.$bsContainer?this.$bsContainer.remove():this.$menu.remove(),this.$element.off(".bs.select").removeData("selectpicker").removeClass("bs-select-hidden selectpicker")}};var l=a.fn.selectpicker;a.fn.selectpicker=c,a.fn.selectpicker.Constructor=k,a.fn.selectpicker.noConflict=function(){return a.fn.selectpicker=l,this},a(document).data("keycount",0).on("keydown.bs.select",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input',k.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input',function(a){a.stopPropagation()}),a(window).on("load.bs.select.data-api",function(){a(".selectpicker").each(function(){var b=a(this);c.call(b,b.data())})})}(a)}); diff --git a/data/web/js/bootstrap-slider.min.js b/data/web/js/bootstrap-slider.min.js deleted file mode 100644 index 72363a2726..0000000000 --- a/data/web/js/bootstrap-slider.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! ======================================================= - VERSION 9.7.2 -========================================================= */ -"use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},windowIsDefined="object"===("undefined"==typeof window?"undefined":_typeof(window));!function(a){if("function"==typeof define&&define.amd)define(["jquery"],a);else if("object"===("undefined"==typeof module?"undefined":_typeof(module))&&module.exports){var b;try{b=require("jquery")}catch(c){b=null}module.exports=a(b)}else window&&(window.Slider=a(window.jQuery))}(function(a){var b="slider",c="bootstrapSlider";windowIsDefined&&!window.console&&(window.console={}),windowIsDefined&&!window.console.log&&(window.console.log=function(){}),windowIsDefined&&!window.console.warn&&(window.console.warn=function(){});var d;return function(a){function b(){}function c(a){function c(b){b.prototype.option||(b.prototype.option=function(b){a.isPlainObject(b)&&(this.options=a.extend(!0,this.options,b))})}function e(b,c){a.fn[b]=function(e){if("string"==typeof e){for(var g=d.call(arguments,1),h=0,i=this.length;i>h;h++){var j=this[h],k=a.data(j,b);if(k)if(a.isFunction(k[e])&&"_"!==e.charAt(0)){var l=k[e].apply(k,g);if(void 0!==l&&l!==k)return l}else f("no such method '"+e+"' for "+b+" instance");else f("cannot call methods on "+b+" prior to initialization; attempted to call '"+e+"'")}return this}var m=this.map(function(){var d=a.data(this,b);return d?(d.option(e),d._init()):(d=new c(this,e),a.data(this,b,d)),a(this)});return!m||m.length>1?m:m[0]}}if(a){var f="undefined"==typeof console?b:function(a){console.error(a)};return a.bridget=function(a,b){c(b),e(a,b)},a.bridget}}var d=Array.prototype.slice;c(a)}(a),function(a){function e(b,c){function d(a,b){var c="data-slider-"+b.replace(/_/g,"-"),d=a.getAttribute(c);try{return JSON.parse(d)}catch(e){return d}}this._state={value:null,enabled:null,offset:null,size:null,percentage:null,inDrag:!1,over:!1},this.ticksCallbackMap={},this.handleCallbackMap={},"string"==typeof b?this.element=document.querySelector(b):b instanceof HTMLElement&&(this.element=b),c=c?c:{};for(var e=Object.keys(this.defaultOptions),f=0;f0)for(var s=0;s0){for(this.ticksContainer=document.createElement("div"),this.ticksContainer.className="slider-tick-container",f=0;f0)for(this.tickLabelContainer=document.createElement("div"),this.tickLabelContainer.className="slider-tick-label-container",f=0;f0&&(this.options.max=Math.max.apply(Math,this.options.ticks),this.options.min=Math.min.apply(Math,this.options.ticks)),Array.isArray(this.options.value)?(this.options.range=!0,this._state.value=this.options.value):this.options.range?this._state.value=[this.options.value,this.options.max]:this._state.value=this.options.value,this.trackLow=k||this.trackLow,this.trackSelection=j||this.trackSelection,this.trackHigh=l||this.trackHigh,"none"===this.options.selection?(this._addClass(this.trackLow,"hide"),this._addClass(this.trackSelection,"hide"),this._addClass(this.trackHigh,"hide")):("after"===this.options.selection||"before"===this.options.selection)&&(this._removeClass(this.trackLow,"hide"),this._removeClass(this.trackSelection,"hide"),this._removeClass(this.trackHigh,"hide")),this.handle1=m||this.handle1,this.handle2=n||this.handle2,p===!0)for(this._removeClass(this.handle1,"round triangle"),this._removeClass(this.handle2,"round triangle hide"),f=0;f0){for(var d,e,f,g=0,h=1;hthis.options.max?this.options.max:k},toPercentage:function(a){if(this.options.max===this.options.min)return 0;if(this.options.ticks_positions.length>0){for(var b,c,d,e=0,f=0;f0?this.options.ticks[f-1]:0,d=f>0?this.options.ticks_positions[f-1]:0,c=this.options.ticks[f],e=this.options.ticks_positions[f];break}if(f>0){var g=(a-b)/(c-b);return d+g*(e-d)}}return 100*(a-this.options.min)/(this.options.max-this.options.min)}},logarithmic:{toValue:function(a){var b=0===this.options.min?0:Math.log(this.options.min),c=Math.log(this.options.max),d=Math.exp(b+(c-b)*a/100);return d=this.options.min+Math.round((d-this.options.min)/this.options.step)*this.options.step,dthis.options.max?this.options.max:d},toPercentage:function(a){if(this.options.max===this.options.min)return 0;var b=Math.log(this.options.max),c=0===this.options.min?0:Math.log(this.options.min),d=0===a?0:Math.log(a);return 100*(d-c)/(b-c)}}};d=function(a,b){return e.call(this,a,b),this},d.prototype={_init:function(){},constructor:d,defaultOptions:{id:"",min:0,max:10,step:1,precision:0,orientation:"horizontal",value:5,range:!1,selection:"before",tooltip:"show",tooltip_split:!1,handle:"round",reversed:!1,rtl:"auto",enabled:!0,formatter:function(a){return Array.isArray(a)?a[0]+" : "+a[1]:a},natural_arrow_keys:!1,ticks:[],ticks_positions:[],ticks_labels:[],ticks_snap_bounds:0,ticks_tooltip:!1,scale:"linear",focus:!1,tooltip_position:null,labelledby:null,rangeHighlights:[]},getElement:function(){return this.sliderElem},getValue:function(){return this.options.range?this._state.value:this._state.value[0]},setValue:function(a,b,c){a||(a=0);var d=this.getValue();this._state.value=this._validateInputValue(a);var e=this._applyPrecision.bind(this);this.options.range?(this._state.value[0]=e(this._state.value[0]),this._state.value[1]=e(this._state.value[1]),this._state.value[0]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[0])),this._state.value[1]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[1]))):(this._state.value=e(this._state.value),this._state.value=[Math.max(this.options.min,Math.min(this.options.max,this._state.value))],this._addClass(this.handle2,"hide"),"after"===this.options.selection?this._state.value[1]=this.options.max:this._state.value[1]=this.options.min),this.options.max>this.options.min?this._state.percentage=[this._toPercentage(this._state.value[0]),this._toPercentage(this._state.value[1]),100*this.options.step/(this.options.max-this.options.min)]:this._state.percentage=[0,0,100],this._layout();var f=this.options.range?this._state.value:this._state.value[0];return this._setDataVal(f),b===!0&&this._trigger("slide",f),d!==f&&c===!0&&this._trigger("change",{oldValue:d,newValue:f}),this},destroy:function(){this._removeSliderEventHandlers(),this.sliderElem.parentNode.removeChild(this.sliderElem),this.element.style.display="",this._cleanUpEventCallbacksMap(),this.element.removeAttribute("data"),a&&(this._unbindJQueryEventHandlers(),this.$element.removeData("slider"))},disable:function(){return this._state.enabled=!1,this.handle1.removeAttribute("tabindex"),this.handle2.removeAttribute("tabindex"),this._addClass(this.sliderElem,"slider-disabled"),this._trigger("slideDisabled"),this},enable:function(){return this._state.enabled=!0,this.handle1.setAttribute("tabindex",0),this.handle2.setAttribute("tabindex",0),this._removeClass(this.sliderElem,"slider-disabled"),this._trigger("slideEnabled"),this},toggle:function(){return this._state.enabled?this.disable():this.enable(),this},isEnabled:function(){return this._state.enabled},on:function(a,b){return this._bindNonQueryEventHandler(a,b),this},off:function(b,c){a?(this.$element.off(b,c),this.$sliderElem.off(b,c)):this._unbindNonQueryEventHandler(b,c)},getAttribute:function(a){return a?this.options[a]:this.options},setAttribute:function(a,b){return this.options[a]=b,this},refresh:function(){return this._removeSliderEventHandlers(),e.call(this,this.element,this.options),a&&a.data(this.element,"slider",this),this},relayout:function(){return this._resize(),this._layout(),this},_removeSliderEventHandlers:function(){if(this.handle1.removeEventListener("keydown",this.handle1Keydown,!1),this.handle2.removeEventListener("keydown",this.handle2Keydown,!1),this.options.ticks_tooltip){for(var a=this.ticksContainer.getElementsByClassName("slider-tick"),b=0;b=0?c:this.attributes["aria-valuenow"].value,e=parseInt(d,10);b.value[0]=e,b.percentage[0]=a.options.ticks_positions[e],a._setToolTipOnMouseOver(b),a._showTooltip()};return b.addEventListener("mouseenter",d,!1),d},addMouseLeave:function(a,b){var c=function(){a._hideTooltip()};return b.addEventListener("mouseleave",c,!1),c}}},_layout:function(){var a;if(a=this.options.reversed?[100-this._state.percentage[0],this.options.range?100-this._state.percentage[1]:this._state.percentage[1]]:[this._state.percentage[0],this._state.percentage[1]],this.handle1.style[this.stylePos]=a[0]+"%",this.handle1.setAttribute("aria-valuenow",this._state.value[0]),isNaN(this.options.formatter(this._state.value[0]))&&this.handle1.setAttribute("aria-valuetext",this.options.formatter(this._state.value[0])),this.handle2.style[this.stylePos]=a[1]+"%",this.handle2.setAttribute("aria-valuenow",this._state.value[1]),isNaN(this.options.formatter(this._state.value[1]))&&this.handle2.setAttribute("aria-valuetext",this.options.formatter(this._state.value[1])),this.rangeHighlightElements.length>0&&Array.isArray(this.options.rangeHighlights)&&this.options.rangeHighlights.length>0)for(var b=0;b0){var g,h="vertical"===this.options.orientation?"height":"width";g="vertical"===this.options.orientation?"marginTop":this.options.rtl?"marginRight":"marginLeft";var i=this._state.size/(this.options.ticks.length-1);if(this.tickLabelContainer){var j=0;if(0===this.options.ticks_positions.length)"vertical"!==this.options.orientation&&(this.tickLabelContainer.style[g]=-i/2+"px"),j=this.tickLabelContainer.offsetHeight;else for(k=0;kj&&(j=this.tickLabelContainer.childNodes[k].offsetHeight);"horizontal"===this.options.orientation&&(this.sliderElem.style.marginBottom=j+"px")}for(var k=0;k=a[0]&&l<=a[1]&&this._addClass(this.ticks[k],"in-selection"):"after"===this.options.selection&&l>=a[0]?this._addClass(this.ticks[k],"in-selection"):"before"===this.options.selection&&l<=a[0]&&this._addClass(this.ticks[k],"in-selection"),this.tickLabels[k]&&(this.tickLabels[k].style[h]=i+"px","vertical"!==this.options.orientation&&void 0!==this.options.ticks_positions[k]?(this.tickLabels[k].style.position="absolute",this.tickLabels[k].style[this.stylePos]=l+"%",this.tickLabels[k].style[g]=-i/2+"px"):"vertical"===this.options.orientation&&(this.options.rtl?this.tickLabels[k].style.marginRight=this.sliderElem.offsetWidth+"px":this.tickLabels[k].style.marginLeft=this.sliderElem.offsetWidth+"px",this.tickLabelContainer.style[g]=this.sliderElem.offsetWidth/2*-1+"px"))}}var m;if(this.options.range){m=this.options.formatter(this._state.value),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=(a[1]+a[0])/2+"%","vertical"===this.options.orientation?this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetWidth/2+"px");var n=this.options.formatter(this._state.value[0]);this._setText(this.tooltipInner_min,n);var o=this.options.formatter(this._state.value[1]);this._setText(this.tooltipInner_max,o),this.tooltip_min.style[this.stylePos]=a[0]+"%","vertical"===this.options.orientation?this._css(this.tooltip_min,"margin-"+this.stylePos,-this.tooltip_min.offsetHeight/2+"px"):this._css(this.tooltip_min,"margin-"+this.stylePos,-this.tooltip_min.offsetWidth/2+"px"),this.tooltip_max.style[this.stylePos]=a[1]+"%","vertical"===this.options.orientation?this._css(this.tooltip_max,"margin-"+this.stylePos,-this.tooltip_max.offsetHeight/2+"px"):this._css(this.tooltip_max,"margin-"+this.stylePos,-this.tooltip_max.offsetWidth/2+"px")}else m=this.options.formatter(this._state.value[0]),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=a[0]+"%","vertical"===this.options.orientation?this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetWidth/2+"px");if("vertical"===this.options.orientation)this.trackLow.style.top="0",this.trackLow.style.height=Math.min(a[0],a[1])+"%",this.trackSelection.style.top=Math.min(a[0],a[1])+"%",this.trackSelection.style.height=Math.abs(a[0]-a[1])+"%",this.trackHigh.style.bottom="0",this.trackHigh.style.height=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";else{"right"===this.stylePos?this.trackLow.style.right="0":this.trackLow.style.left="0",this.trackLow.style.width=Math.min(a[0],a[1])+"%","right"===this.stylePos?this.trackSelection.style.right=Math.min(a[0],a[1])+"%":this.trackSelection.style.left=Math.min(a[0],a[1])+"%",this.trackSelection.style.width=Math.abs(a[0]-a[1])+"%","right"===this.stylePos?this.trackHigh.style.left="0":this.trackHigh.style.right="0",this.trackHigh.style.width=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";var p=this.tooltip_min.getBoundingClientRect(),q=this.tooltip_max.getBoundingClientRect();"bottom"===this.options.tooltip_position?p.right>q.left?(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top="",this.tooltip_max.style.bottom="22px"):(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top=this.tooltip_min.style.top,this.tooltip_max.style.bottom=""):p.right>q.left?(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top="18px"):(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top=this.tooltip_min.style.top)}},_createHighlightRange:function(a,b){return this._isHighlightRange(a,b)?a>b?{start:b,size:a-b}:{start:a,size:b-a}:null},_isHighlightRange:function(a,b){return a>=0&&100>=a&&b>=0&&100>=b?!0:!1},_resize:function(a){this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos],this._layout()},_removeProperty:function(a,b){a.style.removeProperty?a.style.removeProperty(b):a.style.removeAttribute(b)},_mousedown:function(a){if(!this._state.enabled)return!1;this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos];var b=this._getPercentage(a);if(this.options.range){var c=Math.abs(this._state.percentage[0]-b),d=Math.abs(this._state.percentage[1]-b);this._state.dragged=d>c?0:1,this._adjustPercentageForRangeSliders(b)}else this._state.dragged=0;this._state.percentage[this._state.dragged]=b,this._layout(),this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),this.mousemove&&document.removeEventListener("mousemove",this.mousemove,!1),this.mouseup&&document.removeEventListener("mouseup",this.mouseup,!1),this.mousemove=this._mousemove.bind(this),this.mouseup=this._mouseup.bind(this),this.touchCapable&&(document.addEventListener("touchmove",this.mousemove,!1),document.addEventListener("touchend",this.mouseup,!1)),document.addEventListener("mousemove",this.mousemove,!1),document.addEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!0;var e=this._calculateValue();return this._trigger("slideStart",e),this._setDataVal(e),this.setValue(e,!1,!0),a.returnValue=!1,this.options.focus&&this._triggerFocusOnHandle(this._state.dragged),!0},_touchstart:function(a){if(void 0===a.changedTouches)return void this._mousedown(a);var b=a.changedTouches[0];this.touchX=b.pageX,this.touchY=b.pageY},_triggerFocusOnHandle:function(a){0===a&&this.handle1.focus(),1===a&&this.handle2.focus()},_keydown:function(a,b){if(!this._state.enabled)return!1;var c;switch(b.keyCode){case 37:case 40:c=-1;break;case 39:case 38:c=1}if(c){if(this.options.natural_arrow_keys){var d="vertical"===this.options.orientation&&!this.options.reversed,e="horizontal"===this.options.orientation&&this.options.reversed;(d||e)&&(c=-c)}var f=this._state.value[a]+c*this.options.step,g=f/this.options.max*100;if(this._state.keyCtrl=a,this.options.range){this._adjustPercentageForRangeSliders(g);var h=this._state.keyCtrl?this._state.value[0]:f,i=this._state.keyCtrl?f:this._state.value[1];f=[h,i]}return this._trigger("slideStart",f),this._setDataVal(f),this.setValue(f,!0,!0),this._setDataVal(f),this._trigger("slideStop",f),this._layout(),this._pauseEvent(b),delete this._state.keyCtrl,!1}},_pauseEvent:function(a){a.stopPropagation&&a.stopPropagation(),a.preventDefault&&a.preventDefault(),a.cancelBubble=!0,a.returnValue=!1},_mousemove:function(a){if(!this._state.enabled)return!1;var b=this._getPercentage(a);this._adjustPercentageForRangeSliders(b),this._state.percentage[this._state.dragged]=b,this._layout();var c=this._calculateValue(!0);return this.setValue(c,!0,!0),!1},_touchmove:function(a){if(void 0!==a.changedTouches){var b=a.changedTouches[0],c=b.pageX-this.touchX,d=b.pageY-this.touchY;this._state.inDrag||("vertical"===this.options.orientation&&5>=c&&c>=-5&&(d>=15||-15>=d)?this._mousedown(a):5>=d&&d>=-5&&(c>=15||-15>=c)&&this._mousedown(a))}},_adjustPercentageForRangeSliders:function(a){if(this.options.range){var b=this._getNumDigitsAfterDecimalPlace(a);b=b?b-1:0;var c=this._applyToFixedAndParseFloat(a,b);0===this._state.dragged&&this._applyToFixedAndParseFloat(this._state.percentage[1],b)c?(this._state.percentage[1]=this._state.percentage[0],this._state.dragged=0):0===this._state.keyCtrl&&this._state.value[1]/this.options.max*100a&&(this._state.percentage[1]=this._state.percentage[0],this._state.keyCtrl=0,this.handle1.focus())}},_mouseup:function(){if(!this._state.enabled)return!1;this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),document.removeEventListener("mousemove",this.mousemove,!1),document.removeEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!1,this._state.over===!1&&this._hideTooltip();var a=this._calculateValue(!0);return this._layout(),this._setDataVal(a),this._trigger("slideStop",a),!1},_calculateValue:function(a){var b;if(this.options.range?(b=[this.options.min,this.options.max],0!==this._state.percentage[0]&&(b[0]=this._toValue(this._state.percentage[0]),b[0]=this._applyPrecision(b[0])),100!==this._state.percentage[1]&&(b[1]=this._toValue(this._state.percentage[1]),b[1]=this._applyPrecision(b[1]))):(b=this._toValue(this._state.percentage[0]),b=parseFloat(b),b=this._applyPrecision(b)),a){for(var c=[b,1/0],d=0;d (http://larentis.eu) - * @license Apache-2.0 - */ - -'use strict';var _createClass=function(){function a(b,c){for(var e,d=0;d',{class:function _class(){var h=[];return h.push(g.options.state?'on':'off'),g.options.size&&h.push(g.options.size),g.options.disabled&&h.push('disabled'),g.options.readonly&&h.push('readonly'),g.options.indeterminate&&h.push('indeterminate'),g.options.inverse&&h.push('inverse'),g.$element.attr('id')&&h.push('id-'+g.$element.attr('id')),h.map(g._getClass.bind(g)).concat([g.options.baseClass],g._getClasses(g.options.wrapperClass)).join(' ')}}),this.$container=a('
    ',{class:this._getClass('container')}),this.$on=a('',{html:this.options.onText,class:this._getClass('handle-on')+' '+this._getClass(this.options.onColor)}),this.$off=a('',{html:this.options.offText,class:this._getClass('handle-off')+' '+this._getClass(this.options.offColor)}),this.$label=a('',{html:this.options.labelText,class:this._getClass('label')}),this.$element.on('init.bootstrapSwitch',this.options.onInit.bind(this,e)),this.$element.on('switchChange.bootstrapSwitch',function(){for(var j=arguments.length,h=Array(j),k=0;k-(f._handleWidth/2);f._dragEnd=!1,f.state(f.options.inverse?!h:h)}else f.state(!f.options.state);f._dragStart=!1}},'mouseleave.bootstrapSwitch':function mouseleaveBootstrapSwitch(){f.$label.trigger('mouseup.bootstrapSwitch')}})}},{key:'_externalLabelHandler',value:function _externalLabelHandler(){var f=this,e=this.$element.closest('label');e.on('click',function(g){g.preventDefault(),g.stopImmediatePropagation(),g.target===e[0]&&f.toggleState()})}},{key:'_formHandler',value:function _formHandler(){var e=this.$element.closest('form');e.data('bootstrap-switch')||e.on('reset.bootstrapSwitch',function(){b.setTimeout(function(){e.find('input').filter(function(){return a(this).data('bootstrap-switch')}).each(function(){return a(this).bootstrapSwitch('state',this.checked)})},1)}).data('bootstrap-switch',!0)}},{key:'_getClass',value:function _getClass(e){return this.options.baseClass+'-'+e}},{key:'_getClasses',value:function _getClasses(e){return a.isArray(e)?e.map(this._getClass.bind(this)):[this._getClass(e)]}}]),d}();a.fn.bootstrapSwitch=function(d){for(var f=arguments.length,e=Array(13)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/data/web/js/build/000-jquery-3.6.0.min.js b/data/web/js/build/000-jquery-3.6.0.min.js new file mode 100644 index 0000000000..c4c6022f29 --- /dev/null +++ b/data/web/js/build/000-jquery-3.6.0.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
    ",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void I(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach((i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach((o=>{if(o.includes(n)){const n=s[o];I(t,e,i,n.originalHandler,n.delegationSelector)}}))}(t,l,i,e.slice(1))}));const h=l[r]||{};Object.keys(h).forEach((i=>{const n=i.replace(E,"");if(!a||e.includes(n)){const e=h[i];I(t,l,r,e.originalHandler,e.delegationSelector)}}))},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=f(),s=P(e),o=e!==s,r=k.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach((t=>{Object.defineProperty(d,t,{get:()=>i[t]})})),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};class B{constructor(t){(t=r(t))&&(this._element=t,H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach((t=>{this[t]=null}))}_queueCallback(t,e,i=!0){b(t,e,i)}static getInstance(t){return H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.1.3"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}}const R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),c(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class W extends B{static get NAME(){return"alert"}close(){if(j.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),j.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(W,"close"),g(W);const $='[data-bs-toggle="button"]';class z extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function q(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function F(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}j.on(document,"click.bs.button.data-api",$,(t=>{t.preventDefault();const e=t.target.closest($);z.getOrCreateInstance(e).toggle()})),g(z);const U={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter((t=>t.startsWith("bs"))).forEach((i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=q(t.dataset[i])})),e},getDataAttribute:(t,e)=>q(t.getAttribute(`data-bs-${F(e)}`)),offset(t){const e=t.getBoundingClientRect();return{top:e.top+window.pageYOffset,left:e.left+window.pageXOffset}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},V={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(", ");return this.find(e,t).filter((t=>!c(t)&&l(t)))}},K="carousel",X={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},Y={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Q="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z},et="slid.bs.carousel",it="active",nt=".active.carousel-item";class st extends B{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return X}static get NAME(){return K}next(){this._slide(Q)}nextWhenVisible(){!document.hidden&&l(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),V.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(s(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=V.findOne(nt,this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,et,(()=>this.to(t)));if(e===t)return this.pause(),void this.cycle();const i=t>e?Q:G;this._slide(i,this._items[t])}_getConfig(t){return t={...X,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(K,t,Y),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&j.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,"mouseenter.bs.carousel",(t=>this.pause(t))),j.on(this._element,"mouseleave.bs.carousel",(t=>this.cycle(t)))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>this._pointerEvent&&("pen"===t.pointerType||"touch"===t.pointerType),e=e=>{t(e)?this.touchStartX=e.clientX:this._pointerEvent||(this.touchStartX=e.touches[0].clientX)},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=e=>{t(e)&&(this.touchDeltaX=e.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((t=>this.cycle(t)),500+this._config.interval))};V.find(".carousel-item img",this._element).forEach((t=>{j.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()))})),this._pointerEvent?(j.on(this._element,"pointerdown.bs.carousel",(t=>e(t))),j.on(this._element,"pointerup.bs.carousel",(t=>n(t))),this._element.classList.add("pointer-event")):(j.on(this._element,"touchstart.bs.carousel",(t=>e(t))),j.on(this._element,"touchmove.bs.carousel",(t=>i(t))),j.on(this._element,"touchend.bs.carousel",(t=>n(t))))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?V.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===Q;return v(this._items,e,i,this._config.wrap)}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),n=this._getItemIndex(V.findOne(nt,this._element));return j.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=V.findOne(".active",this._indicatorsElement);e.classList.remove(it),e.removeAttribute("aria-current");const i=V.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{j.trigger(this._element,et,{relatedTarget:o,direction:d,from:s,to:r})};if(this._element.classList.contains("slide")){o.classList.add(h),u(o),n.classList.add(c),o.classList.add(c);const t=()=>{o.classList.remove(c,h),o.classList.add(it),n.classList.remove(it,h,c),this._isSliding=!1,setTimeout(f,0)};this._queueCallback(t,n,!0)}else n.classList.remove(it),o.classList.add(it),this._isSliding=!1,f();a&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?m()?t===Z?G:Q:t===Z?Q:G:t}_orderToDirection(t){return[Q,G].includes(t)?m()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const i=st.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){st.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=n(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},s=this.getAttribute("data-bs-slide-to");s&&(i.interval=!1),st.carouselInterface(e,i),s&&st.getInstance(e).to(s),t.preventDefault()}}j.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",st.dataApiClickHandler),j.on(window,"load.bs.carousel.data-api",(()=>{const t=V.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;et===this._element));null!==s&&o.length&&(this._selector=s,this._triggerArray.push(e))}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return rt}static get NAME(){return ot}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t,e=[];if(this._config.parent){const t=V.find(ut,this._config.parent);e=V.find(".collapse.show, .collapse.collapsing",this._config.parent).filter((e=>!t.includes(e)))}const i=V.findOne(this._selector);if(e.length){const n=e.find((t=>i!==t));if(t=n?pt.getInstance(n):null,t&&t._isTransitioning)return}if(j.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e.forEach((e=>{i!==e&&pt.getOrCreateInstance(e,{toggle:!1}).hide(),t||H.set(e,"bs.collapse",null)}));const n=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[n]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const s=`scroll${n[0].toUpperCase()+n.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[n]="",j.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[n]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,u(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);const e=this._triggerArray.length;for(let t=0;t{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),j.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_getConfig(t){return(t={...rt,...U.getDataAttributes(this._element),...t}).toggle=Boolean(t.toggle),t.parent=r(t.parent),a(ot,t,at),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=V.find(ut,this._config.parent);V.find(ft,this._config.parent).filter((e=>!t.includes(e))).forEach((t=>{const e=n(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}))}_addAriaAndCollapsedClass(t,e){t.length&&t.forEach((t=>{e?t.classList.remove(dt):t.classList.add(dt),t.setAttribute("aria-expanded",e)}))}static jQueryInterface(t){return this.each((function(){const e={};"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1);const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,"click.bs.collapse.data-api",ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this);V.find(e).forEach((t=>{pt.getOrCreateInstance(t,{toggle:!1}).toggle()}))})),g(pt);var mt="top",gt="bottom",_t="right",bt="left",vt="auto",yt=[mt,gt,_t,bt],wt="start",Et="end",At="clippingParents",Tt="viewport",Ot="popper",Ct="reference",kt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+Et])}),[]),Lt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+Et])}),[]),xt="beforeRead",Dt="read",St="afterRead",Nt="beforeMain",It="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",Bt=[xt,Dt,St,Nt,It,Pt,jt,Mt,Ht];function Rt(t){return t?(t.nodeName||"").toLowerCase():null}function Wt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function $t(t){return t instanceof Wt(t).Element||t instanceof Element}function zt(t){return t instanceof Wt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Wt(t).ShadowRoot||t instanceof ShadowRoot)}const Ft={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Rt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Rt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Ut(t){return t.split("-")[0]}function Vt(t,e){var i=t.getBoundingClientRect();return{width:i.width/1,height:i.height/1,top:i.top/1,right:i.right/1,bottom:i.bottom/1,left:i.left/1,x:i.left/1,y:i.top/1}}function Kt(t){var e=Vt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Xt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Yt(t){return Wt(t).getComputedStyle(t)}function Qt(t){return["table","td","th"].indexOf(Rt(t))>=0}function Gt(t){return(($t(t)?t.ownerDocument:t.document)||window.document).documentElement}function Zt(t){return"html"===Rt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||Gt(t)}function Jt(t){return zt(t)&&"fixed"!==Yt(t).position?t.offsetParent:null}function te(t){for(var e=Wt(t),i=Jt(t);i&&Qt(i)&&"static"===Yt(i).position;)i=Jt(i);return i&&("html"===Rt(i)||"body"===Rt(i)&&"static"===Yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&zt(t)&&"fixed"===Yt(t).position)return null;for(var i=Zt(t);zt(i)&&["html","body"].indexOf(Rt(i))<0;){var n=Yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function ee(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var ie=Math.max,ne=Math.min,se=Math.round;function oe(t,e,i){return ie(t,ne(e,i))}function re(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ae(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const le={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=Ut(i.placement),l=ee(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return re("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ae(t,yt))}(s.padding,i),d=Kt(o),u="y"===l?mt:bt,f="y"===l?gt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=te(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=oe(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Xt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ce(t){return t.split("-")[1]}var he={top:"auto",right:"auto",bottom:"auto",left:"auto"};function de(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:se(se(e*n)/n)||0,y:se(se(i*n)/n)||0}}(r):"function"==typeof h?h(r):r,u=d.x,f=void 0===u?0:u,p=d.y,m=void 0===p?0:p,g=r.hasOwnProperty("x"),_=r.hasOwnProperty("y"),b=bt,v=mt,y=window;if(c){var w=te(i),E="clientHeight",A="clientWidth";w===Wt(i)&&"static"!==Yt(w=Gt(i)).position&&"absolute"===a&&(E="scrollHeight",A="scrollWidth"),w=w,s!==mt&&(s!==bt&&s!==_t||o!==Et)||(v=gt,m-=w[E]-n.height,m*=l?1:-1),s!==bt&&(s!==mt&&s!==gt||o!==Et)||(b=_t,f-=w[A]-n.width,f*=l?1:-1)}var T,O=Object.assign({position:a},c&&he);return l?Object.assign({},O,((T={})[v]=_?"0":"",T[b]=g?"0":"",T.transform=(y.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",T)):Object.assign({},O,((e={})[v]=_?m+"px":"",e[b]=g?f+"px":"",e.transform="",e))}const ue={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:Ut(e.placement),variation:ce(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,de(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,de(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var fe={passive:!0};const pe={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=Wt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,fe)})),a&&l.addEventListener("resize",i.update,fe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,fe)})),a&&l.removeEventListener("resize",i.update,fe)}},data:{}};var me={left:"right",right:"left",bottom:"top",top:"bottom"};function ge(t){return t.replace(/left|right|bottom|top/g,(function(t){return me[t]}))}var _e={start:"end",end:"start"};function be(t){return t.replace(/start|end/g,(function(t){return _e[t]}))}function ve(t){var e=Wt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ye(t){return Vt(Gt(t)).left+ve(t).scrollLeft}function we(t){var e=Yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ee(t){return["html","body","#document"].indexOf(Rt(t))>=0?t.ownerDocument.body:zt(t)&&we(t)?t:Ee(Zt(t))}function Ae(t,e){var i;void 0===e&&(e=[]);var n=Ee(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Wt(n),r=s?[o].concat(o.visualViewport||[],we(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ae(Zt(r)))}function Te(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Oe(t,e){return e===Tt?Te(function(t){var e=Wt(t),i=Gt(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+ye(t),y:a}}(t)):zt(e)?function(t){var e=Vt(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Te(function(t){var e,i=Gt(t),n=ve(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ie(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ie(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ye(t),l=-n.scrollTop;return"rtl"===Yt(s||i).direction&&(a+=ie(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Gt(t)))}function Ce(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Ut(s):null,r=s?ce(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case mt:e={x:a,y:i.y-n.height};break;case gt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?ee(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Et:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ke(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?At:o,a=i.rootBoundary,l=void 0===a?Tt:a,c=i.elementContext,h=void 0===c?Ot:c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=re("number"!=typeof p?p:ae(p,yt)),g=h===Ot?Ct:Ot,_=t.rects.popper,b=t.elements[u?g:h],v=function(t,e,i){var n="clippingParents"===e?function(t){var e=Ae(Zt(t)),i=["absolute","fixed"].indexOf(Yt(t).position)>=0&&zt(t)?te(t):t;return $t(i)?e.filter((function(t){return $t(t)&&Xt(t,i)&&"body"!==Rt(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Oe(t,i);return e.top=ie(n.top,e.top),e.right=ne(n.right,e.right),e.bottom=ne(n.bottom,e.bottom),e.left=ie(n.left,e.left),e}),Oe(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}($t(b)?b:b.contextElement||Gt(t.elements.popper),r,l),y=Vt(t.elements.reference),w=Ce({reference:y,element:_,strategy:"absolute",placement:s}),E=Te(Object.assign({},_,w)),A=h===Ot?E:y,T={top:v.top-A.top+m.top,bottom:A.bottom-v.bottom+m.bottom,left:v.left-A.left+m.left,right:A.right-v.right+m.right},O=t.modifiersData.offset;if(h===Ot&&O){var C=O[s];Object.keys(T).forEach((function(t){var e=[_t,gt].indexOf(t)>=0?1:-1,i=[mt,gt].indexOf(t)>=0?"y":"x";T[t]+=C[i]*e}))}return T}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?Lt:l,h=ce(n),d=h?a?kt:kt.filter((function(t){return ce(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ke(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Ut(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const xe={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=Ut(g),b=l||(_!==g&&p?function(t){if(Ut(t)===vt)return[];var e=ge(t);return[be(t),e,be(e)]}(g):[ge(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(Ut(i)===vt?Le(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=ke(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),N=x?L?_t:bt:L?gt:mt;y[D]>w[D]&&(N=ge(N));var I=ge(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[I]<=0),P.every((function(t){return t}))){T=C,A=!1;break}E.set(C,P)}if(A)for(var j=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function De(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Se(t){return[mt,_t,gt,bt].some((function(e){return t[e]>=0}))}const Ne={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=De(r,n),c=De(a,s,o),h=Se(l),d=Se(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Ie={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=Lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Ut(t),s=[bt,mt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Pe={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Ce({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},je={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Ut(e.placement),b=ce(e.placement),v=!b,y=ee(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?mt:bt,L="y"===y?gt:_t,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],N=E[y]-g[L],I=f?-T[x]/2:0,P=b===wt?A[x]:T[x],j=b===wt?-T[x]:-A[x],M=e.elements.arrow,H=f&&M?Kt(M):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},R=B[k],W=B[L],$=oe(0,A[x],H[x]),z=v?A[x]/2-I-$-R-O:P-$-R-O,q=v?-A[x]/2+I+$+W+O:j+$+W+O,F=e.elements.arrow&&te(e.elements.arrow),U=F?"y"===y?F.clientTop||0:F.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-U,X=E[y]+q-V;if(o){var Y=oe(f?ne(S,K):S,D,f?ie(N,X):N);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?mt:bt,G="x"===y?gt:_t,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=oe(f?ne(J,K):J,Z,f?ie(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function Me(t,e,i){void 0===i&&(i=!1);var n=zt(e);zt(e)&&function(t){var e=t.getBoundingClientRect();e.width,t.offsetWidth,e.height,t.offsetHeight}(e);var s,o,r=Gt(e),a=Vt(t),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(n||!n&&!i)&&(("body"!==Rt(e)||we(r))&&(l=(s=e)!==Wt(s)&&zt(s)?{scrollLeft:(o=s).scrollLeft,scrollTop:o.scrollTop}:ve(s)),zt(e)?((c=Vt(e)).x+=e.clientLeft,c.y+=e.clientTop):r&&(c.x=ye(r))),{x:a.left+l.scrollLeft-c.x,y:a.top+l.scrollTop-c.y,width:a.width,height:a.height}}function He(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Re(){for(var t=arguments.length,e=new Array(t),i=0;ij.on(t,"mouseover",d))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Je),this._element.classList.add(Je),j.trigger(this._element,"shown.bs.dropdown",t)}hide(){if(c(this._element)||!this._isShown(this._menu))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){j.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._popper&&this._popper.destroy(),this._menu.classList.remove(Je),this._element.classList.remove(Je),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},a(Ue,t,this.constructor.DefaultType),"object"==typeof t.reference&&!o(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ue.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(t){if(void 0===Fe)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:o(this._config.reference)?e=r(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find((t=>"applyStyles"===t.name&&!1===t.enabled));this._popper=qe(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}_isShown(t=this._element){return t.classList.contains(Je)}_getMenuElement(){return V.next(this._element,ei)[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ri;if(t.classList.contains("dropstart"))return ai;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ni:ii:e?oi:si}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(l);i.length&&v(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=V.find(ti);for(let i=0,n=e.length;ie+t)),this._setElementAttributes(di,"paddingRight",(e=>e+t)),this._setElementAttributes(ui,"marginRight",(e=>e-t))}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=`${i(Number.parseFloat(s))}px`}))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(di,"paddingRight"),this._resetElementAttributes(ui,"marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)}))}_applyManipulationCallback(t,e){o(t)?e(t):V.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const pi={className:"modal-backdrop",isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},mi={className:"string",isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"},gi="show",_i="mousedown.bs.backdrop";class bi{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&u(this._getElement()),this._getElement().classList.add(gi),this._emulateAnimation((()=>{_(t)}))):_(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove(gi),this._emulateAnimation((()=>{this.dispose(),_(t)}))):_(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...pi,..."object"==typeof t?t:{}}).rootElement=r(t.rootElement),a("backdrop",t,mi),t}_append(){this._isAppended||(this._config.rootElement.append(this._getElement()),j.on(this._getElement(),_i,(()=>{_(this._config.clickCallback)})),this._isAppended=!0)}dispose(){this._isAppended&&(j.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){b(t,this._getElement(),this._config.isAnimated)}}const vi={trapElement:null,autofocus:!0},yi={trapElement:"element",autofocus:"boolean"},wi=".bs.focustrap",Ei="backward";class Ai{constructor(t){this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}activate(){const{trapElement:t,autofocus:e}=this._config;this._isActive||(e&&t.focus(),j.off(document,wi),j.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),j.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,wi))}_handleFocusin(t){const{target:e}=t,{trapElement:i}=this._config;if(e===document||e===i||i.contains(e))return;const n=V.focusableChildren(i);0===n.length?i.focus():this._lastTabNavDirection===Ei?n[n.length-1].focus():n[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ei:"forward")}_getConfig(t){return t={...vi,..."object"==typeof t?t:{}},a("focustrap",t,yi),t}}const Ti="modal",Oi="Escape",Ci={backdrop:!0,keyboard:!0,focus:!0},ki={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"},Li="hidden.bs.modal",xi="show.bs.modal",Di="resize.bs.modal",Si="click.dismiss.bs.modal",Ni="keydown.dismiss.bs.modal",Ii="mousedown.dismiss.bs.modal",Pi="modal-open",ji="show",Mi="modal-static";class Hi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new fi}static get Default(){return Ci}static get NAME(){return Ti}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add(Pi),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),j.on(this._dialog,Ii,(()=>{j.one(this._element,"mouseup.dismiss.bs.modal",(t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)}))})),this._showBackdrop((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;if(j.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const t=this._isAnimated();t&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),this._focustrap.deactivate(),this._element.classList.remove(ji),j.off(this._element,Si),j.off(this._dialog,Ii),this._queueCallback((()=>this._hideModal()),this._element,t)}dispose(){[window,this._dialog].forEach((t=>j.off(t,".bs.modal"))),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_getConfig(t){return t={...Ci,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Ti,t,ki),t}_showElement(t){const e=this._isAnimated(),i=V.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,i&&(i.scrollTop=0),e&&u(this._element),this._element.classList.add(ji),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,e)}_setEscapeEvent(){this._isShown?j.on(this._element,Ni,(t=>{this._config.keyboard&&t.key===Oi?(t.preventDefault(),this.hide()):this._config.keyboard||t.key!==Oi||this._triggerBackdropTransition()})):j.off(this._element,Ni)}_setResizeEvent(){this._isShown?j.on(window,Di,(()=>this._adjustDialog())):j.off(window,Di)}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Pi),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,Li)}))}_showBackdrop(t){j.on(this._element,Si,(t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())})),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains(Mi)||(n||(i.overflowY="hidden"),t.add(Mi),this._queueCallback((()=>{t.remove(Mi),n||this._queueCallback((()=>{i.overflowY=""}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!m()||i&&!t&&m())&&(this._element.style.paddingLeft=`${e}px`),(i&&!t&&!m()||!i&&t&&m())&&(this._element.style.paddingRight=`${e}px`)}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,xi,(t=>{t.defaultPrevented||j.one(e,Li,(()=>{l(this)&&this.focus()}))}));const i=V.findOne(".modal.show");i&&Hi.getInstance(i).hide(),Hi.getOrCreateInstance(e).toggle(this)})),R(Hi),g(Hi);const Bi="offcanvas",Ri={backdrop:!0,keyboard:!0,scroll:!1},Wi={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"},$i="show",zi=".offcanvas.show",qi="hidden.bs.offcanvas";class Fi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get NAME(){return Bi}static get Default(){return Ri}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(new fi).hide(),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($i),this._queueCallback((()=>{this._config.scroll||this._focustrap.activate(),j.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.remove($i),this._backdrop.hide(),this._queueCallback((()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new fi).reset(),j.trigger(this._element,qi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_getConfig(t){return t={...Ri,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Bi,t,Wi),t}_initializeBackDrop(){return new bi({className:"offcanvas-backdrop",isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_addEventListeners(){j.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}))}static jQueryInterface(t){return this.each((function(){const e=Fi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}j.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this))return;j.one(e,qi,(()=>{l(this)&&this.focus()}));const i=V.findOne(zi);i&&i!==e&&Fi.getInstance(i).hide(),Fi.getOrCreateInstance(e).toggle(this)})),j.on(window,"load.bs.offcanvas.data-api",(()=>V.find(zi).forEach((t=>Fi.getOrCreateInstance(t).show())))),R(Fi),g(Fi);const Ui=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Vi=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Ki=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Xi=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!Ui.has(i)||Boolean(Vi.test(t.nodeValue)||Ki.test(t.nodeValue));const n=e.filter((t=>t instanceof RegExp));for(let t=0,e=n.length;t{Xi(t,r)||i.removeAttribute(t.nodeName)}))}return n.body.innerHTML}const Qi="tooltip",Gi=new Set(["sanitize","allowList","sanitizeFn"]),Zi={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Ji={AUTO:"auto",TOP:"top",RIGHT:m()?"left":"right",BOTTOM:"bottom",LEFT:m()?"right":"left"},tn={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},en={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},nn="fade",sn="show",on="show",rn="out",an=".tooltip-inner",ln=".modal",cn="hide.bs.modal",hn="hover",dn="focus";class un extends B{constructor(t,e){if(void 0===Fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return tn}static get NAME(){return Qi}static get Event(){return en}static get DefaultType(){return Zi}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains(sn))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ln),cn,this._hideModalHandler),this.tip&&this.tip.remove(),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=j.trigger(this._element,this.constructor.Event.SHOW),e=h(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;"tooltip"===this.constructor.NAME&&this.tip&&this.getTitle()!==this.tip.querySelector(an).innerHTML&&(this._disposePopper(),this.tip.remove(),this.tip=null);const n=this.getTipElement(),s=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME);n.setAttribute("id",s),this._element.setAttribute("aria-describedby",s),this._config.animation&&n.classList.add(nn);const o="function"==typeof this._config.placement?this._config.placement.call(this,n,this._element):this._config.placement,r=this._getAttachment(o);this._addAttachmentClass(r);const{container:a}=this._config;H.set(n,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(a.append(n),j.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=qe(this._element,n,this._getPopperConfig(r)),n.classList.add(sn);const l=this._resolvePossibleFunction(this._config.customClass);l&&n.classList.add(...l.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>{j.on(t,"mouseover",d)}));const c=this.tip.classList.contains(nn);this._queueCallback((()=>{const t=this._hoverState;this._hoverState=null,j.trigger(this._element,this.constructor.Event.SHOWN),t===rn&&this._leave(null,this)}),this.tip,c)}hide(){if(!this._popper)return;const t=this.getTipElement();if(j.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove(sn),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains(nn);this._queueCallback((()=>{this._isWithActiveTrigger()||(this._hoverState!==on&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.Event.HIDDEN),this._disposePopper())}),this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");t.innerHTML=this._config.template;const e=t.children[0];return this.setContent(e),e.classList.remove(nn,sn),this.tip=e,this.tip}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),an)}_sanitizeAndSetContent(t,e,i){const n=V.findOne(i,t);e||!n?this.setElementContent(n,e):n.remove()}setElementContent(t,e){if(null!==t)return o(e)?(e=r(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.append(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Yi(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){const t=this._element.getAttribute("data-bs-original-title")||this._config.title;return this._resolvePossibleFunction(t)}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){return e||this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`)}_getAttachment(t){return Ji[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach((t=>{if("click"===t)j.on(this._element,this.constructor.Event.CLICK,this._config.selector,(t=>this.toggle(t)));else if("manual"!==t){const e=t===hn?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i=t===hn?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;j.on(this._element,e,this._config.selector,(t=>this._enter(t))),j.on(this._element,i,this._config.selector,(t=>this._leave(t)))}})),this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ln),cn,this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?dn:hn]=!0),e.getTipElement().classList.contains(sn)||e._hoverState===on?e._hoverState=on:(clearTimeout(e._timeout),e._hoverState=on,e._config.delay&&e._config.delay.show?e._timeout=setTimeout((()=>{e._hoverState===on&&e.show()}),e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?dn:hn]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=rn,e._config.delay&&e._config.delay.hide?e._timeout=setTimeout((()=>{e._hoverState===rn&&e.hide()}),e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach((t=>{Gi.has(t)&&delete e[t]})),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),a(Qi,t,this.constructor.DefaultType),t.sanitize&&(t.template=Yi(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`,"g"),i=t.getAttribute("class").match(e);null!==i&&i.length>0&&i.map((t=>t.trim())).forEach((e=>t.classList.remove(e)))}_getBasicClassPrefix(){return"bs-tooltip"}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn={...un.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},pn={...un.DefaultType,content:"(string|element|function)"},mn={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class gn extends un{static get Default(){return fn}static get NAME(){return"popover"}static get Event(){return mn}static get DefaultType(){return pn}isWithContent(){return this.getTitle()||this._getContent()}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".popover-header"),this._sanitizeAndSetContent(t,this._getContent(),".popover-body")}_getContent(){return this._resolvePossibleFunction(this._config.content)}_getBasicClassPrefix(){return"bs-popover"}static jQueryInterface(t){return this.each((function(){const e=gn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(gn);const _n="scrollspy",bn={offset:10,method:"auto",target:""},vn={offset:"number",method:"string",target:"(string|element)"},yn="active",wn=".nav-link, .list-group-item, .dropdown-item",En="position";class An extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,j.on(this._scrollElement,"scroll.bs.scrollspy",(()=>this._process())),this.refresh(),this._process()}static get Default(){return bn}static get NAME(){return _n}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":En,e="auto"===this._config.method?t:this._config.method,n=e===En?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),V.find(wn,this._config.target).map((t=>{const s=i(t),o=s?V.findOne(s):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[U[e](o).top+n,s]}return null})).filter((t=>t)).sort(((t,e)=>t[0]-e[0])).forEach((t=>{this._offsets.push(t[0]),this._targets.push(t[1])}))}dispose(){j.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){return(t={...bn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target=r(t.target)||document.documentElement,a(_n,t,vn),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`)),i=V.findOne(e.join(","),this._config.target);i.classList.add(yn),i.classList.contains("dropdown-item")?V.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add(yn):V.parents(i,".nav, .list-group").forEach((t=>{V.prev(t,".nav-link, .list-group-item").forEach((t=>t.classList.add(yn))),V.prev(t,".nav-item").forEach((t=>{V.children(t,".nav-link").forEach((t=>t.classList.add(yn)))}))})),j.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){V.find(wn,this._config.target).filter((t=>t.classList.contains(yn))).forEach((t=>t.classList.remove(yn)))}static jQueryInterface(t){return this.each((function(){const e=An.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,"load.bs.scrollspy.data-api",(()=>{V.find('[data-bs-spy="scroll"]').forEach((t=>new An(t)))})),g(An);const Tn="active",On="fade",Cn="show",kn=".active",Ln=":scope > li > .active";class xn extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains(Tn))return;let t;const e=n(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?Ln:kn;t=V.find(e,i),t=t[t.length-1]}const s=t?j.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(j.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==s&&s.defaultPrevented)return;this._activate(this._element,i);const o=()=>{j.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),j.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?V.children(e,kn):V.find(Ln,e))[0],s=i&&n&&n.classList.contains(On),o=()=>this._transitionComplete(t,n,i);n&&s?(n.classList.remove(Cn),this._queueCallback(o,t,!0)):o()}_transitionComplete(t,e,i){if(e){e.classList.remove(Tn);const t=V.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove(Tn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add(Tn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u(t),t.classList.contains(On)&&t.classList.add(Cn);let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&V.find(".dropdown-toggle",e).forEach((t=>t.classList.add(Tn))),t.setAttribute("aria-expanded",!0)}i&&i()}static jQueryInterface(t){return this.each((function(){const e=xn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this)||xn.getOrCreateInstance(this).show()})),g(xn);const Dn="toast",Sn="hide",Nn="show",In="showing",Pn={animation:"boolean",autohide:"boolean",delay:"number"},jn={animation:!0,autohide:!0,delay:5e3};class Mn extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Pn}static get Default(){return jn}static get NAME(){return Dn}show(){j.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Sn),u(this._element),this._element.classList.add(Nn),this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.remove(In),j.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this._element.classList.contains(Nn)&&(j.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.add(Sn),this._element.classList.remove(In),this._element.classList.remove(Nn),j.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains(Nn)&&this._element.classList.remove(Nn),super.dispose()}_getConfig(t){return t={...jn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},a(Dn,t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){j.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),j.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Mn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(Mn),g(Mn),{Alert:W,Button:z,Carousel:st,Collapse:pt,Dropdown:hi,Modal:Hi,Offcanvas:Fi,Popover:gn,ScrollSpy:An,Tab:xn,Toast:Mn,Tooltip:un}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/data/web/js/build/002-slider.min.js b/data/web/js/build/002-slider.min.js new file mode 100644 index 0000000000..f04ef59616 --- /dev/null +++ b/data/web/js/build/002-slider.min.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).noUiSlider={})}(this,function(st){"use strict";var t,e;function n(t){return"object"==typeof t&&"function"==typeof t.to&&"function"==typeof t.from}function at(t){t.parentElement.removeChild(t)}function lt(t){return null!=t}function ut(t){t.preventDefault()}function o(t){return"number"==typeof t&&!isNaN(t)&&isFinite(t)}function ct(t,e,r){0=e[r];)r+=1;return r}function i(t,e,r){if(r>=t.slice(-1)[0])return 100;var n,i,o=f(r,t),s=t[o-1],a=t[o],l=e[o-1],u=e[o];return l+(i=r,p(n=[s,a],n[0]<0?i+Math.abs(n[0]):i-n[0],0)/c(l,u))}function s(t,e,r,n){if(100===n)return n;var i,o,s=f(n,t),a=t[s-1],l=t[s];return r?(l-a)/2this.xPct[i+1];)i++;else t===this.xPct[this.xPct.length-1]&&(i=this.xPct.length-2);r||t!==this.xPct[i+1]||i++,null===e&&(e=[]);var o=1,s=e[i],a=0,l=0,u=0,c=0;for(n=r?(t-this.xPct[i])/(this.xPct[i+1]-this.xPct[i]):(this.xPct[i+1]-t)/(this.xPct[i+1]-this.xPct[i]);0= 2) required for mode 'count'.");for(var e=t.values-1,r=100/e,n=[];e--;)n[e]=e*r;return n.push(100),D(n,t.stepped)}(m),v={},t=y.xVal[0],e=y.xVal[y.xVal.length-1],b=!1,S=!1,x=0;return(g=g.slice().sort(function(t,e){return t-e}).filter(function(t){return!this[t]&&(this[t]=!0)},{}))[0]!==t&&(g.unshift(t),b=!0),g[g.length-1]!==e&&(g.push(e),S=!0),g.forEach(function(t,e){var r,n,i,o,s,a,l,u,c,p,f=t,d=g[e+1],h=m.mode===st.PipsMode.Steps;for(h&&(r=y.xNumSteps[e]),r||(r=d-f),void 0===d&&(d=f),r=Math.max(r,1e-7),n=f;n<=d;n=Number((n+r).toFixed(7))){for(u=(s=(o=y.toStepping(n))-x)/(m.density||1),p=s/(c=Math.round(u)),i=1;i<=c;i+=1)v[(a=x+i*p).toFixed(5)]=[y.fromStepping(a),0];l=-1st.PipsType.NoValue&&((n=C(s,!1)).className=p(r,b.cssClasses.value),n.setAttribute("data-value",String(e)),n.style[b.style]=t+"%",n.innerHTML=String(o.to(e)))}}(t,e[t][0],e[t][1])}),s}function O(){i&&(at(i),i=null)}function j(t){O();var e=L(t),r=t.filter,n=t.format||{to:function(t){return String(Math.round(t))},from:Number};return i=d.appendChild(T(e,r,n))}function z(){var t=l.getBoundingClientRect(),e="offset"+["Width","Height"][b.ort];return 0===b.ort?t.width||l[e]:t.height||l[e]}function H(i,o,s,a){var e=function(t){var e,r,n=function(r,t,n){var e=0===r.type.indexOf("touch"),i=0===r.type.indexOf("mouse"),o=0===r.type.indexOf("pointer"),s=0,a=0;0===r.type.indexOf("MSPointer")&&(o=!0);if("mousedown"===r.type&&!r.buttons&&!r.touches)return!1;if(e){var l=function(t){var e=t.target;return e===n||n.contains(e)||r.composed&&r.composedPath().shift()===n};if("touchstart"===r.type){var u=Array.prototype.filter.call(r.touches,l);if(1r.stepAfter.startValue&&(i=r.stepAfter.startValue-n),o=n>r.thisStep.startValue?r.thisStep.step:!1!==r.stepBefore.step&&n-r.stepBefore.highestStep,100===e?i=null:0===e&&(o=null);var s=y.countStepDecimals();return null!==i&&!1!==i&&(i=Number(i.toFixed(s))),null!==o&&!1!==o&&(o=Number(o.toFixed(s))),[o,i]}dt(e=d,b.cssClasses.target),0===b.dir?dt(e,b.cssClasses.ltr):dt(e,b.cssClasses.rtl),0===b.ort?dt(e,b.cssClasses.horizontal):dt(e,b.cssClasses.vertical),dt(e,"rtl"===getComputedStyle(e).direction?b.cssClasses.textDirectionRtl:b.cssClasses.textDirectionLtr),l=C(e,b.cssClasses.base),function(t,e){var r=C(e,b.cssClasses.connects);u=[],(s=[]).push(V(r,t[0]));for(var n=0;n + if (!String.prototype.startsWith) { + (function () { + 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` + var toString = {}.toString; + var startsWith = function (search) { + if (this == null) { + throw new TypeError(); + } + var string = String(this); + if (search && toString.call(search) == '[object RegExp]') { + throw new TypeError(); + } + var stringLength = string.length; + var searchString = String(search); + var searchLength = searchString.length; + var position = arguments.length > 1 ? arguments[1] : undefined; + // `ToInteger` + var pos = position ? Number(position) : 0; + if (pos != pos) { // better `isNaN` + pos = 0; + } + var start = Math.min(Math.max(pos, 0), stringLength); + // Avoid the `indexOf` call if no match is possible + if (searchLength + start > stringLength) { + return false; + } + var index = -1; + while (++index < searchLength) { + if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) { + return false; + } + } + return true; + }; + if (Object.defineProperty) { + Object.defineProperty(String.prototype, 'startsWith', { + 'value': startsWith, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.startsWith = startsWith; + } + }()); + } + + function toKebabCase (str) { + return str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, function ($, ofs) { + return (ofs ? '-' : '') + $.toLowerCase(); + }); + } + + function getSelectedOptions () { + var options = this.selectpicker.main.data; + + if (this.options.source.data || this.options.source.search) { + options = Object.values(this.selectpicker.optionValuesDataMap); + } + + var selectedOptions = options.filter(function (item) { + if (item.selected) { + if (this.options.hideDisabled && item.disabled) return false; + return true; + } + + return false; + }, this); + + // ensure only 1 option is selected if multiple are set in the data source + if (this.options.source.data && !this.multiple && selectedOptions.length > 1) { + for (var i = 0; i < selectedOptions.length - 1; i++) { + selectedOptions[i].selected = false; + } + + selectedOptions = [ selectedOptions[selectedOptions.length - 1] ]; + } + + return selectedOptions; + } + + // much faster than $.val() + function getSelectValues (selectedOptions) { + var value = [], + options = selectedOptions || getSelectedOptions.call(this), + opt; + + for (var i = 0, len = options.length; i < len; i++) { + opt = options[i]; + + if (!opt.disabled) { + value.push(opt.value === undefined ? opt.text : opt.value); + } + } + + if (!this.multiple) { + return !value.length ? null : value[0]; + } + + return value; + } + + // set data-selected on select element if the value has been programmatically selected + // prior to initialization of bootstrap-select + // * consider removing or replacing an alternative method * + var valHooks = { + useDefault: false, + _set: $.valHooks.select.set + }; + + $.valHooks.select.set = function (elem, value) { + if (value && !valHooks.useDefault) $(elem).data('selected', true); + + return valHooks._set.apply(this, arguments); + }; + + var changedArguments = null; + + var EventIsSupported = (function () { + try { + new Event('change'); + return true; + } catch (e) { + return false; + } + })(); + + $.fn.triggerNative = function (eventName) { + var el = this[0], + event; + + if (el.dispatchEvent) { // for modern browsers & IE9+ + if (EventIsSupported) { + // For modern browsers + event = new Event(eventName, { + bubbles: true + }); + } else { + // For IE since it doesn't support Event constructor + event = document.createEvent('Event'); + event.initEvent(eventName, true, false); + } + + el.dispatchEvent(event); + } + }; + // + + function stringSearch (li, searchString, method, normalize) { + var stringTypes = [ + 'display', + 'subtext', + 'tokens' + ], + searchSuccess = false; + + for (var i = 0; i < stringTypes.length; i++) { + var stringType = stringTypes[i], + string = li[stringType]; + + if (string) { + string = string.toString(); + + // Strip HTML tags. This isn't perfect, but it's much faster than any other method + if (stringType === 'display') { + string = string.replace(/<[^>]+>/g, ''); + } + + if (normalize) string = normalizeToBase(string); + string = string.toUpperCase(); + + if (typeof method === 'function') { + searchSuccess = method(string, searchString); + } else if (method === 'contains') { + searchSuccess = string.indexOf(searchString) >= 0; + } else { + searchSuccess = string.startsWith(searchString); + } + + if (searchSuccess) break; + } + } + + return searchSuccess; + } + + function toInteger (value) { + return parseInt(value, 10) || 0; + } + + // Borrowed from Lodash (_.deburr) + /** Used to map Latin Unicode letters to basic Latin letters. */ + var deburredLetters = { + // Latin-1 Supplement block. + '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A', + '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a', + '\xc7': 'C', '\xe7': 'c', + '\xd0': 'D', '\xf0': 'd', + '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E', + '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e', + '\xcc': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I', + '\xec': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i', + '\xd1': 'N', '\xf1': 'n', + '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O', + '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o', + '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U', + '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u', + '\xdd': 'Y', '\xfd': 'y', '\xff': 'y', + '\xc6': 'Ae', '\xe6': 'ae', + '\xde': 'Th', '\xfe': 'th', + '\xdf': 'ss', + // Latin Extended-A block. + '\u0100': 'A', '\u0102': 'A', '\u0104': 'A', + '\u0101': 'a', '\u0103': 'a', '\u0105': 'a', + '\u0106': 'C', '\u0108': 'C', '\u010a': 'C', '\u010c': 'C', + '\u0107': 'c', '\u0109': 'c', '\u010b': 'c', '\u010d': 'c', + '\u010e': 'D', '\u0110': 'D', '\u010f': 'd', '\u0111': 'd', + '\u0112': 'E', '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E', + '\u0113': 'e', '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e', + '\u011c': 'G', '\u011e': 'G', '\u0120': 'G', '\u0122': 'G', + '\u011d': 'g', '\u011f': 'g', '\u0121': 'g', '\u0123': 'g', + '\u0124': 'H', '\u0126': 'H', '\u0125': 'h', '\u0127': 'h', + '\u0128': 'I', '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I', + '\u0129': 'i', '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i', + '\u0134': 'J', '\u0135': 'j', + '\u0136': 'K', '\u0137': 'k', '\u0138': 'k', + '\u0139': 'L', '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L', + '\u013a': 'l', '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l', + '\u0143': 'N', '\u0145': 'N', '\u0147': 'N', '\u014a': 'N', + '\u0144': 'n', '\u0146': 'n', '\u0148': 'n', '\u014b': 'n', + '\u014c': 'O', '\u014e': 'O', '\u0150': 'O', + '\u014d': 'o', '\u014f': 'o', '\u0151': 'o', + '\u0154': 'R', '\u0156': 'R', '\u0158': 'R', + '\u0155': 'r', '\u0157': 'r', '\u0159': 'r', + '\u015a': 'S', '\u015c': 'S', '\u015e': 'S', '\u0160': 'S', + '\u015b': 's', '\u015d': 's', '\u015f': 's', '\u0161': 's', + '\u0162': 'T', '\u0164': 'T', '\u0166': 'T', + '\u0163': 't', '\u0165': 't', '\u0167': 't', + '\u0168': 'U', '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U', + '\u0169': 'u', '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u', + '\u0174': 'W', '\u0175': 'w', + '\u0176': 'Y', '\u0177': 'y', '\u0178': 'Y', + '\u0179': 'Z', '\u017b': 'Z', '\u017d': 'Z', + '\u017a': 'z', '\u017c': 'z', '\u017e': 'z', + '\u0132': 'IJ', '\u0133': 'ij', + '\u0152': 'Oe', '\u0153': 'oe', + '\u0149': "'n", '\u017f': 's' + }; + + /** Used to match Latin Unicode letters (excluding mathematical operators). */ + var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g; + + /** Used to compose unicode character classes. */ + var rsComboMarksRange = '\\u0300-\\u036f', + reComboHalfMarksRange = '\\ufe20-\\ufe2f', + rsComboSymbolsRange = '\\u20d0-\\u20ff', + rsComboMarksExtendedRange = '\\u1ab0-\\u1aff', + rsComboMarksSupplementRange = '\\u1dc0-\\u1dff', + rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange + rsComboMarksExtendedRange + rsComboMarksSupplementRange; + + /** Used to compose unicode capture groups. */ + var rsCombo = '[' + rsComboRange + ']'; + + /** + * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and + * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols). + */ + var reComboMark = RegExp(rsCombo, 'g'); + + function deburrLetter (key) { + return deburredLetters[key]; + }; + + function normalizeToBase (string) { + string = string.toString(); + return string && string.replace(reLatin, deburrLetter).replace(reComboMark, ''); + } + + // List of HTML entities for escaping. + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' + }; + + // Functions for escaping and unescaping strings to/from HTML interpolation. + var createEscaper = function (map) { + var escaper = function (match) { + return map[match]; + }; + // Regexes for identifying a key that needs to be escaped. + var source = '(?:' + Object.keys(map).join('|') + ')'; + var testRegexp = RegExp(source); + var replaceRegexp = RegExp(source, 'g'); + return function (string) { + string = string == null ? '' : '' + string; + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + }; + }; + + var htmlEscape = createEscaper(escapeMap); + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + var keyCodeMap = { + 32: ' ', + 48: '0', + 49: '1', + 50: '2', + 51: '3', + 52: '4', + 53: '5', + 54: '6', + 55: '7', + 56: '8', + 57: '9', + 59: ';', + 65: 'A', + 66: 'B', + 67: 'C', + 68: 'D', + 69: 'E', + 70: 'F', + 71: 'G', + 72: 'H', + 73: 'I', + 74: 'J', + 75: 'K', + 76: 'L', + 77: 'M', + 78: 'N', + 79: 'O', + 80: 'P', + 81: 'Q', + 82: 'R', + 83: 'S', + 84: 'T', + 85: 'U', + 86: 'V', + 87: 'W', + 88: 'X', + 89: 'Y', + 90: 'Z', + 96: '0', + 97: '1', + 98: '2', + 99: '3', + 100: '4', + 101: '5', + 102: '6', + 103: '7', + 104: '8', + 105: '9' + }; + + var keyCodes = { + ESCAPE: 27, // KeyboardEvent.which value for Escape (Esc) key + ENTER: 13, // KeyboardEvent.which value for Enter key + SPACE: 32, // KeyboardEvent.which value for space key + TAB: 9, // KeyboardEvent.which value for tab key + ARROW_UP: 38, // KeyboardEvent.which value for up arrow key + ARROW_DOWN: 40 // KeyboardEvent.which value for down arrow key + }; + + // eslint-disable-next-line no-undef + var Dropdown = window.Dropdown || bootstrap.Dropdown; + + function getVersion () { + var version; + + try { + version = $.fn.dropdown.Constructor.VERSION; + } catch (err) { + version = Dropdown.VERSION; + } + + return version; + } + + var version = { + success: false, + major: '3' + }; + + try { + version.full = (getVersion() || '').split(' ')[0].split('.'); + version.major = version.full[0]; + version.success = true; + } catch (err) { + // do nothing + } + + var selectId = 0; + + var EVENT_KEY = '.bs.select'; + + var classNames = { + DISABLED: 'disabled', + DIVIDER: 'divider', + SHOW: 'open', + DROPUP: 'dropup', + MENU: 'dropdown-menu', + MENURIGHT: 'dropdown-menu-right', + MENULEFT: 'dropdown-menu-left', + // to-do: replace with more advanced template/customization options + BUTTONCLASS: 'btn-secondary', + POPOVERHEADER: 'popover-title', + ICONBASE: 'glyphicon', + TICKICON: 'glyphicon-ok' + }; + + var Selector = { + MENU: '.' + classNames.MENU, + DATA_TOGGLE: 'data-toggle="dropdown"' + }; + + var elementTemplates = { + div: document.createElement('div'), + span: document.createElement('span'), + i: document.createElement('i'), + subtext: document.createElement('small'), + a: document.createElement('a'), + li: document.createElement('li'), + whitespace: document.createTextNode('\u00A0'), + fragment: document.createDocumentFragment(), + option: document.createElement('option') + }; + + elementTemplates.selectedOption = elementTemplates.option.cloneNode(false); + elementTemplates.selectedOption.setAttribute('selected', true); + + elementTemplates.noResults = elementTemplates.li.cloneNode(false); + elementTemplates.noResults.className = 'no-results'; + + elementTemplates.a.setAttribute('role', 'option'); + elementTemplates.a.className = 'dropdown-item'; + + elementTemplates.subtext.className = 'text-muted'; + + elementTemplates.text = elementTemplates.span.cloneNode(false); + elementTemplates.text.className = 'text'; + + elementTemplates.checkMark = elementTemplates.span.cloneNode(false); + + var REGEXP_ARROW = new RegExp(keyCodes.ARROW_UP + '|' + keyCodes.ARROW_DOWN); + var REGEXP_TAB_OR_ESCAPE = new RegExp('^' + keyCodes.TAB + '$|' + keyCodes.ESCAPE); + + var generateOption = { + li: function (content, classes, optgroup) { + var li = elementTemplates.li.cloneNode(false); + + if (content) { + if (content.nodeType === 1 || content.nodeType === 11) { + li.appendChild(content); + } else { + li.innerHTML = content; + } + } + + if (typeof classes !== 'undefined' && classes !== '') li.className = classes; + if (typeof optgroup !== 'undefined' && optgroup !== null) li.classList.add('optgroup-' + optgroup); + + return li; + }, + + a: function (text, classes, inline) { + var a = elementTemplates.a.cloneNode(true); + + if (text) { + if (text.nodeType === 11) { + a.appendChild(text); + } else { + a.insertAdjacentHTML('beforeend', text); + } + } + + if (typeof classes !== 'undefined' && classes !== '') a.classList.add.apply(a.classList, classes.split(/\s+/)); + if (inline) a.setAttribute('style', inline); + + return a; + }, + + text: function (options, useFragment) { + var textElement = elementTemplates.text.cloneNode(false), + subtextElement, + iconElement; + + if (options.content) { + textElement.innerHTML = options.content; + } else { + textElement.textContent = options.text; + + if (options.icon) { + var whitespace = elementTemplates.whitespace.cloneNode(false); + + // need to use for icons in the button to prevent a breaking change + // note: switch to span in next major release + iconElement = (useFragment === true ? elementTemplates.i : elementTemplates.span).cloneNode(false); + iconElement.className = this.options.iconBase + ' ' + options.icon; + + elementTemplates.fragment.appendChild(iconElement); + elementTemplates.fragment.appendChild(whitespace); + } + + if (options.subtext) { + subtextElement = elementTemplates.subtext.cloneNode(false); + subtextElement.textContent = options.subtext; + textElement.appendChild(subtextElement); + } + } + + if (useFragment === true) { + while (textElement.childNodes.length > 0) { + elementTemplates.fragment.appendChild(textElement.childNodes[0]); + } + } else { + elementTemplates.fragment.appendChild(textElement); + } + + return elementTemplates.fragment; + }, + + label: function (options) { + var textElement = elementTemplates.text.cloneNode(false), + subtextElement, + iconElement; + + textElement.innerHTML = options.display; + + if (options.icon) { + var whitespace = elementTemplates.whitespace.cloneNode(false); + + iconElement = elementTemplates.span.cloneNode(false); + iconElement.className = this.options.iconBase + ' ' + options.icon; + + elementTemplates.fragment.appendChild(iconElement); + elementTemplates.fragment.appendChild(whitespace); + } + + if (options.subtext) { + subtextElement = elementTemplates.subtext.cloneNode(false); + subtextElement.textContent = options.subtext; + textElement.appendChild(subtextElement); + } + + elementTemplates.fragment.appendChild(textElement); + + return elementTemplates.fragment; + } + }; + + var getOptionData = { + fromOption: function (option, type) { + var value; + + switch (type) { + case 'divider': + value = option.getAttribute('data-divider') === 'true'; + break; + + case 'text': + value = option.textContent; + break; + + case 'label': + value = option.label; + break; + + case 'style': + value = option.style.cssText; + break; + + case 'title': + value = option.title; + break; + + default: + value = option.getAttribute('data-' + toKebabCase(type)); + break; + } + + return value; + }, + fromDataSource: function (option, type) { + var value; + + switch (type) { + case 'text': + case 'label': + value = option.text || option.value || ''; + break; + + default: + value = option[type]; + break; + } + + return value; + } + }; + + function showNoResults (searchMatch, searchValue) { + if (!searchMatch.length) { + elementTemplates.noResults.innerHTML = this.options.noneResultsText.replace('{0}', '"' + htmlEscape(searchValue) + '"'); + this.$menuInner[0].firstChild.appendChild(elementTemplates.noResults); + } + } + + function filterHidden (item) { + return !(item.hidden || this.options.hideDisabled && item.disabled); + } + + var Selectpicker = function (element, options) { + var that = this; + + // bootstrap-select has been initialized - revert valHooks.select.set back to its original function + if (!valHooks.useDefault) { + $.valHooks.select.set = valHooks._set; + valHooks.useDefault = true; + } + + this.$element = $(element); + this.$newElement = null; + this.$button = null; + this.$menu = null; + this.options = options; + this.selectpicker = { + main: { + data: [], + optionQueue: elementTemplates.fragment.cloneNode(false), + hasMore: false + }, + search: { + data: [], + hasMore: false + }, + current: {}, // current is either equal to main or search depending on if a search is in progress + view: {}, + // map of option values and their respective data (only used in conjunction with options.source) + optionValuesDataMap: {}, + isSearching: false, + keydown: { + keyHistory: '', + resetKeyHistory: { + start: function () { + return setTimeout(function () { + that.selectpicker.keydown.keyHistory = ''; + }, 800); + } + } + } + }; + + this.sizeInfo = {}; + + // Format window padding + var winPad = this.options.windowPadding; + if (typeof winPad === 'number') { + this.options.windowPadding = [winPad, winPad, winPad, winPad]; + } + + // Expose public methods + this.val = Selectpicker.prototype.val; + this.render = Selectpicker.prototype.render; + this.refresh = Selectpicker.prototype.refresh; + this.setStyle = Selectpicker.prototype.setStyle; + this.selectAll = Selectpicker.prototype.selectAll; + this.deselectAll = Selectpicker.prototype.deselectAll; + this.destroy = Selectpicker.prototype.destroy; + this.remove = Selectpicker.prototype.remove; + this.show = Selectpicker.prototype.show; + this.hide = Selectpicker.prototype.hide; + + this.init(); + }; + + Selectpicker.VERSION = '1.14.0-beta3'; + + // part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both. + Selectpicker.DEFAULTS = { + noneSelectedText: 'Nothing selected', + noneResultsText: 'No results matched {0}', + countSelectedText: function (numSelected, numTotal) { + return (numSelected == 1) ? '{0} item selected' : '{0} items selected'; + }, + maxOptionsText: function (numAll, numGroup) { + return [ + (numAll == 1) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)', + (numGroup == 1) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)' + ]; + }, + selectAllText: 'Select All', + deselectAllText: 'Deselect All', + source: { + pageSize: 40 + }, + chunkSize: 40, + doneButton: false, + doneButtonText: 'Close', + multipleSeparator: ', ', + styleBase: 'btn', + style: classNames.BUTTONCLASS, + size: 'auto', + title: null, + placeholder: null, + allowClear: false, + selectedTextFormat: 'values', + width: false, + container: false, + hideDisabled: false, + showSubtext: false, + showIcon: true, + showContent: true, + dropupAuto: true, + header: false, + liveSearch: false, + liveSearchPlaceholder: null, + liveSearchNormalize: false, + liveSearchStyle: 'contains', + actionsBox: false, + iconBase: classNames.ICONBASE, + tickIcon: classNames.TICKICON, + showTick: false, + template: { + caret: '' + }, + maxOptions: false, + mobile: false, + selectOnTab: true, + dropdownAlignRight: false, + windowPadding: 0, + virtualScroll: 600, + display: false, + sanitize: true, + sanitizeFn: null, + whiteList: DefaultWhitelist + }; + + Selectpicker.prototype = { + + constructor: Selectpicker, + + init: function () { + var that = this, + id = this.$element.attr('id'), + element = this.$element[0], + form = element.form; + + selectId++; + this.selectId = 'bs-select-' + selectId; + + element.classList.add('bs-select-hidden'); + + this.multiple = this.$element.prop('multiple'); + this.autofocus = this.$element.prop('autofocus'); + + if (element.classList.contains('show-tick')) { + this.options.showTick = true; + } + + this.$newElement = this.createDropdown(); + + this.$element + .after(this.$newElement) + .prependTo(this.$newElement); + + // ensure select is associated with form element if it got unlinked after moving it inside newElement + if (form && element.form === null) { + if (!form.id) form.id = 'form-' + this.selectId; + element.setAttribute('form', form.id); + } + + this.$button = this.$newElement.children('button'); + if (this.options.allowClear) this.$clearButton = this.$button.children('.bs-select-clear-selected'); + this.$menu = this.$newElement.children(Selector.MENU); + this.$menuInner = this.$menu.children('.inner'); + this.$searchbox = this.$menu.find('input'); + + element.classList.remove('bs-select-hidden'); + + this.fetchData(function () { + that.render(true); + that.buildList(); + + requestAnimationFrame(function () { + that.$element.trigger('loaded' + EVENT_KEY); + }); + }); + + if (this.options.dropdownAlignRight === true) this.$menu[0].classList.add(classNames.MENURIGHT); + + if (typeof id !== 'undefined') { + this.$button.attr('data-id', id); + } + + this.checkDisabled(); + this.clickListener(); + + if (version.major > 4) this.dropdown = new Dropdown(this.$button[0]); + + if (this.options.liveSearch) { + this.liveSearchListener(); + this.focusedParent = this.$searchbox[0]; + } else { + this.focusedParent = this.$menuInner[0]; + } + + this.setStyle(); + this.setWidth(); + if (this.options.container) { + this.selectPosition(); + } else { + this.$element.on('hide' + EVENT_KEY, function () { + if (that.isVirtual()) { + // empty menu on close + var menuInner = that.$menuInner[0], + emptyMenu = menuInner.firstChild.cloneNode(false); + + // replace the existing UL with an empty one - this is faster than $.empty() or innerHTML = '' + menuInner.replaceChild(emptyMenu, menuInner.firstChild); + menuInner.scrollTop = 0; + } + }); + } + this.$menu.data('this', this); + this.$newElement.data('this', this); + if (this.options.mobile) this.mobile(); + + this.$newElement.on({ + 'hide.bs.dropdown': function (e) { + that.$element.trigger('hide' + EVENT_KEY, e); + }, + 'hidden.bs.dropdown': function (e) { + that.$element.trigger('hidden' + EVENT_KEY, e); + }, + 'show.bs.dropdown': function (e) { + that.$element.trigger('show' + EVENT_KEY, e); + }, + 'shown.bs.dropdown': function (e) { + that.$element.trigger('shown' + EVENT_KEY, e); + } + }); + + if (element.hasAttribute('required')) { + this.$element.on('invalid' + EVENT_KEY, function () { + that.$button[0].classList.add('bs-invalid'); + + that.$element + .on('shown' + EVENT_KEY + '.invalid', function () { + that.$element + .val(that.$element.val()) // set the value to hide the validation message in Chrome when menu is opened + .off('shown' + EVENT_KEY + '.invalid'); + }) + .on('rendered' + EVENT_KEY, function () { + // if select is no longer invalid, remove the bs-invalid class + if (this.validity.valid) that.$button[0].classList.remove('bs-invalid'); + that.$element.off('rendered' + EVENT_KEY); + }); + + that.$button.on('blur' + EVENT_KEY, function () { + that.$element.trigger('focus').trigger('blur'); + that.$button.off('blur' + EVENT_KEY); + }); + }); + } + + if (form) { + $(form).on('reset' + EVENT_KEY, function () { + requestAnimationFrame(function () { + that.render(); + }); + }); + } + }, + + createDropdown: function () { + // Options + // If we are multiple or showTick option is set, then add the show-tick class + var showTick = (this.multiple || this.options.showTick) ? ' show-tick' : '', + multiselectable = this.multiple ? ' aria-multiselectable="true"' : '', + inputGroup = '', + autofocus = this.autofocus ? ' autofocus' : ''; + + if (version.major < 4 && this.$element.parent().hasClass('input-group')) { + inputGroup = ' input-group-btn'; + } + + // Elements + var drop, + header = '', + searchbox = '', + actionsbox = '', + donebutton = '', + clearButton = ''; + + if (this.options.header) { + header = + '
    ' + + '' + + this.options.header + + '
    '; + } + + if (this.options.liveSearch) { + searchbox = + ''; + } + + if (this.multiple && this.options.actionsBox) { + actionsbox = + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    '; + } + + if (this.multiple && this.options.doneButton) { + donebutton = + '
    ' + + '
    ' + + '' + + '
    ' + + '
    '; + } + + if (this.options.allowClear) { + clearButton = '×'; + } + + drop = + ''; + + return $(drop); + }, + + setPositionData: function () { + this.selectpicker.view.canHighlight = []; + this.selectpicker.view.size = 0; + this.selectpicker.view.firstHighlightIndex = false; + + for (var i = 0; i < this.selectpicker.current.data.length; i++) { + var li = this.selectpicker.current.data[i], + canHighlight = true; + + if (li.type === 'divider') { + canHighlight = false; + li.height = this.sizeInfo.dividerHeight; + } else if (li.type === 'optgroup-label') { + canHighlight = false; + li.height = this.sizeInfo.dropdownHeaderHeight; + } else { + li.height = this.sizeInfo.liHeight; + } + + if (li.disabled) canHighlight = false; + + this.selectpicker.view.canHighlight.push(canHighlight); + + if (canHighlight) { + this.selectpicker.view.size++; + li.posinset = this.selectpicker.view.size; + if (this.selectpicker.view.firstHighlightIndex === false) this.selectpicker.view.firstHighlightIndex = i; + } + + li.position = (i === 0 ? 0 : this.selectpicker.current.data[i - 1].position) + li.height; + } + }, + + isVirtual: function () { + return (this.options.virtualScroll !== false) && (this.selectpicker.main.data.length >= this.options.virtualScroll) || this.options.virtualScroll === true; + }, + + createView: function (isSearching, setSize, refresh) { + var that = this, + scrollTop = 0; + + this.selectpicker.isSearching = isSearching; + this.selectpicker.current = isSearching ? this.selectpicker.search : this.selectpicker.main; + + this.setPositionData(); + + if (setSize) { + if (refresh) { + scrollTop = this.$menuInner[0].scrollTop; + } else if (!that.multiple) { + var element = that.$element[0], + selectedIndex = (element.options[element.selectedIndex] || {}).liIndex; + + if (typeof selectedIndex === 'number' && that.options.size !== false) { + var selectedData = that.selectpicker.main.data[selectedIndex], + position = selectedData && selectedData.position; + + if (position) { + scrollTop = position - ((that.sizeInfo.menuInnerHeight + that.sizeInfo.liHeight) / 2); + } + } + } + } + + scroll(scrollTop, true); + + this.$menuInner.off('scroll.createView').on('scroll.createView', function (e, updateValue) { + if (!that.noScroll) scroll(this.scrollTop, updateValue); + that.noScroll = false; + }); + + function scroll (scrollTop, init) { + var size = that.selectpicker.current.data.length, + chunks = [], + chunkSize, + chunkCount, + firstChunk, + lastChunk, + currentChunk, + prevPositions, + positionIsDifferent, + previousElements, + menuIsDifferent = true, + isVirtual = that.isVirtual(); + + that.selectpicker.view.scrollTop = scrollTop; + + chunkSize = that.options.chunkSize; // number of options in a chunk + chunkCount = Math.ceil(size / chunkSize) || 1; // number of chunks + + for (var i = 0; i < chunkCount; i++) { + var endOfChunk = (i + 1) * chunkSize; + + if (i === chunkCount - 1) { + endOfChunk = size; + } + + chunks[i] = [ + (i) * chunkSize + (!i ? 0 : 1), + endOfChunk + ]; + + if (!size) break; + + if (currentChunk === undefined && scrollTop - 1 <= that.selectpicker.current.data[endOfChunk - 1].position - that.sizeInfo.menuInnerHeight) { + currentChunk = i; + } + } + + if (currentChunk === undefined) currentChunk = 0; + + prevPositions = [that.selectpicker.view.position0, that.selectpicker.view.position1]; + + // always display previous, current, and next chunks + firstChunk = Math.max(0, currentChunk - 1); + lastChunk = Math.min(chunkCount - 1, currentChunk + 1); + + that.selectpicker.view.position0 = isVirtual === false ? 0 : (Math.max(0, chunks[firstChunk][0]) || 0); + that.selectpicker.view.position1 = isVirtual === false ? size : (Math.min(size, chunks[lastChunk][1]) || 0); + + positionIsDifferent = prevPositions[0] !== that.selectpicker.view.position0 || prevPositions[1] !== that.selectpicker.view.position1; + + if (that.activeElement !== undefined) { + if (init) { + if (that.activeElement !== that.selectedElement) { + that.defocusItem(that.activeElement); + } + that.activeElement = undefined; + } + + if (that.activeElement !== that.selectedElement) { + that.defocusItem(that.selectedElement); + } + } + + if (that.prevActiveElement !== undefined && that.prevActiveElement !== that.activeElement && that.prevActiveElement !== that.selectedElement) { + that.defocusItem(that.prevActiveElement); + } + + if (init || positionIsDifferent || that.selectpicker.current.hasMore) { + previousElements = that.selectpicker.view.visibleElements ? that.selectpicker.view.visibleElements.slice() : []; + + if (isVirtual === false) { + that.selectpicker.view.visibleElements = that.selectpicker.current.elements; + } else { + that.selectpicker.view.visibleElements = that.selectpicker.current.elements.slice(that.selectpicker.view.position0, that.selectpicker.view.position1); + } + + that.setOptionStatus(); + + // if searching, check to make sure the list has actually been updated before updating DOM + // this prevents unnecessary repaints + if (isSearching || (isVirtual === false && init)) menuIsDifferent = !isEqual(previousElements, that.selectpicker.view.visibleElements); + + // if virtual scroll is disabled and not searching, + // menu should never need to be updated more than once + if ((init || isVirtual === true) && menuIsDifferent) { + var menuInner = that.$menuInner[0], + menuFragment = document.createDocumentFragment(), + emptyMenu = menuInner.firstChild.cloneNode(false), + marginTop, + marginBottom, + elements = that.selectpicker.view.visibleElements, + toSanitize = []; + + // replace the existing UL with an empty one - this is faster than $.empty() + menuInner.replaceChild(emptyMenu, menuInner.firstChild); + + for (var i = 0, visibleElementsLen = elements.length; i < visibleElementsLen; i++) { + var element = elements[i], + elText, + elementData; + + if (that.options.sanitize) { + elText = element.lastChild; + + if (elText) { + elementData = that.selectpicker.current.data[i + that.selectpicker.view.position0]; + + if (elementData && elementData.content && !elementData.sanitized) { + toSanitize.push(elText); + elementData.sanitized = true; + } + } + } + + menuFragment.appendChild(element); + } + + if (that.options.sanitize && toSanitize.length) { + sanitizeHtml(toSanitize, that.options.whiteList, that.options.sanitizeFn); + } + + if (isVirtual === true) { + marginTop = (that.selectpicker.view.position0 === 0 ? 0 : that.selectpicker.current.data[that.selectpicker.view.position0 - 1].position); + marginBottom = (that.selectpicker.view.position1 > size - 1 ? 0 : that.selectpicker.current.data[size - 1].position - that.selectpicker.current.data[that.selectpicker.view.position1 - 1].position); + + menuInner.firstChild.style.marginTop = marginTop + 'px'; + menuInner.firstChild.style.marginBottom = marginBottom + 'px'; + } else { + menuInner.firstChild.style.marginTop = 0; + menuInner.firstChild.style.marginBottom = 0; + } + + menuInner.firstChild.appendChild(menuFragment); + + // if an option is encountered that is wider than the current menu width, update the menu width accordingly + // switch to ResizeObserver with increased browser support + if (isVirtual === true && that.sizeInfo.hasScrollBar) { + var menuInnerInnerWidth = menuInner.firstChild.offsetWidth; + + if (init && menuInnerInnerWidth < that.sizeInfo.menuInnerInnerWidth && that.sizeInfo.totalMenuWidth > that.sizeInfo.selectWidth) { + menuInner.firstChild.style.minWidth = that.sizeInfo.menuInnerInnerWidth + 'px'; + } else if (menuInnerInnerWidth > that.sizeInfo.menuInnerInnerWidth) { + // set to 0 to get actual width of menu + that.$menu[0].style.minWidth = 0; + + var actualMenuWidth = menuInner.firstChild.offsetWidth; + + if (actualMenuWidth > that.sizeInfo.menuInnerInnerWidth) { + that.sizeInfo.menuInnerInnerWidth = actualMenuWidth; + menuInner.firstChild.style.minWidth = that.sizeInfo.menuInnerInnerWidth + 'px'; + } + + // reset to default CSS styling + that.$menu[0].style.minWidth = ''; + } + } + } + + if ((!isSearching && that.options.source.data || isSearching && that.options.source.search) && that.selectpicker.current.hasMore && currentChunk === chunkCount - 1) { + // Don't load the next chunk until scrolling has started + // This prevents unnecessary requests while the user is typing if pageSize is <= chunkSize + if (scrollTop > 0) { + // Chunks use 0-based indexing, but pages use 1-based. Add 1 to convert and add 1 again to get next page + var page = Math.floor((currentChunk * that.options.chunkSize) / that.options.source.pageSize) + 2; + + that.fetchData(function () { + that.render(); + that.buildList(size, isSearching); + that.setPositionData(); + scroll(scrollTop); + }, isSearching ? 'search' : 'data', page, isSearching ? that.selectpicker.search.previousValue : undefined); + } + } + } + + that.prevActiveElement = that.activeElement; + + if (!that.options.liveSearch) { + that.$menuInner.trigger('focus'); + } else if (isSearching && init) { + var index = 0, + newActive; + + if (!that.selectpicker.view.canHighlight[index]) { + index = 1 + that.selectpicker.view.canHighlight.slice(1).indexOf(true); + } + + newActive = that.selectpicker.view.visibleElements[index]; + + that.defocusItem(that.selectpicker.view.currentActive); + + that.activeElement = (that.selectpicker.current.data[index] || {}).element; + + that.focusItem(newActive); + } + } + + $(window) + .off('resize' + EVENT_KEY + '.' + this.selectId + '.createView') + .on('resize' + EVENT_KEY + '.' + this.selectId + '.createView', function () { + var isActive = that.$newElement.hasClass(classNames.SHOW); + + if (isActive) scroll(that.$menuInner[0].scrollTop); + }); + }, + + focusItem: function (li, liData, noStyle) { + if (li) { + liData = liData || this.selectpicker.current.data[this.selectpicker.current.elements.indexOf(this.activeElement)]; + var a = li.firstChild; + + if (a) { + a.setAttribute('aria-setsize', this.selectpicker.view.size); + a.setAttribute('aria-posinset', liData.posinset); + + if (noStyle !== true) { + this.focusedParent.setAttribute('aria-activedescendant', a.id); + li.classList.add('active'); + a.classList.add('active'); + } + } + } + }, + + defocusItem: function (li) { + if (li) { + li.classList.remove('active'); + if (li.firstChild) li.firstChild.classList.remove('active'); + } + }, + + setPlaceholder: function () { + var that = this, + updateIndex = false; + + if ((this.options.placeholder || this.options.allowClear) && !this.multiple) { + if (!this.selectpicker.view.titleOption) this.selectpicker.view.titleOption = document.createElement('option'); + + // this option doesn't create a new
  • element, but does add a new option at the start, + // so startIndex should increase to prevent having to check every option for the bs-title-option class + updateIndex = true; + + var element = this.$element[0], + selectTitleOption = false, + titleNotAppended = !this.selectpicker.view.titleOption.parentNode, + selectedIndex = element.selectedIndex, + selectedOption = element.options[selectedIndex], + firstSelectable = element.querySelector('select > *:not(:disabled)'), + firstSelectableIndex = firstSelectable ? firstSelectable.index : 0, + navigation = window.performance && window.performance.getEntriesByType('navigation'), + // Safari doesn't support getEntriesByType('navigation') - fall back to performance.navigation + isNotBackForward = (navigation && navigation.length) ? navigation[0].type !== 'back_forward' : window.performance.navigation.type !== 2; + + if (titleNotAppended) { + // Use native JS to prepend option (faster) + this.selectpicker.view.titleOption.className = 'bs-title-option'; + this.selectpicker.view.titleOption.value = ''; + + // Check if selected or data-selected attribute is already set on an option. If not, select the titleOption option. + // the selected item may have been changed by user or programmatically before the bootstrap select plugin runs, + // if so, the select will have the data-selected attribute + selectTitleOption = !selectedOption || (selectedIndex === firstSelectableIndex && selectedOption.defaultSelected === false && this.$element.data('selected') === undefined); + } + + if (titleNotAppended || this.selectpicker.view.titleOption.index !== 0) { + element.insertBefore(this.selectpicker.view.titleOption, element.firstChild); + } + + // Set selected *after* appending to select, + // otherwise the option doesn't get selected in IE + // set using selectedIndex, as setting the selected attr to true here doesn't work in IE11 + if (selectTitleOption && isNotBackForward) { + element.selectedIndex = 0; + } else if (document.readyState !== 'complete') { + // if navigation type is back_forward, there's a chance the select will have its value set by BFCache + // wait for that value to be set, then run render again + window.addEventListener('pageshow', function () { + if (that.selectpicker.view.displayedValue !== element.value) that.render(); + }); + } + } + + return updateIndex; + }, + + fetchData: function (callback, type, page, searchValue) { + page = page || 1; + type = type || 'data'; + + var that = this, + data = this.options.source[type], + builtData; + + if (data) { + this.options.virtualScroll = true; + + if (typeof data === 'function') { + data.call( + this, + function (data, more, totalItems) { + var current = that.selectpicker[type === 'search' ? 'search' : 'main']; + current.hasMore = more; + current.totalItems = totalItems; + builtData = that.buildData(data, type); + callback.call(that, builtData); + that.$element.trigger('fetched' + EVENT_KEY); + }, + page, + searchValue + ); + } else if (Array.isArray(data)) { + builtData = that.buildData(data, type); + callback.call(that, builtData); + } + } else { + builtData = this.buildData(false, type); + callback.call(that, builtData); + } + }, + + buildData: function (data, type) { + var that = this; + var dataGetter = data === false ? getOptionData.fromOption : getOptionData.fromDataSource; + + var optionSelector = ':not([hidden]):not([data-hidden="true"]):not([style*="display: none"])', + mainData = [], + startLen = this.selectpicker.main.data ? this.selectpicker.main.data.length : 0, + optID = 0, + startIndex = this.setPlaceholder() && !data ? 1 : 0; // append the titleOption if necessary and skip the first option in the loop + + if (type === 'search') { + startLen = this.selectpicker.search.data.length; + } + + if (this.options.hideDisabled) optionSelector += ':not(:disabled)'; + + var selectOptions = data ? data.filter(filterHidden, this) : this.$element[0].querySelectorAll('select > *' + optionSelector); + + function addDivider (config) { + var previousData = mainData[mainData.length - 1]; + + // ensure optgroup doesn't create back-to-back dividers + if ( + previousData && + previousData.type === 'divider' && + (previousData.optID || config.optID) + ) { + return; + } + + config = config || {}; + config.type = 'divider'; + + mainData.push(config); + } + + function addOption (item, config) { + config = config || {}; + + config.divider = dataGetter(item, 'divider'); + + if (config.divider === true) { + addDivider({ + optID: config.optID + }); + } else { + var liIndex = mainData.length + startLen, + cssText = dataGetter(item, 'style'), + inlineStyle = cssText ? htmlEscape(cssText) : '', + optionClass = (item.className || '') + (config.optgroupClass || ''); + + if (config.optID) optionClass = 'opt ' + optionClass; + + config.optionClass = optionClass.trim(); + config.inlineStyle = inlineStyle; + + config.text = dataGetter(item, 'text'); + config.title = dataGetter(item, 'title'); + config.content = dataGetter(item, 'content'); + config.tokens = dataGetter(item, 'tokens'); + config.subtext = dataGetter(item, 'subtext'); + config.icon = dataGetter(item, 'icon'); + + config.display = config.content || config.text; + config.value = item.value === undefined ? item.text : item.value; + config.type = 'option'; + config.index = liIndex; + + config.option = !item.option ? item : item.option; // reference option element if it exists + config.option.liIndex = liIndex; + config.selected = !!item.selected; + config.disabled = config.disabled || !!item.disabled; + + if (data !== false) { + if (that.selectpicker.optionValuesDataMap[config.value]) { + config = $.extend(that.selectpicker.optionValuesDataMap[config.value], config); + } else { + that.selectpicker.optionValuesDataMap[config.value] = config; + } + } + + mainData.push(config); + } + } + + function addOptgroup (index, selectOptions) { + var optgroup = selectOptions[index], + // skip placeholder option + previous = index - 1 < startIndex ? false : selectOptions[index - 1], + next = selectOptions[index + 1], + options = data ? optgroup.children.filter(filterHidden, this) : optgroup.querySelectorAll('option' + optionSelector); + + if (!options.length) return; + + var config = { + display: htmlEscape(dataGetter(item, 'label')), + subtext: dataGetter(optgroup, 'subtext'), + icon: dataGetter(optgroup, 'icon'), + type: 'optgroup-label', + optgroupClass: ' ' + (optgroup.className || ''), + optgroup: optgroup + }, + headerIndex, + lastIndex; + + optID++; + + if (previous) { + addDivider({ optID: optID }); + } + + config.optID = optID; + + mainData.push(config); + + for (var j = 0, len = options.length; j < len; j++) { + var option = options[j]; + + if (j === 0) { + headerIndex = mainData.length - 1; + lastIndex = headerIndex + len; + } + + addOption(option, { + headerIndex: headerIndex, + lastIndex: lastIndex, + optID: config.optID, + optgroupClass: config.optgroupClass, + disabled: optgroup.disabled + }); + } + + if (next) { + addDivider({ optID: optID }); + } + } + + for (var len = selectOptions.length, i = startIndex; i < len; i++) { + var item = selectOptions[i], + children = item.children; + + if (children && children.length) { + addOptgroup.call(this, i, selectOptions); + } else { + addOption.call(this, item, {}); + } + } + + switch (type) { + case 'data': { + if (!this.selectpicker.main.data) { + this.selectpicker.main.data = []; + } + Array.prototype.push.apply(this.selectpicker.main.data, mainData); + this.selectpicker.current.data = this.selectpicker.main.data; + break; + } + case 'search': { + Array.prototype.push.apply(this.selectpicker.search.data, mainData); + break; + } + } + + return mainData; + }, + + buildList: function (size, searching) { + var that = this, + selectData = searching ? this.selectpicker.search.data : this.selectpicker.main.data, + mainElements = [], + widestOptionLength = 0; + + if ((that.options.showTick || that.multiple) && !elementTemplates.checkMark.parentNode) { + elementTemplates.checkMark.className = this.options.iconBase + ' ' + that.options.tickIcon + ' check-mark'; + elementTemplates.a.appendChild(elementTemplates.checkMark); + } + + function buildElement (mainElements, item) { + var liElement, + combinedLength = 0; + + switch (item.type) { + case 'divider': + liElement = generateOption.li( + false, + classNames.DIVIDER, + (item.optID ? item.optID + 'div' : undefined) + ); + + break; + + case 'option': + liElement = generateOption.li( + generateOption.a( + generateOption.text.call(that, item), + item.optionClass, + item.inlineStyle + ), + '', + item.optID + ); + + if (liElement.firstChild) { + liElement.firstChild.id = that.selectId + '-' + item.index; + } + + break; + + case 'optgroup-label': + liElement = generateOption.li( + generateOption.label.call(that, item), + 'dropdown-header' + item.optgroupClass, + item.optID + ); + + break; + } + + if (!item.element) { + item.element = liElement; + } else { + item.element.innerHTML = liElement.innerHTML; + } + mainElements.push(item.element); + + // count the number of characters in the option - not perfect, but should work in most cases + if (item.display) combinedLength += item.display.length; + if (item.subtext) combinedLength += item.subtext.length; + // if there is an icon, ensure this option's width is checked + if (item.icon) combinedLength += 1; + + if (combinedLength > widestOptionLength) { + widestOptionLength = combinedLength; + + // guess which option is the widest + // use this when calculating menu width + // not perfect, but it's fast, and the width will be updating accordingly when scrolling + that.selectpicker.view.widestOption = mainElements[mainElements.length - 1]; + } + } + + var startIndex = size || 0; + + for (var len = selectData.length, i = startIndex; i < len; i++) { + var item = selectData[i]; + + buildElement(mainElements, item); + } + + if (size) { + if (searching) { + Array.prototype.push.apply(this.selectpicker.search.elements, mainElements); + } else { + Array.prototype.push.apply(this.selectpicker.main.elements, mainElements); + this.selectpicker.current.elements = this.selectpicker.main.elements; + } + } else { + if (searching) { + this.selectpicker.search.elements = mainElements; + } else { + this.selectpicker.main.elements = this.selectpicker.current.elements = mainElements; + } + } + }, + + findLis: function () { + return this.$menuInner.find('.inner > li'); + }, + + render: function (init) { + var that = this, + element = this.$element[0], + // ensure titleOption is appended and selected (if necessary) before getting selectedOptions + placeholderSelected = this.setPlaceholder() && element.selectedIndex === 0, + selectedOptions = getSelectedOptions.call(this), + selectedCount = selectedOptions.length, + selectedValues = getSelectValues.call(this, selectedOptions), + button = this.$button[0], + buttonInner = button.querySelector('.filter-option-inner-inner'), + multipleSeparator = document.createTextNode(this.options.multipleSeparator), + titleFragment = elementTemplates.fragment.cloneNode(false), + showCount, + countMax, + hasContent = false; + + function createSelected (item) { + if (item.selected) { + that.createOption(item, true); + } else if (item.children && item.children.length) { + item.children.map(createSelected); + } + } + + // create selected option elements to ensure select value is correct + if (this.options.source.data && init) { + selectedOptions.map(createSelected); + element.appendChild(this.selectpicker.main.optionQueue); + + if (placeholderSelected) placeholderSelected = element.selectedIndex === 0; + } + + button.classList.toggle('bs-placeholder', that.multiple ? !selectedCount : !selectedValues && selectedValues !== 0); + + if (!that.multiple && selectedOptions.length === 1) { + that.selectpicker.view.displayedValue = selectedValues; + } + + if (this.options.selectedTextFormat === 'static') { + titleFragment = generateOption.text.call(this, { text: this.options.placeholder }, true); + } else { + showCount = this.multiple && this.options.selectedTextFormat.indexOf('count') !== -1 && selectedCount > 0; + + // determine if the number of selected options will be shown (showCount === true) + if (showCount) { + countMax = this.options.selectedTextFormat.split('>'); + showCount = (countMax.length > 1 && selectedCount > countMax[1]) || (countMax.length === 1 && selectedCount >= 2); + } + + // only loop through all selected options if the count won't be shown + if (showCount === false) { + if (!placeholderSelected) { + for (var selectedIndex = 0; selectedIndex < selectedCount; selectedIndex++) { + if (selectedIndex < 50) { + var option = selectedOptions[selectedIndex], + titleOptions = {}; + + if (option) { + if (this.multiple && selectedIndex > 0) { + titleFragment.appendChild(multipleSeparator.cloneNode(false)); + } + + if (option.title) { + titleOptions.text = option.title; + } else if (option.content && that.options.showContent) { + titleOptions.content = option.content.toString(); + hasContent = true; + } else { + if (that.options.showIcon) { + titleOptions.icon = option.icon; + } + if (that.options.showSubtext && !that.multiple && option.subtext) titleOptions.subtext = ' ' + option.subtext; + titleOptions.text = option.text.trim(); + } + + titleFragment.appendChild(generateOption.text.call(this, titleOptions, true)); + } + } else { + break; + } + } + + // add ellipsis + if (selectedCount > 49) { + titleFragment.appendChild(document.createTextNode('...')); + } + } + } else { + var optionSelector = ':not([hidden]):not([data-hidden="true"]):not([data-divider="true"]):not([style*="display: none"])'; + if (this.options.hideDisabled) optionSelector += ':not(:disabled)'; + + // If this is a multiselect, and selectedTextFormat is count, then show 1 of 2 selected, etc. + var totalCount = this.$element[0].querySelectorAll('select > option' + optionSelector + ', optgroup' + optionSelector + ' option' + optionSelector).length, + tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedCount, totalCount) : this.options.countSelectedText; + + titleFragment = generateOption.text.call(this, { + text: tr8nText.replace('{0}', selectedCount.toString()).replace('{1}', totalCount.toString()) + }, true); + } + } + + // If the select doesn't have a title, then use the default, or if nothing is set at all, use noneSelectedText + if (!titleFragment.childNodes.length) { + titleFragment = generateOption.text.call(this, { + text: this.options.placeholder ? this.options.placeholder : this.options.noneSelectedText + }, true); + } + + // if the select has a title, apply it to the button, and if not, apply titleFragment text + // strip all HTML tags and trim the result, then unescape any escaped tags + button.title = titleFragment.textContent.replace(/<[^>]*>?/g, '').trim(); + + if (this.options.sanitize && hasContent) { + sanitizeHtml([titleFragment], that.options.whiteList, that.options.sanitizeFn); + } + + buttonInner.innerHTML = ''; + buttonInner.appendChild(titleFragment); + + if (version.major < 4 && this.$newElement[0].classList.contains('bs3-has-addon')) { + var filterExpand = button.querySelector('.filter-expand'), + clone = buttonInner.cloneNode(true); + + clone.className = 'filter-expand'; + + if (filterExpand) { + button.replaceChild(clone, filterExpand); + } else { + button.appendChild(clone); + } + } + + this.$element.trigger('rendered' + EVENT_KEY); + }, + + /** + * @param [style] + * @param [status] + */ + setStyle: function (newStyle, status) { + var button = this.$button[0], + newElement = this.$newElement[0], + style = this.options.style.trim(), + buttonClass; + + if (this.$element.attr('class')) { + this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi, '')); + } + + if (version.major < 4) { + newElement.classList.add('bs3'); + + if (newElement.parentNode.classList && newElement.parentNode.classList.contains('input-group') && + (newElement.previousElementSibling || newElement.nextElementSibling) && + (newElement.previousElementSibling || newElement.nextElementSibling).classList.contains('input-group-addon') + ) { + newElement.classList.add('bs3-has-addon'); + } + } + + if (newStyle) { + buttonClass = newStyle.trim(); + } else { + buttonClass = style; + } + + if (status == 'add') { + if (buttonClass) button.classList.add.apply(button.classList, buttonClass.split(' ')); + } else if (status == 'remove') { + if (buttonClass) button.classList.remove.apply(button.classList, buttonClass.split(' ')); + } else { + if (style) button.classList.remove.apply(button.classList, style.split(' ')); + if (buttonClass) button.classList.add.apply(button.classList, buttonClass.split(' ')); + } + }, + + liHeight: function (refresh) { + if (!refresh && (this.options.size === false || Object.keys(this.sizeInfo).length)) return; + + var newElement = elementTemplates.div.cloneNode(false), + menu = elementTemplates.div.cloneNode(false), + menuInner = elementTemplates.div.cloneNode(false), + menuInnerInner = document.createElement('ul'), + divider = elementTemplates.li.cloneNode(false), + dropdownHeader = elementTemplates.li.cloneNode(false), + li, + a = elementTemplates.a.cloneNode(false), + text = elementTemplates.span.cloneNode(false), + header = this.options.header && this.$menu.find('.' + classNames.POPOVERHEADER).length > 0 ? this.$menu.find('.' + classNames.POPOVERHEADER)[0].cloneNode(true) : null, + search = this.options.liveSearch ? elementTemplates.div.cloneNode(false) : null, + actions = this.options.actionsBox && this.multiple && this.$menu.find('.bs-actionsbox').length > 0 ? this.$menu.find('.bs-actionsbox')[0].cloneNode(true) : null, + doneButton = this.options.doneButton && this.multiple && this.$menu.find('.bs-donebutton').length > 0 ? this.$menu.find('.bs-donebutton')[0].cloneNode(true) : null, + firstOption = this.$element[0].options[0]; + + this.sizeInfo.selectWidth = this.$newElement[0].offsetWidth; + + text.className = 'text'; + a.className = 'dropdown-item ' + (firstOption ? firstOption.className : ''); + newElement.className = this.$menu[0].parentNode.className + ' ' + classNames.SHOW; + newElement.style.width = 0; // ensure button width doesn't affect natural width of menu when calculating + if (this.options.width === 'auto') menu.style.minWidth = 0; + menu.className = classNames.MENU + ' ' + classNames.SHOW; + menuInner.className = 'inner ' + classNames.SHOW; + menuInnerInner.className = classNames.MENU + ' inner ' + (version.major >= '4' ? classNames.SHOW : ''); + divider.className = classNames.DIVIDER; + dropdownHeader.className = 'dropdown-header'; + + text.appendChild(document.createTextNode('\u200b')); + + if (this.selectpicker.current.data.length) { + for (var i = 0; i < this.selectpicker.current.data.length; i++) { + var data = this.selectpicker.current.data[i]; + if (data.type === 'option' && $(data.element.firstChild).css('display') !== 'none') { + li = data.element; + break; + } + } + } else { + li = elementTemplates.li.cloneNode(false); + a.appendChild(text); + li.appendChild(a); + } + + dropdownHeader.appendChild(text.cloneNode(true)); + + if (this.selectpicker.view.widestOption) { + menuInnerInner.appendChild(this.selectpicker.view.widestOption.cloneNode(true)); + } + + menuInnerInner.appendChild(li); + menuInnerInner.appendChild(divider); + menuInnerInner.appendChild(dropdownHeader); + if (header) menu.appendChild(header); + if (search) { + var input = document.createElement('input'); + search.className = 'bs-searchbox'; + input.className = 'form-control'; + search.appendChild(input); + menu.appendChild(search); + } + if (actions) menu.appendChild(actions); + menuInner.appendChild(menuInnerInner); + menu.appendChild(menuInner); + if (doneButton) menu.appendChild(doneButton); + newElement.appendChild(menu); + + document.body.appendChild(newElement); + + var liHeight = li.offsetHeight, + dropdownHeaderHeight = dropdownHeader ? dropdownHeader.offsetHeight : 0, + headerHeight = header ? header.offsetHeight : 0, + searchHeight = search ? search.offsetHeight : 0, + actionsHeight = actions ? actions.offsetHeight : 0, + doneButtonHeight = doneButton ? doneButton.offsetHeight : 0, + dividerHeight = $(divider).outerHeight(true), + menuStyle = window.getComputedStyle(menu), + menuWidth = menu.offsetWidth, + menuPadding = { + vert: toInteger(menuStyle.paddingTop) + + toInteger(menuStyle.paddingBottom) + + toInteger(menuStyle.borderTopWidth) + + toInteger(menuStyle.borderBottomWidth), + horiz: toInteger(menuStyle.paddingLeft) + + toInteger(menuStyle.paddingRight) + + toInteger(menuStyle.borderLeftWidth) + + toInteger(menuStyle.borderRightWidth) + }, + menuExtras = { + vert: menuPadding.vert + + toInteger(menuStyle.marginTop) + + toInteger(menuStyle.marginBottom) + 2, + horiz: menuPadding.horiz + + toInteger(menuStyle.marginLeft) + + toInteger(menuStyle.marginRight) + 2 + }, + scrollBarWidth; + + menuInner.style.overflowY = 'scroll'; + + scrollBarWidth = menu.offsetWidth - menuWidth; + + document.body.removeChild(newElement); + + this.sizeInfo.liHeight = liHeight; + this.sizeInfo.dropdownHeaderHeight = dropdownHeaderHeight; + this.sizeInfo.headerHeight = headerHeight; + this.sizeInfo.searchHeight = searchHeight; + this.sizeInfo.actionsHeight = actionsHeight; + this.sizeInfo.doneButtonHeight = doneButtonHeight; + this.sizeInfo.dividerHeight = dividerHeight; + this.sizeInfo.menuPadding = menuPadding; + this.sizeInfo.menuExtras = menuExtras; + this.sizeInfo.menuWidth = menuWidth; + this.sizeInfo.menuInnerInnerWidth = menuWidth - menuPadding.horiz; + this.sizeInfo.totalMenuWidth = this.sizeInfo.menuWidth; + this.sizeInfo.scrollBarWidth = scrollBarWidth; + this.sizeInfo.selectHeight = this.$newElement[0].offsetHeight; + + this.setPositionData(); + }, + + getSelectPosition: function () { + var that = this, + $window = $(window), + pos = that.$newElement.offset(), + $container = $(that.options.container), + containerPos; + + if (that.options.container && $container.length && !$container.is('body')) { + containerPos = $container.offset(); + containerPos.top += parseInt($container.css('borderTopWidth')); + containerPos.left += parseInt($container.css('borderLeftWidth')); + } else { + containerPos = { top: 0, left: 0 }; + } + + var winPad = that.options.windowPadding; + + this.sizeInfo.selectOffsetTop = pos.top - containerPos.top - $window.scrollTop(); + this.sizeInfo.selectOffsetBot = $window.height() - this.sizeInfo.selectOffsetTop - this.sizeInfo.selectHeight - containerPos.top - winPad[2]; + this.sizeInfo.selectOffsetLeft = pos.left - containerPos.left - $window.scrollLeft(); + this.sizeInfo.selectOffsetRight = $window.width() - this.sizeInfo.selectOffsetLeft - this.sizeInfo.selectWidth - containerPos.left - winPad[1]; + this.sizeInfo.selectOffsetTop -= winPad[0]; + this.sizeInfo.selectOffsetLeft -= winPad[3]; + }, + + setMenuSize: function (isAuto) { + this.getSelectPosition(); + + var selectWidth = this.sizeInfo.selectWidth, + liHeight = this.sizeInfo.liHeight, + headerHeight = this.sizeInfo.headerHeight, + searchHeight = this.sizeInfo.searchHeight, + actionsHeight = this.sizeInfo.actionsHeight, + doneButtonHeight = this.sizeInfo.doneButtonHeight, + divHeight = this.sizeInfo.dividerHeight, + menuPadding = this.sizeInfo.menuPadding, + menuInnerHeight, + menuHeight, + divLength = 0, + minHeight, + _minHeight, + maxHeight, + menuInnerMinHeight, + estimate, + isDropup; + + if (this.options.dropupAuto) { + // Get the estimated height of the menu without scrollbars. + // This is useful for smaller menus, where there might be plenty of room + // below the button without setting dropup, but we can't know + // the exact height of the menu until createView is called later + estimate = liHeight * this.selectpicker.current.data.length + menuPadding.vert; + + isDropup = this.sizeInfo.selectOffsetTop - this.sizeInfo.selectOffsetBot > this.sizeInfo.menuExtras.vert && estimate + this.sizeInfo.menuExtras.vert + 50 > this.sizeInfo.selectOffsetBot; + + // ensure dropup doesn't change while searching (so menu doesn't bounce back and forth) + if (this.selectpicker.isSearching === true) { + isDropup = this.selectpicker.dropup; + } + + this.$newElement.toggleClass(classNames.DROPUP, isDropup); + this.selectpicker.dropup = isDropup; + } + + if (this.options.size === 'auto') { + _minHeight = this.selectpicker.current.data.length > 3 ? this.sizeInfo.liHeight * 3 + this.sizeInfo.menuExtras.vert - 2 : 0; + menuHeight = this.sizeInfo.selectOffsetBot - this.sizeInfo.menuExtras.vert; + minHeight = _minHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight; + menuInnerMinHeight = Math.max(_minHeight - menuPadding.vert, 0); + + if (this.$newElement.hasClass(classNames.DROPUP)) { + menuHeight = this.sizeInfo.selectOffsetTop - this.sizeInfo.menuExtras.vert; + } + + maxHeight = menuHeight; + menuInnerHeight = menuHeight - headerHeight - searchHeight - actionsHeight - doneButtonHeight - menuPadding.vert; + } else if (this.options.size && this.options.size != 'auto' && this.selectpicker.current.elements.length > this.options.size) { + for (var i = 0; i < this.options.size; i++) { + if (this.selectpicker.current.data[i].type === 'divider') divLength++; + } + + menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding.vert; + menuInnerHeight = menuHeight - menuPadding.vert; + maxHeight = menuHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight; + minHeight = menuInnerMinHeight = ''; + } + + this.$menu.css({ + 'max-height': maxHeight + 'px', + 'overflow': 'hidden', + 'min-height': minHeight + 'px' + }); + + this.$menuInner.css({ + 'max-height': menuInnerHeight + 'px', + 'overflow': 'hidden auto', + 'min-height': menuInnerMinHeight + 'px' + }); + + // ensure menuInnerHeight is always a positive number to prevent issues calculating chunkSize in createView + this.sizeInfo.menuInnerHeight = Math.max(menuInnerHeight, 1); + + if (this.selectpicker.current.data.length && this.selectpicker.current.data[this.selectpicker.current.data.length - 1].position > this.sizeInfo.menuInnerHeight) { + this.sizeInfo.hasScrollBar = true; + this.sizeInfo.totalMenuWidth = this.sizeInfo.menuWidth + this.sizeInfo.scrollBarWidth; + } + + if (this.options.dropdownAlignRight === 'auto') { + this.$menu.toggleClass(classNames.MENURIGHT, this.sizeInfo.selectOffsetLeft > this.sizeInfo.selectOffsetRight && this.sizeInfo.selectOffsetRight < (this.sizeInfo.totalMenuWidth - selectWidth)); + } + + if (this.dropdown && this.dropdown._popper) this.dropdown._popper.update(); + }, + + setSize: function (refresh) { + this.liHeight(refresh); + + if (this.options.header) this.$menu.css('padding-top', 0); + + if (this.options.size !== false) { + var that = this, + $window = $(window); + + this.setMenuSize(); + + if (this.options.liveSearch) { + this.$searchbox + .off('input.setMenuSize propertychange.setMenuSize') + .on('input.setMenuSize propertychange.setMenuSize', function () { + return that.setMenuSize(); + }); + } + + if (this.options.size === 'auto') { + $window + .off('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize') + .on('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize', function () { + return that.setMenuSize(); + }); + } else if (this.options.size && this.options.size != 'auto' && this.selectpicker.current.elements.length > this.options.size) { + $window.off('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize'); + } + } + + this.createView(false, true, refresh); + }, + + setWidth: function () { + var that = this; + + if (this.options.width === 'auto') { + requestAnimationFrame(function () { + that.$menu.css('min-width', '0'); + + that.$element.on('loaded' + EVENT_KEY, function () { + that.liHeight(); + that.setMenuSize(); + + // Get correct width if element is hidden + var $selectClone = that.$newElement.clone().appendTo('body'), + btnWidth = $selectClone.css('width', 'auto').children('button').outerWidth(); + + $selectClone.remove(); + + // Set width to whatever's larger, button title or longest option + that.sizeInfo.selectWidth = Math.max(that.sizeInfo.totalMenuWidth, btnWidth); + that.$newElement.css('width', that.sizeInfo.selectWidth + 'px'); + }); + }); + } else if (this.options.width === 'fit') { + // Remove inline min-width so width can be changed from 'auto' + this.$menu.css('min-width', ''); + this.$newElement.css('width', '').addClass('fit-width'); + } else if (this.options.width) { + // Remove inline min-width so width can be changed from 'auto' + this.$menu.css('min-width', ''); + this.$newElement.css('width', this.options.width); + } else { + // Remove inline min-width/width so width can be changed + this.$menu.css('min-width', ''); + this.$newElement.css('width', ''); + } + // Remove fit-width class if width is changed programmatically + if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') { + this.$newElement[0].classList.remove('fit-width'); + } + }, + + selectPosition: function () { + this.$bsContainer = $('
    '); + + var that = this, + $container = $(this.options.container), + pos, + containerPos, + actualHeight, + getPlacement = function ($element) { + var containerPosition = {}, + // fall back to dropdown's default display setting if display is not manually set + display = that.options.display || ( + // Bootstrap 3 doesn't have $.fn.dropdown.Constructor.Default + $.fn.dropdown.Constructor.Default ? $.fn.dropdown.Constructor.Default.display + : false + ); + + that.$bsContainer.addClass($element.attr('class').replace(/form-control|fit-width/gi, '')).toggleClass(classNames.DROPUP, $element.hasClass(classNames.DROPUP)); + pos = $element.offset(); + + if (!$container.is('body')) { + containerPos = $container.offset(); + containerPos.top += parseInt($container.css('borderTopWidth')) - $container.scrollTop(); + containerPos.left += parseInt($container.css('borderLeftWidth')) - $container.scrollLeft(); + } else { + containerPos = { top: 0, left: 0 }; + } + + actualHeight = $element.hasClass(classNames.DROPUP) ? 0 : $element[0].offsetHeight; + + // Bootstrap 4+ uses Popper for menu positioning + if (version.major < 4 || display === 'static') { + containerPosition.top = pos.top - containerPos.top + actualHeight; + containerPosition.left = pos.left - containerPos.left; + } + + containerPosition.width = $element[0].offsetWidth; + + that.$bsContainer.css(containerPosition); + }; + + this.$button.on('click.bs.dropdown.data-api', function () { + if (that.isDisabled()) { + return; + } + + getPlacement(that.$newElement); + + that.$bsContainer + .appendTo(that.options.container) + .toggleClass(classNames.SHOW, !that.$button.hasClass(classNames.SHOW)) + .append(that.$menu); + }); + + $(window) + .off('resize' + EVENT_KEY + '.' + this.selectId + ' scroll' + EVENT_KEY + '.' + this.selectId) + .on('resize' + EVENT_KEY + '.' + this.selectId + ' scroll' + EVENT_KEY + '.' + this.selectId, function () { + var isActive = that.$newElement.hasClass(classNames.SHOW); + + if (isActive) getPlacement(that.$newElement); + }); + + this.$element.on('hide' + EVENT_KEY, function () { + that.$menu.data('height', that.$menu.height()); + that.$bsContainer.detach(); + }); + }, + + createOption: function (data, init) { + var optionData = !data.option ? data : data.option; + + if (optionData && optionData.nodeType !== 1) { + var option = (init ? elementTemplates.selectedOption : elementTemplates.option).cloneNode(true); + if (optionData.value !== undefined) option.value = optionData.value; + option.textContent = optionData.text; + + option.selected = true; + + if (optionData.liIndex !== undefined) { + option.liIndex = optionData.liIndex; + } else if (!init) { + option.liIndex = data.index; + } + + data.option = option; + + this.selectpicker.main.optionQueue.appendChild(option); + } + }, + + setOptionStatus: function (selectedOnly) { + var that = this; + + that.noScroll = false; + + if (that.selectpicker.view.visibleElements && that.selectpicker.view.visibleElements.length) { + for (var i = 0; i < that.selectpicker.view.visibleElements.length; i++) { + var liData = that.selectpicker.current.data[i + that.selectpicker.view.position0], + option = liData.option; + + if (option) { + if (selectedOnly !== true) { + that.setDisabled(liData); + } + + that.setSelected(liData); + } + } + + // append optionQueue (documentFragment with option elements for select options) + if (this.options.source.data) this.$element[0].appendChild(this.selectpicker.main.optionQueue); + } + }, + + /** + * @param {Object} liData - the option object that is being changed + * @param {boolean} selected - true if the option is being selected, false if being deselected + */ + setSelected: function (liData, selected) { + selected = selected === undefined ? liData.selected : selected; + + var li = liData.element, + activeElementIsSet = this.activeElement !== undefined, + thisIsActive = this.activeElement === li, + prevActive, + a, + // if current option is already active + // OR + // if the current option is being selected, it's NOT multiple, and + // activeElement is undefined: + // - when the menu is first being opened, OR + // - after a search has been performed, OR + // - when retainActive is false when selecting a new option (i.e. index of the newly selected option is not the same as the current activeElement) + keepActive = thisIsActive || (selected && !this.multiple && !activeElementIsSet); + + if (!li) return; + + if (selected !== undefined) { + liData.selected = selected; + if (liData.option) liData.option.selected = selected; + } + + if (selected && this.options.source.data) { + this.createOption(liData, false); + } + + a = li.firstChild; + + if (selected) { + this.selectedElement = li; + } + + li.classList.toggle('selected', selected); + + if (keepActive) { + this.focusItem(li, liData); + this.selectpicker.view.currentActive = li; + this.activeElement = li; + } else { + this.defocusItem(li); + } + + if (a) { + a.classList.toggle('selected', selected); + + if (selected) { + a.setAttribute('aria-selected', true); + } else { + if (this.multiple) { + a.setAttribute('aria-selected', false); + } else { + a.removeAttribute('aria-selected'); + } + } + } + + if (!keepActive && !activeElementIsSet && selected && this.prevActiveElement !== undefined) { + prevActive = this.prevActiveElement; + + this.defocusItem(prevActive); + } + }, + + /** + * @param {number} index - the index of the option that is being disabled + * @param {boolean} disabled - true if the option is being disabled, false if being enabled + */ + setDisabled: function (liData) { + var disabled = liData.disabled, + li = liData.element, + a; + + if (!li) return; + + a = li.firstChild; + + li.classList.toggle(classNames.DISABLED, disabled); + + if (a) { + if (version.major >= '4') a.classList.toggle(classNames.DISABLED, disabled); + + if (disabled) { + a.setAttribute('aria-disabled', disabled); + a.setAttribute('tabindex', -1); + } else { + a.removeAttribute('aria-disabled'); + a.setAttribute('tabindex', 0); + } + } + }, + + isDisabled: function () { + return this.$element[0].disabled; + }, + + checkDisabled: function () { + if (this.isDisabled()) { + this.$newElement[0].classList.add(classNames.DISABLED); + this.$button.addClass(classNames.DISABLED).attr('aria-disabled', true); + } else { + if (this.$button[0].classList.contains(classNames.DISABLED)) { + this.$newElement[0].classList.remove(classNames.DISABLED); + this.$button.removeClass(classNames.DISABLED).attr('aria-disabled', false); + } + } + }, + + clickListener: function () { + var that = this, + $document = $(document); + + $document.data('spaceSelect', false); + + this.$button.on('keyup', function (e) { + if (/(32)/.test(e.keyCode.toString(10)) && $document.data('spaceSelect')) { + e.preventDefault(); + $document.data('spaceSelect', false); + } + }); + + this.$newElement.on('show.bs.dropdown', function () { + if (!that.dropdown && version.major === '4') { + that.dropdown = that.$button.data('bs.dropdown'); + that.dropdown._menu = that.$menu[0]; + } + }); + + function clearSelection (e) { + if (that.multiple) { + that.deselectAll(); + } else { + var element = that.$element[0], + prevValue = element.value, + prevIndex = element.selectedIndex, + prevOption = element.options[prevIndex], + prevData = prevOption ? that.selectpicker.main.data[prevOption.liIndex] : false; + + if (prevData) { + that.setSelected(prevData, false); + } + + element.selectedIndex = 0; + + changedArguments = [prevIndex, false, prevValue]; + that.$element.triggerNative('change'); + } + + // remove selected styling if menu is open + if (that.$newElement.hasClass(classNames.SHOW)) { + if (that.options.liveSearch) { + that.$searchbox.trigger('focus'); + } + + that.createView(false); + } + } + + this.$button.on('click.bs.dropdown.data-api', function (e) { + if (that.options.allowClear) { + var target = e.target, + clearButton = that.$clearButton[0]; + + // IE doesn't support event listeners on child elements of buttons + if (/MSIE|Trident/.test(window.navigator.userAgent)) { + target = document.elementFromPoint(e.clientX, e.clientY); + } + + if (target === clearButton || target.parentElement === clearButton) { + e.stopImmediatePropagation(); + clearSelection(e); + } + } + + if (!that.$newElement.hasClass(classNames.SHOW)) { + that.setSize(); + } + }); + + function setFocus () { + if (that.options.liveSearch) { + that.$searchbox.trigger('focus'); + } else { + that.$menuInner.trigger('focus'); + } + } + + function checkPopperExists () { + if (that.dropdown && that.dropdown._popper && that.dropdown._popper.state) { + setFocus(); + } else { + requestAnimationFrame(checkPopperExists); + } + } + + this.$element.on('shown' + EVENT_KEY, function () { + if (that.$menuInner[0].scrollTop !== that.selectpicker.view.scrollTop) { + that.$menuInner[0].scrollTop = that.selectpicker.view.scrollTop; + } + + if (version.major > 3) { + requestAnimationFrame(checkPopperExists); + } else { + setFocus(); + } + }); + + // ensure posinset and setsize are correct before selecting an option via a click + this.$menuInner.on('mouseenter', 'li a', function (e) { + var hoverLi = this.parentElement, + position0 = that.isVirtual() ? that.selectpicker.view.position0 : 0, + index = Array.prototype.indexOf.call(hoverLi.parentElement.children, hoverLi), + hoverData = that.selectpicker.current.data[index + position0]; + + that.focusItem(hoverLi, hoverData, true); + }); + + this.$menuInner.on('click', 'li a', function (e, retainActive) { + var $this = $(this), + element = that.$element[0], + position0 = that.isVirtual() ? that.selectpicker.view.position0 : 0, + clickedData = that.selectpicker.current.data[$this.parent().index() + position0], + clickedElement = clickedData.element, + prevValue = getSelectValues.call(that), + prevIndex = element.selectedIndex, + prevOption = element.options[prevIndex], + prevData = prevOption ? that.selectpicker.main.data[prevOption.liIndex] : false, + triggerChange = true; + + // Don't close on multi choice menu + if (that.multiple && that.options.maxOptions !== 1) { + e.stopPropagation(); + } + + e.preventDefault(); + + // Don't run if the select is disabled + if (!that.isDisabled() && !$this.parent().hasClass(classNames.DISABLED)) { + var option = clickedData.option, + $option = $(option), + state = option.selected, + optgroupData = that.selectpicker.current.data.find(function (datum) { + return datum.optID === clickedData.optID && datum.type === 'optgroup-label'; + }), + optgroup = optgroupData ? optgroupData.optgroup : undefined, + dataGetter = optgroup instanceof Element ? getOptionData.fromOption : getOptionData.fromDataSource, + optgroupOptions = optgroup && optgroup.children, + maxOptions = parseInt(that.options.maxOptions), + maxOptionsGrp = optgroup && parseInt(dataGetter(optgroup, 'maxOptions')) || false; + + if (clickedElement === that.activeElement) retainActive = true; + + if (!retainActive) { + that.prevActiveElement = that.activeElement; + that.activeElement = undefined; + } + + if (!that.multiple || maxOptions === 1) { // Deselect previous option if not multi select + if (prevData) that.setSelected(prevData, false); + that.setSelected(clickedData, true); + } else { // Toggle the clicked option if multi select. + that.setSelected(clickedData, !state); + that.focusedParent.focus(); + + if (maxOptions !== false || maxOptionsGrp !== false) { + var maxReached = maxOptions < getSelectedOptions.call(that).length, + selectedGroupOptions = 0; + + if (optgroup && optgroup.children) { + for (var i = 0; i < optgroup.children.length; i++) { + if (optgroup.children[i].selected) selectedGroupOptions++; + } + } + + var maxReachedGrp = maxOptionsGrp < selectedGroupOptions; + + if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) { + if (maxOptions && maxOptions === 1) { + element.selectedIndex = -1; + that.setOptionStatus(true); + } else if (maxOptionsGrp && maxOptionsGrp === 1) { + for (var i = 0; i < optgroupOptions.length; i++) { + var _option = optgroupOptions[i]; + that.setSelected(that.selectpicker.current.data[_option.liIndex], false); + } + + that.setSelected(clickedData, true); + } else { + var maxOptionsText = typeof that.options.maxOptionsText === 'string' ? [that.options.maxOptionsText, that.options.maxOptionsText] : that.options.maxOptionsText, + maxOptionsArr = typeof maxOptionsText === 'function' ? maxOptionsText(maxOptions, maxOptionsGrp) : maxOptionsText, + maxTxt = maxOptionsArr[0].replace('{n}', maxOptions), + maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp), + $notify = $('
    '); + // If {var} is set in array, replace it + /** @deprecated */ + if (maxOptionsArr[2]) { + maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]); + maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]); + } + + that.$menu.append($notify); + + if (maxOptions && maxReached) { + $notify.append($('
    ' + maxTxt + '
    ')); + triggerChange = false; + that.$element.trigger('maxReached' + EVENT_KEY); + } + + if (maxOptionsGrp && maxReachedGrp) { + $notify.append($('
    ' + maxTxtGrp + '
    ')); + triggerChange = false; + that.$element.trigger('maxReachedGrp' + EVENT_KEY); + } + + setTimeout(function () { + that.setSelected(clickedData, false); + }, 10); + + $notify[0].classList.add('fadeOut'); + + setTimeout(function () { + $notify.remove(); + }, 1050); + } + } + } + } + + if (that.options.source.data) that.$element[0].appendChild(that.selectpicker.main.optionQueue); + + if (!that.multiple || (that.multiple && that.options.maxOptions === 1)) { + that.$button.trigger('focus'); + } else if (that.options.liveSearch) { + that.$searchbox.trigger('focus'); + } + + // Trigger select 'change' + if (triggerChange) { + if (that.multiple || prevIndex !== element.selectedIndex) { + // $option.prop('selected') is current option state (selected/unselected). prevValue is the value of the select prior to being changed. + changedArguments = [option.index, $option.prop('selected'), prevValue]; + that.$element + .triggerNative('change'); + } + } + } + }); + + this.$menu.on('click', 'li.' + classNames.DISABLED + ' a, .' + classNames.POPOVERHEADER + ', .' + classNames.POPOVERHEADER + ' :not(.close)', function (e) { + if (e.currentTarget == this) { + e.preventDefault(); + e.stopPropagation(); + if (that.options.liveSearch && !$(e.target).hasClass('close')) { + that.$searchbox.trigger('focus'); + } else { + that.$button.trigger('focus'); + } + } + }); + + this.$menuInner.on('click', '.divider, .dropdown-header', function (e) { + e.preventDefault(); + e.stopPropagation(); + if (that.options.liveSearch) { + that.$searchbox.trigger('focus'); + } else { + that.$button.trigger('focus'); + } + }); + + this.$menu.on('click', '.' + classNames.POPOVERHEADER + ' .close', function () { + that.$button.trigger('click'); + }); + + this.$searchbox.on('click', function (e) { + e.stopPropagation(); + }); + + this.$menu.on('click', '.actions-btn', function (e) { + if (that.options.liveSearch) { + that.$searchbox.trigger('focus'); + } else { + that.$button.trigger('focus'); + } + + e.preventDefault(); + e.stopPropagation(); + + if ($(this).hasClass('bs-select-all')) { + that.selectAll(); + } else { + that.deselectAll(); + } + }); + + this.$button + .on('focus' + EVENT_KEY, function (e) { + var tabindex = that.$element[0].getAttribute('tabindex'); + + // only change when button is actually focused + if (tabindex !== undefined && e.originalEvent && e.originalEvent.isTrusted) { + // apply select element's tabindex to ensure correct order is followed when tabbing to the next element + this.setAttribute('tabindex', tabindex); + // set element's tabindex to -1 to allow for reverse tabbing + that.$element[0].setAttribute('tabindex', -1); + that.selectpicker.view.tabindex = tabindex; + } + }) + .on('blur' + EVENT_KEY, function (e) { + // revert everything to original tabindex + if (that.selectpicker.view.tabindex !== undefined && e.originalEvent && e.originalEvent.isTrusted) { + that.$element[0].setAttribute('tabindex', that.selectpicker.view.tabindex); + this.setAttribute('tabindex', -1); + that.selectpicker.view.tabindex = undefined; + } + }); + + this.$element + .on('change' + EVENT_KEY, function () { + that.render(); + that.$element.trigger('changed' + EVENT_KEY, changedArguments); + changedArguments = null; + }) + .on('focus' + EVENT_KEY, function () { + if (!that.options.mobile) that.$button[0].focus(); + }); + }, + + liveSearchListener: function () { + var that = this; + + this.$button.on('click.bs.dropdown.data-api', function () { + if (!!that.$searchbox.val()) { + that.$searchbox.val(''); + that.selectpicker.search.previousValue = undefined; + } + }); + + this.$searchbox.on('click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api', function (e) { + e.stopPropagation(); + }); + + this.$searchbox.on('input propertychange', function () { + var searchValue = that.$searchbox[0].value; + + that.selectpicker.search.elements = []; + that.selectpicker.search.data = []; + + if (searchValue) { + that.selectpicker.search.previousValue = searchValue; + + if (that.options.source.search) { + that.fetchData(function (builtData) { + that.render(); + that.buildList(undefined, true); + that.noScroll = true; + that.$menuInner.scrollTop(0); + that.createView(true); + showNoResults.call(that, builtData, searchValue); + }, 'search', 0, searchValue); + } else { + var i, + searchMatch = [], + q = searchValue.toUpperCase(), + cache = {}, + cacheArr = [], + searchStyle = that._searchStyle(), + normalizeSearch = that.options.liveSearchNormalize; + + if (normalizeSearch) q = normalizeToBase(q); + + for (var i = 0; i < that.selectpicker.main.data.length; i++) { + var li = that.selectpicker.main.data[i]; + + if (!cache[i]) { + cache[i] = stringSearch(li, q, searchStyle, normalizeSearch); + } + + if (cache[i] && li.headerIndex !== undefined && cacheArr.indexOf(li.headerIndex) === -1) { + if (li.headerIndex > 0) { + cache[li.headerIndex - 1] = true; + cacheArr.push(li.headerIndex - 1); + } + + cache[li.headerIndex] = true; + cacheArr.push(li.headerIndex); + + cache[li.lastIndex + 1] = true; + } + + if (cache[i] && li.type !== 'optgroup-label') cacheArr.push(i); + } + + for (var i = 0, cacheLen = cacheArr.length; i < cacheLen; i++) { + var index = cacheArr[i], + prevIndex = cacheArr[i - 1], + li = that.selectpicker.main.data[index], + liPrev = that.selectpicker.main.data[prevIndex]; + + if (li.type !== 'divider' || (li.type === 'divider' && liPrev && liPrev.type !== 'divider' && cacheLen - 1 !== i)) { + that.selectpicker.search.data.push(li); + searchMatch.push(that.selectpicker.main.elements[index]); + } + } + + that.activeElement = undefined; + that.noScroll = true; + that.$menuInner.scrollTop(0); + that.selectpicker.search.elements = searchMatch; + that.createView(true); + showNoResults.call(that, searchMatch, searchValue); + } + } else if (that.selectpicker.search.previousValue) { // for IE11 (#2402) + that.$menuInner.scrollTop(0); + that.createView(false); + } + }); + }, + + _searchStyle: function () { + return this.options.liveSearchStyle || 'contains'; + }, + + val: function (value) { + var element = this.$element[0]; + + if (typeof value !== 'undefined') { + var selectedOptions = getSelectedOptions.call(this), + prevValue = getSelectValues.call(this, selectedOptions); + + changedArguments = [null, null, prevValue]; + + if (!Array.isArray(value)) value = [ value ]; + + value.map(String); + + for (var i = 0; i < selectedOptions.length; i++) { + var item = selectedOptions[i]; + + if (item && value.indexOf(String(item.value)) === -1) { + this.setSelected(item, false); + } + } + + // only update selected value if it matches an existing option + this.selectpicker.main.data.filter(function (item) { + if (value.indexOf(String(item.value)) !== -1) { + this.setSelected(item, true); + return true; + } + + return false; + }, this); + + if (this.options.source.data) element.appendChild(this.selectpicker.main.optionQueue); + + this.$element.trigger('changed' + EVENT_KEY, changedArguments); + + if (this.$newElement.hasClass(classNames.SHOW)) { + if (this.multiple) { + this.setOptionStatus(true); + } else { + var liSelectedIndex = (element.options[element.selectedIndex] || {}).liIndex; + + if (typeof liSelectedIndex === 'number') { + this.setSelected(this.selectpicker.current.data[liSelectedIndex], true); + } + } + } + + this.render(); + + changedArguments = null; + + return this.$element; + } else { + return this.$element.val(); + } + }, + + changeAll: function (status) { + if (!this.multiple) return; + if (typeof status === 'undefined') status = true; + + var element = this.$element[0], + previousSelected = 0, + currentSelected = 0, + prevValue = getSelectValues.call(this); + + element.classList.add('bs-select-hidden'); + + for (var i = 0, data = this.selectpicker.current.data, len = data.length; i < len; i++) { + var liData = data[i], + option = liData.option; + + if (option && !liData.disabled && liData.type !== 'divider') { + if (liData.selected) previousSelected++; + option.selected = status; + liData.selected = status; + if (status === true) currentSelected++; + } + } + + element.classList.remove('bs-select-hidden'); + + if (previousSelected === currentSelected) return; + + this.setOptionStatus(); + + changedArguments = [null, null, prevValue]; + + this.$element + .triggerNative('change'); + }, + + selectAll: function () { + return this.changeAll(true); + }, + + deselectAll: function () { + return this.changeAll(false); + }, + + toggle: function (e, state) { + var isActive, + triggerClick = state === undefined; + + e = e || window.event; + + if (e) e.stopPropagation(); + + if (triggerClick === false) { + isActive = this.$newElement[0].classList.contains(classNames.SHOW); + triggerClick = state === true && isActive === false || state === false && isActive === true; + } + + if (triggerClick) this.$button.trigger('click.bs.dropdown.data-api'); + }, + + open: function (e) { + this.toggle(e, true); + }, + + close: function (e) { + this.toggle(e, false); + }, + + keydown: function (e) { + var $this = $(this), + isToggle = $this.hasClass('dropdown-toggle'), + $parent = isToggle ? $this.closest('.dropdown') : $this.closest(Selector.MENU), + that = $parent.data('this'), + $items = that.findLis(), + index, + isActive, + liActive, + activeLi, + offset, + updateScroll = false, + downOnTab = e.which === keyCodes.TAB && !isToggle && !that.options.selectOnTab, + isArrowKey = REGEXP_ARROW.test(e.which) || downOnTab, + scrollTop = that.$menuInner[0].scrollTop, + isVirtual = that.isVirtual(), + position0 = isVirtual === true ? that.selectpicker.view.position0 : 0; + + // do nothing if a function key is pressed + if (e.which >= 112 && e.which <= 123) return; + + isActive = that.$menu.hasClass(classNames.SHOW); + + if ( + !isActive && + ( + isArrowKey || + (e.which >= 48 && e.which <= 57) || + (e.which >= 96 && e.which <= 105) || + (e.which >= 65 && e.which <= 90) + ) + ) { + that.$button.trigger('click.bs.dropdown.data-api'); + + if (that.options.liveSearch) { + that.$searchbox.trigger('focus'); + return; + } + } + + if (e.which === keyCodes.ESCAPE && isActive) { + e.preventDefault(); + that.$button.trigger('click.bs.dropdown.data-api').trigger('focus'); + } + + if (isArrowKey) { // if up or down + if (!$items.length) return; + + liActive = that.activeElement; + index = liActive ? Array.prototype.indexOf.call(liActive.parentElement.children, liActive) : -1; + + if (index !== -1) { + that.defocusItem(liActive); + } + + if (e.which === keyCodes.ARROW_UP) { // up + if (index !== -1) index--; + if (index + position0 < 0) index += $items.length; + + if (!that.selectpicker.view.canHighlight[index + position0]) { + index = that.selectpicker.view.canHighlight.slice(0, index + position0).lastIndexOf(true) - position0; + if (index === -1) index = $items.length - 1; + } + } else if (e.which === keyCodes.ARROW_DOWN || downOnTab) { // down + index++; + if (index + position0 >= that.selectpicker.view.canHighlight.length) index = that.selectpicker.view.firstHighlightIndex; + + if (!that.selectpicker.view.canHighlight[index + position0]) { + index = index + 1 + that.selectpicker.view.canHighlight.slice(index + position0 + 1).indexOf(true); + } + } + + e.preventDefault(); + + var liActiveIndex = position0 + index; + + if (e.which === keyCodes.ARROW_UP) { // up + // scroll to bottom and highlight last option + if (position0 === 0 && index === $items.length - 1) { + that.$menuInner[0].scrollTop = that.$menuInner[0].scrollHeight; + + liActiveIndex = that.selectpicker.current.elements.length - 1; + } else { + activeLi = that.selectpicker.current.data[liActiveIndex]; + + // could be undefined if no results exist + if (activeLi) { + offset = activeLi.position - activeLi.height; + + updateScroll = offset < scrollTop; + } + } + } else if (e.which === keyCodes.ARROW_DOWN || downOnTab) { // down + // scroll to top and highlight first option + if (index === that.selectpicker.view.firstHighlightIndex) { + that.$menuInner[0].scrollTop = 0; + + liActiveIndex = that.selectpicker.view.firstHighlightIndex; + } else { + activeLi = that.selectpicker.current.data[liActiveIndex]; + + // could be undefined if no results exist + if (activeLi) { + offset = activeLi.position - that.sizeInfo.menuInnerHeight; + + updateScroll = offset > scrollTop; + } + } + } + + liActive = that.selectpicker.current.elements[liActiveIndex]; + + that.activeElement = (that.selectpicker.current.data[liActiveIndex] || {}).element; + + that.focusItem(liActive); + + that.selectpicker.view.currentActive = liActive; + + if (updateScroll) that.$menuInner[0].scrollTop = offset; + + if (that.options.liveSearch) { + that.$searchbox.trigger('focus'); + } else { + $this.trigger('focus'); + } + } else if ( + (!$this.is('input') && !REGEXP_TAB_OR_ESCAPE.test(e.which)) || + (e.which === keyCodes.SPACE && that.selectpicker.keydown.keyHistory) + ) { + var searchMatch, + matches = [], + keyHistory; + + e.preventDefault(); + + that.selectpicker.keydown.keyHistory += keyCodeMap[e.which]; + + if (that.selectpicker.keydown.resetKeyHistory.cancel) clearTimeout(that.selectpicker.keydown.resetKeyHistory.cancel); + that.selectpicker.keydown.resetKeyHistory.cancel = that.selectpicker.keydown.resetKeyHistory.start(); + + keyHistory = that.selectpicker.keydown.keyHistory; + + // if all letters are the same, set keyHistory to just the first character when searching + if (/^(.)\1+$/.test(keyHistory)) { + keyHistory = keyHistory.charAt(0); + } + + // find matches + for (var i = 0; i < that.selectpicker.current.data.length; i++) { + var li = that.selectpicker.current.data[i], + hasMatch; + + hasMatch = stringSearch(li, keyHistory, 'startsWith', true); + + if (hasMatch && that.selectpicker.view.canHighlight[i]) { + matches.push(li.element); + } + } + + if (matches.length) { + var matchIndex = 0; + + $items.removeClass('active').find('a').removeClass('active'); + + // either only one key has been pressed or they are all the same key + if (keyHistory.length === 1) { + matchIndex = matches.indexOf(that.activeElement); + + if (matchIndex === -1 || matchIndex === matches.length - 1) { + matchIndex = 0; + } else { + matchIndex++; + } + } + + searchMatch = matches[matchIndex]; + + activeLi = that.selectpicker.main.data[searchMatch]; + + if (scrollTop - activeLi.position > 0) { + offset = activeLi.position - activeLi.height; + updateScroll = true; + } else { + offset = activeLi.position - that.sizeInfo.menuInnerHeight; + // if the option is already visible at the current scroll position, just keep it the same + updateScroll = activeLi.position > scrollTop + that.sizeInfo.menuInnerHeight; + } + + liActive = that.selectpicker.main.elements[searchMatch]; + + that.activeElement = liActive; + + that.focusItem(liActive); + + if (liActive) liActive.firstChild.focus(); + + if (updateScroll) that.$menuInner[0].scrollTop = offset; + + $this.trigger('focus'); + } + } + + // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu. + if ( + isActive && + ( + (e.which === keyCodes.SPACE && !that.selectpicker.keydown.keyHistory) || + e.which === keyCodes.ENTER || + (e.which === keyCodes.TAB && that.options.selectOnTab) + ) + ) { + if (e.which !== keyCodes.SPACE) e.preventDefault(); + + if (!that.options.liveSearch || e.which !== keyCodes.SPACE) { + that.$menuInner.find('.active a').trigger('click', true); // retain active class + $this.trigger('focus'); + + if (!that.options.liveSearch) { + // Prevent screen from scrolling if the user hits the spacebar + e.preventDefault(); + // Fixes spacebar selection of dropdown items in FF & IE + $(document).data('spaceSelect', true); + } + } + } + }, + + mobile: function () { + // ensure mobile is set to true if mobile function is called after init + this.options.mobile = true; + this.$element[0].classList.add('mobile-device'); + }, + + refresh: function () { + var that = this; + // update options if data attributes have been changed + var config = $.extend({}, this.options, getAttributesObject(this.$element), this.$element.data()); // in this order on refresh, as user may change attributes on select, and options object is not passed on refresh + this.options = config; + + if (this.options.source.data) { + this.render(); + this.buildList(); + } else { + this.fetchData(function () { + that.render(); + that.buildList(); + }); + } + + this.checkDisabled(); + this.setStyle(); + this.setWidth(); + + this.setSize(true); + + this.$element.trigger('refreshed' + EVENT_KEY); + }, + + hide: function () { + this.$newElement.hide(); + }, + + show: function () { + this.$newElement.show(); + }, + + remove: function () { + this.$newElement.remove(); + this.$element.remove(); + }, + + destroy: function () { + this.$newElement.before(this.$element).remove(); + + if (this.$bsContainer) { + this.$bsContainer.remove(); + } else { + this.$menu.remove(); + } + + if (this.selectpicker.view.titleOption && this.selectpicker.view.titleOption.parentNode) { + this.selectpicker.view.titleOption.parentNode.removeChild(this.selectpicker.view.titleOption); + } + + this.$element + .off(EVENT_KEY) + .removeData('selectpicker') + .removeClass('bs-select-hidden selectpicker mobile-device'); + + $(window).off(EVENT_KEY + '.' + this.selectId); + } + }; + + // SELECTPICKER PLUGIN DEFINITION + // ============================== + function Plugin (option) { + // get the args of the outer function.. + var args = arguments; + // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them + // to get lost/corrupted in android 2.3 and IE9 #715 #775 + var _option = option; + + [].shift.apply(args); + + // if the version was not set successfully + if (!version.success) { + // try to retreive it again + try { + version.full = (getVersion() || '').split(' ')[0].split('.'); + } catch (err) { + // fall back to use BootstrapVersion if set + if (Selectpicker.BootstrapVersion) { + version.full = Selectpicker.BootstrapVersion.split(' ')[0].split('.'); + } else { + version.full = [version.major, '0', '0']; + + console.warn( + 'There was an issue retrieving Bootstrap\'s version. ' + + 'Ensure Bootstrap is being loaded before bootstrap-select and there is no namespace collision. ' + + 'If loading Bootstrap asynchronously, the version may need to be manually specified via $.fn.selectpicker.Constructor.BootstrapVersion.', + err + ); + } + } + + version.major = version.full[0]; + version.success = true; + } + + if (version.major >= '4') { + // some defaults need to be changed if using Bootstrap 4 + // check to see if they have already been manually changed before forcing them to update + var toUpdate = []; + + if (Selectpicker.DEFAULTS.style === classNames.BUTTONCLASS) toUpdate.push({ name: 'style', className: 'BUTTONCLASS' }); + if (Selectpicker.DEFAULTS.iconBase === classNames.ICONBASE) toUpdate.push({ name: 'iconBase', className: 'ICONBASE' }); + if (Selectpicker.DEFAULTS.tickIcon === classNames.TICKICON) toUpdate.push({ name: 'tickIcon', className: 'TICKICON' }); + + classNames.DIVIDER = 'dropdown-divider'; + classNames.SHOW = 'show'; + classNames.BUTTONCLASS = 'btn-light'; + classNames.POPOVERHEADER = 'popover-header'; + classNames.ICONBASE = ''; + classNames.TICKICON = 'bs-ok-default'; + + for (var i = 0; i < toUpdate.length; i++) { + var option = toUpdate[i]; + Selectpicker.DEFAULTS[option.name] = classNames[option.className]; + } + } + + if (version.major > '4') { + Selector.DATA_TOGGLE = 'data-bs-toggle="dropdown"'; + } + + var value; + var chain = this.each(function () { + var $this = $(this); + if ($this.is('select')) { + var data = $this.data('selectpicker'), + options = typeof _option == 'object' && _option; + + // for backwards compatibility + // (using title as placeholder is deprecated - remove in v2.0.0) + if (options.title) options.placeholder = options.title; + + if (!data) { + var dataAttributes = $this.data(); + + for (var dataAttr in dataAttributes) { + if (Object.prototype.hasOwnProperty.call(dataAttributes, dataAttr) && $.inArray(dataAttr, DISALLOWED_ATTRIBUTES) !== -1) { + delete dataAttributes[dataAttr]; + } + } + + var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults || {}, getAttributesObject($this), dataAttributes, options); // this is correct order on initial render + config.template = $.extend({}, Selectpicker.DEFAULTS.template, ($.fn.selectpicker.defaults ? $.fn.selectpicker.defaults.template : {}), dataAttributes.template, options.template); + config.source = $.extend({}, Selectpicker.DEFAULTS.source, ($.fn.selectpicker.defaults ? $.fn.selectpicker.defaults.source : {}), options.source); + $this.data('selectpicker', (data = new Selectpicker(this, config))); + } else if (options) { + for (var i in options) { + if (Object.prototype.hasOwnProperty.call(options, i)) { + data.options[i] = options[i]; + } + } + } + + if (typeof _option == 'string') { + if (data[_option] instanceof Function) { + value = data[_option].apply(data, args); + } else { + value = data.options[_option]; + } + } + } + }); + + if (typeof value !== 'undefined') { + // noinspection JSUnusedAssignment + return value; + } else { + return chain; + } + } + + var old = $.fn.selectpicker; + $.fn.selectpicker = Plugin; + $.fn.selectpicker.Constructor = Selectpicker; + + // SELECTPICKER NO CONFLICT + // ======================== + $.fn.selectpicker.noConflict = function () { + $.fn.selectpicker = old; + return this; + }; + + // get Bootstrap's keydown event handler for either Bootstrap 4 or Bootstrap 3 + function keydownHandler () { + if (version.major < 5) { + if ($.fn.dropdown) { + // wait to define until function is called in case Bootstrap isn't loaded yet + var bootstrapKeydown = $.fn.dropdown.Constructor._dataApiKeydownHandler || $.fn.dropdown.Constructor.prototype.keydown; + return bootstrapKeydown.apply(this, arguments); + } + } else { + return Dropdown.dataApiKeydownHandler; + } + } + + $(document) + .off('keydown.bs.dropdown.data-api') + .on('keydown.bs.dropdown.data-api', ':not(.bootstrap-select) > [' + Selector.DATA_TOGGLE + ']', keydownHandler) + .on('keydown.bs.dropdown.data-api', ':not(.bootstrap-select) > .dropdown-menu', keydownHandler) + .on('keydown' + EVENT_KEY, '.bootstrap-select [' + Selector.DATA_TOGGLE + '], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input', Selectpicker.prototype.keydown) + .on('focusin.modal', '.bootstrap-select [' + Selector.DATA_TOGGLE + '], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input', function (e) { + e.stopPropagation(); + }); + + // SELECTPICKER DATA-API + // ===================== + document.addEventListener('DOMContentLoaded', function () { + $('.selectpicker').each(function () { + var $selectpicker = $(this); + Plugin.call($selectpicker, $selectpicker.data()); + }); + }); +})(jQuery); diff --git a/data/web/js/build/004-datatables.js b/data/web/js/build/004-datatables.js new file mode 100644 index 0000000000..8c791713f2 --- /dev/null +++ b/data/web/js/build/004-datatables.js @@ -0,0 +1,18781 @@ +/* + * This combined file was created by the DataTables downloader builder: + * https://datatables.net/download + * + * To rebuild or modify this file with the latest versions of the included + * software please visit: + * https://datatables.net/download/#bs5/dt-1.13.1/r-2.4.0/sl-1.5.0 + * + * Included libraries: + * DataTables 1.13.1, Responsive 2.4.0, Select 1.5.0 + */ + +/*! DataTables 1.13.1 + * ยฉ2008-2022 SpryMedia Ltd - datatables.net/license + */ + +/** + * @summary DataTables + * @description Paginate, search and order HTML tables + * @version 1.13.1 + * @author SpryMedia Ltd + * @contact www.datatables.net + * @copyright SpryMedia Ltd. + * + * This source file is free software, available under the following license: + * MIT license - http://datatables.net/license + * + * This source file 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 license files for details. + * + * For details please refer to: http://www.datatables.net + */ + +/*jslint evil: true, undef: true, browser: true */ +/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ + +(function( factory ) { + "use strict"; + + if ( typeof define === 'function' && define.amd ) { + // AMD + define( ['jquery'], function ( $ ) { + return factory( $, window, document ); + } ); + } + else if ( typeof exports === 'object' ) { + // CommonJS + module.exports = function (root, $) { + if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if ( ! $ ) { + $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window + require('jquery') : + require('jquery')( root ); + } + + return factory( $, root, root.document ); + }; + } + else { + // Browser + window.DataTable = factory( jQuery, window, document ); + } +} +(function( $, window, document, undefined ) { + "use strict"; + + + var DataTable = function ( selector, options ) + { + // When creating with `new`, create a new DataTable, returning the API instance + if (this instanceof DataTable) { + return $(selector).DataTable(options); + } + else { + // Argument switching + options = selector; + } + + /** + * Perform a jQuery selector action on the table's TR elements (from the tbody) and + * return the resulting jQuery object. + * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on + * @param {object} [oOpts] Optional parameters for modifying the rows to be included + * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter + * criterion ("applied") or all TR elements (i.e. no filter). + * @param {string} [oOpts.order=current] Order of the TR elements in the processed array. + * Can be either 'current', whereby the current sorting of the table is used, or + * 'original' whereby the original order the data was read into the table is used. + * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page + * ("current") or not ("all"). If 'current' is given, then order is assumed to be + * 'current' and filter is 'applied', regardless of what they might be given as. + * @returns {object} jQuery object, filtered by the given selector. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Highlight every second row + * oTable.$('tr:odd').css('backgroundColor', 'blue'); + * } ); + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Filter to rows with 'Webkit' in them, add a background colour and then + * // remove the filter, thus highlighting the 'Webkit' rows only. + * oTable.fnFilter('Webkit'); + * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue'); + * oTable.fnFilter(''); + * } ); + */ + this.$ = function ( sSelector, oOpts ) + { + return this.api(true).$( sSelector, oOpts ); + }; + + + /** + * Almost identical to $ in operation, but in this case returns the data for the matched + * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes + * rather than any descendants, so the data can be obtained for the row/cell. If matching + * rows are found, the data returned is the original data array/object that was used to + * create the row (or a generated array if from a DOM source). + * + * This method is often useful in-combination with $ where both functions are given the + * same parameters and the array indexes will match identically. + * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on + * @param {object} [oOpts] Optional parameters for modifying the rows to be included + * @param {string} [oOpts.filter=none] Select elements that meet the current filter + * criterion ("applied") or all elements (i.e. no filter). + * @param {string} [oOpts.order=current] Order of the data in the processed array. + * Can be either 'current', whereby the current sorting of the table is used, or + * 'original' whereby the original order the data was read into the table is used. + * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page + * ("current") or not ("all"). If 'current' is given, then order is assumed to be + * 'current' and filter is 'applied', regardless of what they might be given as. + * @returns {array} Data for the matched elements. If any elements, as a result of the + * selector, were not TR, TD or TH elements in the DataTable, they will have a null + * entry in the array. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Get the data from the first row in the table + * var data = oTable._('tr:first'); + * + * // Do something useful with the data + * alert( "First cell is: "+data[0] ); + * } ); + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Filter to 'Webkit' and get all data for + * oTable.fnFilter('Webkit'); + * var data = oTable._('tr', {"search": "applied"}); + * + * // Do something with the data + * alert( data.length+" rows matched the search" ); + * } ); + */ + this._ = function ( sSelector, oOpts ) + { + return this.api(true).rows( sSelector, oOpts ).data(); + }; + + + /** + * Create a DataTables Api instance, with the currently selected tables for + * the Api's context. + * @param {boolean} [traditional=false] Set the API instance's context to be + * only the table referred to by the `DataTable.ext.iApiIndex` option, as was + * used in the API presented by DataTables 1.9- (i.e. the traditional mode), + * or if all tables captured in the jQuery object should be used. + * @return {DataTables.Api} + */ + this.api = function ( traditional ) + { + return traditional ? + new _Api( + _fnSettingsFromNode( this[ _ext.iApiIndex ] ) + ) : + new _Api( this ); + }; + + + /** + * Add a single new row or multiple rows of data to the table. Please note + * that this is suitable for client-side processing only - if you are using + * server-side processing (i.e. "bServerSide": true), then to add data, you + * must add it to the data source, i.e. the server-side, through an Ajax call. + * @param {array|object} data The data to be added to the table. This can be: + *
      + *
    • 1D array of data - add a single row with the data provided
    • + *
    • 2D array of arrays - add multiple rows in a single call
    • + *
    • object - data object when using mData
    • + *
    • array of objects - multiple data objects when using mData
    • + *
    + * @param {bool} [redraw=true] redraw the table or not + * @returns {array} An array of integers, representing the list of indexes in + * aoData ({@link DataTable.models.oSettings}) that have been added to + * the table. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * // Global var for counter + * var giCount = 2; + * + * $(document).ready(function() { + * $('#example').dataTable(); + * } ); + * + * function fnClickAddRow() { + * $('#example').dataTable().fnAddData( [ + * giCount+".1", + * giCount+".2", + * giCount+".3", + * giCount+".4" ] + * ); + * + * giCount++; + * } + */ + this.fnAddData = function( data, redraw ) + { + var api = this.api( true ); + + /* Check if we want to add multiple rows or not */ + var rows = Array.isArray(data) && ( Array.isArray(data[0]) || $.isPlainObject(data[0]) ) ? + api.rows.add( data ) : + api.row.add( data ); + + if ( redraw === undefined || redraw ) { + api.draw(); + } + + return rows.flatten().toArray(); + }; + + + /** + * This function will make DataTables recalculate the column sizes, based on the data + * contained in the table and the sizes applied to the columns (in the DOM, CSS or + * through the sWidth parameter). This can be useful when the width of the table's + * parent element changes (for example a window resize). + * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable( { + * "sScrollY": "200px", + * "bPaginate": false + * } ); + * + * $(window).on('resize', function () { + * oTable.fnAdjustColumnSizing(); + * } ); + * } ); + */ + this.fnAdjustColumnSizing = function ( bRedraw ) + { + var api = this.api( true ).columns.adjust(); + var settings = api.settings()[0]; + var scroll = settings.oScroll; + + if ( bRedraw === undefined || bRedraw ) { + api.draw( false ); + } + else if ( scroll.sX !== "" || scroll.sY !== "" ) { + /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */ + _fnScrollDraw( settings ); + } + }; + + + /** + * Quickly and simply clear a table + * @param {bool} [bRedraw=true] redraw the table or not + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...) + * oTable.fnClearTable(); + * } ); + */ + this.fnClearTable = function( bRedraw ) + { + var api = this.api( true ).clear(); + + if ( bRedraw === undefined || bRedraw ) { + api.draw(); + } + }; + + + /** + * The exact opposite of 'opening' a row, this function will close any rows which + * are currently 'open'. + * @param {node} nTr the table row to 'close' + * @returns {int} 0 on success, or 1 if failed (can't find the row) + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable; + * + * // 'open' an information row when a row is clicked on + * $('#example tbody tr').click( function () { + * if ( oTable.fnIsOpen(this) ) { + * oTable.fnClose( this ); + * } else { + * oTable.fnOpen( this, "Temporary row opened", "info_row" ); + * } + * } ); + * + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnClose = function( nTr ) + { + this.api( true ).row( nTr ).child.hide(); + }; + + + /** + * Remove a row for the table + * @param {mixed} target The index of the row from aoData to be deleted, or + * the TR element you want to delete + * @param {function|null} [callBack] Callback function + * @param {bool} [redraw=true] Redraw the table or not + * @returns {array} The row that was deleted + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Immediately remove the first row + * oTable.fnDeleteRow( 0 ); + * } ); + */ + this.fnDeleteRow = function( target, callback, redraw ) + { + var api = this.api( true ); + var rows = api.rows( target ); + var settings = rows.settings()[0]; + var data = settings.aoData[ rows[0][0] ]; + + rows.remove(); + + if ( callback ) { + callback.call( this, settings, data ); + } + + if ( redraw === undefined || redraw ) { + api.draw(); + } + + return data; + }; + + + /** + * Restore the table to it's original state in the DOM by removing all of DataTables + * enhancements, alterations to the DOM structure of the table and event listeners. + * @param {boolean} [remove=false] Completely remove the table from the DOM + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * // This example is fairly pointless in reality, but shows how fnDestroy can be used + * var oTable = $('#example').dataTable(); + * oTable.fnDestroy(); + * } ); + */ + this.fnDestroy = function ( remove ) + { + this.api( true ).destroy( remove ); + }; + + + /** + * Redraw the table + * @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Re-draw the table - you wouldn't want to do it here, but it's an example :-) + * oTable.fnDraw(); + * } ); + */ + this.fnDraw = function( complete ) + { + // Note that this isn't an exact match to the old call to _fnDraw - it takes + // into account the new data, but can hold position. + this.api( true ).draw( complete ); + }; + + + /** + * Filter the input based on data + * @param {string} sInput String to filter the table on + * @param {int|null} [iColumn] Column to limit filtering to + * @param {bool} [bRegex=false] Treat as regular expression or not + * @param {bool} [bSmart=true] Perform smart filtering or not + * @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es) + * @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false) + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Sometime later - filter... + * oTable.fnFilter( 'test string' ); + * } ); + */ + this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive ) + { + var api = this.api( true ); + + if ( iColumn === null || iColumn === undefined ) { + api.search( sInput, bRegex, bSmart, bCaseInsensitive ); + } + else { + api.column( iColumn ).search( sInput, bRegex, bSmart, bCaseInsensitive ); + } + + api.draw(); + }; + + + /** + * Get the data for the whole table, an individual row or an individual cell based on the + * provided parameters. + * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as + * a TR node then the data source for the whole row will be returned. If given as a + * TD/TH cell node then iCol will be automatically calculated and the data for the + * cell returned. If given as an integer, then this is treated as the aoData internal + * data index for the row (see fnGetPosition) and the data for that row used. + * @param {int} [col] Optional column index that you want the data of. + * @returns {array|object|string} If mRow is undefined, then the data for all rows is + * returned. If mRow is defined, just data for that row, and is iCol is + * defined, only data for the designated cell is returned. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * // Row data + * $(document).ready(function() { + * oTable = $('#example').dataTable(); + * + * oTable.$('tr').click( function () { + * var data = oTable.fnGetData( this ); + * // ... do something with the array / object of data for the row + * } ); + * } ); + * + * @example + * // Individual cell data + * $(document).ready(function() { + * oTable = $('#example').dataTable(); + * + * oTable.$('td').click( function () { + * var sData = oTable.fnGetData( this ); + * alert( 'The cell clicked on had the value of '+sData ); + * } ); + * } ); + */ + this.fnGetData = function( src, col ) + { + var api = this.api( true ); + + if ( src !== undefined ) { + var type = src.nodeName ? src.nodeName.toLowerCase() : ''; + + return col !== undefined || type == 'td' || type == 'th' ? + api.cell( src, col ).data() : + api.row( src ).data() || null; + } + + return api.data().toArray(); + }; + + + /** + * Get an array of the TR nodes that are used in the table's body. Note that you will + * typically want to use the '$' API method in preference to this as it is more + * flexible. + * @param {int} [iRow] Optional row index for the TR element you want + * @returns {array|node} If iRow is undefined, returns an array of all TR elements + * in the table's body, or iRow is defined, just the TR element requested. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Get the nodes from the table + * var nNodes = oTable.fnGetNodes( ); + * } ); + */ + this.fnGetNodes = function( iRow ) + { + var api = this.api( true ); + + return iRow !== undefined ? + api.row( iRow ).node() : + api.rows().nodes().flatten().toArray(); + }; + + + /** + * Get the array indexes of a particular cell from it's DOM element + * and column index including hidden columns + * @param {node} node this can either be a TR, TD or TH in the table's body + * @returns {int} If nNode is given as a TR, then a single index is returned, or + * if given as a cell, an array of [row index, column index (visible), + * column index (all)] is given. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * $('#example tbody td').click( function () { + * // Get the position of the current data from the node + * var aPos = oTable.fnGetPosition( this ); + * + * // Get the data array for this row + * var aData = oTable.fnGetData( aPos[0] ); + * + * // Update the data array and return the value + * aData[ aPos[1] ] = 'clicked'; + * this.innerHTML = 'clicked'; + * } ); + * + * // Init DataTables + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnGetPosition = function( node ) + { + var api = this.api( true ); + var nodeName = node.nodeName.toUpperCase(); + + if ( nodeName == 'TR' ) { + return api.row( node ).index(); + } + else if ( nodeName == 'TD' || nodeName == 'TH' ) { + var cell = api.cell( node ).index(); + + return [ + cell.row, + cell.columnVisible, + cell.column + ]; + } + return null; + }; + + + /** + * Check to see if a row is 'open' or not. + * @param {node} nTr the table row to check + * @returns {boolean} true if the row is currently open, false otherwise + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable; + * + * // 'open' an information row when a row is clicked on + * $('#example tbody tr').click( function () { + * if ( oTable.fnIsOpen(this) ) { + * oTable.fnClose( this ); + * } else { + * oTable.fnOpen( this, "Temporary row opened", "info_row" ); + * } + * } ); + * + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnIsOpen = function( nTr ) + { + return this.api( true ).row( nTr ).child.isShown(); + }; + + + /** + * This function will place a new row directly after a row which is currently + * on display on the page, with the HTML contents that is passed into the + * function. This can be used, for example, to ask for confirmation that a + * particular record should be deleted. + * @param {node} nTr The table row to 'open' + * @param {string|node|jQuery} mHtml The HTML to put into the row + * @param {string} sClass Class to give the new TD cell + * @returns {node} The row opened. Note that if the table row passed in as the + * first parameter, is not found in the table, this method will silently + * return. + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable; + * + * // 'open' an information row when a row is clicked on + * $('#example tbody tr').click( function () { + * if ( oTable.fnIsOpen(this) ) { + * oTable.fnClose( this ); + * } else { + * oTable.fnOpen( this, "Temporary row opened", "info_row" ); + * } + * } ); + * + * oTable = $('#example').dataTable(); + * } ); + */ + this.fnOpen = function( nTr, mHtml, sClass ) + { + return this.api( true ) + .row( nTr ) + .child( mHtml, sClass ) + .show() + .child()[0]; + }; + + + /** + * Change the pagination - provides the internal logic for pagination in a simple API + * function. With this function you can have a DataTables table go to the next, + * previous, first or last pages. + * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last" + * or page number to jump to (integer), note that page 0 is the first page. + * @param {bool} [bRedraw=true] Redraw the table or not + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * oTable.fnPageChange( 'next' ); + * } ); + */ + this.fnPageChange = function ( mAction, bRedraw ) + { + var api = this.api( true ).page( mAction ); + + if ( bRedraw === undefined || bRedraw ) { + api.draw(false); + } + }; + + + /** + * Show a particular column + * @param {int} iCol The column whose display should be changed + * @param {bool} bShow Show (true) or hide (false) the column + * @param {bool} [bRedraw=true] Redraw the table or not + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Hide the second column after initialisation + * oTable.fnSetColumnVis( 1, false ); + * } ); + */ + this.fnSetColumnVis = function ( iCol, bShow, bRedraw ) + { + var api = this.api( true ).column( iCol ).visible( bShow ); + + if ( bRedraw === undefined || bRedraw ) { + api.columns.adjust().draw(); + } + }; + + + /** + * Get the settings for a particular table for external manipulation + * @returns {object} DataTables settings object. See + * {@link DataTable.models.oSettings} + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * var oSettings = oTable.fnSettings(); + * + * // Show an example parameter from the settings + * alert( oSettings._iDisplayStart ); + * } ); + */ + this.fnSettings = function() + { + return _fnSettingsFromNode( this[_ext.iApiIndex] ); + }; + + + /** + * Sort the table by a particular column + * @param {int} iCol the data index to sort on. Note that this will not match the + * 'display index' if you have hidden data entries + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Sort immediately with columns 0 and 1 + * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] ); + * } ); + */ + this.fnSort = function( aaSort ) + { + this.api( true ).order( aaSort ).draw(); + }; + + + /** + * Attach a sort listener to an element for a given column + * @param {node} nNode the element to attach the sort listener to + * @param {int} iColumn the column that a click on this node will sort on + * @param {function} [fnCallback] callback function when sort is run + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Sort on column 1, when 'sorter' is clicked on + * oTable.fnSortListener( document.getElementById('sorter'), 1 ); + * } ); + */ + this.fnSortListener = function( nNode, iColumn, fnCallback ) + { + this.api( true ).order.listener( nNode, iColumn, fnCallback ); + }; + + + /** + * Update a table cell or row - this method will accept either a single value to + * update the cell with, an array of values with one element for each column or + * an object in the same format as the original data source. The function is + * self-referencing in order to make the multi column updates easier. + * @param {object|array|string} mData Data to update the cell/row with + * @param {node|int} mRow TR element you want to update or the aoData index + * @param {int} [iColumn] The column to update, give as null or undefined to + * update a whole row. + * @param {bool} [bRedraw=true] Redraw the table or not + * @param {bool} [bAction=true] Perform pre-draw actions or not + * @returns {int} 0 on success, 1 on error + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell + * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row + * } ); + */ + this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction ) + { + var api = this.api( true ); + + if ( iColumn === undefined || iColumn === null ) { + api.row( mRow ).data( mData ); + } + else { + api.cell( mRow, iColumn ).data( mData ); + } + + if ( bAction === undefined || bAction ) { + api.columns.adjust(); + } + + if ( bRedraw === undefined || bRedraw ) { + api.draw(); + } + return 0; + }; + + + /** + * Provide a common method for plug-ins to check the version of DataTables being used, in order + * to ensure compatibility. + * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the + * formats "X" and "X.Y" are also acceptable. + * @returns {boolean} true if this version of DataTables is greater or equal to the required + * version, or false if this version of DataTales is not suitable + * @method + * @dtopt API + * @deprecated Since v1.10 + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * alert( oTable.fnVersionCheck( '1.9.0' ) ); + * } ); + */ + this.fnVersionCheck = _ext.fnVersionCheck; + + + var _that = this; + var emptyInit = options === undefined; + var len = this.length; + + if ( emptyInit ) { + options = {}; + } + + this.oApi = this.internal = _ext.internal; + + // Extend with old style plug-in API methods + for ( var fn in DataTable.ext.internal ) { + if ( fn ) { + this[fn] = _fnExternApiFunc(fn); + } + } + + this.each(function() { + // For each initialisation we want to give it a clean initialisation + // object that can be bashed around + var o = {}; + var oInit = len > 1 ? // optimisation for single table case + _fnExtend( o, options, true ) : + options; + + /*global oInit,_that,emptyInit*/ + var i=0, iLen, j, jLen, k, kLen; + var sId = this.getAttribute( 'id' ); + var bInitHandedOff = false; + var defaults = DataTable.defaults; + var $this = $(this); + + + /* Sanity check */ + if ( this.nodeName.toLowerCase() != 'table' ) + { + _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); + return; + } + + /* Backwards compatibility for the defaults */ + _fnCompatOpts( defaults ); + _fnCompatCols( defaults.column ); + + /* Convert the camel-case defaults to Hungarian */ + _fnCamelToHungarian( defaults, defaults, true ); + _fnCamelToHungarian( defaults.column, defaults.column, true ); + + /* Setting up the initialisation object */ + _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ), true ); + + + + /* Check to see if we are re-initialising a table */ + var allSettings = DataTable.settings; + for ( i=0, iLen=allSettings.length ; i').appendTo($this); + } + oSettings.nTHead = thead[0]; + + var tbody = $this.children('tbody'); + if ( tbody.length === 0 ) { + tbody = $('').insertAfter(thead); + } + oSettings.nTBody = tbody[0]; + + var tfoot = $this.children('tfoot'); + if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") ) { + // If we are a scrolling table, and no footer has been given, then we need to create + // a tfoot element for the caption element to be appended to + tfoot = $('').appendTo($this); + } + + if ( tfoot.length === 0 || tfoot.children().length === 0 ) { + $this.addClass( oClasses.sNoFooter ); + } + else if ( tfoot.length > 0 ) { + oSettings.nTFoot = tfoot[0]; + _fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot ); + } + + /* Check if there is data passing into the constructor */ + if ( oInit.aaData ) { + for ( i=0 ; i/g; + + // This is not strict ISO8601 - Date.parse() is quite lax, although + // implementations differ between browsers. + var _re_date = /^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/; + + // Escape regular expression special characters + var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); + + // http://en.wikipedia.org/wiki/Foreign_exchange_market + // - \u20BD - Russian ruble. + // - \u20a9 - South Korean Won + // - \u20BA - Turkish Lira + // - \u20B9 - Indian Rupee + // - R - Brazil (R$) and South Africa + // - fr - Swiss Franc + // - kr - Swedish krona, Norwegian krone and Danish krone + // - \u2009 is thin space and \u202F is narrow no-break space, both used in many + // - ษƒ - Bitcoin + // - ฮž - Ethereum + // standards as thousands separators. + var _re_formatted_numeric = /['\u00A0,$ยฃโ‚ฌยฅ%\u2009\u202F\u20BD\u20a9\u20BArfkษƒฮž]/gi; + + + var _empty = function ( d ) { + return !d || d === true || d === '-' ? true : false; + }; + + + var _intVal = function ( s ) { + var integer = parseInt( s, 10 ); + return !isNaN(integer) && isFinite(s) ? integer : null; + }; + + // Convert from a formatted number with characters other than `.` as the + // decimal place, to a Javascript number + var _numToDecimal = function ( num, decimalPoint ) { + // Cache created regular expressions for speed as this function is called often + if ( ! _re_dic[ decimalPoint ] ) { + _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' ); + } + return typeof num === 'string' && decimalPoint !== '.' ? + num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) : + num; + }; + + + var _isNumber = function ( d, decimalPoint, formatted ) { + var strType = typeof d === 'string'; + + // If empty return immediately so there must be a number if it is a + // formatted string (this stops the string "k", or "kr", etc being detected + // as a formatted number for currency + if ( _empty( d ) ) { + return true; + } + + if ( decimalPoint && strType ) { + d = _numToDecimal( d, decimalPoint ); + } + + if ( formatted && strType ) { + d = d.replace( _re_formatted_numeric, '' ); + } + + return !isNaN( parseFloat(d) ) && isFinite( d ); + }; + + + // A string without HTML in it can be considered to be HTML still + var _isHtml = function ( d ) { + return _empty( d ) || typeof d === 'string'; + }; + + + var _htmlNumeric = function ( d, decimalPoint, formatted ) { + if ( _empty( d ) ) { + return true; + } + + var html = _isHtml( d ); + return ! html ? + null : + _isNumber( _stripHtml( d ), decimalPoint, formatted ) ? + true : + null; + }; + + + var _pluck = function ( a, prop, prop2 ) { + var out = []; + var i=0, ien=a.length; + + // Could have the test in the loop for slightly smaller code, but speed + // is essential here + if ( prop2 !== undefined ) { + for ( ; i') + .css( { + position: 'fixed', + top: 0, + left: $(window).scrollLeft()*-1, // allow for scrolling + height: 1, + width: 1, + overflow: 'hidden' + } ) + .append( + $('
    ') + .css( { + position: 'absolute', + top: 1, + left: 1, + width: 100, + overflow: 'scroll' + } ) + .append( + $('
    ') + .css( { + width: '100%', + height: 10 + } ) + ) + ) + .appendTo( 'body' ); + + var outer = n.children(); + var inner = outer.children(); + + // Numbers below, in order, are: + // inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth + // + // IE6 XP: 100 100 100 83 + // IE7 Vista: 100 100 100 83 + // IE 8+ Windows: 83 83 100 83 + // Evergreen Windows: 83 83 100 83 + // Evergreen Mac with scrollbars: 85 85 100 85 + // Evergreen Mac without scrollbars: 100 100 100 100 + + // Get scrollbar width + browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth; + + // IE6/7 will oversize a width 100% element inside a scrolling element, to + // include the width of the scrollbar, while other browsers ensure the inner + // element is contained without forcing scrolling + browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100; + + // In rtl text layout, some browsers (most, but not all) will place the + // scrollbar on the left, rather than the right. + browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1; + + // IE8- don't provide height and width for getBoundingClientRect + browser.bBounding = n[0].getBoundingClientRect().width ? true : false; + + n.remove(); + } + + $.extend( settings.oBrowser, DataTable.__browser ); + settings.oScroll.iBarWidth = DataTable.__browser.barWidth; + } + + + /** + * Array.prototype reduce[Right] method, used for browsers which don't support + * JS 1.6. Done this way to reduce code size, since we iterate either way + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnReduce ( that, fn, init, start, end, inc ) + { + var + i = start, + value, + isSet = false; + + if ( init !== undefined ) { + value = init; + isSet = true; + } + + while ( i !== end ) { + if ( ! that.hasOwnProperty(i) ) { + continue; + } + + value = isSet ? + fn( value, that[i], i, that ) : + that[i]; + + isSet = true; + i += inc; + } + + return value; + } + + /** + * Add a column to the list used for the table with default values + * @param {object} oSettings dataTables settings object + * @param {node} nTh The th element for this column + * @memberof DataTable#oApi + */ + function _fnAddColumn( oSettings, nTh ) + { + // Add column to aoColumns array + var oDefaults = DataTable.defaults.column; + var iCol = oSettings.aoColumns.length; + var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, { + "nTh": nTh ? nTh : document.createElement('th'), + "sTitle": oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '', + "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], + "mData": oDefaults.mData ? oDefaults.mData : iCol, + idx: iCol + } ); + oSettings.aoColumns.push( oCol ); + + // Add search object for column specific search. Note that the `searchCols[ iCol ]` + // passed into extend can be undefined. This allows the user to give a default + // with only some of the parameters defined, and also not give a default + var searchCols = oSettings.aoPreSearchCols; + searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] ); + + // Use the default column options function to initialise classes etc + _fnColumnOptions( oSettings, iCol, $(nTh).data() ); + } + + + /** + * Apply options for a column + * @param {object} oSettings dataTables settings object + * @param {int} iCol column index to consider + * @param {object} oOptions object with sType, bVisible and bSearchable etc + * @memberof DataTable#oApi + */ + function _fnColumnOptions( oSettings, iCol, oOptions ) + { + var oCol = oSettings.aoColumns[ iCol ]; + var oClasses = oSettings.oClasses; + var th = $(oCol.nTh); + + // Try to get width information from the DOM. We can't get it from CSS + // as we'd need to parse the CSS stylesheet. `width` option can override + if ( ! oCol.sWidthOrig ) { + // Width attribute + oCol.sWidthOrig = th.attr('width') || null; + + // Style attribute + var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/); + if ( t ) { + oCol.sWidthOrig = t[1]; + } + } + + /* User specified column options */ + if ( oOptions !== undefined && oOptions !== null ) + { + // Backwards compatibility + _fnCompatCols( oOptions ); + + // Map camel case parameters to their Hungarian counterparts + _fnCamelToHungarian( DataTable.defaults.column, oOptions, true ); + + /* Backwards compatibility for mDataProp */ + if ( oOptions.mDataProp !== undefined && !oOptions.mData ) + { + oOptions.mData = oOptions.mDataProp; + } + + if ( oOptions.sType ) + { + oCol._sManualType = oOptions.sType; + } + + // `class` is a reserved word in Javascript, so we need to provide + // the ability to use a valid name for the camel case input + if ( oOptions.className && ! oOptions.sClass ) + { + oOptions.sClass = oOptions.className; + } + if ( oOptions.sClass ) { + th.addClass( oOptions.sClass ); + } + + var origClass = oCol.sClass; + + $.extend( oCol, oOptions ); + _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" ); + + // Merge class from previously defined classes with this one, rather than just + // overwriting it in the extend above + if (origClass !== oCol.sClass) { + oCol.sClass = origClass + ' ' + oCol.sClass; + } + + /* iDataSort to be applied (backwards compatibility), but aDataSort will take + * priority if defined + */ + if ( oOptions.iDataSort !== undefined ) + { + oCol.aDataSort = [ oOptions.iDataSort ]; + } + _fnMap( oCol, oOptions, "aDataSort" ); + } + + /* Cache the data get and set functions for speed */ + var mDataSrc = oCol.mData; + var mData = _fnGetObjectDataFn( mDataSrc ); + var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null; + + var attrTest = function( src ) { + return typeof src === 'string' && src.indexOf('@') !== -1; + }; + oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && ( + attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter) + ); + oCol._setter = null; + + oCol.fnGetData = function (rowData, type, meta) { + var innerData = mData( rowData, type, undefined, meta ); + + return mRender && type ? + mRender( innerData, type, rowData, meta ) : + innerData; + }; + oCol.fnSetData = function ( rowData, val, meta ) { + return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta ); + }; + + // Indicate if DataTables should read DOM data as an object or array + // Used in _fnGetRowElements + if ( typeof mDataSrc !== 'number' ) { + oSettings._rowReadObject = true; + } + + /* Feature sorting overrides column specific when off */ + if ( !oSettings.oFeatures.bSort ) + { + oCol.bSortable = false; + th.addClass( oClasses.sSortableNone ); // Have to add class here as order event isn't called + } + + /* Check that the class assignment is correct for sorting */ + var bAsc = $.inArray('asc', oCol.asSorting) !== -1; + var bDesc = $.inArray('desc', oCol.asSorting) !== -1; + if ( !oCol.bSortable || (!bAsc && !bDesc) ) + { + oCol.sSortingClass = oClasses.sSortableNone; + oCol.sSortingClassJUI = ""; + } + else if ( bAsc && !bDesc ) + { + oCol.sSortingClass = oClasses.sSortableAsc; + oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed; + } + else if ( !bAsc && bDesc ) + { + oCol.sSortingClass = oClasses.sSortableDesc; + oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed; + } + else + { + oCol.sSortingClass = oClasses.sSortable; + oCol.sSortingClassJUI = oClasses.sSortJUI; + } + } + + + /** + * Adjust the table column widths for new data. Note: you would probably want to + * do a redraw after calling this function! + * @param {object} settings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnAdjustColumnSizing ( settings ) + { + /* Not interested in doing column width calculation if auto-width is disabled */ + if ( settings.oFeatures.bAutoWidth !== false ) + { + var columns = settings.aoColumns; + + _fnCalculateColumnWidths( settings ); + for ( var i=0 , iLen=columns.length ; i